Перейти к содержимому

Порты ввода-вывода, прерывания и контроллер прерываний NVIC … ↩

Порты ввода-вывода, прерывания и контроллер прерываний NVIC mod. Четверг, 15 октября 2026 22:55 . См. 7

BeeFC · Урок S5 из 33 · Уровень «Старт»

Чем прерывание отличается от опроса, как настроить внешнее прерывание по фронту сигнала, как распределить приоритеты между обработчиками и как отфильтровать дребезг контактов кнопки. В конце урока задержка обработки прерывания измеряется логическим анализатором.

В этом уроке в BeeFC появляется обработчик линии готовности данных гироскопа. Сам гироскоп будет настроен в уроке P1, но прерывание от него уже проверяется в программной имитации.

Содержание

1. Цели урока

Продолжительность урока — три часа: один час теории и два часа практики. После урока слушатель:

  • выбирает между опросом и прерыванием для конкретного сигнала;
  • настраивает внешнее прерывание по фронту на любом выводе и задаёт его приоритет;
  • безопасно передаёт данные из обработчика прерывания в основную программу;
  • реализует фильтр дребезга контактов как конечный автомат и проверяет его модульными тестами;
  • измеряет задержку обработки прерывания логическим анализатором.
↑ К оглавлению

2. Термины и сокращения

ТерминОпределение
прерываниеприостановка основной программы для выполнения обработчика по событию
обработчик прерыванияфункция, которую ядро вызывает при возникновении события
контроллер вложенных векторных прерываний (NVIC)блок ядра Cortex-M, который разрешает прерывания и выбирает порядок их обслуживания
контроллер внешних прерываний (EXTI)блок STM32, который формирует прерывание по фронту сигнала на выводе
линия готовности данных (DRDY)выход датчика, сообщающий о появлении нового измерения
дребезг контактовмногократное замыкание и размыкание механического контакта в момент переключения
конечный автоматмодель, в которой поведение определяется текущим состоянием и входным событием
критическая секцияучасток кода, на время выполнения которого прерывания запрещены
↑ К оглавлению

3. Опрос и прерывания

Программа может узнать о событии двумя способами. При опросе она периодически читает состояние вывода или флага. При прерывании ядро само приостанавливает текущий код и вызывает обработчик.

ПризнакОпросПрерывание
задержка реакциидо одного периода опросадесятки наносекунд
нагрузка на процессорпостоянная, даже без событийтолько при событии
поведение при дребезгефильтруется естественным образомкаждое переключение вызывает обработчик
пример в BeeFCкнопка, состояние батареилиния готовности данных гироскопа, приём байта

Ядро Cortex-M4 начинает выполнять обработчик через 12 тактов после события, то есть примерно через 71 нс на частоте 168 МГц. Такты ожидания флеш-памяти и выполняющиеся в этот момент обработчики с более высоким приоритетом увеличивают задержку.

↑ К оглавлению

4. Контроллер внешних прерываний

Контроллер EXTI STM32F405 имеет шестнадцать линий для выводов: линия с номером n подключается к выводу с тем же номером в одном из портов. Линия EXTI4 может обслуживать PA4, PB4 или PC4, но только один из них. Выбор порта для линии задаётся в регистрах SYSCFG_EXTICR.

ЛинииОбработчик в таблице векторов
EXTI0–EXTI4отдельный обработчик для каждой линии
EXTI5–EXTI9общий обработчик EXTI9_5_IRQHandler
EXTI10–EXTI15общий обработчик EXTI15_10_IRQHandler
Ограничение при проектировании платы: Два сигнала с одинаковым номером вывода в разных портах не могут одновременно вызывать внешние прерывания. На платах Diatone Mamba F405 линия готовности данных гироскопа подключена к выводу PC4, а выбор гироскопа на шине SPI — к выводу PA4. Конфликта нет, потому что выбор микросхемы прерывания не требует.

Настройка прерывания по фронту

Файл hal/stm32f4/exti_stm32.c

void gyro_drdy_exti_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;            /* SYSCFG выбирает порт для линии EXTI */
    (void)RCC->APB2ENR;

    /* Линия EXTI4 подключается к порту C: регистр EXTICR[1], поле EXTI4. */
    SYSCFG->EXTICR[1] = (SYSCFG->EXTICR[1] & ~SYSCFG_EXTICR2_EXTI4)
                      | SYSCFG_EXTICR2_EXTI4_PC;

    EXTI->RTSR |= EXTI_RTSR_TR4;                     /* по переднему фронту */
    EXTI->FTSR &= ~EXTI_FTSR_TR4;
    EXTI->PR    = EXTI_PR_PR4;                       /* сбросить старый запрос (запись 1) */
    EXTI->IMR  |= EXTI_IMR_MR4;                      /* разрешить линию */

    NVIC_SetPriority(EXTI4_IRQn, 1);                 /* 0 — самый высокий из 16 уровней */
    NVIC_EnableIRQ(EXTI4_IRQn);
}

void EXTI4_IRQHandler(void)
{
    EXTI->PR = EXTI_PR_PR4;          /* сбросить флаг первым делом, иначе — повторный вход */
    g_gyro_drdy_cycles = DWT->CYCCNT; /* отметка времени для замера задержки */
    g_gyro_drdy_count++;              /* в обработчике — только короткие действия */
}
Plain text

Флаг запроса в регистре EXTI->PR сбрасывается записью единицы. Если его не сбросить, ядро вызовет обработчик снова сразу после выхода из него, и основная программа перестанет выполняться.

↑ К оглавлению

5. Приоритеты прерываний

В STM32F405 контроллер NVIC использует четыре бита приоритета, то есть шестнадцать уровней. Уровень 0 — самый высокий. Обработчик с более высоким приоритетом прерывает обработчик с более низким. Функция NVIC_SetPriority из CMSIS сама сдвигает значение в старшие биты регистра.

ИсточникПриоритет в BeeFCОбоснование
линия готовности данных гироскопа1от неё зависит частота цикла управления
таймер выходов на двигатели2урок S8
приём по последовательному порту5байт можно обработать в течение нескольких микросекунд
системный таймер SysTick15служебные задачи
Длительные действия в обработчике: Обработчик прерывания должен завершаться за единицы микросекунд. Вывод в консоль, ожидание флагов и вычисления с плавающей точкой в обработчике недопустимы. Обработчик фиксирует событие и время, а обработка выполняется в задаче планировщика.
Вложенные прерывания с приоритетами из таблицы: обработчик гироскопа вытесняет обработчик приёма, запрос SysTick ждёт завершения обоих. Время ориентировочное.
Вложенные прерывания с приоритетами из таблицы: обработчик гироскопа вытесняет обработчик приёма, запрос SysTick ждёт завершения обоих. Время ориентировочное.

Общие данные обработчика и основной программы

Переменная, которую изменяет обработчик, объявляется с квалификатором volatile. Чтение и запись выровненного 32-битного значения на Cortex-M4 выполняются одной инструкцией и не могут быть прерваны. Для значений типа uint64_t и для структур это не так, и их чтение выполняется в критической секции.

uint32_t primask = __get_PRIMASK();
__disable_irq();                      /* начало критической секции */
gyro_sample_t copy = g_gyro_sample;   /* копия структуры целиком */
__set_PRIMASK(primask);               /* восстановить прежнее состояние */
Plain text

Сохранение и восстановление регистра PRIMASK вместо безусловного разрешения прерываний позволяет вызывать такой код и там, где прерывания уже запрещены.

↑ К оглавлению

6. Дребезг контактов

При нажатии кнопки контакты замыкаются и размыкаются несколько раз в течение нескольких миллисекунд. Если повесить на кнопку внешнее прерывание, одно нажатие вызовет несколько обработчиков. Поэтому кнопка в BeeFC опрашивается задачей планировщика, а решение принимает конечный автомат.

Файл core/debounce.h

typedef enum { BTN_EVENT_NONE, BTN_EVENT_PRESSED, BTN_EVENT_RELEASED } btn_event_t;

typedef struct {
    bool    stable;       /* подтверждённое состояние: true — нажата */
    uint8_t count;        /* сколько опросов подряд сырое значение отличается */
    uint8_t threshold;    /* сколько опросов нужно для смены состояния */
} debounce_t;

void        debounce_init(debounce_t *d, uint8_t threshold);
btn_event_t debounce_update(debounce_t *d, bool raw_pressed);
Plain text

Файл core/debounce.c

void debounce_init(debounce_t *d, uint8_t threshold)
{
    d->stable = false;
    d->count = 0;
    d->threshold = threshold;
}

btn_event_t debounce_update(debounce_t *d, bool raw_pressed)
{
    if (raw_pressed == d->stable) {
        d->count = 0;                 /* дребезг закончился, счёт сначала */
        return BTN_EVENT_NONE;
    }
    if (++d->count != d->threshold) {
        return BTN_EVENT_NONE;        /* изменение ещё не подтверждено */
    }
    d->count = 0;
    d->stable = raw_pressed;
    return raw_pressed ? BTN_EVENT_PRESSED : BTN_EVENT_RELEASED;
}
Plain text

Автомат меняет состояние только после того, как новое значение повторилось заданное число опросов подряд. При опросе с частотой 1 кГц и пороге в десять опросов изменение подтверждается через 10 мс.

Файл tests/unit/test_level1.c

void test_debounce_ignores_bounce_and_reports_one_press(void)
{
    debounce_t d;
    debounce_init(&d, 3);
    const bool raw[] = { 1, 0, 1, 0, 1, 1, 1, 1, 1 };   /* дребезг, затем нажатие */
    int presses = 0;
    for (unsigned i = 0; i != sizeof raw; i++)
        if (debounce_update(&d, raw[i]) == BTN_EVENT_PRESSED) presses++;
    TEST_ASSERT_EQUAL_INT(1, presses);
    TEST_ASSERT_TRUE(d.stable);
}
Plain text
↑ К оглавлению

7. Замер задержки прерывания

Задержка измеряется логическим анализатором с двумя каналами. Первый канал подключается к выводу кнопки, второй — к свободному выводу, который обработчик переключает первой же инструкцией. Время между фронтом на первом канале и фронтом на втором — задержка обработки прерывания вместе с временем записи в порт.

На том же снимке анализатора виден дребезг: после нажатия на первом канале появляется серия коротких импульсов, и каждый из них вызывает обработчик.

Подключение к линии гироскопа: К выводу PC4 подключён выход гироскопа. Подавать на этот вывод сигнал от другого вывода или от кнопки нельзя: два выхода, включённые навстречу друг другу, могут выйти из строя. Для опытов используется свободный вывод, например вход неиспользуемого последовательного порта.
↑ К оглавлению

8. Практика

Задание S5.1. Фильтр дребезга

Продолжительность — 30 минут, выполняется на компьютере.

Реализовать модуль core/debounce и добавить тест на отпускание кнопки с дребезгом.

Критерий автоматической проверки: тесты test_debounce_* и скрытые тесты пройдены; скрытые тесты подают на вход записи дребезга реальных кнопок.

Задание S5.2. Счётчик прерываний от кнопки

Продолжительность — 40 минут.

Подключить кнопку между свободным выводом и общим проводом, включить подтягивающий резистор и настроить внешнее прерывание по спаду. Обработчик увеличивает счётчик. Выполнить двадцать нажатий и записать в отчёт docs/s5_report.md, сколько раз был вызван обработчик.

Задание S5.3. Замер задержки

Продолжительность — 30 минут, требуется логический анализатор.

Измерить задержку между фронтом на выводе кнопки и переключением отладочного вывода в обработчике. Приложить к отчёту снимок экрана анализатора и сравнить результат с расчётным значением.

Задание S5.4. Линия готовности данных гироскопа

Продолжительность — 20 минут.

Настроить прерывание по фронту на выводе линии готовности данных своей платы функцией gyro_drdy_exti_init. Обработчик сохраняет значение счётчика тактов DWT и увеличивает счётчик прерываний.

Критерий автоматической проверки: в модели Renode на вывод подаётся сто фронтов, после чего значение g_gyro_drdy_count равно 100.

↑ К оглавлению

9. Как это сделано в больших прошивках

ПрошивкаВерсияФайл в исходном коде
Betaflight2026.6.2src/main/drivers/exti.h, src/platform/STM32/exti.c
INAV9.1.0src/main/drivers/exti.c
ArduPilotCopter-4.7.2libraries/AP_HAL_ChibiOS/GPIO.cpp
PX4v1.17.0src/drivers/imu/invensense/icm42688p/ICM42688P.cpp
  • В Betaflight драйвер датчика регистрирует обработчик функциями EXTIHandlerInit и EXTIConfig, объявленными в exti.h. Реализация в src/platform/STM32/exti.c сама обслуживает общие обработчики линий 5–9 и 10–15 и вызывает нужную функцию драйвера по номеру линии.
  • В INAV устройство модуля exti.c унаследовано от общего предка и похоже на Betaflight.
  • В ArduPilot метод attach_interrupt в файле GPIO.cpp подключает обработчик через события линий ввода-вывода операционной системы ChibiOS.
  • Драйвер ICM42688P.cpp в PX4 по прерыванию готовности данных запускает чтение очереди измерений датчика. Одновременно драйвер планирует резервный запуск через удвоенный интервал. Если прерывание не пришло, срабатывает резервный запуск: драйвер увеличивает счётчик «DRDY missed» и читает количество накопленных измерений из датчика напрямую. Так пропущенное прерывание не останавливает цикл управления.
↑ К оглавлению

10. Проверьте себя

  1. Почему кнопку не подключают к внешнему прерыванию напрямую?
  2. Можно ли одновременно использовать внешние прерывания на выводах PA4 и PC4?
  3. Что произойдёт, если в обработчике не сбросить флаг в регистре EXTI->PR?
  4. Когда для чтения общей переменной нужна критическая секция?
  5. Что делает драйвер ICM-42688-P в PX4, если прерывание готовности данных не пришло?
↑ К оглавлению

11. Чек-лист самопроверки

Результат: Все пункты выполнены. Можно переходить к уроку S6.
↑ К оглавлению

12. Источники

  1. Справочное руководство RM0090, раздел 12, контроллер прерываний и событий.
  2. Руководство программиста PM0214, разделы о контроллере NVIC и регистре PRIMASK.
  3. Описание платы MAMBAF405_2022B для Betaflight.
  4. Драйвер ICM-42688-P в PX4.
↑ К оглавлению

13. Что дальше

  • Следующий урок: S6. Системное время и планировщик задач.
  • Полный перечень уроков приведён в статье «Курс BeeFC: содержание уровня „Старт“».

Проверено: 13.10.2026

↑ К оглавлению
  • мод. : Четверг, 15 октября 2026