- Введение
- Как настроить и включить прерывание
- Как происходит вход в прерывание? Сохранение контекста
- Специальные значения EXC_RETURN
- Как выйти из прерывания? Восстановление контекста
- Пример программы с прерыванием
В предыдущей части мы говорили, что группы инструкций, выполняющие какую-либо элементарную задачу, можно оформлять в виде процедур и потом вызывать их в программе каждый раз, когда они нам понадобятся.
Примерно также устроены прерывания. Разница тут, по сути, только в способе вызова, — процедуры мы вызываем, вставляя в нужное место программы команду перехода, а в случае с прерыванием такой переход происходит автоматически, при возникновении определённого события. Выполнение основной программы при этом прерывается (отсюда, собственно, и название) и начинается выполнение группы инструкций, называемых «обработчиком прерывания» (ISR — interrupt service routine). После выполнения инструкций «обработчика» контроллер вновь возвращается к выполнению инструкций основной программы (в тоже самое место, где выполнение инструкций основной программы прервалось).
Прерываний в STM32 может быть довольно много и для управления всеми ими в ядре Cortex-M3 создан специальный контроллер — NVIC (Nested Vectored Interrupt Controller — контроллер вложенных векторных прерываний).
Почему вложенных? Потому что NVIC может прерывать выполнение не только основной программы, но и других обработчиков, также позднее возвращаясь к их выполнению. Для этого NVIC предоставляет разработчику возможность назначить большинству событий (то есть прерываний) приоритеты. Если при выполнении обработчика прерывания случится событие с более высоким приоритетом, то NVIC прервёт выполнение текущего обработчика и запустит обработчик более приоритетного события. Таким образом, обработчики окажутся как бы «вложенными» друг в друга.
Почему векторных? Потому что начальные адреса обработчиков всех прерываний хранятся в памяти в виде таблицы и называются векторами. NVIC знает где расположена таблица векторов и для перехода к обработчику прерывания автоматически выполняет выборку нужного адреса из этой таблицы.
С точки зрения ядра Cortex-M3, прерывания делятся на внешние и внутренние. Внутренние относятся к самому ядру и их принято называть системными исключениями (exceptions). Набор системных исключений одинаков для всех камней с одним и тем же ядром. Внешние прерывания относятся ко всему, что не входит в ядро. Вообще-то, в кортексах только их принято называть прерываниями (interrupts) и обозначать как IRQ N (где N — номер прерывания). Состав внешних прерываний определяется конкретной моделью микроконтроллера и зависит от того, какой обвес в виде модулей разработчики прикрутили к ядру (Watchdog, DMA, GPIO, UART, SPI, I2C и так далее и тому подобное).
Таблицу векторов прерываний для конкретного контроллера нужно искать в даташите на этот конкретный контроллер. По умолчанию она должна быть расположена в самом начале памяти программ (программного кода), сразу после 4-х байт, определяющих адрес начала стека. Её, при желании, можно перенести, но обычно это не нужно и мы на этом пока останавливаться не будем.
Для контроллеров, входящих в семейства STM32F101, STM32F102, STM32F103, с размером флеша до 512 кб, таблицу векторов прерываний (со смещениями, номерами и приоритетами по умолчанию) можно найти в приложении 2.
Стандартные имена процедур прерываний можно подглядеть в файле startup_stm32f10x_md.s, в котором находятся их «слабые» объявления (с помощью директивы weak).
Обработчики первых трёх системных исключений (Reset, NMI, HardFault) должны присутствовать в любой программе (хотя бы заглушки), остальные исключения и прерывания не смогут произойти, пока не будут разрешены или программно сгенерированы.
Что вообще умеет NVIC:
- поддерживать до 81 прерывания (в зависимости от модели контроллера), обеспечивая автоматическую выборку из таблицы векторов начального адреса обработчика
- задавать до 16 уровней приоритета для каждого прерывания (чем выше значение — тем ниже приоритет) и динамически изменять эти приоритеты
- формировать группы и подгруппы приоритетов (подробнее этот механизм описан ниже)
- откладывать выполнение прерываний (если запускающие события случились во время выполнения обработчика с более высоким приоритетом)
- обеспечивать вложенность прерываний (об этом написано выше)
- организовывать цепочки прерываний (когда отложенные прерывания запускаются сразу после завершения предыдущего обработчика, минуя стадию возврата из прерывания, сопровождающуюся восстановлением контекста)
Прерывания в STM32 бывают чувствительны как к уровню, так и к импульсу (фронту) запускающего сигнала.
Прерывания, чувствительные к уровню, удерживаются утверждёнными пока периферия не сбросит этот сигнал. Обычно это случается из-за того, что обработчик обращается к регистрам периферии и очищает запрос на прерывание. Прерывания, чувствительные к фронту, фиксируются синхронно с процессором по переднему фронту его тактового генератора и остаются сработанными минимум на один такт, чтобы NVIC успел детектировать сигнал и защёлкнуть прерывание.
Когда процессор переходит к обработчику, он автоматически очищает статус ожидания у прерывания. Для прерывания чувствительного к уровню, если сигнал не отменён перед тем, как процессор выйдет из обработчика, прерывание снова перейдёт в состояние ожидания и процессор должен будет снова выполнить его обработчик. Это означает, что периферия может удерживать сигнал прерывания утверждённым до тех пор, пока он не будет больше нуждаться в обслуживании.
С общей вводной частью на этом закончим и перейдём к деталям и частностям.
Как настроить и включить прерывание?
Каждое системное исключение настраивается по-своему, через свои регистры, и про это нужно читать отдельно. Здесь, в очередной раз, главными документами, к которым стоит обратиться, являются Reference Manual RM0008 (CD00171190) и Programming Manual PM0056 (CD00228163).
Внешние прерывания настраиваются одинаково в части настройки NVIC и именно эту их настройку мы рассмотрим ниже. На уровне самих периферийных модулей прерывания тоже обычно нужно включать и настраивать (устанавливать определённые биты в регистрах управления), но это всё уже индивидуально (нужно смотреть по доке). Настройкой и отслеживанием большинства из них занимается специальный контроллер внешних событий/прерываний (EXTI).
Итак, настройка внешних прерываний в части NVIC происходит через специальные регистры:
- регистры разрешения и отмены разрешения прерываний (NVIC_ISERx / NVIC_ICERx)
- регистры установки и сброса признаков ожидания прерываний (NVIC_ISPRx / NVIC_ICPRx)
- регистры уровней приоритета прерываний (PRIx)
- регистры активного состояния прерываний (NVIC_IABRx)
Регистр разрешения прерываний программируется по двум разным адресам: по одному можно только установить разрешения, а по другому — только сбросить. Для разрешения каких-либо прерываний нужно записать единицы в соответствующие биты по адресу NVIC_ISERx (0xE000E100, 0xE000E104), для отмены — единицы в соответствующие биты по адресу NVIC_ICERx (0xE000E180, 0xE000E184). Каждому биту соответствует своё прерывание. Чтение по любому из адресов возвращает текущее состояние регистра.
Как я уже говорил, если прерывание не может быть обработано немедленно, то оно откладывается. Статусы ожидания прерываний хранятся в специальном регистре, который также доступен по двум адресам — по одному можно только установить признак, по другому — только сбросить. Для установки каких-либо признаков ожидания прерываний нужно записать единицы в соответствующие биты по адресу NVIC_ISPRx (0xE000E200, 0xE000E204), для отмены — единицы в соответствующие биты по адресу NVIC_ICPRx (0xE000E280, 0xE000E284). Каждому биту соответствует своё прерывание. Устанавливая или сбрасывая признаки ожидания прерываний мы можем либо вызвать программную генерацию прерывания, либо отменить обработку отложенного (ожидающего) прерывания пока она ещё не началась. Чтение по любому из адресов возвращает текущее состояние регистра.
Уровни приоритета задаются через однобайтные регистры PRIx (0xE000E400, 0xE000E401, 0xE000E402 …), в которых для рассматриваемых чипов используются только 4 старших бита. После старта уровни приоритетов для всех прерываний равны нулю (как и для системных исключений с программируемыми уровнями приоритета).
NVIC поддерживает группировку приоритетов, для чего каждая запись в регистре PRIx можно разделить на два поля: верхнее поле (определяет группу приоритета) и нижнее поле (определяет подгруппу внутри группы). Исключения не могут прерывать выполнение обработчиков других исключений с таким же или меньшим значением группы приоритета (то есть с таким же или большим приоритетом). Значение уровня подгруппы используется в случае возникновения нескольких исключений с одинаковым приоритетом группы. В этом случае первым выполняется обработчик с меньшим значением приоритета подгруппы (он имеет более высокий приоритет). И, наконец, если совпадают и группа и подгруппа, то выполняется обработчик с меньшим номером (адрес которого расположен раньше других в таблице векторов).
То, сколько бит будут относиться к группе, а сколько к подгруппе определяется битами [10:8] регистра SCB_AIRCR (0xE000E0014). По умолчанию там записан 0 (при значениях 0,1,2 никакого разделения нет, имеем 16 групп и ни одной подгруппы).
Каждое прерывание имеет бит активного состояния. Он устанавливается в 1 при переходе к обработчику прерывания и сбрасывается в 0 при выходе из обработчика. Для внешних прерываний биты активного состояния расположены в регистрах NVIC_IABRx (0xE000E300, 0xE000E304). Эти регистры доступны только для чтения. Если во время выполнения обработчика прерывания X произошло прерывание Y с более высоким приоритетом, то процессор перейдёт к выполнению обработчика Y, но прерывание X будет продолжать считаться активным.
Кроме перечисленных выше регистров, на работу с прерываниями влияют также следующие общие регистры:
- регистры маскирования прерываний (PRIMASK, FAULTMASK и BASEPRI)
- регистр смещения таблицы векторов (SCB_VTOR)
- регистр программной генерации прерывания (NVIC_STIR)
- регистр управления прерываниями и сбросом (SCB_AIRCR)
Регистр PRIMASK используется для запрещения всех исключений с программируемым уровнем приоритета. В этом регистре используется только младший бит. Установка этого бита повышает текущий уровень приоритета до 0 и таким образом блокирует вложенный вызов обработчика любого прерывания с программируемым уровнем приоритета.
Регистр FAULTMASK действует аналогично, за исключением того, что текущий уровень приоритета поднимается до -1, блокируя таким образом ещё и вызов обработчика HardFault.
Регистр BASEPRI позволяет запретить прерывания, значение приоритета которых больше или равно записанному в этот регистр значению (помним, да, что в STM32 чем больше значение приоритета — тем ниже приоритет).
Регистр NVIC_STIR можно использовать для программной генерации прерывания. Для этого в него нужно записать номер прерывания, которое нужно сгенерировать (IRQ N).
Регистр SCB_VTOR используется для переноса таблицы векторов, а регистр SCB_AIRCR, помимо описанного выше разделения приоритетов, можно использовать для генерации общего сброса (установкой бита SYSRESETREQ).
Номер текущего обрабатываемого исключения можно посмотреть в регистре IPSR (младшие 8 бит).
Как происходит вход в прерывание? Сохранение контекста.
Переход к обработчику происходит при наличии ожидающего исключения с достаточным приоритетом, плюс одного из следующих условий:
- процессор находится в режиме потока (thread)
- новое исключение имеет более высокий приоритет, чем текущее обрабатываемое исключение (в этом случае новое исключение прерывает выполнение текущего)
Для перехода к обработчику проц должен сделать следующие вещи: сохранить контекст (если нужно), осуществить выборку вектора обработчика и записать в регистр возврата (LR) специальное значение EXC_RETURN. Только после этого начинается выполнение инструкций обработчика.
Cохранение контекста происходит только при переходе к обработчику исключения из режима потока (то есть если это не вложенное исключение внутри другого обработчика). Контекст сохраняется в текущий стек. Эта операция называется стекинг (stacking), а сохраняемая при этом информация называется стековым фреймом. Стековый фрейм содержит следующие 8 слов данных:
- R0-R3, R12
- адрес возврата (адрес инструкции, следующей за последней выполненной в прерванной программе)
- PSR
- LR
Выборка вектора обработчика исключения из таблицы векторов происходит параллельно с операцией стекинга, а сразу после завершения этих операций процессор записывает в LR специальное значение EXC_RETURN и переходит к выполнению инструкций обработчика. Кроме того, при переходе к инструкциям обработчика происходит следующее: процессор автоматически изменяет статус исключения с «ожидание» на «активное» (сбрасывает бит pending и устанавливает бит active), процессор записывает в регистр IPSR (младший байт регистра PSR) номер обрабатываемого исключения.
Если при подготовительных операциях (стекинг, выборка…) случается исключение с более высоким приоритетом, то процессор перейдёт к обработчику исключения с более высоким приоритетом. При этом статус текущего исключения не меняется. Такой случай называется «опоздавшим» исключением.
Специальные значения EXC_RETURN
Для того, чтобы с прерываниями можно было работать совсем как с обычными процедурами, разработчики STM32 придумали специальные значения EXC_RETURN. Они работают следующим образом. При переходе к обработчику исключения процессор, как мы уже говорили, автоматически записывает в LR одно из специальных значений EXC_RETURN (вместо адреса возврата, как это было в случае с процедурами). В дальнейшем, когда мы попытаемся осуществить возврат из процедуры (то есть попытаемся восстановить значение EXC_RETURN в регистр PC), процессор поймёт, что мы не просто выходим из процедуры, а выходим из обработчика прерывания и осуществит регламентированные для этого случая действия (о которых мы поговорим далее).
Наличие значений EXC_RETURN ограничивает диапазон адресов, по которым можно вернуться из процедуры, поэтому они выбраны такими, чтобы не пересекаться с используемой областью памяти. Биты [31:4] значения EXC_RETURN всегда равны 1, бит 3 содержит информацию о режиме, в котором находился процессор на момент перехода к текущему обработчику, бит 2 содержит информацию об активном на этот момент стеке (то есть о том, в какой стек сохраняется контекст), бит 1 всегда должен быть сброшен в 0, а бит 0 должен быть установлен в 1. Таким образом EXC_RETURN может принимать следующие значения:
- 0xFFFF FFF1 — такое значение EXC_RETURN заносится в LR при переходе к прерыванию из режима обработчика (то есть в случае вложенного прерывания)
- 0xFFFF FFF9 — такое значение EXC_RETURN заносится в LR при переходе к прерыванию из режима потока при активном основном стеке
- 0xFFFF FFFD — такое значение EXC_RETURN заносится в LR при переходе к прерыванию из режима потока при активном стеке процесса
Внимательный читатель заметит, что манипулируя двумя битами можно получить 4 различных значения EXC_RETURN. Совершенно верно, однако значение 0xFFFF FFF5 не используется, поскольку в режиме обработчика нельзя выбирать стек (всегда используется основной стек).
Как выйти из прерывания? Восстановление контекста
Как мы уже сказали, благодаря трюку со специальными значениями EXC_RETURN, возврат из прерывания в STM32 ничем не отличается от выхода из обычной процедуры. Соответственно, возврат можно осуществить одним из описанных ниже способов:
- BX регистр — для случая, когда значение EXC_RETURN содержится в регистре (например, если LR при исполнении кода обработчика не изменялось, то там так и лежит EXC_RETURN и можно использовать команду bx LR)
- POP {PC} или POP {…, PC} — для случая, когда значение EXC_RETURN сохранено в стеке
- LDR или LDM с PC в качестве регистра-приёмника
При выходе из обработчика исключения происходит следующее:
- Восстановление контекста из стека (unstacking). При этом восстанавливаются значения регистров, сохранённых в стековом фрейме и указатель стека. Этот пункт выполняется только при переходе в режим потока.
- Обновление регистров NVIC. При этом сбрасывается бит активности обработанного исключения. В случае внешнего прерывания, если вход этого прерывания на момент выхода из обработчика всё ещё активен, то будет заново установлен бит ожидания, что приведёт к повторному срабатыванию прерывания. Для прерываний, подключенных к линиям контроллера EXTI это означает, что в обработчике нужно программно сбрасывать соответствующий бит ожидания (pending bit) регистра EXTI_PR, иначе после завершения обработчика он будет тут же запускаться вновь.
Пример программы с прерыванием
Ну и напоследок переделаем написанный ранее пример программы для работы с портами ввода-вывода таким образом, чтобы в ней использовались прерывания. А именно, — пусть теперь от нажатия кнопки (PA9) случается прерывание, а светодиод (PA8) загорается и гаснет в обработчике этого прерывания. Логику сделаем следующую: когда уровень на PA9 высокий (кнопка не нажата) — светодиод горит (PA8=1), когда уровень на PA9 низкий (кнопка нажата) — светодиод гаснет (PA8=0). Соответственно, нам понадобится, чтобы прерывание срабатывало как от переднего, так и от заднего фронта сигнала на ноге PA9.
Итак, код:
; blocks addresses SAR EQU 0x42000000 ; Start Alias Region FLASH EQU 0x40022000 RCC EQU 0x40021000 PORTA EQU 0x40010800 EXTI EQU 0x40010400 AFIO EQU 0x40010000 NVIC EQU 0xE000E100 ; registers addresses FLASH_ACR EQU FLASH+0x0 RCC_CR EQU RCC+0x0 RCC_CFGR EQU RCC+0x4 RCC_APB2ENR EQU RCC+0x18 GPIOA_CRH EQU PORTA+0x4 ; регистр конфигурации старшей половины PortA (8-15) GPIOA_IDR EQU PORTA+0x8 ; входной регистр PORTA GPIOA_ODR EQU PORTA+0xC ; выходной регистр PORTA GPIOA_BSRR EQU PORTA+0x10 ; регистр установки/сброса отдельных бит PORTA EXTI_IMR EQU EXTI+0x0 ; interrupt mask register EXTI_RTSR EQU EXTI+0x8 ; rising trigger selection register EXTI_FTSR EQU EXTI+0xC ; falling trigger selection register EXTI_PR EQU EXTI+0x14 ; регистр статуса ожидания (для сброса бита нужно записать в этот бит 1) AFIO_EXTICR3 EQU AFIO+0x10 ; регистр выбора источника линий EXTI8_11 NVIC_ISER0 EQU NVIC+0x0 ; регистр разрешения прерываний 0-31 ; bits numbers LAT1 EQU 1 HSEON EQU 16 HSERDY EQU 17 PLLSRC EQU 16 PLLMUL EQU 18 PPRE2 EQU 11 PLLON EQU 24 PLLRDY EQU 25 SW1 EQU 1 AFIOEN EQU 0 IOPAEN EQU 2 MCO EQU 24 MODE8 EQU 0 ; биты MODE для PA8 - это 0-й и 1-й CNF8 EQU 2 ; биты CNF для PA8 - это 2-й и 3-й MODE9 EQU 4 ; биты MODE для PA9 - это 4-й и 5-й CNF9 EQU 6 ; биты CNF для PA9 - это 6-й и 7-й ; мы будем использовать прерывание от линии PA9, - это линия EXTI9, прерывание IRQ23 MR9 EQU 9 ; бит маскировки прерывания на линии EXTI9 TR9 EQU 9 ; бит конфигурации триггеров для EXTI9) PR9 EQU 9 ; Pending bit of line EXTI9 EXTI9 EQU 4 ; биты для выбора источника EXTI9 - это биты 4,5,6,7 SETENA EQU 23 ; номер бита, разрешающего прерывание IRQ23 от линий EXTI9_5 ;------------------------------------------------------------------------ AREA STACK, NOINIT, READWRITE SPACE 0x400 Stack_top ;------------------------------------------------------------------------ AREA RESET, DATA, READONLY dcd Stack_top dcd Program_start dcd NMI dcd HardFault space 0x8C ; пропускаем 0x9C-0x10=0x8C байт, чтобы попасть на адрес 0x9C dcd EXTI9_5 ; прерывание от EXTI9_5 ;------------------------------------------------------------------------ AREA PROGRAM, CODE, READONLY ENTRY Program_start bl InitClock ; процедура настройки системы тактирования bl InitPort ; процедура настройки порта bl IRQInit ; процедура настройки прерывания Work b Work ; просто зацикливаем ;------------------------------------------------------------------------ AREA Interrupts, CODE, READONLY NMI b Program_start ; заглушка для NMI HardFault b Program_start ; заглушка для HardFault ;************************* EXTI9_5 push {lr} ; сохраняем в стек адрес EXC_RETURN ; PR0 alias address (загружаем в r0 адрес бита PR9 в области псевдонимов) ldr r0,=SAR+(EXTI_PR&0x00FFFFFF)*0x20+PR9*4 str r9,[r0] ; сразу сбрасываем pending bit (чтобы не пропустить очередную сработку) ldr r0,=GPIOA_IDR ; в r0 - адрес регистра входов ldr r10,[r0] ; читаем входы (регистр IDR) tst r10,#(1<<9) ; test 9 bit (проверяем уровень на 9-й ноге) beq Null ; если ноль - прыгаем One ldr r0,=GPIOA_BSRR ; загружаем в r0 адрес регистра GPIOA_BSRR ldr r10,=(1<<8) ; загружаем в r10 0x100 (set 8th bit to set 8th bit in GPIOA_ODR) str r10,[r0] ; пишем регистр r10 в GPIOA_BSRR b ExitIRQ Null ldr r0,=GPIOA_BSRR ; загружаем в r0 адрес регистра GPIOA_BSRR ldr r10,=(1<<24); загружаем в r10 0x1000000 (set 24th bit to reset 8th bit in GPIOA_ODR) str r10,[r0] ; пишем регистр r10 в GPIOA_BSRR ExitIRQ pop {pc} ; возвращаемся из прерывания ;------------------------------------------------------------------------ AREA SubProgram, CODE, READONLY InitClock PROC push {lr} ; сохраняем в стек адрес возврата mov r9, #1 ; set Latency = 2 ; LATENCY1 alias address (адрес бита LATENCY1 в области псевдонимов) ldr r0,=SAR+(FLASH_ACR&0x00FFFFFF)*0x20+LAT1*4 str r9,[r0] ; устанавливаем нужный бит ; HSE enable ; HSEON alias address (загружаем в r0 адрес бита HSEON в области псевдонимов) ldr r0,=SAR+(RCC_CR&0x00FFFFFF)*0x20+HSEON*4 str r9,[r0] ; устанавливаем нужный бит ; wait HSERDY flag ldr r0,=RCC_CR ; загружаем в r0 адрес регистра RCC_CR wait_hserdy ldr r10,[r0] ; читаем регистр RCC_CR в r10 tst r10,#(1<<HSERDY) ; проверяем, равен ли бит HSERDY единице (логическое "&" с 0x20000) beq wait_hserdy ; если нет - прыгаем ; выбираем HSE источником для PLL, устанавливаем множитель = 9, ; предделитель для APB2 (/2), MCO - no clock (оно так и по умолчанию, это просто в общем виде) ldr r0,=RCC_CFGR ; загружаем в r0 адрес регистра RCC_CFGR ldr r10,=(1<<PLLSRC)+(7<<PLLMUL)+(4<<PPRE2)+(0<<MCO) str r10,[r0] ; включаем PLL ; PLLON alias address (загружаем в r0 адрес бита PLLON в области псевдонимов) ldr r0,=SAR+(RCC_CR&0x00FFFFFF)*0x20+PLLON*4 str r9,[r0] ; устанавливаем нужный бит ; ждём появления флага PLLRDY ldr r0, =RCC_CR ; загружаем в r0 адрес регистра rcc_cr wait_pllrdy ldr r10,[r0] ; читаем регистр RCC_CR в r10 tst r10, #(1<<PLLRDY); проверяем, равен ли бит PLLRDY единице (логическое "&" с 0x2000000) beq wait_pllrdy ; если нет - прыгаем ; выбираем PLL в качестве SYSCLK ; SW1 alias address (загружаем в r0 адрес бита SW1 в области псевдонимов) ldr r0,=SAR+(RCC_CFGR&0x00FFFFFF)*0x20+SW1*4 str r9,[r0] pop {pc} ; возвращаемся из процедуры ENDP ;************************* InitPort PROC ; включаем тактирование PORTA ; IOPAEN alias address (загружаем в r0 адрес бита IOPAEN в области псевдонимов) ldr r0,=SAR+(RCC_APB2ENR&0x00FFFFFF)*0x20+IOPAEN*4 str r9,[r0] ; устанавливаем нужный бит ; конфигурируем PORTA (PA8 - выход, PA9 - вход) ldr r0,=GPIOA_CRH ; загружаем в r0 адрес регистра GPIOA_CRH ldr r10,[r0] ; читаем регистр GPIOA_CRH в r10 ; устанавливаем PA8 MODE=11 (output max speed = 50MHz), PA8 CNF=00 (output push-pull) ; PA9 MODE=00 (input), PA9 CNF=01 (input floating, т.е Hi-Z) ; ногу PA9 можно было и не настраивать, поскольку все ного после ресета по умолчанию настроены ; на вход Hi-Z, так что это просто чтобы внимание обратить ldr r11,=(3<<MODE8)+(0<<CNF8)+(0<<MODE9)+(1<<CNF9) bfi r10, r11, #0, #8 ; копируем 8 младших бит регистра r11 в r10 str r10,[r0] ; пишем регистр r10 в регистр GPIOA_CRH bx lr ; возвращаемся из процедуры ENDP ;************************* IRQInit PROC push {lr} ; сохраняем в стек адрес возврата ; включаем тактирование AFIO (иначе мы не сможем писать в регистры AFIO и не сможем выбрать ; никакой другой источник для EXTI9 кроме PA9, поскольку для этого нужно писать в AFIO_EXTICR3) ; для источника PA9 этого можно было и не делать, но раз уж это пример - будет в общем виде ; AFIOEN alias address (загружаем в r0 адрес бита AFIOEN в области псевдонимов) ldr r0,=SAR+(RCC_APB2ENR&0x00FFFFFF)*0x20+AFIOEN*4 str r9,[r0] ; устанавливаем нужный бит ; выбираем PA9 в качестве источника для EXTI9 ; этого можно было и не делать, поскольку для этого нужно записать 0000 в EXTI9[3:0], ; но там по умолчанию уже записаны нули после ресета, тем не менее будем всё делать правильно ldr r0,=AFIO_EXTICR3 ; загружаем в r0 адрес регистра AFIO_EXTICR3 ldr r10,[r0] ; читаем регистр AFIO_EXTICR3 в r10 ldr r11,=0 ; EXTI9[3:0] = 0000 = 0 (источник - PA9) ; копируем 4 младших бита регистра r11 в 4 бита регистра r10, начиная с EXTI9 (то есть с 4-го) bfi r10, r11, #EXTI9, #4 str r10,[r0] ; пишем регистр r10 в регистр AFIO_EXTICR3 ; настраиваем mask bits of EXTI_IMR ; MR9 alias address (загружаем в r0 адрес бита MR9 в области псевдонимов) ldr r0,=SAR+(EXTI_IMR&0x00FFFFFF)*0x20+MR9*4 str r9,[r0] ; настраиваем триггеры (EXTI_RTSR и EXTI_FTSR) ; TR9 alias address (загружаем в r0 адрес бита TR9 в области псевдонимов) ldr r0,=SAR+(EXTI_RTSR&0x00FFFFFF)*0x20+TR9*4 str r9,[r0] ; включаем прерывание по переднему фронту ; TR9 alias address (загружаем в r0 адрес бита TR9 в области псевдонимов) ldr r0,=SAR+(EXTI_FTSR&0x00FFFFFF)*0x20+TR9*4 str r9,[r0] ; включаем прерывание по заднему фронту ; настраиваем enable and mask bits of NVIC (в нашем случае только enable, ; мы же не хотим менять приоритеты и прочее) ldr r0,=NVIC_ISER0 ; загружаем в r0 адрес регистра NVIC_ISER0 ldr r10,=(1<<SETENA) ; разрешаем прерывание от GPIO str r10,[r0] pop {pc} ; возвращаемся из процедуры ENDP END |
Вот и всё, вопросы как обычно на форум или в коменты.
- Часть 1. Установка MDK, создание проекта, основы Keil uVision
- Часть 2. Команды и директивы ассемблера, структура и синтаксис программы. Первая программа для STM32
- Часть 3. Карта памяти контроллеров STM32, методы работы с памятью
- Часть 4. Регистры, старт и режимы работы контроллеров STM32
- Часть 5. Как залить прошивку в контроллер
- Часть 6. Настройка системы тактирования
- Часть 7. Работа с портами ввода-вывода
- Часть 8. Процедуры на ассемблере для STM32
- Часть 9. Система прерываний
- Часть 10. CMSIS, использование стандартных библиотек и функций
- Часть 11. Подключение и использование драйверов из пакета StdPeriph
- Часть 12. Работа с модулями USART и UART.
- Часть 13. Работа с модулями ADC
- Часть 14. Использование DMA
- Часть 15. Таймеры. Глава 1 — Введение. Простейшие таймеры
- Часть 15. Таймеры. Глава 2 — Таймеры общего назначения TIM9 — TIM14
- Часть 15. Таймеры. Глава 3 — Таймеры общего назначения TIM2 — TIM5
- Часть 15. Таймеры. Глава 4 — Продвинутые таймеры TIM1, TIM8
- Часть 16. Создание устройства USB HID в Keil uVision при помощи библиотечного компонента USB
- Приложение 1. Набор инструкций THUMB-2 и особенности их использования
- Приложение 2. Таблица векторов прерываний для семейств STM32F101, STM32F102, STM32F103
- Приложение 3. Драйвера и функции библиотеки StdPeriph