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

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 mon23 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 mon23 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 mon23.

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.
BitMáscaraSímboloAção
00x0001MR0I Gera interrupção quando T0TC atinge T0MR0
10x0002MR0R Reinicializa T0TC quando T0TC atinge T0MR0
20x0004MR0S Para T0TC quando T0TC atinge T0MR0
30x0008MR1I Gera interrupção quando T0TC atinge T0MR1
40x0010MR1R Reinicializa T0TC quando T0TC atinge T0MR1
50x0020MR1S Para T0TC quando T0TC atinge T0MR1
60x0040MR2I Gera interrupção quando T0TC atinge T0MR2
70x0080MR2R Reinicializa T0TC quando T0TC atinge T0MR2
80x0100MR2S Para T0TC quando T0TC atinge T0MR2
90x0200MR3I Gera interrupção quando T0TC atinge T0MR3
100x0400MR3R Reinicializa T0TC quando T0TC atinge T0MR3
110x0800MR3S 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 LPC23xx tem 32 fontes de interrupção, classificadas de acordo com a tabela a seguir.
bitMáscaraMóduloFonte de interrupção
00x00000001WDT Watchdog Interrupt
10x00000004ARM-core ICE DbgRX
20x00000008ARM-core ICE DbgTX
40x00000010TIMER0 Contagem atinge MR0, MR1
50x00000020TIMER1 Contagem atinge MR0, MR1
60x00000040UART0 Evento na porta serial UART0
70x00000080UART1 Evento na porta serial UART1
80x00000100PWM1 PWM1 Match ou Capture
90x00000200I2C0 Mudança de estado no I2C0
100x00000400SPI0 Mudança de estado no SSP0
110x00000800SPI1 Mudança de estado no SSP1
120x00001000PLL PLL Lock (Travamento do PLL)
130x00002000RTC Relógio/ Despertador
140x00004000EINT0 Interrupção externa 0
150x00008000EINT1 Interrupção externa 1
160x00010000EINT2 Interrupção externa 2
170x00020000EINT3/GPIO Interrupção externa 3 e GPIO
180x00040000ADC0 Fim de conversão do ADC0
190x00080000I2C1 Mudança de estado no I2C1
200x00100000BOD Brown Out Detect - Detector de tensão baixa
210x00200000Ethernet Evento na interface ethernet
220x00400000USB Evento na interface USB
230x00800000CAN Evento na interface CAN
240x01000000SD/MMC Evento na interface SD/MMC
250x02000000GPDMA Evento no DMA
260x04000000TIMER2 Contagem atinge MR0, MR1
270x08000000TIMER3 Contagem atinge MR0, MR1
280x10000000UART2 Evento na porta serial UART2
290x20000000UART3 Evento na porta serial UART3
300x40000000I2C2 Mudança de estado no I2C2
310x80000000I2S2 Mudança de estado no I2S2

Os processadores da família LPC23xx 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) FIO3CLR = 0x01000000; /* T0MR0: Liga LED 1	*/
if(k & 2) FIO3SET = 0x01000000; /* 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		*/
}