中断arduino例程以运行基于缓慢延迟的进程

时间:2016-03-07 02:05:29

标签: c++ arduino delay interrupt interrupt-handling

我有一个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);
}

2 个答案:

答案 0 :(得分:2)

TL; DR - 见尼克的回答。 : - )

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

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

2)一般来说,ISR应该只做很快的事情...想想微秒。这是几十到几百条指令,可能只是几行代码。即使digitalWrite几乎太慢了。如果还有更多工作要做,您应该只设置volatile中观看的loop标记。然后loop可以完成耗时的工作。

3)计算已用时间必须采用以下格式:

if (millis() - startTime >= DESIRED_TIME)

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

uint32_t startTime;

您可以在适当的时候设置startTime

startTime = millis();

millis() 2 32 -1 翻到 0 时,这可以避免翻转问题。

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

由于loop发生得如此之快,if语句会经常检查时间...除非您delay或阻止其他地方,例如wait_for_esp_response。 :-( while循环也应该变为if语句。例程更像check_for_esp_response

5)您必须跟踪门开启和关闭过程的状态。这是一个Finite-State machine问题。尼克也有很好的描述here。您可以使用enum类型来定义门可以处于的状态:CLOSED,OPENING,OPENED和CLOSING。

按下OPEN按钮后,您可以查看状态并查看是否应该开始打开状态。然后启动计时器,打开继电器,最重要的是,将状态设置为OPENING。下次通过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语句。

答案 1 :(得分:1)

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

然后在你的主循环中看看:

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

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

  

我可以假设将变量设置为“volatile”会阻止编译器对它们进行优化吗?如果是这样,那么你会介意解释为什么你认为这是必要的。

当编译器不期望它们时,在ISR中修改的变量可能会改变。使用volatile告诉编译器从RAM重新加载这些变量(而不是将它们缓存到寄存器中),因此它总是获得最新的副本。

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

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

编译器查看它并认为“好吧,标志永远不会改变”并优化测试它的变化。但是使用volatile,编译器会保留测试,因为它必须不断从RAM重新加载flag