中断 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)
其中 startTime
与 millis()
的类型相同,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
语句进行快速检查。
我有一个 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)
其中 startTime
与 millis()
的类型相同,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
语句进行快速检查。