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

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

Порты ввода-вывода, прерывания и контроллер прерываний NVIC mod. Суббота, 17 октября 2026 22:14 . См. 84

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++;              /* в обработчике — только короткие действия */
}

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

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

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

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

ИсточникПриоритет в BeeFCОбоснование
линия готовности данных гироскопа 1 от неё зависит частота цикла управления
таймер выходов на двигатели 2 урок S8
приём по последовательному порту 5 байт можно обработать в течение нескольких микросекунд
системный таймер SysTick 15 служебные задачи
Длительные действия в обработчике: Обработчик прерывания должен завершаться за единицы микросекунд. Вывод в консоль, ожидание флагов и вычисления с плавающей точкой в обработчике недопустимы. Обработчик фиксирует событие и время, а обработка выполняется в задаче планировщика.
Вложенные прерывания с приоритетами из таблицы: обработчик гироскопа вытесняет обработчик приёма, запрос 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);               /* восстановить прежнее состояние */

Сохранение и восстановление регистра 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);

Файл 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;
}

Автомат меняет состояние только после того, как новое значение повторилось заданное число опросов подряд. При опросе с частотой 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);
}
↑ К оглавлению

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. Как это сделано в больших прошивках

ПрошивкаВерсияФайл в исходном коде
Betaflight 2026.6.2 src/main/drivers/exti.h, src/platform/STM32/exti.c
INAV 9.1.0 src/main/drivers/exti.c
ArduPilot Copter-4.7.2 libraries/AP_HAL_ChibiOS/GPIO.cpp
PX4 v1.17.0 src/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

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