22 April 2014

Начинаем изучать Cortex-M на примере STM32, часть 2

Programming microcontrollers
Tutorial
Данная статья является продолжением цикла по программированию микроконтроллеров на базе ядра Cortex-M.
Первую статью можно прочитать здесь:
Начинаем изучать Cortex-M на примере STM32
Задачей статей является подробное описание особенностей, возникающих при программировании МК. Материал не предназначен для желающих за 10 минут запустить пример мигания светодиодом. Я постараюсь подробно описать то, что часто скрывают от новичков, чтобы их не напугать.

Мне очень хочется, чтобы программисты использующие стандартные библиотеки, шаблоны, примеры и т.д. понимали как все это работает. А при отсутствии этих самых библиотек и примеров могли самостоятельно решить свою задачу.

Основной акцент сделан на изучение документации на ядро Cortex-M и документации на конкретный контроллер.
На этот раз речь пойдет про прерывания, а так же будут затронуты некоторые вопросы архитектуры памяти и структуры прошивки МК.

Несколько слов про документацию ARM


По не совсем ясным для меня причинам, нельзя зайти на сайт ARM и скачать полную документацию на ядро Cortex-M4. Да и на Cortex-M3 тоже нельзя.
Придется почитать несколько документов.
1. Изучение придется начать с Cortex ™-M3 TechnicalReference Manual Revision: r1p1 — самой первой ревизии технической спецификации на ядро Cortex-M3
2. Во всех дальнейших ревизиях и описании Cortex ™-M4 TechnicalReference Manual описаны лишь общие данные и изменения относительно предыдущего документа.
Так что прошу не удивляться ссылкам на спецификации другого ядра.

Interrupt and Events


Прежде всего необходимо разобраться с тем, что такое прерывания.
В МК Cortex-M есть два понятия, которые часто путают Interrupt и Event.
Event — это событие (аппаратное или программное), на которое могут реагировать ядро или периферийные блоки. Одним из вариантов реакции может быть — прерывание.
Interrupt — это прерывание работы программы и переход управления в специализированный участок обработчик прерывания.

Взаимосвязь между Event и Interrupt заключается в следующем:
Каждый Interrupt вызывается Event, но не каждый Event вызывает Interrupt.
Помимо прерываний, события могут активировать и другие возможности МК.

NVIC


Управление и обработка прерываниями производится контроллером приоритетных векторных прерываний NVIC (Nested Vectored Interrupt Controller). Контроллер прерываний часть ядра Cortex-M. Документацию на этот контроллер необходимо начинать изучать с Cortex ™-M3 TechnicalReference Manual Revision: r1p1

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

Из самого названия видно, что контроллер NVIC поддерживает вложенность прерываний и приоритеты. Каждому прерыванию при настройке NVIC присваивается свой приоритет. Если во время обработки низкоприоритетного прерывания возникает высокоприоритетное, то оно, в свою очередь, прервет обработчик низкоприоритетного прерывания.

Как это работает?


Данный пост не претендует на абсолютную полноту, я советую изучить раздел прерываний в Cortex™-M3 Technical Reference Manual. Поскольку эта часть ядра не претерпела изменений, ее описание дано в первой ревизии r1p1 на ядро Cortex-M3.

Вход в прерывание и выход из него

При инициации прерывания NVIC переключает ядро в режим обработки прерывания. После перехода в режим обработки прерывания регистры ядра помещаются в стек. Непосредственно во время записи значения регистров в стек осуществляется выборка начального адреса функции обработки прерывания.

В стек перемещается регистр регистр статуса программы ( Program Status Register (PSR)), счетчик программы (Program Counter (PC)) и регистр связи (Link Register (LR) ). Описание регистров ядра приведено в Cortex-M4 Generic User Guide. Благодаря этому, запоминается состояние, в котором находилось ядро перед переходом в режим обработки прерываний.

Также сохраняются регистры R0 — R3 и R12. Эти регистры используются в инструкциях для передачи параметров, поэтому, помещение в стек делает возможным их использование в функции обработки прерывания, а R12 часто выступает в роли рабочего регистра программы.

По завершении обработки прерывания все действия выполнятся в обратном порядке: извлекается содержимое стека и, параллельно с этим, осуществляется выборка адреса возврата.

С момента инициации прерывания до выполнения первой команды обработчика прерывний проходит 12 тактов, такое же время необходимо для возобновления основной программы после завершения обработки прерывания.

Вложенность прерываний

Как было сказано выше NVIC поддерживает прерывания с различными приоритетами, которые могут прерывать друг друга. При этом, могут возникнуть различные ситуации, обработка которых по разному оптимизирована.

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

2. Непрерывная обработка прерываний
Эта ситуация может возникнуть в двух случаях: если два прерывания имеют одинаковый приоритет и возникают одновременно, если низкоприоритетное прерывание возникает во время обработки высокоприоритетного.
В этом случае, промежуточные операции над стеком не производятся. Происходит только загрузка адреса обработчика низкоприоритетного прерывания и переход к его выполнению. Отказ от операций над стеком экономит 6 тактов. Переход к следующему прерыванию происходит не за 12 тактов, а всего за 6.

3. Запаздывание высокприоритетного прерывания
Ситуация возникает, если высокоприоритетное прерывание происходит во перехода к обработке низкоприоритетного (за те самые 12 тактов). В этом случае переход к высокоприоритетному прерыванию будет происходить не менее 6 тактов с момента его возникновения (время необходимое для загрузки адреса обработчика прерывания и перехода к нему). Возврат в низкоприоритетное уже описан выше.

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

Помимо простой установки приоритета прерываний, NVIC реализует возможность группировки приоритетов.
Прерывания в группе с более высоким приоритетом могут прерывать обработчики прерываний группы с более низким приоритетом. прерывания из одной группы, но с разным приоритетом внутри группы не могут прерывать друг друга. Приоритет внутри группы определяет только порядок вызова обработчика, когда были активизированы оба события.

Значение приоритета прерывания задается в регистрах Interrupt Priority Registers (см. Cortex-M4 Generic User Guide). При этом, часть бит отвечает за приоритет группы, в которой находится прерывание, а часть — за приоритет внутри группы.
Настройка распределение бит на приоритет группы или приоритет внутри группы осуществляется с помощью регистра Application Interrupt and Reset Control Register (ВНИМАТЕЛЬНО!!! см. Cortex-M4 Generic User Guide).

Как вы, наверно, заметили, в Cortex-M4 Generic User Guide сказано, что настройка приоритетов и группировки приоритетов зависят от конкретной реализации implementation defined.
А вот дальше не очень приятная вещь. В Reference manual к МК STM32F407 про NVIC почти нет информации. Но есть ссылка на отдельный документ. Для того, чтобы разобраться с реализацией NVIC в STM32 придется прочитать еще один документ — STM32F3xxx and STM32F4xxx Cortex-M4 programming manual. Вообще говоря, я советую внимательно изучить данный документ и по всем другим вопросам, в нем работа ядра расписана более подробно, чем в документации от ARM.
В нем, уже можно найти:
A programmable priority level of 0-15 for each interrupt. A higher level corresponds to a
lower priority, so level 0 is the highest interrupt priority

Из возможных 8 бит приоритета используются только 4. Но этого вполне достаточно для большинства задач.

Маскирование прерываний

Предположим, что у нас стоит задача запуска ракеты-носителя при нажатии на красную кнопку, но только при условии, что повернут ключ.
Нет совершенно ни какого смысла генерировать прерывание на поворот ключа. А вот прерывание на нажатие красной копки нам понадобится. Для того, чтобы включать/выключать различные вектора прерываний, существует маскирование прерываний.
Маскирование прерывания осуществляется с помощью регистров Interrupt Set-enable Registers.
Если прерывание замаскировано, это не означает, что периферия не генерирует события! Просто NVIC не вызывает обработчик этого события.

Таблица векторов прерываний

Все возможные прерывания, поддерживаемые NVIC, записываются в таблицу векторов прерываний. По сути своей, таблица векторов прерываний есть ни что иное, как список адресов функций обработчиков прерываний. Номер в списке соответствует номеру прерывания.

Как написано в Cortex-M4 Generic User Guide, NVIC поддерживает до 240 различных векторов прерываний. Но реализация уже зависит от конкретного производителя.
В описании ядра стандартизованы только прерывания исключений ядра (см. раздел 2.3.2 Exception types в Cortex-M4 Generic User Guide):
  • Reset
  • NMI
  • HardFault
  • MemManage
  • BusFault
  • UsageFault
  • SVCall
  • PendSV
  • SysTick

С описанием этих исключений я предлагаю вам ознакомиться самостоятельно. Некоторые из них будут затронуты в следующих статьях.
Остальные прерывания уникальны для МК. Описание таблицы векторов вашего МК вы можете найти в соответствующем Reference manual (см. Vector table for STM32F405xx/07xx and STM32F415xx/17xx). Контроллеры STM32F4xx поддерживают 81 вектор прерываний. Можно заметить, что в этой таблице перечислены все периферийные блоки (а некоторые не единожды).
Практически все периферийные блоки генерируют прерывания, чтобы ядро отвлекалось на работку с ним только по наступлению какого-либо значимого события (например, получении данных по UART).

Расположение векторов прерываний и загрузка МК

Разобравшись с принципами работы прерываний в Cortex-M осталось понять только где хранится таблица прерываний.
Для этого стоит рассмотреть процесс загрузки и структуру прошивки контроллера. На этот раз мы будем рассматривать только загрузку из встроенной флеш памяти.
В таблице векторов, находящейся в начале адресного пространства флеш памяти должны находиться по крайней мере (см. Cortex-M3 Technical Reference Manual:
• stack top address
• reset routine location
• NMI ISR location
• Hard Fault ISR location.

Из начала флеш памяти ядро считывает значение SP (stack top addres) и PC (reset routine location). Таким образом, автоматически начинает выполняться функция, с адресом считанным в регистр PC. Это может быть, например main.

После обязательных четырех компонентов, может находиться дальнейшая таблица векторов прерываний. Главное сохранить порядок.
При желании, можно разместить таблицу векторов прерываний в другой области памяти, но тогда, необходимо сообщить NVIC, куда мы передвинули таблицу. За это смещение таблицы векторов отвечает регистр Vector Table Offset Register (см. Cortex-M4 Technical Reference Manual. Это может понадобиться для написание встроенного загрузчика нового ПО (bootloader), но об этом как-нибудь в другой раз.

От теории к практике


ТЗ второго проекта

Пример создается для отладочной платы STM32F4Discovery.
При нажатии на кнопку должен загореться светодиод LED3. При замыкании контактов PC6 и GND загорается светодиод LED5.
В процессе программирования поиграемся с приоритетами прерываний и посмотрим к чему это приведет.

Железная часть

Найдем в документации к плате кнопку и светодиод:

При ненажатой кнопке на пине PA0 будет логический ноль, при нажатии на кнопку на кнопке появится логическая 1 (3.3В).
Светодиод LED3 подключен к пину PD13.
Светодиод LED5 подключен к пину PD14.
Интересней всего с контактом PC6 — он напрямую выведен на штыревой разъем. Нам будет необходимо обеспечить регистрацию логической 1, когда он не закорочен с контактом GND. О том, как это сделать пойдет речь ниже.

Настройка GPIO

Для нашей задачи необходимо настроить пины PD13 и PD14 как выходные. О том, как это делать можно прочитать в предыдущей статье.
С настройкой пина PA0 тоже все достаточно просто — его нужно настроить на вход. Не смотря на то, что после ресчета МК почти все пины настроены на вход, крайне желательно явно прописать эту инициализацию.
С пином PC7 все несколько интереснее. Поскольку он «висит в воздухе», его состояние не определено. Нам же необходимо, чтобы при этом его состояние всегда было «1». Для этого, в блоке GPIO активировать подтяжку. В нашем случае, необходима подтяжка к питанию — PULL UP.
Активация подтяжки осуществляется с помощью регистра GPIO port pull-up/pull-down register.

Прерывания EXTI

Для выполнения нашего «ТЗ» с использованием прерываний, нам необходимо настроить прерывания, которые будут срабатывать при переходе контакта PA0 из состояния «0» в состояние «1», и прерывание при переходе контакта PC6 из состояния «1» в состояние «0».

В МК STM32F4xx для этой цели служит контроллер внешних прерываний/событий - EXTI (External interrupt/event controller). Я настоятельно рекомендую ознакомиться с его функционалом в Reference manual. Нам необходимо поступить в соответствии с описанным:
Hardware interrupt selection
To configure the 23 lines as interrupt sources, use the following procedure:
• Configure the mask bits of the 23 interrupt lines (EXTI_IMR)
• Configure the Trigger selection bits of the interrupt lines (EXTI_RTSR and EXTI_FTSR)
• Configure the enable and mask bits that control the NVIC IRQ channel mapped to the external interrupt controller (EXTI) so that an interrupt coming from one of the 23 lines can be correctly acknowledged.

Нам понадобятся 0 и 6 линии EXTI. Для размаскирования соответствующих линий прерываний необходимо записать в регистр EXTI_IMR значение 0x9.
Для линии PA0, необходима генерация события прерывания по переходу из состояния «0» в состояние «1» — по возрастающему фронту. То есть, необходимо записать 1 в нулевой бит регистра EXTI_RTSR.
Для линии PC6, наоборот, необходима генерация события прерывания по переходу из состояния «1» в состояние «0» — по падающему фронту. То есть, необходимо записать 1 в шестой бит регистра EXTI_FTSR.
На этом настройка блока EXTI закончена. Последний пункт будет реализован при настойке NVIC.

По мимо этого, необходимо определиться, пин с какого порта подключается к определенной линии EXTI. Делается это с помощью регистров SYSCFG external interrupt configuration register (Reference manual). Эти регистры находятся в System configuration controller, что мне кажется не очень логичным (почему было не включить эту насторойку в EXTI?), но оставим сей факт на совести ST.

Настройка NVIC

Активация обработки определенного вектора прерывания осуществляется с помощью регистров Interrupt set-enable registers (NVIC_ISERx). Описание регистров приведено в Cortex-M4 Generic User Guide. Сама таблицу векторов прерываний для нашего МК приведена в Reference manual (см. Table 61).

Из таблицы можно увидеть, что для 0 линии есть отдельное прерывание, а вот линии с 5 по 9 генерируют одно прерывание на всех.
Кроме того, из таблицы мы узнали номера векторов, необходимых нам прерываний. Теперь нужно записать «1» в 6 бит (активация прерываний линии 0 EXTI) регистра NVIC_ISER0 (адрес 0xE000E100) и в 23 бит того же регистра (активация прерываний линий 5-9).

Настройка приоритетов

Для того, чтобы можно было побаловаться с приоритетами прерываний настроим группы приоритетов так, чтобы 2 бита отвечали за приоритет внутри группы, и 2 бита — за приоритет самой группы. Для этого необходимо записать значение 0х05FA0500 в регистр Application interrupt and reset control register (STM32F3xxx and STM32F4xxx Cortex-M4 programming manual).
Настройка приоритетов осуществляется с помощью регистров Interrupt Priority Registers (STM32F3xxx and STM32F4xxx Cortex-M4 programming manual). Нас будут интересовать регистры Interrupt Priority Register 2 (0xE000E4008) и регистр Interrupt Priority Register 5(0xE000E401C).
Пока не будем изменять приоритеты. Пусть будут одинаковы для обоих прерываний.

Обработка прерываний

Функции обработчики прерываний — ни что иное, как просто функции языка C, который ни чего не получают и не возвращают (и правильно — не от кого и не кому).
Главное правило — обработка прерываний должна осуществляться как можно быстрее!!! Иначе низкоприоритетные прерывания могут слишком долго ждать.

После окончания обработки прерывания необходимо сбросить активность события, вызвавшего прерывание — «очистить прерывание». Очистка прерывания EXTI производится с помощью регистра EXTI_PR . Обратите внимание: запись «1» очищает прерывание, запись «0» не имеет ни какого воздействия.

Если с обработкой прерывания линии 0 EXTI все достаточно просто, то с группой линий 5-9 возникает вопрос — как определить какая линия вызвала прерывание. Узнать это можно проверкой бит регистра Pending register (EXTI_PR) — Reference manual.

Создаем таблицу векторов и располагаем ее в правильном месте

Для тог, чтобы таблица векторов с правильными адресами функций обработчиков прерываний располагались в начале флеш памяти МК, создадим и подключим к проекту файл startup.c.
Содержимое файла
// Enable the IAR extensions for this source file.
#pragma language=extended
#pragma segment="CSTACK"
// Forward declaration of the default fault handlers.
void ResetISR(void);
static void NmiSR(void);
static void FaultISR(void);
static void IntDefaultHandler(void);
// The entry point for the application startup code.
extern void __iar_program_start(void);
extern void EXTI_Line0_IntHandler(void);
extern void EXTI_Line6_IntHandler(void);
// A union that describes the entries of the vector table.  The union is needed
// since the first entry is the stack pointer and the remainder are function
// pointers.
typedef union
{
    void (*pfnHandler)(void);
    void * ulPtr;
}
uVectorEntry;

// The vector table.  Note that the proper constructs must be placed on this to
// ensure that it ends up at physical address 0x0000.0000.
__root const uVectorEntry __vector_table[] @ ".intvec" =
{
    { .ulPtr = __sfe( "CSTACK" ) },
                                            // The initial stack pointer
    ResetISR,                               // The reset handler
    NmiSR,                                  // The NMI handler
    FaultISR,                               // The hard fault handler
    IntDefaultHandler,                       // MPU Fault Handler
    IntDefaultHandler,                       // Bus Fault Handler
    IntDefaultHandler,                       // Usage Fault Handler
    IntDefaultHandler,                       // Reserved
    IntDefaultHandler,                       // Reserved
    IntDefaultHandler,                       // Reserved
    IntDefaultHandler,                       // Reserved
    IntDefaultHandler,                       // SVCall Handler
    IntDefaultHandler,                       // Debug Monitor Handler
    IntDefaultHandler,                       // Reserved
    IntDefaultHandler,                       // PendSV Handler
    IntDefaultHandler,                       // SysTick Handler
    //External Interrupts
    IntDefaultHandler,                      // Window WatchDog                                        
    IntDefaultHandler,                      // PVD through EXTI Line detection                        
    IntDefaultHandler,                      // Tamper and TimeStamps through the EXTI line            
    IntDefaultHandler,                      // RTC Wakeup through the EXTI line                       
    IntDefaultHandler,                      // FLASH                                           
    IntDefaultHandler,                      // RCC                                             
    EXTI_Line0_IntHandler,                  // EXTI Line0                                             
    IntDefaultHandler,                      // EXTI Line1                                             
    IntDefaultHandler,                      // EXTI Line2                                             
    IntDefaultHandler,                      // EXTI Line3                                             
    IntDefaultHandler,                      // EXTI Line4                                             
    IntDefaultHandler,                      // DMA1 Stream 0                                   
    IntDefaultHandler,                      // DMA1 Stream 1                                   
    IntDefaultHandler,                      // DMA1 Stream 2                                   
    IntDefaultHandler,                      // DMA1 Stream 3                                   
    IntDefaultHandler,                      // DMA1 Stream 4                                   
    IntDefaultHandler,                      // DMA1 Stream 5                                   
    IntDefaultHandler,                      // DMA1 Stream 6                                   
    IntDefaultHandler,                      // ADC1, ADC2 and ADC3s                            
    IntDefaultHandler,                      // CAN1 TX                                                
    IntDefaultHandler,                      // CAN1 RX0                                               
    IntDefaultHandler,                      // CAN1 RX1                                               
    IntDefaultHandler,                      // CAN1 SCE                                               
    EXTI_Line6_IntHandler,                  // External Line[9:5]s                                    
    IntDefaultHandler,                      // TIM1 Break and TIM9                   
    IntDefaultHandler,                      // TIM1 Update and TIM10                 
    IntDefaultHandler,                      // TIM1 Trigger and Commutation and TIM11
    IntDefaultHandler,                      // TIM1 Capture Compare                                   
    IntDefaultHandler,                      // TIM2                                            
    IntDefaultHandler,                      // TIM3                                            
    IntDefaultHandler,                      // TIM4                                            
    IntDefaultHandler,                      // I2C1 Event                                             
    IntDefaultHandler,                      // I2C1 Error                                             
    IntDefaultHandler,                      // I2C2 Event                                             
    IntDefaultHandler,                      // I2C2 Error                                               
    IntDefaultHandler,                      // SPI1                                            
    IntDefaultHandler,                      // SPI2                                            
    IntDefaultHandler,                      // USART1                                          
    IntDefaultHandler,                      // USART2                                          
    IntDefaultHandler,                      // USART3                                          
    IntDefaultHandler,                      // External Line[15:10]s                                  
    IntDefaultHandler,                      // RTC Alarm (A and B) through EXTI Line                  
    IntDefaultHandler,                      // USB OTG FS Wakeup through EXTI line                        
    IntDefaultHandler,                      // TIM8 Break and TIM12                  
    IntDefaultHandler,                      // TIM8 Update and TIM13                 
    IntDefaultHandler,                      // TIM8 Trigger and Commutation and TIM14
    IntDefaultHandler,                      // TIM8 Capture Compare                                   
    IntDefaultHandler,                      // DMA1 Stream7                                           
    IntDefaultHandler,                      // FSMC                                            
    IntDefaultHandler,                      // SDIO                                            
    IntDefaultHandler,                      // TIM5                                            
    IntDefaultHandler,                      // SPI3                                            
    IntDefaultHandler,                      // UART4                                           
    IntDefaultHandler,                      // UART5                                           
    IntDefaultHandler,                      // TIM6 and DAC1&2 underrun errors                   
    IntDefaultHandler,                      // TIM7                   
    IntDefaultHandler,                      // DMA2 Stream 0                                   
    IntDefaultHandler,                      // DMA2 Stream 1                                   
    IntDefaultHandler,                      // DMA2 Stream 2                                   
    IntDefaultHandler,                      // DMA2 Stream 3                                   
    IntDefaultHandler,                      // DMA2 Stream 4                                   
    IntDefaultHandler,                      // Ethernet                                        
    IntDefaultHandler,                      // Ethernet Wakeup through EXTI line                      
    IntDefaultHandler,                      // CAN2 TX                                                
    IntDefaultHandler,                      // CAN2 RX0                                               
    IntDefaultHandler,                      // CAN2 RX1                                               
    IntDefaultHandler,                      // CAN2 SCE                                               
    IntDefaultHandler,                      // USB OTG FS                                      
    IntDefaultHandler,                      // DMA2 Stream 5                                   
    IntDefaultHandler,                      // DMA2 Stream 6                                   
    IntDefaultHandler,                      // DMA2 Stream 7                                   
    IntDefaultHandler,                      // USART6                                           
    IntDefaultHandler,                      // I2C3 event                                             
    IntDefaultHandler,                      // I2C3 error                                             
    IntDefaultHandler,                      // USB OTG HS End Point 1 Out                      
    IntDefaultHandler,                      // USB OTG HS End Point 1 In                       
    IntDefaultHandler,                      // USB OTG HS Wakeup through EXTI                         
    IntDefaultHandler,                      // USB OTG HS                                      
    IntDefaultHandler,                      // DCMI                                            
    IntDefaultHandler,                      // CRYP crypto                                     
    IntDefaultHandler,                      // Hash and Rng
    IntDefaultHandler,                      // FPU
};

// This is the code that gets called when the processor first starts execution
// following a reset event.  Only the absolutely necessary set is performed,
// after which the application supplied entry() routine is called.  Any fancy
// actions (such as making decisions based on the reset cause register, and
// resetting the bits in that register) are left solely in the hands of the
// application.
void
ResetISR(void)
{
    //
    // Call the application's entry point.
    //
    __iar_program_start();
}

// This is the code that gets called when the processor receives a NMI.  This
// simply enters an infinite loop, preserving the system state for examination
// by a debugger.
static void
NmiSR(void)
{
    //
    // Enter an infinite loop.
    //
    while(1)
    {
    }
}

// This is the code that gets called when the processor receives a fault
// interrupt.  This simply enters an infinite loop, preserving the system state
// for examination by a debugger.
static void
FaultISR(void)
{
    //
    // Enter an infinite loop.
    //
    while(1)
    {
    }
}

// This is the code that gets called when the processor receives an unexpected
// interrupt.  This simply enters an infinite loop, preserving the system state
// for examination by a debugger.
static void
IntDefaultHandler(void)
{
    //
    // Go into an infinite loop.
    //
    while(1)
    {
    }
}

Использование
@ ".intvec"
Располагает таблицу __vector_table в начале секции, объявленной в файле линкера. Сам файл можно посмотреть тут:


Сама секция задается в начале ROM памяти. Адреса можно посмотреть тут (документ, в котором описана адресация флеш памяти STM32):



Комбинация директивы IAR и спецфункции IAR:
#pragma segment="CSTACK"
__sfe( "CSTACK" )
Записывает в начале флеша указатель на верхушку стека.

Саму таблицу заполняют адреса функций, реализующий вечный цикл. Исключение сделано только для интересующих нас функций:
extern void EXTI_Line0_IntHandler(void);
extern void EXTI_Line6_IntHandler(void);

В функции, вызываемой при старте, просто производится переход к
extern void __iar_program_start(void);
Это функция — main(). Сам символ можно переопределить, если возникнет желание:


Переходим к основному файлу

Для начала выпишем и переопределим все адреса и битовые поля, которые нам понадобятся.
Листинг
//Definitions for SCB_AIRCR register
#define SCB_AIRCR			(*(unsigned volatile long*)0xE000ED0C) //acces to SCB_AIRCR
#define SCB_AIRCR_GROUP22	0x05FA0500 //change priority data

//Definitions for RCC_AHB1_ENR register
#define RCC_AHB1_ENR		(*(unsigned volatile long *)(0x40023830)) //acces to RCC_AHB1ENR reg
#define RCC_AHB1_ENR_GPIOA	0x1 //GPIOA bitfield
#define RCC_AHB1_ENR_GPIOC	0x4 //GPIOC bitfield
#define RCC_AHB1_ENR_GPIOD	0x8 //GPIOD bitfield 
//Definitions for RCC_APB2_ENR register
#define RCC_APB2_ENR		(*(unsigned volatile long *)(0x40023844)) //acces to RCC_APB2ENR reg
#define RCC_APB2_ENR_SYSCFG	0x4000 //SYSCFG bitfield
//Definitions for GPIO MODE registers
#define GPIOA_MODER		(*(unsigned volatile long*)(0x40020000)) //acces to GPIOA_MODER reg
#define GPIOC_MODER		(*(unsigned volatile long*)(0x40020800)) //acces to GPIOC_MODER reg
#define GPIOD_MODER		(*(unsigned volatile long*)(0x40020C00)) //acces to GPIOD_MODER reg
//GPIO  ODR register definition
#define GPIOD_ODR			(*(unsigned volatile long*)(0x40020C14)) //acces to GPIOD_MODER reg
#define GPIO_ODR_13PIN		0x2000
#define GPIO_ODR_14PIN		0x4000
//Bitfields definitions
#define GPIO_MODER_0BITS	0x3 //Pin 0 mode bits
#define GPIO_MODER_0IN		0x0 //Pin 0 input mode
#define GPIO_MODER_6BITS		0x300 //Pin 6 mode bits
#define GPIO_MODER_6IN		0x000 //Pin 6 input mode
#define GPIO_MODER_13BITS	0xC000000 //Pin 13 mode bits
#define GPIO_MODER_13OUT	0x4000000 //Pin 13 output mode
#define GPIO_MODER_14BITS	0x30000000 //Pin 14 mode bits
#define GPIO_MODER_14OUT	0x10000000 //Pin 14 output mode
//GPIOC_PUPDR register definition 
#define GPIOC_PUPDR			(*(unsigned volatile long*)(0x4002080C)) //acces to GPIOC_PUPDR reg
#define GPIOC_PUPDR_6BITS	0x3000 //PC6 bitfield
#define GPIOC_PUPDR_6PU		0x1000 //PC6 pull-up enable
//SYSCFG_EXTIx registers definitions
#define SYSCFG_EXTICR1		(*(unsigned volatile long*)0x40013808)	 //SYSCFG_EXTICR1 acces
#define SYSCFG_EXTICR1_0BITS 0xF //EXTI 0 bits
#define SYSCFG_EXTICR1_0PA	0x0 //EXTI 0 - port A
#define SYSCFG_EXTICR2		(*(unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces
#define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 bits
#define SYSCFG_EXTICR2_6PC	0x200 //EXTI 6 - port C
//EXTI definitions
#define EXTI_IMR	(*(unsigned volatile long*)0x40013C00) //EXTI_IMR reg acces
#define EXTI_LINE0	0x1 //LINE 0 definition
#define EXTI_LINE6	0x40 //LINE 6 definition
#define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg acces
#define EXTI_FTSR (*(unsigned volatile long*)0x40013C0C) //EXTI_FTSR reg acces
#define EXTI_PR	(*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces
//NVIC registers and bits definitions
#define NVIC_ISER0_REG		(*(unsigned volatile long*)0xE000E100) //NVIC_ISER0 reg acces
#define NVIC_ISER0_6VECT	0x40 //vect 6 definition
#define NVIC_ISER0_23VECT	0x800000 //vect 30 definition

#define NVIC_IPR0_ADD	(0xE000E400)
#define NVIC_IPR23_REG	(*(unsigned volatile char*)(NVIC_IPR0_ADD + 23))
#define NVIC_IPR6_REG	(*(unsigned volatile char*)(NVIC_IPR0_ADD + 6))

Обратите внимание на то, что значения спецрегистров МК объявлены как volatile. Это необходимо, чтобы компилятор не пытался оптимизировать операции обращения к ним, поскольку это не просто участки памяти и их значения могут изменяться без участия ядра.

Настраиваем группирование приоритетов

В первую очередь стоит настроить группировку приоритетов прерываний:
SCB_AIRCR = SCB_AIRCR_GROUP22;
.Данное действие должно выполняться только один раз. В сложных проектах, использующих сторонние библиотеки стоит проверять данный факт. Изменение разбиения приоритетов на группы может привести к некорректной работе прошивки.

Включение тактирование используемой периферии

Напомню, что перед началом работы с периферийными блоками необходимо включить их тактирование:
//Enable SYSCFG , GPIO port A and D clocking
RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOD;
RCC_APB2_ENR |= RCC_APB2_ENR_SYSCFG;

Работать сразу с SYSCFG нельзя, нужно подождать несколько тактов. Но мы и не будем. Займемся инициализацией GPIO.

Инициализация GPIO

Светодиоды инициализируются так же как и в прошлый раз:
//LED3 and LED5 initialization
GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT;
GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT;

Кнопка PA0 и контакт PC7 инициализируются как входные:
//PA0 and PC6 pins initialization
GPIOA_MODER = (GPIOA_MODER & (~GPIO_MODER_0BITS)) | GPIO_MODER_0IN;
GPIOC_MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS)) | GPIO_MODER_6IN;

Вот только для контакта PC6 необходимо включить подтяжку питания. Активация подтяжки производится с помощью регистра GPIOC_PUPDR:
//Enable PC6 pull-up
GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_7BITS)) | GPIOC_PUPDR_6PU;


Настройка EXTI

И так, на нужно настроить следующие параметры — включить прерывания для линий 0 и 6, для линии 0 прерывание по растущему фронту, для линии 6 — прерывание по падающему фронту:
//Set up EXTI
EXTI_RTSR |= EXTI_LINE0;
EXTI_FTSR |= EXTI_LINE6;
EXTI_IMR  = EXTI_LINE0|EXTI_LINE6;

Осталось настроить пины каких портов подключены к линии EXTI (странное решение, например МК stellaris могут генерировать прерывание при любой комбинации пинов, у STM32 с этим сложнее):
//EXTI to port  connection
SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA;
SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS)) | SYSCFG_EXTICR2_6PC;


Настройка NVIC

Осталось настроить приоритеты прерываний и маскировать их для инициации обработки. Обратите внимание, что регистры NVIC_IPR доступны для побайтового обращения, что значительно упрощает доступ только к необходимым байтам приоритетов отдельных векторов прерываний. Достаточно только сделать сдвиг на величину номера вектора прерывания (см. листинг определений). Еще раз напомним, что EXTI Line 0 имеет 6 номер в таблице векторов, а EXTI line 5_9 — номер 23. У STM32 значение имеют только старшие 4 бита приоритета:
//Set interrupts priority
NVIC_IPR6_REG = 0xF0;
NVIC_IPR23_REG = 0x0;

Для демонстрации приоритеты установлены различными.
Теперь можно включить прерывания:
//Enable interrupts
NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT;

С этого момента нажатие на кнопку и закоротки PC6 и GND будет приводить к вызову функций обработчиков прерываний EXTI_Line0_IntHandler и EXTI_Line6_IntHandler соответственно.

Обработка прерываний

В функциях обработки прерываний в первую очередь необходимо очистить прерывание, после этого можно зажечь светодиоды. Для демонстрации приоритетов прерываний в один из обработчиков добавлен вечный цикл. Если приоритет прерывания с вечным циклом ниже приоритета второго — то оно не сможет быть вызвано. Иначе, оно сможет прервать первое. Я предлагаю вам самим попробовать различные знчения приоритетов прерываний и наглядно увидеть к чему это приводит (ВНИМАНИЕ — не забудьте про группы прерываний!).
void EXTI_Line0_IntHandler(void)
{
	//Clear interrupt
	EXTI_PR = EXTI_LINE0;
	//Turn on LED 3
	GPIOD_ODR |= GPIO_ODR_13PIN;
}
void EXTI_Line6_IntHandler(void)
{
	//Clear interrupt
	EXTI_PR = EXTI_LINE6;
	//Turn LED4 
	GPIOD_ODR |= GPIO_ODR_14PIN;
	while(1);
}


Вместо заключения


На всякий случай приведу полный листинг получившейся программы.
Листинг
//Definitions for SCB_AIRCR register
#define SCB_AIRCR			(*(unsigned volatile long*)0xE000ED0C) //acces to SCB_AIRCR
#define SCB_AIRCR_GROUP22	0x05FA0500 //change priority data

//Definitions for RCC_AHB1_ENR register
#define RCC_AHB1_ENR		(*(unsigned volatile long *)(0x40023830)) //acces to RCC_AHB1ENR reg
#define RCC_AHB1_ENR_GPIOA	0x1 //GPIOA bitfield
#define RCC_AHB1_ENR_GPIOC	0x4 //GPIOC bitfield
#define RCC_AHB1_ENR_GPIOD	0x8 //GPIOD bitfield 
//Definitions for RCC_APB2_ENR register
#define RCC_APB2_ENR		(*(unsigned volatile long *)(0x40023844)) //acces to RCC_APB2ENR reg
#define RCC_APB2_ENR_SYSCFG	0x4000 //SYSCFG bitfield
//Definitions for GPIO MODE registers
#define GPIOA_MODER			(*(unsigned volatile long*)(0x40020000)) //acces to GPIOA_MODER reg
#define GPIOC_MODER			(*(unsigned volatile long*)(0x40020800)) //acces to GPIOC_MODER reg
#define GPIOD_MODER			(*(unsigned volatile long*)(0x40020C00)) //acces to GPIOD_MODER reg
//GPIO  ODR register definition
#define GPIOD_ODR			(*(unsigned volatile long*)(0x40020C14)) //acces to GPIOD_MODER reg
#define GPIO_ODR_13PIN		0x2000
#define GPIO_ODR_14PIN		0x4000
//Bitfields definitions
#define GPIO_MODER_0BITS	0x3 //Pin 0 mode bits
#define GPIO_MODER_0IN		0x0 //Pin 0 input mode
#define GPIO_MODER_6BITS	        0x300 //Pin 6 mode bits
#define GPIO_MODER_6IN		0x000 //Pin 6 input mode
#define GPIO_MODER_13BITS	0xC000000 //Pin 13 mode bits
#define GPIO_MODER_13OUT	0x4000000 //Pin 13 output mode
#define GPIO_MODER_14BITS	0x30000000 //Pin 14 mode bits
#define GPIO_MODER_14OUT	0x10000000 //Pin 14 output mode
//GPIOC_PUPDR register definition 
#define GPIOC_PUPDR			(*(unsigned volatile long*)(0x4002080C)) //acces to GPIOC_PUPDR reg
#define GPIOC_PUPDR_6BITS	0x3000 //PC6 bitfield
#define GPIOC_PUPDR_6PU		0x1000 //PC6 pull-up enable
//SYSCFG_EXTIx registers definitions
#define SYSCFG_EXTICR1		(*(unsigned volatile long*)0x40013808)	 //SYSCFG_EXTICR1 acces
#define SYSCFG_EXTICR1_0BITS 0xF //EXTI 0 bits
#define SYSCFG_EXTICR1_0PA	0x0 //EXTI 0 - port A
#define SYSCFG_EXTICR2		(*(unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces
#define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 bits
#define SYSCFG_EXTICR2_6PC	0x200 //EXTI 6 - port C
//EXTI definitions
#define EXTI_IMR	(*(unsigned volatile long*)0x40013C00) //EXTI_IMR reg acces
#define EXTI_LINE0	0x1 //LINE 0 definition
#define EXTI_LINE6	0x40 //LINE 6 definition
#define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg acces
#define EXTI_FTSR (*(unsigned volatile long*)0x40013C0C) //EXTI_FTSR reg acces
#define EXTI_PR	(*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces
//NVIC registers and bits definitions
#define NVIC_ISER0_REG		(*(unsigned volatile long*)0xE000E100) //NVIC_ISER0 reg acces
#define NVIC_ISER0_6VECT	0x40 //vect 6 definition
#define NVIC_ISER0_23VECT	0x800000 //vect 30 definition

#define NVIC_IPR0_ADD	(0xE000E400)
#define NVIC_IPR23_REG	(*(unsigned volatile char*)(NVIC_IPR0_ADD + 23))
#define NVIC_IPR6_REG	(*(unsigned volatile char*)(NVIC_IPR0_ADD + 6))

void EXTI_Line0_IntHandler(void);
void EXTI_Line6_IntHandler(void);

void main()
{
	//NVIC
	SCB_AIRCR = SCB_AIRCR_GROUP22;

	//Enable SYSCFG , GPIO port A,C and D clocking
	RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOD;
	RCC_APB2_ENR |= RCC_APB2_ENR_SYSCFG;
	
	//LED3 and LED5 initialization
	GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT;
	GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT;

	//PA0 and PC6 pins initialization
	GPIOA_MODER = (GPIOA_MODER & (~GPIO_MODER_0BITS)) | GPIO_MODER_0IN;
	GPIOC_MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS)) | GPIO_MODER_6IN;
	//Enable PC7 pull-up
	GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_6BITS)) | GPIOC_PUPDR_6PU;
	
	//Set up EXTI
	EXTI_RTSR |= EXTI_LINE0;
	EXTI_FTSR |= EXTI_LINE6;
	EXTI_IMR  = EXTI_LINE0|EXTI_LINE6;

	//EXTI to port  connection
	SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA;
	SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS)) | SYSCFG_EXTICR2_6PC;

	//Set interrupts priority
	NVIC_IPR6_REG = 0xF0;
	NVIC_IPR23_REG = 0x00;
	//Enable interrupts
	NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT;

	while(1)
	{
	}
}

void EXTI_Line0_IntHandler(void)
{
	//Clear interrupt
	EXTI_PR = EXTI_LINE0;
	//Turn on LED 3
	GPIOD_ODR |= GPIO_ODR_13PIN;
}
void EXTI_Line6_IntHandler(void)
{
	//Clear interrupt
	EXTI_PR = EXTI_LINE6;
	//Turn LED4 
        GPIOD_ODR |= GPIO_ODR_14PIN;
        while(1);
}


Для проверки влияния приоритетов прерываний и приоритетов групп прерываний попробуйте менять приоритеты и наблюдать, что будет происходить (два бита — приоритет внутри группы, 2 бита — приоритет группы).
Tags:STM32Cortex-MARMмикроконтроллеры
Hubs: Programming microcontrollers
+27
99.5k 292
Comments 25
Ads