Отправка последовательных данных в прерывании

Используется микросхема ATMEGA328-P с тумблером, подключенным к контакту 8, и переключателем мгновенного действия, подключенным к контакту 9. Моя цель — отправить по последовательному порту следующую информацию:

  1. Статус контакта 8, каждую секунду.
  2. Подсчитайте количество нажатий на контакт 9, отправляя значение по мере увеличения счетчика

Я понимаю, что для достижения пункта 2 мне нужно будет использовать прерывания, но я застрял в том, как я пытаюсь их использовать.

Мне знаком простой стиль следующего кода:

  1. В цикле...
  2. Опрос статуса ввода
  3. Отправлять статус по серийному номеру

Код А:

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 для целей тестирования. Следующий фрагмент пытается добиться следующего:

  1. Прикрепите прерывание к контакту 9 при повышении напряжения
  2. Функция цикла не нужна (?)
  3. В функции обратного вызова прерывания...
  4. Увеличить счетчик
  5. Отправить значение счетчика по серийному номеру

Код Б:

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 из функции обратного вызова не разрешено. Это предел моих знаний в настоящее время, я надеюсь, что кто-то может мне помочь.

, 👍4


2 ответа


Лучший ответ:

8

Вы не можете использовать 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++;
}
,

Могу я спросить, какой параметр 0 в attachInterrupt должен быть установлен? Вы произвольно установили 0? Я использую «9», так как это контакт, к которому подключен переключатель, но использование этого метода по-прежнему не вызывает обратный вызов., @Greg

Я закодировал это для Uno. Контакт 2 — прерывание 0. Контакт 3 — прерывание 1. Выберите правильные контакты для выбранной платы., @Majenko

Ваш ответ прямо в точку, спасибо, что сделали его простым для меня., @Greg


1

Проблема

По сути, 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) , чтобы переопределить поведение по умолчанию.

Конечно, вы должны быть осторожны при переопределении поведения по умолчанию --- это не просто так!

Для получения дополнительной информации о работе с прерываниями я рекомендую прочитать эту документацию.

,