Debug问题排查思路
以下内容适用于嵌入式开发工程师。
裸机Debug
裸机调试过程中出现问题时,可以从以下几个角度进行考虑,分别是 电气特性、软件设置 、 代码设置 和 项目设置。
电气特性
排查电气特性的方法,主要是使用 万用表、逻辑分析仪、示波器 等可以捕捉电信号的设备进行直接的观察。
例如使用万用表、示波器来检查电信号的电压、电流是否在正常工作范围。
软件设置
虽然软件设置和代码设置的命名方式相近,但是这里的软件设置更多强调的是系统级、寄存器级、驱动级的基础设置,而非算法级、代码级、逻辑级的更高一级设置。
软件设置中,直观可见的是所有可能需要检查的问题点,都是可以通过查阅技术参考手册中的相关寄存器设置来检查是否设置正确的,包括中断、内存、时钟、外设等各项涉及到寄存器级别的内容。
代码设置
代码设置,强调的是算法级、代码级 或 逻辑级 的软件编写或设置。
需要检查的更多是对应 芯片 及其 板载环境 、工作系统 之间的关系,例如代码里面有没有导致死循环、或者多线程之间的资源锁死、内存溢出等情况。
项目设置
调试文件
调试协议
寻址文件
但看调试文件,同样是f28004x系列芯片,通用的寻址文件就多达十几种。
一般最高型号的芯片还有额外的 .cmd
文件,如:
此时就需要仔细挑选对应的寻址文件,避免多个寻址文件载入导致寻址空间重名冲突。
案例
ADC中断未进入BUG
情况1——组中断未打开
ADC模块有检测到信号(下图ADCRESULT0
)
,转换电容将电压转换成数值,但是没有触发中断,进而中断函数没有被执行。
仔细检查对应的ADC中断标志寄存器,整理思路:SOC可以正常收到EPWM6的触发源进而正常地对电压进行转换,然后将数值存到对应寄存器中。也就是说,EOC也有正常工作,能够生成脉冲中断。
对照着《TRM》中的说明文档再阅读一遍:
接着再对应检查一下与中断相关的溢出标志寄存器,发现的确有中断溢出了。
在ISR中断服务程序中写入一个全局调试变量,用于观察是否进入中断服务程序,可以知道其并未进入中断服务程序。
最后对比检查样例代码中的main函数对ePIE寄存器的控制,发现少了一行对PIE控制寄存器的操作,补上~
成功进入中断:
总结
- 此处很重要的是要仔细研读《TRM》文件,将文档内的中断流程进行一一对照校验检查
- 理解玩系统框图后,按照信号进入的顺序对涉及到的寄存器依次进行检查,逐个排查之后,再对其他可能会出现问题的地方进行DEBUG代码编写。
- 如果没有参照手册,就得参照样例文件逐行检查代码
开启ADC C模块之后IDLE函数就不再执行
情况1——中断函数不要处理大量数据计算
IDLE函数是写在 main()
函数中,最后面的位置的,以
while(1)
语句进行包裹着的永远循环的语句。
在 main()
函数中,一般会对整个系统、外设、内存、中断等进行初始化和配置,一旦所有工作部件跑起来之后,需要CPU进行数据处理时,就会暂停进入IDLE函数,转而去处理更为紧急的事情,例如进行浮点数计算(如果没有开启对应的FPU的话),此时IDLE函数就会过很长一段时间(时间不定,依据实时处理情况而定)才执行一次。
例如下方所示,一旦开启了对应的中断处理函数中的电压、温度、阻值计算,需要处理器实时且大量的计算的话,就会造成CPU短时/长时荷载较高,无法进入IDLE函数,原本在IDLE函数中设置为1秒闪烁一次的LED灯,过了十几二十秒才关闭/打开。
一旦将其注释,LED灯的闪烁速度又得到了恢复。
也可以在中断向量表和处理程序处进行注释以关闭中断处理,也可以看到LED等闪烁正常。
ADC读值/转换失败
情况1——ADC触发源更改
新增了功能:由主循环检测GPIO3上的引脚是否导通,导通则开启ePWM的时钟,否则不开启,开关检测代码如下:
1 | void sw1WrapFunc(void){ |
在增加了新功能之后,发现ADC突然不工作了,每次ePWM一关闭,ADC也跟着不工作,一打开ePWM,ADC又开始工作了。
重新整理思路,想起来ADC工作的配置中,唯一一项跟PWM相关的就是其触发源。
修改其触发源为CPU1 Timer0,正常工作:
情况2——主函数被测试用的死循环卡住
主循环中一定不能再套一个等待状态(或死循环)的语句,否则整个主循环将有可能永远被卡死。
写完SCI部分函数后,发现ADC温度转换的不工作了,但ADC的中断程序(中断程序对全局变量的写入成功)和中断溢出标志是可以正常处理的,同时写了一个Debug变量放在主循环下的ADC包装函数中,发现数据并未被处理,因此可以判断主程序被卡死。
解决方法是,将SCI通信发送和接收的功能写到中断程序中,让中断程序来处理。
ECAP外设没有正常工作
首先是注意到该外设对应的GPIO没有正常工作,查看
GpioDataRegs
下的 GPADAT
寄存器,发现GPIO10在其他外设都能正常工作的情况下,该引脚无法动弹(表现为:使用3.3V电源连接GPIO10,没有反映)。
然后查看eCAP引脚源代码,初步推断可能是引脚方向设置出错,更改以下源代码中的最后一句以更改引脚信号导通方向。
源代码如下:
1 | void Init_eCAP_GPIO(void){ |
再次测试发现结果正常。
修改后代码如下:
1 | void Init_eCAP_GPIO(void){ |
注意:因为GPIO9和GPIO10临近,默认情况下GPIO10还是会受到附近(结构上)其他GPIO电平的影响而拉高,此时即使设置了
GPIO_PULLUP_DISABLE
都没办法置低。同时,GPIO9也会因为GPIO10的电平拉低而拉低。
ECAP引脚配置
从GPIO部分的图8-1、 引脚部分的 表4-6 、X-BAR部分的 图9-1 和 eCAP部分的表19-1 四份内容可以看出,GPIO10 没办法被通过INPUT X-BAR 复用到ECAP上。
因为表4-6决定了,GPIO10不能复用到INPUT X-BAR 或
OUT-BAR,但可以复用为自己(GPIO10),只需要设置
GPAGMUX.bit.GPIO10 = 0x0; GPAMUX.bit.GPIO10 = 0x0;
即可。
SCI引脚导致复位
SCI配置完毕后,与对应的GPIO引脚无法通信(即无高低电平变化),将逻辑分析仪插入对应的引脚,会导致芯片复位重启。
原GPIO引脚配置为:一个GPIO为拉高和异步,另一个为拉高和同步。
将两个GPIO修改为异步配置之后,并未出现复位情况。
SCI RX数据错误
使用RS485进行发送时,不仅接收会存在错误,发送也会存在错误。
在数据量较低且手动点击鼠标进行发送时,仍然会有数据处理错误:
同样的代码(中途未复位重启芯片)对比使用CH340芯片进行发送和接收:
SCI未触发中断
系统Debug
Windows/Linux/Mac
分离式代码的联合编译
上图为使用VS Code进行项目编译时报错的情况,在使用 MinGW 的g++
作为编译器时,一开始使用 main.cpp
作为被编译的文件进行编译运行,成功可用。
后期新增了 SerialPort.h
和 SerialPort.cpp
,再次点击编译发现失败。说是查找不到 SerialPort
类下的函数定义相关的报错,并不是因为没有给
SerialPort::SerialPort()
等这些类里的函数进行定义,只是因为定义和声明并不在同一个 .h
文件里面。因为将类定义写在了 .h
文件中,而定义写在了另一个
.cpp
文件中,因此该编程方式称为
分离式代码。如果将其书写到同一个 .hpp
文件里,就称为 非分离式代码。
采用的 分离式 的代码书写方法,造成了
g++
编译器在只编译 main.cpp
的情况下会报错
函数未定义 。
解决办法
对分离式的 C++ 代码,需要联合多个 .cpp
文件进行编译,在Linux系统上使用命令行进行编译即可提现 如下:
1 | g++ main.cpp SerialPort.cpp -o main --std=c++17 |
上面就把 main.cpp
和 SerialPort.cpp
两个代码文件联合在一起进行编译,只生成一个 main
二进制可运行文件。
类似,只要在Windows上能用同一句编译命令实现多个 .cpp
文件的编译即可,但自己没有找到如何在 VS
Code中进行该操作方法。另辟蹊径找到了用Terminal进行搞定,方法如下:
- 将
D:\mingw64\bin
加入到 系统环境变量 的Path
变量下:
- 打开 Terminal 进行跳转到工作目录,或者在工作目录按
shift
同时点击右键菜单里的在Windows终端中打开
即可。
- 尝试进行编译,发现就不报这个
undefined reference
的错了。
总结
分离式代码就需要联合多个 .cpp
文件进行编译,不能只编译一个 main.cpp
,Windows Terminal
也可以直接操作 g++
(只要加了系统变量)进行多文件编译。
CANNOT CONVERT
以下为 VS Code中的报错:
以下为 Terminal g++ 中的报错:
cannot convert const wchar_t*
to LPCSTR
,即是说,无法将const wchar_t*
类型转换为
LPCSTR
类型。
LPCSTR,即 long pointer const string , 是Win32和VC++所使用的一种字符串数据类型。定义如下:
1 | typedef _Null_terminated_ CONST CHAR *LPCSTR, *PCSTR; |
LPCSTR
代表了 const char *
类型,它是一个指向以 \0
结尾的 8
bit(单字节)ANSI字符数组 的常量指针。
而 const wchar_t *
类型是一个指向 \0
结尾的
16 bit(双字节)Unicode字符数组 的常量指针。
在VS2013编译器中直接输入的字符串常量(如 “abc”
)默认是以 const char *
的格式(即ANSI编码)储存的,因此会导致类型不匹配的编译错误。
解决办法
以下办法查询自网络,未实践:
- Visual Studio:
右击“解决方案资源管理器”中的项目,“属性→配置属性→常规→项目默认值→字符集”,默认的选项是“使用多字节字符集”,将它改为“使用Unicode字符集”即可。这样,输入的字符串会默认以
const wchar_t *
格式储存。 - 使用
_T
宏,它在 <tchar.h> 中定义,它能够自动识别当前编译器字符串的储存格式并做出相应转换,避免这种类型的编译错误。具体使用方法为:将“abc”
改为_T("abc")
。
以下办法亲自实验可用:
- 删除该实参
L"xxxxxx"
前的L
字符即可,在使用 MinGW 编译器的情况下,如果有该L
字符,就会一直报错,就算将文本字符集改成UTF-16 LE/BE
,虽然不会报该L
字符的错了,但仍会出现其他问题,也是因为字符集搞的鬼。在Linux或Mac下就不会报该错误。