Electronics - STM32 - I2C communication between STM32F3Discovery and LSM303DLHC accelerometer

Every smartphone is nowadays provided with an accelerometer.

In this tutorial we are going to see how to retrieve X, Y and Z values from the LSM303DLHC accelerometer on the STM32F3Discovery board.

And to achieve this goal we'll use I2C bus communication.

Let's get started.

Shopping list

 

First things first

The LSM303DLHC accelerometer sits on I2C1 register at address 0x19, connected to PB6 (SCL) and PB7 (SDA).

It measures each value in a 12-bit format.

So to read it correctly we'll have to convert this 12-bit value into a 16-bit one.

The measured values go out over UART on PA9 at 115200 baud, and the 8 compass LEDs on PE8..PE15 light up to indicate the tilt direction.

The CPU runs at 8 MHz on the default HSI oscillator, no PLL involved.

 

How I2C Works on STM32F3

I2C on the STM32F3 is a two-wire bus: SCL carries the clock, SDA carries data.

The master (our STM32) generates the clock and drives every transaction.
The LSM303DLHC is the slave.

Both lines must be open-drain.

That means neither master nor slave can actively drive the line high.

They can only pull it low (to GND).

A pull-up resistor (internal or external) brings the line back to high when nobody is pulling it down.

This is what allows multiple devices to share the bus safely (a bit like a mutex on a program).

 

A typical read transaction has two phases.

In the write phase, the master sends the slave address plus the register it wants to read.

Every transaction has to begin with a write phase and an address (of the device to talk to).

In the read phase, the master sends the slave address again with the read bit set, and the slave clocks

out the data.

So, to read one or more registers from the LSM303DLHC we always use the same pattern:

  • Write phase: send the register address (with bit 7 set for auto-increment).
    • End with TC (Transfer Complete) with no STOP yet.
  • Read phase: repeated START, read direction, AUTOEND.
    • The hardware generates the STOP after all bytes are received.

 

I2C Transaction Helpers

We split the I2C logic into small focused functions that map directly to what the hardware does.

i2c_start_write() loads CR2 to begin a write.

No AUTOEND here, because after a write we may want to issue a repeated START for a read rather than a STOP (the mutex analogy).

i2c_start_read() loads CR2 to begin a read, with AUTOEND set so the hardware generates the STOP automatically after the last byte.

i2c_write_byte() waits for TXIS then writes to TXDR. i2c_read_byte() waits for RXNE then reads from RXDR

i2c_wait_tc() polls TC before issuing a repeated START

i2c_stop() sets the STOP bit in CR2 and waits for BUSY to clear.

The 0x80 bit in the register address tells the LSM303DLHC to auto-increment its internal pointer after each byte.

This lets us read all 6 axis bytes in a single burst starting at address 0x28 from OUT_X_L_A register:

  • X_L
  • X_H
  • Y_L
  • Y_H
  • Z_L
  • Z_H

Otherwise it'd need 6 transactions (instead of 1 for every value).

Build and Flash

The project uses CMake with a separate toolchain file and a run.sh script to wrap the common operations.

In our case we'll use the main command to read I2C and UART data directly from our logic analyser:

$ cd firmware/stm32f3discovery/

$ chmod +x run.sh

$ ./run.sh all

By moving your board, you'll see the LEDs moving accordling and X, Y, Z values sent through the I2C bus communication.

Conclusion

We set up I2C1 from scratch, configured the LSM303DLHC.

The bus talks, the sensor answers, the LEDs point where gravity pulls.

The hard part is done, the bus is alive.

Good job, you did it cool

 

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.