1. 项目概述与SPI总线核心价值
搞嵌入式开发这么多年,SPI总线绝对是我打交道最多的通信协议之一。它不像I2C那样需要上拉电阻和复杂的地址机制,也不像UART那样依赖精确的波特率匹配。SPI的“简单粗暴”恰恰是它的魅力所在:四根线,一个主设备带着一个或多个从设备,时钟一响,数据就位,全双工高速传输,效率极高。你手头但凡有个微控制器,想接个Flash芯片存点数据、挂个传感器读读数,或者驱动个TFT屏幕,SPI往往是首选方案。但“简单”不代表“浅显”,真要把SPI用稳、用透,特别是基于像Freescale(现NXP)S08这类经典MCU的SPI模块时,对寄存器每一位作用的深刻理解,就成了区分“能通信”和“通信稳定高效”的关键。这份手册片段,恰恰是打开这扇门的钥匙,它详细拆解了SPI控制寄存器2、波特率寄存器、状态寄存器等核心配置单元,以及主从模式下的行为细节。今天,我就结合这些寄存器配置和实际踩过的坑,带你从芯片手册的位域描述,走到稳定可靠的SPI应用现场。
2. SPI核心寄存器深度解析与配置逻辑
手册里给出的寄存器信息是静态的,但我们的配置必须是动态和有针对性的。不能光看位域定义,更要理解每个配置位在不同场景下联动的效果。
2.1 SPI控制寄存器2(SPIxC2)的实战配置
这个寄存器是SPI的“功能开关箱”,里面几个位直接决定了SPI的引脚行为、错误检测机制和低功耗特性。
MODFEN(主模式故障功能使能)
:这个位在单主系统中通常可以禁用(设为0),把SS引脚当作普通GPIO用,省心。但在多主系统(虽然SPI多主不常见,但某些冗余设计或总线仲裁场景可能存在)中,它就是“保险丝”。一旦使能(设为1),且
SSOE=0
,主设备的SS引脚就变成了输入,用于检测是否有其他设备试图驱动总线(即拉低SS线)。如果检测到,
MODF
标志位立刻置1,SPI模块会自动将自己切换为从模式并关闭输出驱动器,防止总线冲突。这是一个硬件级别的保护机制。我早年在一个双MCU通过SPI互备的项目中就吃过亏,没开这个功能,结果一方软件异常后强行驱动总线,导致两个MOSI引脚直接“打架”,芯片发烫。教训就是:在可能存在总线争用的环境,务必使能
MODFEN
并妥善处理
MODF
中断。
BIDIROE(双向模式输出使能)
:这个位要和
SPC0
配合理解。当
SPC0=1
启用双向模式(单线数据)时,
BIDIROE
才起作用。
BIDIROE=1
,数据引脚(主模式为MOSI/MOMI,从模式为MISO/SISO)作为输出;
BIDIROE=0
,则作为输入。
这里有个极易忽略的细节
:在双向模式下进行主从切换(比如因模式故障),数据引脚的角色可能会变。手册Note里明确警告,如果MISO引脚在双向主模式下被SPI占用(尽管此时它本应不用),发生模式故障切到从模式时,MISO引脚会被SPI占用。如果你的硬件设计上这个引脚还接了别的外设,那就冲突了。所以,使用双向模式时,一定要检查引脚复用,避免“隐形”的硬件冲突。
SPISWAI(等待模式下SPI停止)
:这是低功耗设计的关键。设成1,进入Wait模式时SPI时钟停止,显著省电;设成0,则SPI在Wait模式下继续运行。选择取决于你的应用场景。如果从设备需要主设备在Wait模式下维持时钟以保持同步(比如某些音频编解码器),就不能停。但大多数情况下,尤其是电池供电设备,建议设为1。需要特别注意从机模式下的行为:即使从机SPI因
SPISWAI=1
在Wait模式下关了核心逻辑,只要主机的SCK还在发,从机的移位寄存器依然会工作,但收到的数据不会立即存入SPIxD寄存器,也不会产生
SPRF
中断,要等退出Wait模式后才处理。这意味着
从机在Wait模式下可能会“丢失”实时数据
。如果你的应用是主机持续发送、从机偶尔唤醒接收,就需要在软件上做额外处理,比如主机在唤醒从机后再发起传输。
2.2 SPI波特率寄存器(SPIxBR)的计算与选择
波特率生成是SPI主设备配置的重头戏。公式很简单:
波特率 = BusClock / ((SPPR+1) * 2^(SPR+1))
。但实际配置时,要考虑的不只是算出个值。
SPPR(预分频除数)与SPR(速率除数)的搭配选择
:手册中的表格给出了离散的倍乘关系。选择时,首要目标是让实际波特率尽可能接近目标值,误差越小,通信越可靠,尤其是在高速或长距离时。其次,要考虑总线负载。过高的SCK频率可能会在PCB上引起信号完整性问题(振铃、过冲)。我的经验法则是,在8MHz以下总线时钟的系统中,SPI时钟不要超过总线时钟的1/2;在更高频率下,最好不超过1/4,并做好阻抗匹配。例如,BusClock为32MHz,需要约4Mbps的SPI速率。计算可得
(SPPR=0, SPR=2)
组合得到
32M/(1*8)=4MHz
,完美匹配。如果找不到完美匹配,优先选择略低于目标值的配置,留出余量。
波特率与从设备兼容性 :不是所有从设备都支持任意速率。比如一些低速的EEPROM,最高SCK可能只有5MHz。你必须查阅从设备的数据手册,确保你配置的主机SCK在其允许范围内。同时,也要注意SCK的占空比,标准SPI要求时钟高电平和低电平时间都要满足从设备的最小脉冲宽度要求。大多数MCU的SPI模块产生的SCK占空比是50%,但最好确认一下。
2.3 SPI状态寄存器(SPIxS)与数据收发流程
状态寄存器是软件与SPI硬件交互的窗口。
SPTEF
(发送缓冲区空)和
SPRF
(接收缓冲区满)这两个标志位构成了最基本的查询式传输流程。
标准查询式发送流程 :
-
等待
SPTEF == 1(发送缓冲区空)。 -
关键一步
:读取SPIxS寄存器(目的是在硬件上清除
SPTEF标志的锁定状态,即使软件不保存该值)。 -
将数据写入SPIxD寄存器。
写入后,数据会移入移位寄存器并开始发送。紧接着,只要移位寄存器一空,发送缓冲区的数据就会立刻补进去,
SPTEF会很快再次置1,这就允许你实现“背靠背”(back-to-back)连续发送,充分利用总线带宽。
标准查询式接收流程 :
-
等待
SPRF == 1(接收缓冲区满)。 - 读取SPIxS寄存器(同样是为了清除标志状态)。
-
从SPIxD寄存器读取数据。
这里有个大坑:接收溢出(Overrun)
。手册明确警告,如果在新传输结束前,没有读取完上一次接收到的数据,新数据就会覆盖旧数据,导致丢失。在高速连续传输时,必须确保你的接收处理代码足够快,或者使用中断/DMA来及时取走数据。
SPMF(匹配标志)是一个高级功能,当接收数据与SPIxMR(匹配寄存器)值相等时置位,可用于特定数据的硬件触发,减少软件轮询开销。
3. 主从模式应用详解与时钟格式玄机
理解了寄存器,才能玩转主从模式。主设备是总线时钟的发起者和控制者,从设备则被动响应。
3.1 主模式(Master Mode)配置与注意事项
配置为Master(
MSTR=1
)后,SCK(SPSCK)和MOSI变为输出,MISO为输入。主设备的任务就是发起并控制每一次传输。
SS引脚在主模式下的三种状态
:
这是最容易混淆的地方,完全由
MODFEN
和
SSOE
控制:
-
MODFEN=0:SS引脚与SPI模块完全无关,可作为普通GPIO。这是 单主系统最常用的配置 ,简单省事。 -
MODFEN=1, SSOE=0:SS引脚作为模式故障检测输入。用于多主系统冲突检测。 -
MODFEN=1, SSOE=1:SS引脚作为自动从设备选择输出。 这个功能非常实用 。在每次传输开始时,硬件会自动将SS引脚拉低;传输结束后,自动拉高。这完美替代了软件手动控制GPIO来选通从设备的操作,不仅代码简洁,而且时序由硬件保证,绝对精准。如果你的系统只有一个SPI从设备,强烈推荐使用此模式。
主模式下的传输启动 :主模式的传输启动相对简单,就是写数据到SPIxD。但手册提到一个细微的“半SCK周期延迟”,这是硬件内部同步所需的时间,对软件透明,但有助于理解SCK启动的时序。
主模式配置变更的“雷区”
:手册用NOTE特别强调,在主模式下,如果更改
CPOL
、
CPHA
、
SSOE
、
LSBFE
、
MODFEN
、
SPC0
、
BIDIROE
(当
SPC0=1
时)、波特率预分频和分频位,会
立即中止正在进行的传输
,并强制SPI进入空闲状态。关键在于“远程从设备无法检测到这一中止”。例如,你正在发送一个16位数据,发到第8位时突然改变了
CPHA
,主设备这边停止了,但从设备可能还在期待第9个时钟边沿,状态就不同步了。因此,任何配置变更都必须在SPI空闲(没有进行中的传输)时进行,并且最好在变更后重新初始化或通过特定序列(如发送哑元)来同步从设备状态。
3.2 从模式(Slave Mode)配置与同步要点
配置为Slave(
MSTR=0
)后,SCK和MOSI变为输入,MISO为输出。从设备完全由主设备的SCK和SS信号牵引。
SS引脚在从模式下的绝对权威 :在从模式下,SS引脚是纯粹的输入,而且是“使能”信号。只有SS为低电平时,从设备的MISO输出才被激活(否则为高阻),并且它才会响应SCK上的时钟进行数据移位。 SS必须在整个传输周期内保持低电平 。如果传输中途SS变高,从设备会立即停止,并进入空闲状态,这通常会导致该次传输失败。因此,主设备控制SS的时序至关重要。
从设备的数据准备(CPHA=0 vs CPHA=1) :这是SPI时钟格式的核心差异之一。
- CPHA=0 :在SS变低后, 第一个数据位(MSB或LSB)必须立即出现在MISO引脚上 ,然后第一个SCK边沿用于采样主设备发给从设备的数据(在MOSI上)。这意味着从设备的CPU必须在SS下降沿到来之前,就把要发送的数据准备好并写入SPIxD寄存器。这对软件响应速度要求较高。
- CPHA=1 :在SS变低后,MISO引脚可以暂时处于未定义状态,直到 第一个SCK边沿 到来时,才需要将第一个数据位输出到MISO上。第一个SCK边沿同时锁存主设备发来的第一位数据。这给了从设备更多的准备时间。
从模式下的“配置禁区”
:与主模式类似,在从模式下更改
CPOL
、
CPHA
、
LSBFE
、
SPC0
等位,会
破坏正在进行的传输
,必须严格避免。从设备无法主动中止传输,所以这些配置只能在确信主设备没有发起传输(SS为高)的空闲期进行修改。
3.3 SPI时钟格式(CPOL与CPHA)的选择与实践
CPOL和CPHA的组合定义了数据相对于时钟的采样和驱动关系,这是SPI与不同设备联调时最常遇到的问题。
CPOL(时钟极性) :决定SCK空闲时的电平。
- CPOL=0:SCK空闲时为低电平。
- CPOL=1:SCK空闲时为高电平。 CPHA(时钟相位) :决定数据在哪个时钟边沿被采样,以及在哪个边沿被驱动(改变)。
- CPHA=0:数据在 第一个时钟边沿 (即SCK从空闲状态第一次跳变时)被采样,在 下一个边沿 被驱动更新。
- CPHA=1:数据在 第二个时钟边沿 被采样,在 第一个边沿 被驱动更新。
如何为你的从设备选择模式 ? 唯一准则:严格遵循从设备数据手册的要求 。比如,一颗常见的SPI Flash芯片AT25DF041A,其数据手册规定支持Mode 0 (CPOL=0, CPHA=0) 和 Mode 3 (CPOL=1, CPHA=1)。你就必须在这两种中选一种配置主机,通常默认选Mode 0。 绝对不要想当然 。我曾经调试过一个温湿度传感器,死活读不出数据,最后发现它只支持Mode 1 (CPOL=0, CPHA=1),而我配置成了Mode 0。
一个快速判断技巧(示波器法) :如果你手头有示波器,可以同时抓取SCK和MOSI(或MISO)信号。看第一个数据位的变化,是发生在SCK第一个边沿之前还是之后。如果在之前,通常是CPHA=0;如果在之后,通常是CPHA=1。再看SCK空闲状态,是高还是低,确定CPOL。
4. 高级功能与低功耗模式下的SPI管理
除了基本收发,SPI模块还有一些高级功能和低功耗特性需要妥善处理。
4.1 双向模式(Bidirectional Mode)的应用场景与陷阱
双向模式(
SPC0=1
)将数据线减少为一根(主为MOMI,从为SISO),适用于引脚资源极其紧张或与特定单线SPI设备通信的场景。此时,
BIDIROE
位控制这一根数据线的方向。
半双工通信
:你需要通过软件在发送和接收阶段切换
BIDIROE
。例如,主机先设
BIDIROE=1
驱动数据线发送命令,然后切到
BIDIROE=0
释放总线并读取从机响应。
切换时机必须谨慎
,要在总线空闲(没有时钟边沿)时进行,并且要考虑到从机响应的时间。一个常见的做法是,主机发送完命令后,发送几个额外的时钟周期(此时不驱动数据线,即
BIDIROE=0
),供从机准备和输出数据。
模式故障下的引脚冲突
:如前所述,这是双向模式最大的隐患。假设你在双向主模式下使用MOSI引脚作为数据I/O(MOMI),并且
MODFEN=1
。此时MISO引脚虽然未被SPI使用,但硬件上可能仍与SPI模块内部连接。一旦发生模式故障,SPI切为从模式,
MISO引脚会被强制用作数据输入(SISO)
。如果你的PCB上这个MISO引脚还连着其他电路(比如一个LED指示灯),冲突就发生了。解决方案:要么在双向模式下禁用模式故障功能(
MODFEN=0
),要么确保MISO引脚在硬件设计上完全空闲或可通过其他方式隔离。
4.2 低功耗模式(Wait/Stop)下的SPI行为与数据完整性
嵌入式设备省电是刚需,SPI模块在低功耗模式下的行为直接影响系统设计。
Wait模式下的SPI
:行为由
SPISWAI
位控制。对于从设备,即使
SPISWAI=1
,移位寄存器在SCK驱动下仍会工作,但数据不会进入SPIxD,也不会产生中断。这会导致一个问题:
从设备在Wait模式下接收的数据是“暂存”在移位寄存器里的
。当MCU被唤醒退出Wait模式后,如果此时恰好有一次传输完成,数据才会被复制到SPIxD并置位
SPRF
。但如果从设备在Wait期间接收了多个字节,只有最后一个字节(唤醒时正在传输的那个)可能被捕获,前面的都丢失了。因此,对于需要连续接收数据的从机,要么禁用
SPISWAI
(功耗高),要么设计通信协议,让主机在需要从机响应时,先通过其他方式(如外部中断)唤醒从机,再进行SPI通信。
Stop模式下的SPI :在Stop3模式下,SPI模块时钟停止,如果传输被中断,恢复后继续。在其他Stop模式下,SPI模块完全关闭,寄存器复位,唤醒后必须 重新初始化 整个SPI模块。这是一个关键点,很多人在低功耗设计时忘了在唤醒后重新配置SPI,导致通信失败。我的做法是,在进入深度Stop模式前,记录SPI的配置参数;唤醒后的初始化流程中,包含完整的SPI模块配置代码。
4.3 中断管理与错误处理
高效的系统离不开中断。SPI提供了发送缓冲区空(
SPTEF
)、接收缓冲区满(
SPRF
)、模式故障(
MODF
)和数据匹配(
SPMF
)四个中断源。
中断服务程序(ISR)设计要点 :
-
及时清除标志位
:进入ISR后,首先要读取状态寄存器(SPIxS)来确定中断源,这个读取操作本身会清除
SPRF和MODF的标志锁定(但标志位本身可能还在,需结合后续操作)。对于SPTEF,需要“读状态寄存器+写数据寄存器”来清除;对于MODF,需要“读状态寄存器(MODF=1)+写控制寄存器1(SPIxC1)”来清除。顺序不能错,否则可能无法清除标志导致中断风暴。 - 处理数据匹配(SPMF) :这个功能可用于实现“地址呼叫”或特定命令触发。例如,在多个同型号SPI从设备共享总线时(通过单独的GPIO片选),可以给每个从设备设置一个唯一的匹配寄存器值。主机广播这个值,只有匹配的从设备会产生中断并响应,实现简单的软件寻址。
-
模式故障(MODF)处理
:一旦进入MODF中断,说明发生了多主冲突。ISR中应该:
- 读取SPIxS确认MODF。
- 写入SPIxC1清除MODF标志并恢复SPI控制(通常需要重新设置MSTR等位)。
- 进行错误记录或系统恢复操作(例如,延迟随机时间后重试,或切换到备份通信路径)。
- 重要 :在冲突解决前,避免立即重新尝试发起传输。
查询与中断的混合使用 :对于简单的单向、低速传输,查询方式足够。但对于全双工、高速或需要及时响应的场景,建议使用“发送中断(或DMA)+接收中断”的方式。可以将发送中断优先级设低,接收中断优先级设高,确保接收到的数据能被及时处理,防止溢出。
5. 实战配置案例与常见问题排查
理论最终要落地。我们以一个具体的场景为例:使用一颗支持SPI的32位MCU作为主机,连接一个SPI Flash存储器(如W25Q128)和一个SPI接口的六轴传感器(如MPU6500)。
5.1 多从设备SPI系统配置实例
硬件连接 :
- 主机MCU:SCK, MOSI, MISO 引脚固定。
- Flash芯片:CS1(连接主机GPIO_PinA), SCK, MOSI, MISO。
- 传感器芯片:CS2(连接主机GPIO_PinB), SCK, MOSI, MISO。
- (注意:两个从设备的SPI数据线可以并联到主机的MOSI/MISO,但片选CS必须独立。)
主机SPI初始化配置(以查询式为例) :
// 假设总线时钟BusClock = 32MHz, 目标SPI时钟 = 8MHz
void SPI_Master_Init(void) {
// 1. 配置GPIO引脚为SPI复用功能(略)
// 2. 配置SPIxC1: 使能SPI,主机模式,模式0 (CPOL=0, CPHA=0)
SPIxC1 = SPIxC1_SPE_MASK | SPIxC1_MSTR_MASK;
// CPHA和CPOL默认为0,即Mode 0
// 3. 配置SPIxC2: 禁用模式故障(单主), 等待模式下停止时钟以省电
SPIxC2 = SPIxC2_SPISWAI_MASK; // MODFEN=0, SSOE=0, BIDIROE=0, SPISWAI=1
// 4. 配置SPIxBR: 计算波特率 32M / ((0+1)* 2^(1+1)) = 32M / 4 = 8MHz
// SPPR = 0 (预分频1), SPR = 1 (速率除数4)
SPIxBR = (0 << 4) | (1 << 0); // 假设SPPR位在6:4, SPR位在3:0
}
分设备通信函数 :
uint8_t SPI_TransferByte(uint8_t txData) {
while(!(SPIxS & SPIxS_SPTEF_MASK)) { /* 等待发送缓冲区空 */ }
(void)SPIxS; // 读取状态寄存器以清除SPTEF标志锁存
SPIxD = txData; // 写入数据,启动传输
while(!(SPIxS & SPIxS_SPRF_MASK)) { /* 等待接收完成 */ }
(void)SPIxS; // 读取状态寄存器以清除SPRF标志锁存
return SPIxD; // 读取接收到的数据
}
void Write_Flash_Enable(void) {
GPIO_PinA_Low(); // 拉低Flash片选
SPI_TransferByte(0x06); // 发送写使能命令
GPIO_PinA_High(); // 拉高片选
// 需要等待一小段时间(tWR)才能进行写操作
}
uint8_t Read_Sensor_Reg(uint8_t regAddr) {
uint8_t value;
GPIO_PinB_Low(); // 拉低传感器片选
SPI_TransferByte(regAddr | 0x80); // 读命令,通常最高位为1表示读
value = SPI_TransferByte(0xFF); // 发送哑元(0xFF)以读取数据
GPIO_PinB_High(); // 拉高片选
return value;
}
5.2 典型问题排查速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 完全无法通信,读回数据全是0xFF或0x00 |
1. 物理连接问题(线断、虚焊)
2. 片选信号未正确控制 3. 主从设备时钟模式(CPOL/CPHA)不匹配 4. SPI模块未使能(SPE=0) |
1. 用万用表或示波器检查SCK、MOSI、MISO、CS引脚连接。
2. 用示波器确认CS引脚在传输期间被正确拉低和释放。 3. 最常用 :用示波器同时抓取SCK和MOSI信号,对照从设备手册检查时钟极性和相位。确保主机配置与从设备要求一致。 4. 检查SPIxC1寄存器,确认SPE位已置1。 |
| 能发送但接收不到数据,或接收数据错误 |
1. MISO和MOSI接反
2. 从设备未准备好或需要特定命令序列 3. 波特率过高,信号质量差 4. 接收溢出(Overrun) |
1. 交换MISO和MOSI线序测试。
2. 仔细阅读从设备数据手册的“上电初始化”和“读数据”时序部分,确保发送了正确的命令头和等待时间。 3. 降低SPI波特率再试。检查PCB走线,过长或靠近干扰源需加串联电阻或缩短走线。 4. 在接收数据后及时读取SPIxD寄存器。考虑使用接收中断或DMA。 |
| 通信偶尔失败,不稳定 |
1. 电源噪声或地线问题
2. 片选信号时序问题(建立/保持时间不足) 3. 多从设备干扰(CS线串扰) 4. 中断或高优先级任务打断了SPI时序 |
1. 测量电源纹波,加强电源滤波。确保主从设备共地良好。
2. 在CS拉低后,增加微秒级延时再发起时钟;在传输结束后,延时再拉高CS。 3. 在未选中的从设备CS线上增加上拉电阻,避免浮空。布线时让CS线远离时钟和数据线。 4. 在关键的SPI连续传输序列中,临时关闭全局中断或提高SPI中断优先级。 |
| 模式故障(MODF)标志被置位 |
1. 在多主系统中使能了MODFEN,且检测到总线冲突。
2. 在单主系统中误使能MODFEN,且SS引脚被意外拉低(如干扰或程序错误)。 |
1. 这是正常保护机制,需在中断服务程序中处理冲突,实现总线仲裁或退避。
2. 检查SS引脚配置,如果不需要模式故障功能,将MODFEN位清零。检查是否有其他驱动源拉低了SS引脚。 |
| 低功耗唤醒后SPI通信失败 |
1. 进入深度Stop模式后,SPI模块被复位,寄存器配置丢失。
2. 从设备在主机休眠时状态发生变化。 |
1. 在系统唤醒后的初始化流程中,
重新完整初始化SPI模块
(配置所有相关寄存器)。
2. 主机唤醒后,对从设备执行一次复位或重新初始化序列(发送复位命令)。 |
调试SPI,示波器或逻辑分析仪几乎是必备的。抓取SCK、MOSI、MISO、CS四路信号,对照数据手册的时序图,绝大部分问题都能一目了然。重点看:CS有效期间,SCK是否有脉冲;数据在正确的时钟边沿是否稳定;数据位之间是否有毛刺。把这些问题一个个揪出来,一个稳定可靠的SPI通信系统就搭建起来了。

1702


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



