Debug问题排查思路

以下内容适用于嵌入式开发工程师。

裸机Debug

image-20210804172539069

裸机调试过程中出现问题时,可以从以下几个角度进行考虑,分别是 电气特性软件设置代码设置项目设置

电气特性

排查电气特性的方法,主要是使用 万用表逻辑分析仪示波器 等可以捕捉电信号的设备进行直接的观察。

例如使用万用表、示波器来检查电信号的电压、电流是否在正常工作范围。

软件设置

虽然软件设置和代码设置的命名方式相近,但是这里的软件设置更多强调的是系统级、寄存器级、驱动级的基础设置,而非算法级、代码级、逻辑级的更高一级设置。

软件设置中,直观可见的是所有可能需要检查的问题点,都是可以通过查阅技术参考手册中的相关寄存器设置来检查是否设置正确的,包括中断、内存、时钟、外设等各项涉及到寄存器级别的内容。

代码设置

代码设置,强调的是算法级、代码级 或 逻辑级 的软件编写或设置。

需要检查的更多是对应 芯片 及其 板载环境 、工作系统 之间的关系,例如代码里面有没有导致死循环、或者多线程之间的资源锁死、内存溢出等情况。

项目设置

调试文件

调试协议

寻址文件

但看调试文件,同样是f28004x系列芯片,通用的寻址文件就多达十几种。

image-20210804172803808

一般最高型号的芯片还有额外的 .cmd 文件,如:

image-20210804172915025

此时就需要仔细挑选对应的寻址文件,避免多个寻址文件载入导致寻址空间重名冲突。

案例

ADC中断未进入BUG

情况1——组中断未打开

ADC模块有检测到信号(下图ADCRESULT0) ,转换电容将电压转换成数值,但是没有触发中断,进而中断函数没有被执行。

image-20210810093022182

仔细检查对应的ADC中断标志寄存器,整理思路:SOC可以正常收到EPWM6的触发源进而正常地对电压进行转换,然后将数值存到对应寄存器中。也就是说,EOC也有正常工作,能够生成脉冲中断。

image-20210818100405137

对照着《TRM》中的说明文档再阅读一遍:

image-20210818100624266

接着再对应检查一下与中断相关的溢出标志寄存器,发现的确有中断溢出了。

image-20210818100710508

在ISR中断服务程序中写入一个全局调试变量,用于观察是否进入中断服务程序,可以知道其并未进入中断服务程序。

image-20210818101346665
image-20210818101314520

最后对比检查样例代码中的main函数对ePIE寄存器的控制,发现少了一行对PIE控制寄存器的操作,补上~

image-20210818101749256

成功进入中断:

image-20210818101822146

总结

  • 此处很重要的是要仔细研读《TRM》文件,将文档内的中断流程进行一一对照校验检查
  • 理解玩系统框图后,按照信号进入的顺序对涉及到的寄存器依次进行检查,逐个排查之后,再对其他可能会出现问题的地方进行DEBUG代码编写。
  • 如果没有参照手册,就得参照样例文件逐行检查代码

开启ADC C模块之后IDLE函数就不再执行

情况1——中断函数不要处理大量数据计算

IDLE函数是写在 main() 函数中,最后面的位置的,以 while(1) 语句进行包裹着的永远循环的语句。

main() 函数中,一般会对整个系统、外设、内存、中断等进行初始化和配置,一旦所有工作部件跑起来之后,需要CPU进行数据处理时,就会暂停进入IDLE函数,转而去处理更为紧急的事情,例如进行浮点数计算(如果没有开启对应的FPU的话),此时IDLE函数就会过很长一段时间(时间不定,依据实时处理情况而定)才执行一次。

例如下方所示,一旦开启了对应的中断处理函数中的电压、温度、阻值计算,需要处理器实时且大量的计算的话,就会造成CPU短时/长时荷载较高,无法进入IDLE函数,原本在IDLE函数中设置为1秒闪烁一次的LED灯,过了十几二十秒才关闭/打开。

一旦将其注释,LED灯的闪烁速度又得到了恢复。

也可以在中断向量表和处理程序处进行注释以关闭中断处理,也可以看到LED等闪烁正常。

image-20210824190557672

ADC读值/转换失败

情况1——ADC触发源更改

新增了功能:由主循环检测GPIO3上的引脚是否导通,导通则开启ePWM的时钟,否则不开启,开关检测代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void sw1WrapFunc(void){
if(GpioDataRegs.GPADAT.bit.GPIO3 == HIGH_LEVEL){ //是否为高电平
if(CpuSysRegs.PCLKCR2.bit.EPWM1 == ON) return;
else {
EALLOW;
CpuSysRegs.PCLKCR2.bit.EPWM1 = ON; //高电平且未开启则开启
EDIS;
}
}
else if(GpioDataRegs.GPADAT.bit.GPIO3 == LOW_LEVEL){ //是否为低电平
if(CpuSysRegs.PCLKCR2.bit.EPWM1 == OFF) return;
else {
EALLOW;
CpuSysRegs.PCLKCR2.bit.EPWM1 = OFF; //低电平且已开启则关闭
EDIS;
}
}
}

在增加了新功能之后,发现ADC突然不工作了,每次ePWM一关闭,ADC也跟着不工作,一打开ePWM,ADC又开始工作了。

image-20210907134639238
image-20210907134734391

重新整理思路,想起来ADC工作的配置中,唯一一项跟PWM相关的就是其触发源。

image-20210907135154333
image-20210907135359661
image-20210907135436244

修改其触发源为CPU1 Timer0,正常工作:

image-20210907135823329

情况2——主函数被测试用的死循环卡住

主循环中一定不能再套一个等待状态(或死循环)的语句,否则整个主循环将有可能永远被卡死。

写完SCI部分函数后,发现ADC温度转换的不工作了,但ADC的中断程序(中断程序对全局变量的写入成功)和中断溢出标志是可以正常处理的,同时写了一个Debug变量放在主循环下的ADC包装函数中,发现数据并未被处理,因此可以判断主程序被卡死。

解决方法是,将SCI通信发送和接收的功能写到中断程序中,让中断程序来处理。

ECAP外设没有正常工作

首先是注意到该外设对应的GPIO没有正常工作,查看 GpioDataRegs 下的 GPADAT 寄存器,发现GPIO10在其他外设都能正常工作的情况下,该引脚无法动弹(表现为:使用3.3V电源连接GPIO10,没有反映)。

image-20210906172346311

然后查看eCAP引脚源代码,初步推断可能是引脚方向设置出错,更改以下源代码中的最后一句以更改引脚信号导通方向。

源代码如下:

1
2
3
4
5
6
7
8
void Init_eCAP_GPIO(void){
//配置输入XBAR10
InputXbarRegs.INPUT10SELECT = 0xA; // 将GPIO10(0xA)与输入XBAR 10进行连接

GpioCtrlRegs.GPAGMUX1.bit.GPIO10 = 0; //外设组多路配置
GpioCtrlRegs.GPAMUX1.bit.GPIO10 = 3; //外设多路配置
GpioCtrlRegs.GPADIR.bit.GPIO10 = 1;
}

再次测试发现结果正常。

image-20210907094432301

修改后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
void Init_eCAP_GPIO(void){
//配置输入XBAR10
EALLOW;
InputXbarRegs.INPUT10SELECT = 0xA; // 将GPIO10(0xA)与输入XBAR 10进行连接

GpioCtrlRegs.GPAGMUX1.bit.GPIO10 = 0; //外设组多路配置
GpioCtrlRegs.GPAMUX1.bit.GPIO10 = 3; //外设多路配置
GpioCtrlRegs.GPADIR.bit.GPIO10 = GPIO_SIGNAL_INPUT; // INPUT 输入
GpioCtrlRegs.GPAPUD.bit.GPIO10 = GPIO_PULLUP_DISABLE;
GpioCtrlRegs.GPAQSEL1.bit.GPIO10 = GPIO_SYNCHRONIZE;
EDIS;
}

注意:因为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; 即可。

image-20210913105909558
image-20210913110038846

image-20210913110545272

image-20210913110506997

SCI引脚导致复位

SCI配置完毕后,与对应的GPIO引脚无法通信(即无高低电平变化),将逻辑分析仪插入对应的引脚,会导致芯片复位重启。

原GPIO引脚配置为:一个GPIO为拉高和异步,另一个为拉高和同步。

将两个GPIO修改为异步配置之后,并未出现复位情况。

SCI RX数据错误

使用RS485进行发送时,不仅接收会存在错误,发送也会存在错误。

image-20210914155055154
图 使用XCOM串口助手和GREE DATACONVERTER进行发送和CCS中数组值有异
image-20210914155215669
图 使用GREE DATACONVERTER接收数据时也存在错误-1

在数据量较低且手动点击鼠标进行发送时,仍然会有数据处理错误:

image-20210914170117568
图 使用GREE DATACONVERTER接收数据时也存在错误-2

同样的代码(中途未复位重启芯片)对比使用CH340芯片进行发送和接收:

image-20210914155521881
图 使用CH340进行发送和接收时数据无误

SCI未触发中断

系统Debug

Windows/Linux/Mac

分离式代码的联合编译

image-20211009172014699

上图为使用VS Code进行项目编译时报错的情况,在使用 MinGW 的g++ 作为编译器时,一开始使用 main.cpp 作为被编译的文件进行编译运行,成功可用。

image-20211011150027217

后期新增了 SerialPort.hSerialPort.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.cppSerialPort.cpp 两个代码文件联合在一起进行编译,只生成一个 main 二进制可运行文件。

类似,只要在Windows上能用同一句编译命令实现多个 .cpp 文件的编译即可,但自己没有找到如何在 VS Code中进行该操作方法。另辟蹊径找到了用Terminal进行搞定,方法如下:

  1. D:\mingw64\bin 加入到 系统环境变量Path 变量下:
image-20211011150836483
  1. 打开 Terminal 进行跳转到工作目录,或者在工作目录按 shift 同时点击右键菜单里的 在Windows终端中打开 即可。
image-20211011150800473
  1. 尝试进行编译,发现就不报这个 undefined reference 的错了。

总结

分离式代码就需要联合多个 .cpp 文件进行编译,不能只编译一个 main.cpp,Windows Terminal 也可以直接操作 g++ (只要加了系统变量)进行多文件编译。

CANNOT CONVERT

以下为 VS Code中的报错:

image-20211009172830768

以下为 Terminal g++ 中的报错:

image-20211011151525322

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编码)储存的,因此会导致类型不匹配的编译错误。

解决办法

以下办法查询自网络,未实践:

  1. Visual Studio: 右击“解决方案资源管理器”中的项目,“属性→配置属性→常规→项目默认值→字符集”,默认的选项是“使用多字节字符集”,将它改为“使用Unicode字符集”即可。这样,输入的字符串会默认以 const wchar_t * 格式储存。
  2. 使用 _T 宏,它在 <tchar.h> 中定义,它能够自动识别当前编译器字符串的储存格式并做出相应转换,避免这种类型的编译错误。具体使用方法为:将 “abc” 改为 _T("abc")

以下办法亲自实验可用:

  1. 删除该实参 L"xxxxxx" 前的 L 字符即可,在使用 MinGW 编译器的情况下,如果有该 L 字符,就会一直报错,就算将文本字符集改成 UTF-16 LE/BE ,虽然不会报该 L 字符的错了,但仍会出现其他问题,也是因为字符集搞的鬼。在Linux或Mac下就不会报该错误。