1. 从一次SPI通信翻车说起为什么F28003x的寄存器值得单独写一篇去年冬天调一块基于TMS320F28003x的电机驱动板主控要和一颗外部ADC通过SPI回读母线电流采样值。硬件同事拍胸脯说走线没问题我这边代码也是照着例程改的结果上电之后示波器一挂SCLK干干净净MOSI一动不动MISO上倒是有一串莫名其妙的毛刺。折腾了整整一个下午最后发现问题出在SPI的使能顺序和时钟配置寄存器上——C2000系列的SPI模块和STM32那种配好GPIO再配外设的直觉流完全不是一回事它的引脚复用、时钟门控、FIFO使能、字符长度设置之间存在明确的先后依赖顺序错了模块就是不出波形而且不会报任何错。这件事之后我把TMS320F28003x的SPI相关寄存器从头到尾捋了一遍也把TI的TRM相关章节翻来覆去看了几遍。这篇就当作一份实战笔记把SPI寄存器配置的来龙去脉、每个关键位的实际含义、以及调试时真正会踩的坑讲清楚。TMS320F28003x的SPI寄存器配置这件事说难不难说简单也绝对不简单它属于那种文档看懂了但一上手就懵的典型。适合正在用C2000系列做电机控制、数字电源、或者需要和外部传感器/存储器做高速串行通信的工程师参考也适合刚接触C2000、被寄存器手册劝退过的朋友。我下面讲的所有内容都基于F28003x的SPI模块C2000系列里叫SPI有些文档也写作SPI-A/SPI-B等实例涉及寄存器位定义的地方我会说明依据涉及实操顺序的地方我会把为什么是这个顺序讲透。你如果手头正好有块LaunchPad或者自制板可以边看边对着寄存器窗口验证。2. SPI模块整体设计与寄存器映射思路拆解2.1 先搞清楚F28003x的SPI到底长什么样TMS320F28003x属于C2000系列的Piccolo阵营片上有多个SPI实例具体数量看具体型号F28003x一般有2到3个。每个SPI模块本质上是一个可配置的同步串行收发器支持标准SPI的四种模式CPOL/CPHA组合支持可编程的字符长度1到16位带收发FIFO支持DMA触发也支持多控制器/多跟随器拓扑。和STM32的SPI外设相比C2000的SPI有几个明显不同的设计哲学第一引脚复用是外设级的。STM32你要先开GPIO时钟、配AF模式再开SPI时钟。C2000这边SPI的引脚SPICLK、SPISIMO、SPISOMI、SPISTE是通过GPIO的复用寄存器GPAMUX/GPBMUX等配置的但配置的时机和SPI模块本身的使能是解耦的这就埋下了顺序坑。第二时钟使能分散在多个寄存器。C2000有专门的PCLKCR外设时钟控制寄存器来门控每个外设的时钟SPI不使能时钟你写它的寄存器全是无效操作读回来全是0而且不报错。第三FIFO和字符长度是强耦合的。F28003x的SPI FIFO深度是16级但字符长度如果设成16位FIFO里每个entry就是一个16位字如果设成8位每个entry就是一个字节。这个细节在配置DMA或者批量收发时非常关键。我个人的理解是C2000的SPI设计更偏向确定性和实时性它把很多配置项拆得很细让你能精确控制每一个时钟边沿和每一帧数据代价就是配置项多、依赖关系复杂。所以寄存器配置的核心不是填值而是理清依赖顺序。2.2 寄存器地图哪些寄存器是必须动的F28003x的SPI模块寄存器大致可以分成几组我按实际配置时的使用频率排个序寄存器组典型寄存器作用是否必配时钟控制PCLKCR6/7等门控SPI模块时钟必配引脚复用GPxMUX/GPxGMUX把GPIO切到SPI功能必配配置控制SPICCR字符长度、时钟极性、复位必配操作控制SPICTL主从模式、时钟相位、中断、TALK必配波特率SPIBRR分频系数必配状态SPISTS缓冲状态、溢出标志调试必看FIFOSPIFFTX/SPIFFRX/SPIFFCTFIFO使能、阈值、延时用FIFO必配优先级SPIPRI中断优先级、仿真行为可选数据SPITXBUF/SPIRXBUF/SPIDAT收发数据运行时操作这张表里SPICCR、SPICTL、SPIBRR是三个最核心的配置寄存器几乎所有SPI初始化都绕不开它们。SPIFFTX/SPIFFRX是FIFO相关的如果你用中断或者DMA批量传输这两个必须配明白。SPIPRI在调试时容易被忽略但它决定了你在仿真器里暂停CPU时SPI的行为做在线调试时很有用。2.3 为什么配置顺序比配置值更重要我踩过的最大一个坑就是顺序问题。正确的初始化顺序应该是使能SPI模块时钟PCLKCR配置GPIO复用GPxMUX/GPxGMUX复位SPI模块SPICCR的SPISWRESET位清零配置SPICCR字符长度、时钟极性配置SPICTL主从、相位、TALK配置SPIBRR波特率配置FIFO如果使用释放复位SPISWRESET置1配置中断/DMA如果使用这个顺序背后的逻辑是复位状态下配置参数配置完再释放复位让模块开始工作。如果你先释放复位再改字符长度模块可能已经用旧参数发了一帧出去导致从设备收到错误数据。而时钟和引脚复用必须在最前面否则后面所有寄存器写入都是无效的。注意C2000的SPI复位位SPISWRESET是SPICCR的第7位写0进入复位写1退出复位。很多人以为这是软复位其实它更像一个配置锁存使能——复位期间配置被锁存退出复位后生效。3. 核心寄存器逐位拆解与配置要点3.1 SPICCR字符长度和时钟极性的老家SPICCRSPI Configuration Control Register是配置的起点它的位定义大致如下以16位寄存器为例bit15-8保留bit7SPISWRESETSPI软件复位位bit6CLKPOLARITY时钟极性bit5-4保留bit3-0SPICHAR字符长度控制SPICHAR这4位决定了每帧传输多少位。它的编码规则是写入的值等于字符位数减1。比如你要传8位数据就写7传16位数据就写15。这个减1的规则是C2000 SPI的一个经典坑我第一次配的时候直接写了8结果实际传了9位从设备完全对不上。CLKPOLARITY决定空闲时SCLK是高还是低。写0表示空闲低电平写1表示空闲高电平。这个位要和SPICTL里的CLK_PHASE配合才能凑出SPI的四种模式模式CPOLCPHA空闲电平采样边沿000低第一个边沿上升101低第二个边沿下降210高第一个边沿下降311高第二个边沿上升选哪个模式完全取决于你的从设备。比如很多SPI Flash用模式0或模式3而某些ADC可能用模式1。配错模式的表现是数据能收到但全是错的或者时好时坏因为采样点偏了。3.2 SPICTL主从模式和相位控制SPICTLSPI Operation Control Register控制的是怎么传bit1TALK发送使能。写1才允许发送写0只能接收bit2MASTER_SLAVE主从选择。写1是控制器主机写0是外设从机bit3CLK_PHASE时钟相位bit4OVERRUNINTENA接收溢出中断使能bit5CLOCK_SCHEMEC2000特有的一位决定SPICLK是正常还是延迟半个周期bit6SPIINTENASPI中断使能MASTER_SLAVE这个位是很多人忽略的。F28003x的SPI默认是从机模式复位后MASTER_SLAVE0如果你不显式写1模块就一直在等外部时钟自己不会产生SCLK。我那次翻车就是因为忘了写这一位示波器上SCLK死活不出来。TALK位也容易漏。有些例程里默认是1但如果你从某个精简初始化流程抄过来可能TALK是0结果数据写进SPITXBUF了但就是不发出去。调试时如果发现TXBUF有值但MOSI没波形先查TALK。CLOCK_SCHEME是C2000比较特殊的一位。它控制SPICLK相对于数据的变化时刻。写0是正常方案SPICLK在数据变化后半个周期采样写1是延迟方案SPICLK延迟半个周期。这个位主要影响高速通信时的建立/保持时间余量一般低速应用保持默认0即可但如果你的从设备对时序很挑剔可以试着改这一位。3.3 SPIBRR波特率到底怎么算SPIBRRSPI Baud Rate Register是一个16位寄存器但实际只用了低7位bit6-0。波特率的计算公式分两种情况当SPIBRR的值在3到127之间时SPI时钟 LSPCLK / (SPIBRR 1)当SPIBRR的值小于3时SPI时钟 LSPCLK / 4这里的LSPCLK是SPI模块的低速外设时钟通常由系统时钟分频得到。F28003x的LSPCLK默认可能是SYSCLK的1/4或1/2具体要看系统时钟配置。举个例子假设SYSCLK是100MHzLSPCLK配置为25MHz你要得到1MHz的SPI时钟那么SPIBRR 25/1 - 1 24。写入24即可。提示SPIBRR的值不要设成0、1、2因为那会触发除以4的固定分频反而得不到你想要的频率。实际配置时建议先算好目标频率再反推SPIBRR并且用示波器实测确认。我实测下来F28003x的SPI在LSPCLK25MHz时SPIBRR设成24能得到非常干净的1MHz SCLK占空比接近50%。但如果SPIBRR设成很小的值比如3、4SCLK频率会很高此时如果PCB走线不好波形会有明显振铃从设备可能误采样。3.4 FIFO寄存器批量传输的关键F28003x的SPI带16级FIFO相关寄存器是SPIFFTX、SPIFFRX、SPIFFCT。这三个寄存器如果不用FIFO可以不管但一旦你要做DMA或者批量收发它们就是核心。SPIFFTXFIFO发送寄存器bit15SPIFFENAFIFO使能总开关bit14TXFIFO复位bit13TXFIFO中断使能bit12-8TXFIFO中断触发级别bit7-0保留SPIFFRXFIFO接收寄存器bit15RXFIFO中断使能bit14RXFIFO复位bit13RXFIFO溢出bit12-8RXFIFO中断触发级别bit7-0保留SPIFFCTFIFO控制寄存器bit7-0TXFIFO延时控制两次传输之间的延迟周期数FIFO的配置逻辑是先复位TX和RX FIFO设置触发级别再使能FIFO。触发级别决定了FIFO里有多少个数据时产生中断。比如你设TX触发级别为8那么当TXFIFO里的数据少于8个时就会触发中断提醒你该填数据了。SPIFFCT的延时这个功能很实用。在某些从设备比如某些ADC需要帧间间隔时你可以设置这个延时让SPI在两次传输之间插入若干个LSPCLK周期的空闲。我调一颗外部ADC时从设备要求CS拉高后至少200ns才能拉低就是靠这个延时搞定的。3.5 状态寄存器SPISTS调试时的眼睛SPISTSSPI Status Register在调试时非常有用它的关键位包括bit5BUFFULL_FLAG发送缓冲满bit6INT_FLAGSPI中断标志bit7OVERRUN_FLAG接收溢出OVERRUN_FLAG是排查通信问题的第一线索。如果这个位被置1说明接收缓冲还没被读走新的数据就来了数据丢了。常见原因是中断响应太慢或者DMA没配好。调试时如果发现收到的数据偶尔错乱先看这个位。INT_FLAG在中断模式下用来判断中断源读SPIRXBUF或者写SPITXBUF会自动清除相关标志但INT_FLAG需要手动清或者通过特定操作清。4. 完整实操流程从零配通一个SPI通信4.1 硬件准备与引脚确认在动寄存器之前先把硬件确认清楚。F28003x的SPI引脚是复用的具体哪个GPIO对应SPICLK/SPISIMO/SPISOMI/SPISTE要看具体封装和引脚复用表。以常见的100引脚封装为例SPI-A的引脚可能是SPICLK - GPIO58SPISIMO - GPIO59SPISOMI - GPIO60SPISTE - GPIO61但这只是示例实际项目一定要查自己芯片型号的引脚复用表。我见过有人照着别人的板子配结果引脚对不上调了半天以为是寄存器问题。确认引脚后还要确认从设备的SPI模式、最大时钟频率、字符长度、CS极性。这些信息决定了你后面寄存器的值。4.2 时钟与引脚复用配置第一步是使能SPI模块时钟。F28003x的PCLKCR寄存器控制各外设时钟SPI-A通常在PCLKCR6或PCLKCR7里具体看手册。假设是SPI-A// 使能SPI-A时钟 EALLOW; CpuSysRegs.PCLKCR6.bit.SPI_A 1; EDIS;注意C2000的很多系统寄存器需要EALLOW/EDIS保护忘了这对宏写入无效。第二步是配置GPIO复用// 配置GPIO58-61为SPI功能 EALLOW; GpioCtrlRegs.GPBMUX2.bit.GPIO58 1; // SPICLK GpioCtrlRegs.GPBMUX2.bit.GPIO59 1; // SPISIMO GpioCtrlRegs.GPBMUX2.bit.GPIO60 1; // SPISOMI GpioCtrlRegs.GPBMUX2.bit.GPIO61 1; // SPISTE EDIS;这里的具体寄存器名和位域要看F28003x的头文件不同型号可能略有差异。关键是MUX值要选对有些引脚有多个复用功能选错了就切到别的外设去了。4.3 SPI核心寄存器初始化接下来是重头戏。我按前面说的顺序来// 1. 进入复位状态 SpiaRegs.SPICCR.bit.SPISWRESET 0; // 2. 配置字符长度和时钟极性 // 字符长度8位 - SPICHAR 7 // 时钟极性空闲低 - CLKPOLARITY 0 SpiaRegs.SPICCR.bit.SPICHAR 7; SpiaRegs.SPICCR.bit.CLKPOLARITY 0; // 3. 配置操作控制 // 主机模式、TALK使能、相位0 SpiaRegs.SPICTL.bit.MASTER_SLAVE 1; SpiaRegs.SPICTL.bit.TALK 1; SpiaRegs.SPICTL.bit.CLK_PHASE 0; // 4. 配置波特率 // 假设LSPCLK25MHz目标1MHz - SPIBRR 24 SpiaRegs.SPIBRR.bit.SPI_BIT_RATE 24; // 5. 配置FIFO如果使用 SpiaRegs.SPIFFTX.bit.TXFIFO 0; // 先复位TX FIFO SpiaRegs.SPIFFRX.bit.RXFIFO 0; // 先复位RX FIFO SpiaRegs.SPIFFTX.bit.TXFFIL 8; // TX触发级别 SpiaRegs.SPIFFRX.bit.RXFFIL 8; // RX触发级别 SpiaRegs.SPIFFTX.bit.SPIFFENA 1; // 使能FIFO // 6. 退出复位 SpiaRegs.SPICCR.bit.SPISWRESET 1;这段代码里SPICHAR7对应8位、SPIBRR24对应1MHz这两个计算是最容易出错的。我建议你把这两个值在纸上算一遍再写进代码。4.4 数据收发与验证初始化完成后可以先做一次简单的单字节收发验证// 发送一个字节 SpiaRegs.SPITXBUF 0x55; // 等待接收完成 while(SpiaRegs.SPISTS.bit.INT_FLAG 0); // 读取接收数据 Uint16 rxData SpiaRegs.SPIRXBUF;如果一切正常示波器上应该能看到8个SCLK脉冲MOSI上有0x55的波形01010101MISO上如果从设备有回应也能看到数据。验证时的一个技巧先把SPICLK频率设低一点比如100kHz这样示波器容易抓也排除时序问题。等通信通了再往上提频率。4.5 中断与DMA配置要点如果要用中断方式收发需要配置PIE中断向量并使能SPIINTENA。F28003x的SPI中断通常挂在PIE的某个组里具体看中断表。DMA方式则要配置DMA通道把SPI的TX/RX触发源映射到DMA请求。这里的关键是FIFO触发级别要和DMA的burst大小匹配。比如你设TX触发级别为8DMA的burst也应该是8这样每次DMA搬8个数据进FIFO刚好触发下一次请求。我实测下来用DMA做SPI批量传输时SPIFFCT的延时设成0能获得最高吞吐但如果从设备需要帧间间隔就要适当增加。5. 常见问题与排查技巧实录5.1 通信完全不生效的排查顺序遇到SPI不通信我一般按这个顺序查时钟使能了吗读PCLKCR确认SPI时钟位是1引脚复用对吗用示波器量引脚如果一直是高阻或者固定电平说明MUX没配对MASTER_SLAVE设了吗主机模式下这一位必须是1TALK使能了吗TALK0时数据不发SPISWRESET释放了吗一直处于复位状态模块不工作SPIBRR算对了吗频率太高或太低都可能导致从设备不响应这个顺序是从最底层往最上层查能快速定位问题层级。5.2 数据错乱的典型原因数据能收到但内容不对常见原因有现象可能原因排查方法数据整体偏移一位SPICHAR设错多一位或少一位检查SPICHAR是否等于位数-1数据时对时错时钟模式CPOL/CPHA不匹配对照从设备手册改模式偶发数据丢失接收溢出查SPISTS的OVERRUN_FLAG高位或低位固定错字节序问题确认MSB/LSB优先高速时出错低速正常时序余量不足降频或调CLOCK_SCHEMESPICHAR设错是最隐蔽的因为通信看起来是通的只是数据错位。我建议第一次配的时候用逻辑分析仪抓一帧数一下SCLK脉冲数和预期位数对比。5.3 FIFO相关的坑FIFO用起来爽但坑也不少坑一忘了复位FIFO。FIFO复位位是写0复位写1正常工作。如果你初始化时没写0再写1FIFO里可能有残留数据。坑二触发级别和实际需求不匹配。触发级别设太高中断来得太晚FIFO可能溢出设太低中断太频繁CPU负载高。一般设成FIFO深度的一半比较平衡。坑三TX和RX FIFO的复位是独立的。只复位TX不复位RX接收侧可能还有旧数据。提示调试FIFO时可以先把FIFO关掉SPIFFENA0用最原始的单字节方式确认通信正常再开FIFO。这样能把问题隔离在FIFO配置本身。5.4 仿真调试时的注意事项用仿真器在线调试时SPIPRI寄存器的FREE和SOFT位会影响CPU暂停时SPI的行为。如果FREE0CPU暂停时SPI也停如果FREE1SPI继续跑。做实时性调试时建议设成FREE1这样你暂停CPU看变量时SPI通信不会中断。另外在线修改SPI寄存器要小心。有些位在通信过程中修改会导致状态机异常最好先停通信再改。5.5 一个真实的排查案例前面提到的那次翻车最后定位到两个问题叠加一是MASTER_SLAVE没设二是SPICHAR写成了8而不是7。第一个问题导致没波形第二个问题导致即使有波形数据也是9位的。两个问题叠在一起现象就是完全不通反而掩盖了第二个问题。后来我先解决主机模式看到波形了再发现数据位数不对才把SPICHAR改对。这个案例的教训是排查要一层一层来不要同时怀疑多个地方。先把有没有波形解决再解决波形对不对最后解决数据对不对。6. 几个让配置更稳的实战习惯调了这么多块C2000的板子我慢慢养成了几个习惯分享出来可能对你有用。第一个习惯是把SPI初始化写成一个带返回值的函数。函数里每一步配置后都读回寄存器确认值写进去了任何一步失败就返回错误码。这样上电初始化时如果SPI没配好能立刻知道是哪一步的问题而不是等到通信失败再回头查。第二个习惯是保留一个最小可用配置。我通常在项目里放一个最简单的SPI测试函数只配时钟、引脚、主机模式、8位、低速用来快速验证硬件通路。硬件没问题了再往上加FIFO、DMA、高速这些复杂配置。这样能把硬件问题和软件配置问题分开。第三个习惯是用GPIO翻转做时间戳。在SPI发送前后翻转一个空闲GPIO示波器上就能看到SPI传输占用了多少时间对评估吞吐和优化很有帮助。这个方法比单纯看SCLK更直观尤其是用DMA的时候。第四个习惯是把关键配置值做成宏定义。比如#define SPI_CHAR_LEN 7、#define SPI_BAUD_DIV 24而不是直接在代码里写魔数。这样后面改频率或者改位数时不容易漏改。最后再分享一个小技巧F28003x的SPI在配置完成后可以先发一个0xFF或者0x00的哑元数据把可能残留的状态清一清再开始正式通信。这个操作在某些从设备上能避免第一帧数据异常的问题。我自己在调一颗SPI接口的传感器时就是靠这个哑元传输解决了上电后第一帧总是错的问题。
