fenglogo
Laboratório de Processadores
Programação do LPC2478 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_piscaled que foi criado quando expandimos o arquivo a02_piscaled.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.
lpc2478_flash.ld
Script de configuração de linker/loader. Este arquivo descreve como o arm-elf-ld deve alocar memória no LPC2478 para os segmentos do programa.
lpc24xx.h
Arquivo de cabeçalho para #include contendo a descrição dos registradores, e portas de I/O dos periféricos do LPC2478. 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 ispu Compila tudo e chama o lpc21isp para gravar o programa na Flash do microcontrolador [usa a interface /dev/ttyUSB0]
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 mon24 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.

lpc2478_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 lpc2478_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 mon24 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 mon24.

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

Mapa de memória do LPC2478

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
0x4000FFFF
(64K)
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
0x4000FEC7
Livre
(pilha)
0x4000FECC Pilha SVC
0x4000FED0 Pilha IRQ
0x4000FED4 Pilha FIQ
0x4000FED8 Pilha ABT
0x4000FEDC Pilha UDF
0x4000FEE0 Variáveis do ISP
0x40010000
0x7FCFFFFF
 Reservado
0x7FD00000
0x7FD01FFF
 USB RAM (16k)
0x7FD02000
0x7FDFFFF
 Reservado
0x7FE00000
0x7FE01FFF
 Ethernet RAM (16k)
0x7FE02000
0xDFFFFFFF
 Livre (não definido)
0xA000000
0xA3FFFFFF
 SDRAM externa (64 MBytes)
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 lpc2478_flash.ld é vinculado ao comando que chama o loader com a opção -T como no comando:

ld -Tlpc2478_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 LPC2478, chamadas de GPIO. O acesso aos dispositivos periféricos do LPC2478 é 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 LPC2478 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).

Os LEDs e os botões estão conectados à porta P4:
PortaDispositivoMáscara hex
P4_15LED10x00008000
P4_16LED20x00010000
P4_17LED30x00020000
P4_18LED40x00040000
P4_19LED50x00080000
P4_20LED60x00010000
P4_21LED70x00020000
P4_22LED80x00040000
P4_26Botão SW00x04000000
P4_27Botão SW10x08000000
P4_30Botão SW20x40000000
P4_31Botão SW30x80000000
P0_17PS2_CP Sinal de clock do conector PS/20x00020000
P0_18PS2_DATA Sinal de dados do conector PS/20x00040000
P1_30Sinal RS do display LCD0x40000000
P1_31Sinal E do display LCD0x80000000
P4_23Sinal R/W do display LCD0x00800000
P2_0 a P2_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<<16) | (1<<17); /* LED2 (P4.16) e LED3 (P4.17) 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<<16) tem apenas o bit 16 ligado (valor numérico = 0x10000).

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 LED2 ligado no P4.17 usa-se o valor 0x00020000 ou (1<<17).

FIO4SET= (1<<17);	/* Liga bit 17 da porta P4 (Desliga LED2) */
FIO4CLR= (1<<17);	/* Desliga bit 17 da porta P4 (liga LED2) */

As portas P0 e P1 podem ser controladas por dois métodos diferentes selecionados por um registrador de configuração: o SCS. O LPC2478 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.26) for apertado, liga LED1 */
if(!(FIO4PIN & (1<<26))) FIO4CLR = (1<<15);
/* Se sw2 (P4.27) for apertado, desliga LED1 */
if(!(FIO4PIN & (1<<27))) FIO4SET = (1<<15);

Os bits da porta P4 podem também ser acessados como 4 grupos de 8 bits:
FIO4PIN0 bits 0 a 7
FIO4PIN1 bits 8 a 15
FIO4PIN2 bits 16 a 23
FIO4PIN3 bits 24 a 31

O loop principal deste programa faz piscar sequencialmente os LEDs.

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.