Процедура прерывания Arduino для запуска процесса с медленной задержкой

У меня есть Arduino, который в основном выполняет сбор данных и отправляет его в ESP8266 через последовательный порт. Как вы, наверное, знаете, последовательное общение с ESP не является быстрым и зависит от большого количества ожидания. У меня есть кнопка, и я хочу немедленно прекратить сбор или отправку данных и открыть дверь. Открытие двери занимает около 30 секунд. Какой лучший способ сделать это?

Не полный код, но он выглядит примерно так:
Конечно, это не работает, потому что вы не можете использовать WHILE или DELAY в ISR, но я не знаю, как его реструктурировать.

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

Решение

TL; DR — см. Ответ Ника. 🙂

Без полного кода я могу только догадываться о нескольких вещах:

1) Вы не должны ждать в ISR. Даже звонит millis() не рекомендуется, так как это зависит от вызова ISR Timer0, который будет предотвращен, пока вы находитесь в 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() переворачивается с 232-1 в 0.

4) Похоже, вы знаете, как «блокировать», пока не истечет определенное время: while петля будет держать ваш эскиз в этой точке. Если вы просто измените его на if Заявление, Arduino может продолжать свой путь, чтобы справиться с другими вещами.

Так как loop происходит так быстро, if Заявление будет проверять время очень часто … если вы delay или заблокировать где-нибудь еще, как wait_for_esp_response, 🙁 То, что в то время как цикл должен измениться на if заявление также. Рутина больше похожа check_for_esp_response,

5) Вы должны отслеживать состояние процесса открытия и закрытия двери. Это Конечный автомат проблема. Ник имеет хорошее описание Вот, тоже. Вы можете использовать enum введите, чтобы определить состояния, в которых может находиться дверь: ЗАКРЫТО, ОТКРЫТО, ОТКРЫТО и ЗАКРЫТО.

Когда нажата кнопка ОТКРЫТЬ, вы можете посмотреть на состояние и посмотреть, стоит ли его открывать. Затем запустите таймер, включите реле и, самое главное, установите состояние на ОТКРЫТО. В следующий раз через loop, вы можете проверить состояние (а switch заявление), а для случая ОТКРЫТИЯ посмотрите на время, чтобы увидеть, было ли оно достаточно длинным. Если он установил состояние на ОТКРЫТО. И так далее.

Если я включу все эти вещи в ваш эскиз, он должен начать выглядеть так:

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 заявления.

2

Другие решения

Откройте дверь в ISR и установите флаг. Также храните время, когда вы открыли его. Обе эти переменные должны быть объявлены volatile,

Тогда в вашем основном цикле посмотрите:

  • Флаг установлен; а также
  • Время вышло

Если это так, закройте дверь (и снимите флажок).


Могу ли я предположить, что установка переменных как «volatile» не позволит компилятору оптимизировать их? Если так, то не могли бы вы объяснить, почему вы считаете это необходимым.

Переменные, измененные внутри ISR, могут изменяться, когда компилятор не ожидает их. С помощью volatile говорит компилятору перезагружать такие переменные из ОЗУ (а не кэшировать их в регистр), чтобы он всегда получал самую последнюю копию.

В качестве примера, скажем, у вас был установлен флаг внутри ISR. И в вашем основном (не ISR) коде у вас было это:

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

Компилятор смотрит на это и думает, что «флаг никогда не изменится» и оптимизирует тест на его изменение. С volatile тем не менее, компилятор держит тест, потому что он должен продолжать перезагрузку flag из оперативки

1

По вопросам рекламы [email protected]