写完程序并下载到芯片之后,调试器虽然能连上,但代码并没有按预想的停在main函数的入口,而且单步跟进时还可能突然跳到一些莫名其妙的地址,这种情况在嵌入式开发里头通常就被叫做程序跑飞。排查的时候,可不能光盯着自己写的那些业务代码去看,还要把启动文件、链接脚本里规定的地址、中断向量表、看门狗的设置,还有C-SPY这一头的复位选项,都一并检查。对于Cortex-M这类内核的芯片,向量表里存放了最初的栈顶指针和各个异常处理的入口地址,一旦复位之后程序要去的那个地方搞错了,芯片在刚启动的阶段,就可能直接钻进硬件错误中断,来回不断地复位重启。
一、IAR下载程序后跑飞是什么原因
程序跑飞,在很多情况下是发生在main函数被调到之前。这个时候,跟向量表、堆栈、时钟,还有启动初始化有关的事情,都会一连串地影响过来。虽然从界面上的表现来看似乎都差不多,但背后的实际原因,却得一个一个地去排除。
1、向量表或链接地址没有设对
先翻出map文件仔细看一看,确认一下中断向量表、复位处理的入口Reset_Handler,还有最终程序的入口,是不是真的被放到了芯片上电后会去读的那个启动地址上。如果系统里用到了Bootloader,那还得再检查一下应用程序的向量表偏移量,是不是已经配到了正确的位置。只要地址偏了一点点,处理器一复位,程序指针跳到错误的地方,一切就都乱套了。
2、启动文件和芯片型号不匹配
换过芯片,或者更换过工程的模板、SDK以后,如果还在凑合着用以前留下来的老startup文件,那堆栈的大小、时钟的配置、中断的入口,还有内存的布局,就很容易对不上号。在这种时候,建议先把芯片型号、链接配置文件、启动文件和下载算法,这几样摆在一起核对一遍,都对上号了再去看业务逻辑的问题。
3、看门狗过早把芯片复位
有些芯片只要一通电,看门狗本身就已经开始跑了,调试器如果停在启动过程的某个阶段,时间稍微一长,程序还没来得及去喂狗,目标板就会又被复位一次。IAR配合J-Link调试的时候,选什么样的复位策略,也会影响到CPU能不能被及时地叫停下来,J-Link的资料里也说过,不同类型的器件,是需要去挑对应的复位方式的。
4、下载进去的内容和当前代码对不上
每一次重新编译之后,都要确认一下烧录器下载的,是不是正好由这次编译配置生成的映像文件。假如Debug和Release这两套配置弄混了,或者不同硬件版本、不同Flash地址的程序掺到了一起,源码窗口看上去是没有问题,但芯片里真正跑着的,却有可能是另一版程序。在有必要的时候,可以先把下载校验的功能打开,再重新往芯片里烧写一遍。
二、IAR复位方式选错后会出现哪些现象
C-SPY这个调试器的复位动作,并不仅仅是让程序重新开始那么简单。不同调试探针、不同芯片系列,它们适合的复位方式是有差别的,J-Link的文档就明确指出,针对ARM7、ARM9和Cortex-M器件,复位策略是会跟着变的,不能直接把别的项目里的设置照搬过来。
1、没法自动停在main函数
进到【Project】→【Options】→【Debugger】→【Setup】这里面,应该看一眼【Run to】这个选项有没有被勾上,而目标名称那一栏,通常是填的main。C-SPY会在那个位置先临时设一个断点,然后执行复位后的一串初始化代码,最后才会带着程序停到我们指定的地方。
2、程序反复停在Reset_Handler周围跳来跳去
如果硬件的复位动作,压根儿没有把复位引脚真正地拉低一下,或者是外设和CPU没有按照设想一起被复位,那寄存器的状态可能就会残留在上一次跑过的样子,启动的条件也会变得很怪。碰到这种情况,就得去检查一下调试探针的连接、复位脚上的信号、电源的供给,还有当前所选的那个复位策略。
3、连上之后很快就丢掉了调试会话
当目标板自己在反复复位的状态下,最常看到的一种表现,就是程序才刚下载结束就马上断开连接,重新连一次,也只能短暂地恢复一下。这个时候,优先要去查的地方,是独立看门狗、低功耗模式的配置、时钟的初始化过程,还有复位脚外围的电路,不要只是反反复复地去点下载按钮。
4、加上断点之后现象就变了
有些问题是只在全速跑起来的时候才会出现,而让程序停在断点附近,看起来却反而正常。这种情况,就要特别留意看门狗、相关的时序、外设初始化的前后顺序,还有那些没被初始化过的变量的值。调试器里的【Run to】功能本身,也会用掉一个临时的断点,对那些断点资源本来就不多的芯片,这一点是要留心一下的。
三、IAR程序跑飞时应该先检查什么
一碰上跑飞的问题,先别着急去大范围地改动代码。更好的顺序,是先把复位的链路和启动的链路确认清楚,然后再一步一步地往业务函数里面查。
1、先取消自动跑到main
可以临时把【Run to main】这个功能给关掉,重新下载程序之后,从Reset_Handler那里开始,一行一行地做单步执行。这样就能看得很清楚,程序到底是跑到了哪一步突然跳走的,是在初始化堆栈的时候,是在配时钟的时候,是在给变量赋初值的时候,还是进了main以后才出的毛病。
2、看看PC、SP和异常寄存器
程序停住了以后,首先要看PC当前这个地址,是不是还落在有效的代码区里面,再去检查SP的值是不是还在RAM的范围之内。对Cortex-M内核来说,一旦进了硬件错误中断,还要把异常状态寄存器和调用栈的情况调出来,看一看是非法的存储器访问,还是总线上的错误,又或者是跳转到了错误的地方。
3、确认选用的复位方式跟硬件能对上
用J-Link、I-jet或者其他调试探针的时候,一开始可以先用工具默认推荐的那一种策略。只有在默认策略始终无法稳定地连上目标板,或者芯片手册里明确要求要用某种特殊的复位方式时,才去调整Reset的类型。改过之后,最好每次只验证一项改动的效果,这样才好分辨变化到底是来自什么地方。
4、保留一份最小的工程
可以先只把时钟的初始化、一个串口输出,还有最外层的主循环保留下来,先来验证一下芯片本身是不是能够稳定地启动。一旦最小工程跑得很正常而原工程却不行,接下来就可以把中断、任务调度、外设,还有看门狗,一项一项地往回加,这样排查的范围就会被缩得非常小,找起来也更容易。
总结
所以,IAR下载程序后出现跑飞现象,比较常见的原因,往往是围绕着向量表、链接地址、启动文件、看门狗,还有下载映像不一致这几个方面来的。而在IAR里面把复位方式选错以后,可能会看到的状况,就包括了没法自动停在main、在Reset_Handler附近反复跳、调试会话一接上就丢,以及加上断点之后跑飞现象反而消失。排查的时候,不妨先把自动跳转的功能关掉,然后从复位入口单步跟过去,再把PC、SP、异常寄存器和复位策略拿出来一项一项地看,这样做通常能更快地找到真正的原因在哪里。