爱通信的小汪同学 2025-08-20 13:56 采纳率: 0%
浏览 11
已结题

HAL库的SPI发送问题

HAL_SPI_TransmitReceive()函数替换寄存器库读写结果不一致问题。

源代码为野火的“[野火]《STM32 HAL库开发实战指南》(HAL库源码)\1-书籍配套例程-F103ZE霸道V1V2_20240311\24-SPI—读写串行FLASH(W25Q64)”,原来的代码是查询硬件SPI的状态,根据状态发送相关字节,然后我就想试着直接使用HAL_SPI_TransmitReceive()去替代原来较长的代码,发现不行,不知道为啥,有佬懂的吗?
以下是代码对比:

img


上部分是我想利用HAL库自带的函数硬件发送数据,下部分为野火官方写的寄存器库使用的方法

img


hal库结果

img


寄存器库结果

  • 写回答

4条回答 默认 最新

  • 檀越@新空间 2025-08-20 13:57
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    你提到的问题是关于 HAL库的 HAL_SPI_TransmitReceive() 函数寄存器级别的 SPI 控制方式 在功能上不一致,导致发送数据结果不一致。这在嵌入式开发中是比较常见的问题,原因可能涉及以下几个方面:


    问题分析

    1. 函数使用方式不同

    • 寄存器方式:直接操作 SPI 的寄存器(如 SPIx->DR),通过手动控制 SPI 状态(如 SPI_I2S_GetFlagStatus(SPIx, SPI_FLAG_TXE))来判断是否可以发送下一个字节。
    • HAL 库方式:使用 HAL_SPI_TransmitReceive() 是一个阻塞函数,会等待传输完成才返回。如果使用不当,可能会出现 超时、未正确配置或未正确处理中断/状态

    2. SPI 模式设置错误

    HAL 库中需要确保以下配置一致:

    • SPI 模式(主/从)
    • 数据帧格式(8位/16位)
    • 时钟极性(CPOL)和相位(CPHA)
    • 方向(全双工/半双工)

    如果这些参数与实际硬件(如 W25Q64)不匹配,会导致通信失败。


    3. DMA 或中断未启用

    如果你使用的是 HAL_SPI_TransmitReceive(),但没有启用 DMA 或中断,那么该函数将 阻塞等待,直到传输完成。如果在某些情况下(如 SPI 处于忙状态),可能导致程序卡死或数据未正确发送。


    4. 数据长度或地址设置错误

    W25Q64 是一个串行 Flash 芯片,读写操作通常包括:

    • 命令字节
    • 地址字节(3 字节)
    • 数据字节

    例如:

    uint8_t cmd[4] = {0x03, 0x00, 0x00, 0x00}; // 读指令 + 地址
    

    如果 HAL_SPI_TransmitReceive()TxDataRxData 长度不对,或者顺序错误,也会导致结果不一致。


    解决方案(重点步骤)

    1. 确保 HAL_SPI_Init() 正确配置 SPI 参数

    SPI_HandleTypeDef hspi1;
    
    void MX_SPI1_Init(void)
    {
        hspi1.Instance = SPI1;
        hspi1.Init.Mode = SPI_MODE_MASTER;              // 主模式
        hspi1.Init.Direction = SPI_DIRECTION_2LINES;     // 全双工
        hspi1.Init.DataSize = SPI_DATASIZE_8BIT;        // 8位数据
        hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;       // CPOL=0
        hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;          // CPHA=1
        hspi1.Init.NSS = SPI_NSS_SOFT;                   // 软件控制 NSS
        hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 16分频
        hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;         // MSB First
        hspi1.Init.TIMode = SPI_TIMODE_DISABLE;         // 不使用 TI 模式
        hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 不使用 CRC
        hspi1.Init.CRCPolynomial = 7;
    
        if (HAL_SPI_Init(&hspi1) != HAL_OK)
        {
            Error_Handler();
        }
    }
    

    重点: 必须确保 hspi1.Init.CLKPolarityCLKPhase 与 W25Q64 的要求一致。


    2. 使用 HAL_SPI_TransmitReceive() 发送命令和地址

    示例代码(替代野火原始寄存器代码):

    // 定义要发送的数据(命令+地址)
    uint8_t txData[4] = {0x03, 0x00, 0x00, 0x00}; // 读操作
    uint8_t rxData[4];                            // 接收数据
    
    // 调用 HAL 函数
    HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 4, 100);
    
    if (status == HAL_OK)
    {
        // 成功接收到数据
        for (int i = 0; i < 4; i++)
        {
            printf("rxData[%d] = 0x%02X\n", i, rxData[i]);
        }
    }
    else
    {
        printf("SPI TransmitReceive Failed!\n");
    }
    

    重点: 一定要检查返回值 HAL_SPI_TransmitReceive(),并确保超时时间足够。


    3. 检查 NSS 引脚控制

    HAL 库默认使用软件控制 NSS(SPI_NSS_SOFT),你需要手动拉低 NSS 引脚后再发送数据,发送完后拉高。

    // 拉低 NSS
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 假设 NSS 连接到 PA4
    
    // 发送数据
    HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 4, 100);
    
    // 拉高 NSS
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    

    重点: 如果 NSS 未正确控制,SPI 通信将失败。


    4. 对比野火原代码(寄存器方式)

    野火代码中可能有如下结构(以读操作为例):

    // 使能 SPI
    SPI_Cmd(SPI1, ENABLE);
    
    // 发送命令字节
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, 0x03);
    
    // 发送地址字节
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, 0x00);
    
    // ...
    

    重点: 野火代码是逐字节发送,而 HAL_SPI_TransmitReceive() 是一次性发送整个缓冲区。


    总结对比

    | 项目 | HAL_SPI_TransmitReceive() | 寄存器方式 | |------|--------------------------|-------------| | 是否自动处理状态 | 是(阻塞) | 需手动轮询标志位 | | 数据发送方式 | 批量发送 | 逐字节发送 | | NSS 控制 | 需手动控制 | 通常由硬件自动处理 | | 代码简洁性 | 更简洁 | 更冗长 | | 易错点 | 配置参数、超时、NSS 控制 | 状态判断复杂 |


    推荐做法

    如果你希望简化代码,并且 SPI 配置正确,建议使用 HAL_SPI_TransmitReceive(),但必须注意以下几点:

    1. 确保 SPI 初始化参数正确
    2. 手动控制 NSS 引脚
    3. 检查返回值并处理超时
    4. 发送数据长度与接收长度一致

    修改后的完整示例代码(适用于 W25Q64 读取)

    #include "main.h"
    #include "stdio.h"
    
    SPI_HandleTypeDef hspi1;
    
    void SystemClock_Config(void);
    static void MX_GPIO_Init(void);
    static void MX_SPI1_Init(void);
    
    int main(void)
    {
        HAL_Init();
        SystemClock_Config();
        MX_GPIO_Init();
        MX_SPI1_Init();
    
        uint8_t txData[4] = {0x03, 0x00, 0x00, 0x00}; // 读操作
        uint8_t rxData[4];
    
        // 拉低 NSS
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
    
        // 发送命令和地址
        HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 4, 100);
    
        // 拉高 NSS
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
    
        // 输出接收到的数据
        for (int i = 0; i < 4; i++)
        {
            printf("rxData[%d] = 0x%02X\n", i, rxData[i]);
        }
    
        while (1);
    }
    
    // ...(其余初始化函数略)
    
    

    如有更多代码细节(如具体引脚、SPI 配置等),欢迎继续提供,我可以帮你进一步调试。

    评论

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 9月8日
  • 创建了问题 8月20日