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

C2000调试实录:从仿真器连不上到FLASH启动崩溃的五大坑

这块TMS32F28P550板子折腾了我将近两周。项目要用它做双电机驱动控制硬件方案整体参考官方评估板没想到真正卡住我的不是电机算法反而是调试阶段接连踩的这几个坑。每一个坑单独拿出来都不算大串在一起却让人头皮发麻。这篇文章不打算讲芯片的外设怎么用而是把我在实际调试中遇到的真问题、完整排查链路和处理手法记录下来。整个过程用的调试环境是CCS XDS110芯片属于C2000家族F28P55x系列如果你也在用这系列芯片调电机、调数字电源或者工业控制项目这篇应该能帮你少走几天弯路。1. 仿真器连不上CCS报错查到最后是复位电容的锅1.1 故障现象新建Target Configuration选择XDS110和TMS32F28P550点击Test Connection等来的却是一行红字Error -1142 0x0 Error connecting to the target: (Error -1142 0x0)第一次遇到这个错误时我下意识以为是板子没焊好。毕竟这块板子是我自己画的不是官方开发板PCB布线、焊料、元器件来源都可能是问题。TI的XDS110调试器我用了很多年几乎没有出过岔子所以第一反应是往硬件上怀疑。但换了一块官方开发板上去仿真器能正常识别芯片说明XDS110本体没问题。那问题就集中在自研板子这一侧。1.2 排查链路我按“从外到内”的顺序排查了一遍这个过程花了大半天回头看看很多步骤其实是可以并行或跳跃的。第一步排除USB线和驱动。换了一条短USB线重新安装XDS110驱动故障依旧。这条可以快速排除但必须做因为XDS110的驱动偶尔会被其他软件干扰。第二步量电压。用万用表量了3.3V和1.2V内核供电都在正常范围内。F28P55x这种规模的C2000芯片对电源时序有要求如果内核电压起来太慢仿真器连接也会失败。但从示波器看3.3V和1.2V的上电时序没有问题。第三步量JTAG线。查了TMS、TCK、TDI、TDO四根线和GND对仿真器头到芯片引脚的导通性逐一测量全部正常。排除了虚焊、断线这些物理问题。第四步量复位引脚XRS。这是找到突破口的关键一步。用示波器抓XRS引脚的波形发现一个很微妙的现象上电瞬间XRS被拉低的时间持续了大约500ms。而我用的复位电路是10uF电容加下拉电阻RC充电时间常数偏大导致复位释放时间非常长。XDS110连接目标板时有一个超时窗口如果在这个窗口内没有等到目标CPU的应答就会直接报-1142错误。500ms的复位时间显然超出了调试器的等待范围于是连接失败。第五步修改复位电路。把10uF复位电容换成100nF同时检查复位芯片的RESET输出时间确保整个复位释放过程在几十毫秒内完成。再次Test Connection一次通过。检查项排查结果结论XDS110本体官方板可连接排除USB线/驱动正常排除3.3V/1.2V电压正常排除JTAG四线导通正常排除XRS复位波形上电拉低约500ms根因方向复位电容10uF过大根因这里有一个容易忽略的点复位电容的大小在设计时主要考虑上电复位的可靠性但在调试阶段它必须兼顾调试器的连接时序。很多人在画板时喜欢把复位电容加大觉得这样更“稳”实际上却给仿真器连接埋了坑。不仅仅C2000其他MCU也有类似情况。1.3 排查结果为后续带来的几个习惯这个坑让我养成了几个习惯之后调试新板子省了不少事第一版板子先焊最小系统电源、时钟、复位、JTAG这四样确认无误再焊周边外设。TRST引脚不要悬空建议按下拉电阻处理具体阻值参考数据手册这个引脚影响JTAG调试模式的进入。连接测试之前先量一下3.3V对地阻抗防止短路或者焊接桥连。示波器抓XRS波形比万用表量电平有用得多电平正常不代表时序正常。提示如果你的板子CCS连不上优先查三样电源时序、复位时间、JTAG信号完整性。找不到问题的时候把JTAG时钟频率调低一档再试有时候走线太长或者阻抗不匹配就会在高速下翻车。2. RAM里一切正常下载到FLASH后程序“消失”了2.1 现象还原仿真器连接问题解决之后程序在CCS的Debug模式下跑得很欢LED闪烁正常串口输出也正常。我以为项目已经顺了就想着烧一版到FLASH里跑跑看结果断电重启之后板子完全没动静LED不闪串口也没有任何输出。把仿真器重新连上程序在RAM里还是正常的。这就很诡异了——同一份代码一个跑得好好的一个死寂。那段时间我一度怀疑是FLASH烧录时出了问题反复擦除、重新烧录了好几次日志都显示校验成功。这反而让我更困惑烧录没问题为什么启动不了2.2 Boot引脚与Boot ROM被忽视的启动链路C2000的启动流程和ARM Cortex-M不太一样。C2000芯片内部有一块Boot ROM上电后CPU先执行Boot ROM里的代码。Boot ROM会采样一组GPIO引脚的状态具体是哪几个引脚要看芯片数据手册里的Boot Mode表格根据这些引脚的电平组合决定下一步跳转到哪里可以跳到FLASH、RAM、SCI引导、SPI引导等。我的板子上Boot模式引脚本来是用电阻配置好的但其中有一个引脚同时接到了外部外设上那个外设上电默认输出高电平直接把Boot引脚拉高了。结果Boot ROM没有进入Boot to Flash模式而是进入了SCI boot模式程序自然就跑不起来。这个问题的排查过程有点波折因为Boot模式引脚的电平状态不是一眼能看出来的。我当时是通过CCS的Registers窗口查看Boot ROM相关寄存器发现启动模式和预期不符才顺着这条线查到了引脚电平冲突。排查步骤可以总结为确认芯片是否进入了FLASH启动模式。看数据手册的Boot Mode表格用万用表量对应GPIO引脚的上电电平。检查Boot ROM的跳转目标。如果CCS支持连接后查看CPU寄存器可以看到当前PC的位置如果PC停在未知区域大概率是启动模式没选对。检查FLASH中入口地址的内容。在CCS的Memory Browser里直接看FLASH起始地址确认第一指令是不是一条跳转指令。2.3 CMD文件里的“暗礁”段放置与入口点解决了Boot引脚冲突之后程序还是没跑起来。这次我换了个方向直接检查链接文件。C2000工程里CMD文件的作用是把代码段、数据段放到指定的内存区域。我一个手写的CMD文件是从网上模板改的当时并没有仔细对照芯片的Memory Map。排查时发现.codestart段被放到了RAM里而不是FLASH起始地址。这里解释一下为什么这个问题非常致命。Boot ROM跳转到FLASH入口地址后第一件事就是执行入口处的代码。C2000通常把这个位置叫做.codestart段里面是一条跳转指令跳到C语言运行环境的初始化代码_c_int00。如果.codestart段不在FLASH入口地址Boot ROM跳过去后CPU从FLASH入口取指令得到的是空白或者随机数据程序直接飞掉。在RAM里调试时调试器会自动把PC设置到入口点所以即使CMD文件有错也能跑起来。但掉电后从FLASH启动时没人帮你设置PC一切都要靠Boot ROM跳转。这就是“RAM正常、FLASH不启动”最常见的几个原因之一。我当时的修复方式是把CMD文件里的段放置改对MEMORY { FLASH_BANK0 : origin 0x080000, length 0x008000 RAM_BANK0 : origin 0x008000, length 0x004000 } SECTIONS { .codestart : FLASH_BANK0 .text : FLASH_BANK0 .stack : RAM_BANK0 }注意不同型号的C2000FLASH起始地址可能不一样动手改之前一定要查对应型号的Memory Map和官方例程CMD文件。复制粘贴别人的CMD文件然后祈祷它能用是嵌入式开发里最危险的操作之一。这一段排查给我的经验是调试下载到RAM里跑得正常的程序实际上绕过了启动链路的所有问题。所以一旦涉及FLASH启动必须单独审视三件事——Boot Mode引脚配置、CMD文件的段放置、工程的Entry Point设置。3. 程序跑到一半进ITRAP最后抓到了数组越界3.1 现象随机死机暂停后PC停在ITRAPFLASH启动问题解决后程序能从FLASH正常启动了。但紧接着又遇到一个更头疼的问题程序运行几秒到几十秒不等随机“死机”。注意我用了“随机”这个词因为复现它需要碰运气有时候一切正常有时候刚开机就挂。用CCS暂停死机现场发现Current PC停在ITRAP对应的中断服务函数里。Stack窗口里的返回地址一个比一个离谱有的甚至指向了不存在的地址。ITRAP全称是Illegal Trap可以理解为C2000的非法指令陷阱。当CPU执行到未定义的操作码、从非法地址取指、或者访问了不允许访问的地址时会进入这个陷阱。既然程序跑到了ITRAP说明CPU在执行过程中的某一步碰到了“不该碰”的东西。3.2 排查链路从PC指针到数据断点我先看了一眼反汇编窗口ITRAP服务函数里就是一条等待循环正常的——它本来就是死循环等待用户处理。关键在于它为什么会进来。第一种可能真的有非法指令被执行。这种情况多发生在函数指针被错误赋值或者栈被破坏返回地址变成了乱码程序跳到了随机位置开始取指。第二种可能中断向量表被破坏。C2000的PIE向量表默认在RAM区域而RAM是可以被CPU随意写入的。如果有一个指针越界写到PIE向量表所在区域中断响应时CPU从向量表取到的地址就是错的跳转后程序就废了。我先用CCS的Memory Browser看了一眼PIE向量表区域发现里面有一些内容确实不正常——某些中断向量地址被改写了。这就说明存在某种非法写入行为。但问题来了是谁写的这时候我用了CCS的数据断点功能Watchpoint在PIE向量表被破坏的那个地址上设置了一个“写入触发”断点。程序全速跑起来一旦有代码尝试写这个地址CCS就会立即停下来把写者暴露在调用栈里。很快断点触发了。调用栈指向了UART接收中断服务函数更具体的说是里面一个环形缓冲区的写操作。缓冲区定义的是一个数组buf[32]写索引bufIdx在边界条件下可能跑到32以上这一写就越界了。越界位置经过内存布局计算恰好落在了PIE向量表区域之前的一个数据结构附近虽然没有直接踩到向量表但破坏了相邻的RAM后续又连锁破坏了更多区域最终把向量表也波及到了。修复方法很简单给写索引加边界判断。但排查过程费了不少时间因为这个bug的出现完全取决于接收数据的时序是典型的“间歇性故障”。3.3 数据断点在C2000调试中的实战手法这次经历让我意识到遇到“程序随机死机”这类问题不要急着逐行读代码。更高效的做法是先用Memory Browser检查可疑区域栈顶、PIE向量表、堆区的内容是否合理。再用数据断点监控关键地址的读写访问。触发断点后通过调用栈找到肇事代码。关于数据断点有两点需要注意。第一硬件资源有限XDS110一般只能同时设置一两个数据观察点所以要把机会用在最可疑的地址上不要到处乱撒。第二数据断点的触发条件可以设置为“读”“写”或“读写”如果怀疑某个变量被莫名修改直接对那个变量地址设置一个写断点让程序自己告诉你谁碰了它。另外一个和ITRAP相关的常见坑是在中断里调用浮点运算时如果编译器没有正确保存FPU上下文也可能触发非法指令。C2000的FPU和TMU毕竟不是ARM内核那种自动压栈的架构在写中断服务程序时尽量保持简单复杂计算放到主循环里做这也是很多老嵌入式工程师的经验。4. SCI串口打印乱码罪魁祸首是时钟配置4.1 现象波特率明明设对了收到的还是乱码FLASH启动正常、程序稳定运行之后我开始调试串口通信。连接USB转串口工具打开串口调试助手波特率设置1152008N1点开串口收到的数据乱成一团偶尔一个字节能对上但绝大多数时候是乱码而且每一帧的乱码规律都不一样。程序里波特率配置我反复检查过公式用的也是TI官方手册里的BRR (LSPCLK / (波特率 * 8)) - 1我的LSPCLK预期是25MHz算出来的BRR大约是26配置进SCIHBAUD寄存器看起来没有任何问题。4.2 实测波特率示波器不会骗人代码看起来没问题但串口就是乱码。这种情况下再看代码没有意义我直接用逻辑分析仪去抓TXD引脚的波形。数一下一个bit的宽度大约是10.4微秒换算下来实际波特率大约96000和115200差了将近17%。这个误差早就超过UART的容忍范围乱码是必然的。问题变成了为什么预期的115200实际输出却差了17%答案出在时钟源头。我配置PLL的时候参考时钟用的是外部晶振20MHz但PCB上的外部晶振实际上没有起振。原因有两种可能要么晶振焊接不良要么负载电容不合适。芯片检测不到外部时钟就自动使用了内部INTOSC110MHz作为时钟源。我的PLL配置完全按20MHz来算实际系统时钟就跑到了预期值的四分之三左右。为什么是四分之三而不是一半因为我配置的PLL倍频关系里INTOSC1和外部晶振频率比不同再加上分频系数最后SYSCLK变成了预期值的约75%LSPCLK也跟着变成了同步比例SCI波特率自然就从115200变成了96000左右。修复分两步。第一步检查外部晶振发现确实是负载电容虚焊导致晶振没起振重新补焊后外部晶振起振正常。第二步在代码里增加PLL锁定等待防止时钟源还没有稳定就切换这个细节在官方例程里有但很多人会忽略。4.3 不同时钟配置下的波特率误差分析这里给出一组对比数据方便理解为什么波特率寄存器看似算对了实际还是出错。LSPCLKBRR计算值BRR取整实际波特率误差25MHz26.12261157400.47%20MHz20.7021113636-1.36%15MHz15.27151171871.73%10MHz9.8510113636-1.36%UART通信一般要求两端波特率误差在2%以内。从表里可以看到有些情况下误差已经开始接近临界值如果再叠加上时钟源本身的误差就容易出现乱码或者偶发错帧。这也是我后来坚持用逻辑分析仪实测波特率的原因。代码里的计算值只是理论值实际时钟源可能因为晶振没起振、PLL没锁定、或者分频系数配置错误而偏离理论值。示波器和逻辑分析仪不会说谎一眼就能看出真实波特率。4.4 串口调试的实操建议调串口之前先跑一个GPIO翻转的程序用示波器量一下翻转频率确认系统时钟是对的。串口乱码时不要只盯着波特率这一个变量先确认时钟树整条链路没有偏离。上拉电阻、引脚MUX配置也会影响通信但通常这类问题会导致完全收不到数据而不是乱码可以用这个现象做初步区分。提示逻辑分析仪不贵与其靠猜波特率问题浪费时间不如直接看波形数bit宽度。一个bit宽度时间秒 1 / 波特率拿这个理论值和实测值对比一切清清楚楚。5. 变量被“神秘修改”优化等级背了锅5.1 现象-O0正常-O2出问题串口通信稳定之后我以为调试阶段终于要结束了。结果把工程切到Release模式编译选项从-O0换成-O2程序行为立刻变了。主循环的状态机乱跳某个功能模块的开关标志位在中断里明明置位了主循环轮询它却一直读不到。-O0正常、-O2异常这几乎是嵌入式开发里最经典的“优化级Bug”信号。5.2 volatileCPU缓存带来的假象这个标志位是一个全局变量在UART接收中断里被设为1主循环里轮询它判断是否收到了完整的一帧。问题是这个变量没有加volatile修饰。编译器在-O2优化下会把频繁访问的全局变量加载到寄存器里缓存起来。主循环轮询它时CPU一次从内存读到寄存器之后一直用寄存器里的值来判断并不知道中断已经改了内存中的值。于是主循环永远读到旧值状态机自然就乱套了。加了volatile之后编译器每次使用这个变量都会重新从内存读取不再缓存到寄存器问题立刻消失。// 错误中断修改主循环轮询 uint16_t g_rx_flag 0; void UART_ISR(void) { g_rx_flag 1; // 中断里置位 } void main_loop(void) { while (g_rx_flag 0) { // 编译器可能把g_rx_flag缓存到寄存器永远读不到中断里的修改 } // ... }// 正确加volatile每次都从内存读 volatile uint16_t g_rx_flag 0;很多人觉得volatile是“老生常谈”但实际项目中真正把它用对的人其实不多。volatile的本质是告诉编译器“这个变量的值可能在编译器不知道的情况下被改变”而中断恰恰是异步于主程序发生的主程序不可能知道中断什么时候修改了变量。但是volatile只是治标它不能解决所有共享变量问题。5.3 位域与读-改-写冲突volatile解决不了的坑接下来的坑更隐蔽。我在一个结构体里用了位域来组织通信协议里的状态位中断里给一个位赋值主循环里给另一个位赋值看上去两者互不干扰。但位域的赋值不是原子操作。它在底层执行的是“读-修改-写”三个步骤先读出整个字节修改其中的某一位再把整个字节写回去。如果中断恰好在这个三步操作的中间发生中断也去修改了同一个字节的另一个位那么中断结束后主程序的“写回”操作会把中断刚刚写入的那一位覆盖掉。这种情况下即使两个位分别由不同代码段访问最终会出现“丢失更新”。volatile无法解决这个问题因为它只保证每次读写都从内存中执行不能保证操作的原子性。解决方案不外乎三种在修改共享变量时关闭中断完成后再开中断保证操作期间不被异步打断。使用专用的原子操作指令但C2000的位操作是否原子要看具体指令集和编译器的实现。避免在主程序和中断之间共享同一个字节内的多个位字段拆分到不同变量或者干脆用完整字节的变量。我最终采用的办法是主程序和中断之间共享的状态变量全部使用完整的字节或字变量并在赋值时短暂关闭中断保护临界区。代码丑一点但稳定可靠。5.4 遇到Release模式才出问题时的排查经验这类问题有一个通用的排查路径我后来基本上按这个顺序执行遇到“只有Release模式才出的bug”先在代码里搜索所有跨中断/主循环共享的全局变量检查有没有漏加volatile。如果变量加volatile后问题依然存在怀疑是不是存在读-改-写冲突重点检查位域和位操作。还可以用CCS的反汇编窗口查看-O0和-O2生成的汇编差异对比主循环轮询部分的汇编代码看看编译器是不是真的把变量访问优化掉了。调试阶段尽量用-Og或-O1这类“能保持调试体验但又有一定优化”的等级不要一直用-O0因为有些和优化相关的bug只有到高优化等级时才会暴露出来。提示数据断点同样适用于这种问题。给可疑变量设置一个写入断点程序跑到任何一处修改它的代码都会立刻停下。但要注意硬件观察点资源有限一次设一个盯住最可疑的。写在最后这次调试留给我的几个固定动作回看这次TMS32F28P550的调试过程最大的体会是C2000调试里大部分看起来“玄学”的问题底层都是电源、时钟、复位、内存访问这几件事在作祟。把这些基础环节按顺序排查清楚很多问题根本不用猜。另外一个实在的经验是遇到数据被篡改别急着读代码先在CCS里给那个地址加个数据断点让程序自己告诉你谁碰了它。希望这篇实录能帮正在调TMS32F28P550或者其他C2000芯片的朋友省下几个通宵。
分享:

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

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