SPI接口原理与CC27xx实战:从基础到高效配置与调试
1. SPI接口核心原理与工作模式深度解析
SPI,全称Serial Peripheral Interface,中文常译为串行外设接口。它本质上是一种同步、全双工、基于主从架构的串行通信总线。说人话就是,它像一条双向、有节奏的数据流水线,一个“老板”(主设备)指挥着“员工”(从设备)在统一的节拍下,同时进行收发数据。这种设计让它比I2C、UART等异步协议在速度上有着先天优势,特别适合对实时性要求高、数据吞吐量大的场景,比如读写高速Flash、驱动高分辨率显示屏,或者与高速ADC/DAC进行数据交换。
它的物理连接通常只需要四根线,这也是它“简单”美誉的由来:
- SCLK (Serial Clock):时钟信号,由主设备产生,是所有数据传输的节拍器。每个时钟脉冲对应一位数据的移入或移出。
- CS/SS (Chip Select / Slave Select):片选信号,由主设备控制。通常低电平有效,主设备通过拉低对应从设备的CS线来“点名”它,告诉它:“接下来我要和你通话了”。这是SPI支持一主多从的关键。
- PICO/MOSI (Peripheral In Controller Out / Master Out Slave In):主设备输出、从设备输入的数据线。数据从主设备流向从设备。
- POCI/MISO (Peripheral Out Controller In / Master In Slave Out):从设备输出、主设备输入的数据线。数据从从设备流回主设备。
这里要特别提一下信号命名。在TI的CC27xx文档中,使用了PICO/POCI而非更常见的MOSI/MISO。这其实更精确地描述了信号在“控制器”(Controller,即主设备)和“外设”(Peripheral,即从设备)视角下的角色,避免了“主/从”可能带来的混淆。PICO就是“外设入,控制器出”,无论谁是主谁是从,这根线永远是数据进入外设、离开控制器的方向。
SPI协议的精髓,或者说最容易让人困惑的地方,在于时钟极性(CPOL, Clock Polarity)和时钟相位(CPHA, Clock Phase)的配置。这两个参数决定了时钟信号在空闲时的状态,以及数据在时钟的哪个边沿被采样(捕获)。
- 时钟极性 (CPOL/SPO):定义SCLK在空闲时的电平。
- CPOL = 0:空闲时SCLK为低电平。
- CPOL = 1:空闲时SCLK为高电平。
- 时钟相位 (CPHA/SPH):定义数据在时钟的哪个边沿被采样。
- CPHA = 0:数据在时钟的第一个边沿(对于CPOL=0是上升沿,对于CPOL=1是下降沿)被采样。
- CPHA = 1:数据在时钟的第二个边沿被采样。
这两者的四种组合(模式0-3)必须与从设备(如传感器、存储器)的时序要求严格匹配,否则通信必然失败。模式0(CPOL=0, CPHA=0)和模式3(CPOL=1, CPHA=1)是最常用的两种。
注意:很多初学者会在这里栽跟头。务必查阅你所用外设的数据手册,确认其要求的SPI模式。一个常见的快速判断方法是:观察数据手册的时序图,看数据线(MOSI/MISO)的变化是发生在时钟边沿之前还是之后。如果数据在时钟边沿之前就稳定了(即建立时间满足),那么采样边沿就是该时钟边沿。
除了标准的Motorola SPI格式,CC27xx的SPI模块还支持TI同步串行格式和MICROWIRE格式。TI格式的特点是CS信号会在每个数据帧传输前产生一个高脉冲,而MICROWIRE则是一种半双工协议,传输以主设备发送的一个8位控制字开始,随后从设备才返回数据,常用于一些老式的串行EEPROM。
2. CC27xx SPI模块架构与核心功能拆解
CC27xx的SPI模块不是一个简单的移位寄存器,而是一个高度集成、功能丰富的通信控制器。理解其内部架构,是进行高效、可靠配置的基础。
模块的核心可以看作由几个关键部分组成:时钟生成单元、双缓冲FIFO、数据传输逻辑、中断与事件系统,以及可选的CRC校验单元和µDMA接口。它们协同工作,将CPU从繁重的字节搬运工作中解放出来。
2.1 可编程时钟系统与速率计算
SPI的通信速率直接由SCLK的频率决定。CC27xx的SPI时钟源是CLKSVT,典型频率为48MHz。模块通过两级分频来产生最终的SCLK:
- 预分频器 (PRESC):位于
SPI.CLKCFG0寄存器,分频系数为1到8。 - 串行时钟速率分频器 (SCR):位于
SPI.CLKCFG1寄存器,分频值为1 + SCR,其中SCR可配置范围为0到1023。
因此,SCLK的频率计算公式非常明确:SCLK频率 = CLKSVT频率 / [PRESC × (1 + SCR)]
例如,要产生一个1MHz的SPI时钟,使用48MHz的CLKSVT。我们可以先设PRESC=4,这样得到12MHz。然后需要再分频12倍得到1MHz,即1+SCR=12,所以SCR=11。配置PRESC=4,SCR=11即可。
实操心得:在满足从设备最高时钟速率的前提下,尽量选择较高的
PRESC值。因为PRESC分频发生在SCR之前,更高的PRESC意味着对CLKSVT的初次分频更大,有时能获得更精确的最终时钟,减少抖动。同时,务必遵守文档中的硬性规定:CLKSVT的频率必须至少是SCLK频率的两倍。
2.2 FIFO机制与数据流管理
FIFO(先进先出)缓冲区是提升SPI通信效率的关键。CC27xx的SPI模块包含独立的TX FIFO和RX FIFO。
- 动态宽度:这是一个非常巧妙的设计。当数据帧大小(DSS)配置为4到8位时,每个FIFO是16个位置深、8位宽。当数据帧大小配置为9到16位时,硬件会自动将FIFO重组为8个位置深、16位宽。这最大限度地利用了存储资源,无论传输8位还是16位数据,都能有效缓冲多个数据帧。
- TX FIFO操作:CPU或µDMA将待发送的数据写入
SPI.TXDATA寄存器,数据即进入TX FIFO。发送逻辑会按顺序从FIFO中取出数据,串行化后从PICO引脚发出。模块提供了“重复发送”功能(通过SPI.CTL1.REPTX配置),允许将FIFO中的最后一个数据帧自动重复发送多次,这对于需要连续发送相同命令(如清屏指令)或从某些设备读取大量数据时特别有用。 - RX FIFO操作:从POCI引脚接收到的串行数据,经移位寄存器转换为并行数据后,被存入RX FIFO。CPU或µDMA通过读取
SPI.RXDATA寄存器来从FIFO中取出数据。 - FIFO刷新:通过设置
SPI.CTL0.FIFORST位,可以原子性地将TX和RX FIFO的读写指针重置为0,实现快速清空。这里有一个至关重要的坑点:FIFO刷新操作必须在没有任何SPI事务正在进行时触发。如果在一个传输过程中执行刷新,你需要在此次传输完全结束后,再次执行一次刷新操作,才能确保FIFO指针正确归零,否则后续传输可能出错。
2.3 中断与µDMA高效协作
为了不让CPU频繁轮询FIFO状态,SPI模块提供了丰富的中断源和强大的µDMA支持。
中断源包括:
- TX FIFO服务中断:当TX FIFO中的数据量低于设定的阈值时触发,提示CPU可以填充更多数据。
- RX FIFO服务中断:当RX FIFO中的数据量达到设定的阈值时触发,提示CPU可以读取数据。
- RX FIFO超时中断:在从设备模式下,如果SCLK时钟在设定时间内没有活动,而RX FIFO中仍有数据,则触发。
- RX FIFO溢出中断:RX FIFO已满,但仍有新数据收到时触发,意味着数据丢失。
- TX FIFO空中断:TX FIFO完全空时触发。
- 传输完成中断(DMA完成/空闲中断)。
所有这些中断在逻辑“或”之后,产生一个总的SPI中断信号提交给系统事件 fabric。你可以通过SPI.IMASK寄存器来屏蔽不需要的中断源。
µDMA集成是实现高效数据搬运的利器。SPI模块为TX和RX路径提供了独立的DMA通道请求。
- TX DMA:当TX FIFO有空间时,会发出DMA请求。可以配置为“单次请求”(每有一个空位就请求一次)或“突发请求”(当空位达到一定数量时请求一次),后者能减少DMA触发次数,提升总线效率。
- RX DMA:当RX FIFO中有数据时,会发出DMA请求。同样支持单次或突发模式。
通过合理配置µDMA描述符,你可以实现“乒乓缓冲”、“循环传输”等高级数据流,让SPI在后台持续传输数KB甚至数MB的数据,而CPU仅在开始和结束时介入,极大节省了CPU资源,降低了系统功耗。
注意事项:文档中明确建议,对于TX/RX FIFO的服务,应优先使用µDMA而非CPU中断。如果不得不用CPU处理中断,务必同时使能TXEMPTY(发送完)和RXOVF(接收溢出)中断作为“安全网”。因为在某些极端情况下(如中断被长时间关闭),CPU可能错过FIFO阈值中断,此时TXEMPTY和RXOVF可以作为一个故障安全机制,防止数据丢失或发送停滞。
2.4 CRC校验保障数据完整性
在要求高可靠性的通信中(如无线固件升级、关键传感器数据读取),SPI模块内置的硬件CRC引擎是个宝藏功能。它支持CRC8-CCITT和CRC16-CCITT两种校验算法。
其工作流程是:
- 发送端:CRC计算单元逻辑上位于
SPI.TXDATA寄存器和TX FIFO之间。每次CPU写入发送数据到TXDATA时,数据既存入FIFO,也实时更新内部的SPI.TXCRC状态寄存器。当一帧或一个数据块发送完毕后,软件读取TXCRC的值,得到该数据块的CRC校验和,然后将这个校验和作为普通数据写入TXDATA发送给对方。 - 接收端:CRC计算单元逻辑上位于RX FIFO和
SPI.RXDATA寄存器之间。每次CPU从RXDATA读取接收数据时,数据既从FIFO读出,也实时更新内部的SPI.RXCRC状态寄存器。接收完数据块和发送方传来的CRC校验和后,软件可以读取RXCRC。如果传输无误,在将对方发来的CRC校验和也纳入计算后,最终的RXCRC值应为0。
重要限制:硬件CRC功能设计用于8位或16位数据帧。虽然硬件不会强制检查,但软件上应遵循:当数据帧配置为8位时,可选择CRC8或CRC16;当数据帧配置为16位时,必须选择CRC16。使用不匹配的CRC位数会导致校验失败。
3. CC27xx SPI模块配置与编程实战
理解了原理和架构,现在我们来手把手进行配置。以下是一个典型的SPI主设备(控制器)初始化流程,假设我们要以模式0(CPOL=0, CPHA=0)、2MHz速率与一个SPI Flash通信。
3.1 初始化步骤详解
引脚复用配置:这是第一步,也是最容易忽略的一步。CC27xx的引脚功能是通过I/O控制器(IOC)模块动态映射的。你需要找到SCLK、CS、PICO、POCI对应的物理GPIO引脚编号(例如
DIO_12,DIO_13等),然后通过配置相应的IOCFGn寄存器,将这些引脚的功能设置为SPI。具体寄存器字段请参考CC27xx的IOC章节。// 伪代码示例:将DIO_12,13,14,15配置为SPI引脚 HWREG(IOC_BASE + IOCFG_12) = PIN_FUNC_SPI_SCLK; HWREG(IOC_BASE + IOCFG_13) = PIN_FUNC_SPI_CS; HWREG(IOC_BASE + IOCFG_14) = PIN_FUNC_SPI_PICO; HWREG(IOC_BASE + IOCFG_15) = PIN_FUNC_SPI_POCI;关闭SPI模块:在修改关键配置(如工作模式、帧格式)前,先确保SPI模块处于禁用状态(
SPI.CTL1.EN = 0)。配置基本参数:
- 主/从模式:设置
SPI.CTL1.MS = 1,选择控制器(主)模式。 - 数据帧大小:根据外设要求设置
SPI.CTL0.DSS。例如,对于8位数据,设置为0x7(因为DSS值=帧位数-1)。 - 帧格式:设置
SPI.CTL0.FRF。对于标准Motorola SPI 4线模式,设为0x0。 - 时钟极性与相位:设置
SPI.CTL0.SPO和SPI.CTL0.SPH。对于模式0,两者都设为0。 - 字节序:设置
SPI.CTL1.MSB,通常设为1(MSB先发送)。
- 主/从模式:设置
配置时钟:根据前面计算的公式,设置
SPI.CLKCFG0.PRESC和SPI.CLKCFG1.SCR。例如,对于48MHz源时钟生成2MHz SCLK,可以设PRESC=4(分频至12MHz),SCR=5(再分频6倍至2MHz,因为1+SCR=6)。配置FIFO中断阈值:通过
SPI.IFLS.TXSEL和RXSEL设置触发中断的FIFO水平。例如,设置TXSEL为1/2FIFO深度,RXSEL为1/4FIFO深度,可以在数据量适中时及时通知CPU或DMA。(可选)配置µDMA:如果使用DMA,需要配置SPI的DMA控制寄存器
SPI.DMACR,使能TX和/或RX DMA请求,并设置请求类型(单次/突发)。同时,在µDMA控制器侧配置相应的通道描述符,指定源地址(发送时为内存数组,接收时为SPI.RXDATA)、目标地址(发送时为SPI.TXDATA,接收时为内存数组)、数据量等。(可选)配置CRC:如果需要,设置
SPI.CTL1中与CRC相关的位(如PEN使能奇偶校验,但注意硬件CRC是常开的),并确保数据帧大小与CRC多项式选择匹配。使能SPI模块:最后,将
SPI.CTL1.EN位设为1,模块开始工作。
3.2 数据收发编程模型
配置完成后,数据的收发主要有三种模式:
1. 轮询模式: 最简单,但效率最低。CPU不断查询状态寄存器(SPI.STA)中的BUSY位或TXFE(TX FIFO空)、RXFF(RX FIFO满)等位。
// 轮询发送一个字节 void SPI_SendBytePolling(uint8_t data) { // 等待TX FIFO有空间 while (!(HWREG(SPI_BASE + SPI_STA) & STA_TXFE_MASK)) { // 空循环等待 } // 写入数据 HWREG(SPI_BASE + SPI_TXDATA) = data; // 如果需要,可以等待发送完成(BUSY位变低) // while (HWREG(SPI_BASE + SPI_STA) & STA_BUSY_MASK); }2. 中断模式: 效率较高。配置好FIFO阈值中断后,在中断服务程序(ISR)中填充TX FIFO或读取RX FIFO。
// SPI中断服务例程(简化版) void SPI_IRQHandler(void) { uint32_t mis = HWREG(SPI_BASE + SPI_MIS); // 读取屏蔽后的中断状态 if (mis & MIS_RX_MASK) { // RX FIFO达到阈值,读取数据 while (!(HWREG(SPI_BASE + SPI_STA) & STA_RXFE_MASK)) { // 直到RX FIFO空 g_rx_buffer[rx_index++] = HWREG(SPI_BASE + SPI_RXDATA); } } if (mis & MIS_TX_MASK) { // TX FIFO低于阈值,填充数据 while (!(HWREG(SPI_BASE + SPI_STA) & STA_TXFF_MASK) && (tx_index < tx_length)) { // 直到TX FIFO满或数据发完 HWREG(SPI_BASE + SPI_TXDATA) = g_tx_buffer[tx_index++]; } } // ... 清除中断标志 }3. µDMA模式: 效率最高,CPU干预最少。只需初始化DMA描述符,启动SPI和DMA传输,然后在DMA传输完成中断中处理即可。
// 配置µDMA描述符用于SPI发送(概念性代码) void ConfigureSPITxDMA(uint32_t *src_buffer, uint32_t size) { // 设置DMA控制结构:源地址=src_buffer,目的地址=SPI.TXDATA // 传输大小=size,源地址递增,目的地址固定,使用基本传输模式 // 使能SPI的TX DMA请求 HWREG(SPI_BASE + SPI_DMACR) |= DMACR_TXDMAE_MASK; // 启动DMA传输 }3.3 片选(CS)与高级控制
CS信号的管理对于可靠通信至关重要。CC27xx提供了硬件CS控制功能(SPI.CTL0.HWCSN)。当使能后,在Motorola 4线模式下,硬件会自动控制CS信号,在TX FIFO中所有数据(以及可选的CRC)传输完毕之前,保持CS为低电平。这简化了软件操作,并确保了传输的原子性。
另一个高级功能是命令/数据控制(Command/Data Control),常用于驱动LCD屏等设备。通过设置SPI.CTL1.CDEN和CDMODE,可以利用CS线的高低电平来区分传输的是命令还是数据。例如,设置CDMODE=1,则下一次通过TXDATA写入的数据字节会在CS为低(命令模式)下发送,之后CS自动恢复为高(数据模式)。这省去了软件手动控制CS引脚电平的步骤。
4. 常见问题排查与调试技巧实录
在实际开发中,SPI通信不出数据是最常见的问题。下面是一个系统性的排查清单和我的实战心得。
问题1:完全无通信,用逻辑分析仪看不到SCLK或数据信号。
- 排查步骤:
- 电源与引脚:确认从设备已上电,且电压电平与CC27xx的GPIO电平(通常3.3V)兼容。
- 引脚配置:百分之九十的问题出在这里!再次用万用表或示波器确认你配置的DIO引脚是否正确输出了SPI功能。一个快速验证方法是,将引脚先配置为GPIO输出模式,手动拉高拉低,看电平是否变化,排除硬件连接问题。
- SPI使能:确认
SPI.CTL1.EN位已设置为1。 - 时钟配置:检查
PRESC和SCR的计算与配置是否正确。SCLK频率是否在从设备支持的范围内?用示波器测量SCLK引脚实际输出频率。 - 主从模式:确认
SPI.CTL1.MS位设置正确(主设备设为1)。
问题2:有SCLK时钟,但数据线(PICO/POCI)上没有波形。
- 排查步骤:
- TX FIFO状态:在发送数据前,检查
SPI.STA.TXFE位是否为1(TX FIFO空,可以写入)。写入数据后,检查SPI.STA.BUSY位是否变为1。 - CS信号:在4线模式下,CS信号是否被正确拉低?检查硬件连接,如果使用软件GPIO控制CS,确保时序正确(通常在传输前拉低,传输后拉高)。
- 数据写入:确认你是向
SPI.TXDATA寄存器写入数据,而不是其他寄存器。写入的数据格式(如8位/16位)是否与DSS配置匹配? - 从设备是否响应:如果是在读数据,POCI上没有波形,可能是从设备没有正确响应。检查从设备的CS、电源,以及它是否支持当前的SPI模式(CPOL, CPHA)。
- TX FIFO状态:在发送数据前,检查
问题3:能收到数据,但数据是错的(全0、全1、或错位)。
- 排查步骤:
- 时钟模式(CPOL/CPHA):这是最可能的原因!务必确保主从设备的CPOL和CPHA设置完全一致。用逻辑分析仪捕获时序图,对照数据手册检查数据采样边沿。
- 字节序(MSB/LSB):检查
SPI.CTL1.MSB位设置,确保数据位的发送顺序与从设备期望的一致。 - 数据帧大小:确认
SPI.CTL0.DSS设置的数据位数与从设备每次传输的位数一致。例如,很多16位ADC要求一次传输16位数据,如果配置成8位,就会错位。 - 时钟速度过快:过高的SCLK速率可能导致从设备建立/保持时间不满足,产生误码。尝试降低SCLK频率。
- 信号完整性:长导线、未端接的线路可能引起信号振铃或边沿退化。在高速率下,尽量缩短走线,并在必要时考虑串联端接电阻。
问题4:使用DMA时,传输不完整或卡住。
- 排查步骤:
- DMA使能:确认
SPI.DMACR中的TXDMAE和/或RXDMAE位已使能。 - DMA描述符配置:仔细检查DMA源/目标地址、传输数量、地址增量模式是否正确。一个常见错误是目标地址没有设置为外设寄存器地址(如
SPI.TXDATA),或者传输数量单位错误(是字节数还是字数?)。 - FIFO阈值与DMA请求:检查
SPI.IFLS的阈值设置是否与DMA的“突发请求”配置匹配。如果FIFO阈值设得太浅,而DMA期望的突发量很大,可能导致DMA请求不触发。 - 中断冲突:确保SPI中断和DMA传输完成中断的优先级和清除逻辑正确,避免相互阻塞。
- DMA使能:确认
调试技巧实录:
- 逻辑分析仪是你的最佳伙伴:一个支持SPI协议解码的逻辑分析仪(如Saleae)能直观地显示SCLK、CS、MOSI、MISO上的波形和解析出的数据值,一眼就能看出时序、数据是否正确,是排查SPI问题的终极利器。
- 寄存器打印调试法:在初始化关键步骤后和通信前后,通过调试器或串口打印关键寄存器的值(如
CTL0,CTL1,CLKCFG0/1,STA),与预期值对比。 - 简化测试:遇到复杂问题,先回归最简单配置。用轮询模式、最低速率、发送固定的已知数据模式(如0xAA, 0x55),排除中断、DMA、高速率等因素的干扰。
- 利用Loopback模式:CC27xx SPI支持内部环回测试模式(通过
SPI.CTL1相关位配置)。在此模式下,发送的数据会被内部直接环回到接收端。这是验证SPI模块自身配置、驱动程序底层是否正确的最快方法,无需连接外部设备。
最后,关于CC27xx SPI模块一个特别需要注意的点是**延迟采样(Delayed Sampling)**功能。当你的PCB走线较长或负载较重,导致POCI引脚上的数据相对SCLK有较大延迟时,主设备可能在正确的时刻采样到的是上一个比特的数据。这时可以通过配置SPI.CLKCFG1.DSAMPLE字段,将采样点延迟若干个CLKSVT时钟周期。通常,在最高SPI速率下,尝试设置为1或2就能有效补偿延迟。这个功能只在控制器模式下可用,是解决高速SPI通信中数据眼图闭合问题的有效硬件手段。
