STM8S103F3P6 + SDCC: внешние прерывания и режимы энергосбережения

разделы: STM8 , дата: 9 июля 2016г.

В STM8S внешние прерывания походят на PCINT в AVR, в том плане, что одно прерывание, является общем для всего порта. Спасает ситуацию тот факт, что от некоторых портов в некоторых чипах, фактически может остаться лишь пара пинов.

Для примера, попробуем собрать схему аналогичную схеме поста про MSP430.

В качестве чипа я использовал известную плату с алиэксперсс. В качестве компилятора я использовал 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

Как видно, в таблице, на месте шестого прерывания появился, соответствующий функции вектор.

Теперь о конфигурации внешнего прерывания.

    Для этого нужно:
  1. Сконфигурировать пин на вход (PxDDR =0);
  2. Разрешить прерывания с этого пина (PxСR2=1);
  3. Задать условие срабатывания через регистр EXTI_CR1 или EXIT_CR2;
  4. Разрешить прерывания ассемблерной командой RIM;
  5. Задать обработчик прерывания.

В STM8, через регистры EXTI_CR1 и EXTI_CR2 можно сконфигурировать внешние прерывания, так, чтобы они наступали при событиях:

  1. Логическом нуле;
  2. Нарастающем фронте;
  3. Падающем фронте:
  4. При любом изменении фронта.

Биты конфигурации регистров 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);
        }
}
поделиться: