ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TMS32F28P550实战调试:从仿真器连接到Flash运行的完整排坑指南

TMS32F28P550实战调试:从仿真器连接到Flash运行的完整排坑指南 1. 为什么这次调试盯上了TMS32F28P550先说项目背景。我们做的是伺服驱动器的主控板升级原来用的是老一代C2000芯片算力吃紧ADC采样和PWM更新在高载频下有点跟不上了。选了TMS32F28P550这颗料看中的是它的实时控制外设组合——多个高精度PWM通道、双ADC内核、强大的中断调度加上主频足够跑复杂的FOC算法。说白了这颗芯片就是奔着电机控制和数字电源这些高实时性场景去的国产替代和成本考量反而排在后面。调试这颗芯片和我以前调STM32、调其他ARM核MCU的感觉完全不一样。C2000系列是C28x内核不是Cortex-M指令集、存储器映射、外设架构、调试接口都有自己的一套逻辑。比如它的中断机制叫PIE外设中断扩展向量表管理方式跟ARM的NVIC完全不同它的PWM模块叫ePWM每个模块内部有时钟分频、相位同步、死区生成、斩波调制一堆逻辑随便配错一个位波形就千奇百怪。更别提第一次上电时仿真器连接、时钟配置、启动模式每一步都可能是坑。这篇文章就把我从拿到样片到跑通第一版电机开环转动的完整调试过程记录下来重点说说那些真正卡过我的问题、我排查的思路、以及最后是怎么解决的。如果你也正在用F28P550或者其他C2000型号做产品这篇应该能帮你少走一些弯路。特别是那些“看起来莫名其妙”的问题——连不上仿真器、程序跑飞、串口乱码、烧进Flash后行为不一致——我都会把根因和验证过程讲清楚。先说明一下我用的开发环境是TI官方的CCSCode Composer Studio仿真器是XDS110调试语言是C。后续所有的寄存器配置、界面操作都基于这个环境。2. 仿真器连接与CCS环境里最容易翻车的几个细节2.1 连接目标板的“玄学”问题多半出在电源和时序第一次把XDS110接到目标板打开CCS的Target Configuration准备连接报错信息很常见Error connecting to the target: (Error -1135 0x0) The debug probe reported an error. Confirm debug probe configuration and connections, restart the debug probe, then retry the operation.这个报错在C2000调试里太经典了几乎每个用过TI芯片的人都见过。网上的通用建议是检查JTAG接线、重启仿真器、重刷固件但我这台机器上这些方法全试了一遍问题照旧。最后排查下来根子出在目标板的供电时序上。TMS32F28P550的电源域有3.3V的I/O电压和内核电压有些板子的DSP内核供电是后级LDO从3.3V转出来的。XDS110上电后仿真器会通过JTAG引脚给目标芯片发送连接请求如果这时内核电压还没稳定下来芯片内部的debug逻辑就没准备好连接自然失败。解决方法是先单独给目标板上电等电源指示灯完全稳定确认万用表量到3.3V和内核电压都正常后再点连接。另外如果目标板上有大电容或者电机驱动这种大负载连接仿真器之前最好把功率部分断开避免上电瞬间的压降把电压拉到阈值以下。还有一个特别容易忽略的点JTAG信号里的TRST引脚。C2000的JTAG口上TRST是复位JTAG TAP控制器的信号如果它被拉低整个调试接口就废了。有些第三方的转接板或者自制调试口原理图里TRST悬空这在某些芯片上能凑合在F28P550上就是连不上。我后来翻原理图发现我们的调试接口设计里TRST是通过10k电阻上拉的但PCB Layout时走线太长且旁边有高频开关节点导致信号被干扰拉低。加粗走线、缩短距离、在TRST对地加一个小容量电容滤掉高频噪声之后连接就稳定了。2.2 XDS110固件版本和CCS版本之间的兼容性还有一个坑是仿真器的固件版本和CCS版本不匹配。TI的XDS110是支持固件升级的但固件和CCS之间有时会有兼容性问题。我用的是CCS 12.x第一次连接时CCS提示仿真器固件版本过旧建议升级。我点了升级结果升级到一半USB断开了仿真器直接变砖Windows识别为未知设备。变砖之后的修复办法是把仿真器上的一个特定引脚短接到地重新上电进入bootloader模式然后再用TI的固件刷写工具恢复。这个操作要说清楚太啰嗦反正最后我换了根质量好的USB线重新刷固件才救回来。从那以后我学乖了连接前先查CCS版本对应的XDS110固件版本号如果差的太多优先用TI官网的离线固件升级包而不是直接在CCS里点在线升级。另外如果你同时插了多个仿真器CCS里选择目标配置时一定要确认选对了序列号。我吃过一次亏两个XDS110插在同一台电脑上没注意序列号结果一直连的是另一块板子上的仿真器配置对了也连不上折腾了半天才发现是选错了设备。2.3 初次上电建议先跑官方例程验证环境在开始写自己的应用代码之前强烈建议先导入一个TI官方提供的例程工程编译、下载、运行一遍确认整个工具链是通的。我用的例程是C2000Ware里的gpio_toggle芯片型号选择F28P55x。这一步看起来很简单但能一次性验证CCS工程配置、编译器版本、链接脚本、仿真器连接、芯片ID识别、Flash烧写这些基础环节后面出了问题才好定位是环境问题还是代码问题。这里有个判断技巧连接成功后在CCS的Debug窗口里检查CPU内核寄存器确认ID寄存器的值跟你手里的芯片型号一致。如果识别出的器件ID不对大概率是CCS里选择的器件型号错了或者是连接到了一个假的/损坏的芯片。对于F28P550正常识别的ID应该和CCS中New Target Configuration里选中的型号match上。3. 程序“跑飞”与异常复位的完整排查链路3.1 现象运行几十秒后系统自动复位硬件环境确认没问题GPIO例程也跑通了我开始往工程里加自己的外设初始化代码ADC、ePWM、SCI、GPIO中断。写完编译下载在RAM里跑调试程序能进main外设寄存器也能正常配置但跑上几十秒后程序就自动复位了——现象是我在while(1)主循环里翻转一个LED它会突然停一下然后从头开始运行。用CCS的Breakpoint在main入口处打断确认复位确实发生了。但问题是是什么触发了复位C2000的复位源有好几种上电复位、看门狗复位、软件复位通过调试器、缺省复位。在调试状态下如果开着Watchdog窗口可以查到看门狗计数器的值以及复位时是否被置位。我首先想到的就是看门狗——C2000的看门狗默认是开启的如果代码里没有周期喂狗它就会不停复位系统。我的代码里其实在main主循环加了喂狗操作用的是KickDog但问题是我在中断服务函数里处理业务逻辑耗时太长导致主循环喂狗的周期超过了看门狗溢出时间。这是新手特别容易踩的坑CPU在跑中断的时候主循环是停住的如果中断里耗时超过看门狗周期系统照样复位。3.2 一步步缩小范围从看门狗到中断风暴怎么确认是看门狗复位两个方法。第一CCS的Registers窗口里看WDCR寄存器的WDFLAG位如果它在复位后被置1说明复位源确实是看门狗。第二在看门狗中断服务函数如果有配置的话里打断点看是否能抓到这个中断触发。第一次用WDFLAG确认了是看门狗复位。但问题来了我的主循环喂狗频率明明很快为什么还会超时我试着把喂狗操作从主循环挪到定时器中断里发现情况没有好转。这时候我开始怀疑——是不是某个外设中断触发的频率太高导致CPU大部分时间都在跑中断主循环根本没机会执行我在中断服务函数入口和出口各加了一个GPIO翻转用示波器测翻转频率。结果吓我一跳ADC中断的触发频率远高于我预期的采样频率某个标志位的判断逻辑写反了导致中断在中断里反复触发。这种“中断风暴”在调试器里特别难发现因为单步执行时速度太慢很难复现但只要全速跑几秒钟就能把看门狗压力打满。解决方法是把ADC中断里那个错误的标志位判断逻辑修正同时在ADC中断入口加上一个软件保护如果上一次中断的服务还没执行完这一次直接返回。具体操作是在ISR里用PIEACK寄存器的控制逻辑确保中断被正确应答。另外我还在主循环的喂狗之前加了一个死循环监控如果主循环超过一定时间没被执行就说明中断占用异常直接触发一个软件陷阱把问题暴露出来而不是悄悄复位。3.3 栈溢出和非法操作码这类“隐藏杀手”看门狗问题解决之后程序稳定跑了一个多小时我以为万事大吉了结果换了几个测试参数之后又出现了另外一种异常复位程序跑飞之后CCS的Call Stack窗口显示PC指针跑到一个完全没意义的地址没有函数调用关系而且SP栈指针的值明显异常。这种情况十有八九是栈溢出或者内存越界。C2000的内部RAM分为好几段链接脚本cmd文件里定义了栈的大小。如果程序里递归调用太深或者局部变量开得太大栈就会溢出覆盖相邻的RAM区域程序跑到未知代码段去执行这就是典型的跑飞。排查栈溢出的办法很简单把栈区域在初始化时填充成固定模式比如0xA5跑一段时间后停下来看栈区域的填充值被覆盖了多少。如果栈顶指针超出设置的栈空间或者栈区被数据破坏就能定位到问题。我之前写的代码里有一个全局结构体数组某个索引变量因为逻辑错误越界了写到了栈区里直接干翻了返回地址导致函数返回时跳到非法地址。另外C2000还有一个专门的机制叫非法操作码检测。C28x内核在执行非法操作码时会触发一个不可屏蔽中断NMI默认的处理函数是死循环。如果在调试时发现程序卡在某个地址动不了查看反汇编窗口看看是不是跳到了一个全是0xFFFF的区域。把NMI中断服务函数加上一个调试用的断点可以在触发时立刻抓到现场比慢慢查寄存器快得多。3.4 一个可以复用的异常捕获思路经过这轮折腾我在自己的工程里做了一个简单的“异常捕获”机制强烈推荐给每个用C2000做产品的人。思路是利用PIE里的不同异常源非法操作码、除法错误、调试中断等在对应的ISR里做一个处理——先把当前的PC、SP、相关寄存器的值保存到一片专门的RAM区域然后置一个错误标志位最后进入死循环。这样程序跑飞后不会默默复位而是会停在现场通过CCS的Memory Browser窗口就能查看当时的状态。这个机制比单纯依赖调试器单步跟踪高效得多特别是现场在客户那边、没有仿真器的时候可以把错误标志通过串口或者GPIO状态指示出来方便远程定位问题。4. 串口调试里那些“玄学”乱码根因往往在时钟配置4.1 波特率不对不是代码逻辑问题跑通了基础程序和中断系统我开始通过SCI串口往电脑上打印调试信息。代码是从例程改的波特率设的1152008位数据位、1位停止位、无校验。连接USB转串口模块用的CH340打开串口调试助手结果屏幕上全是乱码——不是偶尔乱是每一个字符都错位。第一反应是串口助手的参数没设对。检查了串口助手里的波特率、数据位、停止位、校验位都跟代码一致。又怀疑是不是CH340的驱动问题换了个串口调试助手软件还是乱码。这时候我把注意力转回到芯片的时钟配置上——猛然想起来C2000的波特率计算公式里SCI模块的工作时钟是LSPCLK而LSPCLK是从系统时钟分频来的如果时钟树配置错了波特率就算硬件上没问题也一定对不上。4.2 TMS32F28P550的时钟树和PLL配置细节同样一个115200你在STM32上配置寄存器和在C2000上配置寄存器背后的时钟来源完全不同。F28P550上电后默认使用的是内部INTOSC1振荡器大约10MHz经过PLL倍频再分频得到系统时钟SYSCLK。SCI外设所在的低速外设域时钟是SYSCLK再经过LSPCLK分频得到的。任何一个环节的分频系数跟数据手册不一致实际波特率就会偏离设置值积累出来的误差超过一定程度串口就会乱码。具体到PLL配置关键点在于PLL的倍频系数、分频系数、以及每一个外设时钟源的使能开关都分布在系统控制寄存器SysCtrl Registers里。我这次的问题就是初始化的顺序不对——我先配置了SCI的波特率寄存器然后又修改了系统时钟的分频系数等于波特率寄存器里写的是基于老时钟算出来的值但实际运行的却已经变成了新时钟。正确做法是先配置好整个时钟树等PLL锁定可以查PLLLOCK标志位之后再去配置外设的波特率。时钟配置的大致流程是这样// 1. 选择内部振荡器作为PLL的输入时钟源 CLKSRCCTL1.INTOSC1_OE 1; CLKSRCCTL1.PLLSRC 0; // 选择 INTOSC1 // 2. 先关掉PLL再配置倍频和分频 PLLCTL1.PLLEN 0; PLLCTL1.PLLCLKSRC 0x1; PLLCTL1.PLLIMULT 0x0; // 3. 根据需要的SYSCLK频率设置PLL倍频和分频值 PLLCTL1.PLLMULT 33; // 举例10MHz外部/内部时钟 × 倍频 PLLCTL2.PLLDIV 1; // 分频设置 // 4. 重新使能PLL等待锁定 PLLCTL1.PLLEN 1; while (!(PLLSTS PLLSTS_PLLLOCKS_BIT)); // 等待 PLL 锁定这里面的寄存器名我按C2000系列的通用习惯写的具体到F28P550的手册会略有差异但思想一样。调试的时候可以用CCS的寄存器窗口直接查看PLLMULT、PLLDIV等值是否与你预期一致比在代码里打印日志更快。4.3 验证串口波特率的两个土办法在复杂的时钟配置面前串口调试助手显示乱码会让人怀疑是不是硬件坏了。这里分享两个“土办法”帮你快速定位问题。办法一用示波器直接测SCI TX引脚的波形。如果你在代码里让串口循环发送一个固定的字节比如0x55示波器上应该能看到一个方波序列。测量一位的持续时间如果配置的波特率是115200一位的时间大约是8.68微秒。如果示波器量出来一位时间是17.36微秒那实际波特率就是57600——波特率配置差了一倍根因通常就在时钟分频上。办法二在代码里加一个延时翻转GPIO的程序算一下实际的延时和理论延时差多少间接推算系统时钟频率是否等于预期值。比如你配置的是120MHz的SYSCLK但实际只有60MHz那么所有基于时钟周期计算的延时都会慢一倍串口波特率自然也慢一倍。先用这个办法确认系统时钟再查外设时钟排查范围能小很多。4.4 串口调试助手的配置注意点最后一个串口相关的坑不是芯片的问题是我自己操作的问题。F28P550的SCI模块支持FIFO如果代码里使能了发送FIFO和接收FIFO而串口助手那边设置的是“逐字节发送”两边节奏对不上偶尔会丢字符。还有有些串口助手默认勾选了“发送新行”也就是说在数据末尾自动追加一个\n如果下位机代码是按字符解析的多出来的换行符也会导致解析错乱。建议做法是下位机调试阶段串口助手统一设置为“不追加新行”数据位8、停止位1、无校验、无流控波特率跟代码保持一致。打印的内容在代码里自己加清楚的分隔符不要依赖串口助手帮忙加。5. 烧进Flash之后行为不一致RAM调试和Flash运行的“两副面孔”5.1 同一个代码RAM里正常Flash里出问题前面所有调试都是在RAM里跑的——编译工程生成.out文件通过CCS直接加载到RAM里运行。这种模式最适合调试因为下载快、改动即时生效。但产品最终要烧到芯片内部的Flash里上电后从Flash启动。于是我把编译模式切换到Flash运行模式烧录完成后复位结果程序要么不跑要么跑起来之后某些功能变得异常迟钝。这个现象在C2000系列里太典型了几乎每一颗芯片都会遇到。原因有两个层面。第一Flash的访问速度比RAM慢如果在Flash里直接执行代码CPU取指的速度跟不上特别是对时序要求严格的外设配置代码可能因为执行变慢导致瞬间错过某些窗口。第二更关键的是C2000的Flash有一个“等待周期”Flash Wait State的概念它由FLASH_CTRL寄存器控制。默认情况下如果你没有配置足够的等待状态系统时钟很高的时候直接跑Flash代码读出来就是乱数据程序自然跑飞。5.2 把时间敏感的代码搬进RAM执行解决Flash运行问题核心思路是把时间敏感的代码段搬到RAM里执行。C2000工程里常见的做法是把一个函数或者一段初始化代码放到ramfuncs段然后在启动代码里调用memcpy把这一段的代码从Flash拷贝到RAM再跳转到RAM里的地址执行。具体的操作是在CCS的链接脚本cmd文件里声明一个段然后在代码里用#pragma CODE_SECTION把指定函数放到这个段里#pragma CODE_SECTION(initEPWM, ramfuncs); void initEPWM(void) { // ePWM配置代码 // ... }cmd文件里对应加上ramfuncs : LOAD FLASH, RUN RAM, LOAD_START(_ramfuncs_loadstart), RUN_START(_ramfuncs_runstart), SIZE(_ramfuncs_size)然后启动代码里在主函数调用这个被挪到RAM里的函数之前先执行一次拷贝memcpy(_ramfuncs_runstart, _ramfuncs_loadstart, (size_t)_ramfuncs_size);这里有个很容易忽略的细节memcpy这个函数本身如果是标准库提供的它在执行的时候也会调用一些内部代码。如果在拷贝还没完成的时候你又调用了一个也在ramfuncs段里的函数就会出现拷贝覆盖问题。所以建议把整段拷贝操作放在main的最前面其他ramfuncs段的函数都等拷贝完成后再调用。5.3 Flash等待周期配置别省这一步除了搬代码Flash等待周期的配置也是必做的。F28P550在不同系统频率下需要的等待周期数不一样数据手册里有一张表格直接照着填就行。为了保险起见我先把FLASH_CTRL的ACCPROT0寄存器打开然后按手册设置的等待周期值写入再用一个Flash性能测试函数验证读写速度。等待周期配少了会读取错乱配多了会浪费性能这个平衡点一定要按手册来。5.4 启动模式引脚的坑还有一次烧录完成后重新上电程序完全不跑。排查到最后发现是启动模式引脚Boot Mode Pins的电平状态不对。C2000的Boot ROM在上电时会根据几个GPIO引脚的电平决定启动来源——是从Flash启动还是从SCI启动还是从USB启动。我这个板子上启动模式引脚被外部的上拉电阻拉高了结果Boot ROM就跳到了SCI启动模式完全没执行Flash里的程序。解决办法是查阅F28P550的Boot Mode配置表在C2000Ware里可以找到对应文档确认从Flash启动时这几个引脚应该处于什么电平然后检查板上硬件设计有没有问题。调试阶段临时改起来也方便用手头导线把对应引脚强制拉低或拉高上电测试启动逻辑。5.5 供电不足导致的Flash校验失败最后一个Flash相关的问题有点隐蔽。量产阶段有一批芯片烧录时报错提示Flash校验失败。刚开始以为是芯片质量问题换了芯片还是一样的现象。用示波器抓烧录瞬间的电源波形发现烧录Flash时电流尖峰很大而我们的3.3V电源经过一段很长的走线供给DSP导线压降导致烧录期间电压跌落Flash校验自然过不了。解决了板子电源走线问题后烧录就正常了。这个经验在处理任何Flash烧录问题时都值得记住先量电压特别是烧录瞬间的电压再看时序最后才怀疑芯片本身。6. 这次调试留下的几条实打实经验借着这次TMS32F28P550的调试过程我把自己的一些工作习惯总结一下。不一定每条都适合所有人但都是踩过坑之后实实在在总结出来的。第一调试日志要分级管理。开发前期把串口打印当成一等公民所有关键模块的初始化都要有日志输出中期把日志切换到调试等级只保留关注的信息后期发布前统一关闭详细日志。我这次在例程基础上改代码时就是因为日志系统没搭好前期排查问题很多时间浪费在了“这个函数到底有没有执行”这种问题上。第二寄存器配置改动前一定要保存快照。CCS的寄存器窗口可以导出当前所有寄存器值的快照文件每次改配置前导出一份改出问题后直接对比能快速定位是哪几个寄存器被改动了。这个习惯在我调PLL和ePWM时帮了大忙。第三调硬件问题时要敢于做减法。程序跑飞、串口乱码、Flash异常这些问题看起来像软件问题但根因可能是硬件设计缺陷。我处理完Flash烧录供电问题后回头再看之前的一些“灵异现象”很多其实都能用电源纹波、地弹、信号干扰来解释。第四建立一个最小复现工程。碰到奇怪的问题不要在你的完整工程里反复尝试重建一个小工程只包含出问题的功能模块用最小代码量去复现问题。这次调试过程中PLL配置导致串口乱码那个问题就是在最小工程里用示波器量波形才彻底搞明白的。完整工程的变量太多不抽离出来很难看到真相。TMS32F28P550这颗芯片本身的性能表现是可圈可点的CLB模块和片上集成的模拟外设比如比较器、DAC让很多外部电路都省了。但它的调试门槛确实比消费级MCU高一点对硬件设计的要求也更高。希望这篇文章能给你提供一些参考少走一些我走过的弯路。
返回列表