FreeRTOS下SPI读写FLASH的坑:任务切换导致数据全FF的解决方案
最近在做一个基于STM32和W25Q128的项目,用FreeRTOS管理几个并发的任务。其中一个数据采集任务需要频繁地将传感器数据写入外部FLASH,另一个任务则负责在需要时读取这些数据。调试初期,一切看起来都很顺利,直到我开始进行长时间的压力测试。系统偶尔会莫名其妙地重启,看门狗超时了。更诡异的是,重启后读取FLASH里的数据,有时会得到一整片0xFF,仿佛数据从未被写入过。这可不是简单的硬件故障,因为单步调试时写入读取都完全正常。问题只在高负载、多任务并发运行时才显现。如果你也在FreeRTOS环境下被SPI设备(尤其是FLASH)的“灵异”读写错误困扰,特别是那种“全FF”或随机错误,那么你很可能踩进了任务调度与SPI时序冲突这个经典的坑里。这篇文章,我就结合自己的踩坑经历,聊聊问题的根源,并对比几种保护方案的优劣与实战细节。
1. 问题现象与根源剖析:当FreeRTOS遇上SPI时序
最初发现问题时,现象非常具有迷惑性。我的数据记录任务大致逻辑如下:
void data_logger_task(void *pvParameters) {
while(1) {
// 采集数据
acquire_sensor_data(&buffer);
// 写入FLASH
W25QXX_Write(buffer, current_addr, DATA_SIZE);
current_addr += DATA_SIZE;
vTaskDelay(pdMS_TO_TICKS(100));
}
}
同时,一个通信任务会在收到指令后,读取特定地址的数据并上传:
void comm_task(void *pvParameters) {
while(1) {
if (rx_command == READ_DATA) {
W25QXX_Read(read_back_buffer, requested_addr, DATA_SIZE);
// 发送数据...
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
在轻负载下,系统运行数小时无异常。但当我增加任务数量,或者让数据记录任务以更高频率(比如去掉vTaskDelay)连续写入时,系统就开始不稳定。最直接的崩溃表现是看门狗复位。通过调试器追踪,发现程序卡在了某个任务的vTaskDelay内部,或者更准确地说,卡在了任务切换相关的列表操作中。但这只是表象,根源在于SPI的读写被打断了。
SPI(Serial Peripheral Interface)通信是一种全双工、同步、串行的通信方式。一次完整的字节传输,要求时钟(SCLK)、片选(CS)、数据输入(MISO)和数据输出(MOSI)信号之间保持严格的时序关系。以常见的STM32标准SPI库函数操作为例:
uint8_t SPI_ReadWriteByte(SPI_TypeDef* SPIx, uint8_t TxData) {
// 等待发送缓冲区为空
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
// 写入数据,启动传输
SPI_I2S_SendData(SPIx, TxData);
// 等待接收缓冲区非空
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);
// 读取接收到的数据
return (uint8_t)SPI_I2S_ReceiveData(SPIx);
}
这段代码在裸机程序中毫无问题。但在FreeRTOS中,while循环等待标志位的位置,正是任务切换可能发生的“窗口”。FreeRTOS的调度器可以在任何地方发生任务切换,只要它没有被明确禁止。当任务A正在执行SPI_ReadWriteByte函数,刚刚发送完数据(SPI_I2S_SendData),在等待接收完成(第二个while循环)时,如果系统定时器滴答(Tick)中断到来,调度器就可能认为任务A的时间片用完,或有一个更高优先级的任务就绪,从而强行保存任务A的上下文(寄存器状态),切换到另一个任务B。
注意:对于W25Q128这类SPI FLASH,一次写入或读取操作往往由多个字节命令组成。例


170

被折叠的 条评论
为什么被折叠?



