1. 项目概述
搞嵌入式开发,尤其是和各类传感器、存储器、显示屏打交道,SPI(Serial Peripheral Interface)总线绝对是绕不开的“老朋友”。它简单、高效、全双工,一根时钟线加两根数据线就能搞定主从设备间的数据交换。但就是这个看似简单的同步串行协议,其时钟模式的配置却常常让新手甚至是有经验的工程师感到困惑。为什么我的SPI Flash读写总是不对?为什么这个陀螺仪的数据老是错位?很多时候,问题的根源就出在时钟极性(CPOL)和时钟相位(CPHA)这两个关键参数的配置上。
今天,我们就以德州仪器(TI)的TMS320C672x DSP系列中的SPI模块为例,来一次彻底的“庖丁解牛”。我们不止要搞懂那四种经典的时钟模式图,更要深入其硬件实现机制,理解主从通信中每一个时序细节的来龙去脉。你会发现,手册上那些关于建立时间(C2TDELAY)、保持时间(T2CDELAY)、传输间延迟(WDELAY)以及使能信号超时(C2EDELAY/T2EDELAY)的配置,并非可有可无的“高级功能”,而是确保你的系统在复杂电磁环境下依然稳定可靠的“定海神针”。这篇文章适合所有正在或即将使用SPI进行嵌入式开发的工程师,无论你是刚入门的新手,还是想深入理解SPI硬件时序的老鸟,相信都能从中获得启发。
2. SPI时钟模式:从理论到硬件实现的深度解析
SPI通信的核心是时钟同步。主设备(Master)产生时钟信号(SPIx_CLK),从设备(Slave)在这个时钟的节拍下接收或发送数据。时钟模式,本质上定义了时钟空闲时的电平状态(CPOL)以及数据在时钟的哪个边沿被采样和输出(CPHA)。TMS320C672x的SPI模块通过SPIFMTx寄存器中的POLARITY和PHASE位来精确控制这些行为,形成了四种标准模式(Mode 0-3)。
2.1 时钟极性(CPOL)与相位(CPHA)的本质
在深入四种模式之前,我们必须先建立两个核心概念,这关乎你能否真正理解时序图。
时钟极性(CPOL, Clock Polarity):这个参数定义了SPI时钟线(SPIx_CLK)在空闲状态(即没有数据传输时)的电平。
CPOL = 0:时钟空闲时为低电平。CPOL = 1:时钟空闲时为高电平。 你可以把它想象成时钟信号的“基线”或“起点”。
时钟相位(CPHA, Clock Phase):这个参数定义了数据采样的时刻。它决定了数据是在时钟的第一个边沿还是第二个边沿被锁存(采样),以及数据在哪个边沿发生变化(输出)。
CPHA = 0:数据在时钟的第一个边沿被采样。对于输出,数据在时钟的相反边沿准备好(或变化)。CPHA = 1:数据在时钟的第二个边沿被采样。对于输出,数据在时钟的相同边沿准备好。
这里有一个极易混淆的点,也是很多资料语焉不详的地方:“第一个边沿”和“第二个边沿”是相对于一个时钟周期而言的,而一个周期的开始是由CPOL决定的空闲状态结束的瞬间。例如,当CPOL=0时,空闲是低电平,那么时钟的第一个边沿就是从低到高的上升沿。理解这一点,是看懂所有时序图的关键。
2.2 四种时钟模式的逐帧拆解
TMS320C672x的SPI模块手册提供了四种模式的精确时序图,我们结合其文字描述,将其转化为更易于理解的硬件操作逻辑。
2.2.1 模式0:CPOL=0, CPHA=0
这是最常见的一种模式。
- 空闲状态:SPIx_CLK为低电平。
- 数据输出(Master out):数据在SPIx_CLK的上升沿被从设备采样。这意味着主设备必须在上升沿到来之前,就将稳定的数据放到SPIx_SIMO线上。
- 数据输入(Master in):数据在SPIx_CLK的下降沿被主设备锁存。这意味着从设备在下降沿到来时,必须将稳定的数据放到SPIx_SOMI线上。
硬件操作视角:当主设备写入SPIDAT寄存器启动传输后,第一个数据位(MSB)会立即出现在SPIx_SIMO线上(在第一个时钟边沿之前)。然后,在第一个时钟上升沿,从设备采样到这个位。紧接着,在随后的下降沿,主设备采样从设备输出的第一位数据。如此循环。关键点在于,对于CPHA=0,第一个数据位在第一个时钟边沿之前就有效了,这要求从设备必须能在极短时间内(半个时钟周期)响应主设备的时钟启动。
2.2.2 模式1:CPOL=0, CPHA=1
- 空闲状态:SPIx_CLK为低电平。
- 数据输出:数据在SPIx_CLK的下降沿被从设备采样。第一个数据位在第一个上升沿之前的半个周期就准备好,并在第一个下降沿被采样。
- 数据输入:数据在SPIx_CLK的上升沿被主设备锁存。
硬件操作视角:写入SPIDAT后,SPIx_CLK仍然保持低电平空闲。在第一个上升沿到来时,主设备并不采样数据,而是从设备在随后的下降沿采样主设备数据。主设备则在下一个上升沿采样从设备数据。对于CPHA=1,数据的采样和输出都发生在同一个时钟边沿类型上(要么都是上升沿,要么都是下降沿),只是主从设备各用一边。
2.2.3 模式2:CPOL=1, CPHA=0
- 空闲状态:SPIx_CLK为高电平。
- 数据输出:数据在SPIx_CLK的下降沿被从设备采样(因为第一个边沿是从高到低的下降沿)。
- 数据输入:数据在SPIx_CLK的上升沿被主设备锁存。
这个模式可以看作是模式0的“镜像”。所有边沿的逻辑关系与模式0完全一致,只是时钟的基线从低电平翻转为高电平。
2.2.4 模式3:CPOL=1, CPHA=1
- 空闲状态:SPIx_CLK为高电平。
- 数据输出:数据在SPIx_CLK的上升沿被从设备采样。第一个数据位在第一个下降沿之前的半个周期就准备好。
- 数据输入:数据在SPIx_CLK的下降沿被主设备锁存。
这个模式是模式1的“镜像”。
实操心得:如何为外设选择正确的模式?你不需要记忆这四种模式。99%的外设数据手册(Datasheet)都会在SPI接口时序章节明确标注所需的CPOL和CPHA值(通常直接写Mode 0, 1, 2, 3)。你的任务就是“照方抓药”,将主设备(MCU/DSP)的SPI模块配置成与外设完全一致的模式。一个黄金法则是:主从设备的CPOL和CPHA必须严格一致,否则通信必然失败。在调试时,如果发现数据错位或全为0xFF/0x00,首先就应该用逻辑分析仪抓取SPIx_CLK, SPIx_SIMO, SPIx_SOMI三根线的波形,对照外设手册的时序图,检查时钟极性和相位是否匹配。
2.3 模式选择背后的硬件考量
为什么需要四种模式?这主要是为了兼容不同外设内部设计的历史和电路结构。
- 采样稳定性:有些外设的内部数据锁存器设计为在时钟上升沿触发,有些则在下降沿。SPI的四种模式确保了无论外设希望在哪个边沿采样数据,主设备都能提供对应的时钟和数据建立关系。
- 建立与保持时间:这是数字电路设计的核心时序要求。数据在采样边沿到来之前必须稳定一段时间(建立时间,Tsu),并在采样边沿之后继续保持稳定一段时间(保持时间,Th)。CPHA的配置,本质上是在调整数据变化沿和采样沿之间的时间间隔,以满足不同外设对Tsu和Th的要求。例如,CPHA=0时,数据在采样沿(对从设备是第一个边沿)的半个周期前就变化了,这提供了更长的建立时间。
3. 主模式下的高级时序配置:超越基本时钟模式
对于简单的传感器,配置好CPOL和CPHA可能就足够了。但在连接SPI Flash、SD卡、或带有复杂握手协议的外设时,TMS320C672x SPI模块提供的一系列高级时序控制功能就显得至关重要。它们让主设备能够主动适应“慢速”或“有脾气”的从设备,而不是强硬地要求对方跟上自己的节奏。
3.1 芯片选择(SPIx_SCS)的建立与保持时间
芯片选择(Chip Select)信号,有时也叫片选(CS)或从设备选择(SS),是SPI总线中用于寻址从设备的信号。在TMS320C672x的4线(带CS)或5线(带CS和ENABLE)主模式下,你可以精细控制CS信号的时序。
建立时间(C2TDELAY):指从主设备拉低CS信号(选中从设备),到发出第一个SPIx_CLK边沿之间的延迟。有些从设备(尤其是一些老式的或低速的存储器)需要一段时间来“唤醒”或准备其SPI接口。
SPIDELAY.C2TDELAY寄存器字段(5位)允许你配置2到33个SYSCLK2周期的延迟。公式为:最大延迟周期 = C2TDELAY值 + 2。- 应用场景:连接响应速度慢的SPI EEPROM。如果不配置足够的建立时间,主设备可能在从设备还没准备好时就发送时钟,导致第一个字节丢失。
保持时间(T2CDELAY):指在最后一个SPIx_CLK边沿之后,主设备拉高CS信号(取消选中从设备)之前的延迟。这确保了从设备有足够的时间完成最后一次数据操作(例如,将数据写入非易失性存储器)。
SPIDELAY.T2CDELAY字段(5位)可配置1到32个SYSCLK2周期。公式为:最大延迟周期 = T2CDELAY值 + 1。注意:如果SPIFMTx.PHASE=0,保持时间还会自动增加一个完整的SPICLK周期。- 应用场景:写入SPI Flash时,在发送完写使能(WREN)或页编程(PP)命令后,需要保持CS为低一段时间,确保命令被完全锁存。
注意事项:CS保持选项(CSHOLD)的“坑”
SPIDAT1.CSHOLD位用于在连续多次传输期间保持CS信号有效,避免其频繁跳变。这适用于需要连续访问的从设备。但手册中有一个非常重要的警告(CAUTION):如果你在CS有效时向SPIDAT1写入数据并将CSHOLD设为0,CS信号会产生一个毛刺(瞬间拉高又拉低)。正确的做法是:在需要CS保持有效的一系列传输中,始终将CSHOLD设为1。在所有传输结束后,单独向CSHOLD字段写入0来释放CS。或者,在传输开始前和结束后,只操作CSHOLD位,传输过程中只写入数据部分(SPIDAT1[15:0])。忽视这一点可能导致通信间歇性失败。
3.2 传输间自动延迟(WDELAY)
在一次传输结束和下一次传输开始之间,SPI主设备可以自动插入一段空闲时间。这通过设置SPIDAT1.WDEL位并使能SPIFMTx.WDELAY字段(6位)来实现,延迟范围是2到65个SYSCLK2周期(WDELAY值 + 2)。
- 应用场景:
- 背靠背(Back-to-Back)读操作:某些ADC在连续读取时,两次转换之间需要一定的间隔时间。使用WDELAY可以避免用软件延时循环来“傻等”,提高效率。
- 流控:虽然不是标准的硬件流控,但可以作为一种简单的软件流控机制,给从设备处理数据留出时间。
3.3 SPIx_ENA握手与超时检测:5线模式的可靠性保障
5线SPI模式在标准的4线(CLK, MOSI, MISO, CS)基础上,增加了一根SPIx_ENA(Enable)信号线,用于从设备向主设备反馈“就绪”状态。这极大地增强了通信的鲁棒性,尤其适用于高速或对时序要求苛刻的场景。TMS320C672x的SPI主模块可以监控这个信号,并检测两种错误。
从设备反压(Slave Back-Pressure):主设备拉低CS后,会等待从设备拉低
SPIx_ENA(表示从设备已准备好接收/发送数据)。SPIDELAY.C2EDELAY字段定义了主设备等待SPIx_ENA响应的最长时间。如果超时,SPIFLG.TIMEOUTFLG标志置位,传输终止。这防止了主设备因从设备死机而一直等待。- 超时计算公式:这是一个分段函数,与时钟预分频
SPIFMTx.PRESCALE有关。当PRESCALE < 127时,最大超时周期 = C2EDELAY值 * (PRESCALE + 2);当PRESCALE >= 128时,最大超时周期 = C2EDELAY值。这意味着在低速时钟下,超时窗口会被拉长,配置时需要仔细计算。
- 超时计算公式:这是一个分段函数,与时钟预分频
从设备失步检测(Slave Desynchronization):传输开始后,如果从设备在完成前意外释放
SPIx_ENA,或者在传输完成后未能在规定时间内释放SPIx_ENA,主设备都会检测到失步错误(SPIFLG.DESYNCFLG)。- 对于后一种情况,超时时间由
SPIDELAY.T2EDELAY字段控制,计算公式与C2EDELAY类似。这可以捕捉到从设备逻辑错误或总线竞争等问题。
- 对于后一种情况,超时时间由
为什么需要这些复杂的检测?在工业或汽车电子等恶劣环境中,电磁干扰(EMI)可能导致时钟信号抖动或毛刺,造成主从设备计数不同步。ENABLE握手机制提供了一种硬件级的“对话”,确保每一步操作都得到确认,一旦“对话”中断,能立即发现并报错,而不是传递错误数据。
4. 实战配置:以TMS320C672x连接SPI Flash为例
假设我们要连接一颗常见的SPI Flash芯片(如W25Q128),其工作在Mode 0(CPOL=0, CPHA=0),并需要CS保持时间。我们使用5线模式以增加可靠性。
4.1 初始化步骤与寄存器配置
- 软件复位:向
SPIGCR0.RESET位写1,然后写0释放复位。 - 配置引脚功能:通过
SPIPC0寄存器,将SPIx_CLK、SPIx_SIMO、SPIx_SOMI、SPIx_SCS、SPIx_ENA对应的位设置为1,使其作为SPI功能引脚。未使用的SPI引脚应配置为GPIO(对应位清0)。 - 设置工作模式:在
SPIGCR1寄存器中,设置MASTER=1,CLKMOD=1(主模式),LOOPBACK=0(关闭回环),POWERDOWN=0(C672x不支持,必须为0)。注意:先不要使能(ENABLE=0)。 - 配置数据格式:配置
SPIFMT0寄存器(假设使用格式0)。CHARLEN: 设置为8(8位数据长度)。PRESCALE: 根据系统时钟SYSCLK2和期望的SPI时钟频率计算。SPICLK = SYSCLK2 / (PRESCALE + 1)。PHASE和POLARITY: 根据Flash手册设为0(Mode 0)。SHIFTDIR: 设为0(MSB先发)。WAITENA: 设为1(启用ENABLE握手)。
- 配置高级时序:配置
SPIDELAY寄存器。C2TDELAY: 根据Flash数据手册的t_{CSS}(CS有效到第一个时钟的时间)要求计算并设置。T2CDELAY: 根据Flash的t_{CSH}(CS无效保持时间)要求计算并设置。注意PHASE=0时会自动加一个SPICLK周期。C2EDELAY和T2EDELAY: 根据系统容忍度和从设备响应速度,设置合理的超时值。例如,设置C2EDELAY让超时时间略长于Flash的最大t_{V}(输出有效时间)。WDELAY: 如果需要,设置传输间隔。
- 配置中断/DMA:在
SPIINT0寄存器中,使能需要的错误中断(如BITERRENA,DESYNCENA,TIMEOUTENA)。强烈建议使用DMA进行数据传输,将DMAREQEN设为1,并将RXINTEN(接收中断)设为0。在SPILVL中设置中断级别。 - 使能SPI模块:最后,将
SPIGCR1.ENABLE位设为1。务必确保SPIINT0.DMAREQEN是在ENABLE之后设置的,否则第一个DMA请求可能丢失。 - 配置DMA:将SPI的DMA请求事件(SPI0对应dMAX事件13,SPI1对应事件14)与DMA通道关联,配置为读取
SPIBUF和写入SPIDAT0/1。
4.2 数据传输流程与CSHOLD操作
假设我们要向Flash的0x000100地址写入一页数据(256字节)。
// 1. 启动传输:拉低CS,并设置CSHOLD=1以在后续命令中保持CS有效 // 假设使用SPIDAT1(自动CS模式),先发送写使能命令0x06 spi_dat1_reg = (1 << CSHOLD_SHIFT) | 0x06; // CSHOLD=1, 数据=0x06 WRITE_REG(SPI1_BASE + SPIDAT1_OFFSET, spi_dat1_reg); // 2. 发送页编程命令和地址。由于CSHOLD在上一步已设为1,CS会保持低电平。 // 注意:此时写入SPIDAT1时,CSHOLD位应继续保持为1,或者只写入低16位数据。 send_spi_data(0x02); // Page Program command send_spi_data(0x00); // Address high byte send_spi_data(0x01); // Address mid byte send_spi_data(0x00); // Address low byte // 3. 发送256字节数据 for(int i=0; i<256; i++) { send_spi_data(page_data[i]); } // 4. 所有数据发送完毕后,需要释放CS。正确做法是单独清除CSHOLD位。 // 向SPIDAT1寄存器只写入CSHOLD字段为0,数据字段写0即可。 spi_dat1_reg = (0 << CSHOLD_SHIFT); // 仅操作CSHOLD位 WRITE_REG(SPI1_BASE + SPIDAT1_OFFSET, spi_dat1_reg); // 此时,CS信号才会在满足T2CDELAY保持时间后,优雅地拉高。避坑指南:DMA使用的关键细节手册强调,强烈建议使用DMA而非中断来处理SPI数据搬运,因为SPI与I2C共享CPU中断线(INT14),容易阻塞。配置DMA时,要确保SPI的发送缓冲器(TX)和接收缓冲器(RX)的DMA请求是同一个事件。这意味着你的DMA传输链需要配置为:当SPI发出DMA请求时,先读取
SPIBUF(获取接收到的数据),再写入SPIDAT0/1(填充要发送的下一个数据)。这是一个“读取-写入”的原子操作,需要在DMA传输配置中正确设置源地址和目的地址。
5. 调试技巧与常见问题排查实录
即使配置看起来完美,实际通信中仍会遇到各种问题。以下是我在多年调试中总结的一些实战经验和排查清单。
5.1 问题一:通信完全无反应,时钟线没有波形
- 检查清单:
- 电源与引脚:确认从设备已上电,引脚连接正确,没有虚焊。
- SPI模块使能:确认
SPIGCR1.ENABLE位已设置为1。这是最容易被忽略的一步。 - 时钟与分频:检查
SYSCLK2时钟是否正常,SPIFMTx.PRESCALE值是否过大导致SPI时钟极慢(用示波器观察)。 - 主从模式:确认
SPIGCR1.MASTER/CLKMOD位已正确设置为11(主模式)。 - GPIO配置:确认
SPIPC0寄存器已将相关引脚配置为SPI功能,而非GPIO。
5.2 问题二:有时能通信,有时不能,或数据随机错误
- 排查思路:
- 时序违规:这是最常见原因。使用逻辑分析仪或高速示波器捕获
SPIx_CLK、SPIx_SIMO、SPIx_SOMI、SPIx_SCS四根线的波形。- 测量建立/保持时间:对照从设备数据手册,测量数据线相对于时钟采样边的建立时间(Tsu)和保持时间(Th)。如果不足,需要降低SPI时钟频率(增大
PRESCALE)或调整PHASE(切换采样边沿)。 - 检查CS时序:确认CS的建立(C2TDELAY)和保持(T2CDELAY)时间满足从设备要求。特别是写入操作后,CS保持时间是否足够长。
- 测量建立/保持时间:对照从设备数据手册,测量数据线相对于时钟采样边的建立时间(Tsu)和保持时间(Th)。如果不足,需要降低SPI时钟频率(增大
- 电气特性:
- 信号完整性:长距离或高频率下,检查信号是否有过冲、振铃或边沿退化。可能需要串联电阻(如22Ω-100Ω)进行阻抗匹配。
- 电平匹配:确认主从设备IO电平一致(如均为3.3V LVCMOS)。
- 上拉电阻:SPI总线通常不需要上拉电阻,但若从设备是三态输出,MISO线可能需要一个弱上拉(如10kΩ)防止悬空。
- 软件竞争:检查是否有其他任务或中断打断了SPI的连续访问。在DMA传输期间,避免操作SPI数据寄存器。
- 时序违规:这是最常见原因。使用逻辑分析仪或高速示波器捕获
5.3 问题三:使用ENABLE握手(5线模式)时超时错误
- 排查步骤:
- 确认从设备支持:并非所有SPI设备都有ENABLE引脚。确认你的从设备手册。
- 检查连线:确认
SPIx_ENA引脚正确连接,且从设备能正确驱动它。 - 配置使能:确认
SPIFMTx.WAITENA位已设置为1。 - 调整超时值:
C2EDELAY和T2EDELAY设置过小。根据公式重新计算,考虑PRESCALE的影响,给予从设备足够的响应时间。可以先设置一个非常大的值进行测试。 - 检查从设备状态:从设备可能因为忙(如Flash正在擦除)而无法拉低ENABLE。确保在发起传输前,从设备处于就绪状态。
5.4 问题四:回环测试(Loopback)正常,但连接实际外设失败
- 诊断意义:这非常好!它证明了你的SPI主控制器配置和软件驱动基本正确,问题大概率出在主从设备之间的匹配上。
- 下一步行动:
- 立即用逻辑分析仪对比回环模式和外接设备时的波形。重点关注时钟极性/相位、CS时序和数据对齐。
- 检查外设的供电、复位引脚状态。
- 确认发送的命令字(Command)完全符合外设协议(例如,Flash的读命令是0x03,而不是0x30)。
5.5 逻辑分析仪是终极武器
投资一个支持SPI协议解码的逻辑分析仪(如Saleae)能极大提升调试效率。它不仅能显示波形,还能直接以十六进制或二进制形式解析出MOSI和MISO线上的数据流,让你一眼就能看出数据是否错位、命令是否正确。在配置复杂的时序参数(如C2TDELAY)时,通过对比抓取波形和理论时序图,可以直观地验证配置效果。
6. 鲁棒性功能与抗干扰设计
TMS320C672x SPI模块内置的鲁棒性功能,是其在工业应用中价值的体现。
- 内部回环测试(LOOPBACK):在系统上电自检(POST)中非常有用。通过将
SPIGCR1.LOOPBACK置1,可以在不连接外部设备的情况下,测试SPI模块的发送和接收通路,包括FIFO,验证硬件是否完好。 - 位错误检测(BITERR):在非回环模式下,SPI模块会实时比较从MISO线读回的数据和预期从MOSI线发送出去的数据(在内部进行)。如果发现不一致(通常是由于总线短路、严重干扰导致),会置位
SPIFLG.BITERRFLG标志。这可以用于检测持续性的硬件故障。 - 失步检测(DESYNC):如前所述,在5线模式下,这是保障通信同步的“看门狗”。一旦检测到ENABLE信号异常,立即报错,防止系统在错误的状态下继续运行。
在设计高可靠性系统时,务必在软件中使能这些错误中断,并在中断服务程序(ISR)中记录错误类型、采取恢复措施(如复位SPI模块、重发数据包)。同时,遵循良好的PCB布局实践:SPI时钟线和数据线尽量等长、平行走线,远离噪声源,并在靠近驱动端放置串联电阻,能从根本上减少时序问题的发生。