Прерывания на ассемблере

МИФИ · МПС · лабораторная 2 · STM32F103C8T6

Прерывания на ассемблере

Одна история: ты нажимаешь кнопку PA3 — и через несколько микросекунд процессор бросает свой цикл и выполняет твой код. Разберём, через какие блоки чипа проходит нажатие, что каждый из них делает с сигналом и как это совпадает с тем, что было на лекциях.

Вариант 8 R = PA7, G = PB3, B = PB15 Кнопки PA3, PA10, PB5 — все H Источники RM0008 rev 21, PM0056 rev 6, материалы к вопросу 13
Справа — стенд. Схема чипа от кнопки до светодиода. Пока читаешь, подсвечивается блок, о котором идёт речь. Нажимать кнопку можно в любой момент. На телефоне стенд раскрывается сверху. Номера страниц в строках «Источник» кликабельны — слева откроется страница оригинала, цитата подсвечена.
Каждая глава про блок устроена одинаково: зачем он → слова, которые встретятся → как он работает → где он лежит в памяти (база + смещение) → его регистр по битам → числа для твоих трёх кнопок → что с этим в твоей 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.

1 — бит установленрамка — полештриховка — бит не используется

адресГде лежит регистр

Регистр периферии — не ячейка внутри процессора, а ячейка памяти по своему адресу: процессор пишет в неё той же командой 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
Клей с ЛР1AFIO — самый первый блок шины APB2, её «нулевой» адрес. За ним подряд, по килобайту: EXTI, GPIOA, GPIOB. И тактирование включается в регистре той шины, на которой сидит блок: AFIO, GPIOA и GPIOB лежат на APB2 — поэтому их биты в 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 секунды в первой версии. С прерыванием главный цикл может быть пустым — о нажатии процессору сообщит железо.

словаЧто понадобится

прерывание
событие, из-за которого процессор оставляет текущую работу и выполняет специальную процедуру
обработчик
эта процедура — твой код, который выполнится по нажатию
контекст
всё, что нужно прерванной программе, чтобы потом продолжить: регистры, флаги, адрес следующей команды
вектор
адрес обработчика, записанный в таблицу
КПП
контроллер приоритетных прерываний: принимает запросы, фильтрует их масками, выбирает самый важный и сообщает процессору

какПять этапов и КПП

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

1

Фиксация факта — кто-то замечает событие и запоминает его. → EXTI, глава 04

2

Сохранение контекста в стек. → ядро, глава 07

3

Передача управления обработчику через таблицу векторов. → глава 08

4

Обработка — твой код. → глава 09

5

Восстановление контекста и возврат. → глава 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

твои кнопки

КнопкаРегистрБитЗначение при нажатии
PA3GPIOA_IDR30x0000 0008
PB5GPIOB_IDR50x0000 0020
PA10GPIOA_IDR100x0000 0400
В твоей lab2.sГотово ещё с ЛР1: все три кнопки — тетрада 8, подтяжка вниз.

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:

  1. Один переключатель — одна hex-цифра. Переключатель линии занимает 4 бита, то есть ровно одну цифру в шестнадцатеричной записи регистра. Что записано в цифре, тот порт и выбран: 0 — A, 1 — B, 2 — C, 3 — D.
  2. В регистре — четыре переключателя. Из 8 hex-цифр регистра используются только правые четыре, левые всегда нули. Значит, один регистр обслуживает 4 линии, а для 16 линий нужно четыре регистра: EXTICR1 — линии 0–3, EXTICR2 — 4–7, EXTICR3 — 8–11, EXTICR4 — 12–15. Коротко: номер регистра = линия // 4 + 1. Линия 13: 13 // 4 = 3 → EXTICR4.
  3. Какая цифра твоя. Цифры считаются справа, с нуля; первая линия регистра — в цифре 0. Поэтому цифра = линия − первая линия регистра = линия % 4. Линия 13: 13 − 12 = 13 % 4 = 1. Линия 5 в EXTICR2, который начинается с линии 4: цифра 5 − 4 = 1.
  4. Записать значение. В цифру 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

твои кнопки

КнопкаЛинияРегистрЦифра / битыЗначениеРегистр целиком
PA33EXTICR13 − 0 = 3 · 15:120 (A)0x0000 0000
PB55EXTICR25 − 4 = 1 · 7:41 (B)0x0000 0010
PA1010EXTICR310 − 8 = 2 · 11:80 (A)0x0000 0000
В твоей lab2.sСтроки 114–118: 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Куда писать
PA330x0000 0008RTSR (+0x08), IMR (+0x00); в обработчике — PR (+0x14)
PB550x0000 0020то же
PA10100x0000 0400то же
все три0x0000 04280x4001 0408 (RTSR) и 0x4001 0400 (IMR) ← 0x428
В твоей lab2.sСтроки 102–112: 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 не равен номеру линии EXTI: у PA3 это вход 9, у PB5 — 23, у PA10 — 40. Откуда эти числа и как их найти для любой ножки — следующая глава, 06 «Нумерация прерываний».

какАлгоритм КПП с лекции — и что из него нужно тебе

Шаг КПП с лекцииЧто делает 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

  1. Сигнал. EXTI поднял флажок в PR — и держит на входе 9 единицу, пока флажок стоит. Это не короткий импульс, а уровень.
  2. Запомнить. NVIC видит единицу на входе 9 и переводит его в pending. Разрешён вход или нет — неважно.
  3. Решить. NVIC перебирает все входы в состоянии pending, которые разрешены в ISER, и выбирает самый важный (меньший номер приоритета; при равенстве — меньший номер входа). Если он важнее того, что процессор делает сейчас, — прерываем. Главную программу прерывает любой разрешённый вход.
  4. Передать. NVIC сообщает ядру номер входа. Ядро сохраняет регистры в стек (глава 07), берёт адрес из строки 16 + 9 = 25 таблицы векторов (глава 08) и прыгает туда. В этот момент железо само снимает pending и ставит active.
  5. Обработать. Твой код переключает светодиод и сбрасывает флажок в EXTI_PR — единица на входе 9 пропадает.
  6. Вернуться. 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)РегистрБитЗаписать
PA39ISER0, 0xE000 E10090x0000 0200
PB523, общий 5–9ISER0, 0xE000 E100230x0080 0000
PA1040, общий 10–15ISER1, 0xE000 E10440 − 32 = 80x0000 0100
В твоей lab2.sЗаписей в ISER пока нет. Поэтому нажатие PA3 доходит до NVIC, встаёт в «ожидает» — и висит там. Программа ничего не замечает.

PM0056 §4.3, ; Table 41, — какие регистры отвечают за какие входы; §4.1 Table 33, — адреса NVIC 0xE000E100–0xE000E4EF. Вход EXTI3 = 9 — RM0008 Table 63, .

06 · клей между блоками

Нумерация прерываний: от ножки до строки таблицы

зачемОдин сигнал — четыре номера

Пока нажатие идёт по чипу, у него четыре раза меняется номер. У каждого блока своя нумерация, и перевод из одной в другую делает сам блок по своему правилу. В коде нужны все четыре: номер линии — для AFIO и EXTI, номер входа — для ISER, строка — чтобы поставить DCD на нужное место, адрес — чтобы проверить себя в отладчике. Эта глава собирает все четыре правила вместе, и дальше числа для любой кнопки можно найти самому.

ножкаPA3
AFIO
номер ножки
линия EXTI3
таблица ST
вход NVIC, IRQ9
+ 16
строка векторов25
× 4
адрес строки0x64

слова

линия 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
0PA0, PB0, PC0 … — одна из них6EXTI0220x58
1PA1, PB1, PC1 …7EXTI1230x5C
2PA2, PB2, PC2 …8EXTI2240x60
3PA3, PB3, PC3 …9EXTI3250x64
4PA4, PB4, PC4 …10EXTI4260x68
5, 6, 7, 8, 9Px5 … Px923 — общийEXTI9_5390x9C
10, 11 … 15Px10 … Px1540 — общийEXTI15_10560xE0
16детектор питания PVD1PVD170x44
17будильник RTC41RTCAlarm570xE4
18пробуждение по USB42USBWakeup580xE8

Как читать имена 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ИмяЧто это
00x00——начальное значение SP — не обработчик, а адрес вершины стека
10x04—Resetсброс: отсюда стартует программа
20x08—NMIнемаскируемое прерывание (сбой тактирования HSE)
30x0C—HardFaultлюбая авария, которую не обработал никто другой
40x10—MemManageнарушение защиты памяти
50x14—BusFaultошибка доступа к шине / памяти
60x18—UsageFaultнеизвестная инструкция, недопустимое состояние
70x1C——резерв
80x20——резерв
90x24——резерв
100x28——резерв
110x2C—SVCallвызов системы командой SVC
120x30—DebugMonмонитор отладки
130x34——резерв
140x38—PendSVотложенный системный вызов (планировщики ОС)
150x3C—SysTickсистемный таймер ядра — пригодится для антидребезга
160x400WWDGоконный сторожевой таймер
170x441PVDконтроль питания (через линию EXTI 16)
180x482TAMPERвскрытие корпуса (вывод TAMPER)
190x4C3RTCчасы реального времени
200x504FLASHконтроллер флеш-памяти
210x545RCCблок тактирования и сброса
220x586EXTI0линия EXTI 0 — ножки Px0
230x5C7EXTI1линия EXTI 1 — ножки Px1
240x608EXTI2линия EXTI 2 — ножки Px2
250x649EXTI3линия EXTI 3 — ножки Px3
260x6810EXTI4линия EXTI 4 — ножки Px4
270x6C11DMA1_Channel1DMA1, канал 1
280x7012DMA1_Channel2DMA1, канал 2
290x7413DMA1_Channel3DMA1, канал 3
300x7814DMA1_Channel4DMA1, канал 4
310x7C15DMA1_Channel5DMA1, канал 5
320x8016DMA1_Channel6DMA1, канал 6
330x8417DMA1_Channel7DMA1, канал 7
340x8818ADC1_2АЦП 1 и 2 (общий вход)
350x8C19USB_HP_CAN_TXUSB высокий приоритет или передача CAN
360x9020USB_LP_CAN_RX0USB низкий приоритет или приём CAN (FIFO 0)
370x9421CAN_RX1приём CAN (FIFO 1)
380x9822CAN_SCECAN: состояние и ошибки
390x9C23EXTI9_5линии EXTI 5–9 — ножки Px5…Px9, один вход на пять линий
400xA024TIM1_BRKтаймер 1: аварийный стоп (break)
410xA425TIM1_UPтаймер 1: переполнение (update)
420xA826TIM1_TRG_COMтаймер 1: триггер и коммутация
430xAC27TIM1_CCтаймер 1: захват / сравнение
440xB028TIM2таймер 2
450xB429TIM3таймер 3
460xB830TIM4таймер 4
470xBC31I2C1_EVI²C 1: события
480xC032I2C1_ERI²C 1: ошибки
490xC433I2C2_EVI²C 2: события
500xC834I2C2_ERI²C 2: ошибки
510xCC35SPI1SPI 1
520xD036SPI2SPI 2
530xD437USART1USART 1
540xD838USART2USART 2
550xDC39USART3USART 3
560xE040EXTI15_10линии EXTI 10–15 — ножки Px10…Px15, один вход на шесть линий
570xE441RTCAlarmбудильник RTC (через линию EXTI 17)
580xE842USBWakeupпробуждение по 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 их не различить.

твои кнопки

ШагPA3PB5PA10Правило
линия EXTI3510номер ножки
вход NVIC, IRQ923, общий 5–940, общий 10–15таблица: линии 0–4 → +6, 5–9 → 23, 10–15 → 40
бит в ISERISER0, бит 9ISER0, бит 23ISER1, бит 8вход 32 и выше — в ISER1, бит = вход − 32
строка таблицы векторов253956IRQ + 16
адрес строки0x640x9C0xE0строка × 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

8 регистров × 4 байта = 32 байта = 0x20
SP до: 0x2000 1000 (твой STACK_TOP) → SP после: 0x2000 1000 − 0x20 = 0x2000 0FE0
по адресу 0x2000 0FE0 лежит R0, по 0x2000 0FFC — xPSR
Для твоего кодаГлавная программа держит в R4–R11 состояния кнопок и адреса. Когда кнопки переедут в прерывания, главный цикл станет пустым и это будет неважно. Но пока опрос и прерывания живут вместе, обработчик не должен трогать эти регистры.

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

строка 25 × 4 байта = 100 = 6·16 + 4 = 0x64
во флеше: 0x0800 0000 + 0x64 = 0x0800 0064 — туда процессор и смотрит
строка 39 × 4 = 156 = 9·16 + 12 = 0x9C · строка 56 × 4 = 224 = 14·16 + 0 = 0xE0
В твоей lab2.s4 строки из 26 нужных для PA3.

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-битное число
ДирективаСколько байтПример
DCB1DCB 0x41, DCB "hi", 0
DCW2DCW 0x1234
DCD4DCD 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

Почему не BICПусть подняты флажки линий 3 и 5: PR = 0x28. «Прочитать, BIC #8, записать» даст запись 0x20 — единица в бите 5 сбросит чужой флажок линии 5, а ноль в бите 3 свой не сбросит. Ровно наоборот тому, что хотел.
КнопкаЗаписать в EXTI_PRПроверять бит в PR?
PA30x0000 0008можно не проверять — вход только для линии 3
PB50x0000 0020да, вход общий для 5–9
PA100x0000 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
AFIOEXTICR1, цифра 3 = 0есть строки 114–118
EXTIRTSR и IMR, бит 3есть строки 102–112
NVIC0x200 в ISER0 (0xE000 E100)нет
таблица векторовдописать до строки 25 (0x64)нет
обработчик0x8 в EXTI_PR, переключить красный, выйтинет
главный циклубрать опрос кнопокпотом

Три красные строки — три шага. После каждого на стенде видно, как сигнал продвигается на блок дальше. На плате проверять удобнее точкой останова на первой строке обработчика: остановился — вся цепочка работает.

12 · остальные кнопки

PB5 и PA10 — то же с другими числами

PA3PB5PA10
AFIOEXTICR1 = 0 (по умолчанию)EXTICR2 = 0x10 — обязательноEXTICR3 = 0 (по умолчанию)
EXTI: RTSR, IMR1 << 31 << 51 << 10
NVICISER0 ← 0x200ISER0 ← 0x800000ISER1 ← 0x100
строка / адрес вектора25 · 0x6439 · 0x9C, общий 5–956 · 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]—1610x05FA0300
4[7:5][4]820x05FA0400
5[7:6][5:4]440x05FA0500
6[7][6:4]280x05FA0600
7—[7:4]1160x05FA0700

PM0056 §4.4.5 «Application interrupt and reset control register (SCB_AIRCR)», , Table 45 «Priority grouping». Адрес 0xE000 ED0C: база SCB 0xE000ED00 (Table 33, ) + смещение 0x0C.

Ключ 0x05FAВ старшие 16 бит AIRCR при записи обязательно пишется 0x05FA, иначе запись игнорируется целиком — защита от случайного сброса чипа, ведь в том же регистре есть бит SYSRESETREQ. Поэтому AIRCR пишут готовой константой 0x05FA0000 | (PRIGROUP << 8), а не через ORR к прочитанному: при чтении в старшей половине вернётся другое число, 0xFA05 (PM0056, стр. 134).
Почему PRIGROUP 0…3 одинаковыУ STM32F103 реализованы только биты 7:4 приоритета. Значения 0, 1, 2 отдавали бы под субприоритет биты 0…2, которых физически нет, — эффект такой же, как у 3: все 4 бита идут в группу. После сброса PRIGROUP = 0, значит, по умолчанию субприоритетов нет. Методичка поэтому и пишет, что смысл имеют значения от 3 до 7.

Кто кого вытеснит

Задай разбиение и приоритеты двух прерываний: одно уже выполняется, второе приходит во время него. Числа — это 4 значимых бита, от 0 до 15; в регистр IPR пойдёт значение, сдвинутое на 4.

14 · эксперимент 2

Четыре сценария на временной шкале

В эксперименте 2 каждый обработчик зажигает свой цвет, ждёт несколько секунд и гасит. Задержка нужна, чтобы успеть нажать вторую кнопку, пока первый обработчик ещё работает. Вот что должно получиться.

1. Вытеснение: B из группы важнее 2. Одна группа: B ждёт, потом tail-chaining 3. Во время C пришли A и B одной группы: первым тот, у кого субприоритет меньше 4. Всё равно: порядок по номеру IRQ, никто никого не прерывает mainобраб. mainобраб. mainобраб. mainобраб. A B вытеснил A A продолжается нажата B A до конца B сразу после A нажата B → pending C, группа важнее B: суб. 0 A: суб. 1 A B PB5, IRQ 23 PA3, IRQ 9 PA10, IRQ 40 нажаты PA10 и PA3
Цвет полосы — цвет, который горит. Оранжевые линии — моменты нажатий. В сценарии 4 обе кнопки нажаты, пока идёт обработчик PB5: после него первым выполняется PA3, потому что у EXTI3 меньше номер IRQ, хотя PA10 нажали раньше.
Сценарий методичкиЧто настроитьЧто увидишь
1. Прерывание с бо́льшим приоритетом группы во время меньшего — и наоборотPRIGROUP 5; A = группа 2, B = группа 0B прерывает A: на время B горят оба цвета — A не успел погасить свой. Если наоборот, важнее A, то B ждёт до конца A
2. Та же группа, разный субприоритетодна группа, суб. 0 и 1никто никого не прерывает; второй начнётся, как только закончится первый
3. Два прерывания с разными суб. во время более важногоC — группа 0; A, B — группа 1, суб. 1 и 0после C первым идёт B (меньший суб.), даже если A нажали раньше
4. Всё равновсе приоритеты 0как сценарий 2, а порядок в очереди — по номеру IRQ
Наблюдать удобноВ сценарии 1 на светодиоде видно вложенность: во время работы обработчика B горят сразу два цвета — B зажёг свой, а цвет A ещё не погашен, потому что A прерван посередине. Когда B закончит, его цвет погаснет, а цвет A будет гореть до конца оставшейся задержки A. Это наглядное доказательство, что A продолжился с того же места.
Флаг и повторЕсли флаг сбрасывается в начале обработчика, повторное нажатие той же кнопки во время его работы снова поднимет флаг — и после выхода обработчик запустится ещё раз. Больше одного «запомненного» нажатия не бывает: второе, третье и так далее сольются в одно. Если же сбрасывать флаг в конце (глава 15), повторные нажатия во время работы поглощаются целиком.

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красный90xE000 E409STRB — только байт. Запись словом (STR) затёрла бы приоритеты трёх соседних входов
PB5синий230xE000 E417
PA10зелёный400xE000 E428
Частая ошибкаЗаписать в байт просто номер группы, например 1. Он попадёт в биты 3:0, которые у STM32F103 не реализованы и читаются как 0, — и у всех кнопок останется приоритет 0. Признак: в любом сценарии никто никого не прерывает. Проверка в Keil: окно Memory по адресу 0xE000E409 — там должно быть 0x40, 0x80 и т. п., а не 0x01.

шаг 3Байты для четырёх сценариев

PRIGROUP = 5 во всех четырёх, меняются только три байта. Удобно завести три EQU и перед каждым прогоном менять только их. Буквы A/B/C — как на временных диаграммах в главе 14.

СценарийPA3 · E409PB5 · E417PA10 · 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. Всё равно0x000x000x00PB5, во время синего — PA10, потом PA3. После синего первым красный (IRQ 9), потом зелёный (IRQ 40): при равенстве решает номер входа

шаг 4Обработчик: зажечь, подождать, погасить

Порядок действий в каждом из трёх обработчиков:

  1. Сохранить LR в стек — PUSH {LR}. Дальше будет BL Delay, а BL перезапишет LR своим адресом возврата, и EXC_RETURN потеряется.
  2. Зажечь свой цвет через BSRR. Светодиод с общим анодом, поэтому «зажечь» = сбросить бит: в BSRR пишется бит n + 16.
  3. Задержка на несколько секунд: число в R3 и BL Delay.
  4. Погасить: в BSRR бит n.
  5. Сбросить флаг в EXTI_PR записью 1 и сразу прочитать PR обратно (почему в конце — ниже).
  6. Выйти — POP {PC}: в PC попадает сохранённый EXC_RETURN, и это выход из прерывания, как BX LR.
ЦветРегистрЗажечь (BSRR)Погасить (BSRR)
R — PA7GPIOA_BSRR 0x4001 08101 << 231 << 7
G — PB3GPIOB_BSRR 0x4001 0C101 << 191 << 3
B — PB15GPIOB_BSRR 0x4001 0C101 << 311 << 15
Почему BSRR, а не EOR в ODRВ эксперименте 1 обработчики не прерывали друг друга, и LDR/EOR/STR в ODR было безопасно. Теперь прерывают. Пусть зелёный обработчик (PB3) прочитал GPIOB_ODR, и в этот момент его вытеснил синий (PB15): синий зажёг свой цвет и вернулся. Зелёный запишет обратно старое значение ODR — и погасит синий, хотя тот ещё должен гореть. PB3 и PB15 в одном порту, так что это случится на твоём стенде. BSRR меняет только записанные биты одной записью, без чтения, — гонки нет.
Delay и вложенностьЕсли синий обработчик прервал красный посреди Delay, у обоих счётчик в R3. Это не конфликт: при входе в синий ядро само сохранило R3 красного в стек, при выходе — восстановило, и красный досчитает с того же числа. Поэтому в Delay и в обработчиках достаточно R0–R3 и R12. Вложение стоит 32 байта стека на уровень плюс 4 на PUSH {LR} — при стеке 4 КБ хватает с запасом.
Твой Delay игнорирует параметрВ сданном коде внутри 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 мкФ параллельно кнопкетриггер Шмитта на входе довершит дело
Для эксперимента 2Там обработчик и так ждёт несколько секунд, и дребезг «съедается» задержкой. Проблема видна только в эксперименте 1 с быстрым обработчиком — и видна только на плате: в симуляторе Keil кнопка нажимается идеально.

Справочник

Когда рассказ прочитан

Здесь то, к чему возвращаются во время работы: калькулятор чисел для любой кнопки, все регистры одной таблицей, SysTick, отладка, ловушки, шпаргалка варианта и вопросы к контрольной.

С1 · справочник

Калькулятор: вывод → все регистры прерывания

Какие числа нужны для одной кнопки

Выбери вывод и фронт. Открыт PA3 варианта 8. Проверь себя: сначала посчитай на бумаге, потом сравни.


 

С2 · справочник

Все регистры одним списком

Только то, что встречается в лабе. Колонка «нужно» — что пишешь сам в эксперименте 1 (Э1) и 2 (Э2).

БлокРегистрАдресЧто в нёмНужно
RCCAPB2ENR0x4002 1018бит 0 — тактирование AFIOЭ1
AFIOEXTICR1…40x4001 0008 + 4·kпо 4 бита на линию: 0000 — A, 0001 — BЭ1 (для PB5 обязательно)
EXTIIMR0x4001 0400бит n = 1 — пускать линию n в NVICЭ1
EXTIRTSR / FTSR0x4001 0408 / 040Cловить фронт 0→1 / 1→0Э1 (RTSR)
EXTISWIER0x4001 0410запись 1 — вызвать прерывание программнодля отладки
EXTIPR0x4001 0414флажки; сброс записью 1Э1, в обработчике
NVICISER0 / ISER10xE000 E100 / E104запись 1 — разрешить входЭ1
NVICICER0 / ICER10xE000 E180 / E184запись 1 — запретить входнет
NVICISPR / ICPR0xE000 E200 / E280запросы: поставить / стеретьсмотреть в отладчике
NVICIABR0xE000 E300какой обработчик сейчас выполняетсясмотреть в отладчике
NVICIPR0xE000 E400 + IRQбайт приоритета, значимы биты 7:4, писать STRBЭ2
SCBAIRCR0xE000 ED0CPRIGROUP в битах 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 E010STK_CTRLбит 0 ENABLE — запуск; бит 1 TICKINT — вызывать исключение; бит 2 CLKSOURCE — 1: такт ядра, 0: такт / 8; бит 16 COUNTFLAG — «досчитал до нуля»
0xE000 E014STK_LOADс какого числа считать, до 0x00FF FFFF
0xE000 E018STK_VALтекущее значение; любая запись обнуляет
0xE000 E01CSTK_CALIBкалибровка, только чтение
период = (LOAD + 1) / f
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

  1. Peripherals → General Purpose I/O → GPIOA, строка Pins: щелчок по биту 3 — это нажатие PA3, как в лабе 1. Для прерывания важен именно переход: щёлкнуть, чтобы бит стал 1.
  2. Peripherals → External Interrupt/Event Controller (окно EXTI): видно IMR, RTSR и флаги PR. После «нажатия» в PR должен встать бит линии.
  3. Peripherals → Core Peripherals → Nested Vectored Interrupt Controller: таблица IRQ с колонками «разрешено», «ожидает», «активно» и приоритетами. Если флаг в EXTI есть, а здесь IRQ не разрешён — ошибка в ISER.
  4. Точка останова (F9) на первой команде обработчика. Остановился — значит, вся цепочка работает.
  5. Окно Registers: в обработчике LR = 0xFFFFFFF9, а в поле Mode — Handler. Это и есть доказательство «мы в прерывании».
  6. Memory по адресу SP: восемь слов стек-фрейма, среди них адрес возврата.
Без кнопкиПрерывание можно вызвать и программно — записью 1 << n в EXTI_SWIER или бита IRQ в NVIC_ISPR. Удобно проверить обработчик до того, как собрана схема.
ПроверьНазвания окон — по Keil µVision 5 с пакетом STM32F1xx_DFP. В твоей версии пункты могут называться чуть иначе, но разделы «Peripherals» и «Core Peripherals» есть всегда.

С5 · справочник

Подводные камни

СимптомПричинаЧто делать
частоеОбработчик не вызывается, флаг в EXTI_PR стоитIRQ не разрешён в NVIC или записан не в тот ISERIRQ 40 — это ISER1, бит 8
частоеФлага в EXTI_PR нетне выбран фронт, закрыта маска IMR или AFIO не тактируетсяпроверить RTSR, IMR, бит 0 RCC_APB2ENR
частоеПрограмма «висит» в обработчикене сброшен EXTI_PR, или сброшен нулёмзаписать 1 << n
асмHardFault при первом нажатиитаблица векторов короче нужного, вектор 0 или мусордотянуть таблицу до 0xE0, проверить в дизассемблере
асмПосле обработчика программа уходит в никудаBL внутри обработчика затёр LRPUSH {LR} … POP {PC}
асмПосле прерывания главная программа «сошла с ума»обработчик изменил R4–R11сохранить их в стек или не трогать
логикаPB5 срабатывает от PA5не записан EXTICR2 — после сброса линия 5 смотрит на порт Aполе 7:4 = 0001
логикаПриоритеты «не работают»байт записан без сдвига на 4, или AIRCR без ключаSTRB значения p << 4; 0x05FA в старшие биты
логикаЦвет переключается через раздребезграздел 16
железоВсё работает в Keil, на плате зелёный мёртвPB3 всё ещё занят JTAGSWJ_CFG = 010, как в лабе 1

С6 · шпаргалка

Вариант 8: все числа на одной странице

КнопкаЦветЛинияAFIOФронтВекторIRQISERIPR
PA3PA7EXTI3EXTICR1 [15:12] = 0RTSR бит 3EXTI3, 0x649E100, бит 9E409
PA10PB3EXTI10EXTICR3 [11:8] = 0RTSR бит 10EXTI15_10, 0xE040E104, бит 8E428
PB5PB15EXTI5EXTICR2 [7:4] = 1RTSR бит 5EXTI9_5, 0x9C23E100, бит 23E417
RCC_APB2ENR0x40021018 · биты 0, 2, 3
AFIO_MAPR0x40010004 · SWJ_CFG = 010
AFIO_EXTICR1/2/30x40010008 / 0C / 10
EXTI_IMR0x40010400 · 0x0428
EXTI_RTSR0x40010408 · 0x0428
EXTI_PR0x40010414 · сброс записью 1
NVIC_ISER00xE000E100 · (1<<9)|(1<<23)
NVIC_ISER10xE000E104 · 1<<8
NVIC_IPR0xE000E400 + IRQ · STRB p<<4
SCB_AIRCR0xE000ED0C · 0x05FA0000 | G<<8
Таблица векторов57 слов, до 0xE0
EXC_RETURN0xFFFFFFF9
ПовезлоВсе три кнопки попадают в разные векторы. Приоритет в NVIC задаётся вектору, а не линии, поэтому две кнопки на общем векторе нельзя развести по приоритетам. У варианта 8 эксперимент 2 выполним в полном объёме.
СветодиодУ тебя общий анод и open-drain, как в лабе 1: зажечь цвет — сбросить бит (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 в стек.