This example project demonstrates basic I²C communication using the SERCOM peripheral on a PIC32CM JH00 device. The application configures the PIC® MCU as an I²C Host and communicates with the MCP23008 I/O expander on the Curiosity Nano Explorer Board to drive LEDs in a rotating pattern. Pressing and holding the onboard switch (SW0) changes the LED sequence.
While this project uses a PIC32CM JH00 device, the same initialization and communication principles apply to other PIC32CM M0+ devices that support SERCOM-based I²C.
- MPLAB® X IDE 6.25.0 or newer
- MPLAB® XC32 4.60 or newer
- MPLAB® Code Configurator (MCC) v5.6.2
- MPLAB® Harmony v3 v5.8.2
This example project was developed in MPLAB X IDE and can also be opened in VS Code® using the MPLAB Extension Pack. Follow the provided instructions here to import and run the project in VS Code®.
For a step-by-step walkthrough on importing the project, watch the video: Importing an MPLAB® X Project into Microsoft® VS Code®
- Getting Started: Serial Communication
- Arm® Cortex®-Based Microcontrollers (MCUs)
- MCP23008 I2C I/O-expander
I²C communication uses a simple two-wire bus that supports multiple devices. The SDA (data) line carries information, while the SCL (clock) line provides synchronization. Unlike UART, which connects devices point-to-point, I²C allows one host to communicate with many client devices on the same bus, with each device identified by a unique address.
In this configuration, the host generates the clock signal, and all devices synchronize their communication to it. This makes I²C an efficient way to connect multiple devices using only two pins.
In this example, the concept is applied using the SERCOM2 peripheral of a PIC32CM JH00 device configured in I²C Host mode. The I²C lines, SDA and SCL, connect to the Curiosity Nano Explorer Board’s onboard MCP23008 I/O expander.
The PIC32CM JH00 acts as I²C Host, communicating with the MCP23008 client device at its unique I²C address. The host sends commands to update the LEDs in a sequence, and pressing the SW0 push button on the Curiosity Nano changes the active pattern.
- SERCOM2 is initialized in I²C Host mode.
- SysTick introduces a delay between I²C writes to pace LED updates; without it, data would be sent too quickly and the LEDs would appear blurred or constantly on.
- Data is written to the MCP23008 registers to control the LEDs.
- Pressing the Curiosity Nano SW0 push button toggles between two LED patterns.
Once programmed, the device communicates with the MCP23008 over the I²C bus to drive the Curiosity Nano Explorer Board LEDs. The results are visible directly on the LEDs, and the I²C signals can also be validated with a logic analyzer by probing SDA and SCL.
- Connect the Debug USB port of the PIC32CM JH00 Curiosity Nano to a PC using a Micro-USB to USB 2.0 cable.
- If not already installed, download and install MPLAB X IDE version 6.25 or newer.
- If not already installed, download and install the XC32 C-Compiler version 4.60 or newer.
- Clone or download this project from GitHub to the local machine.
- If downloaded as a
.zip
file, extract the files to a location preferably as close as possible to the root directory. - In MPLAB X IDE, go to File → Open Project. Navigate to the extracted location and select the following project file:
- Click the Make and Program Device button in the MPLAB X IDE toolbar to program the device. Then, verify that the device is successfully programmed.
Logic Analyzer: The capture shows a single I²C write to the MCP2300. The MCU selects the MCP23008, points to the OLAT register, and writes a new output pattern. Each subsequent write updates the LEDs, creating the visible moving-light effect.
- 0x25 → the MCP23008 device address (7-bit) with the Write bit.
- 0x0A → the OLAT (Output Latch) register, which controls the LED outputs.
- 0xDF → Updates OLAT to the new LED pattern (
0xDF = 1101 1111
), clearing one bit and leaving the others high.
Board Output: On the hardware, the LEDs update in visible steps:
RotateLED()
→ shifts a lit LED across the row, then wraps around.RotateSingleLED()
→ ensures only one LED is on at a time, scanning across the row.
Note: Playback is accelerated for demonstration purposes.
This example project is already provided as a preconfigured MPLAB X project. Start by opening the project in MPLAB X IDE. Once the project is loaded, launch the MPLAB® Code Configurator (MCC) in Harmony to review the configuration.
For detailed instructions on how to create and set up a new MPLAB X project and open MCC Harmony, refer to the this Microchip online reference here.
When opened in MCC Harmony, the Project Graph provides a visual overview of the project, showing the active components and their interactions.
With the Project Graph open, review the configuration step by step. Use the following checklist to confirm each setting before proceeding:
Open the Clock Configurator from the Plugins section in the Project Graph to verify the system clock is set to 48 MHz.
To check the system clock, go to Project Graph → Plugins → Clock Configurator.
SysTick is a built-in timer that allows the application to create precise delays without relying on software loops. In this lab, it is used to space out I²C writes so the LED updates occur at a controlled rate. Without it, data would be sent too quickly, making the LEDs appear constantly on or causing the pattern to blur.
To enable SysTick:
2. In the Configuration Options panel, expand Cortex M0+ Configuration and Enable the SysTick option.
The PIC32CM JH00 Curiosity Nano includes multiple SERCOM peripherals that can be configured for UART, SPI, or I²C communication. For this lab, SERCOM2 is used as the I²C interface, since it connects directly to the Curiosity Nano Explorer Board I²C bus and the onboard MCP23008 I/O expander.
This mapping is shown in the PIC32CM JH00 Curiosity Nano Hardware User Guide, and similar guides are available for all Curiosity Nano products.
To add and configure SERCOM2:
- Once added, select SERCOM2 in the Project Graph. In the Configuration Options panel, apply the following settings:
After configuring SERCOM2, the SDA and SCL pins must be assigned to the correct I/O lines connected to the Curiosity Nano Explorer Board’s I²C bus. This enables the microcontroller to communicate with the onboard MCP23008 I/O expander over I²C.
According to the Curiosity Nano hardware user guide mentioned previously, and Curiosity Nano Explorer documentation:
- PA12 is used for SDA: Carries the data being transferred between the host (MCU) and the client device (MCP23008). Both read and write operations use this single line for data.
- PA13 is used for SCL: Generated by the host (MCU) to synchronize all data transfers on the SDA line. Each bit of data is valid only when clocked by SCL.
To assign the pins:
- In the Pin Settings tab. Locate PA16 (Pin #35) and PA17 (Pin #36) in the Pin Table.
SW0 is the on-board push button on the PIC32CM JH00 Curiosity Nano, connected to pin PB16. In this lab, it is used to change the LED pattern on the MCP23008 I/O expander when pressed. This pin must be assigned as a digital input in MCC to detect the switch state in the application.
This mapping is shown in the PIC32CM JH00 Curiosity Nano Hardware User Guide, and similar guides are available for all Curiosity Nano products.
Configure pin LED0 (PB16) as Input
- In the Pin Settings tab, locate PB16 (Pin #39) in the Pin Table.
With I²C, SysTick, and pin mappings configured, the final step is to generate the code. MCC creates the initialization files, peripheral drivers, and APIs, preparing the project for I²C communication and timed execution.
In I²C communication, each client device on the bus is identified by a unique 7-bit address. The MCP23008 I/O expander on the Curiosity Nano Explorer Board is assigned the address 0x25, which must be included in every read and write operation. If this address is incorrect or mismatched, the MCU will not be able to communicate with the expander.
The I²C address (0x25) is printed on the Curiosity Nano Explorer Board, above the LEDs, and is also documented in Section 5 of the Curiostiy Nano Explorer User Guide.
In addition to the device address, the MCP23008 uses register addresses to select specific functions, such as configuring pin direction, writing output values, or enabling pull-ups. These register values are written as first byte of an I²C transaction, followed by the data to be stored.
-
IODIR (0x00) – Configures each pin as input (1) or output (0).
Used to set the expander’s LED pins as outputs. -
IPOL (0x01) – Inverts the input logic of each pin when reading.
Optional; used if active-low logic is preferred. -
GPPU (0x06) – Enables internal pull-up resistors on input pins.
Prevents floating inputs, useful for reading switches. -
GPIO (0x09) – Reads the current state of all I/O pins.
Used to check input values, such as the switch state. -
OLAT (0x0A) – Holds the output state; writing here updates the output pins.
Used to control the LEDs by setting their on/off state.
Register information is based on Table 1-2 from the MCP23008 datasheet, Section 1.3.
At the start of the application, a set of #define
statements assigns the I²C address of the MCP23008 and the register addresses used in communication. Using symbolic names instead of raw hex values improves readability and makes the code easier to maintain:
#define MCP23008_I2C_ADDRESS 0x25 // MCP23008 I2C address (adjust if necessary)
#define MCP23008_IPOL_REG 0x01 // I/O Polarity Register
#define MCP23008_IODIR_REG 0x00 // I/O Direction Register
#define MCP23008_GPIO_REG 0x09 // GPIO Register
#define MCP23008_GPPU_REG 0x06 // GPIO Pull-up Register
#define MCP23008_OLAT_REG 0x0A // Output Latch Register
The key functions form the core of the application’s logic.
MCP23008_Write()
provides a simple way to send commands and data to the I/O expander over I²C.
void MCP23008_Write(uint8_t reg, uint8_t data)
{
uint8_t buffer[2] = { reg, data };
SERCOM2_I2C_Write(MCP23008_I2C_ADDRESS, buffer, 2);
SYSTICK_DelayMs(500); // Pace I²C writes
}
RotateLED()
uses that helper to cycle through the LEDs in sequence, creating a moving light effect.
void RotateLED(void)
{
MCP23008_Write(MCP23008_OLAT_REG, rotate_reg);
rotate_reg <<= 1;
if (rotate_reg == 0x00) { rotate_reg = 0x01; }
}
RotateSingleLED()
ensures that only one LED is lit at a time, producing a clean “scanning” effect.
void RotateSingleLED(void)
{
// Example structure
MCP23008_Write(MCP23008_OLAT_REG, rotate_reg);
rotate_reg <<= 1;
if (rotate_reg == 0x00) { rotate_reg = 0x01; }
}
Together, these functions demonstrate how user-defined code can build on top of MCC-generated APIs to control external hardware devices.
In the main() function, the system and SysTick timer are initialized, and the MCP23008 is configured so its pins act as outputs. The SW0 push button on the Curiosity Nano toggles between the two LED modes.
SYS_Initialize(NULL);
SYSTICK_TimerStart();
// Configure MCP23008: all pins as outputs, inverted polarity
MCP23008_Write(MCP23008_IODIR_REG, 0x00);
MCP23008_Write(MCP23008_IPOL_REG, 0xFF);
SysTick delays are used in main() (e.g., for switch debounce) and/or within the pattern functions to pace I²C writes and LED updates. Avoid duplicating delays in multiple places unless the extra delay is intentional.
SYSTICK_TimerStart()
– starts the SysTick timer so delay functions are available.SYSTICK_DelayMs(x)
– creates a blocking delay for the specified number of milliseconds.SERCOM2_I2C_Write()
– writes a buffer of data to the MCP23008 client device over the I²C bus.
The complete application ties everything together in main()
. System initialization and MCP23008 configuration are performed once, and the application loop continuously checks the SW0 pushbutton to toggle between LED patterns.
int main(void) {
// Initialization
SYS_Initialize(NULL);
SYSTICK_TimerStart();
// Configure MCP23008: all pins as outputs, inverted polarity
MCP23008_Write(MCP23008_IODIR_REG, 0x00);
MCP23008_Write(MCP23008_IPOL_REG, 0xFF);
while (1) {
// Toggle mode when SW0 is pressed
if (SW0_Get() == 0) {
Flag = !Flag;
SYSTICK_DelayMs(100); // debounce
}
// Run selected LED pattern
if (Flag) {
RotateLED();
} else {
RotateSingleLED();
}
}
return (EXIT_FAILURE);
}
With this, the README shows the key building blocks (MCP23008_Write()
, RotateLED()
, RotateSingleLED()
) and then demonstrates how they come together inside main(). The full source code in the repository contains the complete implementation.
This project highlights how the PIC32CM JH00 uses its SERCOM peripheral in I²C mode to communicate with an external MCP23008 I/O expander. It demonstrates a simple and effective way to extend the device’s I/O capabilities and control external hardware with minimal code.
- Getting Started with Blink LED on PIC32CM M0+ Devices
- Getting Started with Analog-to-Digital Converters (ADC) on PIC32CM M0+ Devices
- Getting Started with QTouch® Button and Peripheral Touch Controller on PIC32CM M0+ Devices
- Getting Started with SERCOM (UART) Communication on PIC32CM M0+ Devices
- Getting Started with SERCOM (SPI) Communication on PIC32CM M0+ Devices