Говоря простым языком, прерывание это какое-либо внешнее или внутреннее событие, требующее от процессора немедленной реакции на него. При этом выполнение текущей программы на время завершается, процессор сохраняет значения служебных регистров, входит в обработчик прерывания, обрабатывает это прерывание, по выходу восстанавливает служебные регистры и вновь возвращается к месту выполнения основной программы, на котором его прервали. Вообще, в ядре MIPS (а в PIC32 используется именно оно) все прерывания попадают в категорию исключений. К исключениям здесь относится все, что «мешает» нормальной работе основной программы. Например, выполнение процедуры сброса – исключение, ошибка при делении – исключение, и, конечно же, различные прерывания, как внутренние, так и внешние, тоже исключения.
В PIC32 имеются 96 источников прерывания и 64 векторов прерывания. Это значит, что несколько источников могут быть «приписаны» к одному вектору, то есть указателю к функции обработчика прерывания. Нужно учитывать, что механизм прерываний поддерживает одновекторный и мультивекторный режимы. При одновекторном режиме в таблице исключений будет представлен один вектор для прерываний, и, следовательно, будет лишь один обработчик прерываний. Многовекторный режим предоставляет возможность работать с прерываниями в собственных обработчиках, тем самым повышая гибкость и читабельность программы. Давайте начнем разбирать это на примерах и потихоньку вникать во все тонкости прерываний.
Обработчик прерываний может быть представлен в коде следующим образом:
void __attribute__ ((interrupt(ipl3),vector(0)))
InterruptHandler(void)
{
/*Обработка прерывания*/
}
Иличерездирективу #pragma:
#pragma interrupt InterruptHandler ipl5 vector 0
void InterruptHandler(void)
{
/*Обработка прерывания*/
}
Здесь iplx задает приоритет прерывания, x может быть от 0 до 7 (7 самый высокий приоритет). vector 0 – номер вектора прерывания.
Правда есть еще более удобный способ записи функции обработчика с помощью макроса __ISR:
void __ISR(0, ipl1) InterruptHandler (void)
{
/*Обработка прерывания*/
}
Как мы видим, обработчик прерывания похож на простую функцию и может объявляться, где и остальные функции (до main(), после main(), в другом файле, но с обязательной ссылкой на этот файл). Но в отличие от других функций он ничего не принимает и ничего не возвращает (как видно, везде у него void), а также он не может быть вызван из другой функции.
Итак, как же нам организовать прерывание, допустим, от таймера T2? Для начала настроим сам таймер и его прерывание:
OpenTimer2(T2_OFF | T2_SOURCE_INT | T2_PS_1_256, 0xFFFF);
ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_3 | T2_INT_SUB_PRIOR_0);
T2_INT_PRIOR_3 – устанавливает приоритет прерывания,
T2_INT_SUB_PRIOR_0 – устанавливает подприоритет прерывания, хотя это сейчас и не нужно, но в случае, если сработали два прерывания с одинаковым приоритетом, то привелегии получит тот, у кого подприоритет будет выше. Подприоритет изменяется от 0 до 3.
Помимо такой записи библиотека plib предлагает следующие макросы:
mT2SetIntPriority(3) – устанавливает приоритет для прерывания от T2.
mT2IntEnable(1) – разрешает прерывание.
mT2ClearIntFlag() – очищает флаг прерывания (в обработчике пригодиться).
И нужный для разрешения одновекторных прерываний макрос INTEnableSystemSingleVectoredInt().
Обработчик прерываний будет выглядеть так:
void __ISR(0, ipl3) InterruptHandler(void)
{
if ((PORTD & 0x0080)==0x0080) {PORTDCLR=0x0080;}
else {PORTDSET=0x0080;}
mT2ClearIntFlag();
}
Здесь мы то включаем, то выключаем светодиод при каждом заходе в обработчик, и по выходу из него очищаем флаг прерывания T2, чтобы не входить в него сразу же снова.
В основной программе с помощью двух кнопок мы либо включаем таймер, либо отключаем:
if (PORTG & 0x40)
{
WriteTimer2(0);//TMR2=0;
T2CONSET = 0x8000;
PORTDSET=0x0040;
}
if (PORTG & 0x80)
{
WriteTimer2(0);//TMR2=0;
T2CONCLR = 0x8000;
}
Весь код:
#include <p32xxxx.h>
#include <plib.h>
// настраиваем частоту
#pragma config FNOSC=XTPLL
#pragma config FPLLIDIV=DIV_2, FPLLMUL=MUL_20, FPLLODIV=DIV_1
#pragma config FWDTEN=OFF // отключаем сторожевой таймер
// Обработчик прерывания
void __ISR(0, ipl3) InterruptHandler(void)
{
if ((PORTD & 0x0080)==0x0080) {PORTDCLR=0x0080;}
else {PORTDSET=0x0080;}
mT2ClearIntFlag();
}
//основная функция
main ()
{
//настройка кэша
mCheConfigure(CHE_CONF_WS2 | CHE_CONF_PF_ALL | CHE_CONF_COH_INVUNL | CHE_CONF_DC_NONE);
CheKseg0CacheOn();
mBMXDisableDRMWaitState();
//Настройка портов
TRISD=0x0000;
PORTD=0x0000;
TRISG=0x03C0;
PORTG=0x0000;
//настройка таймера
OpenTimer2(T2_OFF | T2_SOURCE_INT | T2_PS_1_256, 0xFFFF);
ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_3 | T2_INT_SUB_PRIOR_0);
//одновекторный режим
INTEnableSystemSingleVectoredInt();
//------------------------------------------------
//бесконечный цикл
do
{
if (PORTG & 0x40)
{
WriteTimer2(0);//TMR2=0;
T2CONSET = 0x8000;
PORTDSET=0x0040;
}
if (PORTG & 0x80)
{
WriteTimer2(0);//TMR2=0;
T2CONCLR = 0x8000;
PORTDCLR=0x0040;
}
}
while(1); // закрытие бесконечного цикла
} // закрытие функции main
В общем, все довольно просто … с одним прерыванием. В одновекторном режиме можно обрабатывать и несколько прерываний, но тогда в обработчике нужно будет вручную организовать вложенность прерываний: каждый раз при заходе в обработчик проверять, установлен ли флаг определенного прерывания, и если да, то обрабатывать его. В случае большого количества источников прерываний это становится крайне неудобно, к тому же возрастает нагрузка на процессор. Все проблемы решает многовекторный режим прерываний!
В этом режиме обязательно нужно учитывать номер вектора прерывания (список прерываний и соответствующие им номера векторов приведены в даташите на контроллер, стр. 112-114). Теперь функция обработчика будет не одна, их будет столько, сколько векторов прерываний мы ожидаем увидеть в нашей программе. Итак, добавим в программу таймер T3 и соответствующий обработчик.
OpenTimer3(T3_OFF | T3_SOURCE_INT | T3_PS_1_256, 0x1FFF);
ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_5 | T3_INT_SUB_PRIOR_0);
void __ISR( _TIMER_3_VECTOR, ipl5) T3InterruptHandler( void)
{
if ((PORTD & 0x0040)==0x0040) {PORTDCLR=0x0040;}
else {PORTDSET=0x0040;}
mT3ClearIntFlag();
}
Обработчик таймера T2 будет выглядеть следующим образом:
void __ISR( _TIMER_2_VECTOR, ipl3) T2InterruptHandler(void)
{
if ((PORTD & 0x0080)==0x0080) {PORTDCLR=0x0080;}
else {PORTDSET=0x0080;}
mT2ClearIntFlag();
}
Незабываем включить мультивекторный режим:
INTEnableSystemMultiVectoredInt();
По первой кнопке будем включать T2, по второй – T3, по третьей отключать оба таймера:
if (PORTG & 0x40)
{
WriteTimer2(0);//TMR2=0;
T2CONSET = 0x8000;
}
if (PORTG & 0x80)
{
WriteTimer3(0);//TMR3=0;
T3CONSET = 0x8000;
}
if (PORTG & 0x0100)
{
WriteTimer2(0);//TMR2=0;
WriteTimer3(0);//TMR3=0;
T2CONCLR = 0x8000;
T3CONCLR = 0x8000;
}
Весь код выглядит так:
#include <p32xxxx.h>
#include <plib.h>
// настраиваем частоту
#pragma config FNOSC=XTPLL
#pragma config FPLLIDIV=DIV_2, FPLLMUL=MUL_20, FPLLODIV=DIV_1
#pragma config FWDTEN=OFF // отключаем сторожевой таймер
// Обработчики прерываний
void __ISR( _TIMER_2_VECTOR, ipl3) T2InterruptHandler(void)
{
if ((PORTD & 0x0080)==0x0080) {PORTDCLR=0x0080;}
else {PORTDSET=0x0080;}
mT2ClearIntFlag();
}
void __ISR( _TIMER_3_VECTOR, ipl5) T3InterruptHandler( void)
{
if ((PORTD & 0x0040)==0x0040) {PORTDCLR=0x0040;}
else {PORTDSET=0x0040;}
mT3ClearIntFlag();
}
//основная функция
main ()
{
//настройка кэша
mCheConfigure(CHE_CONF_WS2 | CHE_CONF_PF_ALL | CHE_CONF_COH_INVUNL | CHE_CONF_DC_NONE);
CheKseg0CacheOn();
mBMXDisableDRMWaitState();
//Настройка портов
TRISD=0x0000;
PORTD=0x0000;
TRISG=0x03C0;
PORTG=0x0000;
//настройка таймеров
OpenTimer2(T2_OFF | T2_SOURCE_INT | T2_PS_1_256, 0xFFFF);
ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_3 | T2_INT_SUB_PRIOR_0);
OpenTimer3(T3_OFF | T3_SOURCE_INT | T3_PS_1_256, 0x1FFF);
ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_5 | T3_INT_SUB_PRIOR_0);
//многовекторный режим
INTEnableSystemMultiVectoredInt();
//------------------------------------------------
//бесконечный цикл
do
{
if (PORTG & 0x40)
{
WriteTimer2(0);//TMR2=0;
T2CONSET = 0x8000;
}
if (PORTG & 0x80)
{
WriteTimer3(0);//TMR3=0;
T3CONSET = 0x8000;
}
if (PORTG & 0x0100)
{
WriteTimer2(0);//TMR2=0;
WriteTimer3(0);//TMR3=0;
T2CONCLR = 0x8000;
T3CONCLR = 0x8000;
}
}
while(1); // закрытие бесконечного цикла
} // закрытие функции main
Нажимаем на кнопки, в обработчиках отдаются команды на включение или отключение соответствующих светодиодов. Если бы не было заходов в обработчики, то мы бы ничего интересного не увидели.
© digitrode.ru