Отправка последовательных данных в прерывании
Используется микросхема ATMEGA328-P с тумблером, подключенным к контакту 8, и переключателем мгновенного действия, подключенным к контакту 9. Моя цель — отправить по последовательному порту следующую информацию:
- Статус контакта 8, каждую секунду.
- Подсчитайте количество нажатий на контакт 9, отправляя значение по мере увеличения счетчика
Я понимаю, что для достижения пункта 2 мне нужно будет использовать прерывания, но я застрял в том, как я пытаюсь их использовать.
Мне знаком простой стиль следующего кода:
- В цикле...
- Опрос статуса ввода
- Отправлять статус по серийному номеру
Код А:
const int SWITCH_1 = 8;
const int READ_INTERVAL = 1000;
int val_s1 = 0;
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(SWITCH_1, INPUT);
digitalWrite(S1, HIGH);
}
void loop() {
val_s1 = digitalRead(SWITCH_1);
Serial.print("S1:");
Serial.print(val_s1);
Serial.print("\n");
digitalWrite(LED_BUILTIN, HIGH);
delay(10);
digitalWrite(LED_BUILTIN, LOW);
delay(READ_INTERVAL);
}
Представляя концепцию прерываний, я попытался сохранить упрощенный стиль кода A для целей тестирования. Следующий фрагмент пытается добиться следующего:
- Прикрепите прерывание к контакту 9 при повышении напряжения
- Функция цикла не нужна (?)
- В функции обратного вызова прерывания...
- Увеличить счетчик
- Отправить значение счетчика по серийному номеру
Код Б:
void c1_rise();
const int COUNTER_1 = 9;
const int READ_INTERVAL = 1000;
volatile int val_c1 = 0;
void setup() {
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(COUNTER_1, INPUT_PULLUP);
attachInterrupt(COUNTER_1, c1_rise, RISING);
}
void loop() {
}
void c1_rise() {
val_c1 ++;
Serial.print("C1:");
Serial.print(val_c1);
Serial.print("\n");
}
Я пытался понять, где я ошибаюсь, но я застрял и решил задать свои вопросы здесь.
- Наблюдение: я ожидаю, что некоторые данные будут отправлены по последовательному порту при нажатии кнопки, но на самом деле ничего заметного не происходит.
- Проблема 1. Это может быть связано с тем, что прерывание вообще не подключено.
- Проблема 2. Возможно, это связано с тем, что я неправильно понимаю ограничения функций обратного вызова прерывания.
При попытке решить проблему 1 документация Arduino подразумевает, что используйте функцию attachInterrupt
, параметр 1 должен быть прерыванием от digitalPinToInterrupt
, но если я использую эту функцию, я получаю следующее сообщение об ошибке: ошибка: 'digitalPinToInterrupt' не был объявлен в этой области.
Прочитав о том, как ведут себя прерывания, я обнаружил проблему 2: кажется, что хотя я объявил целое число как volatile, использование Serial из функции обратного вызова не разрешено. Это предел моих знаний в настоящее время, я надеюсь, что кто-то может мне помочь.
@Greg, 👍4
2 ответа
Лучший ответ:
Вы не можете использовать Serial внутри прерывания. Передача Serial зависит от доступности прерываний, а внутри прерывания их нет.
Вся последовательная связь должна осуществляться из loop()
.
Поэтому вам нужно просто подсчитать количество переключений и проверить, изменилось ли это значение в вашем цикле.
volatile uint32_t toggles = 0;
uint32_t old_toggles = 0;
uint32_t ts = 0;
void setup() {
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
attachInterrupt(0, toggle, RISING);
}
void loop() {
if (toggles != old_toggles) {
old_toggles = toggles;
Serial.print("C1: ");
Serial.println(toggles);
}
if (millis() - ts >= 1000) {
ts = millis();
Serial.print("S1: ");
Serial.println(digitalRead(3));
}
}
void toggle() {
toggles++;
}
Проблема
По сути, attachInterrupt использует макрос с именем ISR
(расшифровывается как Interrupt Service Routine) для регистрации предоставленной пользователем функции для обработки внешнего события прерывания.
Проблема, с которой вы столкнулись, заключается в том, что ISR
также оборачивает вызов вашей функции специальными инструкциями для очистки глобального флага прерывания вашего микроконтроллера и его повторной установки после возврата функции. Это хорошая идея, и почти всегда то, что вам нужно, потому что она предотвращает возможность бесконечно рекурсивных прерываний.
Однако такое поведение будет препятствовать правильной работе Serial
, поскольку для выполнения своей задачи он использует включенные прерывания.
Решение
Хотя это поведение по умолчанию, ничто не мешает вам снова разрешить прерывания внутри вашей функции c1_rise
, например:
void c1_rise() {
val_c1 ++;
interrupts();
Serial.print("C1:");
Serial.print(val_c1);
Serial.print("\n");
}
В качестве альтернативы, но, возможно, более сложной, вы можете использовать макрос ISR
напрямую, включив avr/interrupt.h
, и использовать ISR(vect, NO_BLOCK)
, чтобы переопределить поведение по умолчанию.
Конечно, вы должны быть осторожны при переопределении поведения по умолчанию --- это не просто так!
Для получения дополнительной информации о работе с прерываниями я рекомендую прочитать эту документацию.
- Печать неизвестного текста на последовательном мониторе
- Серийное прерывание
- Почему люди используют 115200 вместо 9600?
- Последовательная связь ESP8266 с ATMega328P
- Ошибка 'Serial' was not declared in this scope
- Ардуино как ISP с serial monitor для ATmega328
- Как получить ненулевой выход из HX711 и ячейки загрузки?
- Serial.availableForWrite против Serial.flush
Могу я спросить, какой параметр 0 в
attachInterrupt
должен быть установлен? Вы произвольно установили0
? Я использую «9», так как это контакт, к которому подключен переключатель, но использование этого метода по-прежнему не вызывает обратный вызов., @GregЯ закодировал это для Uno. Контакт 2 — прерывание 0. Контакт 3 — прерывание 1. Выберите правильные контакты для выбранной платы., @Majenko
Ваш ответ прямо в точку, спасибо, что сделали его простым для меня., @Greg