拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TMS32F28P550调试实战:C2000 CCS仿真器与Flash烧写问题排查

把TMS32F28P550的板子拿回来那天我以为这会是一次很常规的调试。C2000系列玩了这么多年CCS装好、XDS110插上点一下Connect Target剩下就是写代码而已。结果刚上电就被教育了仿真器连不上、Flash烧不进去、程序自己复位、PWM一停电机就啸叫、串口调试助手那边全是乱码。这些问题单拎出来每个都是小事但密集地砸过来确实容易让人怀疑人生。这篇就是那段时间的调试问题实录主要围绕TMS32F28P550这款TI的C2000实时控制MCU记录我在CCSXDS110环境下遇到的高频问题、排查过程和最终解决办法。适合正在用或者准备用C2000系列做电机控制、数字电源、工业控制的工程师尤其适合从STM32转到C2000的朋友。文章里没有太多原理推导重点是实操很多坑是官方文档上不会明说、只有踩过才知道的那种。1. 实录一XDS110连不上目标板折腾半小时的真相1.1 现象复盘点Connect Target报错弹窗一句英文第一次拿到板子我习惯性先上电然后打开CCS新建一个空工程。点下Connect Target的时候弹窗出来一串英文大意是无法建立JTAG连接目标板没有响应。当时我第一反应是芯片焊接有问题拿放大镜看了半天引脚又拿万用表量了电源对地阻抗都没发现问题。后来仔细看了板子发现一个特别基础的坑板子上的3.3V电源灯根本没亮。检查了一圈是供电跳线帽没插到位。官方评估板一般有一个电源选择跳线可以从外部5V转3.3V也可以直接用板载LDO。跳线帽松了或者插错位置整块板子就没有3.3V仿真器自然连不上。这个问题说出来很丢人但在实际调试里出现的概率非常高尤其是很多板子到手时跳线帽是散装放在包装袋里的。1.2 排查顺序电源、复位、JTAG、线材一个都不能省如果遇到仿真器连不上我建议按下面的顺序排查避免像无头苍蝇一样乱试。第一量电源。先确认3.3V和1.2V内核电压是否正常不光是量电压值最好用示波器看波形排除电源芯片启动太慢或者有大的纹波。C2000这类芯片对上电时序有一定要求虽然不像FPGA那么严格但电源不稳时JTAG很容易初始化失败。第二查复位。JTAG连接过程中调试器需要将芯片置于复位状态然后再释放。如果复位引脚被一个大电容拉低或者复位芯片输出有问题仿真器就会一直报连接失败。我有一块板子就是复位脚上放了10uF电容导致复位时间过长XDS110在超时时间内等不到芯片就绪。换成一个0.1uF的电容或者调整复位芯片的延时参数就好了。第三看JTAG引脚有没有被复用。C2000的JTAG引脚有时候会被配置成GPIO使用虽然在连接仿真器之前芯片还没跑用户代码一般不会出现这个问题但如果你在芯片里烧过一段把JTAG引脚复用掉的程序并设置了从Flash启动那上电后引脚就被占用了仿真器自然连不上。这种情况通常需要把芯片擦除或者进入boot ROM模式才能恢复。第四换线。XDS110的USB线看起来都一样但有些线只支持充电不支持数据传输或者线材老化导致D/D-信号质量差。我踩过这个坑用一根很细的充电线连XDS110时报错换了一根带屏蔽的USB线立刻就正常了。另外如果使用外部XDS110注意JTAG排线的长度超过20cm还没屏蔽的话通信稳定性会明显下降。1.3 一个更隐蔽的坑XDS110固件版本和CCS版本不匹配还有一个问题容易被忽略就是XDS110的固件版本和CCS版本不匹配。新版本CCS在连接时通常会自动升级仿真器固件但如果你用的是比较老的CCS而仿真器固件已经被别的电脑升级到新版本就可能出现连接失败。解决方法是去TI官网下载最新的XDS110固件升级工具或者在CCS里手动触发一次固件更新。到这里关于仿真器连接的问题基本就是这些套路。只要电源、复位、JTAG信号、USB线这几个环节都干净XDS110连上TMS32F28P550是很稳定的。2. 实录二Flash烧录失败与CSM锁死差点以为芯片返厂2.1 现象复盘第一次能烧第二次报擦除失败仿真器终于连上了我兴致勃勃地写了第一个点灯程序加载进去跑起来一切正常。然后我改了代码再次烧写CCS直接报错说是Flash擦除失败某个sector无法访问。当时我以为是芯片坏了换了一块新的结果还是一样。后来才意识到问题出在C2000特有的安全机制上。C2000的Flash不是一块完全开放的内存它被划分成若干个zone每个zone都有对应的CSMCode Security Module或者DCSMDual Code Security Module模块。如果这些zone里的密码位被设置成了非全FF的值芯片就会进入锁定状态。一旦锁定JTAG就无法读取或写入对应的Flash区域表现出来就是擦除失败、Program失败甚至整个芯片连接时直接提示Target is locked。2.2 为什么会锁死DCSM密码区和调试期的坏习惯很多初学者不知道C2000有这个机制在写Flash的时候会不小心把密码区的数据写进去。比如你定义了一个const数组链接脚本把它放到了Flash的密码区里面恰好不是0xFF那芯片就被锁了。还有一种情况是量产烧录工具会刻意写入密码保护代码如果你误用了量产配置文件来烧开发板也会把芯片锁住。我在调试阶段吃过一次亏为了测试Flash读保护功能我手动往DCSM寄存器里写了一个非默认值然后忘了保存密码。结果再想连仿真器软件一直提示芯片被锁定而我又不知道密码是什么那块芯片基本等于报废。好在后来查了TI的wiki发现某些型号可以通过boot ROM里预留的解锁流程恢复但非常麻烦。2.3 处理流程判断是否锁定、备份密码、关闭保护遇到锁死问题第一步在CCS的Debug配置里找一下有没有带Lock/Unlock相关的选项。有些版本的CCS会在连接目标板的时候弹出密码输入窗口如果你手上有密码填进去就能解锁。如果没有这个弹窗可以查看CCS的syscfg文件或者target configuration里的配置项看是否启用了DCSM组件。第二步如果你怀疑是密码区的非法数据导致的锁死可以尝试用TI的Code Composer Studio里提供的“On-Chip Flash”插件做一次Mass Erase也就是全片擦除。全片擦除会把你设置的密码也一并擦掉芯片就能恢复。前提是芯片没有使能“防全片擦除”的保护位否则这个方法也无效。第三步也是最推荐的开发调试阶段在工程里显式地把DCSM配置成“不使能保护”。TI的例程工程里通常有一个dcsm_z1_z2.c或者类似的源文件里面对密码区做了初始化。你要检查里面的密码值是不是默认的0xFFFFFFFF。如果不是务必改回去并且把Zone1和Zone2的EXEONLY位都设为0这样才能保证调试时Flash可以被随意读写。2.4 Flash编程的另一个坑擦除阶段CPU不要从Flash取指烧写Flash失败还有一种常见原因不是锁死而是操作顺序问题。C2000的Flash擦除和编程操作需要调用TI提供的Flash API这些API运行在RAM里。如果你把Flash API放在Flash里执行在擦除Flash的时候代码本身所在的sector也在被擦后果就是程序跑飞或者烧写失败。官方例程一般会把Flash API复制到RAM运行或者在链接脚本里给Flash API单独分配一个RAM段。手动建工程的时候很容易忽略这一步。我遇到过的情况是烧写校验总是不通过但芯片没锁单步跟进去发现Flash API在擦除过程中跳转到了一个非法地址。后来把代码中运行Flash API的函数放到RAM section问题就消失了。另外Flash的等待周期也要按照主频配置好。C2000的Flash在高速时钟下需要设置等待状态否则从Flash读指令会有概率出错表现为程序烧进去后运行不稳定时好时坏。用官方初始化函数Flash_initModule()可以一次性把等待周期、流水线使能都配好不建议手动改寄存器。3. 实录三程序一运行就复位看门狗和引导模式各占一半3.1 现象复盘仿真器能连上代码也能load但跑起来就是不对有一个非常典型的场景程序烧进去了点击Resume看起来一切正常但没过多久芯片就自动复位或者程序根本就没进main函数。用仿真器单步的时候又发现PC指针在一个奇怪的地方跳来跳去。这种问题如果只看代码很难找到原因因为问题往往出在上电到进main之间的那段启动过程。第一个要怀疑的就是看门狗。C2000的看门狗在芯片复位后默认是使能的如果你的工程没有在初始化阶段及时关闭或者喂狗程序运行后不久就会触发看门狗复位表现为系统反复重启。更麻烦的是看门狗还会在debug模式下继续工作即使你停在断点上它也可能把芯片复位导致你连断点都断不住。解决方案很简单在main函数最开始的地方或者更早的启动代码里调用SysCtl_disableWatchdog()把看门狗关掉。如果你的产品设计需要看门狗那就在外设初始化和主循环里都加上喂狗逻辑注意不要在长阻塞的高速循环里忘记喂狗。3.2 引导模式GPIO程序根本没进Flash第二个常见原因是引导模式引脚配置不对。C2000上电时芯片内部的boot ROM会采样一组GPIO的电平状态然后根据这些电平决定从Flash启动、从SCI启动、从SPI启动还是进入等待模式。不同型号对应的引脚不一样具体要查对应型号的Technical Reference Manual里Boot ROM章节的表格。我碰到过一次程序怎么烧都“没有反应”其实程序烧进去了也运行了但运行的不是我编译的最新代码而是一直执行boot ROM里的默认行为。因为我把板子上的boot引脚拨码拨到了SCI Boot模式芯片上电后一直在等串口下载程序根本没跳到Flash。用仿真器看PC指针停在了一个Boot ROM的等待循环里。排查方法看一下你板子上的boot拨码开关或跳线。如果支持多种启动模式先拨到Flash Boot也叫Boot to Flash模式再重新上电。注意有些板子在调试时会把boot引脚接到特定电平仿真器连接后如果程序已经烧在Flash里直接复位运行一般没问题但如果你习惯用CCS的Load Program程序运行方式可能和硬件启动模式混淆建议每次上电前都确认boot引脚状态。3.3 时钟配置卡死外部晶振没焊代码却在等它时钟配置也是一个能卡你一整天的问题。C2000芯片内部有振荡器也支持外部晶振和外部时钟输入。如果在syscfg或者初始化代码里选择了外部晶振作为PLL的输入时钟但板子上根本没焊晶振那么程序就会卡在时钟切换的等待循环里表现为加载后没有输出单步卡在SysCtl_setClock或者类似函数里面出不不来。我在一块自制的底板上就踩过这个坑参考设计里画了晶振的位置但我偷懒没有焊程序里又配置成外部时钟结果上电后串口没有输出调试器能连上但我单步就跟不动最后定位到时钟初始化函数。排查方法很简单检查板子上是否有外部晶振再检查代码里PLL输入时钟源的选择。如果没晶振就使用内部INTOSCInternal Oscillator作为时钟源或者把晶振焊上。调试的时候最好在程序里加一个超时退出机制万一时钟不可用也能跳到错误处理分支方便用串口打印状态。3.4 编译优化等级和变量被优化掉调试时看到的值不对还有一个看起来像“程序跑飞”的问题其实和优化有关。C2000的CCS默认编译优化等级可能是-O2或者更高你定义了一个全局变量在某个地方给它赋了值但调试的时候在Expressions窗口里看不到它变化或者看到的永远是一个常量。这不是代码逻辑问题是编译器把这个变量优化掉了。解决方法有三个第一给调试时要观察的变量加上volatile修饰告诉编译器不要优化它第二在工程属性里把优化等级调低比如-O0专门用于调试第三在CCS的Expressions窗口里勾选“Enable silicon real-time mode”并开启“Continuous Refresh”让软件持续读取目标板上的实际值。第三种方法在后文实时调试部分还会继续讲。4. 实录四实时控制场景下断点怎么打才不炸机4.1 现象复盘一暂停PWM输出就停了电机直接啸叫调电机控制或者数字电源的时候我最怕的不是代码写不出来而是想调试的时候没法停。你打一个断点CPU一停ePWM模块也跟着停止输出电机的相电流直接就断了。如果此时电机还在高速旋转轻则产生很大的反电动势重则烧驱动。或者你调的是数字电源一停PWM输出电压瞬间掉到零负载那边可能直接报故障。这时候就不能用普通断点。C2000的调试系统支持实时调试模式Real-time Debug在这种模式下CPU运行到断点时会暂停但是外设时钟持续运行PWM输出可以保持当前状态或者继续按配置输出波形。这样你可以安全地查看变量、修改参数然后再恢复运行。4.2 如何开启实时调试模式CCS里一个小开关在CCS里打开Target Configuration选择对应的仿真器和芯片型号在“Advanced”或者“Debug”选项卡里有一个“Enable silicon real-time mode”的选项把它勾上。然后在Debug视图下确认你的调试会话已经进入实时模式具体表现是工具栏上多了一个“Timing”相关的图标或者Modify Variables窗口可以实时刷新。接下来说断点。实时调试模式下最好使用硬件断点Hardware Breakpoint而不是软件断点Software Breakpoint。软件断点是用特殊指令替换原代码CPU跑到该地址时需要停下来但这个过程会打断正常的外设流程实时性不够好。硬件断点则是调试器硬件实时监测总线上地址匹配后触发暂停对外设影响更小。在CCS里右键断点处可以配置断点类型把“SW Breakpoint”改成“HW Breakpoint”。4.3 实时观察PWM寄存器Expressions窗口的刷新设置调试PWM时我还习惯在Expressions窗口里添加几个关键寄存器比如ePWM的CMPA、TBCTR还有ADC的转换结果寄存器以及电机的电角度、转速等变量。默认情况下CCS只有在目标芯片暂停运行时才会刷新这些窗口你需要在窗口上方的下拉菜单里把刷新模式改成“Continuous Refresh”并且确认已经开启实时模式。这样即使CPU在执行while(1)循环窗口里的变量也会周期性刷新方便你观察控制环路里每个环节的值。如果发现窗口刷新还是会跳变或者卡顿检查一下是否把太多变量加进去了。实时刷新走的是JTAG口虽然XDS110的带宽在调试器里算不错的但如果你同时在窗口里刷几百个变量刷新速度会非常慢。建议一次只保留最关键的二三十个变量。4.4 实战建议能用日志输出就别迷信断点说实话实时控制类项目真正跑到最后我几乎不打断点。干扰外设输出倒是其次主要是一旦断点触发控制环路上的所有中间状态都会跳变你看到的变量很可能已经不是真实运行状态下的值了。更好的办法是利用片上资源做“软示波器”把关键变量存成一个数组周期性地记录下来然后在调试模式下把数组拷出来画曲线。我自己的习惯是用DMA定期把ADC采样结果和控制环路计算结果搬运到一块RAM缓冲区然后在断点处把所有数据导出放在MATLAB或者Python里分析。这样既不干扰实时控制又能看到完整的波形细节。同时配合一个串口打印函数把电机状态机、错误标志等离散量打出来两相结合定位问题比单步调试快得多。5. 实录五串口调试助手联调乱码、丢帧与假死机5.1 现象复盘串口调试助手收到的全是乱码C2000的SCI模块其实就是我们常说的UART。调串口的时候最典型的问题就是上位机用串口调试助手发数据单片机收到了但回传的内容全是乱码。有些时候是固定乱码有些时候是前几个字节正常后面全乱。出现这种情况先别急着怀疑代码按下面的顺序排查。首先看波形。用示波器或者逻辑分析仪夹在TXD引脚上看一帧数据的位宽。比如配置115200波特率那么每一位的宽度应该是8.68us左右。如果测出来位宽不对要么是波特率配置错了要么是SCI模块的时钟频率和你代码里用的不是同一个值。C2000的SCI挂在低速外设时钟LSPCLK上如果你的时钟树初始化没有把LSPCLK配成预期值那么波特率计算就会整体偏移。其次看串口调试助手的参数。波特率、数据位、停止位、校验位这四项必须和代码里配置的一致。我知道这听起来很基础但实际调试中经常出现上位机设了115200下位机代码却是9600或者下位机配置了偶校验上位机没有开启校验结果是每一个字节都多了一个校验位自然全是乱码。还有一个很容易被忽略的点TXD和RXD有没有接反。我在自制的转接板上因为两个排针标注印反了线序就反了单片机发出去的东西根本没到上位机上位机发的东西单片机也没收到。结果串口调试助手显示“接收无数据”而我还以为代码哪里没配置对。最简单的验证方法在单片机程序里做回环测试直接收发短接自发自收看数据对不对先把链路验证干净。5.2 丢帧和假死机FIFO阈值和中断代码太冗长串口的问题除了乱码还有丢帧和假死机。丢帧最典型的场景是上位机发了十几个字节单片机只正确处理了前几个后面的数据丢了或者收到的内容错位。原因通常是接收缓冲区溢出或者接收中断处理速度跟不上。C2000的SCI有FIFO功能可以把接收中断触发级别设成一个合理的值。比如设置成FIFO里收到4个字节再触发一次中断不要收到一个字节就弹一次中断这样可以降低CPU被频繁打断的概率。同时中断服务函数里要尽快把FIFO里的数据读出来存入软件缓冲区不要在中断里做耗时的信号处理、字符串解析等操作。假死机的问题往往也和中断有关。如果串口中断里有一个死循环等待某个标志位而那个标志位因为逻辑错误永远等不到程序就会卡在中断里表现就是整个系统假死串口调试助手发送什么都没反应。排查方法是把中断服务函数尽量写得短小只做数据搬运和置标志位真正的解析放在主循环里。5.3 SCI初始化与发送示例示意代码以官方SDK为准下面给一段基于C2000 driverlib风格的SCI初始化示意代码我用的是SCIA引脚和时钟树都参考了官方例程具体函数名以你下载的SDK版本为准。// SCIA 串口初始化115200-8-N-1开启FIFO void scia_init(void) { // 1. 配置GPIO复用为SCI功能 GPIO_setPinConfig(GPIO_28_SCIA_RX); GPIO_setPinConfig(GPIO_29_SCIA_TX); GPIO_setDirectionMode(28, GPIO_DIR_MODE_IN); GPIO_setDirectionMode(29, GPIO_DIR_MODE_OUT); GPIO_setPadConfig(28, GPIO_PIN_TYPE_STD); GPIO_setPadConfig(29, GPIO_PIN_TYPE_STD); // 2. 配置SCI参数 SCI_setConfig(SCIA_BASE, DEVICE_LSPCLK_FREQ, 115200, (SCI_CONFIG_WLEN_8 | SCI_CONFIG_STOP_ONE | SCI_CONFIG_PAR_NONE)); // 3. 复位并清除状态 SCI_resetChannels(SCIA_BASE); SCI_clearOverflowStatus(SCIA_BASE); // 4. 开启FIFO并设置接收中断触发级别为4字节 SCI_enableFIFO(SCIA_BASE); SCI_resetTxFIFO(SCIA_BASE); SCI_resetRxFIFO(SCIA_BASE); SCI_setFIFOInterruptLevel(SCIA_BASE, SCI_FIFO_TX0, SCI_FIFO_RX4); // 5. 使能接收中断和SCI模块 SCI_enableInterrupt(SCIA_BASE, SCI_INT_RXFF); SCI_enable(SCIA_BASE); } // 发送一个字节等待FIFO有空位再写入 void scia_send_char(uint16_t c) { while (SCI_getTxFIFOStatus(SCIA_BASE) SCI_FIFO_TX4) { // 等待发送FIFO低于阈值 } SCI_writeCharNonBlocking(SCIA_BASE, c); }这里特别提一下DEVICE_LSPCLK_FREQ这个宏。它必须在board_init或者system_init里根据你实际的时钟树配置被赋值否则波特率算出来就是错的。我见过有人在syscfg里改了PLL倍频但没有同步更新这个宏导致串口波特率全错排查了很久才找到。5.4 串口调试助手的几个使用习惯最后说几个使用串口调试助手的小习惯。第一工程中需要区分Hex模式和ASCII模式。如果你在串口调试助手里发送的是十六进制数字比如01 03 00 00 00 01而下位机是按字符串解析的那收到的就是一堆乱码。反过来也一样。调试前先约定好协议格式别一边用Hex一边用ASCII。第二发送命令时注意是否自动追加回车换行。很多串口调试助手默认在发送内容后面自动加\r\n。如果下位机协议里没有处理换行符可能每个命令后面都会多出两个字节导致解析错位。建议在调试助手里关闭自动加回车换行或者在代码里把\r\n都过滤掉。第三严格共地。电脑的USB地、目标板的地、外部电源的地必须连在一起否则串口电平参考点不一致轻则乱码重则烧芯片。这个问题在接隔离电源时特别常见我遇到过几次串口偶尔正常偶尔乱码最后发现是地线没有接牢。6. 高频问题排查速查表与一点经验总结6.1 调试问题速查表下面这个表格是我调TMS32F28P550期间整理出来的高频问题速查表建议收藏。遇到问题先对着表格过一遍很多坑不用重新踩。故障现象最常见原因检查顺序处理办法仿真器连不上供电、复位、JTAG引脚、USB线量3.3V查复位电容查线材插好跳线帽更换USB线调整复位延时Flash擦除/烧写失败CSM/DCSM锁定、Flash等待周期不对查看CCS连接提示确认DCSM配置Mass Erase开发阶段关闭DCSM保护程序运行后反复复位看门狗默认使能检查看门狗配置初始化早期关看门狗或正确喂狗程序没进main函数引导模式GPIO拨到了非Flash Boot查boot引脚电平切换到Boot to Flash模式串口输出乱码时钟配置和波特率不一致示波器测TXD位宽统一LSPCLK频率检查调试助手参数串口丢帧/假死机FIFO阈值过高、中断处理太长单步看中断标志调整FIFO触发级别中断只做数据搬运PWM输出异常或啸叫调试断点暂停了外设检查CCS实时模式开启Real-time Debug用硬件断点调试变量显示不对编译器优化、窗口未刷新确认优化等级看窗口刷新状态加volatile开启Continuous Refresh6.2 几点个人建议调试TMS32F28P550这段时间我自己最大的体会是这类实时控制芯片的调试和普通MCU有很大不同核心区别在于“不能随意暂停”。所以调试思路也要跟着转变少依赖断点多依赖信号、日志和状态机。建议在工程里从一开始就加入一个轻量级的调试日志模块把关键事件和错误码通过串口打出来。不需要很复杂一个环形缓冲区加一个空闲中断发送就够用。有了这个排查问题的时候效率会高很多不用反复烧写调试。另外开发阶段一定不要开启DCSM保护。很多C2000的老工程师会有意无意地忽略这件事直到某天芯片锁死了才追悔莫及。调试阶段保持Flash完全可读可写量产前再单独评估是否要加保护这样最稳妥。最后再分享一个小技巧每次烧写前先点一下CCS里的“Restore Debug State”或者把工程重新build一次避免加载旧固件。我因为没clean直接点烧写遇到过好几次程序明明改了却没生效的情况浪费了不少时间。养成好习惯很多看似莫名其妙的问题都能提前避免。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门