【问题标题】:Debouncing a limit switch in Arduino ISR with delays在 Arduino ISR 中使用延迟去抖动限位开关
【发布时间】:2015-10-26 23:48:49
【问题描述】:

我在 arduino Mega 2650 上连接了一个限位开关,用于运动控制。限位开关的两个常开触点连接到 Arduino 引脚和接地,这样当限位开关接合时,Arduino 引脚会短路到接地。

正如预期的那样,我在此设置中遇到了弹跳问题。我使用 ISR 中的计数器确认了这一点。最后,我编写了以下代码,该代码似乎可以可靠地识别我的限位开关在任何给定时间点是接合还是分离。

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));
}

但是,这是我的问题。我遇到了这个Arduino documentation page,上面写着

"由于delay()需要中断才能工作,所以调用它就不起作用 在 ISR 内部。 "

但是,我确实在 ISR 中使用了delay(),而且它似乎有效,这是怎么回事?我是否有一种情况,目前一切正常,但很容易中断,因为 delay() 函数可能会像文档所说的那样在我身上发生故障?

【问题讨论】:

  • Here is an answer 应该可以解决你所有的问题。
  • 在中断中使用忙等待循环是一个非常糟糕的主意——总是这样!
  • 您应该(也)提供去抖动硬件(RC低通),因为反弹可能太快而无法由µC处理。
  • 开关弹跳会导致许多 MHz 的杂散脉冲,远远超过 IO 的施密特触发器或 FF 或任何指定处理的脉冲。这些脉冲很难在软件中可靠地处理,但可能会导致输入电路或 µC 的其他部分发生一些不希望的操作。首先最好让它们远离 IO 引脚。
  • 嗯,很奇怪。我似乎找不到有关这些“A/C 电气特性”的任何信息。然而,我确实发现50ns(= 20MHz)是中断逻辑保证检测到的最小脉冲长度。无论如何,我不会超过零件的最大值。时钟频率。

标签: arduino embedded interrupt debouncing


【解决方案1】:

TomKeddie 的回答看起来是对的:您不会有任何问题。无论如何,在我看来,您的代码在概念上是错误的,至少有两个原因。现在我将向您解释原因。

有两种输入:一种是您必须立即回答的输入,另一种是您必须回答但不是直接威胁的输入。例如,通常一个安全止动装置属于第一组,因为一旦你击中它,你就需要停止执行器。另一方面,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 毫秒)时才会更新。

这样你就不会用那个任务阻塞你的主程序。

另一方面,如果您的输入对您的设备构成严重威胁(例如终点站),您需要尽快回答。如果是这种情况,您在回答之前要等待 100 毫秒,这与您输入速度的需要相违背。

当然,您不能对这样的输入进行去抖动处理,因为去抖动处理会带来延迟。但是,您可以将一种状态置于另一种状态之上。在连接到地的终端停止情况下,严重的威胁是输入状态为接地时。所以我建议你以这样的方式设置你的变量:

  1. 当引脚断开时,您立即将其设置为 0
  2. 当引脚上升时,您等待 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 必须尽可能短,因为当微控制器位于其中一个时,它就会对其他所有内容视而不见

【讨论】:

  • 感谢您的深刻见解。在您确定的两种情况中,我的设置确实属于第二种情况,即我需要立即快速处理中断。但是,有一个问题。当限位开关脱离时,ISR1 再次被调用,但只有这一次,我不想让它做任何事情。我该如何处理? ISR1 被调用是因为你得到一个 RISING 边,然后是 RISINGFALLING
  • 好吧,在我上传的基本草图中你没有问题,因为lsEngaged 设置为LOW,即使它已经在这种情况下。如果您只想在第一次进入ISR1 时执行某项操作,您可以利用lsEngaged 本身:例如,您可以将函数更改为{ if (lsEngaged) { ... do something just once when it goes down ... } lsEngaged = LOW; } 之类的东西。这样if 中的代码将在lsEngaged 回到true 时执行(在main 循环内)
【解决方案2】:

在 AVR 上,delay() 的实现如下。不涉及中断(micros() 返回 timer0 计数值,yield() 指的是在您的简单草图中不会使用的调度程序)。

我认为该评论是为了可移植性,您使用的环境可以在越来越多的平台上运行。你在 AVR 上做的很好,在其他平台上可能就不行了。

我建议使用简单的 for 循环来旋转等待。 cpu 没有做任何其他事情,除非功耗是一个问题,但这超出了这里的范围。

来自https://github.com/arduino/Arduino/blob/79f5715c21a81743443269a855979a64188c93df/hardware/arduino/avr/cores/arduino/wiring.c

void delay(unsigned long ms)
{
    uint16_t start = (uint16_t)micros();

    while (ms > 0) {
        yield();
        if (((uint16_t)micros() - start) >= 1000) {
            ms--;
            start += 1000;
        }
    }
}

【讨论】:

  • delay() 的实现特定于 Arduino 接线库而不是 AVR。它可以通过多种方式实现。
【解决方案3】:

从您的去抖动代码看来,您可以节省 100 毫秒的反应时间来启动开关。

因此,如果您真的不需要在事件发生后的几微秒内做出反应,请考虑每隔 10 毫秒轮询一次输入(例如从计时器 ISR)。

(使用外部中断只有两个原因:1. 您需要非常快(微秒!)对信号做出反应,或者 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():

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;
  }
}

【讨论】:

    【解决方案4】:

    比使用时间戳更容易

    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);
      ...........
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-17
      相关资源
      最近更新 更多