中断 arduino 例程到 运行 一个基于缓慢延迟的进程

Interrupt arduino routine to run a slow delay based process

我有一个 arduino,它主要进行数据收集并通过串口将其发送到 ESP8266。与 ESP 的串行通信并不如您所知的那样快速,并且需要大量等待。我有一个按钮,我想立即停止任何数据收集或发送并让它打开一扇门。开门大约需要 30 秒。执行此操作的最佳方法是什么?

不是完整的代码,但大致如下所示。 当然这不行,因为你不能在 ISR 中使用 WHILE 或 DELAY,但我不知道如何重组它。

attachInterrupt(4 , openadoor, FALLING);

void loop(){
gathersomedata();
senddatatoESP();
if(wait_for_esp_response(2000,"OK")) lightGreenLED();
else lightRedLED();
}  

byte wait_for_esp_response(int timeout, const char* term) {
unsigned long t = millis();
bool found = false;
int i = 0;
int len = strlen(term);
while (millis() < t + timeout) {
    if (Serial2.available()) {
        buffer[i++] = Serial2.read();
        if (i >= len) {
            if (strncmp(buffer + i - len, term, len) == 0) {
                found = true;
                break;
            }
        }
    }
}
buffer[i] = 0;
}


void openadoor(){
while (doortimer + dooropentime >= millis() && digitalRead(openbutton)  == HIGH && digitalRead(closebutton)  == HIGH) {
    digitalWrite(DoorOpenRelay, LOW);
    }
digitalWrite(DoorOpenRelay, HIGH);
}

在ISR中打开门并设置标志。还存储打开它的时间。这两个变量都应声明为 volatile.

然后在主循环中查看是否:

  • 标志已设置;和
  • 时间到了

如果是,关上门(并清除标志)。


May I assume that setting the variables as "volatile" will prevent the compiler optimizing them? If so, then would you mind explaining why you thought this necessary.

在 ISR 中修改的变量可能会在编译器不希望它们发生变化时发生变化。使用 volatile 告诉编译器从 RAM 中重新加载此类变量(而不是将它们缓存到寄存器中),以便它始终获得最新的副本。

举个例子,假设您在 ISR 中设置了一个标志。在你的主要(非 ISR)代码中你有这个:

flag = false;
while (!flag)
  { }  // wait for flag to be set

编译器查看并思考 "well, flag will never change" 并针对它的变化优化测试。但是,对于 volatile,编译器会保留测试,因为它必须继续从 RAM 重新加载 flag

TL;DR - 请参阅 Nick 的回答。 :-)

没有完整的代码,我只能猜测一些事情:

1) 你不应该在 ISR 中等待。甚至不鼓励调用 millis(),因为它取决于调用 Timer0 ISR,只要您在 openadoor ISR 中,它就会被阻止。

2) 一般来说,ISR 应该只做非常快的事情……想想微秒。那是几十到几百条指令,可能只是几行代码。即使 digitalWrite 也几乎太慢了。如果还有更多事情要做,您应该只设置一个在 loop 中监视的 volatile 标志。那么loop就可以完成耗时的工作了。

3)计算经过的时间必须是这种形式:

if (millis() - startTime >= DESIRED_TIME)

其中 startTimemillis() 的类型相同,a uint32_t:

uint32_t startTime;

您在适当的地方设置 startTime

startTime = millis();

这避免了翻转问题,当 millis()232-1 翻转到 时0.

4) 看起来你知道如何 "block" 直到经过一定的时间:while 循环将使你的草图保持在那一点。如果你只是将其更改为 if 语句,Arduino 可以继续处理其他事情。

因为 loop 发生得如此之快,if 语句将非常频繁地检查时间...除非您 delay 或在其他地方阻塞,例如 wait_for_esp_response。 :-( while 循环也应该更改为 if 语句。例程更像是 check_for_esp_response

5) 你得跟踪开门关门过程的状态。这也是一个Finite-State machine problem. Nick has a good description here。您可以使用 enum 类型来定义门可以处于的状态:关闭、打开、打开和关闭。

当打开按钮被按下时,您可以查看状态,看看是否应该开始打开它。然后启动计时器,打开继电器,最重要的是,将状态设置为打开。下次通过loop,可以测试状态(一个switch语句),对于OPENING的情况,看时间是否已经够长了。如果它已将状态设置为 OPENED。等等。

如果我将所有这些东西都融入到你的草图中,它应该开始看起来像这样:

volatile bool doorOpenPressed = false;
volatile bool doorClosePressed = false;

static const uint32_t DOOR_OPEN_TIME  = 30000UL; // ms
static const uint32_t DOOR_CLOSE_TIME = 30000UL; // ms
static const uint32_t DATA_SAMPLE_TIME = 60000UL; // ms

static uint32_t lastDataTime, sentTime, relayChanged;

static bool waitingForResponse = false;
static uint8_t responseLen = 0;

enum doorState_t { DOOR_CLOSED, DOOR_OPENING, DOOR_OPENED, DOOR_CLOSING };
doorState_t doorState = DOOR_CLOSED;

void setup()
{
  attachInterrupt(4 , openadoor, FALLING);
}

void loop()
{
  //  Is it time to take another sample?

  if (millis() - lastDataTime > DATA_SAMPLE_TIME) {
    lastDataTime = millis();
    gathersomedata();

    //  You may want to read all Serial2 input first, to make
    //  sure old data doesn't get mixed in with the new response.
    senddatatoESP();
    sentTime = millis();

    waitingForResponse = true;
    responseLen = 0; // ready for new response
  }

  //  If we're expecting a response, did we get it?

  if (waitingForResponse) {
    if (check_for_esp_response("OK")) {
      // Got it!
      lightGreenLED();
      waitingForResponse = false;

    } else if (millis() - sentTime > 2000UL) {
      // Too long!
      lightRedLED();
      waitingForResponse = false;

    } // else, still waiting
  }

  // Check and handle the door OPEN and CLOSE buttons,
  //   based on the current door state and time

  switch (doorState) {

    case DOOR_CLOSED:
      if (doorOpenPressed) {
        digitalWrite(DoorOpenRelay, LOW);
        relayChanged = millis();
        doorState = DOOR_OPENING;
      }
      break;

    case DOOR_OPENING:
      //  Has the door been opening long enough?
      if (millis() - relayChanged > DOOR_OPEN_TIME) {
        digitalWrite(DoorOpenRelay, HIGH);
        doorState = DOOR_OPENED;

      } else if (!doorOpenPressed && doorClosePressed) {
        // Oops, changed their mind and pressed the CLOSE button.

        // You may want to calculate a relayChanged time that
        //   is set back from millis() based on how long the
        //   door has been opening.  If it just started opening,
        //   you probably don't want to drive the relay for the
        //   full 30 seconds.
           ...
      }
      break;

    case DOOR_OPENED:
      if (doorClosePressed) {
        ...
      }
      break;

    case DOOR_CLOSING:
      if (millis() - relayChanged > DOOR_CLOSE_TIME) {
        ...
      }
      break;
  }
}

void openadoor()
{
  doorOpenPressed = true;
}

bool check_for_esp_response(const char* term)
{
  bool found = false;

  if (Serial2.available()) {
      // You should make sure you're not running off the end
      //   of "buffer" here!
      buffer[responseLen++] = Serial2.read();

      int len = strlen(term);
      if (responseLen >= len) {
          if (strncmp(buffer + responseLen - len, term, len) == 0) {
              found = true;
          }
      }
  }

  return found;
}

关键是你不要在任何地方阻塞或拖延。 loop 被一遍又一遍地调用,它只检查几个变量。大多数时候,无事可做。但有时,它会根据状态或当前时间收集一些数据,发送数据,读取响应,然后打开或关闭门。这些操作不会相互干扰,因为没有阻塞 while 循环,只有使用 if 语句进行快速检查。