So I'm learning embedded development, and I recently learned the basics of SPI. As a project, I wanted to communicate with the LIS3DSH accelerometer on my STM32F407G-DISC1 board using only the CMSIS headers.
I pasted the entire code below, but I'll explain it first because no one wants to read all that code.
As a reference, these are the pins needed (according to the MCU's datasheet) to communicate via SPI:
- PA5 - SPI1_SCK
- PA7 - SPI1_MOSI
- PA6 - SPI1_MISO
- PE3 - CS_I2C/SPI
Here's the steps I took in my code:
- Enabled the clock for GPIOA and GPIOE using the AHB1ENR register.
- For GPIOA, I set the three pins as alternate function, output is push-pull, speed is low, no pull-up/pull-down, and configured the alternate function as SPI.
- For GPIOE, set it as GPIO mode, push-pull, low speed, pull-up, and then set it high (as in wrote to the BSSR register).
- Enabled the clock for SPI using the APB2ENR register.
- Configured SPI1: first disabled it, enabled 2-line unidirectional mode, set baud rate to fPCL/16 since the APB2 peripheral clock is 84MHz and the max clock of the accelerometer is 10MHz. Then set clock phase and polarity to 1. 8-bit data frame, MSB first, enabled software slave management, and also enabled master configuration. Finally, enabled SPI1.
- After all this, I transmit 0x63 to the 0x20 register of the accelerometer. This sets the output rate to 100Hz and enables both the x and y axis. I have no idea if this is actually working. I'm assuming it is because the TX buffer is empty when I check the SPI status register.
- Then to test whether I can recieve, I attempt to get the data from the WHO_AM_I register of the accelerometer. But I only see garbage data when I debug it (0xFF).
I've googled around to see why this may be, and a lot of people suggested that the clock polarity and phase may be incorrect. However, I've checked it multiple times, and I'm fairly certain I configured it properly.
I've tried setting interrupts. During the interrupt, even though RXNE (RX buffer not empty) is true, it still reads only 0xFF. I'm stumped as to why this is happening.
The code is below. The starting point is accelerometer_init()
. The reading of the data from the WHO_AM_I register is in turn_on_accelerometer()
.
#include <stdint.h>
#include <stdbool.h>
#include "stm32f4xx.h"
#include "accelerometer.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) {
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) | (1 << 4);
}
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 low
gpio_a->OSPEEDR &= ~(0x3 << 10) & ~(0x3 << 12) & ~(0x3 << 14);
// Set to no pull-up / pull-down
gpio_a->PUPDR &= ~(0x3 << 10) & ~(0x3 << 12) & ~(0x3 << 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 low speed
gpio_e->OSPEEDR &= ~(0x3 << 6);
// Set to 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 enabled
spi_1->CR1 |= (1 << 9);
spi_1->CR1 |= (1 << 8);
// Master configuration enabled
spi_1->CR1 |= (1 << 2);
// 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();
// Temp test checking the WHO_AM_I register on the accelerometer.
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;
}
}
volatile uint32_t data = spi_1->DR;
pull_slave_high();
}
/*
* 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;
spi_1->DR;
spi_1->SR;
}
0xFF
then it seems to me that the accelerometer is not responding, as that could just be the default state of the line (high data idle, or pull-ups). – Hein Wessels