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.
// Programa para teste do avr-gcc

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>

// Programa Principal
int main (void)
{
    // Pino PB0 é saída
    DDRB = _BV(PB0);
    PORTB = 0;

    // Configura o timer 0
    TCCR0A = 0;                     // overflow a cada 256 contagens
    TCCR0B = _BV(CS01) | _BV(CS00); // Usar clkIO/64: int a cada 
                                    //   64*256/12000 = 1,365 ms
    TIMSK0 = _BV(TOIE0);            // Interromper no overflow

    // Permite interrupções
    sei ();
    
    // Loop infinito
    for (;;)
        ;
}

// Tratamento da interrupção do timer 0
#define DLY_LED 732        // piscar a cada segundo
ISR (TIMER0_OVF_vect)
{
    static unsigned int cntLED = DLY_LED;
    
    if (--cntLED == 0)
    {
        cntLED = DLY_LED;
        PORTB ^= _BV(PB0);
    }
}
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 é:
PiscaLed.hex: PiscaLed.obj
    avr-objcopy -R .eeprom -O ihex PiscaLed.obj PiscaLed.hex

PiscaLed.obj: PiscaLed.c
    avr-gcc -g -Os -Wall -mcall-prologues -mmcu=atmega328p \
    -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: