1. 为什么需要SPI+DMA来读写外部Flash?
在实际的嵌入式开发中,我们经常需要与外部Flash芯片进行数据交互,尤其是在OTA固件更新、数据存储等场景下。如果你只是偶尔读写几个字节的数据,使用STM32的硬件SPI配合查询方式完全够用,简单直接。但当你需要处理大量数据时,比如一次性写入几百KB的固件文件,如果还让CPU傻傻地等待每个字节的传输完成,那简直就是对处理器资源的极大浪费。
这时候DMA(直接存储器访问)就派上用场了。DMA可以在不需要CPU干预的情况下,直接在存储器和外设之间传输数据。想象一下,你让一个专业的搬运工(DMA)来帮你搬箱子(数据),而你(CPU)可以腾出手来做其他更重要的事情,比如处理用户输入或者运行复杂的算法。这种分工协作的方式大大提高了系统效率。
我最早在OTA项目中使用SPI+DMA方案时,发现系统响应速度明显提升。原本在写入固件时界面会卡顿的问题得到了完美解决,这就是DMA的威力所在。
2. 硬件SPI与DMA的协同工作原理
2.1 SPI全双工通信的特殊性
SPI是一种全双工通信协议,这意味着在通信过程中,数据是同时发送和接收的。这个特性在使用DMA时需要特别注意,因为你不能只配置发送而不配置接收,或者反过来。
在实际配置中,我们需要同时设置两个DMA流:一个用于发送(TX),一个用于接收(RX)。即使你只是想要读取数据,SPI也会在接收数据的同时发送数据,通常是0xFF(空字节)。相反,当你写入数据时,SPI也会同时接收数据,虽然这些接收到的数据可能并不重要。
2.2 DMA通道选择策略
不同的STM32型号在DMA配置上有些差异。以STM32F407为例,打开数据手册的DMA章节,找到请求映射表,我们可以看到SPI1的RX和TX请求都映射到了通道3。这意味着我们可以使用DMA2的Stream0和Stream3来分别处理SPI1的接收和发送。
选择DMA流时需要注意,同一通道的不同流不能同时使用,因为它们共享相同的通道资源。这就好比一条高速公路(通道)上有多个车道(流),但同一时间每个车道只能有一辆车行驶。
我在实际项目中通常这样配置:
- SPI1_RX使用DMA2_Stream0
- SPI1_TX使用DMA2_Stream3
这样的配置在实践中表现稳定,中断冲突的概率很小。
3. 详细配置步骤与代码实现
3.1 DMA初始化配置
配置DMA是整个过程中最关键的环节。我们需要仔细设置每个参数,确保数据传输的准确性和效率。以下是我在项目中经过验证的配置代码:
void flash_start_dma(uint8_t *buffer, uint32_t length, uint8_t isread)
{
uint32_t temp = 0xffffffff;
DMA_InitTypeDef DMA_InitStructure;
// 初始化DMA结构体
DMA_StructInit(&DMA_InitStructure);
DMA_DeInit(DMA2_Stream0); // RX流
DMA_DeInit(DMA2_Stream3); // TX流
// 等待DMA流禁用完成
while(DMA_GetCmdStatus(DMA2_Stream0) != DISABLE);
while(DMA_GetCmdStatus(DMA2_Stream3) != DISABLE);
/* 配置SPI1_RX DMA */
DMA_InitStructure.DMA_Channel = DMA_Channel_3;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
if (isread == 1) {
// 读操作:将接收到的数据存储到buffer中
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
} else {
// 写操作:接收的数据丢弃,地址不递增
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp;



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



