How to Create Library for SPI Sensor Part 2: Creating the library

In the second part of creating a library for SPI sensor, we shall construct the source and header 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 File:

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 MPU9250.c as following:

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

5. Gathering the Registers:

From part 1, we shall use the register map document of the MPU9250.

In MPU9250.h header file, we shall declare most of the important register as following:

// Gyroscope Data Registers
#define MPU9250_GYRO_XOUT_H       0x43  // High byte of X-axis gyroscope data
#define MPU9250_GYRO_XOUT_L       0x44  // Low byte of X-axis gyroscope data
#define MPU9250_GYRO_YOUT_H       0x45  // High byte of Y-axis gyroscope data
#define MPU9250_GYRO_YOUT_L       0x46  // Low byte of Y-axis gyroscope data
#define MPU9250_GYRO_ZOUT_H       0x47  // High byte of Z-axis gyroscope data
#define MPU9250_GYRO_ZOUT_L       0x48  // Low byte of Z-axis gyroscope data

// Gyroscope Configuration
#define MPU9250_GYRO_CONFIG       0x1B  // Gyroscope configuration register

// Accelerometer Data Registers
#define MPU9250_ACCEL_XOUT_H      0x3B  // High byte of X-axis accelerometer data
#define MPU9250_ACCEL_XOUT_L      0x3C  // Low byte of X-axis accelerometer data
#define MPU9250_ACCEL_YOUT_H      0x3D  // High byte of Y-axis accelerometer data
#define MPU9250_ACCEL_YOUT_L      0x3E  // Low byte of Y-axis accelerometer data
#define MPU9250_ACCEL_ZOUT_H      0x3F  // High byte of Z-axis accelerometer data
#define MPU9250_ACCEL_ZOUT_L      0x40  // Low byte of Z-axis accelerometer data



// Accelerometer Configuration
#define MPU9250_ACCEL_CONFIG      0x1C  // Accelerometer configuration register
#define MPU9250_ACCEL_CONFIG_2    0x1D  // Accelerometer configuration 2 register

#define USER_CTRL				  0x6A  // User control register

#define I2C_IF_DIS				  0x10  // Disable I2C Mode and Enable SPI Mode

Here, we can find the configuration of the gyroscope and accelerometer, also another important register called user control which shall see how to use it later.

6. Developing the Library:

After the declaration of the registers, declare the following enumeration to hold the acceleration range as following:

/**
 * @brief Acceleration range options for the MPU9250 accelerometer.
 *
 * This enumeration defines the possible full-scale acceleration ranges
 * that can be configured for the MPU9250 accelerometer. The range determines
 * the sensitivity and maximum measurable acceleration.
 */
typedef enum
{
    ACC_FULL_SCALE_2_G = 0x00, /**< Full-scale range: ±2g */
    ACC_FULL_SCALE_4_G = 0x08, /**< Full-scale range: ±4g */
    ACC_FULL_SCALE_8_G = 0x10, /**< Full-scale range: ±8g */
    ACC_FULL_SCALE_16_G = 0x18 /**< Full-scale range: ±16g */
} ACC_Range_Typedef;

Next, we shall declare the initialization function of the accelerometer as following:

/**
 * @brief Initialize the accelerometer of the MPU9250.
 *
 * This function configures the accelerometer section of the MPU9250.
 * It sets the acceleration range based on the input parameter.
 *
 * @param range The desired acceleration range to configure the MPU9250 accelerometer.
 *              The range should be of type `ACC_Range_Typedef`.
 *              It typically includes options like ±2g, ±4g, ±8g, or ±16g.
 *
 * @note This function does not return any value.
 */
void MPU9250_Accel_Init(ACC_Range_Typedef range);

Next, the acceleration update function:

/**
 * @brief Update the accelerometer data from the MPU9250.
 *
 * This function reads the latest accelerometer data from the MPU9250
 * and updates the internal data structure or variables storing the
 * acceleration values.
 *
 * @note Ensure that the MPU9250 is properly initialized before calling
 *       this function. This function does not return any value.
 */
void MPU9250_Acceleration_Update(void);

Next, the functions to get the x, y and z accelerations as following:

/**
 * @brief Get the acceleration value along the X-axis.
 *
 * This function retrieves the most recent acceleration value measured
 * along the X-axis by the MPU9250.
 *
 * @return The acceleration value along the X-axis in g (gravity) units.
 *
 * @note Ensure that the accelerometer data is updated by calling
 *       `MPU9250_Acceleration_Update()` before using this function.
 */
float MPU9250_Get_AccelX(void);

/**
 * @brief Get the acceleration value along the Y-axis.
 *
 * This function retrieves the most recent acceleration value measured
 * along the Y-axis by the MPU9250.
 *
 * @return The acceleration value along the Y-axis in g (gravity) units.
 *
 * @note Ensure that the accelerometer data is updated by calling
 *       `MPU9250_Acceleration_Update()` before using this function.
 */
float MPU9250_Get_AccelY(void);

/**
 * @brief Get the acceleration value along the Z-axis.
 *
 * This function retrieves the most recent acceleration value measured
 * along the Z-axis by the MPU9250.
 *
 * @return The acceleration value along the Z-axis in g (gravity) units.
 *
 * @note Ensure that the accelerometer data is updated by calling
 *       `MPU9250_Acceleration_Update()` before using this function.
 */
float MPU9250_Get_AccelZ(void);

Thats all for the header file, save it.

Open MPU9250.c source, we start by including the MPU9250 header file, main.h and spi header file as following:

#include "MPU9250.h"

#include "main.h"
#include "spi.h"

Next, we need to declare a read flag as mentioned the datasheet of the MPU9250 as following:

#define READ_FLAG   0x80

Declare a variable to hold the acceleration range passed by the user to be used later in the code as following:

static float accelRange;

It is declared as static since it will be only used in the current MPU9250 source file and not accessible by any other source file.

Declare the following two buffers:

static uint8_t data[2];
static uint8_t AccelerationData[6];

The data[2] which will hold the data to be transmitted to sensor while the accelerationData will hold the acceleration data measured by the sensor.

Next, we need to declare the read/write functions for the sensor as following:

static void MPU9250_Write(uint8_t *data, uint8_t len)
{
	HAL_SPI_Transmit(&hspi1, data, len, 100);
}


static void MPU9250_Read_Reg(uint8_t addr,uint8_t *data, uint8_t len)
{
	uint8_t DummyBuffer[len];

	DummyBuffer[0]=addr|READ_FLAG;

	for (int i=1;i<len;i++)
	{
		DummyBuffer[i]=0x00;
	}

	HAL_SPI_TransmitReceive(&hspi1, DummyBuffer, data, len, 100);
}

For the write function:

static void MPU9250_Write(uint8_t *data, uint8_t len)
{
	HAL_SPI_Transmit(&hspi1, data, len, 100);
}

The function shall take the following parameters:

  • Pointer to the data to be written to the sensor.
  • Length of the data.

In the function, we shall call:

HAL_SPI_Transmit(&hspi1, data, len, 100);

The function shall take the following:

  • HandleTypeDef to SPI to be use which is SPI1 defined as hspi1 in this case.
  • Data to be written.
  • Length of the data.
  • Timeout which is 100ms in this case.

For the read register:

static void MPU9250_Read_Reg(uint8_t addr,uint8_t *data, uint8_t len)

The function shall take the following:

  • Register address.
  • Buffer to hold the data to be read.
  • Length of the data.

Within the function:

	uint8_t DummyBuffer[len];

	DummyBuffer[0]=addr|READ_FLAG;

	for (int i=1;i<len;i++)
	{
		DummyBuffer[i]=0x00;
	}

	HAL_SPI_TransmitReceive(&hspi1, DummyBuffer, data, len, 100);

First, create dummy buffer by the size passed to the function, this is needed to keep the clock of the SPI.

Fill the first element of the buffer with the address ored with the read flag as mentioned in the datasheet of the sensor.

File the rest of the buffer with zero.

Next, we shall call:

HAL_SPI_TransmitReceive(&hspi1, DummyBuffer, data, len, 100);

This function shall write and read from SPI bus at the same time.

It will take the following as parameters:

  • handleTypedef to the SPI which is hspi1 in this case.
  • Data to be written which is in this case is dummy buffer.
  • Buffer to the data to be read.
  • Length of the data to be read/write.
  • Timeout of 100ms.

Next, for selecting deselecting the sensor:

It is as simple as setting the CS pin low when need to react with the sensor and high when not.

static void MPU9250_Select(void)
{
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, RESET);
}

static void MPU9250_Deselect(void)
{
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, SET);
}

Next. for initialization of the accelerometer part:

void MPU9250_Accel_Init(ACC_Range_Typedef range)
{
	switch(range)
	{
	  case ACC_FULL_SCALE_2_G:
	    accelRange = 2.0;
	    break;
	  case ACC_FULL_SCALE_4_G:
	    accelRange = 4.0;
	    break;
	  case ACC_FULL_SCALE_8_G:
	    accelRange = 8.0;
	    break;
	  case ACC_FULL_SCALE_16_G:
	    accelRange = 16.0;
	    break;
	  default:
	    return; // Return without writing invalid mode
	  }

	data[0]=USER_CTRL; data[1]=I2C_IF_DIS;

	MPU9250_Select();
	MPU9250_Write(data,2);
	MPU9250_Deselect();

	data[0]=MPU9250_ACCEL_CONFIG; data[1]=range;
	MPU9250_Select();
	MPU9250_Write(data,2);
	MPU9250_Deselect();

}

The function shall take the acceleration range passed by the user.

Check which value is passed, and store the equivalent of the range in accelRange.

After that, we shall write to the sensor telling it that we are in SPI mode not in I2C by setting I2C_IF_DIS bit to 1 in user control register as following:

	data[0]=USER_CTRL; data[1]=I2C_IF_DIS;

	MPU9250_Select();
	MPU9250_Write(data,2);
	MPU9250_Deselect();

Next, set the acceleration range required by the user as following:

	data[0]=MPU9250_ACCEL_CONFIG; data[1]=range;
	MPU9250_Select();
	MPU9250_Write(data,2);
	MPU9250_Deselect();

Next, to update the sensor, it is as simple as reading the XOUT_H register as following:

void MPU9250_Acceleration_Update(void)
{

	MPU9250_Select();

	MPU9250_Read_Reg(MPU9250_ACCEL_XOUT_H,AccelerationData,6);


	MPU9250_Deselect();

}

Since the sensor features auto increment to the register during the reading, it will automatically push the data of the next register with the next clock of the SPI.

For the update function:

static float MPU9250_accelGet(uint8_t highIndex, uint8_t lowIndex)
{
	  int16_t v = ((int16_t) AccelerationData[highIndex]) << 8 | AccelerationData[lowIndex];
	  return ((float) -v) * accelRange / (float) 0x8000;
}

float MPU9250_Get_AccelX(void)
{
  return MPU9250_accelGet(1, 2);
}

float MPU9250_Get_AccelY(void) {
  return MPU9250_accelGet(3, 4);
}

float MPU9250_Get_AccelZ(void) {
  return MPU9250_accelGet(5, 6);
}

It is as following:

  • Get the signed 16-bit data from the acceleration buffer.
  • Multiply the result by the acceleration range.
  • Divide the results by 0x8000 to ensure the correct conversion.

Thats all for the source file.

Save the project for now.

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 "stdio.h"
#include "MPU9250.h"

In user code begin PV, declare the following:

float AccX, AccY, AccZ;

In user code begin 2 in the main function:

Initialize the accelerometer with scale of 2G as following:

MPU9250_Accel_Init(ACC_FULL_SCALE_2_G);

In user code begin 3, update the sensor, get the new values and print them as following:

	  MPU9250_Acceleration_Update();

	  /*Get the acceleration data*/
	  AccX=MPU9250_Get_AccelX();
	  AccY=MPU9250_Get_AccelY();
	  AccZ=MPU9250_Get_AccelZ();

	  printf("Ax=%0.3f \t Ay=%0.3f \t Az=%0.3f \r\n",AccX,AccY,AccZ);
	  HAL_Delay(100);

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: The gyroscope part shall be left as task for the reader 😉

Happy coding 😉

Add Comment

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