Речь пойдет про использования 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 необходимо выполнить следующие шаги:
- Определить группу прерывания да заданного пина. Для D04 это группа 2 (PCINT2)
- Добавить обработчик прерываний для группы.
// Для D04 используется прерывание группы PCINT2 ISR(PCINT2_vect) { // функция будет вызвана для любого изменения уровня пина D04 }
- Активировать прерывание для группы в регистре управления PCICR
PCICR |= 1 << 2 - Активировать прерывание для конкретного пина в группе. Для D04 это бит 4 в группе 2.
PCIMSK2 |= 1 << 4 - Разрешить прерывания микроконтроллера.
SREG |= 1 << SREG_I; // бит 7
После этого любое изменение уровня на пине D04 должно приводить в вызову функции ISR(PCINT2_vect).
Но среда arduino содержит макросы упрощающие вычисление групп и битов для конфигурирования PCINT. Это позволяет создавать читаемый и переносимый код.
// Библиотека TaskManagerIO поможет организовать // очередь для обработки прерываний. #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