尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TMS320F28003x SPI寄存器级配置与调试实战

TMS320F28003x SPI寄存器级配置与调试实战 1. 项目缘起与整体设计思路1.1 为什么选择TMS320F28003x的SPI外设TMS320F28003x属于TI C2000系列实时微控制器主频100MHz带FPU和TMU在数字电源、电机驱动、光伏逆变这些场景里用得非常多。我手头这个项目是做三相PFC的数字控制板主控就是F28003x板子上挂了EEPROM、外部ADC和一颗隔离型栅极驱动配置芯片三者都走SPI总线。选SPI而不是I2C或者UART理由很直接SPI是全双工同步串行速率能拉到几十MHz协议开销几乎为零适合周期性读取ADC采样值这种对时序敏感的场景。I2C虽然省引脚但速率和总线仲裁机制在强干扰的功率板上容易出问题UART是异步的没有时钟线长距离传输时波特率误差累积会丢帧。F28003x内部有多个SPI模块我这次用的是SPIA和SPIC。SPIA接外部ADCSPIC接EEPROM和驱动配置芯片通过不同的片选引脚分时复用。这里有个设计取舍为什么不用一个SPI挂三个从设备因为外部ADC的采样率要求高需要独占总线避免被其他设备的传输阻塞而EEPROM和配置芯片都是低速设备共享一条总线完全够用。这种“高速独占、低速共享”的分配思路在实际项目里比一股脑全挂一条总线要稳妥得多。1.2 寄存器配置与库函数配置的取舍TI给C2000提供了driverlib库很多新手直接调SPI_init()就完事了。但我这个项目对时序要求苛刻库函数封装了一些默认行为比如FIFO触发深度、时钟相位极性这些如果不去翻寄存器手册出了问题根本不知道从哪查。所以我的做法是用driverlib做初始化框架但关键寄存器手动覆写尤其是SPICCR、SPICTL、SPIBRR、SPIFFTX、SPIFFRX这几个。这样既保留了库函数的可读性又能在调试时精确控制每一个bit。具体来说SPICCR负责时钟极性和字符长度SPICTL管相位、主从模式和中断使能SPIBRR决定波特率SPIFFTX和SPIFFRX是FIFO的发送接收控制。很多人配置SPI不生效十有八九是SPICCR里的SPICHAR字符长度和实际数据宽度对不上或者SPICTL的CLKPOLARITY设反了。这些细节后面会逐个拆开讲。1.3 整体调试策略的规划调试SPI不能上来就写代码我的习惯是分三步走第一步用示波器或者逻辑分析仪确认硬件连线没问题包括片选、时钟、MOSI、MISO四根线的电平和时序第二步写一个最简单的单字节收发测试不接从设备只测主设备能不能正常产生时钟和片选信号第三步接上真实从设备逐步增加数据量和速率观察波形是否畸变。这个顺序看起来笨但能帮你把问题隔离在最小范围内。我见过太多人一上来就写完整驱动结果通信失败既怀疑硬件又怀疑软件最后查了两天发现是片选引脚配错了复用功能。2. SPI寄存器核心细节逐位拆解2.1 SPICCR寄存器时钟极性与字符长度SPICCR的全称是SPI Configuration Control Register地址在SPIA基址0x0000。这个寄存器里最关键的三个字段是SPICHAR、CLKPOLARITY和SPISWRESET。SPICHAR占bit0到bit3决定每次传输的字符长度范围是1到16位。注意这里写的是“字符长度减一”比如你要传8位数据SPICHAR要写7不是8。这个坑我踩过当时设成8结果每次传输多出一个bit从设备完全不理我。CLKPOLARITY在bit6决定时钟空闲时的电平。如果从设备要求时钟空闲为低这一位写0空闲为高写1。SPISWRESET在bit7写0会让SPI进入复位状态配置其他寄存器之前必须先把它清零配置完再置1。很多人配置不生效就是因为忘了这一步SPI一直在复位状态寄存器写进去也没用。注意SPISWRESET复位后SPICCR和SPICTL的其他位才能正常写入。配置顺序一定是先复位、再配置、最后释放复位。2.2 SPICTL寄存器相位、主从与中断控制SPICTL是SPI Operation Control Register地址偏移0x0001。bit0是SPIINTENA使能SPI中断bit1是TALK主模式下必须置1才能发送数据从模式下置1才能接收bit2是MASTER_SLAVE1为主模式0为从模式bit3是CLOCK_PHASE决定数据在时钟的哪个边沿采样bit4是OVERRUNINTENA溢出中断使能。CLOCK_PHASE和CLKPOLARITY组合起来就是SPI的四种模式。Mode0是CPOL0、CPHA0数据在时钟上升沿采样、下降沿切换Mode1是CPOL0、CPHA1Mode2是CPOL1、CPHA0Mode3是CPOL1、CPHA1。大部分SPI Flash和EEPROM用Mode0或Mode3具体要看从设备手册。我这次用的外部ADC是Mode1配置芯片是Mode0所以两条SPI总线的SPICTL值是不一样的不能照抄。2.3 SPIBRR寄存器波特率的精确计算SPIBRR是SPI Baud Rate Register地址偏移0x0004。波特率的计算公式分两种情况当SPIBRR的值在3到127之间时波特率等于LSPCLK除以(SPIBRR1)当SPIBRR小于3时波特率等于LSPCLK除以4。LSPCLK是低速外设时钟默认是SYSCLKOUT除以4也就是100MHz除以4等于25MHz。假设我要把SPIA配到10MHz计算过程是SPIBRR1 25MHz / 10MHz 2.5取整后SPIBRR 1.5但SPIBRR必须是整数所以要么选SPIBRR1得到12.5MHz要么选SPIBRR2得到8.33MHz。实际项目中我选了SPIBRR2因为8.33MHz对外部ADC来说足够而且留了余量避免信号完整性问题。这里有个经验SPI速率不要贴着从设备的最高速率配留20%到30%的余量尤其是板子上有功率器件开关噪声的时候。2.4 SPIFFTX与SPIFFRXFIFO的发送与接收管理SPIFFTX是FIFO Transmit Register地址偏移0x000A。bit13是SPIFFENA使能FIFO增强功能bit14是TXFIFO复位bit8到bit12是TXFFIL发送FIFO中断触发深度。SPIFFRX是FIFO Receive Register地址偏移0x000Bbit13是RXFFIENA接收FIFO中断使能bit8到bit12是RXFFIL接收FIFO中断触发深度。FIFO深度是16级。我一般把TXFFIL设成8RXFFIL设成8这样发送到一半就触发中断填充下一批数据接收收到一半就触发中断读取避免溢出。如果不用FIFO每次收发一个字节都要进中断CPU开销太大在100MHz主频下虽然扛得住但会影响其他实时任务的响应。用了FIFO之后中断频率降到原来的八分之一CPU有更多时间处理控制算法。3. 完整实操流程与关键环节实现3.1 硬件连线检查与引脚复用配置F28003x的SPI引脚是GPIO复用的SPIA的默认引脚是GPIO16到GPIO19分别对应SPICLK、SPISIMO、SPISOMI和SPISTE。但实际板子上可能用了其他GPIO需要通过GPIO_setPinConfig()函数把引脚配成SPI功能。我这次SPIA用的是GPIO58到GPIO61因为GPIO16到GPIO19被PWM模块占用了。配置代码是这样的GPIO_setPinConfig(GPIO_58_SPICLKA); GPIO_setPinConfig(GPIO_59_SPISIMOA); GPIO_setPinConfig(GPIO_60_SPISOMIA); GPIO_setPinConfig(GPIO_61_SPISTEA);片选引脚我单独用了一个普通GPIO因为从设备需要软件片选硬件片选在F28003x上只有SPISTE一个不够用。软件片选的好处是灵活可以在传输前后精确控制片选拉低和拉高的时刻缺点是占用CPU时间。对于低速设备无所谓对于高速ADC我用了SPISTE硬件片选因为时序要求严格软件控制容易引入抖动。3.2 SPI初始化代码的完整实现初始化分四步复位SPI、配置时钟和字符长度、配置相位和主从模式、配置FIFO和波特率。下面是我实际项目里的代码基于driverlib但做了寄存器级覆写void SPI_A_Init(void) { // 第一步复位SPI SPI_resetModule(SPIA_BASE); // 第二步配置SPICCR // SPICHAR7 (8位), CLKPOLARITY0 (空闲低), SPISWRESET0 (保持复位) HWREGH(SPIA_BASE SPI_O_CCR) 0x0007; // 第三步配置SPICTL // MASTER1, TALK1, CLOCK_PHASE1 (Mode1), SPIINTENA0 HWREGH(SPIA_BASE SPI_O_CTL) 0x000E; // 第四步配置SPIBRR // LSPCLK25MHz, 目标8.33MHz, SPIBRR2 HWREGH(SPIA_BASE SPI_O_BRR) 0x0002; // 第五步配置FIFO // TXFFIL8, RXFFIL8, 使能FIFO HWREGH(SPIA_BASE SPI_O_FFTX) 0xE008; HWREGH(SPIA_BASE SPI_O_FFRX) 0x6008; // 第六步释放复位 HWREGH(SPIA_BASE SPI_O_CCR) | 0x0080; }这段代码里SPICTL写0x000E二进制是0000 0000 0000 1110bit1到bit3都是1对应TALK1、MASTER1、CLOCK_PHASE1。SPIFFTX写0xE008bit13到bit15是111使能FIFO增强和复位bit3到bit7是00001对应TXFFIL8。SPIFFRX写0x6008bit13和bit14是11使能接收FIFO中断和复位bit3到bit7是00001对应RXFFIL8。3.3 数据收发函数的编写与调试发送一个字节并接收返回值的函数uint16_t SPI_A_TransferByte(uint16_t txData) { // 等待发送FIFO有空位 while (SPI_getTxFIFOStatus(SPIA_BASE) SPI_FIFO_FULL); // 写入发送FIFO SPI_writeDataNonBlocking(SPIA_BASE, txData); // 等待接收FIFO有数据 while (SPI_getRxFIFOStatus(SPIA_BASE) SPI_FIFO_EMPTY); // 读取接收FIFO return SPI_readDataNonBlocking(SPIA_BASE); }这个函数是阻塞式的适合调试阶段用。实际项目里我改成了中断方式发送和接收都在FIFO中断里处理主循环只负责搬运数据。调试时先用阻塞式确认通信正常再切换到中断方式这样出了问题容易定位。3.4 逻辑分析仪抓取波形的关键观察点接上逻辑分析仪之后我重点看四个东西片选拉低的时刻是否在时钟开始之前、时钟的极性和相位是否和从设备手册一致、MOSI上的数据在时钟边沿是否稳定、MISO上的数据是否在正确的边沿被采样。我这次遇到一个典型问题片选拉低和第一个时钟沿之间只有10ns从设备要求至少50ns的建立时间导致第一个bit经常读错。解决办法是在片选拉低之后加一个短延时用DEVICE_DELAY_US(1)就够了1微秒的延时对8.33MHz的SPI来说完全可以接受。4. 常见问题排查与实战避坑指南4.1 SPI通信完全不生效的排查顺序通信不生效是最常见的问题我的排查顺序是先查引脚复用有没有配对再查SPI有没有从复位状态释放然后查片选有没有正常拉低最后查时钟有没有输出。这四步能覆盖90%的问题。具体来说用万用表测片选引脚传输时应该看到电平翻转用示波器测SPICLK应该有方波输出。如果片选不动检查GPIO方向寄存器是不是设成了输出如果时钟不动检查SPICCR的SPISWRESET是不是忘了置1。4.2 数据错位的三种典型原因数据错位表现为读回来的值整体偏移一位或者几位。原因一SPICHAR设错了比如实际传8位但SPICHAR设成了7导致每次少传一位。原因二CLOCK_PHASE设反了数据在错误的边沿被采样。原因三从设备的片选建立时间不够第一个bit丢失。这三种情况我都遇到过最隐蔽的是第三种因为波形看起来完全正常只是第一个bit的值偶尔不对。解决办法就是在片选拉低后加延时或者降低SPI速率。4.3 FIFO溢出与中断丢失的处理FIFO溢出表现为接收数据丢失或者中断进不去。SPIFFRX的bit15是RXFFOVF溢出标志位一旦置1说明接收FIFO满了但CPU没及时读走。处理方法是在接收中断里一次性把FIFO里的数据全部读出来而不是只读一个。我一般用while循环读直到SPI_getRxFIFOStatus返回EMPTY为止。另外RXFFIL不要设得太高设成8比较稳妥设成14的话只剩两个空位稍微有点延迟就溢出了。4.4 高速SPI的信号完整性优化SPI速率超过10MHz之后信号完整性问题开始显现。我这次把SPIA配到8.33MHz波形还算干净但之前试过12.5MHzMOSI上有明显的过冲和振铃。优化手段有三个一是在MOSI和SPICLK上串22欧姆的电阻抑制反射二是缩短走线长度SPI走线不要超过10厘米三是降低驱动强度F28003x的GPIO驱动强度可以通过GPADAT寄存器配置从默认的8mA降到4mA边沿变缓过冲减小。这三个手段我全用上了12.5MHz也能稳定通信。4.5 常见问题速查表现象可能原因排查方法解决办法完全无通信引脚复用未配置测引脚电平调用GPIO_setPinConfig完全无通信SPI处于复位状态读SPICCR bit7置位SPISWRESET时钟无输出主从模式配错读SPICTL bit2设MASTER1数据错位SPICHAR设错核对数据宽度SPICHAR宽度-1数据错位相位极性不匹配对照从设备手册调整CPOL/CPHA第一个bit错误片选建立时间不足测片选到时钟延时加1us延时数据丢失FIFO溢出读SPIFFRX bit15降低RXFFIL及时读FIFO高速振铃信号反射示波器看过冲串22欧姆电阻5. 调试心得与扩展思考5.1 寄存器级调试的价值用库函数能快速跑通但寄存器级调试能让你真正理解外设的行为。我这次把SPICCR、SPICTL、SPIBRR、SPIFFTX、SPIFFRX五个寄存器的每一位都翻了一遍手册虽然花了大半天时间但后面遇到问题时排查速度比之前快了好几倍。比如有一次接收数据偶尔多一个字节我直接读SPIFFRX发现RXFFOVF置1了立刻定位到是FIFO溢出而不是去怀疑从设备或者硬件连线。这种“知道自己在配什么”的感觉是库函数给不了的。5.2 多从设备共享总线的片选管理SPIC和SPIA不同SPIC上挂了EEPROM和配置芯片两个从设备。片选管理我用的是软件片选两个GPIO分别控制。关键点是切换从设备时必须先拉高当前片选等至少一个时钟周期再拉低另一个片选。如果两个片选同时为低两个从设备会同时驱动MISO线导致总线冲突。我一开始没注意这个EEPROM读出来的数据全是0xFF查了半天才发现是片选切换太快两个设备同时在线。5.3 从SPI扩展到其他外设的通用思路SPI调通之后我发现这套寄存器级调试的方法可以复用到I2C、UART、CAN这些外设上。核心思路是一样的先复位外设、再配置控制寄存器、然后配置速率和中断、最后释放复位。区别只是寄存器的名字和字段不同。所以花时间搞懂一个外设的寄存器对理解整个芯片的外设架构都有帮助。我后面调I2C的时候直接套用SPI的调试流程半天就跑通了。5.4 后续可以继续深挖的方向这个项目目前SPI跑在8.33MHz数据量不大FIFO中断方式完全够用。如果后续要接更高速度的ADC比如20MHz以上的可以考虑用DMA来搬运SPI数据进一步降低CPU开销。F28003x的DMA支持外设触发SPI的接收FIFO可以触发DMA搬运这样CPU只需要在DMA传输完成中断里处理数据就行。另外SPI的TI模式多数据线模式也可以研究一下用四根数据线并行传输速率能翻四倍适合大数据量的场景。这些等当前项目稳定之后再折腾先把基础打牢。
返回列表