cstm32accelerometerdmahal

Getting data from LIS2DH12 accelerometer on stm32 microcontroller using "HAL" library, SPI+DMA


Task: to get data from the accelerometer LIS2DH12 via SPI using DMA (stm32f103cbt6), that is, free up CPU time for data collection, because two buffers are used - one is being processed, the other is being filled. It is known that register 0x28 is the low byte of the X axis, register 0x29 is the low byte of the Y axis, and so on up to the Z axis. I need to get all 3 axes, i.e. 6 bytes. There should be 2048 such data, that is, arrays are declared:

#define SAMPLE_COUNT 2048
uint8_t sample_buf1[SAMPLE_COUNT * 6] = { 0 };
uint8_t sample_buf2[SAMPLE_COUNT * 6] = { 0 };
uint16_t current_sample = 0;
uint8_t current_buf = 0;
uint8_t *sample_buf[2] = {sample_buf1, sample_buf2};

The bottom line is that I do not fully understand how to organize the reception and transmission of data using the HAL function HAL_SPI_TransmitReceive_DMA(&hspi1, buff_source, buff_destination, 6);. Should I read 7 bytes instead of 6 because the first byte is wrong? Should I use Normal mode instead of Circular? In addition, if I use the void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) interrupt, then the code is in this interrupt 90% of the time, which is logical, but how to implement otherwise - difficulties arise.

If you recommend using another library (LL, SPL or CMSIS), please provide a minimally reproducible example, thanks.

I tried just getting the device ID:

uint8_t cmd_accelerometer_read_ID[2] = { LIS2DH12_WHO_AM_I_ADDR | 0x80, 0 };
uint8_t dma_received_data[2] = {0, 0};

HAL_SPI_TransmitReceive_DMA(&hspi1, cmd_accelerometer_read_ID, dma_received_data, 2);

dma_received_data[0] always zero (as expected), dma_received_data[1] always 0xFF (255), expected 0x33.


Solution

  • In general, the problem was solved a long time ago, but I could only share it now.

    Digital accelerometers like LIS2... have on board interrupt output, in this case 11 (INT2) and 12 (INT1). They can be used in different ways, including to notify the microcontroller of the readiness of data (CTRL_REG3, bit I1_ZYXDA). In my case, the interrupt occurred once after the configuration of the sensor, so for 100% result about the readiness of data you need at least once to read the contents of registers OUT_X_L..OUT_Z_H.

    After receiving the notification there are several options for handling the interrupt. I copy the data directly from the temporary buffer (6 bytes) into the main buffer and increment the counter by 1. This looks like this:

    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
      if(GPIO_Pin == GPIO_PIN_1)
      {
        if(current_sample < SAMPLE_COUNT)
        {
          memcpy(&data_buffers[flag.current_buffer][6 * current_sample], buffer.data_temp, sizeof(buffer.data_temp)
        }
        current_sample++; // so 6 bytes its 1 sample
        Accel_IO_Read(&dev_ctx, LIS2DH12_OUT_X_L, buffer.data_temp, sizeof(buffer.data_temp)); // restart measurements
      }
    }
    

    Accel_IO_Read(...) is better used as a notification that it's time to collect data, rather than calling it as a separate function from an interrupt for obvious reasons.

    In this case I don't use DMA because for my tasks it does give an increase in performance but it increases power consumption which is unacceptable in the context of the project. However, to answer my own questions:

    Should I use DMA? Yes, it will offload the CPU significantly but in some cases it might be slower with DMA than without it.

    Do I have to read 7 bytes instead of 6? Yes, the first received byte is garbage.

    Should I use Normal mode instead of Circular? Yes, Circular mode is not needed in this case.

    Should I increment the DMA address? No, since the LIS2DH12 has multiple reading (28 page datasheet) you do not need to increment the peripheral address yourself.

    The SPI data acquisition algorithm using the DMA and the INT1 data readiness interrupt looks something like this:

    uint8_t buffer_source[7] = {0};
    uint8_t buffer_destination[2048] = {0};
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
        static uint8_t temp_buffer[7];
        temp_buffer[0] = 0x28 | 0x40 | 0x80;
        CS_DOWN();
        HAL_SPI_TransmitReceive_DMA(&hspi1, buffer_source, temp_buffer, 7);
    }
     
    void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
    {
        CS_UP();
        memcpy(buffer_destination + count_measures*6, buffer_source + 1, 6); // + 1 since the first byte is garbage
        count_measures++;
    }
     
    void main(void)
    {
        LIS2DH12_get_data();
        while(1)
        {
             processing_data();
        }
    }
    

    For this callback to work in Normal Mode, the global SPI interrupt must be enabled.