Arduino 崩溃或挂起的 7 种方式(以及如何防止它发生)
作者:Chris in Arduino
查看原文
为了帮助防止Arduino崩溃或挂起,我进行了一系列实验,以确定Arduino崩溃,挂起,重置,冻结,停止运行代码或做一些奇怪的事情的所有方式。我把这些实验的结果放在一起,作为Arduino如何崩溃或挂起的指南,以及如何防止这种情况发生在你身上。
1. 调用太多函数
调用太多函数(例如在递归循环中)可能会导致 Arduino 崩溃和重置。根据实验,这可能在大约 300 次函数调用后发生。避免调用具有更多局部变量或更大局部变量的函数的调用次数,会减少 Arduino 重置次数。函数的递归循环是达到此函数限制的最可能原因。
// Recursive loop - a function that calls itself
reFunction(int a) {
if (a > 10000) return; // how to end the recursion
reFunction(a++); // recursion
}
每个函数调用都会消耗一些 Arduino 的内存;一旦Arduino耗尽了功能内存,它通常会重置(就像按下外部重置按钮一样)并再次运行setup()
。
在Arduino UNO上对此进行了测试。构建了一个具有 1 个参数和 1 个局部变量的小函数。通过递归中的参数递增,我可以看到在 Arduino 重置之前递归发生了多少次。在这个Arduino模型上,大约360次调用这个函数。
我用来用完所有地址的代码。Arduino在调用此功能大约360次后崩溃。
具有更多内存的 Arduino 可能会允许更多的函数调用;内存较少的Arduino可能允许较少的函数调用。
为了防止 Arduino 因函数调用过多而崩溃:
- 如果使用递归函数,请确保结束递归的条件不会运行太长时间
- 考虑使用
for
orwhile
循环,大多数 Arduino 应用程序不需要递归函数 - 减少函数中局部变量的数量;如果必须使用递归函数,减少局部变量的数量将允许更多的函数调用以适应Arduino内存。
2. 分配过多内存
如果 Arduino 内存不足,它可能会崩溃、卡住或以不可预测的方式运行。Adruino 内存不足的最简单方法是为变量分配太多变量或太多空间,例如使用malloc()
函数。也可能使用函数调用耗尽内存(如上所述)。
我做了一个实验,我分配了Arduino(UNO WiFi Rev 2)上的所有内存,看看Arduino的行为。分配所有内存后,任何进一步的内存请求都将不起作用,但函数调用仍然有效。我还发现Arduino上没有内存保护,我可以引用任何我想要的地址。
要防止 Arduino 崩溃耗尽内存,请执行以下操作:
- 准备程序时在函数中声明变量,对于大多数Arduino项目,可以计算编写代码时所需的变量;如果这些变得太大,编译器会警告您;
- 避免使用
malloc()
; - 如果使用
malloc()
是不可避免的,则使用程序free()
,尽管使用了`free()仍然可能耗尽内存,但可能性较小。
如果您需要在Arduino上存储更多数据,我写了一整本指南,列出了使用Arduino存储数据的一系列不同方法(从几千字节到数百千字节。在这里查看: chipwired.com/arduino-store-data/
3. 陷入无限循环
如果Arduino卡在无限循环中,阻止它执行其他代码,则Arduino可能会显示为挂起。这通常是在代码检查始终以 true 结尾的循环条件时引起的。
// Accidental infinite loop
int a = 0;
while(a == 3){
a = returnThree(); // a function that always returns 3
}
从技术上讲,Arduino总是运行无限循环void loop()
(函数),但是如果Arduino卡在你不想要的循环中,它可能会显得挂起。与PC不同,Arduino通常不能同时执行两个程序。这意味着如果程序卡在循环中,整个Arduino通常会停止执行任何其他任务。
上面提到的递归函数也是一种无限循环。除了函数调用自身的递归之外,函数也可以在循环中相互调用。
// Accidental infinite loop with functions
function x() {
y();
}
function y() {
x();
}
要防止无限循环,请执行以下操作:
- 通读代码流并确保退出无限循环的条件
- 计划调用函数的条件,尽量避免函数的无限循环
- 使用回调函数和中断,而不是在循环中等待,直到发生某些事情(请参阅下面的中断处理)
如果使用无限循环来等待输入,则可以在等待时运行其他代码。我发现了这个任务调度程序库,它允许Arduino同时执行程序的多个部分。它似乎有点难以设置,但它似乎确实适用于各种Arduino板。
4. 等待函数返回
Arduino 在等待库函数执行时可能会显示为挂起。当Arduino等待函数执行时,它通常无法处理其他代码(除非使用我上面提到的任务调度库之类的东西)。这通常是因为这些函数正在执行此列表中的其他操作,导致Arduino挂起或崩溃(例如,它们陷入无限循环)。
我看到这种情况发生的最常见功能是Serial.available()
。我经常看到人们将其用作循环条件,这意味着 Arduino 将卡在循环中,直到串行连接可用;如果没有连接可用,Arduino将不会执行任何其他代码。
void setup(){
Serial.begin(9600);
while(!Serial.available()); // wait for the serial connection
}
void loop() {
// do stuff
}
在上面的示例中,如果没有任何可用内容可从串行连接读取,则Arduino将在启动时挂起。虽然我没有深度研究过Serial.available()
,但我想它是等待数据可用的无限循环。
要降低函数调用崩溃或挂起 Arduino 的可能性,请执行以下操作:
- 确保您的 Arduino 可以退出无限循环,例如在上面的示例中,为要读取的数据提供串行连接;
- 考虑一下函数在调用它们时所做的“promise”库函数,函数的这个承诺(例如,串行可用意味着询问串行连接是否有可用)是否意味着它可以进入无限循环。如果不确定,请查看 Arduino 参考或库的文档。
5. 中断处理不正确
如果Arduino不断提供中断而不是执行其主代码,则它可能显示为挂起。如果处理器不断跳转以中断服务例程,它将无法完成其他任务,因此似乎挂起。
中断的典型执行流程为:
- 在
setup()
函数中将中断处理函数注册到特定事件(当事件发生时,此函数将由处理器执行); - Arduino 执行其
loop()
; - 事件发生,Arduino去执行中断处理功能;
- Arduino 返回执行
loop()
。
如果 Arduino 在完成处理中断之前就被中断淹没,它可能永远无法完成处理它们,并且看起来会挂起。我使用此页面作为Arduino中断处理的参考。
为了尽量减少处理中断时 Arduino 挂起的可能性:
- 尽可能少地处理中断,最大限度地减少中断处理函数中的代码;
- 在处理中断时考虑修改变量的**atomicity* *,可以用相同的中断处理程序(我知道这很混乱!
- 与其打开和关闭中断,不如考虑保存处理器寄存器的状态,关闭中断,然后恢复寄存器的状态 - 这可以防止在中断应该关闭时意外打开中断(由另一个处理程序)
6. 电源掉电
如果输入电压降至一定水平以下,Arduino 将崩溃并重置。可以将警告配置为在电压开始下降时开始执行中断处理功能;一旦达到掉电级别,Arduino 就会崩溃并重置。
当电源电压显着且持续下降时,通常会发生掉电,超出 Arduino 的容差水平。这可能是由以下原因引起的:
- 如果 Arduino 使用电池运行,则电池电量耗尽
- 电源发出严重噪音或中断
- 另一个设备突然消耗电流,该设备错误地配置为与 Arduino 共享电源(例如,配置不良的电机)
电压输入电平因 Arduino 型号而异:例如,UNO 系列通常为 7V 至 12V(或 USB 5V),而 MKR 系列为 5V。
大多数Arduino板,当然是Arduino自己生产的板,在板中内置了质量不错的电源管理电路(如稳压器)。我发现,只要您遵守数据手册上的电压要求,并提供足够的电流,就不会有问题。
要避免由于掉电而导致 Arduino 重置,请执行以下操作:
- 提供稳定的电源,大多数商用电源设备(包括手机充电器)在兼容规格内应能工作;
- 小心将使用大量电流的电路组件(例如电机)连接到Arduino使用的同一电压源,突然的电流消耗可能会中断Arduino的电源。
我的UNO WiFi Rev 2由标准USB手机充电器供电*
7. 看门狗定时器配置不正确
如果看门狗计时器已启用并且到达其倒计时期结束时,Arduino 将重置,而代码未清除。如果 Arduino 未能在时间用完之前清除看门狗计时器,则看门狗将重新启动 Arduino。
忘记在代码中清除看门狗计时器,或者不够频繁地清除,可能会导致Arduino崩溃。
查看此处的示例程序,以了解有关如何配置看门狗计时器的更多信息,以便它不会意外崩溃您的 Arduino。
如何知道您的Arduino是否还活着
为了证明 Arduino 仍在正确执行代码,通常使用检测信号例程。Arduino 的心跳是 LED 通过固定模式闪烁,因此可以观察到,如果 LED 未能遵循该模式,则 Arduino 没有正确执行代码。
如果心跳 LED 已编码但未闪烁,则 Arduino 已崩溃或挂起。闪光灯的模式很重要,因为卡在重启循环中的Arduino(例如,由于看门狗定时器)可能仍然闪烁LED,但模式错误。
下面是一个示例检测信号例程:
void loop() {
do_stuff();
heartbeat();
delay(200);
}
void heartbeat(){
//toggle LED
}
我更喜欢这种切换方法,这样您就可以看到Arduino是否需要很长时间来处理事情。如果检测信号变得不均匀,则意味着 花费的时间过长,或者处理器正在处理大量中断。do_stuff()
引用
在整理本指南时,我使用了一些参考资料。如果您有兴趣阅读有关这些内容的更多信息,请查看下面的列表:
- 中断
- 我的 Arduino 核心处理器的数据表(UNO WiFi Rev 2)