AnsweredAssumed Answered

Why am I getting garbage data reading from the LIS3DSH accelerometer with the STM32F407G MCU?

Question asked by Jay Gaz on Jun 12, 2018
Latest reply on Jun 15, 2018 by AvaTar

I'm very much new to embedded development, so apologies if I'm making a basic mistake.

 

I'm learning the SPI communication protocol, and I wanted to read data from the LIS3DSH accelerometer. Additionally, I'm only using the CMSIS headers in order to really understand what is going on.

I started writing a simple driver as follows (you don't have to read the whole thing, I have an explanation after):

 

 

#include <stdint.h>
#include <stdbool.h>
#include "stm32f4xx.h"
#include "accelerometer.h"
#include "core_cm4.h"

 

static void gpio_clock_enable(void);
static void gpio_a_init(void);
static void gpio_e_init(void);
static void accelerometer_clock_enable(void);
static void configure_accelerometer(void);
static void pull_slave_high(void);
static void pull_slave_low(void);
static void turn_on_accelerometer(void);
static void wait_till_transmit_complete(void);
static void transmit_only(uint8_t address, uint8_t data);
static void receive_dummy_data(void);

 

void accelerometer_init(void) {
    NVIC_EnableIRQ(SPI1_IRQn);

    gpio_clock_enable();
    gpio_a_init();
    gpio_e_init();

    accelerometer_clock_enable();
    configure_accelerometer();
    turn_on_accelerometer();
}

 

void gpio_clock_enable(void) {
    RCC_TypeDef *rcc = RCC;
    rcc->AHB1ENR |= (1 << 0);
}

 

void gpio_a_init(void) {
    GPIO_TypeDef *gpio_a = GPIOA;

 

    // Reset mode and set as alternate function
    gpio_a->MODER &= ~(0x3 << 10) & ~(0x3 << 12) & ~(0x3 << 14);
    gpio_a->MODER |= (0x2 << 10) | (0x2 << 12) | (0x2 << 14);

 

    // Set output to PP
    gpio_a->OTYPER &= ~(1 << 5) & ~(1 << 6) & ~(1 << 7);

 

    // Set speed to high
    gpio_a->OSPEEDR |= (0x3 << 10) | (0x3 << 12) | (0x3 << 14);

 

    // Reset pull-up/pull-down and set to pull-down
    gpio_a->PUPDR &= ~(0x3 << 10) & ~(0x3 << 12) & ~(0x3 << 14);
    gpio_a->PUPDR |= (0x2 << 10) | (0x2 << 12) | (0x2 << 14);

 

    // Reset alternate function and set to SPI
    gpio_a->AFR[0] &= ~(0xF << 20) & ~(0xF << 24) & ~(0xF << 28);
    gpio_a->AFR[0] |= (0x5 << 20) | (0x5 << 24) | (0x5 << 28);
}

 

void gpio_e_init(void) {
    GPIO_TypeDef *gpio_e = GPIOE;

 

    // Set as general purpose output mode
    gpio_e->MODER &= ~(0x3 << 6);
    gpio_e->MODER |= (1 << 6);

 

    // Set as push pull
    gpio_e->OTYPER &= ~(1 << 3);

 

    // Set as high speed
    gpio_e->OSPEEDR |= (0x3 << 6);

 

    // Set as pull-up
    gpio_e->PUPDR &= ~(0x3 << 6);
    gpio_e->PUPDR |= (1 << 6);

 

    // Set it high
    pull_slave_high();
}

 

void accelerometer_clock_enable(void) {
    RCC_TypeDef *rcc = RCC;
    rcc->APB2ENR |= (1 << 12);
}

 

void configure_accelerometer(void) {
    SPI_TypeDef *spi_1 = SPI1;

 

    // First disable it while we configure SPI
    spi_1->CR1 &= ~(1 << 6);

 

    // 2-line unidirectional data mode enabled
    spi_1->CR1 &= ~(1 << 15);

 

    // Reset baud rate and set to fPCLK/16
    // because APB2 peripheral clock currently is 84 MHz
    // and the max clock of the accelerometer is 10 MHz.
    spi_1->CR1 &= ~(0x7 << 3);
    spi_1->CR1 |= (0x3 << 3);

 

    // Set clock phase to 1
    spi_1->CR1 |= (1 << 0);

 

    // Set clock polarity to 1
    spi_1->CR1 |= (1 << 1);

 

    // 8 bit data frame format
    spi_1->CR1 &= ~(1 << 11);

 

    // MSB first
    spi_1->CR1 &= ~(1 << 7);

 

    // Software slave management disabled
    spi_1->CR1 &= ~(1 << 9);

 

    // Master configuration enabled
    spi_1->CR1 |= (1 << 2);

 

    // Enable RX buffer not empty interrupt
    spi_1->CR2 |= (1 << 6);

 

    // SS output enabled
    spi_1->CR2 |= (1 << 2);

 

    // Enable SPI
    spi_1->CR1 |= (1 << 6);

 

    // Wait a little bit for accelerometer to turn on
    for (int i=0; i<1000000; i++);
}

 

void pull_slave_high(void) {
    // Wait until SPI is no longer busy
    SPI_TypeDef *spi_1 = SPI1;
    while ((spi_1->SR >> 7) & 1);

 

    GPIO_TypeDef *gpio_e = GPIOE;
    gpio_e->BSRR |= (1 << 19);
}

 

void pull_slave_low(void) {
    // Wait until SPI is no longer busy
    SPI_TypeDef *spi_1 = SPI1;
    while ((spi_1->SR >> 7) & 1);

 

    GPIO_TypeDef *gpio_e = GPIOE;
    gpio_e->BSRR |= (1 << 3);
}

 

void turn_on_accelerometer(void) {
    // Set output data rate to 100Hz
    // and enable X-axis, Y-axis.
    transmit_only(0x20, 0x63);
    receive_dummy_data();

 

    //test
    SPI_TypeDef *spi_1 = SPI1;
    pull_slave_low();
    wait_till_transmit_complete();
    uint8_t address = 0x0F | 0x80;
    spi_1->DR = address;
    wait_till_transmit_complete();

    while (true) {
        volatile bool is_busy = (spi_1->SR >> 7) & 1;
        volatile bool is_rx_buffer_not_empty = (spi_1->SR >> 0) & 1;

        if (!is_busy && is_rx_buffer_not_empty) {
            break;
        }
    }

    pull_slave_high();
    volatile uint32_t data = spi_1->DR; // BAD DATA HERE!!
}

 

/*
* Transmit is synchronous.
*/
void transmit_only(uint8_t address, uint8_t data) {
    SPI_TypeDef *spi_1 = SPI1;

 

    // Select the accelerometer as the slave
    pull_slave_low();

 

    // Wait till transmit buffer is ready
    wait_till_transmit_complete();

 

    spi_1->DR = address;

 

    // Wait till transmit buffer is ready
    wait_till_transmit_complete();

 

    spi_1->DR = data;

 

    // Wait till transmit buffer has been read
    wait_till_transmit_complete();

 

    // Deselect the slave
    pull_slave_high();
}

 

void wait_till_transmit_complete(void) {
    SPI_TypeDef *spi_1 = SPI1;

    while (true) {
        volatile bool is_busy = (spi_1->SR >> 7) & 1;
        volatile bool is_transmit_buffer_empty = (spi_1->SR >> 1) & 1;

        if (!is_busy && is_transmit_buffer_empty) {
            break;
        }
    }
}

 

void receive_dummy_data(void) {
    SPI_TypeDef *spi_1 = SPI1;
    uint8_t dr = spi_1->DR;
    uint8_t sr = spi_1->SR;
}

 

In main() (which is not included above), I only call accelerometer_init(). This will encapsulate all the logic of enabling the clock, configuring the various pins, SPI configuration, and even turning on the accelerometer. 

I have doubled checked the reference manuals of both the MCU and the accelerometer to ensure I'm configuring them properly, unless I'm misunderstanding some concept.

 

Finally, in turn_on_accelerometer(), I transmit some data to the accelerometer to power it up and enable the x/y axes. After, as a simple test, I try to receive data from the WHO_AM_I register of the accelerometer. However, now I only get 0xFF as the data when I debug the program.

 

I've googled around trying to find out what I did wrong. People have suggested that the clock polarity or clock phase may be wrong, but I'm fairly certain I configured them properly. But clearly there's something wrong, otherwise I would get something other than 0xFF.

 

So please let me know what I'm doing wrong. I've been going through this code for around 3 days now, and I'm completely stumped.

 

Let me know if I need to make any clarifications.

Thanks!

Outcomes