STM32 Hardware I2C Peripheral Configuration and Usage
STM32 microcontrollers integrate dedicated I2C hardware peripherals that offload timing-critical tasks—including clock generation, START/STOP condition handling, ACK/NAK management, and byte-level data transfer—from the CPU. This enables reliable, low-overhead communication with I2C-compatible sensors and peripherals.
Key Capabilities
- Dual-role operation: supports both master and slave modes
- Address support: 7-bit and 10-bit addressing schemes
- Configurable speed modes: standard mode (≤100 kHz) and fast mode (≤400 kHz)
- Optional DMA integration for zero-CPU-transfer bulk operations
- Programmable PEC (Packet Error Checking) generation or verification
- SMBus 2.0 protocol compatibility
On the STM32F103C8T6, two independent I2C interface are available: I2C1 and I2C2.
Operational Modes
The peripheral operates in one of four mutually exclusive roles:
- Master transmitter
- Master receiver
- Slave transmitter
- Slave receiver
Note: Both SDA and SCL pins are bidirectional. In slave mode, the SCL line is driven externally by the active master; the peripheral synchronizes its internal logic to that clock.
State Machine Events (Master Transmitter)
Event flags (e.g., EV5–EV9) are asserted when corresponding conditions occur and ITEVFEN is enabled. Clearing them typically involves reading status registers or writing to data registers.
- EV5: SB flag set → read SR1, then write target slave address to DR
- EV6: ADDR flag set → read SR1, then read SR2
- EV8_1: TxE set, shift register empty, DR empty → write first data byte to DR
- EV8: TxE set, shift register busy, DR empty → write next data byte to DR
- EV8_2: TxE and BTF both set → issue STOP via
I2C_GenerateSTOP() - EV9: ADDR10 set (10-bit addressing) → read SR1, then write second address byte to DR
State Machine Events (Master Receiver)
- EV5: SB set → read SR1, then write slave adress to DR
- EV6: ADDR set → read SR1, then read SR2; for 10-bit reads, set
START=1in CR2 afterward - EV6_1: Implicit state after EV6 for single-byte reads → disable ACK and assert STOP before reading DR
- EV7: RxNE set → read DR to clear
- EV7_1: RxNE set, final byte expectde → disable ACK and request STOP before reading DR
- EV9: ADDR10 set → read SR1, then write second address byte to DR
Initialization Example (MPU6050 Interface)
void init_i2c2_for_mpu6050(void) {
// Enable clocks
RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOB, ENABLE);
// Configure PB10 (SCL) and PB11 (SDA) as open-drain alternate function
GPIO_InitTypeDef gpio_cfg;
gpio_cfg.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
gpio_cfg.GPIO_Mode = GPIO_Mode_AF_OD;
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_cfg);
// Configure I2C2 peripheral
I2C_InitTypeDef i2c_cfg;
i2c_cfg.I2C_Mode = I2C_Mode_I2C;
i2c_cfg.I2C_ClockSpeed = 50000;
i2c_cfg.I2C_DutyCycle = I2C_DutyCycle_2;
i2c_cfg.I2C_OwnAddress1 = 0x00;
i2c_cfg.I2C_Ack = I2C_Ack_Enable;
i2c_cfg.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C2, &i2c_cfg);
// Enable peripheral
I2C_Cmd(I2C2, ENABLE);
// Initialize MPU6050 device registers
i2c_write_byte(MPU6050_ADDR, MPU6050_REG_PWR_MGMT_1, 0x01);
i2c_write_byte(MPU6050_ADDR, MPU6050_REG_PWR_MGMT_2, 0x00);
i2c_write_byte(MPU6050_ADDR, MPU6050_REG_SMPLRT_DIV, 0x09);
i2c_write_byte(MPU6050_ADDR, MPU6050_REG_CONFIG, 0x06);
i2c_write_byte(MPU6050_ADDR, MPU6050_REG_GYRO_CFG, 0x18);
i2c_write_byte(MPU6050_ADDR, MPU6050_REG_ACCEL_CFG, 0x18);
}
Write Transaction Implementation
void i2c_write_byte(uint8_t dev_addr, uint8_t reg_addr, uint8_t value) {
I2C_GenerateSTART(I2C2, ENABLE);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, dev_addr, I2C_Direction_Transmitter);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, reg_addr);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2, value);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2, ENABLE);
}
Read Transaction Implementation
uint8_t i2c_read_byte(uint8_t dev_addr, uint8_t reg_addr) {
// First phase: send register address
I2C_GenerateSTART(I2C2, ENABLE);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, dev_addr, I2C_Direction_Transmitter);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, reg_addr);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
// Second phase: restart and read
I2C_GenerateSTART(I2C2, ENABLE);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, dev_addr, I2C_Direction_Receiver);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
// Prepare for final byte: disable ACK and request STOP
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
wait_i2c_event(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
uint8_t result = I2C_ReceiveData(I2C2);
// Restore ACK for future multi-byte reads
I2C_AcknowledgeConfig(I2C2, ENABLE);
return result;
}