Записная книжка разработчика

Мои проекты

Прерывания в Cortex M3. Часть 2

| Comments

В первой части (http://32bit.me/?p=839) были приведены некоторые сведения о структуре контроллера прерываний Cortex M3. В этой части будут рассматриваться примеры обработки прерываний в этой архитектуре.

Обработка большинства прерываний происходит следующим образом: в файле cstartup_M.s находится таблица векторов прерываний следующего вида:

__vector_table_0x1c
DCD     0                           ; Reserved
DCD     0                           ; Reserved
DCD     0                           ; Reserved
DCD     0                           ; Reserved
DCD     SVC_Handler                 ; SVCall Handler
DCD     DebugMon_Handler            ; Debug Monitor Handler
DCD     0                           ; Reserved
DCD     PendSV_Handler              ; PendSV Handler
DCD     SysTick_Handler             ; SysTick Handler
DCD     WDT_IRQHandler              ; Watchdog Handler
DCD     TMR0_IRQHandler             ; TIMER0 Handler
DCD     TMR1_IRQHandler             ; TIMER1 Handler

Она устанавливает связь между самим прерыванием и его обработчиком. По умолчанию все обработчики установлены в Default_Handler, эта команда приводит к останову программы. Для того, чтобы задействовать свой обработчик прерывания, нужно скопировать из cstartup_M.s нужное имя функции, например:

void TMR0_IRQHandler(void)
{
//тело обработчика
}

Но, для того, чтобы обработчик работал правильно, нужно сделать ещё две вещи: присвоить прерыванию приоритет и в теле обработчика снять флаг обработки прерывания. Последнее действие информирует контроллер прерывания, что данное прерывание уже обработано.

Итак, приоритет прерывания устанавливается следующим образом:

void NVIC_IntPri(Int32U IntNumber, Int8U Priority)
{
//pNVIC - указатель на таблицу приоритетов прерываний.
volatile Int8U * pNVIC_IntPri = (Int8U *)&IP0;
//проверка того, что номер прерывания не выходит за пределы таблицы
assert((NVIC_WDT < IntNumber) && (NVIC_PLL1 >= IntNumber));
//получаем смещение в таблице
IntNumber -= NVIC_WDT;
//получаем указатель на регистр приоритета прерывания
pNVIC_IntPri += IntNumber;
// устанавливаем приоритет
*pNVIC_IntPri = Priority;
}

В рассмотренном коде наибольшие затруднения может вызвать действие IntNumber -= NVIC_WDT. Проясним его смысл. Дело в том, что формально таблица прерываний начинаются именно с прерывания NVIC_WDT. Всё, что находится выше NVIC_WDT, считается не прерыванием, а системным исключением. NVIC_WDT имеет в таблице прерываний 16-й номер, однако в таблице приоритетов прерываний оно имеет номер 0. Чтобы получить из номера прерывания смещение в таблице приоритетов, мы должны из номера прерывания вычесть значение NVIC_WDT. Дальнейшие действия понятны: мы прибавляем смещение в таблице к адресу самой таблицы приоритетов и в полученный регистр записываем значение приоритета прерывания.  Так как регистры приоритета имеют разрядность 8 бит, все операции с адресами здесь производятся с указателями на байты (Int8U *).

По умолчанию все прерывания имеют приоритет 0 (самый высокий).

Однако, для корректной работы обработчика требуется сообщить контроллеру прерываний, что прерывание обработано, для того, чтобы снять флаг "отложено" (pending). Делается это с помощью аналогичной функции:

void NVIC_ClrPend(Int32U IntNumber)
{
//pNVIC - указатель на таблицу Interrupt Clear-Pending Register
volatile unsigned long * pNVIC_ClrPend = &CLRPEND0;
//проверка того, что номер прерывания не выходит за пределы таблицы
assert((NVIC_WDT <= IntNumber) && (NVIC_PLL1 >= IntNumber));
//получаем смещение в таблице
IntNumber -= NVIC_WDT;
//получаем указатель на нужный регистр
//сейчас регистры 32-битные,
//флаг очистки каждого прерывания занимает 1 бит
//поэтому номер прерывания делим на 32
pNVIC_ClrPend += IntNumber/32;
*pNVIC_ClrPend = (1UL<<(IntNumber%32));
}

Как видно из приведённого кода, он аналогичен коду функции установки приоритета прерывания, за исключением следующих особенностей: указатель вычисляется  в 32-битной арифметике, поэтому мы здесь оперируем с типом (unsigned long*), указатель pNVIC расположен по базовому адресу ICPR0 (Interrupt Clear-Pending Register 0), и номер прерывания делим на 32, так как в каждом регистре находятся флаги 32-х прерываний.

Этот код обязательно должен находиться в конце каждого обработчика прерывания (но у этого правила есть исключение, которое будет рассмотрено ниже).

В случае, если логика работы программы требует запрета и разрешения прерываний, то и эти операции производятся аналогично.
Разрешение прерывания:

void NVIC_IntEnable(Int32U IntNumber)
{
volatile unsigned long * pNVIC_SetEn = &SETENA0;

assert((NVIC_WDT <= IntNumber) && (NVIC_PLL1 >= IntNumber));
IntNumber -= NVIC_WDT;

pNVIC_SetEn += IntNumber/32;
*pNVIC_SetEn = (1UL << (IntNumber%32));
}

Запрет прерывания:

void NVIC_IntDisable(Int32U IntNumber)
{
volatile unsigned long * pNVIC_ClrEn = & CLRENA0;

assert((NVIC_WDT <= IntNumber) && (NVIC_PLL1 >= IntNumber));
IntNumber -= NVIC_WDT;

pNVIC_ClrEn += IntNumber/32;
*pNVIC_ClrEn = (1UL << (IntNumber%32));
}

Ранее говорилось о том, что системные исключения располагаются в таблице векторов прерываний выше, чем NVIC_WDT. Однако, приведённые функции работают только с прерываниями, начиная с NVIC_WDT. В самом деле, для работы с системными прерываниями нужны другие функции.