1. SSI模块核心架构与设计思路在嵌入式系统开发中设备间的通信是构建复杂应用的基石。同步串行接口SSI作为一种高效、可靠的短距离通信方案其核心价值在于通过一根时钟线同步主从设备的数据收发从而避免了异步通信中复杂的波特率匹配问题。CC13x2/CC26x2系列无线MCU集成了两个高度灵活的SSI模块这不仅仅是增加了一个外设更是为开发者提供了应对多种传感器、存储器和无线模组通信协议的“瑞士军刀”。我接触过不少项目从简单的温湿度传感器数据读取到复杂的多通道ADC采集系统再到与外部Flash或SRAM的高速数据交换SSI都扮演着关键角色。它的设计思路非常清晰以硬件FIFO缓冲数据流以可编程时钟控制时序以灵活的帧格式适配不同协议最终通过中断或DMA解放CPU。理解这个思路比死记硬背寄存器位要有用得多。例如当你需要驱动一个SPI接口的OLED屏时数据吞吐量不大但要求实时更新你会优先考虑使用FIFO中断而当你需要从一块SPI Flash中连续读取数MB的固件时DMA就成了不二之选它能将CPU从繁琐的数据搬运中彻底解放出来。CC13x2/CC26x2的SSI模块在设计上考虑得非常周全。其双缓冲FIFO深度为8宽度为16位是一个亮点。这意味着你可以在TX FIFO中预先填充最多8个数据帧然后SSI硬件会自动依次发送同时接收到的数据也会被存入RX FIFO等待CPU或DMA取走。这种“预存后发先收后处”的机制极大地平滑了数据流避免了因CPU处理延迟而导致的数据丢失或通信停顿。在实际调试中我经常通过监控FIFO的状态标志位来判断通信瓶颈所在。如果TX FIFO经常为空说明CPU填充数据的速度跟不上发送速度如果RX FIFO经常满则说明CPU或DMA读取数据的速度太慢。这时就需要调整你的数据处理策略或者启用DMA的突发传输模式。1.1 模块框图与信号流解析要驾驭SSI模块不能只看软件API必须对其硬件信号流有清晰的认知。模块框图对应手册中的Figure 22-1是理解其工作原理的路线图。整个数据通路的核心是TX FIFO、RX FIFO以及连接它们的收发逻辑单元。数据发送的旅程始于CPU或μDMA控制器对SSI数据寄存器SSI_DR的写入。这个写入操作实际上是将数据压入了TX FIFO队列。当SSI被启用且时钟条件满足时收发逻辑会从TX FIFO的底部取出一个数据帧装入发送移位寄存器然后在时钟SSIn_CLK的节拍下将数据一位一位地从SSIn_TX引脚移出。与此同时SSIn_RX引脚上来自从设备的数据也在同样的时钟节拍下被采样并移入接收移位寄存器。当一个完整的数据帧接收完毕后该帧数据会被从移位寄存器推入RX FIFO的顶部等待被读取。这里需要特别注意SSIn_FSS帧同步信号引脚的角色它是区分三种协议模式的关键。在Motorola SPI模式下它通常作为低有效的片选信号nSS在TI SSI模式下它是在每个数据帧前产生一个时钟周期的高脉冲在MICROWIRE模式下它则在整帧传输期间保持低电平。SSIn_CLK时钟信号则由片内的可编程分频器链生成其频率由系统时钟PERDMACLK、预分频器CPSDVSR和时钟速率因子SCR共同决定公式为SSIn_CLK PERDMACLK / [CPSDVSR × (1 SCR)]。这个公式是配置通信速率的根本我后面会用一个具体的计算例子来演示如何“配平”它。中断和DMA控制器像两个高效的“秘书”。中断系统监控着FIFO的空满状态、超时和溢出一旦触发条件满足就会向CPU“汇报”请求处理。而DMA控制器则更“自动化”它可以直接与FIFO对接在满足特定条件如FIFO非空或非满时自动发起数据搬运完全不需要CPU介入。在资源紧张的实时系统中合理分配中断和DMA任务是优化系统性能的关键。1.2 三种协议模式的选择与考量CC13x2/CC26x2的SSI模块支持Motorola SPI、TI SSI和National MICROWIRE三种协议这不是简单的兼容而是给了我们根据实际外设灵活选择的权利。Motorola SPI是目前最通用、支持器件最多的模式它是一个全双工、四线制CLK,MOSI,MISO,nSS的同步接口。其最大的特点是通过SPO时钟极性和SPH时钟相位两个位的组合可以产生四种不同的时钟时序模式Mode 0-3。绝大多数SPI器件都会在其数据手册中明确要求工作在哪种模式。例如很多Flash存储器使用Mode 0SPO0,SPH0而一些ADC则可能使用Mode 3SPO1,SPH1。在配置时必须严格与从设备匹配否则通信必然失败。我的经验是拿到一个新器件第一件事就是翻数据手册的时序图确认其CPOL和CPHA对应SPO和SPH。TI SSI模式是德州仪器自己的同步串行格式。它与SPI的主要区别在于帧同步信号SSIn_FSS的行为它在每个数据帧传输前产生一个时钟周期的高脉冲而不是在整个传输期间保持有效。这种模式在某些TI专属的音频编解码器或旧式器件中可能会用到。如果你的项目中没有这类器件通常可以忽略此模式。MICROWIRE模式是一种半双工协议采用类似“命令-响应”的机制。主机先发送一个8位的控制字从机解析后再回送4-16位的数据。这非常适用于那些需要先发送指令、再读取数据的简单传感器。它的优势是连线简单同样四根线协议本身包含了寻址或命令功能。但需要注意的是它是半双工的同一时刻只能有一个方向的数据流因此吞吐量通常低于全双工的SPI。选择建议对于全新的设计优先选择Motorola SPI因其生态最广。如果外设是TI的老产品检查是否要求TI SSI模式。如果通信逻辑是典型的“先发命令后读数据”且速率要求不高可以考虑MICROWIRE以简化协议层设计。2. 关键功能深度解析与配置要点2.1 比特率生成计算与误差控制比特率的配置是SSI通信稳定的第一步。手册中给出的公式SSIn_CLK PERDMACLK / [CPSDVSR × (1 SCR)]看似简单但其中有两个关键约束常被忽略1.CPSDVSR必须是2到254之间的偶数2.SCR的范围是0到255。最终计算出的SSIn_CLK必须满足主从模式下的最低核心时钟倍数要求。假设我们的系统时钟PERDMACLK为48MHz目标比特率是4 Mbps。我们首先需要确定分频系数N PERDMACLK / SSIn_CLK 48MHz / 4MHz 12。接下来我们需要将这个N分解为CPSDVSR × (1SCR)的形式且CPSDVSR为偶数。一种直观的尝试是让CPSDVSR2则(1SCR)6所以SCR5。校验48MHz / (2 * 6) 4MHz完美匹配。但如果我们想要一个更精确的波特率比如3.125 Mbps常用于某些特定音频采样率计算N48MHz / 3.125MHz 15.36这不是一个整数。我们必须寻找最接近的整数分频比。取N15则SSIn_CLK3.2MHz误差为2.4%取N16则SSIn_CLK3.0MHz误差为4%。我们需要根据外设的容忍度来选择。分解N1515的因数有1,3,5,15。为了满足CPSDVSR为偶数我们可以令CPSDVSR2则(1SCR)7.5SCR必须为整数此路不通。令CPSDVSR6则(1SCR)2.5也不行。实际上15无法分解为一个偶数和一个整数的乘积。因此最接近的可行配置是N16 (CPSDVSR2,SCR7) 或 N14 (CPSDVSR2,SCR6)。这时就需要通过示波器测量实际时钟并评估通信误码率。实操心得在计算波特率时不要只追求理论值的完美匹配。首先应使用PRCMPeripheralClockGet()函数确认当前运行模式下准确的PERDMACLK值它可能在休眠模式下被降低。其次对于高速通信1MHz建议优先将CPSDVSR设置为较小的偶数如2或4这样可以获得更精细的SCR调整粒度从而得到更接近目标值的时钟。最后务必用逻辑分析仪抓取SSIn_CLK的实际波形测量其频率和占空比这是排查通信故障最直接的手段。2.2 FIFO操作机制与中断策略TX和RX FIFO是SSI模块的“心脏”深度为8宽度为16位。这意味着它们可以缓存最多8个数据帧每帧4-16位。这种缓冲机制带来了巨大的灵活性但也需要精心管理。TX FIFO的工作逻辑是“写即缓冲”。当你向SSI_DR寄存器写入数据时数据实际上进入了TX FIFO队列。只有当SSI发送逻辑空闲且时钟就绪时才会从FIFO底部取出数据发送。你可以通过查询SSI_SR寄存器中的TFE发送FIFO空和TNF发送FIFO未满位或者利用发送FIFO服务中断来管理数据写入。当中断被使能且TX FIFO中的数据条目数少于或等于4个即半空或更空时就会触发中断提示你可以安全地写入更多数据而不会溢出。RX FIFO的工作逻辑是“收即缓冲”。从SSIn_RX引脚接收到的完整数据帧会被自动推入RX FIFO。你可以通过查询SSI_SR寄存器中的RNE接收FIFO非空位或者利用接收FIFO服务中断来读取数据。当中断被使能且RX FIFO中的数据条目数达到或超过4个即半满或更满时就会触发中断提示你可以批量读取数据提高效率。除了服务中断还有两个重要的中断源接收超时中断和接收溢出中断。接收超时中断在RX FIFO非空状态持续超过32个SSIn_CLK周期时触发。这非常有用想象一下主机发送了3个字节的数据触发了RX FIFO半满中断阈值4但你因为阈值未到而没有进入中断服务程序。如果没有超时机制这3个数据将永远留在FIFO里。超时中断确保了即使数据量不足半满也能被及时取出。而接收溢出中断则是在RX FIFO已满时又有新数据到来触发的这通常意味着你的数据读取速度严重跟不上接收速度是一个需要紧急处理的错误状态。避坑指南在中断服务程序ISR中读取SSI_DR以清空RX FIFO时务必使用一个while(SSIBusy())或检查RNE位的循环确保取出所有有效数据。否则残留的数据会立即再次触发中断导致ISR重入造成系统锁死。同样在清除中断标志时特别是超时中断通过写SSI_ICR寄存器的RTIC位这个操作必须在ISR返回前完成。我曾遇到过因为清除中断标志的代码放在了ISR末尾且前面有较长的处理逻辑导致中断标志被清除前系统又检测到超时条件引发了虚假的连续中断。2.3 DMA配置与高效数据传输当需要传输大量数据时使用DMA是必须的。SSI模块为发送和接收提供了独立的DMA通道并与FIFO深度智能联动。对于接收通道当RX FIFO中有任何数据时会产生一个单次传输请求当RX FIFO中数据达到或超过4个时会产生一个突发传输请求。对于发送通道当TX FIFO中至少有一个空位时产生单次请求当TX FIFO中空位达到或超过4个时产生突发请求。这种设计非常精妙。单次请求适合零散、非连续的数据传输而突发请求则能最大化总线利用率一次性搬运多个数据特别适合连续读写场景。在μDMA控制器中你需要为SSI的TX和RX通道分别配置一个通道描述符Control Table Entry设置好源地址、目的地址、传输数据量ARB_SIZE和传输模式。一个典型的SPI Flash读取DMA配置流程如下使能SSI模块和对应引脚的时钟与功能映射。配置SSI为SPI主模式并设置好时钟速率、帧格式。初始化μDMA控制器为SSI RX通道配置描述符源地址为SSI_DR外设地址目的地址为内存中的缓冲区传输模式为基本模式BasicARB_SIZE设置为8与FIFO深度匹配。使能SSI的接收DMA设置SSI_DMACR.RXDMAE。使能SSI本身设置SSI_CR1.SSE。向Flash发送读命令和地址通常通过CPU或另一个DMA通道写TX。一旦SSI开始接收到数据RX FIFO非空即会触发DMA请求μDMA自动将数据从SSI_DR搬运到指定内存。传输完成后DMA会触发完成中断你在中断中处理数据或启动下一次传输。注意事项DMA的开启和关闭时机需要仔细控制。在SSI使能SSE1前最好不要开启DMA否则可能产生不可预料的传输请求。在传输结束后应先关闭DMA通道再考虑关闭SSI。另外DMA的传输数据量单位是“次”一次传输的大小取决于你在SSI控制寄存器中设置的数据帧长度DSS。如果你设置的是8位数据帧那么DMA的一次传输就是搬运一个字节。3. 三种帧格式的时序实现与代码配置理解协议时序图是正确配置和调试的基石。手册中的波形图看起来复杂但抓住几个关键点就能化繁为简时钟极性(SPO)、时钟相位(SPH)、帧同步信号(FSS)的行为以及数据在哪个边沿采样和输出。3.1 Motorola SPI模式详解与配置实例SPI的四种模式由SPO和SPH组合而成。我习惯用“空闲电平”和“采样边沿”来记忆SPO0时钟空闲时为低电平。SPO1时钟空闲时为高电平。SPH0数据在第一个时钟边沿采样对于SPO0是上升沿对于SPO1是下降沿。SPH1数据在第二个时钟边沿采样。以最常见的Mode 0 (SPO0,SPH0)为例时钟线空闲为低。传输开始时片选SSIn_FSS拉低。在第一个时钟上升沿之前主设备的数据SSIn_TX就已经建立好了。在时钟上升沿时刻从设备采样主设备的数据MOSI同时主设备采样从设备的数据MISO。数据在时钟下降沿时发生改变。对于单次传输传输结束后片选拉高。在CC13x2/CC26x2的驱动库中配置一个SPI主机的典型代码如下以TI的DriverLib为例#include ti/drivers/SPI.h #include ti/drivers/spi/SPICC26X2DMA.h #include ti/drivers/dma/UDMACC26XX.h SPI_Handle spiHandle; SPI_Params spiParams; SPICC26X2DMA_HWAttrsV1 spiHWAttrs; SPICC26X2DMA_Object spiObject; // 1. 初始化SPI参数结构体为默认值 SPI_Params_init(spiParams); // 2. 配置用户参数 spiParams.bitRate 4000000; // 4 Mbps spiParams.frameFormat SPI_POL0_PHA0; // Mode 0 spiParams.mode SPI_MASTER; // 主模式 spiParams.dataSize 8; // 8位数据帧 // 3. 配置硬件属性引脚、DMA通道等 spiHWAttrs.baseAddr SSI0_BASE; // 使用SSI0模块 spiHWAttrs.intNum INT_SSI0_COMB; // 中断号 spiHWAttrs.intPriority ~0; // 默认优先级 spiHWAttrs.swiPriority 0; spiHWAttrs.powerMngrId PERIPH_SSI0; // 电源管理ID spiHWAttrs.rxChannelBitMask UDMA_CHAN_SSI0_RX; // RX DMA通道掩码 spiHWAttrs.txChannelBitMask UDMA_CHAN_SSI0_TX; // TX DMA通道掩码 // 引脚配置根据你的电路板原理图定义 spiHWAttrs.pinMosi IOID_10; // MOSI spiHWAttrs.pinMiso IOID_9; // MISO spiHWAttrs.pinClk IOID_8; // SCLK spiHWAttrs.pinCs IOID_11; // CS (可选也可用GPIO手动控制) // 4. 打开SPI实例 spiHandle SPI_open(Board_SPI0, spiParams); if (spiHandle NULL) { // 打开失败处理 } // 5. 进行数据传输 (阻塞式) uint8_t txBuffer[4] {0x01, 0x02, 0x03, 0x04}; uint8_t rxBuffer[4] {0}; SPI_Transaction transaction; transaction.count 4; transaction.txBuf txBuffer; transaction.rxBuf rxBuffer; bool transferOK SPI_transfer(spiHandle, transaction); if (!transferOK) { // 传输失败处理 }这段代码隐藏了底层对SSI_CR0、SSI_CPSR等寄存器的复杂操作。驱动库会根据bitRate和系统时钟自动计算CPSDVSR和SCR并设置FRF字段为SPI模式。对于需要精细控制或使用TI SSI、MICROWIRE模式的情况你可能需要直接操作寄存器或者修改驱动库的底层实现。3.2 TI SSI与MICROWIRE模式的关键差异TI SSI模式的独特之处在于SSIn_FSS信号。它不是一个持续的片选而是在每个数据帧开始前产生一个SSIn_CLK周期的高脉冲。这意味着在连续传输多个帧时SSIn_FSS信号会在每帧之间产生一个高脉冲。这种模式要求从设备能识别这种脉冲式的帧起始信号。在配置上只需将SSI_CR0寄存器中的FRF帧格式字段设置为0x01即可。MICROWIRE模式的配置则更为特殊。首先需要将FRF字段设置为0x02。其次由于它是半双工且分控制字/数据两阶段在软件处理上需要分两步。第一步将8位控制字写入TX FIFOSSI会自动发送。第二步在控制字发送结束后SSI会切换为接收状态等待从设备回送数据。你需要确保在从设备回送数据期间TX FIFO没有新的数据写入否则会破坏通信。在代码实现上通常采用查询或中断的方式等待TX FIFO空且一次传输完成然后再去读取RX FIFO中的数据。调试技巧当通信不正常时逻辑分析仪是你的最佳伙伴。同时抓取CLK、FSS、MOSI、MISO四路信号。首先看FSS信号的行为是否符合预期模式持续低、脉冲高。其次看CLK的极性和相位。最后对照数据手册的时序图看数据在哪个边沿变化在哪个边沿稳定采样点。90%的SPI通信问题都可以通过这时序“四线图”定位。4. 初始化流程、常见问题与实战排查4.1 完整的SSI模块初始化步骤手册22.6节给出了初始化的步骤但那是寄存器级别的“骨骼”。结合TI的DriverLib一个健壮的初始化流程应该包含以下“血肉”电源与时钟使能这是所有外设操作的前提。确保SSI模块所在的电源域已上电并通过PRCM模块使能其运行态、休眠态和深度休眠态的时钟。使用PRCMPeripheralRunEnable(),PRCMPeripheralSleepEnable(),PRCMPeripheralDeepSleepEnable()函数最后调用PRCMLoadSet()生效。PRCMPeripheralRunEnable(PRCM_PERIPH_SSI0); PRCMPeripheralSleepEnable(PRCM_PERIPH_SSI0); PRCMPeripheralDeepSleepEnable(PRCM_PERIPH_SSI0); PRCMLoadSet(); while(!PRCMLoadGet()); // 等待时钟配置加载完成GPIO引脚复用配置将具体的物理IO引脚映射到SSI功能。通过IOC模块的IOCFGn寄存器配置或者使用PIN_init()和PIN_setMux()函数。务必确认引脚功能与硬件原理图一致。PIN_Handle pinHandle; PIN_State pinState; PIN_Config pinConfig[] { Board_SPI0_MOSI | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_DRVSTR_MAX, // MOSI Board_SPI0_MISO | PIN_INPUT_EN, // MISO Board_SPI0_CLK | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_DRVSTR_MAX, // SCLK Board_SPI0_CS | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_DRVSTR_MAX, // CS (手动控制) PIN_TERMINATE }; pinHandle PIN_open(pinState, pinConfig); PIN_setMux(pinHandle, Board_SPI0_MOSI, IOC_PORT_MCU_SSI0_TX); PIN_setMux(pinHandle, Board_SPI0_MISO, IOC_PORT_MCU_SSI0_RX); PIN_setMux(pinHandle, Board_SPI0_CLK, IOC_PORT_MCU_SSI0_CLK); // CS引脚通常作为GPIO手动控制不映射到SSI_FSSSSI模块基础配置 a. 确保SSI已禁用SSI_CR1.SSE 0。 b. 配置主从模式SSI_CR1.MS位。 c. 配置时钟预分频器SSI_CPSR。 d. 配置SSI_CR0寄存器设置SCR时钟速率、SPO/SPHSPI模式、FRF协议模式、DSS数据位宽。 e. 可选配置DMA并设置SSI_DMACR寄存器使能TX/RX DMA。 f. 最后置位SSI_CR1.SSE使能SSI模块。4.2 典型问题排查速查表在实际开发中你会遇到各种各样的问题。下面这个表格是我多年调试经验的总结可以帮助你快速定位问题根源。现象可能原因排查步骤与解决方案完全无时钟输出1. SSI模块时钟未使能。2. GPIO引脚功能映射错误。3. SSI未使能SSE0。4. 配置为主模式但TX FIFO为空。1. 检查PRCM配置代码确认PRCMPeripheralRunEnable已调用且PRCMLoadSet()已执行。2. 用万用表或示波器检查引脚电压或用寄存器查看工具确认IOCFGn.PORTID值正确。3. 单步调试检查SSI_CR1寄存器的SSE位是否为1。4. 主模式只在有数据要发送时才产生时钟。向TX FIFO写一个数据再观察。时钟频率不对1.PERDMACLK系统时钟频率与预期不符。2.CPSDVSR或SCR计算错误。3. 寄存器值未成功写入。1. 确认MCU运行在何种频率下例如48MHz DCO使用PRCMPeripheralClockGet()验证。2. 重新计算分频系数确保CPSDVSR为偶数(1SCR)为整数。3. 在写配置寄存器前确保SSE0。写完后读取寄存器回读确认。能发不能收或收的数据全为0/FF1. SPI模式SPO/SPH不匹配。2. 从设备未就绪或损坏。3. 主从设备间电平不兼容。4. 接收FIFO溢出或未及时读取。1.这是最常见原因用逻辑分析仪抓取时序与从设备数据手册对比CLK极性和相位。2. 检查从设备电源、复位引脚、片选信号。尝试与已知正常的设备通信。3. 检查双方IO电压是否一致如3.3V vs 5V可能需要电平转换。4. 检查SSI_SR寄存器的RFF接收FIFO满和ROR接收溢出位。确保中断或DMA能及时取走数据。通信不稳定偶发错误1. 时钟速率过高信号质量差。2. 布线过长引入干扰。3. 电源噪声大。4. 未正确处理多从设备片选。1. 降低比特率测试。检查PCB布线CLK和MOSI/MISO线应等长、靠近远离高频噪声源。2. 增加串联匹配电阻如22-33欧姆。3. 检查电源纹波在MCU和从设备电源引脚就近加退耦电容0.1uF。4. 确保在切换不同从设备时有足够的片选建立和保持时间通常用delay_us()短暂延时。DMA传输不启动或数据不全1. DMA通道未正确配置或使能。2. DMA源/目的地址或传输量错误。3. SSI的DMA使能位未设置。4. DMA传输完成中断未清除。1. 检查μDMA通道控制字、源地址SSI_DR、目的地址、ARB_SIZE设置。2. 确认SSI_DMACR中的TXDMAE或RXDMAE位已置位。3. 在DMA完成中断ISR中必须读取UDMA_REQDONE寄存器以清除中断标志并重新配置描述符以备下次传输。4.3 低功耗设计中的SSI使用要点CC13x2/CC26x2是无线MCU低功耗是核心考量。SSI模块本身在空闲时功耗很低但需要注意以下几点动态时钟管理在不需要通信时可以通过PRCM模块关闭SSI的外设时钟PRCMPeripheralRunDisable()以节省功耗。再次使用前重新使能并初始化。IO引脚配置在SSI禁用期间将相关的CLK、MOSI、CS引脚配置为输出低或高阻输入避免引脚浮空产生漏电流。对于MISO输入引脚使能内部上拉或下拉电阻将其固定在一个确定电平。DMA与唤醒可以配置DMA在传输完成后产生中断并结合MCU的休眠模式。让MCU进入深度睡眠由DMA完成大量数据搬运搬运完成后触发中断唤醒MCU进行处理这能极大降低平均功耗。从设备功耗别忘了控制从设备的电源或片选。通信间隙拉高从设备的片选引脚如果它是低有效使其进入休眠模式。对于完全断电的从设备重新上电后要留足复位稳定时间。最后再分享一个调试复杂SPI总线多从设备的心得逐个击破。先将所有从设备的片选线置为无效通常拉高然后单独使能一个设备进行通信测试。确认一个正常后再加入第二个。同时仔细规划每个从设备的片选GPIO确保软件操作片选时不会通过总线串扰影响到其他设备。有时一个设备初始化时发出的垃圾数据可能会被另一个设备误接收因此严格的时序隔离非常重要。