我有一个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);
}
答案 0 :(得分:2)
TL; DR - 见尼克的回答。 : - )
如果没有完整的代码,我只能猜测一些事情:
1)您不应该在ISR中等待。不鼓励调用millis()
,因为它取决于被调用的Timer0 ISR,只要你在openadoor
ISR中就会被阻止。
2)一般来说,ISR应该只做很快的事情...想想微秒。这是几十到几百条指令,可能只是几行代码。即使digitalWrite
几乎太慢了。如果还有更多工作要做,您应该只设置volatile
中观看的loop
标记。然后loop
可以完成耗时的工作。
3)计算已用时间必须采用以下格式:
if (millis() - startTime >= DESIRED_TIME)
其中startTime
与millis()
的类型相同,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
。