Interfacing I2C Sensors with STM32: A Practical Developer Guide for Embedded IoT Systems
Key Takeaway
I2C (Inter-Integrated Circuit) remains the backbone of embedded sensor networks in IoT devices. With STM32 microcontrollers offering robust I2C hardware support, developers can reliably interface temperature, humidity, accelerometer, and pressure sensors using standard protocols. This guide covers initialization, addressing, data acquisition, and troubleshooting techniques for production-grade embedded IoT systems deployed across industrial automation environments in India and globally.
Table of Contents
1. I2C Bus Fundamentals
I2C is a two-wire serial protocol developed by Philips (now NXP) in the 1980s. It uses a serial clock line (SCL) and a serial data line (SDA), both open-drain with pull-up resistors. The bus supports multi-master, multi-slave communication with 7-bit or 10-bit addressing. Devices on the bus are addressed by a unique address, with the master initiating communication by sending a START condition, followed by the slave address and read/write bit.
| Parameter | Value |
|---|---|
| Clock Speed | Standard: 100 kHz, Fast: 400 kHz, Fast+: 1 MHz |
| Address Space | 7-bit: 128 addresses, 10-bit: 1024 addresses |
| Pull-up Voltage | 3.3V typical for STM32 |
2. STM32 I2C Initialization
STM32 microcontrollers provide hardware I2C peripherals with DMA support for efficient data transfer. The HAL library simplifies initialization with HAL_I2C_Init(). Key configuration parameters include clock speed, duty cycle, addressing mode, and own address. The peripheral clock must be enabled via __HAL_RCC_I2C1_CLK_ENABLE() before calling the init function.
// STM32 I2C initialization example
I2C_HandleTypeDef hi2c1;
hi2c1.Instance = I2C1;
hi2c1.Init.Timing = 0x00702991;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
3. Pin Configuration
The I2C pins must be configured in alternate open-drain mode with pull-up resistors. On STM32, the SCL and SDA pins are configured as GPIO_MODE_AF_OD. The internal pull-up resistors can be enabled, but external 4.7k ohm resistors provide better control.
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
On STM32F103 (Blue Pill), I2C1 uses PB6 (SCL) and PB7 (SDA). STM32H7 and STM32F4 series offer up to 4 I2C peripherals.
4. Addressing Modes
Standard 7-bit addressing provides 128 possible addresses (0x00 to 0x7F). Common sensor addresses include 0x44 (SHT30/31/35), 0x76 (BME280 at alternative address), and 0x68 (MPU6050). Some sensors support address selection via pins, allowing two devices on one bus.
| Sensor | Address | Notes |
|---|---|---|
| SHT30 | 0x44 (default) | 0x45 if ADDR pin high |
| BME280 | 0x76 or 0x77 | SDO pin selects |
| MPU6050 | 0x68 (default) | 0x69 if AD0 high |
5. Data Transfer Techniques
I2C data transfer in embedded systems uses a master-slave protocol with three primary operations: write to register, read from register, and burst read/write. The HAL library provides HAL_I2C_Mem_Write() and HAL_I2C_Mem_Read() for register-based access.
HAL_I2C_Mem_Write(&hi2c1, (device_addr << 1), reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
HAL_I2C_Mem_Read(&hi2c1, (device_addr << 1), reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000);
For higher throughput, DMA-based transfers (HAL_I2C_Mem_Write_DMA()) allow non-blocking operation. Interrupt-driven transfers are useful for low-power applications.
6. SHT30 Temperature and Humidity Sensor
The SHT30 sensor from Sensirion provides 0.2C accuracy for temperature and 2% RH accuracy for humidity. Communication uses I2C with address 0x44. The sensor supports polling and interrupt modes with programmable alert thresholds.
#define SHT30_ADDR (0x44 << 1)
void SHT30_Read(float *temp, float *hum) {
uint8_t buf[6];
HAL_I2C_Mem_Read(&hi2c1, SHT30_ADDR, 0x2400, I2C_MEMADD_SIZE_16BIT, buf, 6, 1000);
*hum = ((buf[0] << 8 | buf[1]) * 100.0) / 65535.0;
*temp = (((buf[3] << 8 | buf[4]) * 175.0) / 65535.0) - 45.0;
}
For production deployments, add CRC validation on received data. The sensor includes an integrated heating element for humidity calibration.
7. BME280 Pressure, Temperature, and Humidity Sensor
The BME280 from Bosch Sensortec combines barometric pressure, temperature, and humidity. It supports I2C and SPI interfaces with addresses 0x76 or 0x77. The sensor includes factory-trimmed calibration data in registers.
#define BME280_ADDR (0x76 << 1)
#define BME280_WHO_AM_I 0xD0
#define BME280_CHIP_ID 0x58
uint8_t id;
HAL_I2C_Mem_Read(&hi2c1, BME280_ADDR, BME280_WHO_AM_I, I2C_MEMADD_SIZE_8BIT, &id, 1, 100);
if (id == BME280_CHIP_ID) { load_calibration_data(); }
The BME280 offers programmable oversampling (1x to 16x), filter settings, and standby time. Used in weather stations, altitude measurement, and HVAC applications.
8. MPU6050 6-Axis Accelerometer and Gyroscope
The MPU6050 from TDK Invensense integrates a 3-axis MEMS accelerometer and 3-axis gyroscope. It includes a Digital Motion Processor (DMP) for onboard sensor fusion. Default I2C address is 0x68, with 0x69 via the AD0 pin.
#define MPU6050_ADDR (0x68 << 1)
#define MPU6050_PWR_MGMT_1 0x6B
uint8_t wakeup = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, MPU6050_PWR_MGMT_1, I2C_MEMADD_SIZE_8BIT, &wakeup, 1, 100);
uint8_t raw[14];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x3B, I2C_MEMADD_SIZE_8BIT, raw, 14, 100);
int16_t ax = (raw[0] << 8) | raw[1];
Industrial applications combine multiple MPU6050 sensors for vibration monitoring across machinery.
9. Troubleshooting Common Issues
I2C buses can experience lockup due to stuck SDA low, timing violations, or noise. Implement a bus recovery routine. Other issues include incorrect pull-up resistor values and address conflicts.
void I2C_RecoverBus(I2C_HandleTypeDef *hi2c) {
for (int i = 0; i < 9; i++) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
HAL_Delay(1);
}
HAL_I2C_DeInit(hi2c);
HAL_I2C_Init(hi2c);
}
10. Related Reading
- STM32 GPIO Interrupt-Driven Development for Real-Time Embedded Systems
- SPI vs I2C: A Comprehensive Comparison for Embedded Designers
- Mastering STM32 Timers for Precise Embedded Control Applications
Key Takeaways
- I2C uses two wires (SCL, SDA) with open-drain drivers and pull-up resistors
- STM32 hardware I2C supports standard-mode (100kHz) and fast-mode (400kHz) operation
- Always verify sensor chip ID after power-on before reading data registers
- SHT30, BME280, and MPU6050 are common sensors with well-documented I2C interfaces
- DMA transfers enable non-blocking sensor data acquisition
- Bus recovery via SCL clocking resolves stuck SDA conditions
- Proper pull-up resistor values (2.2k-4.7k) ensure signal integrity
- Use oscilloscope or logic analyzer for I2C bus debugging
- 10-bit addressing extends device count beyond 127 on a single bus
- CRC validation on sensor data prevents corrupted readings