延时解除Arduino ISR中限位开关的抖动
我在arduino Mega 2650上安装了一个限位开关,用于运动控制。限位开关的两个常开触点连接至Arduino引脚和接地,因此当限位开关接合时,Arduino引脚对地短路 正如所料,我有反弹问题与此设置。我使用ISR中的计数器进行了确认。最后,我编写了下面的代码,它似乎可以可靠地确定我的限位开关在任何给定的时间点是接合还是断开延时解除Arduino ISR中限位开关的抖动,arduino,embedded,interrupt,debouncing,Arduino,Embedded,Interrupt,Debouncing,我在arduino Mega 2650上安装了一个限位开关,用于运动控制。限位开关的两个常开触点连接至Arduino引脚和接地,因此当限位开关接合时,Arduino引脚对地短路 正如所料,我有反弹问题与此设置。我使用ISR中的计数器进行了确认。最后,我编写了下面的代码,它似乎可以可靠地确定我的限位开关在任何给定的时间点是接合还是断开 const int lsOuterLeftIn = 18; // lsOuterLeftIn is my Limit Switch const int LED =
const int lsOuterLeftIn = 18; // lsOuterLeftIn is my Limit Switch
const int LED = 9;
volatile bool lsEngaged = false; // flag for limit switch engaged
void setup() {
pinMode(lsOuterLeftIn, INPUT_PULLUP);
pinMode(LED, OUTPUT);
attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR1, FALLING);
attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR2, RISING);
}
void loop() {
if (lsEngaged) digitalWrite(LED, HIGH);
else digitalWrite(LED, LOW);
}
void ISR1(){
delay(100);
lsEngaged = (digitalRead(lsOuterLeftIn));
}
void ISR2(){
delay(100);
lsEngaged = (digitalRead(lsOuterLeftIn));
}
但是,这是我的问题。我偶然发现了这个,它说
由于delay()需要中断才能工作,因此如果调用它,它将无法工作
在ISR内部。”
但是,我确实在ISR内部使用了delay()
,而且似乎有效,到底发生了什么?我是否遇到过这样的情况,即事情目前正在运行,但很容易中断,因为delay()
函数可能会像文档中所说的那样在我身上出现故障 在AVR上,delay()的实现如下所示。不涉及中断(micros()返回timer0计数值,yield()指简单草图中不使用的调度程序)
我认为这是对可移植性的评论,因为您使用的环境可以在越来越多的平台上工作。你在AVR上做的很好,在另一个平台上可能没有那么多
我建议使用一个简单的for循环进行旋转等待。cpu没有做任何其他事情,除非功耗是一个问题,但这超出了本文的范围
从
汤姆凯迪的答案看起来是正确的:你不会有任何问题。无论如何,在我看来,您的代码在概念上是错误的,至少有两个原因。现在我来解释原因 有两种输入:一种是你必须立即回答的输入,另一种是你必须回答但不是直接威胁的输入。例如,通常安全止动器落在第一组中,因为一旦碰到它,就需要停止执行器。另一方面,UI按钮属于第二组,因为您不需要立即回答 注意:在一个做得好的程序中,您通常可以在十分之一毫秒内回答第二类输入,因此用户永远不会看到延迟 现在,如果您的输入属于第二组输入,您不应使用ISR读取它,因为您可能会阻止更重要的内容。相反,在主循环中阅读它,正确地去抖动它。例如,您可以使用
Bounce
库,也可以自己实现它:
#define CHECK_EVERY_MS 20
#define MIN_STABLE_VALS 5
unsigned long previousMillis;
char stableVals;
...
void loop() {
if ((millis() - previousMillis) > CHECK_EVERY_MS)
{
previousMillis += CHECK_EVERY_MS;
if (digitalRead(lsOuterLeftIn) != lsEngaged)
{
stableVals++;
if (stableVals >= MIN_STABLE_VALS)
{
lsEngaged = !lsEngaged;
stableVals = 0;
}
}
else
stableVals = 0;
}
...
}
如果值发生变化,则每20毫秒检查一次。但是,仅当该值稳定超过5个周期(即100 ms)时,才会更新该值
这样,您就不会用该任务阻塞主程序
另一方面,如果您的输入对您的设备构成严重威胁(例如endstop),您需要尽快回答。如果这是您的情况,您需要等待100毫秒才能回答,这不符合输入速度的需要
当然,您不能对这样的输入进行去抖动,因为去抖动会带来延迟。然而,你可以让一个州优先于另一个州。在连接至接地端止点的情况下,严重威胁是输入状态为接地时。因此,我建议您以如下方式设置变量:
#define CHECK_EVERY_MS 20
#define MIN_STABLE_VALS 5
unsigned long previousMillis;
char stableVals;
attachInterrupt(digitalPinToInterrupt(lsOuterLeftIn), ISR1, FALLING);
...
void loop() {
if ((millis() - previousMillis) > CHECK_EVERY_MS)
{
previousMillis += CHECK_EVERY_MS;
if ((digitalRead(lsOuterLeftIn) == HIGH) && (lsEngaged == LOW))
{
stableVals++;
if (stableVals >= MIN_STABLE_VALS)
{
lsEngaged = HIGH;
stableVals = 0;
}
}
else
stableVals = 0;
}
...
}
void ISR1()
{
lsEngaged = LOW;
}
正如你所看到的,唯一的中断是下降的中断,最重要的是,它非常短
如果您需要执行其他指令,如停止电机,可以在ISR1功能中执行(如果指令很短)
请记住:ISR必须尽可能短,因为当微控制器在其中一个ISR中时,它会对除抖动代码中的其他内容视而不见,看起来您可以为开关接合留出100毫秒的反应时间
所以,如果你真的不需要在事件的微秒内做出反应,就只需在每10ms(例如从一个定时器ISR)上轮询输入。
(使用外部中断的原因只有两个:1.您需要对信号做出非常快速的反应(µs!),或2.您需要从定时器未激活的深度省电模式中唤醒。对于其他一切,您都可以进行基于定时器的轮询。) 伪代码:#define STABLE_SIGNAL_DURATION 5
uint8_t button_time_on = 0;
volatile bool button_is_pressed = false;
...
// Every 10ms do (can be done in a timer ISR):
if ( read_button_input() == ON ) {
if ( button_time_on >= STABLE_SIGNAL_DURATION ) {
button_is_pressed = true;
} else {
button_time_on++;
}
} else {
button_time_on = 0; // button not pressed (any more).
button_is_pressed = false;
}
...
在main()
中:
比使用时间戳更容易
volatile bool buttonDirty = false;
void setup() {
attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPress, FALLING);
}
void loop() {
while(1){
readButtons();
}
}
void buttonPress(){
if(buttonDirty) return;
buttonDirty = true;
}
void readButtons(){
if(!buttonDirty) return;
delay(100);
...........
}
这应该可以解决你所有的问题。在中断中使用繁忙的等待循环是一个非常糟糕的主意——永远!您还应该(还)提供去抖动硬件(RC低通),因为反弹可能太快,无法由µC处理。开关反弹可能会导致许多MHz的杂散脉冲,远远超过IO的施密特触发器或FF或任何指定处理的杂散脉冲。这些脉冲在软件中很难可靠处理,但可能会导致输入电路或µC的其他部分出现一些不希望出现的操作。最好首先让它们远离IO引脚。嗯,奇怪。我似乎找不到有关这些“空调电气特性”的任何信息。然而,我发现50ns(=20MHz)是中断logik保证检测到的最小脉冲长度。无论如何,我不会超过零件的最大时钟频率。谢谢你的伟大洞察力。在您确定的两种情况中,我的设置确实属于第二种情况,即我需要立即快速处理中断。然而,有一个问题。当限位开关断开时,
ISR1
再次被调用,但只有这一次,我希望它什么也不做。我该怎么处理?ISR1之所以被调用,是因为您得到了一个上升
边缘,然后是上升
和下降
边缘。在我上传的基本草图中,您没有问题,因为lsEngaged
被设置为低
,即使它已经在
bool button_press_handled = false;
while(1) {
// do your other main loop stuff...
button_press_handled = button_press_handled && button_is_pressed;
if ( !button_press_handled && button_is_pressed ) {
// Handle press of the button
// ...
// Note that we handled the event for now:
button_press_handled = true;
}
}
volatile bool buttonDirty = false;
void setup() {
attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPress, FALLING);
}
void loop() {
while(1){
readButtons();
}
}
void buttonPress(){
if(buttonDirty) return;
buttonDirty = true;
}
void readButtons(){
if(!buttonDirty) return;
delay(100);
...........
}