Alguns meses atrás nós vimos como funcionam as portas de E/S dos RL78 (veja Primeiras experiências com o RL78). Neste post vamos conhecer um pouco sobre a operação do sistema de interrupções destes microcontroladores.
O sistema de interrupções do RL78 é bastante avançado, possui um mecanismo de vetoramento individual de interrupções e um sistema de 4 níveis programáveis de prioridade para cada uma das fontes de interrupção. Ao todo existem 54 fontes de interrupção oriundas dos mais diversos periféricos. Dentre as fontes de interrupção do RL78 podemos destacar: interrupções externas INTPx, interrupção de teclado, interrupções dos timers e TAU, interrupção do ADC, interrupções dos periféricos de comunicação, DMA, hardware de divisão, etc. Há também um tipo especial de interrupção, a interrupção por software, disparada pela execução da instrução assembly BRK. Ela é uma interrupção não mascarável e não é afetada pelo mecanismo de prioridade de interrupções nem pelo controle global de interrupções (IE no registrador PSW).
A disponibilidade de um sistema com 4 níveis de prioridade significa que é possível configurar prioridades diferentes para cada fonte de interrupção numa aplicação. Além disso, é possível que uma interrupção de mais alta prioridade interrompa uma interrupção de menor prioridade que se encontre em tratamento.
A qualquer instante, os bits ISP0 e ISP1 (no registrador PSW da CPU) indicam qual o nível corrente de prioridade. Qualquer interrupção de prioridade igual ou superior ao nível corrente poderá interromper a execução do programa (desde que as interrupções estejam habilitadas, IE = 1).
Cada interrupção é composta pelos seguintes elementos: um flag que indica a pendência ou não da interrupção, um bit de mascaramento e dois bits de configuração da prioridade da interrupção.
Os flags de interrupção são armazenados nos registradores IFxx (IF0L, IF0H, IF1L, IF1H, IF2L, IF2H e IF3L. Alguns flags de interrupção nos registradores IFxx são compartilhados com mais de uma fonte de interrupção, neste caso o fabricante recomenda não utilizar simultaneamente tais interrupções. Uma característica dos RL78 é que ao reconhecer uma interrupção o respectivo flag é automaticamente apagado pelo hardware, mas além disso, os registradores IFxx também podem ser lidos e escritos livremente pelo software.
Os bits de mascaramento de interrupção permitem controlar quais interrupções estão mascaradas e quais não estão. Uma interrupção que esteja mascarada não pode afetar a CPU. Os registradores responsáveis pelo mascaramento das interrupções são os MKxx (MK0L, MK0H, MK1L, MK1H, MK2L, MK2H e MK3L). Vale lembrar que como se trata do mascaramento de interrupções, um bit em 1 significa que a interrupção está mascarada (desabilitada) ao passo que um bit em 0 significa que a interrupção não está mascarada (ou seja, ela está habilitada).
Os bits de configuração de prioridade de interrupção são armazenados nos registradores PRxxx (PR00L, PR10L, PR00H, PR10H, PR01L, PR11L, PR01H, PR11H, PR02L, PR12L, PR02H, PR12H, PR03L, PR13L), note que a nomenclatura destes registradores obedece ao seguinte formato: PRxyz, onde x pode ser 0 ou 1 (PR0yz guarda o bit menos significativo dos dois bits da prioridade e PR1yz guarda o bit mais significativo dos dois bits da prioridade), y corresponde aos valores 0, 1, 2 e 3 tal qual os registradores IFxx e MKxx, e z corresponde ao L ou H (tal qual os registradores IFxx e MKxx).
Note que a prioridade atribuída a uma determinada interrupção pode ir de 00 (0) a 11(3), onde 0 é a maior prioridade e 3 a menor. Sempre que ocorre uma interrupção, a configuração de prioridade dela (registradores PR0xx e PR1xx) é comparada com a prioridade atual (bits ISP0 e ISP1 no PSW), caso a interrupção tenha prioridade maior ou igual a atual, o programa desvia para o respectivo vetor de interrupção.
A utilização de interrupções em C no compilador IAR é muito simples. Existem duas macros para habilitação e desabilitação global das interrupções (é necessário incluir o arquivo intrinsics.h):
__enable_interrupt(); // habilita interrupções (EI=1)
__disable_interrupt(); // desabilita interrupções (EI=0)
Para habilitar/desabilitar uma interrupção podemos escrever uma expressão C com o nome do bit de mascaramento de interrupção, assim, para habilitar a interrupção externa INTP0, basta escrever:
PMK0 = 0; // habilita a interrupção INTP0
No caso do timer de intervalo, utilizamos:
ITMK = 0; // habilita a interrupção do timer de intervalo
Para definir o nível de prioridade de uma determinada interrupção podemos utilizar também os nomes dos respectivos bits de seleção de prioridade, no caso da interrupção INTP0, os bits PPR00 e PPR10. Assim, para configurar esta interrupção para prioridade 2 podemos escrever:
PPR00 = 0; PPR10 = 1;
O tratamento da interrupção é feito facilmente através do seguinte código:
#pragma vector = INTP0_vect __interrupt void trata_INTP0(void) { // código de tratamento da interrupção }
Agora que já vimos o básico sobre as interrupções do RL78, vamos nos concentrar em uma fonte de interrupção em particular: o timer de intervalo. Este é um timer simples de 12 bits que pode ser utilizado para gerar interrupções periódicas e servir como base de tempo em aplicações. Este timer pode operar utilizando duas fontes de clock: Fsub ou Fil.
O sinal Fsub é proveniente do oscilador XT1 que opera com um cristal de 32768Hz ou um sinal de clock externo na mesma frequência. O sinal Fil é derivado do oscilador interno de 15kHz. A seleção entre as duas fontes de clock é selecionada através do bit WUTMMCK0 (registrador OSMC): quando WUTMMCK0 = 0, seleciona-se Fsub como fonte de clock do timer, quando WUTMMCK0 = 1, seleciona-se Fil como fonte de clock do timer.
É importante ressaltar que antes de se realizar qualquer configuração do timer é necessário habilitá-lo através do bit RTCEN (registrador PER0). Uma vez que RTCEN=1, o timer de intervalo e o RTC interno podem ser configurados.
Para configurar o período da interrupção utiliza-se o registrador de 16 bits ITMC. Este registrador é composto de dois campos: RINTE que permite iniciar a contagem do timer e ITCMP0 a ITCMP11 que permitem configurar o período de interrupção.
Como o período de interrupção possui 12 bits, é possível ter um fator de divisão do clock de entrada de 1 a 4096 (o fator de divisão é igual ao valor escrito no campo ITCMP + 1). O exemplo a seguir configura o timer para gerar uma interrupção a cada 3750 contagens do clock interno de 15kHz, ou seja 4 Hz ou 4 interrupções por segundo. O led D2 da placa de demonstração do RL78G13 deverá piscar numa frequência de 4Hz! O arquivo completo do projeto no IAR está aqui: pisca_led_int .
/************************************************** * * Pisca led com RL78/G13, kit YRPBRL78G13 * * Led3 conectado ao pino P7.7 * * Fábio Pereira * 05/10/2012 * www.sctec.com.br/blog * **************************************************/ #include "ior5f100le.h" #include "ior5f100le_ext.h" #include "intrinsics.h" #pragma location = "OPTBYTE" __root const char opbyte0 = 0xEF; // Watchdog desligado #pragma location = "OPTBYTE" __root const char opbyte1 = 0x73; // Configura detector de baixa tensão #pragma location = "OPTBYTE" __root const char opbyte2 = 0xE8; // oscilador HS 32MHz #pragma location = "OPTBYTE" __root const char opbyte3 = 0x85; // debug ativado, sem proteção de memória #define LED_D2 P7_bit.no7 #pragma vector = INTIT_vect __interrupt void trata_timer(void) { LED_D2 = !LED_D2; } void main(void) { LED_D2 = 1; // desliga o led PM7_bit.no7 = 0; // porta P7.7 como saída RTCEN = 1; // ativa o timer de intervalo OSMC = 0x10; // WUTMMCK0 = 1 clock do timer = fil (15kHz) ITMC = 0x8EA5; // habilita o timer e configura o fator de divisao para 3749+1 = 3750 ITMK = 0; // habilita a interrupção do timer de intervalo __enable_interrupt(); while (1); }