How to Create a Library for sensors Part 2: Creating the Library

In part 2 of building library for ADXL345 sensor, we shall construct the header file and source file to interface the sensor with STM32F4.

In this guide, we shall cover the following:

  • Creating the header and source files.
  • Gathering the registers.
  • Developing the library.
  • Retargting printf.
  • Testing the library.

4. Creating the Header and Source Files:

After the project has been generated by STM32CubeMX within STM32CubeIDE, we shall add new source file and header file.

Right click on Src folder and add new source file as following:

Give a name for the source file, we shall name it as adxl345.c as following:

In similar manner, right click on Inc folder and add new header file with name of adxl345.h.

5. Gathering the Registers:

From the datasheets of adxl345, we can find the register map as following:

From the registers map, we shall create macros for those registers.

6. Developing the Library

In adxl345.h header file, declare the following file macros as following:

First, include the stdint library as following:

#include "stdint.h"

The register map as following:

// ADXL345 Register Map
#define ADXL345_DEVID             (uint8_t)0x00  // Device ID
#define ADXL345_THRESH_TAP        (uint8_t)0x1D  // Tap threshold
#define ADXL345_OFSX              (uint8_t)0x1E  // X-axis offset
#define ADXL345_OFSY              (uint8_t)0x1F  // Y-axis offset
#define ADXL345_OFSZ              (uint8_t)0x20  // Z-axis offset
#define ADXL345_DUR               (uint8_t)0x21  // Tap duration
#define ADXL345_LATENT            (uint8_t)0x22  // Tap latency
#define ADXL345_WINDOW            (uint8_t)0x23  // Tap window
#define ADXL345_THRESH_ACT        (uint8_t)0x24  // Activity threshold
#define ADXL345_THRESH_INACT      (uint8_t)0x25  // Inactivity threshold
#define ADXL345_TIME_INACT        (uint8_t)0x26  // Inactivity time
#define ADXL345_ACT_INACT_CTL     (uint8_t)0x27  // Axis enable control for activity and inactivity detection
#define ADXL345_THRESH_FF         (uint8_t)0x28  // Free-fall threshold
#define ADXL345_TIME_FF           (uint8_t)0x29  // Free-fall time
#define ADXL345_TAP_AXES          (uint8_t)0x2A  // Axis control for single/double tap
#define ADXL345_ACT_TAP_STATUS    (uint8_t)0x2B  // Source of single/double tap
#define ADXL345_BW_RATE           (uint8_t)0x2C  // Data rate and power mode control
#define ADXL345_POWER_CTL         (uint8_t)0x2D  // Power-saving features control
#define ADXL345_INT_ENABLE        (uint8_t)0x2E  // Interrupt enable control
#define ADXL345_INT_MAP           (uint8_t)0x2F  // Interrupt mapping control
#define ADXL345_INT_SOURCE        (uint8_t)0x30  // Source of interrupts
#define ADXL345_DATA_FORMAT       (uint8_t)0x31  // Data format control
#define ADXL345_DATAX0            (uint8_t)0x32  // X-Axis Data 0
#define ADXL345_DATAX1            (uint8_t)0x33  // X-Axis Data 1
#define ADXL345_DATAY0            (uint8_t)0x34  // Y-Axis Data 0
#define ADXL345_DATAY1            (uint8_t)0x35  // Y-Axis Data 1
#define ADXL345_DATAZ0            (uint8_t)0x36  // Z-Axis Data 0
#define ADXL345_DATAZ1            (uint8_t)0x37  // Z-Axis Data 1
#define ADXL345_FIFO_CTL          (uint8_t)0x38  // FIFO control
#define ADXL345_FIFO_STATUS       (uint8_t)0x39  // FIFO status

Also, we need extra macros for reset and enable measuring mode as following:

#define PWR_RST					  (uint8_t)0x00 //Reset power register

#define MEASUREMODE				  (uint8_t)0x08 //Set ADXL345 into measure mode

Create data structure to hold the acceleration data as following:

typedef struct
{
	float ax;
	float ay;
	float az;
}ADXL345_AccData;

During the intialization, we need to configure the acceleration range, hence, we shall create the following data structure to hold the initialization parameters, for now, it holds the acceleration range as following:

typedef struct
{
	uint8_t Accel_Range;


}ADXL345_InitParam;

Declare the following enum to hold the acceleration ranges as following:

typedef enum
{
	accl_2g=0,
	accl_4g=1,
	accl_8g=2,
	accl_16g=3
}adxl345Parameters;

Also, declare the following functions:

void adxl345_SetAdd(uint8_t add);

uint8_t adxl345_ReadID(void);

void adxl345_Init(ADXL345_InitParam *Param);

void adxl345_update(ADXL345_AccData *accData);

Thats all for the header file, save the project as this point.

Next, we shall move to source file.

Open adxl345.c source file.

Within the source file, include the following header files:

#include "adxl345.h"

#include "i2c.h"

Declare address of the adxl345, by default it is 0x53.

Since HAL or the address by 1 or 0 for R/W bit, we need to shift the address to left by 1 as following:

uint8_t ADXL345_Add = (0x53<<1);

Declare the following variable to hold the acceleration range which is needed to calculate the acceleration:

uint8_t acc_range=0;

Next, we shall create a function that will allow us to read/write from/to adxl345 sensor.

This function shall be declare as static since it will be used only within the adxl345 source file.

For the write to adxl345 function, declare the following:

static void adxl345_write(uint8_t reg, uint8_t *data, uint8_t len)

This function shall write data to the sensor and takes the following as parameter:

  • Reg which is the register to write to.
  • Pointer to the data to be written to the sensor.
  • Length of the data to be written.

Within the function, we shall call the function related to writing to sensor, in this case it is the following function:

HAL_I2C_Mem_Write(&hi2c1, ADXL345_Add, reg, 1, data, len, 100);

Note: This function can be replaced with any function for any other MCU.

For the read functionality:

static void adxl345_read(uint8_t reg, uint8_t *data, uint8_t len)
{
	HAL_I2C_Mem_Read(&hi2c1, ADXL345_Add, reg, 1, data, len, 100);
}

Next, for setting the address, the user might uses different address for his adxl345, you can change it using the following function:

void adxl345_SetAdd(uint8_t add)
{
	ADXL345_Add=(add<<1);
}

To read the ID we can simply read the DEVID register as following:

uint8_t adxl345_ReadID(void)
{
	uint8_t ret_data;
	adxl345_read(ADXL345_DEVID, &ret_data, 1);
	return ret_data;
}

This function will return the ID of the sensor which should be 0xE5 as shown below:

Next for the initialization, declare the following function:

void adxl345_Init(ADXL345_InitParam *Param)

The function shall take point of ADXL345_InitParam structure as argument.

Within the function:

First, get the acceleration range and store it the acc_range variable as following:

	/*Get the acceleration range */
	acc_range=Param->Accel_Range;

Next, we shall set the range to be measured by the sensor:

This is as simple as writing the desired range as following:

	uint8_t temp[1];

	/*Set the acceleration range*/
	temp[0]=acc_range;
	adxl345_write(ADXL345_DATA_FORMAT, temp, 1);

From the power up sequence section in the datasheets:

After the i2c bus is Initialized, the sensor is in standby mode. Hence we need to enable the measurement mode as following:

It is always recommended to reset the register before intialization the sensor, since this sensor doesn’t have software reset, we can’t reset it, but we can bypass this issue by reseting the register then setting the required parameter as following:

	/*Reset Power Control register*/
	temp[0]=PWR_RST;
	adxl345_write(ADXL345_POWER_CTL, temp, 1);

	/*Set ADXL345 into measure mode*/
	temp[0]=MEASUREMODE;
	adxl345_write(ADXL345_POWER_CTL, temp, 1);

For the updating the adxl345 acceleration data, we can simply read the 6 bytes starting from the DATAX0 as following:

void adxl345_update(ADXL345_AccData *accData)
{
	uint8_t adxl345Data[6];

	adxl345_read(ADXL345_DATAX0,adxl345Data,6);

	int16_t x,y,z;

	x = ((adxl345Data[1]<<8)|adxl345Data[0]);
	y = ((adxl345Data[3]<<8)|adxl345Data[2]);
	z = ((adxl345Data[5]<<8)|adxl345Data[4]);

	float divider;
	switch(acc_range)
	{
		case accl_2g: divider=0.003906; /*1/256*/	break;
		case accl_4g: divider=0.0078125;/*1/128*/	break;
		case accl_8g: divider=0.01563;	/*1/64*/	break;
		case accl_16g: divider=0.03125;	/*1/32*/	break;
	}

	accData->ax=x*divider;
	accData->ay=y*divider;
	accData->az=z*divider;
}

After reading the 6 register, we construct the x,y and z values from the 6 values.

Since the sensor will send the X0 first, hence X0 is the first 8 bits and X1 is the second 8-bits.

Then we check which acceleration range is used and get the required divider which is taken from the datasheets as following:

Then store the results in the datas structure passed by the user.

Thats all for the source file.

7. Retargeting Printf:

In order to retarget printf to use serial, create new source file in src folder with name of uartPrintf.c

The content of the source file as following:

#include "stdio.h"

#include "usart.h"

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart2, &ch, 1, 10);
	return ch;
}

8. Testing the library:

In main.c source file, first in user begin include section, include the following:

#include "adxl345.h"
#include "stdio.h"

In user code begin PTD, declare the following data structures:

ADXL345_InitParam InitParm;

ADXL345_AccData AccData;

In user code begin 2 section of the main function:

  uint8_t adxlID;
  adxlID=adxl345_ReadID();

  printf("ADXL345 ID is 0x%x\r\n",adxlID);

  InitParm.Accel_Range=accl_4g;

  adxl345_Init(&InitParm);

First, get the ID then initialize the accelerometer with range of 4.

In user code begin 3:

	  adxl345_update(&AccData);
	  printf("Ax =%0.3f \t Ay =%0.3f \t Az =%0.3f\r\n",AccData.ax,AccData.ay,AccData.az);
	  HAL_Delay(50);

Get the new data and print them.

Note: In order to printf with float, you need to enable it a following:

Right click on the project, then properties:

Save the project, build it and run it on your board as following:

Open your favourite serial terminal application, set the baudrate to be 115200 and you should get the following:

Note that we are getting the correct ID and the results are correct.

Happy coding 😉

Add Comment

Your email address will not be published. Required fields are marked *