МИФИ · МПС · лабораторная 2 · STM32F103C8T6
Прерывания на ассемблере
Одна история: ты нажимаешь кнопку PA3 — и через несколько микросекунд процессор бросает свой цикл и выполняет твой код. Разберём, через какие блоки чипа проходит нажатие, что каждый из них делает с сигналом и как это совпадает с тем, что было на лекциях.
lab2.s.00 · перед началом
Как читать регистр
Во всех главах будет одна и та же картинка регистра, поэтому сначала — как её читать. Это то же самое, что ты делал в ЛР1 с CRL, просто теперь явно.
Регистр — 32 бита, пронумерованных справа налево от 0 до 31. Четыре бита подряд — это одна шестнадцатеричная цифра, поэтому регистр — это 8 hex-цифр. Запись вида 15:12 означает «биты с 15-го по 12-й включительно» — четыре бита, одна цифра.
Подробнее про эту запись. Счёт с нуля: самый правый бит — 0, потому что его вес 2⁰ = 1, бит n весит 2ⁿ. Старший пишут первым — так же, как в любом числе старший разряд слева, и так же, как биты нарисованы на картинках регистров в RM0008: слева направо от 31 к 0. Оба конца входят: ширина поля = старший − младший + 1, у 15:12 это 4. Цифра номер k занимает биты 4k+3 : 4k, так что 15:12 — цифра 3. Внутри поля своя нумерация, тоже с нуля: в документации поле пишут как EXTI3[3:0], и его бит 0 — это бит 12 регистра. Достать значение поля: (регистр >> 12) & 0xF.
Смысл бывает у отдельного бита (тогда «бит n отвечает за n-е что-то») или у группы соседних битов — её называют полем. 1 << n — число, у которого стоит только бит n: 1 << 3 = 0x8, 1 << 10 = 0x400.
адресГде лежит регистр
Регистр периферии — не ячейка внутри процессора, а ячейка памяти по своему адресу: процессор пишет в неё той же командой STR, что и в ОЗУ. Адрес всегда складывается одинаково — как в ЛР1, где GPIOB_BSRR = 0x4001 0C00 + 0x10:
база — из таблицы блоков, смещение — из строки «Address offset» в описании регистра
В этой лабе блоки лежат в двух районах адресного пространства:
| Район | Что там | Где база |
|---|---|---|
| 0x4000 0000 … | периферия ST, три шины: APB1 с 0x4000 0000 (таймеры 2–7, USART2/3, I2C…), APB2 с 0x4001 0000 (AFIO, EXTI, порты GPIO, АЦП, USART1), AHB с 0x4001 8000 и 0x4002 0000 (DMA, RCC, флеш) | RM0008, Table 3, столбец Bus |
| 0xE000 0000 … | периферия самого ядра Cortex-M3: SysTick, NVIC, SCB — одинаковая у всех производителей | PM0056, Table 33 |
RCC_APB2ENR. Таймер с APB1 включался бы в RCC_APB1ENR.RM0008 §3.2, : «The addressable memory space is divided into 8 main blocks, each of 512 MB»; Table 3 «Register boundary addresses», . PM0056 §2.2 «Memory model», ; Table 33 «STM32 core peripheral register regions», .
01 · с лекции
Что такое прерывание
зачемЧем плох опрос
В ЛР1 программа сама в цикле читала IDR и сравнивала с прошлым значением — это опрос. Процессор всё время занят проверкой, а если в цикле стоит задержка, нажатие пролетает мимо: помнишь 0,75 секунды в первой версии. С прерыванием главный цикл может быть пустым — о нажатии процессору сообщит железо.
словаЧто понадобится
- прерывание
- событие, из-за которого процессор оставляет текущую работу и выполняет специальную процедуру
- обработчик
- эта процедура — твой код, который выполнится по нажатию
- контекст
- всё, что нужно прерванной программе, чтобы потом продолжить: регистры, флаги, адрес следующей команды
- вектор
- адрес обработчика, записанный в таблицу
- КПП
- контроллер приоритетных прерываний: принимает запросы, фильтрует их масками, выбирает самый важный и сообщает процессору
какПять этапов и КПП
На лекциях обработку прерывания разбирали по пяти этапам. Каждая следующая глава говорит, на каком из них мы сейчас:
Фиксация факта — кто-то замечает событие и запоминает его. → EXTI, глава 04
Сохранение контекста в стек. → ядро, глава 07
Передача управления обработчику через таблицу векторов. → глава 08
Обработка — твой код. → глава 09
Восстановление контекста и возврат. → глава 10
А решает, прерывать ли вообще, КПП — по алгоритму: принять запрос → проверить маску → определить приоритет → сформировать вектор → передать процессору. В STM32F103 таких контроллеров два: маленький (EXTI) на краю чипа и главный (NVIC) в ядре. Весь путь нажатия — это эти пять этапов и два КПП, только с конкретными адресами.
«Прерывания и исключения в МПС… Контроллер приоритетных прерываний (КПП): функции, структура и алгоритм работы». Список тем к экзамену 2024/25, тема 3: «STM32F103C8: …система прерываний».
02 · блок 1 из 4
Ножка: GPIO
зачемОтсюда всё начинается
Кнопка одним концом на 3,3 В, другим — на выводе PA3. Нажатие меняет уровень на ножке с 0 на 1. Этот блок ты полностью настроил ещё в ЛР1.
слова
- вывод, ножка, пин
- одно и то же: контакт чипа. PA3 — порт A, номер 3
- IDR
- Input Data Register — «регистр входных данных»: бит n показывает уровень на ножке n
- подтяжка вниз
- резистор внутри чипа держит на ножке 0, пока кнопка отпущена
как
Вывод настроен как в ЛР1: вход с подтяжкой вниз, тетрада 0x8. Отпущена — 0, нажата — 3,3 В перебивают подтяжку, и на ножке 1. Новое только одно: сигнал с ножки идёт не только в IDR, но и дальше, в блок, который сам умеет заметить перемену. Настраивать GPIO для этого иначе не нужно.
адресGPIOA_IDR
Базы блоков — RM0008 Table 3, ; смещение IDR — §9.2.3, ; все регистры порта — Table 59, .
регистрGPIOA_IDR при нажатой PA3
твои кнопки
| Кнопка | Регистр | Бит | Значение при нажатии |
|---|---|---|---|
| PA3 | GPIOA_IDR | 3 | 0x0000 0008 |
| PB5 | GPIOB_IDR | 5 | 0x0000 0020 |
| PA10 | GPIOA_IDR | 10 | 0x0000 0400 |
RM0008 Table 20, (вход с подтяжкой); §9.2.3 IDR, .
03 · блок 2 из 4
Стрелочник: AFIO
зачемВходов меньше, чем ножек
Следующий блок, EXTI, следит только за 16 входами, а ножек у чипа 37. Кто-то должен решить, какие ножки подключить к этим 16 входам. Это делает AFIO — тот самый блок, через который в ЛР1 освобождали PB3 от JTAG.
слова
- линия
- вход блока EXTI. Их 16 для ножек: EXTI0 … EXTI15
- n
- номер ножки без буквы порта: у PA3 n = 3, у PB5 n = 5, у PA10 n = 10
- EXTICR
- External Interrupt Configuration Register — регистр AFIO, где выбирается порт для линии
- поле
- группа соседних битов с одним смыслом; здесь — 4 бита, одна hex-цифра на линию
как
Правило жёсткое: линия n слушает ножку с номером n — но только одного порта. Линия 3 может слушать PA3, или PB3, или PC3, а сразу несколько — нельзя. Представь железнодорожную стрелку: пути A, B, C, D сходятся в один путь — линию 3, — и стрелка ставится четырьмя битами.
Как это разложено по регистрам — по шагам, на примере PB5:
- Один переключатель — одна hex-цифра. Переключатель линии занимает 4 бита, то есть ровно одну цифру в шестнадцатеричной записи регистра. Что записано в цифре, тот порт и выбран: 0 — A, 1 — B, 2 — C, 3 — D.
- В регистре — четыре переключателя. Из 8 hex-цифр регистра используются только правые четыре, левые всегда нули. Значит, один регистр обслуживает 4 линии, а для 16 линий нужно четыре регистра:
EXTICR1— линии 0–3,EXTICR2— 4–7,EXTICR3— 8–11,EXTICR4— 12–15. Коротко: номер регистра = линия // 4 + 1. Линия 13: 13 // 4 = 3 →EXTICR4. - Какая цифра твоя. Цифры считаются справа, с нуля; первая линия регистра — в цифре 0. Поэтому цифра = линия − первая линия регистра = линия % 4. Линия 13: 13 − 12 = 13 % 4 = 1. Линия 5 в
EXTICR2, который начинается с линии 4: цифра 5 − 4 = 1. - Записать значение. В цифру 1 пишем 1 — порт B. Регистр целиком:
0x0000 0010.
EXTICR2 = 0x 0 0 0 0 0 0 1 0
цифра: 7 6 5 4 3 2 1 0
линия: — — — — 7 6 5 4
↑ цифра 1 — линия 5, в ней 1 → порт B → PB5
Это та же арифметика, что с CRH в ЛР1: CRH хранит пины 8–15, первый у него — пин 8, поэтому PB15 лежал в цифре 15 − 8 = 7. Разница лишь в том, что в CRH заняты все 8 цифр, а в EXTICR — четыре. Почему поле 4 бита, хотя портов у твоего чипа четыре и хватило бы двух: RM0008 описывает всё семейство, у старших моделей портов до G (значения 0–6), а 4 бита — ровно одна hex-цифра. Очищать при записи всё равно нужно все 4 бита: BIC … #(0xF << сдвиг).
После сброса во всех цифрах 0 — порт A. Для PA3 и PA10 стрелка уже стоит правильно, а для PB5 её обязательно надо перевести, иначе линия 5 будет слушать PA5.
адресБлок AFIO
База AFIO — RM0008 Table 3, ; смещения — §9.4.1–9.4.7, ; сводная карта — Table 60, .
регистрEXTICR1 и EXTICR2
твои кнопки
| Кнопка | Линия | Регистр | Цифра / биты | Значение | Регистр целиком |
|---|---|---|---|---|---|
| PA3 | 3 | EXTICR1 | 3 − 0 = 3 · 15:12 | 0 (A) | 0x0000 0000 |
| PB5 | 5 | EXTICR2 | 5 − 4 = 1 · 7:4 | 1 (B) | 0x0000 0010 |
| PA10 | 10 | EXTICR3 | 10 − 8 = 2 · 11:8 | 0 (A) | 0x0000 0000 |
BIC R1, R1, #(0xF << 12) по адресу 0x40010008 — «обнулить цифру 3 в EXTICR1», то есть порт A для линии 3. Верно. Комментарий над блоком «разрешение векторов NVIC» стоит заменить: это AFIO. Для PB5 записи в EXTICR2 пока нет.RM0008 §9.4.3–9.4.6, : «0000: PA[x] pin, 0001: PB[x] pin, 0010: PC[x] pin». Тактирование AFIO — бит 0 в RCC_APB2ENR.
04 · блок 3 из 4 · этап 1
Фиксация: EXTI — маленький КПП
зачемЗаметить и запомнить
Первый этап с лекции — фиксация факта прерывания. Нажатие длится миллисекунды, а процессор может быть занят. Кто-то должен заметить перемену и запомнить её до тех пор, пока процессор не освободится. Это делает EXTI.
слова
- фронт
- момент перехода уровня. Нарастающий — 0 → 1 (нажатие у тебя), спадающий — 1 → 0
- флажок
- бит, который ставит железо, чтобы запомнить событие; опускать его — забота программы
- маска
- бит-разрешение: пропускать сигнал дальше или нет
- EXTI
- External Interrupt/event controller — контроллер внешних прерываний
- RTSR
- Rising Trigger Selection Register — на каких линиях ловить нарастающий фронт
- FTSR
- Falling Trigger… — то же для спадающего
- PR
- Pending Register — флажки «событие было и ждёт обработки»
- IMR
- Interrupt Mask Register — пускать ли запрос линии в NVIC
как
На ножке произошёл переход 0 → 1. Если в RTSR стоит бит этой линии, EXTI поднимает флажок — бит в PR. Флажок сам не опускается: кнопку давно отпустили, процессор занят ещё десять секунд — событие всё равно записано. Если в IMR бит линии тоже стоит, EXTI сообщает о флажке в NVIC.
EXTI реагирует на перемену, а не на состояние: пока кнопку держат, новых флажков нет. В ЛР1 ты делал это руками через BIC с прошлым состоянием — здесь детектор фронта встроен в железо.
Сравни с КПП с лекции: PR — это его регистр запросов, IMR — регистр масок. Поэтому «маленький КПП».
Все регистры EXTI устроены одинаково: бит n — линия n, по одному биту на линию (не поле, как в AFIO). Биты 0–15 — линии ножек, 16–19 — внутренние события чипа, выше — не используются.
адресБлок EXTI
База EXTI — RM0008 Table 3, ; смещения — §10.3.1–10.3.6, ; сводная карта — Table 64, .
регистрEXTI_IMR для трёх кнопок
твои кнопки
| Кнопка | Линия | 1 << n | Куда писать |
|---|---|---|---|
| PA3 | 3 | 0x0000 0008 | RTSR (+0x08), IMR (+0x00); в обработчике — PR (+0x14) |
| PB5 | 5 | 0x0000 0020 | то же |
| PA10 | 10 | 0x0000 0400 | то же |
| все три | 0x0000 0428 | 0x4001 0408 (RTSR) и 0x4001 0400 (IMR) ← 0x428 |
IMR и RTSR для линий 3 и 10, адреса в EQU верные. Для PB5 добавь линию 5.RM0008 §10.2.4, : «When the selected edge occurs on the external interrupt line, an interrupt request is generated. The pending bit corresponding to the interrupt line is also set». Регистры — §10.3.1–10.3.6, ; база EXTI 0x4001 0400 — Table 3, .
05 · блок 4 из 4
Решение: NVIC — главный КПП
зачемКто решает, прерывать ли
EXTI только сообщает: «на линии 3 было событие». Прервать ли программу — решает NVIC. Это и есть контроллер приоритетных прерываний с лекции, только встроенный прямо в ядро Cortex-M3. Поэтому он описан не в документации ST, а в документации ARM (PM0056), и его адреса начинаются на 0xE000.
слова
- NVIC
- Nested Vectored Interrupt Controller: nested — «вложенный», одно прерывание может прервать другое; vectored — переход сразу по вектору
- вход, IRQ
- у NVIC несколько десятков входов, к каждому подведён свой источник. Номер входа называют IRQ (Interrupt ReQuest). Линия EXTI3 подключена ко входу 9
- pending
- «ожидает»: запрос пришёл, но обработчик ещё не начался
- active
- «в работе»: обработчик этого входа сейчас выполняется
- ISER
- Interrupt Set-Enable Register — разрешение входов, по биту на вход
- ISPR
- Interrupt Set-Pending Register — какие входы ждут
- ICER, ICPR
- Clear-Enable, Clear-Pending — пары к ISER и ISPR: запретить вход, стереть запрос
- IABR
- Interrupt Active Bit Register — какие обработчики сейчас в работе
- ISR
- Interrupt Service Routine — так в PM0056 называется обработчик
- IPR
- Interrupt Priority Register — приоритеты, по байту на вход
какАлгоритм КПП с лекции — и что из него нужно тебе
| Шаг КПП с лекции | Что делает NVIC для PA3 | Регистр | Тебе |
|---|---|---|---|
| 1. Приём запроса | на вход 9 пришёл сигнал от EXTI3 — NVIC ставит бит 9 «ожидает» | ISPR | ставит железо |
| 2. Проверка маски | разрешён ли вход 9? Нет — запрос будет висеть вечно | ISER | одна запись |
| 3. Приоритет | важнее ли запрос того, что выполняется? Главную программу прерывает кто угодно | IPR | только в эксперименте 2 |
| 4. Вектор | строка таблицы = 9 + 16 = 25 | — | сам |
| 5. Передача процессору | ядро останавливает программу | — | сам |
Отсюда главное: из всех регистров NVIC для эксперимента 1 нужен один — ISER, одна запись на кнопку (как он устроен — сразу ниже). Остальные (ICER — выключить вход, ICPR — стереть запрос, IABR — что сейчас выполняется) — кнопки пульта для особых случаев; в лабе их можно разве что посмотреть в отладчике.
У STM32F103C8 43 входа NVIC (0–42, глава 06), а в регистре 32 бита, по биту на вход. В один регистр входы не помещаются, поэтому ISER два: ISER0 — входы 0–31, ISER1 — 32–63 (у C8 заняты только его биты 0–10); в каждом бит k — вход k. Удобная особенность: запись единицы включает вход, а запись нуля не делает ничего. Поэтому ISER не надо читать и маскировать: пишешь 1 << k одной командой, соседние входы не пострадают — как с BSRR.
Почему масок две — IMR и ISER. Линии 5–9 приходят в NVIC на один общий вход 23, линии 10–15 — на общий вход 40. Выбрать среди них одну линию можно только в EXTI, а NVIC решает лишь, слушать ли вход целиком. И отличие от КПП Intel 8259 с лекции: там 1 в маске значит «запрещено», а в ISER 1 значит «разрешено».
регистрISER0 и ISER1 — как он устроен
ISER — PM0056 §4.3.2, : «Write: 0: No effect 1: Enable interrupt»; там же: «If an interrupt is not enabled, asserting its interrupt signal changes the interrupt state to pending». Какой регистр для какого входа — Table 41, : входы 0–31 → ISER0, 32–63 → ISER1. Адреса ISER0 и ISER1 — Table 44, .
какЧто NVIC помнит про каждый вход
Про каждый из своих входов NVIC хранит три вещи: разрешён ли он (бит в ISER), в каком он состоянии и насколько он важен (байт в IPR). Состояний четыре:
| Состояние | Что значит | Когда так у PA3 (вход 9) |
|---|---|---|
| inactive — покой | запроса нет, обработчик не выполняется | кнопку не нажимали, или обработчик закончился и флажок в EXTI сброшен |
| pending — ожидает | запрос пришёл, обработчик ещё не начался | нажали, а вход 9 не разрешён в ISER — или процессор занят чем-то важнее |
| active — в работе | обработчик выполняется (или прерван более важным) | процессор внутри твоего EXTI3_IRQHandler |
| active + pending | обработчик идёт, а от того же входа пришёл новый запрос | пока обработчик работал, кнопку нажали ещё раз |
Главное, что следует из таблицы: запрос запоминается, даже если вход запрещён. Нажал кнопку до записи в ISER — вход 9 перешёл в «ожидает» и ждёт. Как только разрешишь его, обработчик вызовется сразу.
какПуть одного нажатия через NVIC
- Сигнал. EXTI поднял флажок в
PR— и держит на входе 9 единицу, пока флажок стоит. Это не короткий импульс, а уровень. - Запомнить. NVIC видит единицу на входе 9 и переводит его в pending. Разрешён вход или нет — неважно.
- Решить. NVIC перебирает все входы в состоянии pending, которые разрешены в ISER, и выбирает самый важный (меньший номер приоритета; при равенстве — меньший номер входа). Если он важнее того, что процессор делает сейчас, — прерываем. Главную программу прерывает любой разрешённый вход.
- Передать. NVIC сообщает ядру номер входа. Ядро сохраняет регистры в стек (глава 07), берёт адрес из строки 16 + 9 = 25 таблицы векторов (глава 08) и прыгает туда. В этот момент железо само снимает pending и ставит active.
- Обработать. Твой код переключает светодиод и сбрасывает флажок в
EXTI_PR— единица на входе 9 пропадает. - Вернуться.
BX LR— ядро восстанавливает регистры, NVIC снимает active и ещё раз смотрит на вход: если там всё ещё единица, вход снова становится pending, и обработчик немедленно вызывается опять.
EXTI_PR — EXTI держит единицу на входе, после каждого BX LR NVIC видит её снова, и программа крутится в обработчике бесконечно. Снаружи это выглядит как зависание сразу после первого нажатия. Сбрасывать надо флажок в EXTI, а не в NVIC: NVIC свой pending снимает сам (шаг 4).какЗачем столько регистров: пары «поставить / снять»
Регистры NVIC идут парами: ISER/ICER — разрешение, ISPR/ICPR — ожидание. Внутри пары хранится один и тот же набор битов, а регистры — две кнопки к нему: через S (set) единица ставит бит, через C (clear) единица снимает. Ноль в обоих ничего не делает. Прочитать можно любой из пары — оба покажут текущее состояние.
Зачем так: чтобы менять один вход одной записью, не трогая остальные. Если бы разрешение лежало в обычном регистре, пришлось бы читать → править бит → писать, и прерывание, случившееся между чтением и записью, могло бы изменить регистр — твоя запись затёрла бы его изменение. Ровно та же идея, что у BSRR в ЛР1.
| Регистр | Для чего | Когда нужен |
|---|---|---|
ISER | разрешить вход | обязательно, при настройке — одна запись на кнопку |
ICER | запретить вход | временно выключить кнопку целиком — например, на время антидребезга |
ISPR | поставить запрос вручную | отладка без стенда: записал бит 9 в ISPR0 через окно Memory в Keil — обработчик PA3 вызовется, как от кнопки. Так проверяется таблица векторов и сам обработчик |
ICPR | стереть ожидающий запрос | выбросить старое нажатие, случившееся до настройки; помогает, только если флажок в EXTI тоже сброшен — иначе единица на входе тут же поставит запрос снова |
IABR | кто сейчас в работе (только чтение) | отладка и эксперимент 2: видно, какие обработчики вложены друг в друга |
IPR | приоритет входа, байт на вход | эксперимент 2 — глава «Кто важнее» |
какОбщий выключатель — PRIMASK
Кроме масок отдельных входов у ядра есть один общий бит PRIMASK: при 1 не срабатывает ни одно прерывание с настраиваемым приоритетом, при 0 — работают все разрешённые. Меняют его командами CPSID I (запретить всё) и CPSIE I (разрешить). После сброса он равен 0, поэтому тебе ничего делать не нужно — достаточно ISER. Но если встретишь CPSID I в чужом коде — это он.
Состояния входа — PM0056 §2.3.1, : «Each exception is in one of the following states». Повторный вызов при невыключенном сигнале — §4.3.9, : «if the signal is not deasserted before the processor returns from the ISR, the interrupt becomes pending again». Запрос копится и у запрещённого входа — §4.3.10, : «A interrupt can enter pending state even it is disabled». Выбор по приоритету — §2.3.5, : «If multiple pending exceptions have the same priority, the pending exception with the lowest exception number takes precedence». PRIMASK — : «Prevents the activation of all exceptions with configurable priority»; значение после сброса 0x00000000 — Table 2, .
адресБлок NVIC
База NVIC — PM0056 Table 33, ; смещения — Table 44 «NVIC register map», .
твои кнопки
| Кнопка | Вход (IRQ) | Регистр | Бит | Записать |
|---|---|---|---|---|
| PA3 | 9 | ISER0, 0xE000 E100 | 9 | 0x0000 0200 |
| PB5 | 23, общий 5–9 | ISER0, 0xE000 E100 | 23 | 0x0080 0000 |
| PA10 | 40, общий 10–15 | ISER1, 0xE000 E104 | 40 − 32 = 8 | 0x0000 0100 |
PM0056 §4.3, ; Table 41, — какие регистры отвечают за какие входы; §4.1 Table 33, — адреса NVIC 0xE000E100–0xE000E4EF. Вход EXTI3 = 9 — RM0008 Table 63, .
06 · клей между блоками
Нумерация прерываний: от ножки до строки таблицы
зачемОдин сигнал — четыре номера
Пока нажатие идёт по чипу, у него четыре раза меняется номер. У каждого блока своя нумерация, и перевод из одной в другую делает сам блок по своему правилу. В коде нужны все четыре: номер линии — для AFIO и EXTI, номер входа — для ISER, строка — чтобы поставить DCD на нужное место, адрес — чтобы проверить себя в отладчике. Эта глава собирает все четыре правила вместе, и дальше числа для любой кнопки можно найти самому.
номер ножки
слова
- линия EXTI
- один из входов EXTI. Линии 0–15 — для ножек, 16–18 — внутренние сигналы чипа
- вход NVIC, IRQ
- номер провода, по которому запрос приходит в NVIC. Этот номер пишется в
ISER - строка таблицы векторов
- она же «номер исключения». Одно 4-байтовое слово в начале флеша: адрес обработчика
- общий вход
- вход NVIC, к которому подключено несколько линий EXTI сразу
шаг 1Ножка → линия: номер ножки, порт не важен
Правило простое: ножка с номером n идёт на линию n, какой бы ни был порт. PA3, PB3 и PC3 претендуют на одну линию 3. Кто из них её получит — решает AFIO, полем в EXTICR (глава 03).
Отсюда первое ограничение: на одной линии — одна ножка. Нельзя получить прерывания одновременно от PA5 и PB5: это одна линия 5. Зато PA3, PB5 и PA10 друг другу не мешают — у них разные номера.
PA3 ─┐ PB3 ─┼─ AFIO_EXTICR1, биты 15:12 ─→ линия 3 PC3 ─┘ (0000 = A, 0001 = B, 0010 = C)
шаг 2Линия → вход NVIC: по таблице, и тут есть группы
Этот переход не вычисляется: какую линию к какому входу подключить, решила ST при разработке чипа. Правило берут из таблицы векторов в RM0008. У STM32F103C8 43 входа NVIC на весь чип, и почти все заняты другой периферией: таймерами, DMA, USART… Отдельные входы получили только линии 0–4. Остальные собраны в две группы.
| Линия EXTI | Откуда сигнал | Вход NVIC (IRQ) | Имя входа в RM0008 | Строка вектора = IRQ + 16 | Адрес строки = строка × 4 |
|---|---|---|---|---|---|
| 0 | PA0, PB0, PC0 … — одна из них | 6 | EXTI0 | 22 | 0x58 |
| 1 | PA1, PB1, PC1 … | 7 | EXTI1 | 23 | 0x5C |
| 2 | PA2, PB2, PC2 … | 8 | EXTI2 | 24 | 0x60 |
| 3 | PA3, PB3, PC3 … | 9 | EXTI3 | 25 | 0x64 |
| 4 | PA4, PB4, PC4 … | 10 | EXTI4 | 26 | 0x68 |
| 5, 6, 7, 8, 9 | Px5 … Px9 | 23 — общий | EXTI9_5 | 39 | 0x9C |
| 10, 11 … 15 | Px10 … Px15 | 40 — общий | EXTI15_10 | 56 | 0xE0 |
| 16 | детектор питания PVD | 1 | PVD | 17 | 0x44 |
| 17 | будильник RTC | 41 | RTCAlarm | 57 | 0xE4 |
| 18 | пробуждение по USB | 42 | USBWakeup | 58 | 0xE8 |
Как читать имена EXTI9_5 и EXTI15_10. В RM0008 они расшифрованы как «EXTI Line[9:5]» и «EXTI Line[15:10]». Это та же запись диапазона, что у битов 15:12 (глава 00): сначала старший номер, потом младший, оба включены. EXTI9_5 — линии 9, 8, 7, 6, 5. В имени метки двоеточие нельзя, поэтому подчёркивание.
Правило «+6» для линий 0–4 — не закон, а следствие порядка в таблице: входы 0–5 уже заняты (WWDG, PVD, TAMPER, RTC, FLASH, RCC), и EXTI0 встал шестым. Для остальных линий: 5–9 → 23, 10–15 → 40.
шаг 3Вход → строка таблицы векторов: + 16
Таблица векторов — это не только прерывания периферии. Первые 16 строк отведены событиям самого ядра: начальный SP, Reset, NMI, HardFault и другие, вплоть до SysTick. Входы NVIC идут после них, поэтому строка = IRQ + 16. Это и есть «16 +» с лекции: прибавляют к номеру входа, а не к номеру линии.
Ниже — таблица целиком. Серые строки — ядро, у них нет номера IRQ. Своя программа должна описать таблицу как минимум до строки самого старшего используемого входа. Для PA10 это строка 56, то есть 57 слов DCD, считая строку 0.
Вся таблица векторов STM32F103C8: 16 строк ядра + 43 входа NVIC
| Строка | Адрес | IRQ | Имя | Что это |
|---|---|---|---|---|
| 0 | 0x00 | — | — | начальное значение SP — не обработчик, а адрес вершины стека |
| 1 | 0x04 | — | Reset | сброс: отсюда стартует программа |
| 2 | 0x08 | — | NMI | немаскируемое прерывание (сбой тактирования HSE) |
| 3 | 0x0C | — | HardFault | любая авария, которую не обработал никто другой |
| 4 | 0x10 | — | MemManage | нарушение защиты памяти |
| 5 | 0x14 | — | BusFault | ошибка доступа к шине / памяти |
| 6 | 0x18 | — | UsageFault | неизвестная инструкция, недопустимое состояние |
| 7 | 0x1C | — | — | резерв |
| 8 | 0x20 | — | — | резерв |
| 9 | 0x24 | — | — | резерв |
| 10 | 0x28 | — | — | резерв |
| 11 | 0x2C | — | SVCall | вызов системы командой SVC |
| 12 | 0x30 | — | DebugMon | монитор отладки |
| 13 | 0x34 | — | — | резерв |
| 14 | 0x38 | — | PendSV | отложенный системный вызов (планировщики ОС) |
| 15 | 0x3C | — | SysTick | системный таймер ядра — пригодится для антидребезга |
| 16 | 0x40 | 0 | WWDG | оконный сторожевой таймер |
| 17 | 0x44 | 1 | PVD | контроль питания (через линию EXTI 16) |
| 18 | 0x48 | 2 | TAMPER | вскрытие корпуса (вывод TAMPER) |
| 19 | 0x4C | 3 | RTC | часы реального времени |
| 20 | 0x50 | 4 | FLASH | контроллер флеш-памяти |
| 21 | 0x54 | 5 | RCC | блок тактирования и сброса |
| 22 | 0x58 | 6 | EXTI0 | линия EXTI 0 — ножки Px0 |
| 23 | 0x5C | 7 | EXTI1 | линия EXTI 1 — ножки Px1 |
| 24 | 0x60 | 8 | EXTI2 | линия EXTI 2 — ножки Px2 |
| 25 | 0x64 | 9 | EXTI3 | линия EXTI 3 — ножки Px3 |
| 26 | 0x68 | 10 | EXTI4 | линия EXTI 4 — ножки Px4 |
| 27 | 0x6C | 11 | DMA1_Channel1 | DMA1, канал 1 |
| 28 | 0x70 | 12 | DMA1_Channel2 | DMA1, канал 2 |
| 29 | 0x74 | 13 | DMA1_Channel3 | DMA1, канал 3 |
| 30 | 0x78 | 14 | DMA1_Channel4 | DMA1, канал 4 |
| 31 | 0x7C | 15 | DMA1_Channel5 | DMA1, канал 5 |
| 32 | 0x80 | 16 | DMA1_Channel6 | DMA1, канал 6 |
| 33 | 0x84 | 17 | DMA1_Channel7 | DMA1, канал 7 |
| 34 | 0x88 | 18 | ADC1_2 | АЦП 1 и 2 (общий вход) |
| 35 | 0x8C | 19 | USB_HP_CAN_TX | USB высокий приоритет или передача CAN |
| 36 | 0x90 | 20 | USB_LP_CAN_RX0 | USB низкий приоритет или приём CAN (FIFO 0) |
| 37 | 0x94 | 21 | CAN_RX1 | приём CAN (FIFO 1) |
| 38 | 0x98 | 22 | CAN_SCE | CAN: состояние и ошибки |
| 39 | 0x9C | 23 | EXTI9_5 | линии EXTI 5–9 — ножки Px5…Px9, один вход на пять линий |
| 40 | 0xA0 | 24 | TIM1_BRK | таймер 1: аварийный стоп (break) |
| 41 | 0xA4 | 25 | TIM1_UP | таймер 1: переполнение (update) |
| 42 | 0xA8 | 26 | TIM1_TRG_COM | таймер 1: триггер и коммутация |
| 43 | 0xAC | 27 | TIM1_CC | таймер 1: захват / сравнение |
| 44 | 0xB0 | 28 | TIM2 | таймер 2 |
| 45 | 0xB4 | 29 | TIM3 | таймер 3 |
| 46 | 0xB8 | 30 | TIM4 | таймер 4 |
| 47 | 0xBC | 31 | I2C1_EV | I²C 1: события |
| 48 | 0xC0 | 32 | I2C1_ER | I²C 1: ошибки |
| 49 | 0xC4 | 33 | I2C2_EV | I²C 2: события |
| 50 | 0xC8 | 34 | I2C2_ER | I²C 2: ошибки |
| 51 | 0xCC | 35 | SPI1 | SPI 1 |
| 52 | 0xD0 | 36 | SPI2 | SPI 2 |
| 53 | 0xD4 | 37 | USART1 | USART 1 |
| 54 | 0xD8 | 38 | USART2 | USART 2 |
| 55 | 0xDC | 39 | USART3 | USART 3 |
| 56 | 0xE0 | 40 | EXTI15_10 | линии EXTI 10–15 — ножки Px10…Px15, один вход на шесть линий |
| 57 | 0xE4 | 41 | RTCAlarm | будильник RTC (через линию EXTI 17) |
| 58 | 0xE8 | 42 | USBWakeup | пробуждение по USB (через линию EXTI 18) |
Серым — события самого ядра, у них нет номера входа NVIC: это и есть 16 строк, из-за которых «строка = IRQ + 16». Цветом — входы EXTI; светлее — периферия, которая приходит через внутренние линии EXTI 16–18. В RM0008 Table 63 дальше идут строки 59–75 (TIM8, FSMC, SDIO, UART4 …) — их у C8 нет: у него 43 входа, IRQ 0–42. Таблица векторов в твоей программе должна доходить до строки самого старшего используемого входа: с PA10 это строка 56.
шаг 4Строка → адрес: × 4, и что там лежит
Каждая строка — одно слово DCD, 4 байта, таблица начинается с адреса 0. Поэтому адрес строки = строка × 4: 25 × 4 = 100 = 0x64. Этот адрес виден в Keil: окно Memory, адрес 0x08000064 (при BOOT0 = 0 флеш виден и с адреса 0, и с 0x0800 0000).
В строке лежит ровно одно: адрес обработчика, то есть метка из DCD. Одна строка — один адрес — один обработчик. Именно здесь группировка и становится заметной: у линий 0–4 по своей строке, значит по своему обработчику, а у всех линий 5–9 одна строка 39 и один обработчик на всех.
важноГде группировка кончается
Группы есть только в NVIC и в таблице векторов. В EXTI у каждой линии по-прежнему свои биты:
| Где | Линия 5 и линия 7 (обе в группе 5–9) | Что из этого следует |
|---|---|---|
EXTI: IMR, RTSR, PR | раздельные биты 5 и 7 | какие линии пропускать, решает EXTI |
NVIC: ISER0 | один бит 23 на обе | ISER открывает вход для всей группы сразу |
| таблица векторов | одна строка 39 | один обработчик на всю группу |
EXTI_PR: проверить бит своей линии и сбросить именно его. Если в группе включена одна линия — как у тебя PB5 в группе 5–9, — можно просто сбрасывать её бит. Но если на линиях 5–9 две кнопки, без проверки PR их не различить.твои кнопки
| Шаг | PA3 | PB5 | PA10 | Правило |
|---|---|---|---|---|
| линия EXTI | 3 | 5 | 10 | номер ножки |
| вход NVIC, IRQ | 9 | 23, общий 5–9 | 40, общий 10–15 | таблица: линии 0–4 → +6, 5–9 → 23, 10–15 → 40 |
| бит в ISER | ISER0, бит 9 | ISER0, бит 23 | ISER1, бит 8 | вход 32 и выше — в ISER1, бит = вход − 32 |
| строка таблицы векторов | 25 | 39 | 56 | IRQ + 16 |
| адрес строки | 0x64 | 0x9C | 0xE0 | строка × 4 |
Ножка → линия — RM0008 §10.2.5, . Линии 16–18 — там же: «EXTI line 16 is connected to the PVD output». Входы NVIC, имена и адреса строк — Table 63, : «EXTI Line[9:5] interrupts». Число входов у STM32F103C8 — даташит DS5319, : «up to 43 maskable interrupt channels».
07 · этап 2
Сохранение контекста: ядро
зачемЧтобы потом вернуться
NVIC решил прерывать. Чтобы после обработчика главная программа продолжилась как ни в чём не бывало, нужно отложить её контекст. У x86, который разбирали на лекциях, процессор сам сохраняет только флаги и адрес возврата, а остальное — программа. Cortex-M3 делает всё сам, без единой твоей команды.
слова
- стек
- область ОЗУ, куда складывают и откуда достают значения «последним положил — первым достал»
- SP
- Stack Pointer — адрес вершины стека; при записи в стек уменьшается
- PC
- Program Counter — адрес текущей команды
- LR
- Link Register — адрес возврата из подпрограммы (его пишет
BL) - xPSR
- регистр состояния с флагами N, Z, C, V
- Thread / Handler
- два режима ядра: обычная программа / обработчик прерывания
как
дописывает текущую команду;
кладёт в стек 8 регистров: xPSR, PC — куда вернуться, LR, R12, R3, R2, R1, R0;
кладёт в LR особое число 0xFFFFFFF9 — не адрес, а метку «идёт прерывание» (глава 10);
переходит в режим Handler и помечает запрос «в работе».
Сохраняются именно те 8 регистров, которые по соглашениям ARM любая подпрограмма имеет право испортить. Значит, обработчик — обычная подпрограмма, при условии, что сам не трогает R4–R11: их ядро не сохраняет. Если нужны — PUSH в начале и POP в конце.
числаЧто происходит с SP
SP до: 0x2000 1000 (твой STACK_TOP) → SP после: 0x2000 1000 − 0x20 = 0x2000 0FE0
по адресу 0x2000 0FE0 лежит R0, по 0x2000 0FFC — xPSR
PM0056 §2.3.7 «Exception entry and return», : «…eight data words… R0-R3, R12, Return address, PSR, LR», «the processor writes an EXC_RETURN value to the LR».
08 · этап 3
Передача управления: таблица векторов
зачемКуда прыгать
Ядро знает номер — 25, но не знает, где твой обработчик. Адрес ему подсказывает таблица векторов — «телефонная книга» в начале флеша.
слова
- таблица векторов
- список адресов обработчиков с адреса 0x0800 0000; строка n — обработчик исключения n
- слово
- 4 байта; одна строка таблицы — одно слово
- DCD
- директива: положить в память 4-байтное число, например адрес метки
как
Ядро берёт слово по адресу номер × 4, кладёт его в PC и оказывается в начале обработчика. Никакого кода, который разбирался бы, «кто позвал», нет — поэтому контроллер и называется векторным.
У тебя в таблице 4 строки: SP, Reset, NMI, HardFault. Строки 25 нет — но ядро всё равно пойдёт по адресу 0x64 и возьмёт то, что там лежит, то есть байты твоего же кода. Прыжок по случайному числу, HardFault.
Поэтому таблицу дописывают подряд до нужной строки: 16 системных строк, затем IRQ 0, 1, … 9. Строку нельзя пропустить: DCD кладёт слова одно за другим, и если выбросить одну, все следующие съедут на 4 байта. Для PA10 это 57 слов, до 0xE0.
числаОткуда 0x64
во флеше: 0x0800 0000 + 0x64 = 0x0800 0064 — туда процессор и смотрит
строка 39 × 4 = 156 = 9·16 + 12 = 0x9C · строка 56 × 4 = 224 = 14·16 + 0 = 0xE0
RM0008 §10.1.2, Table 63, . PM0056 §2.3.4 «Vector table», . Материалы к вопросу 13: таблица векторов «содержит адреса обработчиков, располагается в начале адресного пространства».
DCD: как строчка кода становится байтами флеша
Чтобы понять таблицу векторов, полезно один раз увидеть, чем вообще является прошивка. Флеш — это длинная лента пронумерованных ячеек по одному байту, начиная с адреса 0x0800 0000. Прошивка — просто то, что лежит на этой ленте. Никаких «файлов», «переменных» или «функций» там нет — только байты подряд.
Ассемблер превращает твой текст в эти байты двумя способами:
- команды —
LDR,ORR,B— кодирует в машинные коды, которые процессор умеет выполнять; - директиву
DCDвообще ничем не кодирует: он берёт число и кладёт его на ленту как есть, 4 байта. Процессор эту строчку никогда не «выполняет» — она существует только как 4 байта в памяти.
Процессор не различает, где на ленте «код», а где «данные». Он просто читает. При сбросе он по правилу берёт первые 8 байт как числа: в SP — слово по адресу 0, в PC — слово по адресу 4. Дальше он идёт по адресу из PC и читает байты уже как команды. Поэтому первые слова и нужно положить именно числами, через DCD, а не командами.
Код → байты во флеше
Щёлкни строчку слева — справа подсветятся байты, которые она создала. Числа настоящие: это твой lab1_my/main.s от 28 сентября, собранный и разобранный дизассемблером.
| Адрес | +0 | +1 | +2 | +3 | Что это |
|---|
Почему байты «задом наперёд»
Число 0x20001000 занимает 4 байта: 20, 00, 10, 00. STM32 хранит слово младшим байтом вперёд — little endian. Поэтому на ленте они лежат как 00 10 00 20, и в дампе прошивки ты видишь именно это. Читать слово надо справа налево по байтам. Когда процессор читает слово, он сам собирает байты обратно, программе об этом думать не нужно.
RM0008 §3.2 «Memory organization», : «The bytes are coded in memory in Little Endian format. The lowest numbered byte in a word is considered the word’s least significant byte».
Почему адреса обработчиков нечётные
Reset_Handler начинается по адресу 0x0800 0010, а в таблицу попадает 0x0800 0011. Единица в младшем бите — это не часть адреса, а флаг «переходить в режим Thumb». Команды Thumb-2 всегда лежат на чётных адресах, поэтому младший бит свободен, и процессор использует его как признак режима. Cortex-M3 умеет только Thumb, так что бит обязан быть единицей — иначе HardFault. Ставит его сам ассемблер, когда видит, что метка стоит в секции кода.
PM0056 §2.3.4 «Vector table», : «The least-significant bit of each vector must be 1, indicating that the exception handler is Thumb code».
EQU, DCD и LDR =: три похожие вещи
| Запись | Место в памяти | Что происходит | Зачем |
|---|---|---|---|
NAME EQU 0x… | не занимает | имя для числа на время сборки; везде, где написано NAME, ассемблер подставит число | адреса регистров, маски |
DCD 0x… | 4 байта ровно здесь | число кладётся на ленту в месте, где стоит строка | таблица векторов, таблицы констант |
LDR R0, =0x… | 4 байта в литеральном пуле + сама команда | ассемблер сам делает «скрытый DCD» после кода и команду, читающую его оттуда | загрузить в регистр любое 32-битное число |
| Директива | Сколько байт | Пример |
|---|---|---|
| DCB | 1 | DCB 0x41, DCB "hi", 0 |
| DCW | 2 | DCW 0x1234 |
| DCD | 4 | DCD 1, 5, 20 — три слова подряд |
Справка Keil, установленная вместе с MDK: файл ARM\Hlp\armclang_ref.chm, раздел «armasm Legacy Assembler Reference → armasm Directives Reference → DCD and DCDU directives»: «The DCD directive allocates one or more words of memory, aligned on four-byte boundaries, and defines the initial runtime contents of the memory». Там же «EQU directive» и «END directive». В методичке ЛР0 директива показана в листинге 1, ; что после END «не воспринимается компилятором» — .
DCD NMI_Handler — это ссылка: «положи сюда адрес метки». Если метки нет в файле, ассемблер не знает, какое число класть, и выдаёт ошибку «символ не определён». Метка обязана стоять до END: всё, что ниже END, ассемблер не читает.09 · этап 4
Обработка: твой код
зачемДве обязанности
Обработчик эксперимента 1 делает две вещи: опускает флажок EXTI и переключает цвет.
слова
- опустить флажок
- сбросить бит линии в EXTI_PR, чтобы EXTI перестал сообщать о событии
- rc_w1
- так в документации помечают биты, которые сбрасываются записью единицы; запись нуля ничего не делает
как
Флажок. Сам он не опускается. Не сбросишь — выйдешь из обработчика, NVIC тут же снова увидит запрос, и ядро снова войдёт в обработчик. Так без конца. Сбрасывают его записью числа 1 << n в EXTI_PR целиком. Лучше — в начале обработчика: запись идёт через шину несколько тактов, и если сделать её последней командой перед выходом, NVIC может ещё успеть увидеть старый запрос.
Цвет — как в финальной версии ЛР1: прочитать ODR, инвертировать бит светодиода, записать. Только теперь без проверки фронта — фронт уже поймал EXTI.
Общие входы. Обработчик PB5 вызывается от любой из линий 5–9, PA10 — от любой из 10–15. Поэтому в них сначала проверяют свой бит в EXTI_PR через TST.
регистрЧто записать в EXTI_PR для PA3
PR = 0x28. «Прочитать, BIC #8, записать» даст запись 0x20 — единица в бите 5 сбросит чужой флажок линии 5, а ноль в бите 3 свой не сбросит. Ровно наоборот тому, что хотел.| Кнопка | Записать в EXTI_PR | Проверять бит в PR? |
|---|---|---|
| PA3 | 0x0000 0008 | можно не проверять — вход только для линии 3 |
| PB5 | 0x0000 0020 | да, вход общий для 5–9 |
| PA10 | 0x0000 0400 | да, вход общий для 10–15 |
RM0008 §10.3.6 «Pending register (EXTI_PR)», : «This bit is cleared by writing a ‘1’ into the bit». , листинг: «Сбрасываем флаг прерывания и ждем, пока он снимится».
10 · этап 5
Восстановление контекста: возврат
зачемПродолжить, как будто ничего не было
Обработчик закончил — главная программа должна продолжиться с той самой команды, на которой её прервали.
слова
- EXC_RETURN
- особое число, которое ядро кладёт в LR при входе в прерывание; у тебя
0xFFFFFFF9
как
Выход — обычный BX LR. Но в LR лежит не адрес, а 0xFFFFFFF9. Увидев, что в PC загружается такое число, ядро понимает: обработчик закончен. Оно достаёт из стека те 8 регистров, возвращается в режим Thread и продолжает с сохранённого PC.
Ловушка. Если в обработчике есть BL Delay — а в эксперименте 2 он будет, — BL перезапишет LR, и 0xFFFFFFF9 пропадёт. Решение — как для вложенной подпрограммы: PUSH {LR} в начале, POP {PC} в конце.
регистрИз чего состоит 0xFFFFFFF9
PM0056 §2.3.7, , Table 17 «Exception return behavior»: 0b1001 — «Return to Thread mode… uses MSP»; 0b0001 — «Return to Handler mode». Выйти можно «a POP instruction that includes the PC», «a BX instruction with any register».
11 · твой код
Собираем всё в твоей lab2.s
Предлагаю сначала довести до конца одну кнопку — PA3: у неё свой вход NVIC, никаких общих векторов.
| Шаг | Для PA3 | Состояние |
|---|---|---|
| тактирование AFIO | бит 0 в RCC_APB2ENR | есть из ЛР1 |
| GPIO | тетрада 0x8, подтяжка вниз | есть из ЛР1 |
| AFIO | EXTICR1, цифра 3 = 0 | есть строки 114–118 |
| EXTI | RTSR и IMR, бит 3 | есть строки 102–112 |
| NVIC | 0x200 в ISER0 (0xE000 E100) | нет |
| таблица векторов | дописать до строки 25 (0x64) | нет |
| обработчик | 0x8 в EXTI_PR, переключить красный, выйти | нет |
| главный цикл | убрать опрос кнопок | потом |
Три красные строки — три шага. После каждого на стенде видно, как сигнал продвигается на блок дальше. На плате проверять удобнее точкой останова на первой строке обработчика: остановился — вся цепочка работает.
12 · остальные кнопки
PB5 и PA10 — то же с другими числами
| PA3 | PB5 | PA10 | |
|---|---|---|---|
| AFIO | EXTICR1 = 0 (по умолчанию) | EXTICR2 = 0x10 — обязательно | EXTICR3 = 0 (по умолчанию) |
| EXTI: RTSR, IMR | 1 << 3 | 1 << 5 | 1 << 10 |
| NVIC | ISER0 ← 0x200 | ISER0 ← 0x800000 | ISER1 ← 0x100 |
| строка / адрес вектора | 25 · 0x64 | 39 · 0x9C, общий 5–9 | 56 · 0xE0, общий 10–15 |
| в обработчике | PR ← 0x8 | проверить бит 5, PR ← 0x20 | проверить бит 10, PR ← 0x400 |
Таблицу векторов для PA10 придётся дотянуть до строки 56 — 57 слов, самая длинная часть программы. Пиши у каждой строки комментарий с адресом, чтобы сверять с Table 63.
13 · эксперимент 2
Кто важнее: приоритеты
зачемКогда прерывание приходит во время другого
Третий шаг алгоритма КПП — определение приоритета. В эксперименте 1 он не участвовал: главную программу прерывает кто угодно. В эксперименте 2 обработчик работает несколько секунд, и другая кнопка нажимается во время него — тогда NVIC сравнивает приоритеты.
слова
- приоритет
- число 0–15; меньше — важнее
- группа
- старшая часть приоритета: решает, может ли пришедшее прерывание вытеснить работающий обработчик
- субприоритет
- младшая часть: никогда не вытесняет, решает только, кто первым выйдет из очереди
- PRIGROUP
- поле в регистре SCB_AIRCR: где граница между группой и субприоритетом
- вложенное прерывание
- прерывание внутри обработчика другого прерывания — отсюда Nested в имени NVIC
как
У каждого входа — свой байт в IPR: у PA3 это 0xE000 E400 + 9 = 0xE000 E409. Из 8 бит у STM32F103 работают только старшие 4, поэтому приоритет 6 записывают как 0x60, командой STRB — один байт.
Эти 4 бита делятся на группу и субприоритет. При PRIGROUP = 5 — два бита на группу и два на субприоритет. Вытеснение возможно, только если группа пришедшего строго меньше.
адресIPR и AIRCR
Базы NVIC и SCB — PM0056 Table 33, ; IPR — Table 44, ; AIRCR — §4.4.5, , карта SCB — Table 48, .
регистрБайт приоритета 0x60 при PRIGROUP = 5
PM0056 §2.3.6, : «Only the group priority determines preemption of interrupt exceptions»; §4.3.7, (IPR); §4.4.5, (AIRCR, Table 45). Про 4 бита: методичка ЛР2 и PM0056 «Configurable priority values are in the range 0-15».
13 · подробно
Приоритеты подробно: PRIGROUP и калькулятор
PM0056 §2.3.5–2.3.6, : «Only the group priority determines preemption of interrupt exceptions», «If an exception occurs with the same priority as the exception being handled, the handler is not preempted», «the pending exception with the lowest exception number takes precedence».
PRIGROUP: как делится число
| PRIGROUP | Биты группы | Биты суб. | Групп | Субприоритетов | AIRCR = |
|---|---|---|---|---|---|
| 0…3 | [7:4] | — | 16 | 1 | 0x05FA0300 |
| 4 | [7:5] | [4] | 8 | 2 | 0x05FA0400 |
| 5 | [7:6] | [5:4] | 4 | 4 | 0x05FA0500 |
| 6 | [7] | [6:4] | 2 | 8 | 0x05FA0600 |
| 7 | — | [7:4] | 1 | 16 | 0x05FA0700 |
PM0056 §4.4.5 «Application interrupt and reset control register (SCB_AIRCR)», , Table 45 «Priority grouping». Адрес 0xE000 ED0C: база SCB 0xE000ED00 (Table 33, ) + смещение 0x0C.
0x05FA, иначе запись игнорируется целиком — защита от случайного сброса чипа, ведь в том же регистре есть бит SYSRESETREQ. Поэтому AIRCR пишут готовой константой 0x05FA0000 | (PRIGROUP << 8), а не через ORR к прочитанному: при чтении в старшей половине вернётся другое число, 0xFA05 (PM0056, стр. 134).Кто кого вытеснит
Задай разбиение и приоритеты двух прерываний: одно уже выполняется, второе приходит во время него. Числа — это 4 значимых бита, от 0 до 15; в регистр IPR пойдёт значение, сдвинутое на 4.
14 · эксперимент 2
Четыре сценария на временной шкале
В эксперименте 2 каждый обработчик зажигает свой цвет, ждёт несколько секунд и гасит. Задержка нужна, чтобы успеть нажать вторую кнопку, пока первый обработчик ещё работает. Вот что должно получиться.
| Сценарий методички | Что настроить | Что увидишь |
|---|---|---|
| 1. Прерывание с бо́льшим приоритетом группы во время меньшего — и наоборот | PRIGROUP 5; A = группа 2, B = группа 0 | B прерывает A: на время B горят оба цвета — A не успел погасить свой. Если наоборот, важнее A, то B ждёт до конца A |
| 2. Та же группа, разный субприоритет | одна группа, суб. 0 и 1 | никто никого не прерывает; второй начнётся, как только закончится первый |
| 3. Два прерывания с разными суб. во время более важного | C — группа 0; A, B — группа 1, суб. 1 и 0 | после C первым идёт B (меньший суб.), даже если A нажали раньше |
| 4. Всё равно | все приоритеты 0 | как сценарий 2, а порядок в очереди — по номеру IRQ |
15 · эксперимент 2 · код
Эксперимент 2 на ассемблере
зачемЧто добавить к сданному эксперименту 1
Настройка GPIO, AFIO, EXTI, ISER и таблица векторов из эксперимента 1 остаются как есть. Меняется немного: в начале программы — две записи приоритетов (AIRCR и IPR), а каждый обработчик вместо «переключить цвет» делает «зажечь — подождать несколько секунд — погасить». Ниже — по шагам, в том порядке, в каком это пишется в Reset_Handler и в обработчиках.
слова
- STRB
- запись одного байта (Store Register Byte) — младшие 8 бит регистра в один байт памяти. Нужна для IPR: у каждого входа свой байт
- PUSH {LR} / POP {PC}
- сохранить LR в стек и при выходе загрузить его сразу в PC. Нужно, когда обработчик вызывает подпрограмму через
BL - EXC_RETURN
- число
0xFFFFFFF9, которое ядро кладёт в LR при входе в прерывание. Загрузка его в PC — это и есть выход из прерывания - BSRR
- регистр установки/сброса выходов: запись 1 меняет только свой бит, остальные не трогает и не читает
наглядноКак устроены AIRCR и IPR
Выбери сценарий или поменяй числа сам — схема покажет три вещи сразу: какое слово уходит в AIRCR, где в памяти лежат байты приоритета твоих трёх кнопок и как каждый байт делится на группу и субприоритет.
1 · SCB_AIRCR 0xE000 ED00 + 0x0C = 0xE000 ED0C · пишется один раз словом (STR)
2 · NVIC_IPR в памяти байт на вход: 0xE000 E400 + IRQ · пишется байтом (STRB)
3 · Байт приоритета изнутри работают только биты 7:4; PRIGROUP решает, где кончается группа
Поля AIRCR — PM0056 §4.4.5, стр. 134; разбиение — Table 45, стр. 135; раскладка IPR по байтам — §4.3.7, стр. 125.
шаг 1AIRCR: как делить приоритет
Одна запись слова в 0xE000 ED0C. Число берётся из таблицы PRIGROUP в главе 13: для двух бит группы и двух бит субприоритета — 0x05FA0500. Записывать целиком, без чтения и ORR: при чтении в старшей половине вернётся 0xFA05, а не ключ.
Адрес и число не помещаются в одну команду MOV, поэтому оба грузятся через LDR Rx, =…, как маски CRL в ЛР1. Делать это нужно до записи в ISER — чтобы к моменту первого нажатия всё уже было настроено.
шаг 2IPR: по байту на кнопку
Адрес байта = 0xE000 E400 + IRQ. Здесь IRQ — номер входа NVIC (у PA3 это 9, а не номер линии 3), и без умножения на 4: в IPR на каждый вход приходится один байт, а не слово, как в таблице векторов. Один 32-битный IPR держит приоритеты четырёх входов: вход 9 — байт 1 регистра IPR2, 0xE000 E408 + 1 = 0xE000 E409. Значение — приоритет, сдвинутый на 4: работают только биты 7:4. При PRIGROUP = 5 байт складывается так: группа << 6 | субприоритет << 4.
| Кнопка | Цвет | IRQ | Адрес байта | Чем писать |
|---|---|---|---|---|
| PA3 | красный | 9 | 0xE000 E409 | STRB — только байт. Запись словом (STR) затёрла бы приоритеты трёх соседних входов |
| PB5 | синий | 23 | 0xE000 E417 | |
| PA10 | зелёный | 40 | 0xE000 E428 |
1. Он попадёт в биты 3:0, которые у STM32F103 не реализованы и читаются как 0, — и у всех кнопок останется приоритет 0. Признак: в любом сценарии никто никого не прерывает. Проверка в Keil: окно Memory по адресу 0xE000E409 — там должно быть 0x40, 0x80 и т. п., а не 0x01.шаг 3Байты для четырёх сценариев
PRIGROUP = 5 во всех четырёх, меняются только три байта. Удобно завести три EQU и перед каждым прогоном менять только их. Буквы A/B/C — как на временных диаграммах в главе 14.
| Сценарий | PA3 · E409 | PB5 · E417 | PA10 · E428 | Что нажимать и что увидишь |
|---|---|---|---|---|
| 1. Вытеснение | 0x80 гр. 2 | 0x00 гр. 0 | 0x40 гр. 1 | PA3, затем во время красного PB5 — синий загорится сразу, красный продолжит гореть (A прерван). Наоборот: PB5, затем PA3 — красный загорится только после синего |
| 2. Одна группа, разный суб. | 0x50 гр. 1 суб. 1 | 0x40 гр. 1 суб. 0 | 0x60 гр. 1 суб. 2 | PA3, затем PB5 — синий ждёт конца красного, хотя его субприоритет меньше: субприоритет не вытесняет |
| 3. Очередь по суб. во время старшей группы | 0x50 гр. 1 суб. 1 | 0x40 гр. 1 суб. 0 | 0x00 гр. 0 | PA10, во время зелёного — PA3, потом PB5. После зелёного первым пойдёт синий (суб. 0), хотя PA3 нажали раньше |
| 4. Всё равно | 0x00 | 0x00 | 0x00 | PB5, во время синего — PA10, потом PA3. После синего первым красный (IRQ 9), потом зелёный (IRQ 40): при равенстве решает номер входа |
шаг 4Обработчик: зажечь, подождать, погасить
Порядок действий в каждом из трёх обработчиков:
- Сохранить LR в стек —
PUSH {LR}. Дальше будетBL Delay, аBLперезапишет LR своим адресом возврата, иEXC_RETURNпотеряется. - Зажечь свой цвет через BSRR. Светодиод с общим анодом, поэтому «зажечь» = сбросить бит: в BSRR пишется бит
n + 16. - Задержка на несколько секунд: число в R3 и
BL Delay. - Погасить: в BSRR бит
n. - Сбросить флаг в
EXTI_PRзаписью 1 и сразу прочитать PR обратно (почему в конце — ниже). - Выйти —
POP {PC}: в PC попадает сохранённыйEXC_RETURN, и это выход из прерывания, какBX LR.
| Цвет | Регистр | Зажечь (BSRR) | Погасить (BSRR) |
|---|---|---|---|
| R — PA7 | GPIOA_BSRR 0x4001 0810 | 1 << 23 | 1 << 7 |
| G — PB3 | GPIOB_BSRR 0x4001 0C10 | 1 << 19 | 1 << 3 |
| B — PB15 | GPIOB_BSRR 0x4001 0C10 | 1 << 31 | 1 << 15 |
LDR/EOR/STR в ODR было безопасно. Теперь прерывают. Пусть зелёный обработчик (PB3) прочитал GPIOB_ODR, и в этот момент его вытеснил синий (PB15): синий зажёг свой цвет и вернулся. Зелёный запишет обратно старое значение ODR — и погасит синий, хотя тот ещё должен гореть. PB3 и PB15 в одном порту, так что это случится на твоём стенде. BSRR меняет только записанные биты одной записью, без чтения, — гонки нет.Delay, у обоих счётчик в R3. Это не конфликт: при входе в синий ядро само сохранило R3 красного в стек, при выходе — восстановило, и красный досчитает с того же числа. Поэтому в Delay и в обработчиках достаточно R0–R3 и R12. Вложение стоит 32 байта стека на уровень плюс 4 на PUSH {LR} — при стеке 4 КБ хватает с запасом.Delay первой строкой стоит LDR R3, =50000 — она затирает число, переданное в R3 перед BL. Для нескольких секунд эту строку нужно убрать, иначе задержка всегда будет ~20 мс. Итерация цикла SUBS/BNE — около 3 тактов, при 8 МГц это примерно 2,7 млн итераций на секунду; на 3 секунды — порядка 8 млн. Точное число подбери на плате.шаг 5Где сбрасывать PR: в конце, с чтением обратно
В эксперименте 1 флаг сбрасывался первым действием. Здесь удобнее сбрасывать в конце, после задержки:
- пока обработчик горит несколько секунд, дребезг этой же кнопки и повторные нажатия только снова ставят уже поднятый флаг — они поглощаются, и после выхода обработчик не запустится второй раз. Это и есть защита от дребезга для эксперимента 2, без таймеров;
- но сброс прямо перед выходом — ровно та ошибка, из-за которой у тебя на плате обработчик вызывался дважды: запись в PR не успевает дойти до EXTI к моменту возврата. Поэтому после
STRв PR —LDRиз PR: чтение не завершится, пока не завершится запись. Это то «ждём, пока он снимется», что написано в шаблоне методички.
Вытеснению это не мешает: флаг своей линии на чужие входы NVIC не влияет.
проверкаПеред стендом
- в таблице векторов по-прежнему строки 25, 39, 56; в Keil Memory по
0x08000064,…9C,…E0— адреса обработчиков (нечётные); 0xE000ED0Cчитается как0xFA050500— ключ при чтении другой, PRIGROUP = 5 на месте;- байты
0xE000E409,…E417,…E428равны числам сценария, младшая цифра 0; - в каждом обработчике
PUSH {LR}в начале иPOP {PC}в конце — без лишнихBX LRмежду ними; Main_Loopпустой —B Main_Loop.
Байтовый доступ к IPR и биты [7:4] — PM0056 §4.3.7, : «The processor implements only bits[7:4] of each field». Ключ AIRCR — §4.4.5, : «To write to this register, you must write 0x5FA to the VECTKEY field»; разбиение — Table 45, . Выход через POP — §2.3.7, : «A POP instruction that includes the PC». Атомарная запись BSRR — RM0008 §9.1.2, : «it is possible to modify only one or several bits in a single atomic APB2 write access». Порядок при равных приоритетах — PM0056 §2.3.5, . Задание — методичка ЛР2, .
16 · дребезг
Дребезг в прерываниях
Дребезг никуда не делся, но стал заметнее. В лабе 1 медленный опрос его просто «не видел». EXTI же реагирует на каждый фронт за доли микросекунды, и пачка из пяти отскоков — это пять прерываний подряд. С переключением цвета это выглядит так: нажал, а цвет не изменился или мигнул, потому что переключился чётное число раз.
| Способ | Как | Плюсы и минусы |
|---|---|---|
| Пауза в обработчике | переключил цвет → задержка ~20 мс → сбросил PR → вышел | просто; отскоки за 20 мс ставят флаг, но он сбрасывается после паузы. Минус: на 20 мс блокируются все прерывания той же и более низкой группы |
| Закрыть линию | в обработчике снять бит в EXTI_IMR, открыть обратно по таймеру | правильно и без простоя; нужен таймер — например, SysTick |
| Отметка времени | SysTick считает миллисекунды; обработчик игнорирует нажатие, если с прошлого прошло меньше 50 мс | так делают в реальных устройствах |
| Проверить уровень | после паузы перечитать IDR: если кнопка уже отпущена — это была помеха | отсеивает импульсные наводки |
| Аппаратно | RC-цепочка: 0,1 мкФ параллельно кнопке | триггер Шмитта на входе довершит дело |
Справочник
Когда рассказ прочитан
Здесь то, к чему возвращаются во время работы: калькулятор чисел для любой кнопки, все регистры одной таблицей, SysTick, отладка, ловушки, шпаргалка варианта и вопросы к контрольной.
С1 · справочник
Калькулятор: вывод → все регистры прерывания
Какие числа нужны для одной кнопки
Выбери вывод и фронт. Открыт PA3 варианта 8. Проверь себя: сначала посчитай на бумаге, потом сравни.
С2 · справочник
Все регистры одним списком
Только то, что встречается в лабе. Колонка «нужно» — что пишешь сам в эксперименте 1 (Э1) и 2 (Э2).
| Блок | Регистр | Адрес | Что в нём | Нужно |
|---|---|---|---|---|
| RCC | APB2ENR | 0x4002 1018 | бит 0 — тактирование AFIO | Э1 |
| AFIO | EXTICR1…4 | 0x4001 0008 + 4·k | по 4 бита на линию: 0000 — A, 0001 — B | Э1 (для PB5 обязательно) |
| EXTI | IMR | 0x4001 0400 | бит n = 1 — пускать линию n в NVIC | Э1 |
| EXTI | RTSR / FTSR | 0x4001 0408 / 040C | ловить фронт 0→1 / 1→0 | Э1 (RTSR) |
| EXTI | SWIER | 0x4001 0410 | запись 1 — вызвать прерывание программно | для отладки |
| EXTI | PR | 0x4001 0414 | флажки; сброс записью 1 | Э1, в обработчике |
| NVIC | ISER0 / ISER1 | 0xE000 E100 / E104 | запись 1 — разрешить вход | Э1 |
| NVIC | ICER0 / ICER1 | 0xE000 E180 / E184 | запись 1 — запретить вход | нет |
| NVIC | ISPR / ICPR | 0xE000 E200 / E280 | запросы: поставить / стереть | смотреть в отладчике |
| NVIC | IABR | 0xE000 E300 | какой обработчик сейчас выполняется | смотреть в отладчике |
| NVIC | IPR | 0xE000 E400 + IRQ | байт приоритета, значимы биты 7:4, писать STRB | Э2 |
| SCB | AIRCR | 0xE000 ED0C | PRIGROUP в битах 10:8, ключ 0x05FA в старших 16 битах | Э2 |
RM0008: Table 3, (базы); §9.4.3–9.4.6, (EXTICR); §10.3, (EXTI). PM0056: Table 33, (адреса ядра); §4.3, (NVIC); §4.4.5, (AIRCR).
С3 · справочник
SysTick — системный таймер
SysTick упомянут в цели работы и, скорее всего, будет на контрольной. Это 24-битный вычитающий счётчик в ядре Cortex-M3, одинаковый у всех производителей. Он считает вниз от значения LOAD до нуля, в момент нуля вызывает исключение 15 (вектор 0x3C) и перезагружается сам.
| Адрес | Регистр | Что там |
|---|---|---|
| 0xE000 E010 | STK_CTRL | бит 0 ENABLE — запуск; бит 1 TICKINT — вызывать исключение; бит 2 CLKSOURCE — 1: такт ядра, 0: такт / 8; бит 16 COUNTFLAG — «досчитал до нуля» |
| 0xE000 E014 | STK_LOAD | с какого числа считать, до 0x00FF FFFF |
| 0xE000 E018 | STK_VAL | текущее значение; любая запись обнуляет |
| 0xE000 E01C | STK_CALIB | калибровка, только чтение |
1 мс при 8 МГц: LOAD = 8 000 − 1 = 7 999
максимум: 224 / 8 МГц ≈ 2,1 с
PM0056 §4.5 «SysTick timer», : «The processor has a 24-bit system timer»; §4.5.1 STK_CTRL, ; адрес — Table 33, .
SUBS/BNE, и их длительность зависит от тактов на итерацию: 3 на плате, 4 в симуляторе. SysTick считает такты напрямую, поэтому миллисекунда будет миллисекундой везде. И это правильная основа для антидребезга.С4 · справочник
Отладка прерываний в Keil
- Peripherals → General Purpose I/O → GPIOA, строка Pins: щелчок по биту 3 — это нажатие PA3, как в лабе 1. Для прерывания важен именно переход: щёлкнуть, чтобы бит стал 1.
- Peripherals → External Interrupt/Event Controller (окно EXTI): видно IMR, RTSR и флаги PR. После «нажатия» в PR должен встать бит линии.
- Peripherals → Core Peripherals → Nested Vectored Interrupt Controller: таблица IRQ с колонками «разрешено», «ожидает», «активно» и приоритетами. Если флаг в EXTI есть, а здесь IRQ не разрешён — ошибка в ISER.
- Точка останова (F9) на первой команде обработчика. Остановился — значит, вся цепочка работает.
- Окно Registers: в обработчике LR =
0xFFFFFFF9, а в поле Mode — Handler. Это и есть доказательство «мы в прерывании». - Memory по адресу SP: восемь слов стек-фрейма, среди них адрес возврата.
1 << n в EXTI_SWIER или бита IRQ в NVIC_ISPR. Удобно проверить обработчик до того, как собрана схема.С5 · справочник
Подводные камни
| Симптом | Причина | Что делать | |
|---|---|---|---|
| частое | Обработчик не вызывается, флаг в EXTI_PR стоит | IRQ не разрешён в NVIC или записан не в тот ISER | IRQ 40 — это ISER1, бит 8 |
| частое | Флага в EXTI_PR нет | не выбран фронт, закрыта маска IMR или AFIO не тактируется | проверить RTSR, IMR, бит 0 RCC_APB2ENR |
| частое | Программа «висит» в обработчике | не сброшен EXTI_PR, или сброшен нулём | записать 1 << n |
| асм | HardFault при первом нажатии | таблица векторов короче нужного, вектор 0 или мусор | дотянуть таблицу до 0xE0, проверить в дизассемблере |
| асм | После обработчика программа уходит в никуда | BL внутри обработчика затёр LR | PUSH {LR} … POP {PC} |
| асм | После прерывания главная программа «сошла с ума» | обработчик изменил R4–R11 | сохранить их в стек или не трогать |
| логика | PB5 срабатывает от PA5 | не записан EXTICR2 — после сброса линия 5 смотрит на порт A | поле 7:4 = 0001 |
| логика | Приоритеты «не работают» | байт записан без сдвига на 4, или AIRCR без ключа | STRB значения p << 4; 0x05FA в старшие биты |
| логика | Цвет переключается через раз | дребезг | раздел 16 |
| железо | Всё работает в Keil, на плате зелёный мёртв | PB3 всё ещё занят JTAG | SWJ_CFG = 010, как в лабе 1 |
С6 · шпаргалка
Вариант 8: все числа на одной странице
| Кнопка | Цвет | Линия | AFIO | Фронт | Вектор | IRQ | ISER | IPR |
|---|---|---|---|---|---|---|---|---|
| PA3 | PA7 | EXTI3 | EXTICR1 [15:12] = 0 | RTSR бит 3 | EXTI3, 0x64 | 9 | E100, бит 9 | E409 |
| PA10 | PB3 | EXTI10 | EXTICR3 [11:8] = 0 | RTSR бит 10 | EXTI15_10, 0xE0 | 40 | E104, бит 8 | E428 |
| PB5 | PB15 | EXTI5 | EXTICR2 [7:4] = 1 | RTSR бит 5 | EXTI9_5, 0x9C | 23 | E100, бит 23 | E417 |
1 << (n + 16) в BSRR), погасить — установить.С7 · самопроверка
Задачи и вопросы к контрольной
Задачи
З1Кнопка на PC13, уровень L. Какой регистр AFIO, поле, значение, фронт, вектор?
Линия 13 → EXTICR4 (13 / 4 = 3, регистр четвёртый), поле 4·(13 mod 4) = биты 7:4, значение 0010 (порт C). Уровень L → спадающий фронт → FTSR бит 13. Вектор EXTI15_10, IRQ 40, адрес 0xE0.
З2По какому адресу лежит вектор IRQ 23 и сколько слов должно быть в таблице, чтобы он там оказался?
4 × (23 + 16) = 156 = 0x9C. Слов: 23 + 16 + 1 = 40 (от 0 до 39 включительно).
З3Разрешить IRQ 40. Куда и что записать?
40 / 32 = 1 → ISER1 = 0xE000E104. 40 mod 32 = 8 → записать 0x100.
З4PRIGROUP = 5. У A приоритет 0x60, у B — 0x40. A работает, приходит B. Что будет?
Значимые 4 бита: A = 6 = 01|10, B = 4 = 01|00. При PRIGROUP 5 группа — два старших бита: у обоих 01. Группы равны → B не вытесняет, ждёт в pending. Субприоритет B (00) меньше, чем у A (10), но это важно, только если в очереди несколько.
З5Что окажется в LR в первой команде обработчика, если прерывание пришло во время главного цикла? А если во время другого обработчика?
0xFFFFFFF9 — возврат в Thread mode со стеком MSP. Во время другого обработчика — 0xFFFFFFF1, возврат в Handler mode. PM0056 Table 17, стр. 39.
З6Обработчик сбрасывает флаг так: LDR R1,[R0] / BIC R1,R1,#8 / STR R1,[R0], где R0 = EXTI_PR. Что не так?
Биты PR сбрасываются записью единицы. BIC запишет в бит 3 ноль — ничего не произойдёт, а все остальные установленные флаги будут записаны единицами и тоже сброшены. Двойная ошибка: свой флаг не сброшен, чужие потеряны. Правильно: MOV R1,#8 / STR R1,[R0].
З7Приоритет записали как MOV R1,#2 / STRB R1,[R0] по адресу IPR. Какой приоритет получился?
0. У STM32F103 реализованы только биты 7:4, запись в младшие пропадает. Нужно MOV R1,#(2<<4).
З8Сколько времени до первого исключения SysTick при LOAD = 7999, CLKSOURCE = 0 и частоте ядра 8 МГц?
CLKSOURCE = 0 — это AHB / 8 = 1 МГц. (7999 + 1) / 1 МГц = 8 мс, а не 1 мс. Ловушка на невнимательность.
З9Что окажется в прошивке от строк X EQU 5, DCD X, DCD 1, 2? Сколько байт и какие?
EQU — ни одного байта. DCD X — 4 байта: 05 00 00 00. DCD 1, 2 — 8 байт: 01 00 00 00 02 00 00 00. Всего 12 байт, младшим байтом вперёд.
Вопросы на понимание
В1Чем прерывание отличается от опроса? Когда опрос лучше?
Опрос тратит время процессора и может пропустить событие между проверками; прерывание реагирует сразу и не нагружает программу, но требует настройки и аккуратности с регистрами. Опрос лучше, когда событие частое и регулярное, а реакция не критична, или для сложного антидребезга.
В2Что процессор сохраняет сам при входе в прерывание? Почему обработчик на Cortex-M3 может быть обычной функцией?
xPSR, PC, LR, R12, R3–R0 — восемь слов. Это ровно те регистры, которые по соглашению о вызовах ARM (AAPCS) может портить любая функция. Значит, обработчик-функция портит только то, что уже сохранено, а R4–R11 функция обязана сохранять сама — как и любая другая.
В3Как процессор понимает, что BX LR — выход из прерывания, а не из подпрограммы?
По значению: в LR лежит EXC_RETURN вида 0xFFFFFFFx. Загрузка такого числа в PC в Handler mode запускает последовательность возврата — восстановление стек-фрейма.
В4Зачем нужен блок AFIO в системе внешних прерываний?
Линий EXTI 16, а выводов больше. AFIO_EXTICR выбирает, вывод какого порта подключён к каждой линии.
В5Почему у EXTI0…EXTI4 свои векторы, а у 5–9 и 10–15 общие? Как тогда узнать источник?
Экономия позиций в таблице векторов. Источник определяется по регистру EXTI_PR: какой бит стоит, та линия и сработала.
В6Чем приоритет группы отличается от субприоритета?
Группа определяет вытеснение: работающий обработчик прерывается только прерыванием с меньшим номером группы. Субприоритет определяет лишь порядок среди ожидающих прерываний одной группы и никогда не вытесняет.
В7Зачем в SCB_AIRCR ключ 0x05FA?
Защита от случайной записи: в том же регистре есть бит программного сброса чипа. Без ключа запись игнорируется.
В8Что такое tail-chaining и late-arriving?
Tail-chaining — переход от одного обработчика к следующему ожидающему без восстановления и повторного сохранения стека. Late-arriving — если во время стекинга пришло более важное прерывание, процессор сразу берёт его вектор, а стек сохраняется тот же.
В9Почему флаг EXTI_PR сбрасывается записью единицы, а не нуля?
Чтобы сбрасывать флаг одной записью, без чтения-модификации-записи. Иначе можно потерять флаг другой линии, установившийся между чтением и записью. Та же идея, что у BSRR.
В10Что будет, если в обработчике вызвать подпрограмму через BL и выйти через BX LR?
BL перезапишет LR адресом возврата из подпрограммы, EXC_RETURN потеряется, и BX LR вернёт управление внутрь обработчика, а не в прерванную программу. Нужно сохранить LR в стек.