为什么从 STM32F407G-Disc1 MCU 上的 LIS3DSH 加速度计读取时我只得到 0xFF?
Why am I only getting 0xFF when reading from the LIS3DSH accelerometer on the STM32F407G-Disc1 MCU?
所以我正在学习嵌入式开发,最近学习了SPI的基础知识。作为一个项目,我想仅使用 CMSIS 接头连接我的 STM32F407G-DISC1 板上的 LIS3DSH 加速度计。
我在下面粘贴了整个代码,但我会先解释一下,因为没有人想阅读所有代码。
作为参考,这些是通过 SPI 进行通信所需的引脚(根据 MCU 的数据表):
- PA5 - SPI1_SCK
- PA7 - SPI1_MOSI
- PA6 - SPI1_MISO
- PE3 - CS_I2C/SPI
这是我在代码中执行的步骤:
- 使用 AHB1ENR 寄存器为 GPIOA 和 GPIOE 启用时钟。
- 对于GPIOA,我将三个引脚设置为复用功能,输出为推挽,速度低,没有pull-up/pull-down,并将复用功能配置为SPI。
- 对于GPIOE,设置为GPIO模式,推挽,低速,上拉,然后置高(如写入BSSR寄存器)。
- 使用 APB2ENR 寄存器为 SPI 启用时钟。
- 配置SPI1:首先禁用它,启用2线单向模式,设置波特率为fPCL/16,因为APB2外设时钟为84MHz,加速度计的最大时钟为10MHz。然后将时钟相位和极性设置为1。8位数据帧,MSB在前,启用软件slave管理,也启用master配置。最后,启用 SPI1。
- 完成所有这些后,我将 0x63 传输到加速度计的 0x20 寄存器。这会将输出速率设置为 100Hz 并启用 x 轴和 y 轴。我不知道这是否真的有效。我假设这是因为当我检查 SPI 状态寄存器时 TX 缓冲区为空。
- 然后为了测试是否可以接收,我尝试从加速度计的WHO_AM_I寄存器中获取数据。但是我调试时只看到垃圾数据(0xFF)。
我在谷歌上搜索了一下,想知道为什么会这样,很多人认为时钟极性和相位可能不正确。但是,我已经检查了很多次,而且我相当确定我的配置是正确的。
我试过设置中断。在中断期间,即使 RXNE(RX 缓冲区非空)为真,它仍然只读取 0xFF。我很困惑为什么会这样。
代码如下。起点是accelerometer_init()
。从WHO_AM_I寄存器读取数据在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;
}
您没有正确使用 SPI。
这辆巴士是这样工作的:
- 主机(MCU)在MOSI中发送字节
- 行在同一 (!) 时间从站 (LIS) 在 MISO 行中发送字节。此时slave不知道,master到底传给它的字节是什么
要传输一个字节,您应该:
- 写入数据寄存器中的字节
- 等待传输完成
- 读取数据寄存器
因此,读取WHO_AM_I寄存器,我们得到下一个序列:
- 初始化 SPI
- 刷新数据寄存器(只读SPI->DR)
- 发送命令
- 等待
- 读取虚拟数据(你的 0xFF)
- 写入第二个字节(0x00或0xFF,无所谓)
- 等待
- 从 LIS 阅读正确答案
所以我正在学习嵌入式开发,最近学习了SPI的基础知识。作为一个项目,我想仅使用 CMSIS 接头连接我的 STM32F407G-DISC1 板上的 LIS3DSH 加速度计。
我在下面粘贴了整个代码,但我会先解释一下,因为没有人想阅读所有代码。
作为参考,这些是通过 SPI 进行通信所需的引脚(根据 MCU 的数据表):
- PA5 - SPI1_SCK
- PA7 - SPI1_MOSI
- PA6 - SPI1_MISO
- PE3 - CS_I2C/SPI
这是我在代码中执行的步骤:
- 使用 AHB1ENR 寄存器为 GPIOA 和 GPIOE 启用时钟。
- 对于GPIOA,我将三个引脚设置为复用功能,输出为推挽,速度低,没有pull-up/pull-down,并将复用功能配置为SPI。
- 对于GPIOE,设置为GPIO模式,推挽,低速,上拉,然后置高(如写入BSSR寄存器)。
- 使用 APB2ENR 寄存器为 SPI 启用时钟。
- 配置SPI1:首先禁用它,启用2线单向模式,设置波特率为fPCL/16,因为APB2外设时钟为84MHz,加速度计的最大时钟为10MHz。然后将时钟相位和极性设置为1。8位数据帧,MSB在前,启用软件slave管理,也启用master配置。最后,启用 SPI1。
- 完成所有这些后,我将 0x63 传输到加速度计的 0x20 寄存器。这会将输出速率设置为 100Hz 并启用 x 轴和 y 轴。我不知道这是否真的有效。我假设这是因为当我检查 SPI 状态寄存器时 TX 缓冲区为空。
- 然后为了测试是否可以接收,我尝试从加速度计的WHO_AM_I寄存器中获取数据。但是我调试时只看到垃圾数据(0xFF)。
我在谷歌上搜索了一下,想知道为什么会这样,很多人认为时钟极性和相位可能不正确。但是,我已经检查了很多次,而且我相当确定我的配置是正确的。
我试过设置中断。在中断期间,即使 RXNE(RX 缓冲区非空)为真,它仍然只读取 0xFF。我很困惑为什么会这样。
代码如下。起点是accelerometer_init()
。从WHO_AM_I寄存器读取数据在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;
}
您没有正确使用 SPI。
这辆巴士是这样工作的:
- 主机(MCU)在MOSI中发送字节
- 行在同一 (!) 时间从站 (LIS) 在 MISO 行中发送字节。此时slave不知道,master到底传给它的字节是什么
要传输一个字节,您应该:
- 写入数据寄存器中的字节
- 等待传输完成
- 读取数据寄存器
因此,读取WHO_AM_I寄存器,我们得到下一个序列:
- 初始化 SPI
- 刷新数据寄存器(只读SPI->DR)
- 发送命令
- 等待
- 读取虚拟数据(你的 0xFF)
- 写入第二个字节(0x00或0xFF,无所谓)
- 等待
- 从 LIS 阅读正确答案