fenglogo
Laboratório de Processadores
Programação do LPC2378 usando o arm-elf-gcc
Parte 2: Análise dos programas
puclogo

Agora vamos ver mais atentamente os arquivos que formam o projeto. Cada projeto de software para ARM deve ter o seu próprio diretório. Neste caso temos o diretório a02_pisca_led que foi criado quando expandimos o arquivo a02_pisca_led.tgz. Vimos que neste diretório temos os seguintes arquivos:

Makefile
Arquivo de configuração do programa make, descrevendo os procedimentos necessários para automatizar a compilação do projeto.
crt.S
Procedimento de inicialização (startup) escrito em linguagem assembly do ARM7. Este procedimento contem o ponto de entrada principal do programa e as entradas das interrupções. Ele tambem incializa a memória e o sistema de clock e chama a rotina main() da parte escrita em linguagem C.
lpc2378_flash.ld
Script de configuração de linker/loader. Este arquivo descreve como o arm-elf-ld deve alocar memória no LPC2378 para os segmentos do programa.
lpc23xx.h
Arquivo de cabeçalho para #include contendo a descrição dos registradores, e portas de I/O dos periféricos do LPC2378. Nos programas atuais este arquivo está junto com o compilador no subdiretório
arch/nxp
main.c
Programa principal. Neste exemplo procurou se fazer a coisa mais simples possível que tenha resultados práticos: Fazer piscar um LED.
A seguir vai uma explicação sobre cada um destes arquivos.

Makefile

Para executar automaticamente todas as etapas da compilação do projeto abre-se um shell, entra-se o diretório do projeto e usa-se o comando:
make
A forma geral do make é:
make alvo
Onde o alvo é o nome de um arquivo ou procedimento definido como alvo no Makefile. O comando make sem parâmetros equivale ao alvo default chamado all. Neste projeto os sequintes alvos podem ser úteis:
make all Equivale a simplesmente make. Compila todos os módulos gerando o arquivo arm02.hex.
make arm02.hex Compila todos os módulos gerando o arquivo arm02.hex.
make isp Compila tudo e chama o lpc21isp para gravar o programa na Flash do microcontrolador
make tser Compila uma versão para ser executada na RAM e chama o terminal ltser que envia o programa para a RAM. O monitor mon23 deve estar instalado na Flash e sendo executado. Para executar o programa feito com o make tser usa-se o comando "P" do monitor.
make clean Apaga os arquivos intermediários gerados na compilação.

O Makefile é organizado como um conjunto de alvos (targets) que são receitas de como obter determinado tipo de arquivo. Antes dos targets temos as macros ou definições de variáveis de ambiente, que são uma forma de dar nomes para strings usadas na configuração dos targets. Linhas que começam com # são comentários;

Perto do início do Makefile temos uma definição de macro na linha:

SERIALDEV = /dev/ttyS0
Isto significa que doravante $(SERIALDEV) será substituído por /dev/ttyS0. Neste caso específico estamos definindo o nome do dispositivo a ser usado como interface serial para programar o Microcontrolador. Observe que este nome é típico da primeira porta serial no Linux. Para outras situações devemos alterar esta linha de acordo. Por exemplo:
SERIALDEV = com1 Porta serial 1 no Windows
SERIALDEV = com2 Porta serial 2 no Windows
SERIALDEV = /dev/ttyUSB0 Conversor USB-RS232 no Linux
Nesta parte inicial também temos outras macros podem requerer modificações para usar o Makefile em outros contextos:
CLOCKFREQ = 12000 Frequência em kHz do clock principal
TARGET = arm02 Nome do programa a ser gerado na compilação
MODULOS = main.o crt.o Lista dos módulos (arquivos fonte) que compõe o projeto
HEADERS = lpc23xx.h Lista dos arquivos de cabeçalho para #includes
BAUDRATE = 19200 Velocidade da porta serial.

A parte final do Makefile define os comandos para obter os targets. A sintaxe é algo assim:

nome_do_alvo: dependencias
--tab-->comando
O nome_do_alvo especifica o arquivo a ser gerado. As dependencias são os arquivos necessários como pré requisitos. Se as dependências não forem satisfeitas, o make irá procurar resolve-las executando outros alvos. Antes do comando deve haver um caractere de tabulação. Observe que, para editar o Makefile, não se deve usar editores de texto que substituem tabulação por espaços (como, por exemplo, o notepad do Windows).

O Makefile também pode ter alvos genéricos, convertendo um tipo de arquivo em outro. Por exemplo, o seguinte comando compila programas *.c em linguagem C, gerando o arquivo objeto *.o corresponednte.

#Compila os modulos em linguagem C
%.o: %.c
	$(CC) -c $(CFLAGS) -o $@ $<
Esta diretiva é usada para compilar o programa principal main.c, gerando o arquivo objeto main.o.
A macro $(CC) é expandida como arm-elf-gcc; a macro $(CFLAGS) é substituida por
-Wall -O2 -mcpu=arm7tdmi-s.
O nome especial $< é substituído pelo nome do arquivo de entrada main.c e a palavra especial $@ é o nome do arquivo de saída main.o. Assim o make vai gerar a seguinte linha de comando:
arm-elf-gcc -c -Wall -O2 -mcpu=arm7tdmi-s -o main.o main.c

crt.S

O arquivo crt.S é o programa de partida, em linguagem assembly, que contém o ponto de entrada inicial do reset e os pontos de entrada das rotinas de interrupções. Este programa inicializa as áreas de memória e de pilha e então chama a rotina main() definida na parte em linguagem C. Por enquanto não é necessário saber detalhes do funcionamento deste módulo.

lpc2378_flash.ld

Este é o arquivo de configuração do linker ld, que descreve como o linker deve alocar memória para os diversos segmentos do programa.

O arquivo lpc2378_ram.ld é uma variante deste arquivo de configuração do linker que serve para gerar um programa para ser executado em memória RAM a pratir do endereço 0x40000000. Para usar este modelo de memória o kit deve ter o programa mon23 intalado na flash. Usa-se o comando
make tser
para compilar o programa para rodar na RAM e envia-lo usando o terminal ltser. Para executar o programa usa-se então o comando "P" do monitor mon23.

O modelo de memória mais comunmente usado pelo gcc usa os seguintes segmentos:
.textUsado para armazenar as instuções do programa
.dataUsado para armazenar dados variáveis inicializados: Variáveis locais e variáveis estáticas.
.bssUsado para dados não inicializados
.stackPilha do microprocessador e variáveis locais
Estes segmentos devem ser mapeados para a memória do LPC2378.

Mapa de memória do LPC2378

No mapa de memória alguns endereços são típicos, mas podem variar de acordo com o tamanho do programa aplicativo.
Flash
0x00000000
0x0007FFFF
(512K)
0x00000000
0x0000003F
Vetores de Interrupção (64 Bytes)
0x00000040 Rotinas de partida crt.S
0x00000400 Programas em Linguagem C main()
0x00001000 Valores iniciais do segmento .data
0x00001100
0x0007FFFF
Memória Flash livre
0x00080000
0x3FFF7FFF
 Livre (não definido)
0x3FFF8000
0x3FFFBFFF
 Registradores de configuração
0x3FFFC000
0x3FFFFFFF
  Controle das portas FAST GPIO
RAM
0x40000000
0x40007FFF
(32K)
0x40000000
0x4000003F
Vetores de interrupção
(remapeados em RAM)
0x40000040
0x4000011F
Livre
0x40000120
0x400001FF
Usado pelo programador ISP
0x40000200
0x4000020F
.data
0x40000210
0x4000023F
.bss
0x40000240
0x40007EC7
Livre
(pilha)
0x40007ECC Pilha SVC
0x40007ED0 Pilha IRQ
0x40007ED4 Pilha FIQ
0x40007ED8 Pilha ABT
0x40007EDC Pilha UDF
0x40007EE0 Variáveis do ISP
0x40008000
0x7FCFFFFF
 Reservado
0x7FD00000
0x7FD01FFF
 USB RAM (16k)
0x7FD02000
0x7FDFFFF
 Reservado
0x7FE00000
0x7FE01FFF
 Ethernet RAM (16k)
0x7FE02000
0xDFFFFFFF
 Livre (não definido)
0xE0084000
0xE00847FF
 Battery RAM: 2kBytes mantidos pela bateria do RTC.
0xE0000000
0xFFFFFFFF
 Periféricos mapeados em memória.

O firmware do gravador da memória flash (ISP boot loader) usa certas áreas de memória. Os programas aplicativos devem evitar usar estas áreas. O arquivo lpc2378_flash.ld é vinculado ao comando que chama o loader com a opção -T como no comando:

ld -Tlpc2378_flash.ld -o arm02.elf main.o crt.o

main.c

O programa main.c contém o programa principal da parte em linguagem C. Este módulo pode ser modificado para alterar a funcionalidade do programa. Como exemplo inicial vamos fazer uma operação muito simples: Fazer piscar um LED. Para isto é necessário aprender como usar as portas de E/S do LPC2378, chamadas de GPIO. O acesso aos dispositivos periféricos do LPC2378 é mapeado em memória. O arquivo de cabeçalho lpc23xx.h, incluído no início do main.c, contém diretivas para dar nomes aos endereços usados para acessar periféricos de I/O. O LPC2378 tem 4 portas de E/S, com 32 bits cada. Vários pinos também tem outras funções, mas todos eles podem ser configurados para serem portas genéricas de E/S (GPIO).

No kit de 2009-1 os LEDs e os botões estão conectados à porta P4:
PinoPortaDispositivoMáscara hex
52P4_0LED10x00000001
55P4_1LED20x00000002
58P4_2LED30x00000004
68P4_3LED40x00000008
72P4_4LED50x00000010
74P4_5LED60x00000020
78P4_6LED70x00000040
84P4_7LED80x00000080
86P4_8Botão SW00x00000100
91P4_9Botão SW10x00000200
94P4_10Botão SW20x00000400
101P4_11Botão SW30x00000800
104P4_12Botão SW40x00001000
127P4_24Sinal E do display LCD0x01000000
124P4_25Sinal RS do display LCD0x02000000
130P4_30PS2_CP Sinal de clock do conector PS/20x40000000
134P4_31PS2_DATA Sinal de dados do conector PS/20x80000000
 P3_0 a P3_7Dados D0 a D7 do LCD0x000000ff

Na versão 2008/2 do kit LPC2378 da PUCRS temos os seguintes componentes ligados a portas de I/O:
PinoPortaDispositivoMáscara hex
45P3_23LED10x00800000
40P3_24LED20x01000000
39P3_25LED30x02000000
38P3_26LED40x04000000
72P2_6LED50x00000040
74P2_9LED60x00000200
118P4.28LED70x10000000
122P4.29LED80x20000000
47P1_19Botão SW10x00080000
51P1_22Botão SW20x00400000
56P1_25Botão SW30x02000000
57P1_26Botão SW40x04000000
127P4_24Sinal E do display LCD0x01000000
124P4_25Sinal RS do display LCD0x02000000
 P3_0 a P3_7Dados D0 a D7 do LCD0x000000ff

Registradores do GPIO

Usam-se os seguites registradores para configurar e usar as portas GPIO
FIO4DIR
0x3FFFC080
Escreve-se neste registrador um padrão de bits que indica se os pinos são de entrada (bit=0) ou saída (bit=1).
FIO4PIN
0x3FFFC094
O valor escrito neste registrador será escrito nos bits da porta P4. Este registrador pode ser lido para obter o estado dos bits de entrada da porta P4
FIO4SET
0x3FFFC098
Escreve-se neste registrador para ligar bits da porta P4. Escevendo 1 faz ligar o bit correspondente da porta. Onde o bit for zero, a porta permanecerá com o valor inalterado. Isto equivale a fazer uma operação OU do padrão de bits escrito com o valor anterior da porta.
FIO4CLR
0x3FFFC09C
Escreve-se neste registrador para desligar bits da porta P4. Escevendo 1 faz desligar o bit correspondente da porta. Onde o bit for zero, a porta permanecerá com o valor inalterado. Isto equivale a fazer uma operação E do complemento do padrão de bits escrito com o valor anterior da porta.

Descrição do programa

Para acessar os pinos como GPIO é necessário inicialmente fazer algumas configurações. Um conjunto de registradores chamado PINSEL0 a PINSEL8, com 2 bits para cada pino, seleciona a função que o pino vai ter. O valor inicial destes registradores PINSEL é geralmente 0, selecionando a função GPIO para os pinos.

Para configurar os pinos dos LEDs como saídas usam-se as configurações

FIO4DIR = (1<<2) | (1<<3); /* LED3 (P4.2) e LED4 (P4.3) como saida */
O Registrador de 32 bits FIO4DIR determina se os bits da porta P3 são entradas ou saídas. Configura-se escrevendo neste registrador um pardrão de bits onde 0 significa entrada e um significa saída. No caso, o valor (1<<2) tem apenas o bit 2 ligado (valor numérico = 4).

Para ligar bits da porta P4 escreve-se no FIO4SET um padão de bits onde 1 faz ligar o bit e 0 deixa o bit como está. Para desligar um bit escreve-se no FIO4CLR um padrão onde 1 (um) faz desligar o bit e 0 (zero) deixa como está. Para controlar o LED4 ligado no P4.3 usa-se o valor 0x00000008 ou simplesmente 8. (Observe que 8 corresponde ao valor binário 1000 bin: com 1 no bit 3)

FIO4SET= 8;	/* Liga bit 3 da porta P4 (Desliga LED4) */
FIO4CLR= 8;	/* Desliga bit 3 da porta P4 (liga LED4) */

As portas P0 e P1 podem ser controladas por dois métodos diferentes selecionados por um registrador de configuração: o SCS. O LPC2378 inicia em um modo legado, compatível com processadores antigos da família LPC20xx, que usa comandos tipo IOPIN0, IODIR0, IOSET0 e IOCLR0 para controlar as portas P0 e P1.

Ligando bit 0 do registrador SCS seleciona-se o método "fast" para as portas GPIO P0 e P1. Neste modo usam-se comandos tipo FIO0PIN, FIO0DIR, FIO0SET ou FIO0CLR para controlar os pinos das portas P0 ou P1. Nas portas P2, P3 e P4 existe apenas o método "fast".

Para ler o estado dos botões ligados na porta P4 usa-se o registrador FIO4PIN. Este registrador da acesso a todos os 32 bits da porta P1. Para testar um bit individual da porta P4 pode-se usar uma operação e bit a bit, como no trecho de código a seguir:

/* Se sw1 (P4.9) for apertado, liga LED3 */
if(!(FIO4PIN & (1<<9))) FIO4CLR = 4;
/* Se sw2 (P4.10) for apertado, desliga LED3 */
if(!(FIO4PIN & (1<<10))) FIO4SET = 4;
O loop principal deste programa faz piscar um LED e controla o outro usando os botões.

Depois da rotina main() tem a seguinte série de funções que por enquanto não fazem nada:

/* Estas rotinas são chamados pelo crt.S. 
Devem existir, mas ainda não estão sendo usadas */
void UNDEF_Routine(){}
void SWI_Routine(){}
void timer_routine(){}

Estas funções são chamadas pela rotina de partida crt.S quando ocorrem interrupções. Veremos para que elas servem quando estudarmos o sistema de interrupções.