3. SPI控制器驱动核心:寄存器、时钟与传输模式
SPI控制器驱动,说白了就是跟寄存器打交道。你想想看,CPU要控制外设,最终都得落到寄存器上。我刚开始做SPI驱动时,总觉得datasheet里那几十页寄存器描述看着头疼,后来踩过几次坑才明白——搞懂核心的几个寄存器,比背下所有细节重要得多。
3.1 SPI控制器寄存器详解
不同厂商的SPI控制器寄存器布局差异很大,但核心功能寄存器基本逃不出下面这几类。我以ARM架构下最常见的SPI控制器为例,给你拆开讲讲。
| 寄存器类别 | 典型名称 | 核心作用 |
|---|---|---|
| 控制寄存器 | SPI_CR1 / SPI_CR2 | 使能SPI、设置主从模式、配置数据帧格式 |
| 时钟分频寄存器 | SPI_CKDIV / SPI_BAUD | 设定SCK时钟频率 |
| 状态寄存器 | SPI_SR | 指示发送/接收缓冲区状态、忙标志 |
| 数据寄存器 | SPI_DR | 发送和接收数据的通道 |
| 配置寄存器 | SPI_CFG / SPI_MODE | 设置极性(CPOL)、相位(CPHA)、LSB/MSB优先 |
核心要点:控制寄存器决定SPI能不能跑起来,时钟分频寄存器决定跑多快,配置寄存器决定怎么跟从设备对上暗号。状态寄存器则是你判断传输是否完成的依据——我见过不少人忘了查忙标志,直接往数据寄存器里写,结果数据全丢了。
3.2 SPI时钟与极性配置
时钟配置这块,我建议你从两个维度去理解:频率和极性。
3.2.1 时钟频率
SPI时钟频率由系统总线时钟经过分频得到。举个例子,如果系统时钟是100MHz,分频系数设成100,那SCK就是1MHz。分频系数通常写在SPI_CKDIV或SPI_BAUD寄存器里。
我的经验:别一上来就把时钟拉到最高。我在一个项目里遇到过,SPI从设备标称支持20MHz,结果实际跑15MHz就丢数据了。后来查出来是PCB走线太长,信号质量不行。建议先跑低速验证通信,再逐步提频。
3.2.2 极性(CPOL)与相位(CPHA)
这两个位决定了SCK空闲时的电平,以及数据在哪个边沿被采样。说白了,就是主设备和从设备怎么对表。
- CPOL = 0:SCK空闲时为低电平
- CPOL = 1:SCK空闲时为高电平
- CPHA = 0:数据在SCK第一个边沿被采样
- CPHA = 1:数据在SCK第二个边沿被采样
组合起来就是四种模式。我刚开始做的时候总觉得这玩意儿抽象,后来画了个时序图才彻底搞明白。
3.3 SPI传输模式详解
四种模式怎么选?我个人的习惯是:先看从设备datasheet。大多数SPI从设备会明确告诉你它支持哪种模式。如果没写,那就用模式0试试——这是最通用的配置。
| 模式 | CPOL | CPHA | 空闲电平 | 采样边沿 | 常见应用 |
|---|---|---|---|---|---|
| 模式0 | 0 | 0 | 低 | 上升沿 | 大多数SPI Flash、SD卡 |
| 模式1 | 0 | 1 | 低 | 下降沿 | 部分ADC、DAC |
| 模式2 | 1 | 0 | 高 | 下降沿 | 某些传感器 |
| 模式3 | 1 | 1 | 高 | 上升沿 | 部分射频芯片 |
我曾经踩过的坑:有一次调试一个温湿度传感器,死活读不到数据。用逻辑分析仪一看,SCK波形没问题,但数据就是不对。折腾了两小时才发现——从设备要求模式2,我配成了模式0。CPOL和CPHA搞反了,数据采样点全错位了。从那以后,我每次调SPI都先用逻辑分析仪抓波形确认。
3.4 代码示例:配置SPI模式0
下面这段代码展示了如何配置一个ARM SPI控制器工作在模式0,时钟频率1MHz。我建议你重点关注寄存器位的设置顺序——先关SPI再配置,配完再使能,这个顺序不能乱。
/* SPI控制器基地址 */
#define SPI_BASE 0x40013000
#define SPI_CR1 (SPI_BASE + 0x00)
#define SPI_CKDIV (SPI_BASE + 0x04)
#define SPI_CFG (SPI_BASE + 0x08)
#define SPI_SR (SPI_BASE + 0x0C)
#define SPI_DR (SPI_BASE + 0x10)
/* 配置SPI为模式0,主模式,1MHz */
void spi_mode0_init(void)
{
uint32_t reg_val;
/* 第一步:先关闭SPI */
reg_val = readl(SPI_CR1);
reg_val &= ~(1 << 0); /* 清除使能位 */
writel(reg_val, SPI_CR1);
/* 第二步:配置时钟分频 */
/* 假设系统时钟100MHz,分频100得到1MHz */
writel(100, SPI_CKDIV);
/* 第三步:配置模式0 */
reg_val = readl(SPI_CFG);
reg_val &= ~((1 << 1) | (1 << 0)); /* CPOL=0, CPHA=0 */
reg_val |= (1 << 2); /* 设置为主模式 */
writel(reg_val, SPI_CFG);
/* 第四步:使能SPI */
reg_val = readl(SPI_CR1);
reg_val |= (1 << 0); /* 设置使能位 */
writel(reg_val, SPI_CR1);
/* 等待SPI就绪 */
while (!(readl(SPI_SR) & (1 << 0)));
}
小技巧:配置时钟分频时,我习惯先算一下理论值,再留点余量。比如从设备支持10MHz,我一般配到8MHz左右。这样即使PCB走线有点干扰,通信也不会崩。另外,别忘了检查状态寄存器的忙标志——我见过有人连续发数据,结果前一个还没发完,后一个就把缓冲区覆盖了。
3.5 传输流程:一次完整的数据交换
SPI传输是同步的——主设备发一个字节的同时,会收到一个字节。这个机制很多人一开始不理解,我画个流程你就明白了。
流程其实就三步:
- 写数据:把要发送的字节写入SPI_DR
- 等待完成:轮询状态寄存器,等发送完成标志置位
- 读数据:从SPI_DR读出从设备返回的字节
关键点:SPI_DR是发送和接收共用的。你写进去的是要发的数据,读出来的是收到的数据。如果你只想接收数据,也得先写一个字节——通常写0xFF或0x00,用来产生时钟。这个机制我刚开始也觉得别扭,后来习惯了就好。
3.6 避坑指南
做SPI驱动这些年,我总结了几条血泪教训:
- 时钟极性搞反:用逻辑分析仪看SCK空闲电平,跟datasheet对一下。我曾经因为CPOL配反,调了一整天。
- 分频系数算错:有些控制器的分频寄存器是2的幂次,有些是线性分频。看清楚再配,别想当然。
- 忘了查忙标志:连续发送时一定要等前一个传输完成。我见过有人连续写SPI_DR,结果数据全乱了。
- 片选信号没处理好:有些从设备要求片选在传输期间一直拉低,有些允许片选在字节间拉高。这个得看具体设备。
特别提醒:如果你在调试时发现SPI通信时好时坏,先别急着怀疑代码。检查一下GPIO的复用功能配置——我遇到过好几次,GPIO没配成SPI功能,结果SCK和MOSI输出全是乱的。
好了,SPI控制器的核心寄存器、时钟配置和四种传输模式就讲到这里。这些东西看着简单,但每个细节都可能让你在调试时抓狂。记住:先看datasheet,再用逻辑分析仪验证,最后才写代码。这个顺序能帮你省下大把时间。