TI CC27xx SPI高级功能解析:自动CRC与原子化头部更新实战
1. 项目概述
在嵌入式开发中,SPI(Serial Peripheral Interface)因其简单、高速和全双工的特性,成为连接微控制器与传感器、存储器、无线模块等外设的骨干通信协议。然而,随着应用场景对通信可靠性和实时性要求的不断提升,传统的、依赖软件轮询和手动计算的SPI通信方式逐渐暴露出其局限性。尤其是在工业控制、无线传感网络等对数据完整性和响应延迟有严苛要求的领域,如何在不增加CPU负担的前提下,确保数据校验的准确性,以及如何在极短的响应窗口内更新并发送关键状态信息,成为了工程师们必须直面的挑战。
德州仪器(TI)在其CC27xx系列SimpleLink™无线MCU的SPI模块中,引入了两项极具工程价值的高级功能:自动CRC(Auto CRC)和原子化头部更新(Atomic Header Update)。自动CRC功能将CRC校验的计算与插入过程硬件化,在数据块传输结束时自动完成,彻底解放了CPU。而原子化头部更新功能,则通过一组专用的硬件寄存器,实现了对传输FIFO中“头部”数据的原子性、即时性更新,完美解决了在严格时序约束下(例如,作为从设备等待主设备片选信号拉低前的瞬间)更新状态信息的难题。这两个功能并非简单的“锦上添花”,而是从系统架构层面优化了通信的可靠性与实时性,是构建高可靠、低延迟嵌入式通信系统的关键利器。
本文将深入解析TI CC27xx SPI模块中这两项高级功能的工作原理、配置方法,并结合实际的工程实践,分享从寄存器配置、驱动编写到调试排错的全流程经验。无论你是正在评估CC27xx系列芯片,还是希望深入了解现代SPI外设的高级特性以优化现有设计,这篇文章都将提供从理论到实践的详尽参考。
2. 自动CRC功能深度解析与配置实战
循环冗余校验(CRC)是确保数据完整性的基石。在传统的SPI通信中,实现CRC通常需要软件在发送前计算校验和并填入数据流,接收后再进行校验计算与比对。这个过程不仅消耗宝贵的CPU周期,在高速或大数据量传输时还可能引入延迟,甚至因为软件时序问题导致校验失效。CC27xx的自动CRC功能,正是为了根治这些问题而设计。
2.1 自动CRC的工作原理与核心寄存器
自动CRC功能的精髓在于“自动”二字。其核心机制是:当使能该功能后,SPI硬件会在检测到发送FIFO(TX FIFO)下溢(Underflow)时,自动将内部CRC引擎计算出的校验值加载到TX FIFO中,并紧随数据块之后发送出去。整个过程无需CPU干预。
实现这一功能,主要涉及以下几个关键寄存器位:
SPI.CTL0[14] AUTOCRC:这是自动CRC的总开关。将其置1,即启用自动插入CRC功能。SPI.CTL0[15] CRCPOLY:选择CRC多项式。0代表使用8位CCITT多项式(通常为0x07),1代表使用16位CCITT多项式(通常为0x1021)。这决定了生成的是CRC-8还是CRC-16校验码。SPI.CTL0[13] CRCEND:仅对16位CRC有效,用于控制自动插入的CRC16的字节序。0为最高有效字节(MSB)优先,1为最低有效字节(LSB)优先。必须根据通信对端的预期格式进行设置。SPI.CTL0[16] GPCRCEN:通用CRC使能位。当SPI功能未启用(CTL1.EN=0)时,将此位置1,应用程序软件可以直接使用发送侧的CRC引擎作为独立的CRC计算器。这是一个非常实用的附加功能。SPI.TXCRC[31] AUTOCRCINS:这是一个状态位。当自动CRC被插入TX FIFO后,此位会被硬件置1。软件必须在一次数据传输(包含CRC)完成后,读取SPI.TXCRC寄存器来清除此状态,并同时将TX CRC引擎的种子值重新初始化为全1(0xFF或0xFFFF),为下一次计算做好准备。SPI.RXCRC:接收侧CRC值寄存器。在接收完所有数据(包括CRC)后,软件必须读取此寄存器以获取计算出的CRC值,并与期望值进行比对。同样,读取此寄存器也会自动将RX CRC引擎的种子值重置为全1。
关键操作流程:一次完整的、带自动CRC的SPI传输(控制器模式为例)软件流程应为:配置SPI并启用AUTOCRC -> 将待发送数据写入TXDATA -> 等待传输完成(或通过中断/DMA)-> 读取TXCRC寄存器以清除AUTOCRCINS状态并复位CRC引擎 -> 从RXDATA读取接收到的数据 -> 读取RXCRC寄存器获取校验值并验证,同时复位RX CRC引擎。
2.2 自动CRC的配置步骤与示例代码
假设我们需要在控制器模式下,以1Mbps的速率,使用Motorola SPI模式(4线制,CPOL=0, CPHA=0),发送8位数据,并在每次传输后自动附加CRC-8校验。
首先,根据系统时钟计算波特率分频器。公式为:SCLK = CLKSVT / [PRESC × (1 + SCR)]。若系统时钟CLKSVT为48MHz,目标波特率为1Mbps,我们可以选择PRESC=2,则SCR = (CLKSVT / (SCLK * PRESC)) - 1 = (48e6 / (1e6 * 2)) - 1 = 23。
接下来是具体的寄存器配置步骤:
- 确保SPI未启用:检查并确保
SPI.CTL1[0] EN = 0。 - 配置时钟:向
SPI.CLKCFG0写入PRESC=2(即0x2),向SPI.CLKCFG1写入SCR=23(即0x17)。 - 配置控制寄存器0(CTL0):
DSS[3:0] = 0x7(8位数据)FRF[1:0] = 0x1(Motorola 4线模式)SPO=0, SPH=0(时钟极性相位)CRCPOLY=0(CRC-8)CRCEND=0(MSB优先,对CRC-8此位无效但建议设为0)AUTOCRC=1(启用自动CRC)- 假设其他位为0,则
CTL0值约为0x00004047(注意AUTOCRC在bit 14,即0x4000)。
- 配置控制寄存器1(CTL1):
MS=1(控制器模式)EN=0(先保持禁用)- 假设其他位为0,则
CTL1值约为0x00000004(MS在bit 2)。
- 启用SPI:最后,将
SPI.CTL1的EN位置1。
以下是基于TI SDK驱动风格的伪代码示例:
// 假设 SPI0_BASE 为 SPI 模块基地址 #define SPI0_CTL0 (*(volatile uint32_t *)(SPI0_BASE + 0x100)) #define SPI0_CTL1 (*(volatile uint32_t *)(SPI0_BASE + 0x104)) #define SPI0_CLKCFG0 (*(volatile uint32_t *)(SPI0_BASE + 0x108)) #define SPI0_CLKCFG1 (*(volatile uint32_t *)(SPI0_BASE + 0x10C)) #define SPI0_TXDATA (*(volatile uint32_t *)(SPI0_BASE + 0x150)) #define SPI0_TXCRC (*(volatile uint32_t *)(SPI0_BASE + 0x11C)) void SPI_AutoCRC_Init(void) { // 1. 确保SPI禁用 SPI0_CTL1 &= ~(1 << 0); // 清除EN位 // 2. 配置时钟:PRESC=2, SCR=23 for 48MHz -> 1Mbps SPI0_CLKCFG0 = 0x2; SPI0_CLKCFG1 = 0x17; // 3. 配置CTL0: 8-bit data, Motorola 4-wire, CPOL=0 CPHA=0, CRC-8, AutoCRC enabled uint32_t ctl0Value = 0; ctl0Value |= (0x7 << 0); // DSS = 8-bit ctl0Value |= (0x1 << 5); // FRF = Motorola 4-wire // SPO=0, SPH=0 是复位默认值,无需设置 ctl0Value |= (0x0 << 15); // CRCPOLY = CRC-8 ctl0Value |= (0x1 << 14); // AUTOCRC = 1 (启用!) SPI0_CTL0 = ctl0Value; // 4. 配置CTL1: Controller mode uint32_t ctl1Value = 0; ctl1Value |= (0x1 << 2); // MS = 1 (Controller) SPI0_CTL1 = ctl1Value; // 5. 启用SPI SPI0_CTL1 |= (1 << 0); // 设置EN位 } void SPI_TransmitWithAutoCRC(uint8_t *data, uint16_t length) { // 将数据写入TX FIFO for(uint16_t i = 0; i < length; i++) { // 等待TX FIFO非满 (在实际应用中应使用中断或查询TNF位) while(!(SPI0_STA & (1 << 1))); // 假设通过STA寄存器查询TNF SPI0_TXDATA = data[i]; } // 等待传输完成(TX FIFO为空且移位寄存器空闲) // 这里简化处理,实际需结合BUSY和TFE位判断 while(!(SPI0_STA & (1 << 0))); // 等待TFE=1 // !!! 关键步骤:读取TXCRC以清除AUTOCRCINS状态并复位CRC引擎 !!! volatile uint32_t crcValue = SPI0_TXCRC; // 读取即可,返回值可忽略 // 此时,CRC已自动附加并发送,AUTOCRCINS位被清除,CRC种子复位 }2.3 自动CRC功能的使用陷阱与避坑指南
尽管自动CRC大大简化了操作,但几个细节若处理不当,极易导致通信失败或校验错误。
陷阱一:TX FIFO下溢的时机与“意外CRC插入”自动CRC的触发条件是“TX FIFO下溢”。这意味着,如果软件在SPI传输过程中(时钟正在输出)动态地向TX FIFO填充数据,必须极其小心,绝不能让其变空。一旦在非预期的时刻发生下溢,CRC就会被立即插入并发送,导致数据帧结构错误。数据手册特别警告,在SPI速率高于8MHz时,建议在启动传输前,就将所有需要发送的数据(或至少两个数据帧)全部装入TX FIFO。在高性能应用中,配合DMA进行数据传输是避免此问题的推荐方案。
陷阱二:CRC引擎的复位这是一个非常容易遗漏的步骤。每次完成包含自动CRC的传输后,必须通过读取SPI.TXCRC寄存器来清除AUTOCRCINS标志并复位发送侧CRC引擎。同样,接收侧在读取完数据后,也必须读取SPI.RXCRC来复位接收侧CRC引擎。如果忘记这一步,下一次计算的CRC将从上次的残余值开始,导致校验值完全错误。最好将这两个“读取复位”操作封装成固定的后处理函数。
陷阱三:MICROWIRE模式下的限制在MICROWIRE帧格式下,自动CRC功能仅在控制器模式下支持,且仅支持CRC-8配置。如果在MICROWIRE从设备模式下尝试使用自动CRC,或者配置为CRC-16,功能将无法正常工作。在模式选择时务必查阅数据手册的对应说明。
陷阱四:数据位序(MSB/LSB)与CRC字节序的匹配CTL1.MSB位控制着SPI数据传输的位序(MSB先发还是LSB先发)。而CRCEND位控制着CRC-16的字节序。必须确保整个数据流(包括数据体和附加的CRC)的位序/字节序对通信双方是一致的。一个常见的错误是数据按MSB先发,但CRC的字节序配置错误,导致对端无法正确解析校验码。在协议设计阶段,就应明确规定这些细节。
3. 原子化头部更新功能详解与实时性优化
在无线收发器或其它主从式实时系统中,从设备(Peripheral)常常需要在主设备(Controller)发起查询时,立即返回自身的状态信息,如FIFO深度、接收信号强度指示(RSSI)、错误标志等。这些信息被称为“头部”(Header)。传统的做法是,CPU在收到片选(CS)信号后,紧急从内存中读取最新状态,并写入SPI的TX FIFO。但在高速系统中,这段软件响应时间可能无法满足严格的时序要求,或者可能因为中断延迟等原因,导致头部信息不是“最新”的。
原子化头部更新功能就是为了实现“零延迟”更新而生的。它允许CPU在任何时候(只要满足条件),通过写入一组特殊的寄存器(TXFHDR8/16/24/32),原子性地将1-4字节的头部数据更新到TX FIFO的顶部。这个“原子性”体现在,硬件会管理FIFO指针和CRC引擎复位,确保更新操作不会被中断,且能与即将开始的SPI传输无缝衔接。
3.1 原子化头部更新的工作机制
该功能的核心是一组头部寄存器和一个控制寄存器:
- 头部数据寄存器:
SPI.TXFHDR8,TXFHDR16,TXFHDR24,TXFHDR32。分别对应写入1、2、3、4字节的头部数据。写入哪个寄存器,就决定了头部数据的长度。 - 头部控制寄存器:
SPI.TXFHDRC。包含几个关键控制与状态位:HDREN:头部更新功能使能位。HDRCMT:头部提交标志。当HDREN=1且主设备CS信号变低(启动传输)时,此位被硬件置1,表示之前写入的头部数据已正式提交到TX FIFO并将在本次传输中发出。HDRIGN:头部忽略标志。如果在前一个头部已被提交(HDRCMT=1)后,软件再次写入头部寄存器,此位会被置1,提示本次写入被忽略。软件可以轮询此位来判断上次的头部是否已被使用。CSGATE:片选门控。此位决定了在CS信号有效(低电平)时,是否允许写入头部寄存器。CSGATE=0 (UNBLK):默认值。首次头部写入不受CS状态限制。如果CS为高时未写入头部,则允许在CS变低后第一次写入头部,此时HDRCMT会立即置位。若头部已在CS高时写入,则CS变低时HDRCMT立即置位,且在CS低期间的任何头部写入都会被忽略。CSGATE=1 (BLK):只有当CS为高(无效)时,才能成功写入头部寄存器。如果CS为低时尝试写入,操作会被忽略,并设置HDRIGN标志。
工作流程(以CSGATE=0的典型应用为例):
- 初始化SPI,并根据需要设置
TXFHDRC.HDREN(在CSGATE=1时必须软件使能;在CSGATE=0时,首次写入头部寄存器会自动使能)。 - 在系统运行中,每当有新的状态信息(如新的RSSI值)产生,CPU立即将其写入
TXFHDRx寄存器(例如,写入TXFHDR16更新2字节状态字)。 - 写入操作会触发硬件自动完成以下动作:
- 复位TX FIFO和RX FIFO的指针(清空FIFO)。
- 复位TX CRC引擎(种子置为全1)。
- 将指定字节数的头部数据写入TX FIFO。
- 阻塞CPU和µDMA的中断,防止它们同时操作TX FIFO造成冲突。
- 当外部主设备拉低CS信号启动SPI传输时,硬件将
HDRCMT位置1,并释放对中断的阻塞。此时,TX FIFO中准备好的头部数据将作为本次传输的第一个数据帧被发送出去。 - 主设备在发送完所需的时钟周期后,释放CS。
- 从设备(我们的MCU)检测到传输结束,可以通过查询
HDRCMT或HDRIGN位来判断头部是否被成功使用,并准备下一次更新。
3.2 工程配置与代码实现
假设我们作为从设备,需要在每次SPI传输开始时,自动发送一个2字节的头部,包含设备状态和FIFO深度。我们选择CSGATE=0模式,以便在CS变低后仍有最后一次更新机会。
#define SPI0_TXFHDR16 (*(volatile uint32_t *)(SPI0_BASE + 0x128)) #define SPI0_TXFHDRC (*(volatile uint32_t *)(SPI0_BASE + 0x130)) #define SPI0_STA (*(volatile uint32_t *)(SPI0_BASE + 0x160)) void SPI_AtomicHeader_Init(void) { // 初始化SPI为Peripheral模式,输出使能 (CTL1.MS=0, POD=0) // ... 其他SPI配置代码(时钟、帧格式等)... // 配置原子化头部更新:CSGATE = 0 (UNBLK), HDREN在首次写入时自动置位 // 假设其他位为0, TXFHDRC 默认复位值为0,即CSGATE=0, HDREN=0。 // 在CSGATE=0模式下,我们不需要显式设置HDREN。 } void Update_Peripheral_Header(uint16_t statusWord) { // 在实际系统中,这个函数可能由定时器中断、事件触发等调用 // 写入TXFHDR16寄存器,更新2字节头部 SPI0_TXFHDR16 = statusWord; // 可选:检查上一次头部是否已被提交或忽略 uint32_t hdrcReg = SPI0_TXFHDRC; if (hdrcReg & (1 << 1)) { // 检查HDRIGN位 // 上次更新被忽略,可能是因为在CS低时或HDRCMT已置位后写入 // 可以进行错误处理或记录 SPI0_TXFHDRC &= ~(1 << 1); // 写1清除HDRIGN标志(根据手册,写1清0) } // 注意:HDRCMT标志需要在主设备启动传输(CS拉低)后,由硬件置位。 // 软件可以在传输结束后查询并清除它。 } // 假设在SPI传输完成的中断服务程序(ISR)中 void SPI_Peripheral_ISR(void) { // ... 处理接收数据等 ... // 检查并清除头部提交标志 if (SPI0_TXFHDRC & (1 << 2)) { // 检查HDRCMT位 // 头部已成功提交并发送 SPI0_TXFHDRC &= ~(1 << 2); // 写1清除HDRCMT标志 // 此时可以安全地准备下一次头部更新 } // ... 清除其他中断标志 ... }3.3IDLEPOCI引脚与状态指示的巧妙结合
SPI.CTL0[17] IDLEPOCI位提供了一个增强通信可靠性的小技巧。它可以配置当TX FIFO为空且无数据发送时,POCI引脚输出的空闲电平(0或1)。我们可以利用这个特性,结合头部数据的最高位(MSB),向主设备传递简单的“数据就绪”或“状态变化”信号。
策略如下:
- 设置
IDLEPOCI = 0(空闲时输出低电平)。 - 约定头部数据的最高位(MSB)为“数据有效”标志。例如,
0x8XXX表示有新的有效数据,0x0XXX表示数据未更新或无效。 - 当从设备有新的状态需要报告时,它更新头部数据,并确保其MSB为1(与
IDLEPOCI相反)。 - 主设备可以在不发起完整SPI传输的情况下,通过轮询(Polling)从设备的POCI引脚来检测其电平。如果发现POCI引脚为高电平(因为头部MSB为1,驱动输出为高),就知道从设备有新的头部数据待读取,然后才拉低CS启动传输。
这种方法特别适用于主设备需要高效管理多个从设备的场景,避免了盲目的轮询传输,节省了总线时间和功耗。
3.4 原子化头部更新的应用场景与注意事项
核心应用场景:
- 无线收发器状态报告:在低功耗无线通信中,从设备(如传感器节点)的RSSI、链路质量、电池电压等信息需要被主控制器及时读取。使用原子化头部更新,可以确保主控制器每次读到的都是采样时刻的最新值,而不是软件准备过程中可能过时的值。
- 实时数据流控制:在音频、图像等流式数据传输中,头部可以包含当前FIFO的填充水平。主设备可以根据此信息动态调整发送速率,实现硬件级的流控。
- 多从机系统的快速状态查询:结合
IDLEPOCI引脚轮询,主设备可以快速扫描多个从设备的状态引脚,仅与有数据需求的从设备进行通信,极大提升总线效率。
关键注意事项:
CSGATE模式的选择:CSGATE=0 (UNBLK)模式更灵活,允许在CS变低后“抢跑”一次更新,但对软件时序要求高,需理解其“首次写入”规则。CSGATE=1 (BLK)模式则更严格、更可预测,头部更新必须在CS为高时完成,适合时序规整的系统。选择哪种模式取决于你的主设备通信协议是否允许在CS有效期间有延迟。- 头部长度与数据对齐:写入
TXFHDR8/16/24/32时,数据应位于寄存器的低8/16/24/32位。虽然寄存器是32位的,但硬件只取对应的字节数。确保你的数据格式与选择的数据长度匹配。 - 中断阻塞的理解:在头部写入后、
HDRCMT置位前,CPU和µDMA的中断被阻塞。这意味着在此期间,不能依赖中断来服务SPI的TX/RX FIFO。你的软件架构需要适应这一点,通常采用查询方式处理本次头部之后的传输数据,或者确保在更新头部时,没有正在进行的中断驱动DMA传输。 - 状态标志的清理:
HDRCMT和HDRIGN标志必须由软件写1清除。良好的习惯是在每次传输后或准备下一次更新前,检查并清除这些标志,避免状态机混乱。
4. 结合µDMA实现高效数据传输
自动CRC和原子化头部更新解决了数据校验和实时更新的问题,但要实现大数据量的高效、低CPU占用传输,还需要借助直接存储器访问(DMA)。CC27xx的SPI模块与µDMA控制器紧密集成,可以极大地提升吞吐量。
4.1 SPI µDMA的配置要点
SPI的DMA控制主要通过SPI.DMACR寄存器实现:
RXEN位:使能接收通道DMA。TXEN位:使能发送通道DMA。
使能后,SPI模块会根据FIFO状态自动向µDMA控制器发出传输请求。关键点在于FIFO阈值(IFLS.TXSEL和IFLS.RXSEL)的配置,它决定了何时触发“单次请求”或“突发请求”。
- 接收通道(RX):当RX FIFO中有任何数据时,触发单次请求;当数据量达到
RXSEL设定的阈值(如1/2满)时,触发突发请求。 - 发送通道(TX):当TX FIFO中至少有一个空位时,触发单次请求;当空位数量达到
TXSEL设定的阈值(如1/2空)时,触发突发请求。
一个重要的警告:数据手册明确指出,软件应避免为µDMA操作选择3/4阈值(即IFLS.TXSEL或RXSEL设置为3)。这是因为该阈值可能在某些边界条件下导致DMA请求时序问题。通常,使用默认的1/2阈值(值2)是最安全稳定的选择。
4.2 带自动CRC和DMA的完整传输流程
将自动CRC、原子化头部(可选)与µDMA结合,可以构建一个极其高效的SPI通信引擎。以下是一个控制器模式的示例流程:
初始化阶段:
- 配置SPI时钟、模式、数据位宽。
- 启用自动CRC(
AUTOCRC=1),并配置CRC多项式。 - 配置
IFLS寄存器,设置合适的DMA触发阈值(通常为1/2)。 - 配置µDMA通道:设置源/目标地址、传输数据量、增量模式等。
- 使能SPI的DMA控制(
DMACR.RXEN=1和/或DMACR.TXEN=1)。 - 使能SPI模块(
CTL1.EN=1)。
数据传输阶段:
- (可选)如果需要发送固定的头部,可以先通过
TXDATA寄存器或DMA写入头部数据。 - 启动µDMA发送通道,将主数据块从内存传输到SPI的TX FIFO。
- SPI硬件自动发送数据。当TX FIFO下溢时,硬件自动计算并插入CRC,然后继续发送CRC值。
- 同时,µDMA接收通道将来自RX FIFO的数据搬运到内存。
- µDMA传输完成会产生中断。
- (可选)如果需要发送固定的头部,可以先通过
传输后处理阶段:
- 在DMA完成中断服务程序(ISR)中,检查DMA完成状态(
DMA.REQDONE或SPI.RIS.DMATX/DMARX)。 - 关键步骤:读取
SPI.TXCRC寄存器,清除AUTOCRCINS标志并复位TX CRC引擎。 - 读取
SPI.RXCRC寄存器,获取接收数据的CRC计算结果,并与预期值比较,验证数据完整性。此操作也会复位RX CRC引擎。 - 清除相应的DMA和SPI中断标志。
- 在DMA完成中断服务程序(ISR)中,检查DMA完成状态(
这种模式下,CPU仅在初始化、启动DMA和最终校验时介入,数据传输和CRC处理完全由硬件并行完成,系统效率最大化。
5. 调试技巧与常见问题排查
在实际工程中,启用这些高级功能后,可能会遇到一些意想不到的问题。以下是一些常见的排查思路和调试技巧。
5.1 CRC校验失败
- 现象:通信双方CRC校验不一致,数据接收错误。
- 排查步骤:
- 检查CRC引擎复位:这是最常见的原因。确认在每次传输(或连续传输的每个数据块)后,都执行了读取
TXCRC和RXCRC寄存器的操作。可以在调试器中观察这些寄存器的读取操作是否发生。 - 验证多项式与字节序:确认通信双方使用的CRC多项式(
CRCPOLY)和字节序(CRCEND,针对CRC-16)完全一致。一个快速验证的方法是,让控制器发送一个已知的数据块(如0xAA, 0x55),禁用自动CRC,用软件计算CRC值,然后与使能自动CRC后硬件发送的值进行比对。 - 检查数据位序:确保
CTL1.MSB位(MSB/LSB先发)的设置与对端匹配。位序错误会导致计算的CRC完全不同。 - 检查数据内容:确保你计算CRC期望值所基于的数据,与实际通过SPI总线发送的数据完全一致。注意FIFO的数据对齐(小于16位时需右对齐)。
- 检查CRC引擎复位:这是最常见的原因。确认在每次传输(或连续传输的每个数据块)后,都执行了读取
5.2 原子化头部更新功能不生效
- 现象:写入
TXFHDRx寄存器后,主设备读到的头部数据仍是旧值或全零。 - 排查步骤:
- 检查
HDREN和CSGATE状态:读取TXFHDRC寄存器,确认HDREN位已置1(在CSGATE=0模式下,首次写入后应自动置1)。检查CSGATE模式是否符合你的预期。 - 检查
HDRIGN标志:如果HDRIGN被置1,说明你的头部写入被忽略了。原因可能是:在CSGATE=1 (BLK)模式下于CS低电平时写入;或者在CSGATE=0模式下,但前一次头部已提交(HDRCMT=1)后再次写入。需要调整软件写入头部的时机。 - 检查
HDRCMT标志:主设备CS拉低后,HDRCMT是否置1?如果没有,说明头部未被提交。可能的原因包括SPI未正确配置为外设模式,或者CS信号路径有问题。 - 逻辑分析仪抓包:使用逻辑分析仪同时捕获CS、SCLK、POCI信号。观察在CS下降沿之后,POCI上出现的第一组数据是否是你写入头部寄存器的值。这是最直接的验证手段。
- 检查
5.3 DMA传输异常中断或数据丢失
- 现象:使能DMA后,传输不开始、提前结束或数据错位。
- 排查步骤:
- 确认DMA通道配置:确保SPI的TX/RX DMA请求信号正确映射到了µDMA的对应通道,并且通道的优先级、传输模式(基本模式或Ping-Pong模式)配置正确。
- 检查FIFO阈值:确认
IFLS.TXSEL和RXSEL没有设置为保留值或不建议的3/4阈值。恢复为默认值2(1/2)进行测试。 - 检查中断处理:DMA完成中断和SPI本身的中断(如RX/TX FIFO阈值中断)可能都需要处理。确保中断服务程序正确识别中断源,并清除了正确的标志位(
DMA.REQDONE和SPI.ICLR)。 - 核对数据量:确保DMA配置的传输数据量(字节数)与SPI数据帧大小(
DSS)匹配。例如,如果SPI配置为8位数据(DSS=7),那么DMA的一次传输应对应1个字节。同时,如果使能了自动CRC,DMA传输的数据量不应包含CRC字节,CRC是硬件自动附加的。
5.4 性能优化建议
- 中断与DMA的权衡:对于小数据量、低频率的传输,使用中断模式可能更简单。但对于大数据块或高速连续传输,务必使用DMA以释放CPU并减少中断延迟带来的影响。
- FIFO深度的利用:了解你的SPI模块的FIFO深度(CC27xx通常是8级或16级)。设置合适的DMA突发请求阈值(如1/2满),可以让DMA以更大的数据块进行搬运,减少总线仲裁次数,提升整体效率。
- 时钟配置与延迟采样:在高速率(>10MHz)或长走线情况下,考虑使用
CLKCFG1.DSAMPLE字段配置延迟采样,以补偿信号在PCB上的传播延迟,提高数据采样稳定性。 - 电源与时钟域:确保SPI所在的电源域(如SVT)已正确上电,并且模块时钟(
CLKSVT)已使能(通过CLKCTL.CLKCFG0.SPI0位)。这是模块正常工作的前提。
通过深入理解自动CRC和原子化头部更新的硬件机制,遵循正确的配置流程,并运用有效的调试方法,你可以充分发挥CC27xx SPI模块的强大功能,构建出既可靠又高效的嵌入式通信子系统。这两个功能代表了现代MCU外设设计向着更智能化、更减轻CPU负担方向发展的趋势,熟练掌握它们,无疑会为你的嵌入式项目增添重要的竞争力。