ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F429 DMA读写W25Q128 SPI Flash高效实现

STM32F429 DMA读写W25Q128 SPI Flash高效实现 简介面向STM32F429嵌入式开发者一套围绕DMA与W25Q128 SPI闪存大数据读写的工程资源解决CPU频繁介入数据搬运导致的实时性下降问题适合需要大容量外部存储和高速传输的中级嵌入式项目也可作为课程设计或电子竞赛的外扩存储参考方案。压缩包共192个文件其中58个h头文件与49个c源码文件构成完整固件工程另有20个调试配置文件、19个crf编译输出、12个s汇编文件与最终axf可执行映像整体4.67MB目录结构清晰便于快速定位和查阅。目前已有3082人学习下载。资料提供SPI与DMA通道初始化、传输方向与数据宽度配置、W25Q128读写命令时序、错误中断处理和循环缓冲示例并附带uvprojx/uvoptx工程文件、map/lst映射列表、ini初始化脚本读者可对照工程实际配置逐段调试快速掌握DMA与SPI Flash高效交互的驱动开发方法提升外设数据吞吐能力其中关键驱动代码注释详细、易于移植可直接在开发板上验证效果明显缩短同类存储项目的开发周期。该工程在真实项目中经过验证可为嵌入式存储扩展设计提供直接参考。 做数据记录设备时我需要在 STM32F429 上把大量采集数据持续写入 W25Q128。这块 SPI NOR Flash 容量有 16MB理论 SPI 时钟能跑到几十 MHz但第一次用轮询方式一字节一字节写数据时CPU 几乎被 SPI 占满180MHz 主频的芯片活活被拖成了“等待状态”。后来把读写流程改成 DMA 搬运情况立刻不一样启动 DMA 之后 CPU 该干嘛干嘛数据搬运由 DMA 控制器完成大容量写入的效率才真正跑起来。这篇我主要分享 STM32F429 DMA 读写 W25Q128 的完整实现思路包括为什么读 Flash 不能只开接收 DMA、页编程和扇区擦除的边界怎么处理、DMA 完成回调里该做什么不该做什么以及我实际调试中踩过的一堆坑。适合正在用 F4 系列做数据记录、固件升级、离线存储的开发者参考也适合刚接触 SPI DMA 的朋友理解原理。1. 项目背景与核心需求拆解1.1 为什么选 W25Q128为什么需要 DMAW25Q128 是华邦推出的 SPI NOR Flash容量 128Mbit也就是 16MB。它支持标准 SPI、Dual SPI、Quad SPI页编程固定 256 字节扇区擦除固定 4KB。对于很多嵌入式项目来说16MB 足够存音频片段、传感器历史数据、UI 图片资源或者 Bootloader 升级包性价比很高。但我刚开始用的时候犯了个错直接用阻塞式 SPI 发数据结果写 256 字节页要等 SPI 一字节一字节发完再等 Flash 内部编程完成CPU 全程空转。如果系统里还有显示刷新、按键扫描、通信协议处理整个程序就被拖垮了。DMA 解决的就是这个“搬运问题”。STM32F429 的 DMA 控制器可以不经过 CPU直接把内存里的数据搬到 SPI 数据寄存器或者把 SPI 收到的数据搬到内存。CPU 只需要在传输开始前配置好源地址、目的地址、长度然后去做别的事等 DMA 传输完成中断再回来处理结果。1.2 这类方案典型的应用场景数据记录仪传感器连续采集数据按块写入 FlashDMA 可以把采集缓冲区和 Flash 写入解耦。音频离线存储ADC 采样数据持续写入DMA 保证采样不丢点。固件升级上位机分包下发固件数据先暂存到外部 Flash升级时再读出写入内部 Flash。文件系统介质SPIFFS 或 LittleFS 跑在 W25Q128 上底层读写接口都用 DMA 实现。这些场景的共同点是数据量大、要求 CPU 利用率高。所以 DMA 不是锦上添花是这个场景下的标配。2. SPI 与 DMA 的关键原理2.1 F429 上 SPI 的 DMA 请求通道STM32F429 有两个 DMA 控制器DMA1 和 DMA2每个控制器有 8 个 Stream每个 Stream 可以响应多个外设请求通过 Channel 号区分。SPI 和 DMA 的对应关系是固定的以最常用的 SPI1、SPI2、SPI3 为例外设DMA 控制器TX Stream / ChannelRX Stream / ChannelSPI1DMA2Stream3 / Channel3Stream0 / Channel3SPI2DMA1Stream4 / Channel0Stream3 / Channel0SPI3DMA1Stream6 / Channel0Stream1 / Channel0具体映射以参考手册里的 DMA request mapping table 为准不同型号之间可能有差异。配置 DMA 的核心原则有三个外设地址固定不动所以 PeriphInc 必须关闭内存地址按字节递增所以 MemInc 打开SPI 的数据寄存器是 8 位DMA 的数据宽度也设置成 Byte。2.2 读 Flash 为什么不能“只收不发”这是新手最容易卡住的地方。SPI 是全双工总线主设备要产生时钟必须有数据往 MOSI 上发。你读 W25Q128 的时候命令和地址是自己发的但后续数据阶段Flash 在 MISO 上返回数据靠的仍然是你这边提供的 SCLK 时钟。如果只开 RX DMA不往 TX 方向写任何数据SCLK 可能根本不产生自然收不到数据。实用的做法是发送端准备一个全 0xFF 的 dummy 缓冲区用 HAL_SPI_TransmitReceive_DMA 同时做发送和接收。发送的 dummy 字节在 MOSI 上产生时钟接收的数据从 MISO 进 DMA。虽然 HAL 库里也有 HAL_SPI_Receive_DMA但它的可用性取决于 SPI 是否配置成了接收方向模式而 TransmitReceive 是全双工通用写法在 F4 上兼容性最好也最容易排查我建议直接用它。2.3 两种 DMA 方案分段式与全 DMA 拼接实现上有两种思路。方案 A命令加地址用阻塞发送DMA 只负责数据阶段。优点是逻辑清晰代码容易调试命令只有四字节阻塞发送的开销可以忽略。缺点是一次传输分成两段CPU 在命令发送阶段还是有少量参与。方案 B把命令、地址、数据拼接成一个完整缓冲区一次性交给 TX DMA。优点是一整帧完全由 DMA 处理适合追求极限效率的场景。缺点是需要额外 memcpy 组装数据读操作还要处理命令阶段返回的垃圾字节复杂度明显上升。我实际项目里默认用方案 A只有需要进一步压榨性能时才考虑方案 B。后面章节先详细讲方案 A再把方案 B 的思路单独说。3. 实操分段式 DMA 读写 W25Q1283.1 硬件连接与 CubeMX 配置我用的连接是 SPI1对应引脚为信号引脚SCKPA5MISOPA6MOSIPA7CSPA4软件控制 GPIOCubeMX 里 SPI1 配置成 Full-Duplex MasterCPOL 选 LowCPHA 选 1 EdgeNSS 选 Software分频先选 2 或 4。DMA Settings 里分别添加 SPI1_RX 和 SPI1_TXMode 用 Normal数据宽度都是 Byte优先级给 High。生成工程后代码里会看到hdma_spi1_tx和hdma_spi1_rx两个 DMA 句柄并且自动和 SPI1 句柄关联在一起。在 HAL 库里这个关联动作靠的是__HAL_LINKDMA本质是把 DMA 句柄的指针挂到外设句柄的hdmatx、hdmarx字段上。不只是 SPIUART、I2C、DAC 都是同一套机制。所以网上看到“dma 句柄与 dac 句柄关联”“串口 dma 发送完成中断”这类问题底层逻辑都一样。__HAL_LINKDMA(hspi1, hdmatx, hdma_spi1_tx); __HAL_LINKDMA(hspi1, hdmarx, hdma_spi1_rx);3.2 基础命令封装W25Q128 的常用命令不多我整理成一张表方便对照命令码功能说明0x06Write Enable写操作前必须发否则写不进去0x05Read Status Register轮询 bit0判断 Flash 是否忙0x02Page Program页编程最大 256 字节0x03Read Data普通读数据0x20Sector Erase4KB 扇区擦除0x66 / 0x99Enable Reset / Reset Device异常恢复用基础封装里CS 拉低表示开始一帧命令CS 拉高表示命令结束。W25Q128 要求整个命令加数据过程 CS 一直保持低中途拉高会被判定为命令终止这个点后面排查问题时会反复遇到。static void flash_cs_low(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); } static void flash_cs_high(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); } static void flash_write_enable(void) { uint8_t cmd 0x06; flash_cs_low(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); flash_cs_high(); } static void flash_wait_busy(uint32_t timeout) { uint8_t cmd 0x05; uint8_t status 0; do { flash_cs_low(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, status, 1, HAL_MAX_DELAY); flash_cs_high(); if (--timeout 0) { break; } } while (status 0x01); }3.3 DMA 写页编程实现页编程的流程是写使能然后 CS 拉低发 0x02 命令加 24 位地址再用 DMA 发送最多 256 字节数据。关键点是 CS 拉高不能放在调用 DMA 之后立刻执行因为HAL_SPI_Transmit_DMA只是启动了 DMA传输还在后台进行。正确做法是放在HAL_SPI_TxCpltCallback回调里等 DMA 真正搬完数据再拉高。static uint8_t dma_tx_done 0; void flash_page_program_dma(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; flash_write_enable(); cmd[0] 0x02; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; dma_tx_done 0; flash_cs_low(); HAL_SPI_Transmit(hspi1, p a hrefhttps://download.csdn.net/download/zoujiao6609/10608593 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表