2. ARM处理器SPI控制器硬件原理
各位同学,今天我们来啃一块硬骨头——SPI控制器的硬件原理。说实话,很多做驱动开发的朋友,上来就对着寄存器手册写代码,结果遇到时序问题抓瞎了。我个人习惯是,先搞懂硬件怎么工作的,再动手写代码。这样出了问题,你心里有底。
SPI控制器说白了,就是处理器和外部设备之间的一条「串行高速公路」。这条路上有四个关键角色:时钟、数据输入、数据输出和片选。但控制器内部到底是怎么把数据发出去的?又是怎么收数据的?我们一层层剥开来看。
2.1 SPI控制器内部架构
一个典型的ARM SPI控制器,内部主要由三大模块构成:移位寄存器、时钟分频器和FIFO。我画了个框图,大家先有个整体印象。
2.1.1 移位寄存器——SPI的核心
移位寄存器是SPI的心脏。它的工作方式特别有意思:每次时钟脉冲到来,寄存器里的数据就「挪动」一位。发送时,最高位先被推出去;接收时,进来的数据从最低位挤进来。
我记得第一次调SPI驱动时,死活读不到数据。后来发现是移位寄存器的位序搞反了——有些控制器默认MSB first,有些可以配成LSB first。你想想看,如果发送方和接收方位序不一致,数据就全乱了。
关键点:移位寄存器的位宽决定了单次传输的数据长度。常见的有8位、16位和32位。配置时一定要和从设备的数据格式匹配。
2.1.2 时钟分频器——决定传输速度
时钟分频器负责把系统时钟(比如PCLK)分频成SPI的SCK时钟。说白了,就是决定数据能传多快。
分频系数通常是一个偶数,从2到256不等。比如系统时钟100MHz,分频系数设为100,SCK就是1MHz。我建议刚开始调试时,先把时钟设慢一点,比如1MHz以下。等通信稳定了,再慢慢往上提。我曾经在某个项目里,一上来就设了30MHz,结果示波器一看,波形都变形了。
经验之谈:时钟频率不是越高越好。要考虑PCB走线长度、从设备支持的最大频率、信号完整性等因素。保守的做法是:先看从设备数据手册,取它支持频率的80%作为上限。
2.1.3 FIFO——缓解CPU压力
FIFO是个好东西。没有FIFO的SPI控制器,每收发一个字节CPU都要中断一次。有了FIFO,你可以一次塞进去好几个字节,然后该干嘛干嘛去。
FIFO的深度各厂家不一样。有些是8级,有些是16级,高端点的有32级。发送FIFO和接收FIFO通常是独立的。我习惯的做法是:配置FIFO阈值,比如发送FIFO空到一半时触发中断,这样CPU可以批量填充数据。
注意:FIFO不是万能的。如果FIFO深度和DMA配合不好,还是会有数据溢出或欠载的问题。尤其是高速传输时,要算好FIFO的填充和读取节奏。
2.2 SPI时序参数
时序参数是SPI驱动开发中最容易踩坑的地方。CPOL、CPHA、时钟频率,这三个参数配不对,设备就是不理你。
2.2.1 CPOL和CPHA——时钟极性与相位
CPOL决定SCK空闲时的电平:0是低电平空闲,1是高电平空闲。CPHA决定数据在哪个时钟沿采样:0是第一个沿采样,1是第二个沿采样。
组合起来就是四种模式。我整理了个表格,大家对照着看:
| 模式 | CPOL | CPHA | 空闲SCK电平 | 数据采样沿 | 数据输出沿 |
|---|---|---|---|---|---|
| 模式0 | 0 | 0 | 低电平 | 上升沿 | 下降沿 |
| 模式1 | 0 | 1 | 低电平 | 下降沿 | 上升沿 |
| 模式2 | 1 | 0 | 高电平 | 下降沿 | 上升沿 |
| 模式3 | 1 | 1 | 高电平 | 上升沿 | 下降沿 |
最常用的是模式0和模式3。很多SPI Flash和传感器默认就是模式0。我建议你拿到从设备后,第一件事就是看数据手册里的时序图,确认它工作在哪个模式。
避坑指南:我曾经在一个项目里,主控和从设备都是模式0,但就是通信失败。查了两天,最后发现是片选信号拉低后,主控立刻发时钟,而从设备需要片选拉低后等几个微秒才能准备好。这不是CPOL/CPHA的问题,而是片选建立时间的问题。
2.2.2 时钟频率——不是越快越好
时钟频率的选择要考虑三个因素:
- 从设备支持的最大频率——这是硬上限
- PCB走线的信号完整性——频率高了,反射和串扰就来了
- 系统负载——SPI时钟频率越高,CPU中断频率也越高
我一般这样选:先看从设备手册,取最大频率的一半作为起始值。调通了再慢慢往上加,直到出现误码为止。然后回退一档,作为稳定工作频率。
2.3 ARM SPI寄存器详解
搞清楚了硬件架构和时序,接下来就是和寄存器打交道了。不同厂家的ARM芯片,SPI寄存器布局略有差异,但核心寄存器就那么几个。我以最常见的三件套为例:控制寄存器、数据寄存器、状态寄存器。
2.3.1 控制寄存器(CR0/CR1)
控制寄存器负责配置SPI的工作模式。通常包含以下关键位:
| 位域 | 名称 | 功能描述 |
|---|---|---|
| [1:0] | CPOL/CPHA | 配置时钟极性和相位,对应四种模式 |
| [5:4] | DFS | 数据帧大小:00=8位,01=16位,10=32位 |
| [7:6] | SCR | 时钟分频系数,SCK = PCLK / (2*(SCR+1)) |
| [8] | SPIEN | SPI使能位,1使能,0禁能 |
| [9] | LSB | 位序:0=MSB first,1=LSB first |
| [10] | MSTR | 主从模式选择:1=主机,0=从机 |
配置时有个顺序要注意:先配好所有参数,最后再使能SPI。如果先使能再改参数,可能会产生错误的时钟脉冲。
2.3.2 数据寄存器(DR)
数据寄存器是CPU和移位寄存器之间的桥梁。写DR就是把数据送进发送FIFO,读DR就是从接收FIFO取数据。
这里有个细节:有些芯片的DR是16位的,但只用了低8位或低16位。读的时候要注意掩码。我见过有人读32位寄存器,结果把高16位的垃圾数据也读进来了。
/* 以STM32为例,发送和接收数据 */
/* 发送:检查TXE标志,然后写DR */
while (!(SPI1->SR & SPI_SR_TXE));
SPI1->DR = tx_data;
/* 接收:检查RXNE标志,然后读DR */
while (!(SPI1->SR & SPI_SR_RXNE));
rx_data = SPI1->DR;
2.3.3 状态寄存器(SR)
状态寄存器是SPI控制器的「晴雨表」。几个关键标志位:
- BUSY:SPI正在忙,正在传输数据
- TXE:发送FIFO空,可以写入新数据
- RXNE:接收FIFO非空,有数据可读
- OVR:溢出错误,接收FIFO满了还被写入
- MODF:模式错误,多主机冲突
重要提醒:读状态寄存器时,有些标志位是「读清除」的。比如OVR标志,读SR寄存器就会自动清零。如果你在中断服务函数里先读SR判断错误,然后又读SR取数据,可能就把错误标志清掉了。正确的做法是:一次读取SR,保存到局部变量,然后判断。
2.4 小结
这一章我们聊了SPI控制器的内部架构、时序参数和关键寄存器。移位寄存器是核心,时钟分频器决定速度,FIFO提升效率。CPOL/CPHA四种模式要记牢,寄存器配置顺序不能乱。