Lab. Processadores: Programação do LPC2478 usando o gcc

| Laboratório de Processadores Programação do LPC2378 usando o arm-elf-gcc Parte 7: Interrupções |

Neste exemplo vamos fazer o programa a07_T0_FIQ para configurar o Kit de modo a usar a interrupção tipo FIQ para fazer piscar o LED de forma cadenciada usando o timer T0. Para configurar e testar o programa fazemos como de costume:

Configurar para gravar na memória Flash

Colocar o kit em modo de programação

Compilar e gravar a flash com o comando:

    make isp

Testar chamando o terminal `ltser` e iniciar a execução apertando no botão de RESET.

Configurar para gravar na memória RAM

Pode-se executar o programa na RAM. Até os vertores de interrupção podem rer remapeados para funcionar a partir da RAM a partir do endereço 0x40000000. Um registrador de configuração do LPC2378 chamado MEMMAP permite remapear os vetores de interrupção para a RAM.

O programa mon24 inclui um carregador de arquivos *.hex que pode colocar o programa na RAM usando o terminal ltser. Isto é feito automaticamente usando a diretiva `make tser` deste projeto. Antes de executar `make tser` é necessário que o monitor mon24 esteja gravado na Flash e sendo executado. Então basta fazer

    make tser

O programa será compilado com a configuração para executar na RAM e carregado na RAM usando o terminal `ltser`. Para executa-lo usa-se o comando P do monitor mon24.

### Análise do programa: Configuração do Timer

Agora veremos como este programa foi feito, estudando os recursos do LPC2378 que foram usados. Neste programa o timer T0 foi configurado para gerar interrupções tipo FIQ. A rotina de atendimento da interrupção faz acender ou apagar um LED.

Os registradores usados na configuração do timer são os seguintes:

T0CR 0xE0004004 | Timer Control Register. Habilita ou Inicializa o Timer bit 0: Habilita o Timer. bit 1: Inicializa o timer com zero.
T0TC 0xE0004008 | T0 Timer Count. Neste registrador está o valor da contagem do timer.
T0PR 0xE000400C | T0 Prescale Register: Configura o divisor da frequência de incremento do timer. O registrador T0PC é incrementado até ficar igual ao T0PR. Neste momento a contagem T0TC é incrementada e T0PC é zerado. Portanto configura-se este registrador com o divisor de freqência menos um.
T0MCR 0xE0004014 | T0 Match Control Register. Registrador configuração dos registradores de comparação Match Registers. Os bits deste registrador selecionam o que fazer quando o valor da contagem se iguala ao Match Register correspondente. 1 (um) habilita. 0 (zero) desabilita.
Bit | Máscara | Símbolo | Ação
--- | --- | --- | ---
0 | 0x0001 | MR0I | Gera interrupção quando `T0TC` atinge `T0MR0`
1 | 0x0002 | MR0R | Reinicializa `T0TC` quando `T0TC` atinge `T0MR0`
2 | 0x0004 | MR0S | Para `T0TC` quando `T0TC` atinge `T0MR0`
3 | 0x0008 | MR1I | Gera interrupção quando `T0TC` atinge `T0MR1`
4 | 0x0010 | MR1R | Reinicializa `T0TC` quando `T0TC` atinge `T0MR1`
5 | 0x0020 | MR1S | Para `T0TC` quando `T0TC` atinge `T0MR1`
6 | 0x0040 | MR2I | Gera interrupção quando `T0TC` atinge `T0MR2`
7 | 0x0080 | MR2R | Reinicializa `T0TC` quando `T0TC` atinge `T0MR2`
8 | 0x0100 | MR2S | Para `T0TC` quando `T0TC` atinge `T0MR2`
9 | 0x0200 | MR3I | Gera interrupção quando `T0TC` atinge `T0MR3`
10 | 0x0400 | MR3R | Reinicializa `T0TC` quando `T0TC` atinge `T0MR3`
11 | 0x0800 | MR3S | Para `T0TC` quando `T0TC` atinge `T0MR3`

Temos também os Match Registers `T0MR0, T0MR1, T0MR2 e T0MR3` que tem os valores de contagem em que devem acontecer os eventos.

No programa principal do projeto a07_T0_FIQ temos o seguinte procedimento para configurar o timer para gerar interrupções:

    T0TCR = 2; T0TCR=0;	/* Limpa e habilita T0 */
    /* Preescalonador do T0 para incrementar 32 vezes por segundo */
    T0PR = (CRYSTALFREQ / 32) - 1;
    T0TCR=1;
    /* Configura T0 para gerar int na contagem 8 e 24 e resetar em 32 */
    T0MCR=0x89;	/* Interrompe em T0MR0 e T0MR1; Reset em T0MR2 */
    T0IR=3;		/* Interrompe quando encontrar T0MR0, T0MR1 */
    /* Deve colocar 1 a menos nos T0MR porque o evento acontece no fim do ciclo */
    T0MR0 = 7;	/* Interrompe quando T0TC==8	*/
    T0MR1 = 23;	/* Interrompe quando T0TC==24	*/
    T0MR2 = 31;	/* Reinicia quando T0TC==32	*/

### Sistema de Interrupções - FIQ

Os processadores da família lpc23xx tem tres tipos distintos de interrupção: A interrupção tipo FIQ (Fast Interrupt reQuest) é um tipo de interrupção de alta prioridade, geralmente usada para apenas uma fonte de interrupção. A interrupção tipo IRQ (Interrupt ReQuest) pode ser vetorada ou não vetorada. A IRQ não vetorada pode ser usada de forma muito semelhante à FIQ, porém com menor prioridade. A IRQ vetorada permite que várias fontes de interrupção sejam automaticamente vetoradas para as respectivas rotinas de atendimento.

Neste exemplo vamos demonstrar a configuração da interrupção tipo FIQ.

O gcc usa uma extenção especial de sintaxe para declarar rotinas de atendimento de interrupção: [no arquivo vic_cpsr.h]

    /* Declaracoes especiais para rotinas de atendimento a interrupcoes*/
    void SWI_Routine(void) __attribute__ ((interrupt("SWI")));
    void FIQ_Routine(void) __attribute__ ((interrupt("FIQ")));

Estas declarações encontram-se no arquivo de cabeçalho `vic_cpsr.h`. Neste arquivo também tem rotinas que geram instruções em linha para habilitar as interrupções no registrador de status CPSR:

    /* Macros para habilitar/desabilitar IRQ/FIQ no CPSR */
    #define habilitaFIQ() asm volatile(\
    	"mrs r3, cpsr\n" \
    	"bic r3, r3, # 0x40\n" \
    	"msr cpsr, r3\n")

    #define desabilitaFIQ() asm volatile(\
    	"mrs r3, cpsr\n" \
    	"orr r3, r3, # 0x40\n" \
    	"msr cpsr, r3\n")

Usando estas intruções pode-se habilitar as interrupções tipo FIQ com o comando:

    habilitaFIQ();

Os processadores da faília LPC24xx tem 32 fontes de interrupção, classificadas de acordo com a tabela a seguir.

bit | Máscara | Módulo | Fonte de interrupção
--- | --- | --- | ---
0 | 0x00000001 | WDT | Watchdog Interrupt
1 | 0x00000004 | ARM-core | ICE DbgRX
2 | 0x00000008 | ARM-core | ICE DbgTX
4 | 0x00000010 | TIMER0 | Contagem atinge MR0, MR1
5 | 0x00000020 | TIMER1 | Contagem atinge MR0, MR1
6 | 0x00000040 | UART0 | Evento na porta serial UART0
7 | 0x00000080 | UART1 | Evento na porta serial UART1
8 | 0x00000100 | PWM1 | PWM1 Match ou Capture
9 | 0x00000200 | I2C0 | Mudança de estado no I2C0
10 | 0x00000400 | SPI0 | Mudança de estado no SSP0
11 | 0x00000800 | SPI1 | Mudança de estado no SSP1
12 | 0x00001000 | PLL | PLL Lock (Travamento do PLL)
13 | 0x00002000 | RTC | Relógio/ Despertador
14 | 0x00004000 | EINT0 | Interrupção externa 0
15 | 0x00008000 | EINT1 | Interrupção externa 1
16 | 0x00010000 | EINT2 | Interrupção externa 2
17 | 0x00020000 | EINT3/GPIO | Interrupção externa 3 e GPIO
18 | 0x00040000 | ADC0 | Fim de conversão do ADC0
19 | 0x00080000 | I2C1 | Mudança de estado no I2C1
20 | 0x00100000 | BOD | Brown Out Detect - Detector de tensão baixa
21 | 0x00200000 | Ethernet | Evento na interface ethernet
22 | 0x00400000 | USB | Evento na interface USB
23 | 0x00800000 | CAN | Evento na interface CAN
24 | 0x01000000 | SD/MMC | Evento na interface SD/MMC
25 | 0x02000000 | GPDMA | Evento no DMA
26 | 0x04000000 | TIMER2 | Contagem atinge MR0, MR1
27 | 0x08000000 | TIMER3 | Contagem atinge MR0, MR1
28 | 0x10000000 | UART2 | Evento na porta serial UART2
29 | 0x20000000 | UART3 | Evento na porta serial UART3
30 | 0x40000000 | I2C2 | Mudança de estado no I2C2
31 | 0x80000000 | I2S2 | Mudança de estado no I2S2

Os processadores da família LPC24xx tem um controlador de interrupções chamado VIC (Vectored Interrupt Controller). Este dispositivo permite administrar o sistema de interrupções fazendo coisas como vetorar as interrupções para as respectivas rotinas de atendimento, atribuir prioridades, habilitar/desabilitar e ativar interrupções por software.

Neste programa desejamos habilitar a interrupção do timer 0, número 4, com máscara 0x00000010. Queremos também configurar esta interrupção como FIQ. Isto é feito com os seguintes comandos.

    VICIntSelect |= 0x10;	/* Timer0=bit 4 como FIQ	*/
    VICIntEnable = 0x10;	/* Habilita int do Timer0	*/

Foram usados os seguintes registradores do VIC:

VICAddress 0xFFFFF000 | A rotina de atendimento tipo IRQ deve escrever 0 neste registrador para indicar ao VIC que a interrupção foi atendida.
VICIntEnable 0xFFFFF010 | Bits 0 a 31: Valor 0: Mantem o estado anterior. Valor 1: Habilita a interrupção correspondente ao bit.

VICIntClear 0xFFFFF014 | Bits 0 a 31: Valor 0: Mantem o estado anterior. Valor 1: Desabilita a interrupção correspondente ao bit.
VICIntSelect 0xFFFFF00C | Bits 0 a 31: Valor 0: Configuara como FIQ. Valor 1: Configura como IRQ.

A rotina de atendimento da FIQ acende ou apaga o LED, dependendo de qual T0MR gerou a interrupção. No final a rotina FIQ deve escrever no T0IR um valor (igual ao que foi lido no T0IR) para indicar que a interrupção foi atendida.

    /* A rotina de atendimento da FIQ
      Liga o LED quando a contagem atinge MR0
      Desliga o LED quando a contagem atinge MR1
    */
    void FIQ_Routine()
    {
    int k;
    k=T0IR;		/* T0IR identifica quem causou a int	*/
    if(k & 1) FIO4CLR = (1<<15); /* T0MR0: Liga LED 1	*/
    if(k & 2) FIO4SET = (1<<15); /* T0MR1: Desliga LED 1	*/
    T0IR = k;	/* Deve limpar a INT no T0IR */
    }

### Interrupções vetoradas tipo IRQ

No sistema de interrupção vetorada, a rotina de atendimento recebe o vetor que pode transferir a execução para uma rotina específica para aquele tipo de interrupção.

Isto é feito por uma instrção tipo:

    		ldr	PC, [PC,#-0x0120]

O programa exemplo arm06 demonstra como usar uma interrupção vetorada no do relógio RTC para escrever no display LCD. Neste programa o main.c é apenas uma rotina de teste. As rotinas de inerrupção estão no módulo lcd.c.

A habilitação é feita de forma similar à FIQ, mas pode-se ter diferentes rotinas de atendimento de acordo com a fonte de interrupção. O vetor ´ configurado usando uma escrita em VICVectAddr4 (Para a IRQ numero 4: Timer 0). O seguinte trecho de programa coloca 0 no bit 4 do VICIntSelect para selecionar como IRQ, liga o bit 4 do VICIntEnable para habilitar a interrupção no VIC e coloca o vetor no VICVectAddr4. Observe que o valor 0x10 é um número com apenas o bit 4 ligado.

    CISS = 0x85;	/* Configura o RTC para gerar 64 iterrupções por segundo */
    ILR=0;		/* Limpa o identificador de interrupções do RTC */
    desabilitaIRQ();	/* Desabilita IRQ no reg CPSR da CPU */
    VICVectAddr13 = (int)IRQ_RTC;	/* Instala o vetor 13: RTC */
    VICIntSelect &= ~0x2000;	/* Seleciona como IRQ, mantendo os outros bits*/
    VICIntEnable = 0x2000;		/* Habilita IRQ do RTC */
    habilitaIRQ();

A rotina de atendimento de interrupção tipo IRQ também deve ter um tipo de declaração especial no gcc:

    void IRQ_RTC(void) __attribute__ ((interrupt("IRQ")));

No final da rotina de atendimento o programa deve escrever 0 no VICVectAddr para indicar para o VIC que a interrupção foi atendida. Também é necessário limpar a interrupção no dispositivo que a gerou, o caso o registrador RTC_ILR do relógio. Isto é feito lendo o RTC_ILR e escrevendo nele o mesmo valor.

    void IRQ_RTC(void)
    {
    short k;
    /* Aqui vão as operações de atendimento */
    k=RTC_ILR;		/* Le o identificador de IRQ do RTC */
    RTC_ILR = k;		/* Limpa a IRQ do RTC	*/
    VICVectAddr = 0;	/* Rearma o VIC		*/
    }