2、硬件环境导致"死机"
1)供电电源电压不在合适范围
单片机都需要有一个能够稳定运行的电压工作范围,如果低于或者高于正常工作电压范围其单片机并不一定会立马无法工作(也有可能会立马死机),而是工作一段时间在某种环境条件满足的时候造成死机。
处理办法:可以通过万用表或者示波器进行电压值监控并作出判断。
2)供电电源干扰杂讯较多
我们都知道高频杂讯会对单片机的内部各时钟或者信号造成辐射等干扰,这样会导致单片机内部电路异常工作而导致单片机死机。
处理方法:可以使用高带宽的示波器对电源进行杂讯的显示,或者使用干净的电源做对比实验进行排查。
3)复位电路设计问题
复位电路分为可操作复位和不可操作复位,可操作复位一般可以通过按钮形成电平或者跳变使得其芯片复位,不可操作复位一般是电平复位,需满足复位引脚为高电平或者低电平才能完成上电复位。(所以也需要根据芯片的datasheet来查看其复位属于那种电平复位)所以如果我们复位电路设计不正确或者电路本身元件虚焊接等都会导致芯片无法正常工作。
解决办法:了解好对应芯片的复位电路工作原理并进行电平电源的测量。
4)晶振电路异常
晶振电路相当于单片机的心脏,晶振与内部的电容形成一种储能元件的充放震荡电路,从而形成了类似如下图所示波形。晶振通常和锁相环一起工作产生时钟信号为单片机内部各种动作提供基准,一旦时钟信号不稳定或者是错乱等都会有可能导致芯片出现异常。
解决办法:
1、用示波器测量法
通过示波器测量晶振两端,可以看到示波器上会产生同频率的正弦波出现。大体如下图所示
注意点:
1)晶振本身无法产生对应频率的正弦波,而是要配合单片机内部电路,比如说通过整形电路变成方波以后供芯片内部其他模块使用。
2)晶振尽量紧挨着IC,由于晶振输出能力有限,容易受到外界电磁波等信号的干扰。
3)晶振需要在单片机所规定的范围中,因为单片机内部数字电路对频率都有限制。
2、万用表测量法
通过上面的震荡波形我们大致了解晶振两端的电压类似于正弦波,那么万用表测量为平均电压值,那么其电压值基本位于芯片供电电压的一半左右,如果测量为0或者VCC都认为是有问题的。
3、IO口输出法
对于高端一点的芯片,其存在内部的晶振,这样的芯片一般都存在能够通过配置寄存器把相应的频率通过IO口进行输出,这样我们也可以通过测量该频率来判断晶振电路的好坏。
5)IO保护
我们都知道我们的MCU与外界的接口就是通过引脚来传递信号的,那么每个引脚都会有其他的属性,比如耐压值,过流值等等,一旦超过!小则可能导致单片机复位或者死机,大则导致芯片损坏。有时候我们用手不小心碰到了芯片的IO引脚,导致芯片复位或者无法正常工作,也就是因为这个原因导致芯片内部逻辑错乱。
解决办法:通过设计相关电路进行电流、电压的限制即可,比如常用在没有使用到的引脚一般接一个150欧姆左右的电阻到地,或者加一些二极管防止电流的倒灌等等。
6)硬件原因小节
硬件方面的还有很多,比说说芯片上数字地和模拟地的处理等,这里只是为大家展示一下大伙经常遇到的现象和相应的解决办法。具体的项目实战过程中可以使用排除法,或者是采集相应的波形进行判断和处理。
3、软件异常导致"死机"
1)看门狗处理不当
作者之前写过一篇文章《看门狗你确定会用了吗?》里面介绍了看门狗的使用小技巧和注意事项,大家可以搜索阅读,虽然说看门狗能够在一定程度上能够缓解软件上的异常死机,不过对于硬件上的一些死机能够复位的情况相对较少,所以说watchdog对于程序死机也不一定能够确保解决的。
有些单片机芯片默认启动代码中存在看门狗初始化,那么当我们使用的时候需要关闭看门狗,否则导致程序启动、复位循环造成死机。
2)没有超时处理的while
程序尽量不要写while(!条件满足);这样的语句,一旦条件不满足就一直处于循环当中,条件发生异常就会导致程序一直卡死在当前位置无法继续执行。比如说我们常用的等待某个指令接受,等待某个电平信号翻转等等。
解决办法:1)加上等待超时处理,设置一个最长的延时时间;
2)尽量采用轮询的机制对相关信号或者数据进行查询来进行处理;
3)该现在一般出现在程序运行中,对于出现比较频繁的死机可以用仿真器进行暂停查找出现异常的代码;
4)对于任务运行较慢的情况,也可以通过串口在进入while前打印进入等待信息,出while后打印退出等待的信息,便于程序员分析问题。
3)中断处理时间过程
我们大部分的芯片都是单核的,在同一时刻就只能有一个任务在执行,同时中断和主程序是先后执行的,执行情况如下:
当中断服务函数中运行的程序花费的时间太长或者由于中断不正确使用导致频繁的进入中断服务函数而使得主程序得不到执行,表现出死机的假象。
解决办法:看过的往期的小伙伴一定看过《如何测定程序运行时间》,该文章中详细的为大家讲述了多种手段获得程序运行时间,那么我们同样可以测定中断服务函数的时间来进行判断和处理问题。
4)堆栈溢出
堆栈溢出可能是很多程序员的噩梦,堆栈其实分为堆和栈两种结构:堆主要是我们malloc自动分配的内存,而栈则是我们进行函数调用等进行的保护现场使用的容器,对于跑RTOS的小伙伴对每个任务的堆栈分配也值得注意的,对于堆栈太大则浪费内存,太小就会容易溢出!比如一旦我们的栈溢出会导致程序相关寄存器的值无法恢复,从而导致程序紊乱。(对于堆栈的理解的具体实例可以观看之前写的ucos移植到stm32有比较形象的解析)
解决办法:1)对于堆可能我们大部分玩单片机的小伙伴用得不是很多,对于大部分性能强大一些的单片机我们一般模拟一套堆;而对于栈,一旦我们怀疑到这一点上,一般的处理办法是加大堆栈看是否程序跑飞,或者是尽量减少函数嵌套和局部变量的使用等。
2)通过设计堆栈溢出监控程序,基本的原理就是通过检测堆栈的末端安插特殊的字符序列,一旦堆栈溢出势必会修改该部分数据,从而等到检测,不过也会存在漏检测问题。具体设计根据需求来设计。
5)数据越界、指针使用不当
数据越界和指针使用可能是很多小伙伴们遇到过的问题,它也是会造成程序异常的原因,小则随便改变一下不该修改的数据,大则程序直接飞了,这也是很多小伙伴在项目过程中遇到的比较头疼的bug,经常有一些小伙伴们说"这个变量我其他地方都没有用,怎么就变了呢?",还有使用了非法指令或者访问了非法地址从而触发的异常中断,比如stm32里面的hardFault中断,我们一般都会在该中断服务函数里面写一个死循环来阻止程序继续运行。
处理办法:1)首先对于数据越界问题,这个大部分是因为我们平时没有养成非常好的编程习惯,对于函数参数没有范围限定等处理,导致数据的下标越界,导致程序异常。作者在往期非常多的文章中总结了非常多的代码书写规范等。推荐阅读往期中的《根据库学驱动编程》和《嵌入式编程之代码重构》等。
2)对于上面的两个问题对于一般高性能单片机都会触发对应的异常中断,比如stm32中的hardFault异常中断,一旦数据越界、指针访问错误或者是堆栈溢出基本上都会触发该中断。
触发HardFaultHandler处理办法:
1)我们会在中断中安插一条while死循环,我们可以通过仿真器查看当前CPU中各寄存器中的数据,因为我们知道在从主程序到中断服务函数的切换过程中需要保存中断现场,那么一定会保存返回的地址,我们需要查看对应芯片的入栈顺序,从而找到发生异常地址的下一条程序,从而定位问题。
2)我们可以在hardFaultHandler函数中写入一条返回的语句,然后把断点打在该位置,一旦程序运行到该位置,然后单步调试即可到达异常位置的下一条,从而定位问题。
3)对于现在的IDE都会有堆栈等异常检测的功能,可以了解一下进行使用。