为啥从 STM32F407G-Disc1 MCU 上的 LIS3DSH 加速度计读取时我只得到 0xFF?

Posted

技术标签:

【中文标题】为啥从 STM32F407G-Disc1 MCU 上的 LIS3DSH 加速度计读取时我只得到 0xFF?【英文标题】:Why am I only getting 0xFF when reading from the LIS3DSH accelerometer on the STM32F407G-Disc1 MCU?为什么从 STM32F407G-Disc1 MCU 上的 LIS3DSH 加速度计读取时我只得到 0xFF? 【发布时间】:2018-11-24 20:21:42 【问题描述】:

所以我正在学习嵌入式开发,最近学习了 SPI 的基础知识。作为一个项目,我想仅使用 CMSIS 接头与我的 STM32F407G-DISC1 板上的 LIS3DSH 加速度计进行通信。

我在下面粘贴了整个代码,但我会先解释一下,因为没有人愿意阅读所有这些代码。

作为参考,这些是通过 SPI 通信所需的引脚(根据 MCU 的数据表):

PA5 - SPI1_SCK PA7 - SPI1_MOSI PA6 - SPI1_MISO PE3 - CS_I2C/SPI

这是我在代码中采取的步骤:

    使用 AHB1ENR 寄存器为 GPIOA 和 GPIOE 启用时钟。 对于GPIOA,我将三个引脚设置为复用功能,输出为推挽,速度为低,无上拉/下拉,复用功能配置为SPI。 对于GPIOE,将其设置为GPIO模式,推挽,低速,上拉,然后设置为高(如写入Bs-s-r寄存器)。 使用 APB2ENR 寄存器启用 SPI 时钟。 配置 SPI1:首先禁用它,启用 2 线单向模式,将波特率设置为 fPCL/16,因为 APB2 外设时钟为 84MHz,加速度计的最大时钟为 10MHz。然后将时钟相位和极性设置为 1。 8 位数据帧,MSB 在前,启用软件从机管理,同时启用主机配置。最后,启用 SPI1。 在这之后,我将 0x63 传输到加速度计的 0x20 寄存器。这会将输出速率设置为 100Hz,并同时启用 x 轴和 y 轴。我不知道这是否真的有效。我假设这是因为当我检查 SPI 状态寄存器时 TX 缓冲区是空的。 然后为了测试是否可以接收,我尝试从加速度计的 WHO_AM_I 寄存器中获取数据。但是我只在调试时看到垃圾数据(0xFF)。

我用谷歌搜索了为什么会这样,很多人建议时钟极性和相位可能不正确。但是,我已经检查了多次,并且我相当确定我正确配置了它。

我试过设置中断。在中断期间,即使 RXNE (RX buffer not empty) 为真,它仍然只读取 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;

【问题讨论】:

使用逻辑分析仪或示波器来验证信号是否按预期工作。 CS是不是低了? SCK 切换了吗? MOSI/MISO 上的数据是您所期望的吗?尝试时钟极性和相位的其他三种组合不会有什么坏处。 我同意@kkrambo。最好的办法是用示波器探测电线。如果您只收到0xFF,那么在我看来加速度计没有响应,因为这可能只是线路的默认状态(高数据空闲或上拉)。 确实,如果您没有范围,您甚至无法开始考虑这样的应用程序。它是所有嵌入式软件开发的必备工具。 任何 GPIO 在任何特定“速度”设置下的最大频率取决于您的电源电压和负载电容。在您的时钟频率(5.25MHz?)下,“低速”设置充其量是微不足道的。您应该至少使用“中”速度模式。这就是示波器对于验证信号完整性和时序至关重要的地方。如果线路太“慢”,​​则时钟信号可能由于压摆率过低而无效。 ... 或进一步划分 PCLK - 您不需要非常高的速率来及时获取加速度计数据。对于两个 100sps 的 16 位寄存器,10KHz 已经足够快了。 【参考方案1】:

您使用 SPI 不正确。

这条总线是这样工作的:

主控(MCU)在 MOSI 中发送字节 在同一行 (!) 从机 (LIS) 在 MISO 行中发送字节。此时从机不知道,主机向它传输的究竟是什么字节。

要传输一个字节,您应该:

在数据寄存器中写入字节 等待传输完成 读取数据寄存器

因此,为了读取 WHO_AM_I 寄存器,我们得到下一个序列:

初始化 SPI 刷新数据寄存器(只需读取 SPI->DR) 发送命令 等一下 读取虚拟数据(你的 0xFF) 写入第二个字节(0x00 或 0xFF,没关系) 等一下 从 LIS 中读取正确答案

【讨论】:

以上是关于为啥从 STM32F407G-Disc1 MCU 上的 LIS3DSH 加速度计读取时我只得到 0xFF?的主要内容,如果未能解决你的问题,请参考以下文章

极海APM32F407xG系列Cortex-M4工业级MCU-可兼容替换STM32F407xG

LVGL学习笔记 | 02 - 移植LVGL 8.2到STM32F407开发板(MCU屏)

为啥我的STM32F407发现刷机软件重新插线后生效?

STM32F407之搭建工程

stm32f407v与stm32f407zg的区别

stm32 f103和f407的区别?