【单片机、硬件调试】嵌入式调试bug记录

13 篇文章 4 订阅
4 篇文章 0 订阅

2023.5-2023.11

待输入

2023.12-2023.2

辞职阶段:【STM32调试】寄存器调试不良问题记录持续版

2024.3-至今

1.spi通信问题

现象说明:
mcu与afe芯片为spi通信:①速度很慢。 ②mos控制动作总是第二次成功,偶尔一次就能成功。

调试过程:
spi速率为4kHz(确实很慢),但是第二个现象的问题,怎么也无法定位,芯片的寄存器都和代码仔细对了一遍,逻辑分析仪的spi数据都没有问题。

解决:
①afe数据手册发现,该芯片速率最高支持10Khz,确实很慢。目前代码中使用的是模拟spi,我也改为了硬件spi,依旧很慢。【教训:先看数据手册,看看人家最高能支持到多少,再修改spi驱动】。
②询问厂商,厂商回答该芯片内部是三颗单通道的afe芯片级联而成,级联方式为spi菊花链模式,spi菊花链数据写入,是将数据依次往后面的spi从机中递推(不明白的具体百度下)。而刚好该芯片的mos控制,只能由第一个spi芯片控制,我有对比了spi波形,以及数据手册中人家说明的spi写入时序,最后发现,确实spi写入时序搞反了,没有按照人家手册说明写。

教训:①硬件spi和模拟spi都熟悉了,以及其速率计算方式。②spi菊花链通信方式。③如何使用逻辑分析仪分析spi数据。④详细按照芯片数据手册办事。

spi

2.记一次由全局变量自动改变而引发的问题

现象说明:
一个c文件中,定义的一个全局结构体变量,总是在程序的一个固定位置,这个位置是调用freertos的延迟函数之后,该结构体的数据成员值总是发生改变。

调试过程:
① 首先,确定看是不是自己将其数值进行了改变,经过调试发现,并未是自己改变了该变量。
② 其次,可能是发生了内存覆写现象。例如,对一个数据进行越界的写入。但是,程序中并没有这种情况。
③ 最后,变量改变总是发生在调用freertos的延迟函数之后,那么可以联想,是不是因为任务堆栈分配过小,导致内存覆盖。

验证过程:

方法① 使用freertos的栈溢出检测的钩子函数

#define configCHECK_FOR_STACK_OVERFLOW	   1  /* 1: 使能栈溢出检测方法1, 2: 使能栈溢出检测方法2 */

在main函数中,定义钩子函数。

void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName )

然后,在钩子函数中打断点,通过仿真可以发现,程序停在了断点处。

方法② 使用keil工具
找一个发生覆写的数据成员,不要直接使用大的结构体。右击选择Set Access…
在这里插入图片描述
出现下图对话框,先点击kill all将全部断点取消,然后点击1处和2处,再点击Close即可。
在这里插入图片描述
然后,仿真程序直接往下run即可,仿真会停在,该数据成员内存发生改变的位置。
我的程序出现问题的位置,如下图所示。很容易看出,任务堆栈的内存溢出了。
在这里插入图片描述

3.由于程序增加,导致Flash的覆盖问题

现象说明:
程序中定义了一个const的结构体变量,程序运行起来之后,发现数据总是不对。

调试过程:
查看该变量内存,发现其改变后的数据,很特殊。。
因为const类型的变量,是存储在falsh中,所以其地址为0x80开头。
在这里插入图片描述>看到这个数据,想到程序中有一个配置信息,就是这个数据,这个配置信息是我存储到flash中的,有可能是发生了flash占用。

解决:
我将我配置信息变量的写入地址,更改之后,程序正常了。
由于程序越写越大,新定义的const变量,被我后来存入Flash的配置信息覆盖了。

4.直流电源输出负载不足,导致的电压读取异常

现象说明:
测试环境中,使用直流电源给电阻供电模拟电池,电池监控芯片测量的电压,偶尔在负载启动的时候出现电压测量错误的问题。

调试过程:
调试过程中发现,当负载启动的时候,直流电源的电压会被拉低一下,然后电压在恢复正常,导致电池监控芯片读取的电压偶尔不正常,甚至出现充电的状态。

解决:
直流电源的电压被拉低,是由于它的带载能力不足,导致电压会被拉低。
充电状态的判断,需要添加时间滤波。

5.GD32低功耗调试记录

  1. GD32的深度睡眠模式和待机模式,分别对应stm32的stop和standby模式。
  2. 深度睡眠模式醒过来,如果你不是使用的内部8m时钟,需要重新配置时钟。
  3. 进入低功耗前,将gpio设置为浮空输入模式(高阻态)。
  4. 通过EXTI外部中断唤醒,对应的gpio引脚也是需要配置的。
  5. 进入低功耗前,虽然外设时钟都会自动关闭,但是有些外设的功能(例如DMA、ADC功能)也是需要关闭的。等唤醒后,再重新进行初始化该外设。

6.触发HardFault_Handler的问题调试记录。

现象说明:
调试发现,总是进入硬件错误出来中断函数HardFault_Handler。进入次函数的原因大多是:
1、数组越界操作;(单纯的数组越界,在运行时,并不会进入硬件错误中断。只有在数组越界了,你写入了值,导致超范围的数据错误了,有可能会造成程序跑飞。) EN:Access Array Over Range
2、内存溢出,访问越界; EN:Access RAM/FLASH Over Range
3、堆栈溢出,程序跑飞; EN:Access Stack Over Range
4、中断处理错误; EN:Interrupt Process Error

调试过程:

  1. 进入Keil仿真,在HardFault_Handler内部开始打断点。然后全速运行,直到运行到该断点。
    在这里插入图片描述
  2. 然后去看左侧LR寄存器中的内容是什么,根据这个值去搜一下。
    我问的chatgpt,因为我仿真的LR寄存器中的值是0xFFFFFFF9,所以再去看MSP寄存器的值。
    在这里插入图片描述
  3. MSP寄存器的值,在memory中查看。
    在ARM Cortex-M4内核中,当发生异常(如中断、故障或系统调用)时,处理器会自动将一些寄存器的值压入堆栈【至于是哪些寄存器以及顺序如何,自动百度】。因为栈是向下增长的,根据MSP的地址,往上偏移6-7个寄存器大小的位置。我们查看第6个和第7个寄存器的值分别为0x080009A5和0x080009AA。
    在这里插入图片描述
  4. 在汇编窗口查找0x080009A5和0x080009AA地址对应指令。
    在这里插入图片描述
    在这里插入图片描述
    可以看到上边两个地址分别对应如下图的指令
    0x080009A5为什么没有,因为CM3使用的是Thumb模式,所有指令地址的最低位是0。所以对应地址应该为0x080009A4。
    再次仿真发现,实际上在0x080009AA指令处,在继续进行,就进入HardFault_Handler函数了。
    则这个位置就是导致错误发生的关键,在继续进行分析错误即可。
    在这里插入图片描述

7.记一次因arm对齐问题产生的bug记录,接上第6条。

现象说明:
总是在这一句的位置进入到HardFault_Handler函数,说明这一句有问题。
经过第6条的仿真,已经发现错误的指令为:0x080009AA 6028 STR r0,[r5,#0x00];
在这里插入图片描述
我直接一个chatgpt,得出结论:
在这里插入图片描述
然后我们再去看这个buf[0]的地址:

在这里插入图片描述
发现地址确实为奇数,并没有4字节对齐。
所以我们必须使得该数组为4字节对齐。
使变量地址为4字节对齐的方法有很多,我采用的如下方法:
__attribute__((aligned(4)))
在这里插入图片描述
再次调试,问题消除。
上述为以(uint32*)四字节方式访问,会有这样的问题,当然以(uint16*)二字节方式访问,也会有这样的问题。

7.电阻烧坏了

现象说明:
测量硬件板子,发现某一个电阻两端的电压总是不正常,因为该电阻附近也有其他元件坏过(但是已经更换),总是以为是其他原件又发生了问题,从来没有考虑过是电阻烧坏了。

结论:
上次该电阻附近的稳压二极管坏掉了,导致这个电阻烧坏了。当然,也有可能是这个电阻先烧坏了,才导致稳压二极管坏掉了。测量该电阻,直接从100欧姆变成了16k。

8.有时候你看到的现象并不是真的程序运行逻辑

现象说明:
bms有触发阈值3s后保护的功能。我的板子存储了一条触发了环境温度过温报警记录,查看当时温度为117.8,这个温度很异常啊,一看电路板,该ntc并没有焊接,按电路图adc读出来应该一直是最大值4095,拿串口监控adc读出来的数据一直是4095,对应的温度为144.1。这我就纳闷了,怎么会平空出来一个117.8,并且持续了3s的时间呢,就算是出现了adc读取干扰,也不可能一直持续干扰了3s啊。

调试过程:
此时,能想到的出问题的地方就是①adc读取错误 ②程序中数据转换错误 ③程序对3s时间检测有问题。有时候用jlink仿真多了,就忘了串口打印的这位老朋友。直接将adc中寄存器数据、转换过程中重要的过程数据、转换后的温度都打印出来,看看那里出了问题,鉴于该问题能够复现,最终发现,①adc读取值并不一直是4095,偶尔是4092 4093啥的,也属于正常,所以adc没问题。②转换后的温度,确实不对了。adc:4095,ntc:144.1;adc:4092,ntc:117.8;adc就差这么点,怎么可能温度差这么多呢?并且一看117.8这值,奇怪?为什么和原来的越限一样。99%是逻辑问题。一看计算过程,确实有大问题,不仅有数据错误(0成了除数),而且ntc阻值都越限了,越界处理有问题。
那么为什么能持续3s呢?因为程序加了数据平滑处理,下一次数据读取的值如果对上一次在一定范围之内的话,是不用改变的。所以就算是这次4093,下次4095了,值人就是原来的值。就这样一直持续了,啧啧啧。

结论
这程序是原来的人写的,而且是量产的程序,如果这个ntc不焊接,根本找不到这个软件bug。
查询问题:①根据现象设置查询实验,不能上来就盲目的调试差问题。
②串口打印还是好用的。

9.单片机重启原因查看寄存器

对于stm32,是重启原因寄存器(RCC_CSR)。
对于gd32,是重启原因寄存器(RCU_RSTSCK)。
一定要将断点打在main一进来的地方就行,防止该寄存器被刷新。

10.程序打不了断点?代码不按流程执行?百思不得其姐!

刚来公司时,接手了一个代码,工程既不是keil的也不是iar的,是通过纯纯makefile(gcc)编译的,当时光环境啥的,编译成功就鼓捣了两周。好不容易编译成功了,使用vscode安装插件调试,TM的发现程序有的地方没法打断点,甚至代码不按流程运行。我第一次用vscode调试也不知道那里的问题。后来发现,makefile文件中编译参数开启了-os级别优化,极大的减少了代码体积。问问度娘,确实对调试有影响,直接改为了-o0级别优化,可想而知发生了什么,flash直接干爆炸了。我说为什么前工程师开启这个级别优化呢。后来想办法,在-o0级别下编译编译成功,再次进行调试,什么问题都没有了。

-os是ac6编译器或者gcc编译器才有的优化等级,是专门针对代码大小的优化,ac5中没有这个优化等级,但是-o2 -o3的优化等级都会引起上述问题的出现。所以在开发过程中,开发调试阶段不开启编译优化,等release的时候才进行开启优化。

11.Keil MDK优化选项说明

Keil5软件使用-进阶工程配置篇
上面这一篇总结的相当全面。
如何正确使用MDK-ARM优化
上面这一篇是专门针对优化文章
如果你想优化代码体积
①Use MicroLIB:使用微库 会减小一点代码体积,不会很明显
②Level 2 (-O2) : 高度优化,目标代码到源代码的映射并不一定对应,因此,不利于调试。但是能极大减少代码体积。
③The One ELF Section per Function:能极大减少代码体积。
如果你想优化代码内存
在代码中使用了C标准库的时候,开启Use MicroLIB,会减少大量内存。

12.从bootloader跳转到app程序,我们还需要注意什么?

对于bootloader程序:
如果你是起始地址不变的话,只需要设置一个程序占用大小即可(其实,不设置也行,但是假如我们就给boot留了10k,程序写着写着大于10k了,如果这里设置了大小,编译的时候就会报错。)

在这里插入图片描述

然后,就是程序跳转函数

typedef  void (*otaFun)(void);    // 函数指针
Uint32 app_address;
otaFun application;

static void JumpToApplication(Uint32 uiAppAddr)
{
	/* 检测栈顶地址是否合法,app程序的第一个字用于存放栈顶地址 */
    if (((*(__IO Uint32*)uiAppAddr) & 0x2FFE0000U) == 0x20000000U) 
    {
    	/* 跳转前,关闭总中断 */
        __disable_irq();  

        /* 第二个字为程序开始地址(复位地址) */
        app_address = *(__IO Uint32*) (uiAppAddr + 4U);
        application = (otaFun) app_address;

        /* 设置栈顶地址 第一个字用于存放栈顶地址*/
        __set_MSP(*(__IO Uint32*) uiAppAddr);

        /* 跳转到app程序 */
        application();
    }
}

对于app程序:
程序起始地址和程序大小都需要设置下(当然,你想把app程序放在0x08000000也是可以的,全看你怎么分配flash)

在这里插入图片描述

假如你的程序起始地址不是0x08000000了,那么相应的中断向量表的位置也就发生了改变,我们需要将中断向量表进行重定向。
一般来说,有两种方法。
一是,告诉程序我的app程序的地址发生了改变了,偏移了多少,这样程序会自己去找到中断向量表。
二是,将中断向量表的这段数据复制到内存中,也就是0x20000000里面,并设置单片机中断向量表去内存中查找(一般为某寄存器),假如你从0x20000000开始存放中断向量表,并占用了0x100大小,当然这时你的内存使用也不能从0x20000000开始了,应该从0x20000100开始使用。

例如,GD32F3x0单片机就支持两种方式的是设置方法,但是对于M0低端些的mcu,可能只有方法二。

在这里插入图片描述

进入app程序后,不要忘记把总中断打开。

对于一般的情况,上面的解释都可以搞定了,但是我又遇到了一个新的问题,这里记录下。
我的bootloader程序是无os的,并且使用的systick功能,也开启了systick中断。
我的app程序是freertos的。
这个时候,我用上述跳转函数能跳转到app,没有问题,但是app程序总是莫名重启,而且重启的位置不定。
仿真app发现总是进入HardFault_Handler,根据我上述方法查看跳转到这里的程序,就是从freertos的底层程序跳转过来的。
弄了半天也没找到问题所在,最后发现竟然是systick的问题。
freertos的节拍使用的是systick的,是自己会去配置systick的,无需用户手动配置,但是我在boot程序中使用过systick,也没关闭,是这里出了问题。原本以为跳转前关闭总中断就没有问题了,看来还是不行。仍需要手动关闭systick。

13.程序进行浮点运算,总是进入HardFault_Handler函数?

前提条件:
使用GD32F310单片机,操作系统为Freertos,开启了FPU(浮点运算单元)
GD代码中的__FPU_PRESENT、__FPU_USED也均开启。
使用“C:\Keil_v5\ARM\Segger\JLink.exe”工具,连接好单片机,使用mem32 0XE000ED88 1命令,得到为00F0 0000的值,证明FPU已经开启。
在这种情况下,程序进行浮点类型数据的赋值或者计算,都会进入HardFault_Handler函数。

解决问题:
进入HardFault_Handler函数多半都是内存上的问题。
首先能想到的是排查rtos任务栈大小设置的是否足够。如何查看任务或系统占用堆栈大小
但是我的程序堆栈设置都是足够的。
通过百度发现,FPU未正常开启,也会出现上述情况,我使用“C:\Keil_v5\ARM\Segger\JLink.exe”工具,连接好单片机,使用mem32 0XE000ED88 1命令,得到为00F0 0000的值,证明FPU已经开启。
还有人提到是freertos的配置文件有几个中断配置的不对,但是我看也没有问题。
最后查找发现是字节对齐问题,在上述条件下,浮点类型的数据需要进行四字节对其设置。(至于为什么是四字节对其,可以去看反汇编出来的代码,我看不懂。)
例如:Float afParas[2] __attribute__((aligned(4)));
但是我的程序还是不行,排查发现,我的变量位于一个结构体中,而这个结构体位于pack之间,这样字节对齐的功能就失效了:
#pragma pack(1) /*浮点类型定义*/ #pragma pack( )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辛集电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值