第4章 SPI总线驱动与调试:主从模式、相位极性、DMA传输与Flash读写
SPI这个接口,做嵌入式的人都不陌生。但说实话,真正把它调稳、调快,还能在工业现场不出问题,这里面门道不少。CH32V203的SPI模块继承了RISC-V内核的灵活性和沁恒一贯的外设设计思路,既有通用性,又有一些值得注意的细节。
这一章,我会从最基础的配置讲起,结合我实际调试中踩过的坑,带你一步步把SPI吃透。
4.1 SPI主从模式配置
CH32V203有3个SPI模块。SPI1挂在APB2上,SPI2和SPI3挂在APB1上。主频不同,这点在高速传输时要留意。
配置主模式,核心就几步:
- 时钟使能:RCC_APB2PeriphClockCmd或RCC_APB1PeriphClockCmd
- GPIO配置:SCK、MOSI设为推挽复用输出,MISO设为浮空输入
- SPI初始化:SPI_InitStructure设置模式、极性、相位、波特率
- 使能SPI:SPI_Cmd
从模式配置更简单,但有个关键点——片选信号必须由主机控制。从机模式下,NSS引脚的电平变化直接决定SPI是否响应。
主从模式差异速查表
| 项目 | 主模式 | 从模式 |
|---|---|---|
| 时钟来源 | 内部产生 | 外部SCK输入 |
| 波特率控制 | SPI_CR1寄存器 | 由主机决定 |
| NSS管理 | 软件或硬件输出 | 硬件输入 |
| 数据流方向 | 主动发起 | 被动响应 |
| 典型应用 | Flash、传感器、LCD | 多机通信、从设备 |
我个人习惯,主模式用软件管理NSS,这样灵活。从模式则必须硬件NSS,否则容易丢数据。
4.2 CPOL/CPHA相位极性详解
这个知识点,面试常考,项目里也常坑人。CPOL决定时钟空闲电平,CPHA决定数据采样时刻。
- CPOL=0:空闲时SCK为低电平
- CPOL=1:空闲时SCK为高电平
- CPHA=0:第一个时钟边沿采样数据
- CPHA=1:第二个时钟边沿采样数据
组合起来就是4种模式。大多数Flash芯片用模式0或模式3。模式0:CPOL=0, CPHA=0;模式3:CPOL=1, CPHA=1。这两种模式下,数据在上升沿采样,下降沿切换,时序最对称。
我曾经在一个项目中,把从设备的CPOL/CPHA搞反了。现象很诡异——能读到数据,但偶尔错一个字节。用示波器一看,采样点正好落在数据跳变沿上。嗯,这就是典型的时序余量不足。后来改成正确的模式,问题消失。
怎么确认?看从设备数据手册的时序图。手册上会标出SCK的极性和数据采样边沿。实在不行,用示波器抓一下,对比主机发出的波形和从机期望的波形。
4.3 DMA方式传输
SPI用轮询方式,CPU一直在等。数据量小还行,大了就浪费。DMA传输,说白了就是让DMA控制器帮你搬数据,CPU可以干别的。
CH32V203的SPI支持DMA请求。配置流程:
- 使能DMA时钟
- 配置DMA通道:源地址、目的地址、数据长度、传输方向
- 使能SPI的DMA发送或接收请求
- 启动DMA传输
这里有个坑——SPI的DMA传输必须同时开启发送和接收。因为SPI是同步协议,发一个字节的同时必须收一个字节。如果你只开发送DMA,接收缓冲区会溢出;只开接收DMA,发送线没有时钟。
我建议:用DMA环形缓冲区配合SPI中断。比如,接收DMA用循环模式,每次传输完成触发中断,在中断里处理数据。这样既保证了实时性,又减少了CPU占用。
DMA传输的另一个好处是速度稳定。轮询方式下,中断响应时间不确定,会导致SPI时钟抖动。DMA是硬件控制,时序一致性更好。
4.4 SPI Flash读写实战
W25Q系列Flash是SPI外设的经典案例。以W25Q64为例,容量8MB,支持标准SPI和Dual/Quad SPI。
基本操作流程:
- 写使能:发送0x06指令,等待WIP位清零
- 擦除:扇区擦除(0x20)、块擦除(0xD8)、整片擦除(0xC7)
- 页编程:发送0x02,地址+数据,每页256字节
- 读数据:发送0x03,地址+连续读取
读操作最简单,不需要先擦除。写操作必须先擦除再编程。擦除的最小单位是扇区(4KB)。
Flash操作时序要点
| 操作 | 指令码 | 地址长度 | 数据长度 | 典型时间 |
|---|---|---|---|---|
| 读ID | 0x9F | 0字节 | 3字节 | ~10μs |
| 读数据 | 0x03 | 3字节 | N字节 | ~50ns/字节 |
| 页编程 | 0x02 | 3字节 | 1-256字节 | ~0.5ms/页 |
| 扇区擦除 | 0x20 | 3字节 | 0字节 | ~45ms |
| 整片擦除 | 0xC7 | 0字节 | 0字节 | ~10s |
实际项目中,我习惯把Flash驱动封装成三层:
- 底层:SPI收发函数,直接操作寄存器
- 中间层:Flash指令封装,如flash_read、flash_write、flash_erase
- 应用层:文件系统或参数存储接口
这样做的好处是,换Flash芯片时只需要改中间层,上层代码不动。
注意:Flash写操作前一定要检查WIP位。我见过有人连续发写指令,结果数据全丢了。因为前一次写还没完成,后一次指令被忽略。加个超时等待,别死等。
4.5 知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作调试时的检查清单。
这张图把SPI调试的四个关键环节串起来了。从主从模式配置开始,到CPOL/CPHA的匹配,再到DMA提升效率,最后落到Flash读写这个具体场景。每个环节都有对应的调试要点。
实际调试时,我建议按这个顺序排查:先确认硬件连接(SCK、MOSI、MISO、NSS),再检查SPI寄存器配置,然后用示波器看波形,最后才怀疑芯片本身。大部分问题,出在配置和时序上。
一个小技巧:调试SPI时,先读从设备的ID寄存器。如果能读到正确的ID,说明基本通信没问题。读不到?先查时钟和片选,再查极性配置。这个方法帮我省了不少时间。