Прерыване PCINT для arduino

Речь пойдет про использования Pin Change Interrupt (PCINT) для платы ардуино на основе Atmel ATmega328. Но статья так же справедлива и для всего семейства AVR контроллеров.

Термины

  • SREG (AVR Status Register) — Основной регистр статусов процессора AVR
  • SREG I-bit: Global Interrupt Enable — Бит 7 регистра SREG: включение прерываний процессора AVR. Если этот бит сброшен, то никакие прерывания не вызываются, независимо от других локальных настроек прерываний.
  • PCICR (Pin Change Interrupt Control Register) — Регистр управления PCINT прерываниями. биты 0..2 отвечают за активацию PCI0..2 групп прерываний.
  • PCIFR (Pin Change Interrupt Flag Register) — Регистр флагов сработавших прерываний PCINT. 0..2 биты выставляются, если было изменения на любом PINT из портов групп PCINT0..2
  • PCMSK (Pin Change Mask Register) 0..2 — Регистр маскировки прерываний каждого из портов для групп PCI0..2. На каждый порт группы отводится бит соответствующего регистра. Младший бит маскирует порт с меньшим номером.
  • EICRA (External Interrupt Control Register A) — Регистр управления внешними прерываниями, содержит биты управления чувствительностью прерываний INT: Любой переход между уровнями, переход на верхний уровень, на нижний, низкий уровень.
  • EIMSK (Extrnal Interrupt Mask Register) — Регистр маскировки внешних прерываний INT. Включает/выключает по отдельности прерывания INT0, INT1
  • EIFR (Extrnal Interrupt Flag Register) — Содержит флаги сработавших прерываний INT0, INT1. Соответствующие флаги выставляется перед вызовом процедуры обработки INT0 или INT1 и автоматически обнуляется после ее завершения.
  • PINx — Регистр значений на выводах микроконтроллера. х — значение порта: B, C, D. Каждый бит регистра означает уровень сигнала на соответствующем биту пине.

Введение

ATmega328 имеет два вывода поддерживающих INT-прерывания и 24 поддерживающих PCINT. Т.е. PCINT поддерживают почти все цифровые и аналоговые выводы ардуины в отличии от INT. Оба типа прерываний срабатывают даже если они сконфигурированы на выходы вывода.

Разница между этими типами прерывания в том что обработчик INT можно навесить индивидуально на любой INT вывод, тогда как PCINT выводы объединены в группы и изменение по любому пину группы вызовет общее для группы прерывание PCI.

ATmega328 поддерживает три такие группы:

  • PCI0: PCINT0..7, что соответствует пинам D08 — D13
  • PCI1: PCINT8..14, A0 — A5
  • PCI2: PCINT16..23, RX,TX, D02 — D07

Пины A6, A7 — сугубо аналоговые и не имеют дополнительных функций, включая PCINT
PCINT14 — не имеет ножки на микросхеме ATmega328, но может иметь на других моделях.
D02, D03 — могут вызывать как INT, так и PCINT прерывания.

Конфигурация

Рассмотрим как сконфигурировать PCINT на примере ноги D04.

Для того чтобы заработал обработчик прерываний PCINT необходимо выполнить следующие шаги:

  1. Определить группу прерываний для заданного пина. Для D04 это группа 2 (PCINT2)
  2. Добавить обработчик прерываний для группы.
// Для D04 используется прерывание группы PCINT2
ISR(PCINT2_vect) {
// функция будет вызвана для любого изменения уровня пина D04,
// впрочем как и для любого другого пина группе,
// если это указано в PCIMSK2 
}
  1. Активировать прерывание для группы в регистре управления PCICR
    PCICR |= 1 << 2
  2. Активировать прерывание для конкретного пина в группе. Для D04 это бит 4 в группе 2. Ставим его в единичку.
    PCIMSK2 |= 1 << 4
  3. Разрешить прерывания микроконтроллера.
    SREG |= 1 << SREG_I; // бит 7

После этого любое изменение уровня на пине D04 должно приводить в вызову функции ISR(PCINT2_vect).

Но среда arduino содержит макросы упрощающие вычисление групп и битов для настройки PCINT. Это позволяет создавать более читаемый и переносимый код.

Для примера рассмотри следующий код:

// Библиотека TaskManagerIO поможет организовать
// очередь для обработки прерываний.
// https://www.thecoderscorner.com/products/arduino-libraries/taskmanager-io/
#include <TaskManager.h>

// Кнопка должна быть подключена к выводу D04.
#define BUTTON_PIN 4

/**
 * Event class to marshal interupt to the loop thread.
 */
class PciEvent: public Executable {
    const uint8_t _Pcifr;
public:
    PciEvent(uint8_t aPcifr): _Pcifr(aPcifr) {}
    void exec() {
        Serial.print(F("PIND: 0b"));
        Serial.println(_Pcifr, 2);
    }
};

/**
 * Обработчик прерываний группы PCINT2 в которую входит D04.
 * Pins 0 - 7
 */
ISR(PCINT2_vect) {
    uint8_t pins = PIND; // Port D pins 0 - 7.
    // Формируем событие и отправляем его на обработку в функции loop().
    taskManager.scheduleOnce(0, new PciEvent(pins), TIME_MICROS, true);
}

void setup() {
    // Настроить серийный порт для передачи сообщений на PC на скорости 9600 бот.
    Serial.begin(9600);

    // Перeключить пин кнопки на вход с резистором подтяжки.
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    // Включить PCINT для группы порта BUTTON_PIN (4) D0-D7 - группа 2
    PCICR |= bit(digitalPinToPCICRbit(BUTTON_PIN));  // PCICR |= 1 << 2
    // Включить PCINT для пина внутри группы (пин 4)
    *digitalPinToPCMSK(BUTTON_PIN) |= bit(digitalPinToPCMSKbit(BUTTON_PIN)); // PCIMSK2 |= 1 << 4
    // Включить прерывания глобально.
    sei(); // SREG |= 1 << 7  или SREG |= bit(SREG_I)
}

void loop() {
    taskManager.runLoop();
}

При каждом нажатии на кнопку программа выводит значение регистра PIND в бинарном виде. бит 4 (5й справа) этого регистра соответствует высокому или низкому уровню на пине D04. Остальные биты могут произвольно меняться согласно значениям на советующих пинах. В частности биты 0 и 1 соответствуют ногам RX, TX поэтому в примере вывода программы они принимают различные значения:

# Кнопка отжата
# резистор подтяжки дает на ногу высокий уровень - бит 4 = 1
PIND: 0b10011
# Кнопка нажата и выдает на ногу низкий уровень - бит 4 = 0
PIND: 0b11
PIND: 0b10011
...
PIND: 0b11
PIND: 0b10011

Но рассмотрим код программы внимательней.

ISR(PCINT2_vect) {
    uint8_t pins = PIND; // Port D pins 0 - 7.
    // Формируем событие и отправляем его на обработку в функции loop().
    taskManager.scheduleOnce(0, new PciEvent(pins), TIME_MICROS, true);
}

Функция ISR вызывается для обработки прерывания и она должна быть максимально быстрой. Пока она не завершится, другие прерывания не будут обрабатываться, в том числе те, что изменяют счетчик функции millis(). Поэтому, в примере значение регистра PIND, просто передается в очередь для последующей обработки уже в основном цикле программы.

    // Включить PCINT для группы порта BUTTON_PIN (4) D0-D7 - группа 2
    PCICR |= bit(digitalPinToPCICRbit(BUTTON_PIN));  // PCICR |= 1 << 2

Здесь используется макрос digitalPinToPCICRbit, объявленный в Arduino.h, который возвращает номер группы прерываний для указанного номера порта. Далее это значение используется для активации группы в регистре управления PCICR.

 // Включить PCINT для пина внутри группы (пин 4)
    *digitalPinToPCMSK(BUTTON_PIN) |= bit(digitalPinToPCMSKbit(BUTTON_PIN)); // PCIMSK2 |= 1 << 4

Здесь используются сразу два макроса:

digitalPinToPCMSK — возвращает ссылку на нужный регистр маскировки прерываний PCMSK0, PCMSK1 или PCMSK2, для включения прерываний для указанного пина.

digitalPinToPCMSKbit — позволяет получить номер соответствующего бита.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *