quinta-feira, maio 17, 2012

Programação C no AVR: Usando o avr-gcc

Agora que já sabemos o que é o avr-gcc e o instalamos, vamos ver como usá-lo para compilar um programa C e gerar o HEX a ser gravado no microcontrolador.

Antes, alguns detalhes para o Windows

Os programas do avr-gcc são programas de linha de comando, que serão executados no Windows em uma janela DOS. Um ponto importante é os diretórios de executáveis estarem no path.O instalador do WinAvr se dispõe a fazer isto, mas ele é daqueles folgados que chega por último e quer sentar na janelinha: ele coloca os diretórios do avr-gcc no início do path. Isto causa problemas quando ocorre a colisão de nomes. Em particular o WinAvr inclui alguns utilitários do GNU (no diretório utils\bin) que tem o mesmo nome de utilitários do Windows, como find e sort.

No diretório de instalação você encontrará os arquivos path1.log e path2.log, que contém o path antes e depois da instalação. Se o novo path estiver incomodando, vá no Painel de Controle, System, Advanced, Environment Variables e recoloque o path original. Para rodar o WinAvr você precisará colocar o path novo.

Um Programa de Teste

Para podermos usar o compilador, precisamos de um programa C. O programa abaixo é mais um exemplo de como piscar um LED. Não se preocupe em entender o código, veremos mais detalhes quando formos examinar a avr-libc.
  1. // Programa para teste do avr-gcc  
  2.   
  3. #include <inttypes.h>  
  4. #include <avr/io.h>  
  5. #include <avr/interrupt.h>  
  6.   
  7. // Programa Principal  
  8. int main (void)  
  9. {  
  10.     // Pino PB0 é saída  
  11.     DDRB = _BV(PB0);  
  12.     PORTB = 0;  
  13.   
  14.     // Configura o timer 0  
  15.     TCCR0A = 0;                     // overflow a cada 256 contagens  
  16.     TCCR0B = _BV(CS01) | _BV(CS00); // Usar clkIO/64: int a cada   
  17.                                     //   64*256/12000 = 1,365 ms  
  18.     TIMSK0 = _BV(TOIE0);            // Interromper no overflow  
  19.   
  20.     // Permite interrupções  
  21.     sei ();  
  22.       
  23.     // Loop infinito  
  24.     for (;;)  
  25.         ;  
  26. }  
  27.   
  28. // Tratamento da interrupção do timer 0  
  29. #define DLY_LED 732        // piscar a cada segundo  
  30. ISR (TIMER0_OVF_vect)  
  31. {  
  32.     static unsigned int cntLED = DLY_LED;  
  33.       
  34.     if (--cntLED == 0)  
  35.     {  
  36.         cntLED = DLY_LED;  
  37.         PORTB ^= _BV(PB0);  
  38.     }  
  39. }  
Um Mundo de Opções e Uma Escolha Simples

Se você for olhar a documentação do gcc, verá que a descrição das opções na linha de comando ocupa mais de 150 páginas. Vamos começar com o mínimo:

avr-gcc -mmcu=atmega328p -o PiscaLed.obj PiscaLed.c

Este comando manda compilar e linkar PiscaLed.c, gerando o objeto PiscaLed.obj para o microcontrolador atmega328p. Para gerar o hex, usamos o avr-objcopy:

avr-objcopy -O ihex PiscaLed.obj PiscaLed.hex

Complicando um Pouco

Um par de comandos mais realistas são:

avr-gcc -g -Os -Wall -mcall-prologues -mmcu=atmega328p -Wl,-Map,PiscaLed.map -o PiscaLed.obj PiscaLed.c
avr-objcopy -R .eeprom -O ihex PiscaLed.obj PiscaLed.hex

No comando do gcc, acrescentamos:
  • inclusão de informações de debug no objeto (-g)
  • otimização para o menor tamanho (-Os)
  • liga todos os avisos (-Wall)
  • o uso de subrotinas para os prólogos das funções (-mcall-prologues) 
  • geração do mapa da memória pelo linker (-Wl,0Map,PiscaLed.map)
Mesmo num programa pequeno como o PiscaLed ocorre uma grande redução no tamanho do código com a otimização ligada.

No avr-objcopy solicitamos a não colocação dos dados para a eeprom no arquivo hex.

Em situações especiais podemos precisar das outras opções, mas isto cobre o básico.

Automatizando o Processo

Os masoquistas podem pular este item e digitar os comandos na mão. Pessoas normais irão colocar os comandos em um arquivo batch (um script se você estiver usando *nix). Já os especialistas vão fazer um makefile.

Um makefile é um arquivo com instruções para o programa make. O make é uma espécie de batch com uma inteligência focada na tarefa de gerar programas. Em linhas gerais, você define no makefile as regras para geração dos vários arquivos; através delas o make determina as dependências entre eles. Comparando as datas dos arquivos, o make descobre que arquivos precisam ser regerados e executa os comandos correspondentes. No caso de erro em um comando ele interrompe o processamento.

No nosso programa exemplo, PiscaLed.hex é gerado a partir de PiscaLed.obj rodando o avr-objcopy. PiscaLed.obj, por sua vez, é gerado a partir de PiscaLed.c rodando o avr-gcc. Quando editamos o fonte, o arquivo PiscaLed.c fica mais novo que o PiscaLed.obj. Ao rodarmos o make será gerado um novo PiscaLed.obj, que terá data mais recente que o PiscaLed.hex que por isto também será regerado.

Um makefile simples para isto é:
  1. PiscaLed.hex: PiscaLed.obj  
  2.     avr-objcopy -R .eeprom -O ihex PiscaLed.obj PiscaLed.hex  
  3.   
  4. PiscaLed.obj: PiscaLed.c  
  5.     avr-gcc -g -Os -Wall -mcall-prologues -mmcu=atmega328p \  
  6.     -Wl,-Map,PiscaLed.map -o PiscaLed.obj PiscaLed.c  
Atenção que nas linhas do comando é necessário ter um "tab" no início; espaços não servem.

O make se torna interessante à medida em que cresce o número de arquivos envolvidos e torna-se indesejado regerar todos os arquivos intermediários. O arquivo makefile pode ser incrementado com regras genéricas (por exemplo, como converter um .c em .obj), definições de textos para deixar o arquivo mais legível e facilitar alterações (pelo menos na teoria) e outros recursos. Você encontra exemplos de makefiles reais nos arquivos do arduino, sob o diretório hardware\arduino\bootloaders.

Nenhum comentário: