How to Build an ADXL345 Accelerometer Circuit using the I2C Protocol with an STM32F446 Microcontroller Board in C

Nucleo STM32F446 board

In this article, we explain how to build an ADXL345 accelerometer circuit using the I2C protocol with an STM32F446 microcontroller board in C.

An I2C driver allows us to communicate with a device that uses the I2C communication protocol.

In our example, we will use our STM32 board as the master device and communicate with a slave device, an ADXL345 accelerometer. We will get information from this slave device to know its position.

The I2C, which stands for Inter-Integrated Circuit, communication protocol is a two-wire interface protocol that has 2 lines of transmission, SCL and SDA.

SCL is the serial clock line, which is used for synchronizing data transfer between the master and salve.

SDA is the serial data line, which is used to transfer the data.

The operation modes of the I2C protocol is either Master Transmitter, Master Receiver, Slave Transmitter, or Slave Receiver.

With the I2C protocol, transactions are initiated and completed by the master device.

All messages have an address frame and a data frame.

Data is placed on the SDA line after SCL goes LOW, and it is sampled after the SCL line goes HIGH.

In our sample program, we will use an STM32 board with an ADXL345 accelerometer.

Because the ADXL345 accelerometer, as pretty much all components need, require power, we need to connect the component to +5V and GND, in order to power it.

Because we are using the I2C communication protocol, which is a two-wire protocol, we need to make 2 connections, SCL and SDA, to establish I2C communication between the STM32 board and the ADXL345 accelerometer.

We also need to pull the CS (Chip Select) pin of the ADXL345 accelerometer to HIGH voltage and we need to ground the SDO/ALT ADDRESS pin.

The diagram below was taken from the datasheet for the ADXL345 accelerometer and it shows the connections needed for I2C when the alternative address 0x53 is used.

ADXL345 accelerometer circuit with I2C communication protocol

So, in total, 4 wire connections are needed between the STM32 board and the acceleromter.

But we also must make sure

The circuit schematic for the circuit we will build is shown below.

ADXL345 accelerometer circuit using I2C with an STM32F446 microcontroller board

So, after this hardware connection is made, the only thing we need now is the software to execute on the board.

We should then be able to read the positional data that the accelerometer gives us. The ADXL345 is a 3-axis accelerometer. So we get back x data, y data, and z data, which gives us the 3-dimensional data of the tilt or position of the accelerometer at any given time.

So below is the code needed in order to run this accelerometer circuit.

Before we go directly into our main C code, there are some supporting files we need, including register maps and macros needed.

Below we have the stm32f446.h file, which contains many register maps (including for the I2C register) and macros needed for the software program.

In the next file, adxl345.h, we have macros that are completely specific to the ADXL345 accelerometer.

This is shown below.

So we now have all the supporting files.

So we can now go to our main.c file.

So this code is somewhat long, so we will break it down to make it understandable.

So we first include the header files we need.

We then have a list of the function prototypes, so that the compiler knows about them, as in what they return and what parameters they take in, as well as the parameter types.

We then define a number of variables, which we use in our program. We will get back to these, as well as the main() function.

Instead now we skip ahead to the I2C1_init() function, which initializes the I2C communication protocol so that we can use it to communicate with the ADXL345 accelerometer device.

So we can start taking a look at charts.

Below is the alternate function mapping of GPIO Port B for an STM32F446 microcontroller board.

Alternate function mapping for GPIO Port B of an STM32F446 microcontroller board

So you can see that in AF4, pin PB8 functions as I2C1_SCL and pin PB9 function as I2C1_SDA.

This is important to know because these are the 2 pins that we use for I2C communication.

I2C has 3 channels in an STM32F446 board, I2C1, I2C2, and I2C3. I2C1 is the one we are using in this case.

So because we are using GPIO Port B, we must enable the clock for GPIOB.

After this, we must enable the clock to I2C1. I2C1 (as well as I2C2 and I2C3) are all connected to the APB1 bus.

To turn on the clock for I2C1, we write a 1 to bit 21 of the APB1ENR register.

We then must configure pin PB8 to be in alternate function mode. This we do by writing 0b10 to bit 16 to the GPIOx_MODER register.

We then go to the AFHR (Alternate Function High Register) and we write 0b0100 to bit 0 in order to put pin PB8 in AF4, which makes it act as I2C1_SCL.

We then do the same for pin PB9, putting the pin in alternate function mode with the MODER register and then setting the mode to AF4 with the AFHR register.

An important thing to remember with the I2C communication protocol is that the various pins must be in open drain configuration. I2C does not operate with push-pull mode. Therefore, we must set this for pins PB8 and PB9 in the OTYPER register.

Another important thing for the I2C communication protocol is having pull-up resistors for each of the pins, SCL and SDA. You don't have to physically connect these lines to pull-up resistors. It is sufficient to connect these pins to pull-up resistors in software. We do this in our code with the PUPDR register.

Next, we begin setting bits of the I2C registers to configure I2C settings.

Below is the I2C control register 1.

I2C control register 1 (I2C_CR1) of an STM32F446 micrcontroller board

First, we must reset I2C1 and then come out of the reset state. This is done by setting bit 15 to 1 and then clearing it and setting it back to its default state of 0.

We then set the clock frequency of I2C1 to 16MHz, the I2C clock to standard mode (100 KHz), and the rise time of the clock signal.

We then enable I2C1.

The I2C1_byteRead() function takes in 3 parameters: the slave address, the memoryaddress, and data.

The slave address is how the master device (the STM32 board) can identify the slave device (the ADXL345 accelerometer). The slave address is a unique address that identifies which slave the master wants to communicate with.

The memory address is the address of the internal register(s) of the slave device that we want to either write to or read data from. In the case of the accelerometer, it will be the data registers which hold the positional data (X data, Y data, Z data) of the

The data will be the actual data.

We then create a variable, tmp

We then must wait for the I2C1 to not be busy (in the event that it is in some other process).

Once it is not busy (by us checking the busy bit, bit 1, of the status register 2, then we can generate a start condition.

The start condition is generated by setting the start bit, bit 8, of the control register 1.

We then need to wait until the start flag is set.

Once the start flag is set, we then transmit the slave address + write We do this in order to communicate with the slave device.

In order for the master to know which slave device to communicate it with, the slave address must be specified, which should be unique to any other slave device on the bus.

In order for the master device to communicate with the slave device, we must transmit the slave address + write to the data register.

This needs to be done any time we are communicating with any slave device to either write to it or read from it.

We must then wait until the address flag is set.

If this address flag is set, then this means that the master has successfully communicated with the slave device. If the address flag never sets, this means that the master was never able to establish successful communication with the slave device.

We then need to clear the addr flag and this we do by writing the contents of status register 2 to the tmp variable.

We then need to send the memory address. This we do, just like the slave, by writing the address to the data register.

We then wait until the transmitter is empty.

We then generate another start (restart) condition.

We then must wait until the start flag is set.

We then this time transmit the slave address + read.

Last time it was write, but this time it is read, because we want to read data from this device.

We then wait until the address flag is set.

We choose to disable the acknowledge (ACK) bit.

Usually each time we communicate with a slave device either with a write or read, the slave device always sends an ACK bit to show the transmitting device that it received the request successfully. However, if you want, after the first successful communication when we initially sent the write command, you can disable it for future address sends, which can help to speed up the program.

We then clear the address flag.

We then generate a stop condition.

This is done by setting the stop bit, bit 9, HIGH.

We then must wait until the RXNE flag is set. This means all data has been received.

We then read the data from the data register.

Next, we have our I2C1_burstRead() function.

The parameter n is the bytes that we are going to read.

I2C1_burstRead() is very similar to the I2C1_byteRead() function, except it reads multiple bytes; it isn't restricted to 1 byte.

The I2C1_burstWrite() function allows us to write multiple bytes to a given register.

The adxl_read_address() function takes in a single parameter, which represents the slave address of the slave device.

The adxl_read_address() function will be used inside our adxl_init() function in order to read the device address, so that we can see if initial communication with the slave device is successful.

The adxl_write() function uses the I2C1_burstWrite() function to write data to the desired register of a slave device.

The next function, I2C1_burstRead() reads data from the registers of the slave device, the ADXL345 accelerometer. These registers are the registers which give the X, Y, and Z positions of the accelerometer.

We then have the adxl_init() function, which encases many of these functions.

We initialize I2C by the I2C1_init() function.

We read the slave address with the adxl_read_address() function.

We set the data format range to + -4g with the adxl_write() function.

We reset all bits and then we configure the power control measure bit.

In our main() function, we read the values in an infinite loop.

If you run the code in debug mode and put these variables as live variables, you should see them updating as the accelerometer changes positions.

And this is how to build an ADXL345 accelerometer circuit using the the I2C communication protocol with an STM32F446 micrcontroller board in C.

Related Resources

HTML Comment Box is loading comments...