Lab 103 (AI KIT): Reading the BMI270 and BMM350 to Compute Pitch, Roll, and Yaw

Introduction

In this lab, we combine the two motion sensors on the PSoC 6 AI Evaluation Kit — the Bosch BMI270 6-axis IMU (accelerometer + gyroscope) and the Bosch BMM350 3-axis magnetometer — to compute the board's attitude angles: Pitch, Roll, and Yaw.

Reading raw sensor data is only the first step. To turn raw numbers into stable, trustworthy attitude angles, we must also solve three practical problems that every real-world motion system faces:

  • Sensor calibration — removing the gyroscope bias and magnetometer hard-iron offset;
  • Axis alignment — the BMI270 and BMM350 are two independent chips whose axes do not necessarily point in the same direction on the PCB;
  • Deterministic sampling — replacing software polling with the sensor's data-ready hardware interrupt so that the integration time step Δt is constant.

The attitude estimation pipeline developed in this lab serves as the foundation for a drone or RC airplane flight controller. Every flight controller on the market performs the same sequence: calibrate at power-up, align the sensor axes to the body frame, sample at a fixed rate, and fuse accelerometer, gyroscope, and magnetometer data into attitude angles. After completing this lab, you will understand why a drone asks you to "keep the aircraft still" before it arms.


Prerequisites

Hardware:

  • Infineon CY8CKIT-062S2-AI PSoC 6 AI Evaluation Kit
  • USB Type-C cable (connected to the KitProg3 port)

Software:

  • ModusToolbox IDE
  • Termite, Tera Term, or any serial terminal (115200, 8N1)


Reading Materials

  • PSoC 6 AI Kit: CY8CKIT-062S2-AI
  • Datasheet:
    • CY8CKIT-062S2-AI Board Guide and Schematics (Document No. 630-60699-01): PDF
    • BMI270 Datasheet (6-axis IMU): PDF
    • BMM350 Datasheet (3-axis Magnetometer): PDF
    • Bosch BMI270 SensorAPI and BMM350 SensorAPI (GitHub repositories, used as reference material in this lab and integrated in later labs)


Background and Theory of Operation

Bosch BMI270 6-Axis IMU Sensor

Bosch BMI270 6-Axis IMU Sensor

Overview of the BMI270 Sensor:

The BMI270 is a 6-axis Inertial Measurement Unit (IMU) that contains a 16-bit triaxial accelerometer and a 16-bit triaxial gyroscope. This kit communicates with the PSoC 6 MCU via the I2C interface, using a default I2C address of 0x68 (the SDO pin is tied to GND on the board).

Step 1: Device Initialization (Config File Upload)

The BMI270 is unusual among IMUs: after power-up or soft reset, an 8-KB configuration blob (bmi270_config_file[], provided by Bosch) must be uploaded into the chip before any sensor can be enabled. This blob is the firmware of the internal Feature Engine — a small processor inside the sensor that later labs will use for step counting and gesture detection:

  1. Soft Reset: write 0xB6 to the CMD register (0x7E), then wait 2 ms.
  2. Disable Advanced Power Save: write 0x00 to PWR_CONF (0x7C), then wait at least 450 µs.
  3. Write 0x00 to the INIT_CTRL register (0x59) to prepare for the configuration load.
  4. Burst-write the initialization array (bmi270_config_file) to the INIT_DATA register (0x5E) in chunks. Before each chunk, program the word address (byte index / 2) into INIT_ADDR_0/1 (0x5B/0x5C).
  5. Write 0x01 to the INIT_CTRL register (0x59) to complete the configuration load.
  6. Wait at least 20 ms, and then read the INTERNAL_STATUS register (0x21). Bits [3:0] must equal 0b0001 to confirm successful initialization.

Step 2: Enable Sensors and Configure Normal Power Mode

To continuously read both the acceleration and gyroscope data, you must switch the device to normal power mode and enable the corresponding sensors.

  1. Enable the sensors: Write 0x0E to the PWR_CTRL register (0x7D). This enables the acquisition of acceleration, gyroscope, and temperature sensor data while disabling the auxiliary interface.
  2. Keep advanced power save disabled: Write 0x02 to the PWR_CONF register (0x7C). This disables the advanced power save bit but leaves the FIFO self-wakeup enabled.

Step 3: Configure Output Data Rates (ODR) and Filters

Next, set the sample rates and bandwidth filters for the sensors:

  1. Accelerometer Configuration: Write 0xA8 to the ACC_CONF register (0x40). This enables the accelerometer performance filter, sets the bandwidth to normal mode, and sets the Output Data Rate (ODR) to 100 Hz. Write 0x00 to ACC_RANGE (0x41) for ±2 g (16384 LSB/g).
  2. Gyroscope Configuration: Write 0xA8 to the GYR_CONF register (0x42). This enables the gyroscope performance filter, sets the bandwidth to normal mode, and sets the ODR to 100 Hz. Write 0x00 to GYR_RANGE (0x43) for ±2000 dps (16.4 LSB/dps).

Step 4: Reading Acceleration and Gyroscope Data

The 16-bit data for each axis is split into an LSB (lower) and an MSB (upper). You must always read the LSB register first, because doing so locks the MSB register to ensure data integrity (known as the shadowing procedure). A burst read handles this automatically.

  • Acceleration Data is stored in registers DATA_8 to DATA_13 (0x0C to 0x11).
  • Gyroscope Data is stored in registers DATA_14 to DATA_19 (0x12 to 0x17).

Reading operation: To capture all 6 axes of data at once, perform a burst read of 12 bytes starting at register address 0x0C.

  • Bytes 0-5 will contain the X, Y, and Z acceleration data.
  • Bytes 6-11 will contain the X, Y, and Z gyroscope data.


Schematic Circuit and Pin Configuration

Both sensors share the same I2C bus (SCB0). All peripherals in this lab are configured at runtime using the cyhal API — the Device Configurator is not used.

Function PSoC 6 Pin Peripheral Note
I2C SCL P0.[2] SCB0 Shared sensor bus
I2C SDA P0.[3] SCB0 Shared sensor bus
BMI270 I2C addr 0x68 SDO tied to GND
BMM350 I2C addr 0x15 ADSEL is tied high on this board
BMI270 INT1 P1.[5] GPIO interrupt Data-ready interrupt (Experiment C4)
BMI270 INT2 P0.[4] GPIO Not used in this lab
BMM350 INT P1.[0] GPIO Not used in this lab
Debug UART TX P5.[1] SCB5 KitProg3 USB-UART, 115200 8N1
Debug UART RX P5.[0] SCB5 KitProg3 USB-UART
User LED1 P5_3 GPIO Heartbeat indicator
User Button P5.[2] GPIO Active low, internal pull-up

The firmware follows the modular architecture used throughout this lab series:

  • Board_Config.h — all physical pin definitions in one place
  • I2C_Bus.c/.hI2C_ReadBytes() / I2C_WriteBytes() primitives
  • UART_Console.c/.h — printf-free UART output functions
  • GPIO_Control.c/.h — LED and button
  • BMI270.c/.h, BMM350.c/.h — register-level sensor drivers


Lab Experiments


Q&A / Troubleshooting

Q1. The BMM350 Chip ID reads 0x00 or a wrong value.
Two usual causes: (1) the 2 dummy bytes were not discarded — the "Chip ID" you read is actually a dummy byte; (2) wrong slave address — on this board, the BMM350 answers at 0x15, not 0x14.

Q2. BMI270 INTERNAL_STATUS (0x21) stays at 0x00 or 0x02 after the config upload.
The 8-KB blob upload failed. Check that INIT_ADDR_0/1 is programmed with the word address (byte index / 2) before every chunk, that the chunk size divides 8192 evenly, and that PWR_CONF was set to 0x00 (with the 450 µs wait) before starting the upload.

Q3. Yaw rotates in the wrong direction, or shifts when the board tilts.
This is the axis-alignment problem from Exp#2. Adjust the sign/swap in the MAG_ALIGN_x macros. If yaw is stable but offset by a constant angle, that is the hard-iron offset — run the Exp#3 Part B calibration.

Q4. Yaw slowly rotates even when the board is untouched.
If yaw comes from gyro integration, this is normal bias drift — apply Exp#3 Part A. If yaw comes from the tilt-compensated magnetometer and still wanders, check for magnetic interference: USB cables, steel desks, and speakers all bend the local field.

Q5. The INT1 interrupt never fires.
Verify the two register writes: INT1_IO_CTRL (0x53) must have the output-enable bit set (0x0A), and INT_MAP_DATA (0x58) must map data-ready to INT1 (0x04). Also, confirm the GPIO event is CYHAL_GPIO_IRQ_RISE and that the callback was registered before the event was enabled.

Q6. No UART output at all.
Use the heartbeat LED as a divide-and-conquer tool: if the LED blinks but the terminal is silent, the problem is the UART or the terminal settings (115200, 8N1, correct KitProg3 COM port). If the LED never blinks, the program is stuck in sensor initialization — usually Q1 or Q2, as shown above.

Q7. Readings freeze at a constant value.
Almost always a broken burst read — reading MSB before LSB (BMI270 shadowing) or issuing single-register reads on the BMM350 instead of one burst starting at 0x31.

© 2026 Air Supply Information Center (Air Supply BBS)