прерывание с кнопки и ожидание, пока на последовательный порт 1 поступит сообщение
Я немного растерян, потому что учусь использовать прерывания на Arduino. Я создаю программу для считывания RFID-кода, который приходит, если RFID-передатчик находится близко к антенне (маленькая антенная плата RFID). У меня есть два режима работы: один, в котором я связываю свою плату с RFID, поэтому я буду действовать только там, где мой RFID-код идентифицирован (я сохраняю RFID-код на плате). и другой режим, в котором я считываю все RFID-коды и сохраняю их.
У меня есть один шаг конфигурации, где если пользователь нажимает кнопку, я хочу, чтобы светодиод мигал, чтобы указать, что мы ждем представления кода RFID для привязки к плате. Я пытаюсь создать код, и у меня возникают некоторые проблемы с выходом из состояния, когда я жду кода RFID (serial1).
Вот мой код:
#include <Wire.h>
//const int reception_rfid = 15;
const int btnAssociation = 2; // кнопка для индикации включения устройства RFID (прерывание)
const int SwitchModeConfig = 4;
const int LedAssociationEnCours = A0;
int compteurLedAsso;
const int BUFFER_SIZE_RFID = 13;
char buf[BUFFER_SIZE_RFID];
void setup() {
Wire.begin(8); // присоединяемся к шине i2c с адресом #8
Wire.onRequest(requestEvent); // регистрируем событие
Serial.begin(9600);
Serial1.begin(9600);
pinMode(SwitchModeConfig, INPUT_PULLUP);
pinMode(btnAssociation, INPUT_PULLUP);
pinMode(LedAssociationEnCours, OUTPUT);
ConfigurationAssociation();
digitalWrite(LedAssociationEnCours, HIGH);
}
void loop() {
delay(100);
}
void ConfigurationAssociation(){
int stateSwitch = digitalRead(SwitchModeConfig);
if(stateSwitch == LOW){
attachInterrupt(digitalPinToInterrupt(btnAssociation), InterruptAssociation, RISING);
Serial.println("Mode association");
}else{
detachInterrupt(digitalPinToInterrupt(btnAssociation));
Serial.println("Mode aleatoire");
}
}
void InterruptAssociation(){
compteurLedAsso = 0;
Serial.println("Ouverture de l'Association d'un code RFID");
while (Serial1.available() == 0 && compteurLedAsso <= 600)
{
// мигаем красным светодиодом на A0
digitalWrite(LedAssociationEnCours, LOW);
delay(5000);
digitalWrite(LedAssociationEnCours, HIGH);
delay(5000);
compteurLedAsso++;
Serial.println(compteurLedAsso);
}
if(Serial1.available() > 0){
int codeRFID = Serial1.readBytes(buf, BUFFER_SIZE_RFID);
Serial.println("CodeRFID: ");
for(int i = 0; i < codeRFID; i++)
Serial.print(buf[i]);
}
//Serial.println(compteurLedAsso);
//compteurLedAsso++;
Serial.println("Fin de l'attente code RFID");
}
void requestEvent() {
}
Первая проблема: я знаю, что когда мы находимся внутри прерывания, мы не можем считать время, прошедшее некоторое время, я хотел бы, чтобы светодиод мигал 2 минуты: ожидание представления RFID-кода для сохранения, и как только 2 минуты пройдут, я выхожу из прерывания. Как заставить ждать 2 минуты RFID-код на порту serial1 внутри прерывания? моя приемная плата RFID работает нормально, а TX подключен к RX1 (контакт 19) Arduino mega (я проверяю на другом коде, где работает чтение RFID)
Вторая проблема: когда моя плата находится внутри прерывания, которое поступает от кнопки (подключенной к контакту 2), я всегда остаюсь в цикле while, даже когда мой RFID-передатчик считывает RFID-код, я не могу выйти и не знаю почему! Может быть, вы можете помочь разобраться и найти решение, пожалуйста?
@Aeva, 👍2
2 ответа
Лучший ответ:
Дело в том, что внутри ISR (Interrupt Service Routine) не только измерение времени с помощью millis()
не работает, delay()
также не будет работать (он использует тот же механизм прерываний, что и millis()
в фоновом режиме). Как и все остальное, что зависит от их собственного ISR для выполнения — например, получение последовательных данных. Аппаратный последовательный интерфейс может получать отдельные байты, не завися от прерываний, но чтобы поместить эти байты в буфер (и фактически использовать их через Serial
), вам нужно разрешить выполнение других ISR.
НИКОГДА не следует писать ISR, который выполняется долго. Держите его в микросекундах, максимум в очень низком диапазоне миллисекунд. То, как долго вы сможете дойти до этого, пока не увидите негативные эффекты, зависит от других факторов. Вам действительно следует выполнять только минимальную работу в ISR, а остальное в основном коде.
Общая структура кода для этого выглядит следующим образом:
- В вашем основном коде вы не используете никаких вызовов
delay()
. Все хронометрированное должно быть сделано с помощьюmillis()
(как в примереBlinkWithoutDelay
, который поставляется с Arduino IDE), чтобы это не блокировало выполнение. Также никаких длинных циклов внутриloop()
. Убедитесь, что ваша функцияloop()
может свободно и быстро итерироваться. - В глобальной области видимости вы определяете переменную-флаг. Простая однобайтовая переменная, помеченная как
volatile
. - В вашем ISR вы ничего не делаете, кроме установки этой переменной. В зависимости от ситуации вы можете захотеть установить ее в зависимости от цифрового входа через
digitalRead()
, что все еще быстро, так что это нормально. - В вашем основном коде вы используете оператор
if
для проверки установки этой переменной-флага. В большинстве итерацийloop()
это не будет выполнено. Только если переменная-флаг была установлена внутри ISR, она будет выполнена. Это место, где вы пишете код, обрабатывающий событие. В конце блока вам нужно только сбросить переменную-флаг, чтобы вы были готовы к следующему прерыванию.
Это будет выглядеть примерно так:
int button_pin = 2;
volatile bool button_flag = false;
void setup(){
pinMode(button_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(button_pin), button_ISR, RISING);
}
void loop(){
if(button_flag){
// Обработка прерывания здесь
button_flag = false; // Сбросить переменную флага, чтобы быть готовым к следующему прерыванию
}
}
void button_ISR(){
button_flag = true;
}
Обратите внимание, что переменная button_flag
определена как volatile
, поэтому компилятор не кэширует ее значение в каком-либо регистре, а вместо этого всегда считывает ее фактическое текущее значение (не оптимизируя его).
В вашем коде вам, вероятно, следует использовать другую концепцию кодирования, называемую конечным автоматом (FSM). Это самый простой способ реализовать код с различными состояниями/стадиями, сохраняя при этом его неблокируемость. Вы можете почитать о FSM в Интернете (или, например, в моем ответе на этот вопрос).
Суть в том, что вы используете переменную, которая представляет состояние, в котором в данный момент находится ваш код. В loop()
вы затем выполняете код только для текущего состояния. Изменение в другое состояние осуществляется путем установки переменной состояния. Состояние, которое требуется вашему текущему коду, вероятно, CHECK_FOR_SINGLE_RFID_TAG
и ASSOCIATE_SINGE_RFID_TAG
(хотя вы можете назвать их так, как хотите). Простой FSM с этим может выглядеть следующим образом:
#define CHECK_FOR_SINGLE_RFID_TAG 0
#define ASSOCIATE_SINGLE_RFID_TAG 1
// однобайтовая переменная, поэтому нам не нужно беспокоиться об атомарных чтениях/записях
uint8_t state = CHECK_FOR_SINGLE_RFID_TAG;
void loop(){
switch(state){
case CHECK_FOR_SINGLE_RFID_TAG:
if(Serial1.available()){
// здесь считываем Serial1, сверяем идентификатор RFID-метки с сохраненным
// и выполнить код соответствующим образом
}
break;
case ASSOCIATE_SINGLE_RFID_TAG:
if(Serial1.available()){
// здесь считываем Serial1 и сохраняем новый идентификатор RFID-метки в переменной
// затем сбросьте переменную состояния, чтобы автоматически вернуться в режим проверки
// после регистрации новой RFID-метки
state = CHECK_FOR_SINGLE_RFID_TAG;
}
// Используйте идиому BlinkWithoutDelay для неблокируемого мигания светодиода
// что-то вроде
// если (millis() - led_timestamp > led_interval) {
// digitalWrite(led, !digitalRead(led));
// }
break;
}
}
Видите, как мы определяем возможные состояния, сохраняем текущее в переменной и действуем в соответствии с ним внутри loop()
? Эта концепция действительно мощная, поэтому ее определенно стоит изучить.
Примечание: я использовал переменную типа uint8 _t
(один байт), чтобы сделать это прерывание безопасным (вы можете прочитать об этом, выполнив поиск по запросу arduino interrupt atomic read
или arduino interrupt safe variable
) для следующей части и определений, чтобы дать каждому значению читаемое имя. Вы можете использовать enum
для этого, хотя во время написания этого я наткнулся на размер enum
, который, кажется, составляет 16 бит на AVR (например, Arduino Mega), если вы не используете определенный флаг компилятора, согласно этой статье в блоге. Это объяснение сейчас не так уж и важно для вас, но я также хотел объяснить, почему здесь не используются перечисления.
Теперь мы можем объединить это с нашим кодом прерывания выше. В этом случае нам больше не нужна отдельная переменная флага. Мы можем напрямую установить переменную state
внутри ISR:
#define CHECK_FOR_SINGLE_RFID_TAG 0
#define ASSOCIATE_SINGLE_RFID_TAG 1
volatile uint8_t state = CHECK_FOR_SINGLE_RFID_TAG;
int button_pin = 2;
void setup(){
pinMode(button_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(button_pin), button_ISR, RISING);
Serial.begin(115200);
Serial1.begin(115200);
}
void button_ISR(){
state = ASSOCIATE_SINGLE_RFID_TAG;
}
void loop(){
switch(state){
case CHECK_FOR_SINGLE_RFID_TAG:
if(Serial1.available()){
// здесь считываем Serial1, сверяем идентификатор RFID-метки с сохраненным
// и выполнить код соответствующим образом
}
break;
case ASSOCIATE_SINGLE_RFID_TAG:
if(Serial1.available()){
// здесь считываем Serial1 и сохраняем новый идентификатор RFID-метки в переменной
// затем сбросьте переменную состояния, чтобы автоматически вернуться в режим проверки
// после регистрации новой RFID-метки
state = CHECK_FOR_SINGLE_RFID_TAG;
}
// Используйте идиому BlinkWithoutDelay для неблокируемого мигания светодиода
// что-то вроде
// если (millis() - led_timestamp > led_interval) {
// digitalWrite(led, !digitalRead(led));
// }
break;
}
}
Вы можете видеть, что button_ISR()
очень маленький, поэтому очень быстрый. И наш loop()
также работает без блокировки, поэтому его следующая итерация будет очень быстрой, и затем он выполнит состояние, которое мы установили в ISR.
Примечание: при чтении из Serial
убедитесь, что вы не используете функции, которые блокируют на долгое время. Например, Serial.readString()
займет 1 с для выполнения (так как это тайм-аут по умолчанию), что заблокирует неблокируемый код (нехорошо).
Ответ Крисла содержит много отличных моментов, и я настоятельно рекомендую вам изучите его подробно. Есть только несколько моментов, которые я бы лично добавил или изменить:
- ISR, вероятно, не должен длиться более нескольких десятков микросекунд, и много плохих вещей может произойти во время 1 мс ISR (пропущенный тактовый сигнал) тики, потерян последовательный ввод...)
- итерация
loop()
не должна занимать больше нескольких миллисекунд - нет смысла использовать прерывание для чтения кнопки: пользователь
будет удерживать кнопку нажатой в течение многих миллисекунд, и у вас будет
много возможностей для его перехвата в
loop()
; прерывания приносят сложность, которая имеет смысл только тогда, когда вам приходится иметь дело с время менее миллисекунды - не беспокойтесь о размере
перечисления
, если только вам не нужно беспокоиться о субмикросекундные тайминги
Теперь, мой взгляд на эту проблему. Первый шаг — определить состояние
машина. Я могу назвать состояния НОРМАЛЬНОЕ
и АССОЦИАЦИЯ
.
переходы будут такими:
НОРМАЛЬНОЕ
→АССОЦИАЦИЯ
при обнаружении нажатия кнопкиАССОЦИАЦИЯ
→НОРМАЛЬНАЯ
при считывании RFID-кода или после тайм-аут
Моя предварительная реализация:
// Попытка прочитать RFID без блокировки.
// Возвращает найденный код RFID как указатель на буфер с нулевым завершением,
// или nullptr, если в данный момент RFID недоступен.
char *read_rfid() {
// Реализация оставлена в качестве упражнения читателю.
}
void loop() {
static enum {NORMAL, ASSOCIATION} state;
static uint32_t started_association; // время начала ассоциации
char *rfid = read_rfid();
switch (state) {
case NORMAL:
if (rfid) {
Serial.print("Read RFID code: ");
Serial.println(rfid);
}
if (digitalRead(btnAssociation) == LOW) {
Serial.println("Starting association");
mode = ASSOCIATION;
started_association = millis();
}
break;
case ASSOCIATION:
if (rfid) {
assocate_rfid(rfid);
Serial.println("Association successful, code: ");
Serial.println(rfid);
mode = NORMAL;
}
if (millis() - started_association >= assocation_timeout) {
Serial.println("Association timeout");
mode = NORMAL;
}
break;
}
}
Есть несколько вещей, которые я здесь упустил, в частности:
- Мигание светодиода, которое должно быть сделано в стиле Blink Пример без задержки
- Функция
read_rfid()
, которая считывает последовательный порт в неблокирующий мод. Для этого вам понадобится правило для решение о том, когда сообщение завершено. Это может быть сообщение терминатор (если читатель его отправляет) или тайм-аут.
Наконец, я бы порекомендовал вам прочитать пару записей в блоге Majenko:
- Конечный автомат — это руководство по реализации конечные автоматы
- Чтение последовательного порта на Arduino научит вас читать последовательный порт в неблокируемом режиме.
- Отсутствующие буферы с последовательной связью
- Как разделить входящую строку?
- Какова максимальная длина провода для последовательной связи между двумя Arduino?
- Последовательная связь между двумя Arduino (запрос и получение)
- Не нашел датчик отпечатков пальцев :( Arduino Mega 2560 Adafruit Fingerprint Sensor
- Отправка последовательных данных в прерывании
- Выводы прерываний Arduino Mega 2560 и отображение портов с помощью поворотного энкодера
- Серийное прерывание