STM8S SDCC interrupt
http://www.count-zero.ru/2016/stm8s_exti_interrupt/
STM8S103F3P6 + SDCC: внешние прерывания и режимы энергосбережения
разделы: STM8 , дата: 9 июля 2016г.
В STM8S внешние прерывания походят на PCINT в AVR, в том плане, что одно прерывание, является общем для всего порта. Спасает ситуацию тот факт, что от некоторых портов в некоторых чипах, фактически может остаться лишь пара пинов.
Для примера, попробуем собрать схему аналогичную схеме поста про MSP430.
В качестве чипа я использовал известную плату с aлиекспресс. В качестве компилятора я использовал SDCC, в качестве флешера stm8flash.
Итак, на PB5 обозначенной платы висит светодиод "Test", будем его использовать для индикации рабочего цикла. Для индикации нажатой кнопки, на пин С3, посадим светодиод+резистор. Саму кнопку, посадим на пин D2. Схема подключения кнопки здесь.
Вначале составим программу индикации нажатой кнопки на основе опроса(Polling) порта:
/* homepage: www.count-zero.ru/stm8/ * hard&soft: SDCC && stm8flash && stm8s103f3p6 * compile: sdcc -mstm8 -DSTM8S103 ./code.c * download to chip: # stm8flash -c stlinkv2 -p stm8s103 -w ./code.ihx * */ #define CLK_CKDIVR *(unsigned char*)0x50C6 #define CFG_GCR *(unsigned char*)0x7F60 #define PA_ODR *(unsigned char*)0x5000 #define PA_IDR *(unsigned char*)0x5001 #define PA_DDR *(unsigned char*)0x5002 #define PA_CR1 *(unsigned char*)0x5003 #define PA_CR2 *(unsigned char*)0x5004 #define PB_ODR *(unsigned char*)0x5005 #define PB_IDR *(unsigned char*)0x5006 #define PB_DDR *(unsigned char*)0x5007 #define PB_CR1 *(unsigned char*)0x5008 #define PB_CR2 *(unsigned char*)0x5009 #define PC_ODR *(unsigned char*)0x500A #define PC_IDR *(unsigned char*)0x500B #define PC_DDR *(unsigned char*)0x500C #define PC_CR1 *(unsigned char*)0x500D #define PC_CR2 *(unsigned char*)0x500E #define PD_ODR *(unsigned char*)0x500F #define PD_IDR *(unsigned char*)0x5010 #define PD_DDR *(unsigned char*)0x5011 #define PD_CR1 *(unsigned char*)0x5012 #define PD_CR2 *(unsigned char*)0x5013 #define EXTI_CR1 *(unsigned char*)0x50A0 #define EXTI_CR2 *(unsigned char*)0x50A1 #define P7 7 #define P6 6 #define P5 5 #define P4 4 #define P3 3 #define P2 2 #define P1 1 #define P0 0 #define LED P3 #define TEST P5 #define BUTTON P2 static void delay(unsigned int t) { while(t--); } int main( void ) { // led PC_DDR|=(1<<LED); PC_CR1|=(1<<LED); // on board led aka 'test' PB_DDR|=(1<<TEST); PB_CR1|=(1<<TEST); CLK_CKDIVR=4; //set prescaler = 16, 1MHz for(;;){ if (PD_IDR & (1<<BUTTON)) PC_ODR |= (1<<LED); else PC_ODR &= ~(1<<LED); delay(6000); PB_ODR ^= (1<<TEST); } }
Здесь, пин D2 используется в HiZ режиме, он не должен иметь разность потенциалов ни с землей ни с шиной питания(проверте!!) Если при нажатии на кнопку, загорается светодиод, а при отпускании кнопки, он снова гаснет, значит все схема собрана верно и можно переходить к прерываниям. В datasheet на stm8s103f3 имеется таблица доступных прерываний:
Всего их шестнадцать, из них шесть - внешние прерывания. В SDCC прерывания вызываются из функций с директивой __interrupt:
void ext_port_d(void) __interrupt(6) { }
где в качестве аргумента директивы выступает номер прерывания в обозначенной таблице. Так как я использую порт D, то мне соответственно, следует использовать вектор шестого прерывания. Для контроля можно взглянуть на таблицу векторов в ассемблерном файле:
;-------------------------------------------------------- ; interrupt vector ;-------------------------------------------------------- .area HOME __interrupt_vect: int s_GSINIT ;reset int 0x0000 ;trap int 0x0000 ;int0 int 0x0000 ;int1 int 0x0000 ;int2 int 0x0000 ;int3 int 0x0000 ;int4 int 0x0000 ;int5 int _ext_port_d ;int6 int 0x0000 ;int7 int 0x0000 ;int8 int 0x0000 ;int9 int 0x0000 ;int10 int 0x0000 ;int11 int 0x0000 ;int12 int 0x0000 ;int13 int 0x0000 ;int14 int 0x0000 ;int15 int 0x0000 ;int16 int 0x0000 ;int17 int 0x0000 ;int18 int 0x0000 ;int19 int 0x0000 ;int20 int 0x0000 ;int21 int 0x0000 ;int22 int 0x0000 ;int23 int 0x0000 ;int24 int 0x0000 ;int25 int 0x0000 ;int26 int 0x0000 ;int27 int 0x0000 ;int28 int 0x0000 ;int29
Как видно, в таблице, на месте шестого прерывания появился, соответствующий функции вектор.
Теперь о конфигурации внешнего прерывания.
- Для этого нужно:
- Сконфигурировать пин на вход (PxDDR =0);
- Разрешить прерывания с этого пина (PxСR2=1);
- Задать условие срабатывания через регистр EXTI_CR1 или EXIT_CR2;
- Разрешить прерывания ассемблерной командой RIM;
- Задать обработчик прерывания.
В STM8, через регистры EXTI_CR1 и EXTI_CR2 можно сконфигурировать внешние прерывания, так, чтобы они наступали при событиях:
- Логическом нуле;
- Нарастающем фронте;
- Падающем фронте:
- При любом изменении фронта.
Биты конфигурации регистров EXTI_CR1 и EXTI_CR2 для каждого прерывания:
Осталось дело за малым, написать пробную программу:
/* homepage: www.count-zero.ru/stm8/ * hard&soft: SDCC && stm8flash && stm8s103f3p6 * compile: sdcc -mstm8 -DSTM8S103 ./code.c * download to chip: # stm8flash -c stlinkv2 -p stm8s103 -w ./code.ihx * */ #define CLK_CKDIVR *(unsigned char*)0x50C6 #define CFG_GCR *(unsigned char*)0x7F60 #define PA_ODR *(unsigned char*)0x5000 #define PA_IDR *(unsigned char*)0x5001 #define PA_DDR *(unsigned char*)0x5002 #define PA_CR1 *(unsigned char*)0x5003 #define PA_CR2 *(unsigned char*)0x5004 #define PB_ODR *(unsigned char*)0x5005 #define PB_IDR *(unsigned char*)0x5006 #define PB_DDR *(unsigned char*)0x5007 #define PB_CR1 *(unsigned char*)0x5008 #define PB_CR2 *(unsigned char*)0x5009 #define PC_ODR *(unsigned char*)0x500A #define PC_IDR *(unsigned char*)0x500B #define PC_DDR *(unsigned char*)0x500C #define PC_CR1 *(unsigned char*)0x500D #define PC_CR2 *(unsigned char*)0x500E #define PD_ODR *(unsigned char*)0x500F #define PD_IDR *(unsigned char*)0x5010 #define PD_DDR *(unsigned char*)0x5011 #define PD_CR1 *(unsigned char*)0x5012 #define PD_CR2 *(unsigned char*)0x5013 #define EXTI_CR1 *(unsigned char*)0x50A0 #define EXTI_CR2 *(unsigned char*)0x50A1 #define P7 7 #define P6 6 #define P5 5 #define P4 4 #define P3 3 #define P2 2 #define P1 1 #define P0 0 #define LED P3 #define TEST P5 #define BUTTON P2 static void delay(unsigned int t) { while(t--); } void ext_port_d(void) __interrupt(6) { if (PD_IDR & (1<<BUTTON)) PC_ODR |= (1<<LED); else PC_ODR &= ~(1<<LED); } int main( void ) { // led PC_DDR|=(1<<LED); PC_CR1|=(1<<LED); PB_DDR|=(1<<TEST); PB_CR1|=(1<<TEST); PB_ODR&=~(1<<TEST); // button PD_DDR&=~(1<<BUTTON); PD_CR1&=~(1<<BUTTON); PD_CR2|=(1<<P2); // enable interrupt EXTI_CR1=(1<<7)|(1<<6); // any change // mode CLK_CKDIVR=4; //set prescaler = 16, 1MHz // enable interrupt __asm RIM; __endasm; for(;;){ delay(6000); PB_ODR ^= (1<<TEST); } }
Программа должна работать аналогично предыдущей, с той лишь разницей, что в этот раз она работает через прерывания.
Ок. Теперь, когда микроконтроллер работает через прерывания, можно посылать его в спящий режим, вместо выполнения пустого цикла for(;;).
В stm8 есть режим энергосбережения WFI ( Wait For Interrupt), который позволит микроконтроллеру просыпаться по прерыванию. Однако у него есть неприятная фича, разбуженный микроконтроллер, после выполнения прерывания, не засыпает ввтоматически после завершения обработчика. По умолчанию, управление после передается основной программе.
Для, того что бы он снова засыпал до следующего прерывания, нужно установить режми активности: "Interrupt Only" путем установки бита AL в "Глобальном Конфигурационном Регистре" - CFG_GCR:
теперь меняем всего две строчки в коде:
/* homepage: www.count-zero.ru/stm8/ * hard&soft: SDCC && stm8flash && stm8s103f3p6 * compile: sdcc -mstm8 -DSTM8S103 ./code.c * download to chip: # stm8flash -c stlinkv2 -p stm8s103 -w ./code.ihx * */ #define CLK_CKDIVR *(unsigned char*)0x50C6 #define CFG_GCR *(unsigned char*)0x7F60 #define PA_ODR *(unsigned char*)0x5000 #define PA_IDR *(unsigned char*)0x5001 #define PA_DDR *(unsigned char*)0x5002 #define PA_CR1 *(unsigned char*)0x5003 #define PA_CR2 *(unsigned char*)0x5004 #define PB_ODR *(unsigned char*)0x5005 #define PB_IDR *(unsigned char*)0x5006 #define PB_DDR *(unsigned char*)0x5007 #define PB_CR1 *(unsigned char*)0x5008 #define PB_CR2 *(unsigned char*)0x5009 #define PC_ODR *(unsigned char*)0x500A #define PC_IDR *(unsigned char*)0x500B #define PC_DDR *(unsigned char*)0x500C #define PC_CR1 *(unsigned char*)0x500D #define PC_CR2 *(unsigned char*)0x500E #define PD_ODR *(unsigned char*)0x500F #define PD_IDR *(unsigned char*)0x5010 #define PD_DDR *(unsigned char*)0x5011 #define PD_CR1 *(unsigned char*)0x5012 #define PD_CR2 *(unsigned char*)0x5013 #define EXTI_CR1 *(unsigned char*)0x50A0 #define EXTI_CR2 *(unsigned char*)0x50A1 #define P7 7 #define P6 6 #define P5 5 #define P4 4 #define P3 3 #define P2 2 #define P1 1 #define P0 0 #define LED P3 #define TEST P5 #define BUTTON P2 static void delay(unsigned int t) { while(t--); } void ext_port_d(void) __interrupt(6) { if (PD_IDR & (1<<BUTTON)) PC_ODR |= (1<<LED); else PC_ODR &= ~(1<<LED); } int main( void ) { // led PC_DDR|=(1<<LED); PC_CR1|=(1<<LED); PB_DDR|=(1<<TEST); PB_CR1|=(1<<TEST); PB_ODR&=~(1<<TEST); // button PD_DDR&=~(1<<BUTTON); PD_CR1&=~(1<<BUTTON); PD_CR2|=(1<<P2); // enable interrupt EXTI_CR1=(1<<7)|(1<<6); // any change // mode CLK_CKDIVR=4; //set prescaler = 16, 1MHz CFG_GCR=(1<<1); // interrupt-only activation mode // enable interrupt __asm RIM; WFI; __endasm; for(;;){ delay(6000); PB_ODR ^= (1<<TEST); } }
и.... беспрерывно мигающий зеленый светодиод на плате наконец-то остановился. Микроконтроллер в спящем режиме. Индикация нажатий кнопки при этом работает по прежнему.
Ок. А как можно послать микроконтроллер в самый глубокий сон? С одной сторы, это просто. Достаточно ассемблерную команду RIM заменить на HALT. Онако бит AL на этот режм не будет действовать. Проснувшись от прерывания, он не уйдет обратно в спящий режим. Поэтому самым эффективным способом борьбы с таким поведением, будет размещение команды HALT внутри бесконечного цикла for(;;).
Код:
/* homepage: www.count-zero.ru/stm8/ * hard&soft: SDCC && stm8flash && stm8s103f3p6 * compile: sdcc -mstm8 -DSTM8S103 ./code.c * download to chip: # stm8flash -c stlinkv2 -p stm8s103 -w ./code.ihx * */ #define CLK_CKDIVR *(unsigned char*)0x50C6 #define CFG_GCR *(unsigned char*)0x7F60 #define PA_ODR *(unsigned char*)0x5000 #define PA_IDR *(unsigned char*)0x5001 #define PA_DDR *(unsigned char*)0x5002 #define PA_CR1 *(unsigned char*)0x5003 #define PA_CR2 *(unsigned char*)0x5004 #define PB_ODR *(unsigned char*)0x5005 #define PB_IDR *(unsigned char*)0x5006 #define PB_DDR *(unsigned char*)0x5007 #define PB_CR1 *(unsigned char*)0x5008 #define PB_CR2 *(unsigned char*)0x5009 #define PC_ODR *(unsigned char*)0x500A #define PC_IDR *(unsigned char*)0x500B #define PC_DDR *(unsigned char*)0x500C #define PC_CR1 *(unsigned char*)0x500D #define PC_CR2 *(unsigned char*)0x500E #define PD_ODR *(unsigned char*)0x500F #define PD_IDR *(unsigned char*)0x5010 #define PD_DDR *(unsigned char*)0x5011 #define PD_CR1 *(unsigned char*)0x5012 #define PD_CR2 *(unsigned char*)0x5013 #define EXTI_CR1 *(unsigned char*)0x50A0 #define EXTI_CR2 *(unsigned char*)0x50A1 #define P7 7 #define P6 6 #define P5 5 #define P4 4 #define P3 3 #define P2 2 #define P1 1 #define P0 0 #define LED P3 #define TEST P5 #define BUTTON P2 static void delay(unsigned int t) { while(t--); } void ext_port_d(void) __interrupt(6) { if (PD_IDR & (1<<BUTTON)) PC_ODR |= (1<<LED); else PC_ODR &= ~(1<<LED); } int main( void ) { // led PC_DDR|=(1<<LED); PC_CR1|=(1<<LED); PB_DDR|=(1<<TEST); PB_CR1|=(1<<TEST); PB_ODR&=~(1<<TEST); // button PD_DDR&=~(1<<BUTTON); PD_CR1&=~(1<<BUTTON); PD_CR2|=(1<<P2); // enable interrupt EXTI_CR1=(1<<7)|(1<<6); // any change // mode CLK_CKDIVR=4; //set prescaler = 16, 1MHz CFG_GCR=(1<<1); // interrupt-only activation mode // enable interrupt __asm RIM; __endasm; for(;;){ __asm HALT; __endasm; delay(6000); PB_ODR ^= (1<<TEST); } }
Май(1)
Апрель(2)
Март(2)
Февраль(2)
Январь(1)
Декабрь(1)
Ноябрь(1)
Октябрь(3)
Сентябрь(3)
Август(1)
Апрель(2)
Февраль(2)
Ноябрь(4)
Октябрь(2)
Август(3)
Июль(7)
Апрель(4)
Март(8)
Ноябрь(4)
Октябрь(3)
Сентябрь(10)
Aвгуст(4)
Июль(6)
Обратная связь
Интересуют вопросы реализации алгоритмов, программирования, выбора электроники и прочая информация, постараюсь осветить в отдельных статьях
пишите мне на netdm@mail.ru
Добавить комментарий