Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

Программирование ARM-контроллеров STM32 на ядре Cortex-M3. Часть 9. Система прерываний

Введение

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

Примерно также устроены прерывания. Разница тут, по сути, только в способе вызова, — процедуры мы вызываем, вставляя в нужное место программы команду перехода, а в случае с прерыванием такой переход происходит автоматически, при возникновении определённого события. Выполнение основной программы при этом прерывается (отсюда, собственно, и название) и начинается выполнение группы инструкций, называемых «обработчиком прерывания» (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 бит).

Для глобального запрета/разрешения прерываний через регистры PRIMASK и FAULTMASK есть специальная ассемблерная команда: CPS, описание которой можно найти в приложении 1. Кроме того, в программах на Си для запрета/разрешения прерываний можно воспользоваться макросами: __disable_irq(), __enable_irq(), которые, впрочем, всё равно в итоге скомпилируются в команду CPS.

Как происходит вход в прерывание? Сохранение контекста.

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

  • процессор находится в режиме потока (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. Часть 1. Установка MDK, создание проекта, основы Keil uVision
  2. Часть 2. Команды и директивы ассемблера, структура и синтаксис программы. Первая программа для STM32
  3. Часть 3. Карта памяти контроллеров STM32, методы работы с памятью
  4. Часть 4. Регистры, старт и режимы работы контроллеров STM32
  5. Часть 5. Как залить прошивку в контроллер
  6. Часть 6. Настройка системы тактирования
  7. Часть 7. Работа с портами ввода-вывода
  8. Часть 8. Процедуры на ассемблере для STM32
  9. Часть 9. Система прерываний
  10. Часть 10. CMSIS, использование стандартных библиотек и функций
  11. Часть 11. Подключение и использование драйверов из пакета StdPeriph
  12. Часть 12. Работа с модулями USART и UART.
  13. Часть 13. Работа с модулями ADC
  14. Часть 14. Использование DMA
  15. Часть 15. Таймеры. Глава 1 — Введение. Простейшие таймеры
  16. Часть 15. Таймеры. Глава 2 — Таймеры общего назначения TIM9 — TIM14
  17. Часть 15. Таймеры. Глава 3 — Таймеры общего назначения TIM2 — TIM5
  18. Часть 15. Таймеры. Глава 4 — Продвинутые таймеры TIM1, TIM8
  19. Часть 16. Создание устройства USB HID в Keil uVision при помощи библиотечного компонента USB
  20. Приложение 1. Набор инструкций THUMB-2 и особенности их использования
  21. Приложение 2. Таблица векторов прерываний для семейств STM32F101, STM32F102, STM32F103
  22. Приложение 3. Драйвера и функции библиотеки StdPeriph

Добавить комментарий