STM32: I2C setup and the first WHO_AM_I
Video coming soon
We connect the IMU to the Nucleo with four wires and turn on I2C in CubeMX. Then we read one register, WHO_AM_I: if the right value comes back, the wires and the address are correct.
In this lesson · 5 steps
1Wiring (NUCLEO-F401RE → IMU breakout)
3V3→ VIN / VCCGND→ GNDPB6(Arduino headerD10) → SCLPB7(left morpho headerCN7, pin 21) → SDA- Most breakouts already have pull-up resistors on SDA and SCL. The address pin (SDO / SA0 / AD0) left open gives the default address.
2Enable I2C1 in CubeMX
- Open the
.iocfile → Connectivity → I2C1: Mode =I2C - Parameter Settings: I2C Speed Mode =
Standard Mode(100 kHz): slower than Fast Mode (400 kHz), but more reliable with jumper wires - Check the pins on the pinout:
PB6=I2C1_SCL,PB7=I2C1_SDA(the CubeMX default) - GPIO Settings: Pull-up =
No pull-up(the breakout has them) - Save → Generate Code
3Sensor address and registers
main.c · USER CODE BEGIN PD
Only these three values change from sensor to sensor.
/* USER CODE BEGIN PD */ /* IMU: change these three lines for your sensor * * sensor I2C address WHO_AM_I register expected answer * ISM330DHCX 0x6A (0x6B) 0x0F 0x6B * LSM6DSOX 0x6A (0x6B) 0x0F 0x6C * MPU-6050 0x68 (0x69) 0x75 0x68 * MPU-9250 0x68 (0x69) 0x75 0x71 * ICM-20948 0x69 (0x68) 0x00 0xEA * ICM-42688-P 0x68 (0x69) 0x75 0x47 * (in brackets: the address when the address pin is pulled the other way) */ #define IMU_ADDR 0x6A /* 7-bit I2C address */ #define IMU_WHO_AM_I 0x0F /* WHO_AM_I register */ #define IMU_WHO_AM_I_VAL 0x6B /* expected answer */ /* USER CODE END PD */
Why?
Find them in your datasheet: the address, the WHO_AM_I register and the answer it must give. My ISM330DHCX answers at 0x6B, because the SDO pin on my board is high. If you get No answer with 0x6A, change IMU_ADDR to 0x6B.
4Read WHO_AM_I once
main.c · USER CODE BEGIN 2
We read 1 byte from register IMU_WHO_AM_I and compare it with the expected answer.
/* USER CODE BEGIN 2 */ uint8_t who_am_i = 0; HAL_StatusTypeDef status = HAL_I2C_Mem_Read(&hi2c1, IMU_ADDR << 1, IMU_WHO_AM_I, I2C_MEMADD_SIZE_8BIT, &who_am_i, 1, 100); if (status != HAL_OK) printf("No answer: check the wires and the address\r\n"); else if (who_am_i == IMU_WHO_AM_I_VAL) printf("WHO_AM_I = 0x%02X: sensor found!\r\n", who_am_i); else printf("WHO_AM_I = 0x%02X: unexpected value\r\n", who_am_i); /* USER CODE END 2 */
Why?
HAL wants the address shifted left by one bit, that's why we write IMU_ADDR << 1. I2C_MEMADD_SIZE_8BIT says the register number is one byte, and 100 is the timeout in ms. If the sensor doesn't answer, status is not HAL_OK, and we print No answer.
5Build, run and check
- Build Project (0 errors), then Debug and press Resume
- In the serial plotter (115200 baud, ST-LINK port), the console shows
WHO_AM_I = 0x6B: sensor found!
Check & troubleshoot
What you should see
WHO_AM_I = 0x6B: sensor found!in the serial plotter console (ISM330DHCX)
If it doesn't work
- No answer: check the wires first. Then the address: the snippet uses 0x6A; many boards use 0x6B (SDO pin high), as mine. If you get No answer, change
IMU_ADDRto 0x6B - 0x6B twice? On my board the address and the WHO_AM_I answer are both 0x6B. That is a coincidence: they are two different things
- Why the shift? HAL wants the 7-bit address moved one bit left: 0x6A << 1 = 0xD4
- Unexpected value: another sensor answered. Use the row of your sensor in the table
- Code disappears after Generate Code: keep everything between the USER CODE comments