2

我在 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 文档页面,上面写着

“由于 delay() 需要中断才能工作,因此如果在 ISR 内调用它将无法工作。”

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

4

4 回答 4

3

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 ms)时才会更新。

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

另一方面,如果您的输入对您的设备构成严重威胁(例如终点站),您需要尽快回答。如果这是您的情况,您在回答之前要等待 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 必须尽可能短,因为当微控制器位于其中之一时,它会对其他所有内容视而不见

于 2015-10-27T10:12:37.107 回答
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;
        }
    }
}
于 2015-10-27T00:53:12.477 回答
0

比使用时间戳更容易

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

}
于 2019-09-16T20:02:54.200 回答
0

从您的去抖动代码来看,您似乎可以节省 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;
  }
}
于 2015-10-30T16:21:29.717 回答