一、调试准备
1. STM32的调试模式
对于STM32的CPU,支持调试模式,即CPU可以被暂停,用户可以在CPU暂停后查看寄存器或变量的状态。
2. 在线调试工具
由于版权问题,JLink调试工具渐渐淡出,现在大部分使用ST-Link,它与JTAG调试方式兼容,虽然JTAG要比ST-Link快,但是JTAG要用到多个引脚,ST-link只需要3根线。
3. 调试准备
首先插上ST-Link后,需要正确安装驱动,然后在设备管理器可以看到ST-Link。驱动安装好后,需要设置IDE工具,在MDK的魔术棒即option for project中的Debug选项中,要选择ST-Link,然后点击setting,这个时候如果能看到ST-Link版本等信息,说明ST-Link没问题,这个时候,还需要选择ort为SW表示用st-link,频率选择4MHz。
要在Utilities 选项卡里面设置下载时的目标编程器,直接勾选Use Debug Driver,选择ST_LINK V2.1 来给目标器件的FLASH
编程,
然后点击Settings,进入FLASH 算法设置,
这里MDK5 会根据新建工程时选择的目标器件,自动设置flash 算法。一般Programming Algorithm 里面默认会有相应处理器型号的 算法。另外,如果这里没有flash 算法,可以点击Add 按钮,自行添加即可。最后,选中Reset and Run 选项,以实现在编程后自动运行,如果在MDK软件包安装完后,没有正确安装了对应处理器的pack包,这里的Programming Algorithm中可能会找不到对应处理器型号。
上述设置完后,确定后,选择run to main,这样调试模式开始后,就会跳到main函数,相应设置如下:
二、基本调试选项
1. 开始调试
一般IDE开发工具上都有start/stop调试按钮,点击后进入调试模式。
2. 基本调试选项
复位:其功能等同于硬件上按复位按钮。相当于实现了一次硬复位。按下该按钮之后,代
码会重新从头开始执行。
run
。 它可以执行到断点处或者重新从上一次调试中开始。 进入调试模式后,可以进行断点调试,断点的作用是可以指定程序运行区间,方便查找问题,对于单任务来说,非常有用,但对于像Linux系统那样单线程调试就不是问题,关键是多线程比较麻烦,因此STM在上系统后的多任务和中断服务等调试才是重点。
step in
,如果选择step in,那么调试会进入到选中的函数中去(比如当前光标选中一个函数,如果选择step in,那么会进入到这个函数中调试,如果此函数中还有其他函数,并且任然使用step in,那么会继续进入另外函数)。
step over
,如果当前光标在一个函数中的某一行,这个时候选择step over,那么会一行一行地运行,哪怕某一行是函数,也是一行一行地运行。
step out
, 当执行进一个函数后,函数里面是一个很大的for循环,如果这个时候还是用step over一行一行地执行,将执行很久,这个时候就可以选择step out,意思是执行完这个函数,并退出到函数外执行其他代码。
执行到光标处
, 执行到光标处,意思是当把光标移动到某个地方后,调试时选择执行到光标处,那么程序就会执行到光标那个地方。注意光标处一定是将来要执行的程序,如果把光标移动到已经执行过的程序那里,再点击执行到光标处会不成功,会直接执行后面的程序。
stop挂起
, 不同IDE图标不一样,当一个程序中有个无线循环,并且没有打断点,点击执行到断点后,会进入循环一直执行,这个时候如果我们选择了挂起,那么程序就会在这个循环中暂停。
与断点相比,在实际问题出现后,我们看到设备故障现象发生后,马上点击挂起,我们就知道程序大致运行到哪里了。因此,这种调试是当不知道问题代码在哪时使用。
3. Call Stack 窗口
进入调试模式后,一般会显示出堆栈窗口(如果没有出来可以在view视图中打开),在这个窗口里面可以看到函数的调用关系和入口地址,以及局部变量的值,下图就是一个例子:
图中可见,首先是main函数调用了LED_Init函数,然后在LED_Init函数中调用了HAL_GPIO_WritePin函数,另外在ED_Init函数中,可以看到类似Pin等局部变量的值(Location/Value列),当我们调试时,选择一行一行地运行,就会观察到这些值在变化。
4. Watch窗口
这个窗口也可以在View-》watch 打开,可以选择watch1或watch2。用于查看特定函数入口地址和变量值。当我们要查看特定变量值的时候,可以直接把变量加入到watch窗口中查看值。
方法是,选中变量或函数,右键选择add 变量或函数to watch1或watch2.
5. 代码优化
编译器会根据设置对程序进行优化,优化后的程序,调试时可能看不到实际值,比如下会显示not in scope:
原因在于编译器已经对该变量进行过优化,有可能会直接从缓存中取的,因此调试时最好不要打开优化选项,这样编译器会重新编译所有文件函数,如果优化打开,有可能就会只编译使用到的函数,并且某些变量有可能不是最新的值。设置编译优化方法如下(设置为leve0即可):
6. 查看外设寄存器的值Peripherals
在调试的时候,有可能需要查看控制的外设寄出去是否为预期控制值,可以通过Peripherals查看。
7. 基于多任务的调试
目前为止对多任务调试的理解,原先以为单CPU多任务会受到操作系统比如UCOSII的调度影响,无法进行单步调试。然而通过实验发现,在某一个UcosII的任务中打个断点,然后再全速运行,然后会调试到断点处,此时可进行此任务的单步调试,其他任务类似。
如果要查看其他任务修改的全局变量,也可以通过watch窗口查看,在调试本任务时,其他任务也在运行。
三、高级调试方法
1. 查看代码段运行时间,比如如下代码:
调试时,点击一行一行的运行,然后查看MDK最右下角
处就可以知道时间。用当前值减去前面一行运行时的时间就是这段代码运行时间。
为了计算准确,调试前得先设置调试时钟频率,也就是STM32运行时的频率,对于STM32F411系列来说,填96MHz即可。并且勾选上Trace Enabl。这个设置框在MDK魔术棒-》Debug-》Setting-》Trace处设置。
2. 查看内存地址数据的方法
比如查看一个数组所有元素的数据,我们可以用watch查看,但是如果数组元素非常多,就需要用到Memory窗口查看。我们可以先用Watch查看到数组的首地址,然后把数组首地址输入到Memory窗口就可以看到后面连续内存的值,即所有数组元素的值为05 04 03.。。 如下图:
Memory也在View视图选项中打开。
3. 查看中断执行时间和次数
有时候需要调试中断的时候,可以打开这个窗口,这个窗口在下图中可以打开。
打开后就会在MDK的右下角弹出Trace Exceptions窗口,当发生外部中断后,比如按键按下,就会执行多次外部中断,详见下图中的EXTI2和EXTI3,其中Count就是执行次数,也可以查看到中断服务函数大致执行时间。由此可见,按键按下会产生多次按键中断,但是我们只需要一次,所以需要防抖。
4. 全局变量在读写的时候停止运行
这个功能只支持全局变量,不支持局部变量。有时候我们需要知道全局变量到底在哪里被改变了,就可以使用这个方法定位全局变量在哪被修改或读取。
首先在把全局变量拖到前面基本调试方法中的Watch窗口中,然后在Watch窗口中选中这个变量后选择Set Access Breakpoint at ‘全局变量’。
然后会弹出一个窗口,这个窗口就是对全加变量进行操作。类似如下图,先选择Write,然后点击Define后,Close。
然后再点击运行到断点,这个时候断点就会停留在全局变量读或写操作的下一行代码。
四、看门狗与任务
多任务系统中,为了防止任务跑飞,采用喂狗方式。首先调用HAL库函数初始化自带watchDog,然后在每个任务中喂狗,这样在调测阶段,如果有任务跑飞,系统就会重启,然后解决问题,再正式发布。
当多个任务有某个任务跑飞后,为了调试,先得关闭WatchDog,然后再IAR中调试,全速运行,由于看门狗被关闭,程序跑飞后不会导致CPU复位,程序会卡在跑飞位置,这样进入调试模式后,全速运行,然后偶尔停止运行,调试界面会跳转到源码得某个地方,如果多次全速运行停止后都跳转到那个地方,说明那就是程序卡死得地方。
总结一下,这里与Linux线程调试相比,还是比较方便,至少程序跑飞后,在调试模式下IDE会提示到卡死位置。linux多线程调试只有借助线程管理或应用逻辑本身服务管理机制找到线程卡死或段错误得地方,没用GDB。
五、IAR环境下调试
1. 定义全局宏的地方:Options -》 C/C++ Compiler-》Preprocessor-》Defined symbols
这样定义宏后,代码所有文件中都可以访问此宏,可以通过此宏决定不同CPU平台、不同IDE工具编译等条件编译。
2. IAR中printf打印输出,初始化处理器的uart后,重写fputc函数:
(1) 首先在1中增加_DLIB_FILE_DESCRIPTOR宏
(2) 在bsp_uart.c文件中增加如下函数:
int fputc(int ch, FILE *f)
{
while((UART3-》SR&0X40)==0);//循环发送,直到发送完毕
UART3-》DR = (u8) ch;
return ch;
}
根据不同的uart,修改上面代码中的UART3即可。用printf之前需初始化uart。
3. 对于多任务系统,程序心跳喂狗等管理:单独一个喂狗任务,此任务可以统计其他任务运行状态,比如CPU使用率,各个任务中堆栈剩余最小值是多少等。
另外,喂狗任务中,会统计在所有任务中,是否有任务心跳没有增加,如果有,并且持续2分钟就在喂狗任务中停止喂狗(这里的喂狗是调用hal库函数喂硬件狗,不喂系统就会重启),从而导致系统重启。
4. IAR调试多任务时,一定要关闭硬件狗,否则断点后会系统重启。
5. Breakpoints在view设置中可以打开,打开后可以查看所有打的断点。
6. 如果要查看变量值,只需要进入调试模式,然后把相应变量加入watch窗口,然后在要查看变量值地方的下一行代码打个断点,并全速运行,程序就会停在断点处(注意是暂停,这也是STM32cpu的魅力),此时watch窗口就会显示变量值。如果是循环,继续全速运行即可,也可单步。
7. 有时候值不能正常显示或调试不能在断点处停止,就复位,重新开始调试。
8. 每次更改代码后要下载到STM32中再开始调试,否则新增代码无法调试。
9. 如何查看程序是否跑飞,可以全速运行,然后停止,看每次停止的地方是否都是卡在一个地方等。