Вопрос был о том: Как выполняется обработчик, зарегистрированный при помощи attachInterrupt(). (сколько уже можно повторять - читайте исходный пост!)
Данивопрос.
ИИ:
Реализация функции attachInterrupt() зависит от архитектуры микроконтроллера (AVR для Uno/Nano, SAM для Due, ESP32 и т.д.), так как она напрямую работает с регистрами процессора.
Ниже приведены примеры исходного кода для двух самых популярных платформ.
1. Архитектура AVR (Arduino Uno, Nano, Mega)
В классических платах код находится в файле WInterrupts.c. Здесь функция сопоставляет номер прерывания с пользовательской функцией и настраивает регистры управления прерываниями (EICRA, EIMSK).
Код: Выделить всё
// Упрощенный фрагмент из WInterrupts.c для AVR
void attachInterrupt(uint8_t interruptNum, void (*userFunc)(void), int mode) {
if(interruptNum < EXTERNAL_NUM_INTERRUPTS) {
// Сохраняем указатель на функцию пользователя в массив колбэков
intFunc[interruptNum] = userFunc;
// Настройка режима (Low, Change, Falling, Rising) через регистры процессора
switch (interruptNum) {
case 0:
EICRA = (EICRA & ~((1 << ISC00) | (1 << ISC01))) | (mode << ISC00);
EIMSK |= (1 << INT0); // Разрешаем внешнее прерывание 0
break;
case 1:
EICRA = (EICRA & ~((1 << ISC10) | (1 << ISC11))) | (mode << ISC10);
EIMSK |= (1 << INT1); // Разрешаем внешнее прерывание 1
break;
// ... и так далее для других прерываний
}
}
}
2. Архитектура SAM (Arduino Due)
На более мощных платах код сложнее, так как прерывания могут быть на любом пине. Файл также называется WInterrupts.c.
Код: Выделить всё
// Фрагмент из WInterrupts.c для Arduino Due (SAM)
void attachInterrupt(uint32_t pin, void (*callback)(void), uint32_t mode) {
// Инициализация контроллера прерываний, если еще не сделана
static int enabled = 0;
if (!enabled) { __initialize(); enabled = 1; }
// Получаем информацию о порте и маске пина
Pio *pio = g_APinDescription[pin].pPort;
uint32_t mask = g_APinDescription[pin].ulPin;
uint32_t pos = 0;
// Определяем позицию пина для сохранения колбэка
for (uint32_t t = mask; t > 1; t >>= 1, pos++);
// Сохраняем функцию в массив для соответствующего порта (PIOA, PIOB и т.д.)
if (pio == PIOA) callbacksPioA[pos] = callback;
if (pio == PIOB) callbacksPioB[pos] = callback;
// Настройка аппаратного режима (FALLING, RISING и т.д.)
if (mode == CHANGE) {
pio->PIO_AIMDR = mask; // Отключаем доп. режимы для детекции обоих фронтов
} else {
pio->PIO_AIMER = mask; // Включаем расширенный режим
// Выбор конкретного типа: LOW, HIGH, FALLING или RISING
// через регистры PIO_LSR, PIO_ESR и др.
}
// Включаем прерывание на данном пине
pio->PIO_IER = mask;
}
/ИИ
Т.е. аппаратно ставится вектор и добавляется ссылка на обработчик.
----------
ИИ
2. "Мегаобработчики" (ISR макросы)
В AVR прерывания обрабатываются макросом ISR(). Для каждого внешнего прерывания (INT0, INT1 и т.д.) прописан такой блок. Он и является мостом между «железом» и вашим кодом:
Код: Выделить всё
// Обработчик для прерывания №0 (обычно пин 2 на Uno)
ISR(INT0_vect) {
if(intFunc[0]) { // Проверяем, назначил ли пользователь функцию
intFunc[0](); // ВЫЗОВ ВАШЕЙ ФУНКЦИИ
}
}
// Обработчик для прерывания №1 (обычно пин 3 на Uno)
ISR(INT1_vect) {
if(intFunc[1]) {
intFunc[1](); // ВЫЗОВ ВАШЕЙ ФУНКЦИИ
}
}
Как это работает пошагово:
Событие: Вы нажали кнопку на пине 2.
Железо: Процессор AVR видит сигнал, приостанавливает loop() и смотрит в таблицу векторов.
Вектор: Таблица говорит: "При событии на INT0 прыгай на адрес функции ISR(INT0_vect)".
Мегаобработчик: Выполняется код внутри ISR(INT0_vect). Он заглядывает в массив intFunc под индексом 0.
Вызов: Если там лежит адрес вашей функции (который вы передали через attachInterrupt), процессор переходит к её выполнению.
Возврат: После завершения вашей функции и выхода из ISR, процессор возвращается в loop() ровно в то место, где прервался.
Почему это сделано именно так?
Почему нельзя сразу записать вашу функцию в таблицу векторов?
Таблица векторов неизменна: Она прошивается в память программ (Flash) и не может меняться "на лету" во время работы программы.
Гибкость: Благодаря массиву указателей в оперативной памяти (RAM), вы можете в любой момент работы программы вызвать detachInterrupt() или переназначить attachInterrupt() на другую функцию.
Примечание: Если вы напишете ISR(INT0_vect) в своем скетче вручную, Arduino IDE выдаст ошибку "multiple definition", так как этот "мегаобработчик" уже определен внутри стандартной библиотеки.
/ИИ