Mbed – Desenvolvendo em Cortex-M3 com o GCC
- por Sergio Prado
O uso de microntroladores ARM da linha Cortex-M3 tem crescido bastante nos últimos tempos. Ele preenche bem um espaço que fica entre as aplicações mais simples, que podem se desenvolvidas em 8/16 bits, e as aplicações mais complexas que exigem um sistema operacional completo.
E você não precisa necessariamente de uma ferramenta paga como a da IAR ou a da Keil para trabalhar com um Cortex-M3. Uma das grandes vantagens da arquitetura ARM é o enorme ecossistema de ferramentas e soluções de código aberto existentes.
Neste artigo iremos aprender a configurar um ambiente de desenvolvimento em Linux com o GCC e utilizá-lo para desenvolver uma aplicação para o LPC1768, um Cortex-M3 da NXP.
NOSSO AMBIENTE
O ambiente de desenvolvimento será uma máquina Linux com a distribuição Ubuntu 12.04.
Para os testes, utilizaremos o kit de desenvolvimento mbed, uma plataforma criada pelos engenheiros da própria ARM para diminuir a curva de aprendizagem em projetos com ARM. O kit é baseado no NXP LPC1768, um ARM Cortex-M3 com 512K de memória Flash, 32K de RAM e diversas interfaces como CAN, SPI, I2C e Ethernet.
O legal deste kit é a facilidade de gravar o firmware. Não é necessário nenhum gravador especial ou interface JTAG. Quando você pluga ele na USB, ele se transforma em um pendrive. Basta então copiar o firmware (arquivo .bin) para lá!
Se você quiser ler mais sobre o mbed, escrevi um tempo atrás o artigo “Projetando na nuvem com o kit mbed“.
PREPARANDO O AMBIENTE
Crie um diretório no seu HOME para a realização dos trabalhos:
$ mkdir -p ~/workspace |
Precisamos de um toolchain para gerar código bare-metal para o Cortex-M3. Se você não sabe o que é um toolchain, leia o artigo “Desmistificando toolchains em Linux embarcado“.
Vou te dar duas opções para a configuração de um toolchain para Cortex-M3, uma “sem emoção”, e outra “com emoção”! :)
TOOLCHAIN “SEM EMOÇÃO”
Basta baixar e instalar o toolchain pronto da CodeSourcery (agora Mentor Graphics Sourcery Tools):
$ sudo mkdir -p /opt/toolchain/codesourcery/ $ sudo chown -R $USER:$USER /opt/toolchain $ cd /opt/toolchain/codesourcery/ $ wget https://sourcery.mentor.com/sgpp/lite/arm/portal/package8734/public/arm-none-eabi/arm-2011.03-42-arm-none-eabi-i686-pc-linux-gnu.tar.bz2 $ tar xjfv arm-2011.03-42-arm-none-eabi-i686-pc-linux-gnu.tar.bz2 $ rm *.tar.bz2 |
Pronto! Toolchain instalado!
Agora, para os corações mais fortes…
TOOLCHAIN “COM EMOÇÃO”
Vamos usar o crosstool-ng para gerar um toolchain para Cortex-M3 bare-metal. Para quem não esta familiarizado com o termo, “bare-metal” é basicamente um firmware que roda direto na CPU sem o suporte de um sistema operacional. E se você não conhece o crosstool-ng, ou não sabe como usá-lo, leia o artigo “Gerando e usando toolchains em Linux embarcado“.
Execute os comandos abaixo para baixar e instalar a ferramenta:
$ cd ~/workspace $ wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.15.0.tar.bz2 $ tar jxfv crosstool-ng-1.15.0.tar.bz2 && rm *.tar.bz2 $ cd crosstool-ng-1.15.0/ $ ./configure $ make $ sudo make install $ ct-ng arm-bare_newlib_cortex_m3_nommu-eabi |
É a última linha que configura o crosstool-ng para trabalhar com bare-metal em Cortex-M3. Agora abra o menu de configuração:
$ ct-ng menuconfig |
Vamos fazer algumas modificações no toolchain que será gerado:
Paths and misc options ---> (/opt/toolchain/${CT_TARGET}) Prefix directory (8) Number of parallel jobs Toolchain options ---> (arm-cortexm3-bare) Tuple's alias Debug facilities ---> [*] gdb ---> [*] Cross-gdb [*] Enable 'sim' [*] Enable python scripting |
Ajuste a opção “Number of parallel jobs” de acordo com a quantidade de núcleos da CPU da sua máquina de desenvolvimento.
Crie também o diretório onde será instalado o toolchain:
$ sudo mkdir /opt/toolchain $ sudo chown -R $USER:$USER /opt/toolchain |
Depois é só compilar com o comando abaixo:
$ ct-ng build |
Dependendo da sua máquina, o processo de compilação do toolchain deve levar de 20 à 60 minutos. Quando terminar, verifique se as ferramentas foram geradas corretamente:
$ cd /opt/toolchain/arm-bare_newlib_cortex_m3_nommu-eabi/bin/ $ ls arm-cortexm3-bare-* arm-cortexm3-bare-addr2line arm-cortexm3-bare-c++filt arm-cortexm3-bare-g++ arm-cortexm3-bare-gdb arm-cortexm3-bare-nm arm-cortexm3-bare-run arm-cortexm3-bare-ar arm-cortexm3-bare-cpp arm-cortexm3-bare-gcc arm-cortexm3-bare-gdbtui arm-cortexm3-bare-objcopy arm-cortexm3-bare-size arm-cortexm3-bare-as arm-cortexm3-bare-ct-ng.config arm-cortexm3-bare-gcc-4.4.1 arm-cortexm3-bare-gprof arm-cortexm3-bare-objdump arm-cortexm3-bare-strings arm-cortexm3-bare-c++ arm-cortexm3-bare-elf2flt arm-cortexm3-bare-gccbug arm-cortexm3-bare-ld arm-cortexm3-bare-ranlib arm-cortexm3-bare-strip arm-cortexm3-bare-cc arm-cortexm3-bare-flthdr arm-cortexm3-bare-gcov arm-cortexm3-bare-ld.real arm-cortexm3-bare-readelf |
NEWLIB
Tanto o toolchain da CodeSourcery quanto o toolchain gerado pelo crosstool-ng usam a newlib, uma biblioteca C padrão com foco em sistemas embarcados.
Criada originalmente pela Cygnus Solutions (que mais pra frente foi adquirida pela Red Hat), ela é hoje bastante usada em boa parte dos toolchains livres e comerciais baseados no GCC para sistemas embarcados.
CMSIS
O CMSIS (Cortex Microcontroller Software Interface Standard) é uma camada de software que abstrai o acesso à microcontroladores de arquitetura ARM Cortex-M. É um padrão desenvolvido pela própria ARM com o objetivo de facilitar o acesso aos periféricos, reduzir a curva de aprendizado e simplificar o reuso de código.
A idéia aqui é ter uma camada de software que permita migrar facilmente sua aplicação usando um Cortex-M para diferentes fornecedores de chip (Freescale, TI, NXP, ST, etc), desde que cada um deles tenha implementado o padrão. Na prática, são dois arquivos-fonte C e cinco arquivos de cabeçalho que padronizam o nome de funções de inicialização, manipuladores de interrupção, nomes dos registradores, etc.
Com o toolchain preparado, vamos então baixar e instalar os fontes do CMSIS para usá-los depois na nossa aplicação.
$ cd ~/workspace $ mkdir -p cmsis && cd cmsis $ wget http://ics.nxp.com/support/lpcxpresso/zip/CMSISv2p00_LPC17xx.zip $ unzip CMSISv2p00_LPC17xx.zip && rm CMSISv2p00_LPC17xx.zip |
Talvez você precise alterar as definições de clock no arquivo “system_LPC17xx.c” do CMSIS (XTAL, RTC_CLK e IRC_OSC). No meu caso, eu precisei alterar o RTC_CLK para 32768UL, já que esta é a frequência do oscilador do RTC no kit mbed.
CRIANDO O PROJETO
Vamos criar agora o nosso projeto “na unha”! O segredo aqui é preparar o Makefile para usar corretamente o cross-compiler e ter um script de linker bem configurado.
Mas vamos começar primeiro pelos fontes da aplicação.
A APLICAÇÃO
Vamos desenvolver o famoso “Hello World” de sistemas embarcados: um pisca led!
Precisamos de um código de inicialização e da função main() da aplicação, que irá efetivamente piscar o led.
O código de inicialização é bem simples. Segue um trecho abaixo (o código completo você poderá baixar no fim do artigo):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/* startup.c */ #include /* extern symbols */ extern unsigned int _end_stack; ... /* extern functions */ extern int main(void); extern void SystemInit(void); /* Cortex M3 core interrupt handlers */ void Reset_Handler(void); void NMI_Handler(void) __attribute__ ((weak, alias ("Dummy_Handler"))); ... /* LPC13xx specific interrupt handlers */ void WDT_IRQHandler(void) __attribute__ ((weak, alias ("Dummy_Handler"))); void TIMER0_IRQHandler(void) __attribute__ ((weak, alias ("Dummy_Handler"))); ... /* prototypes */ void Dummy_Handler(void); /* interrupt vector table */ void *vector_table[] __attribute__ ((section(".vectors"))) = { &_end_stack, Reset_Handler, NMI_Handler, ... }; /* startup function */ void Reset_Handler(void) { unsigned int *src, *dst; /* Copy data section from flash to RAM */ src = &_end_text; dst = &_start_data; while (dst < &_end_data) *dst++ = *src++; /* Clear the bss section */ dst = &_start_bss; while (dst < &_end) *dst++ = 0; SystemInit(); main(); } /* default interrupt handler */ void Dummy_Handler(void) { } |
O código de inicialização basicamente inicializa a tabela de vetores de interrupção (linha 26) e declara a função Reset_Handler(), que será executada no boot da CPU (linha 34). Esta função prepara as regiões de memória DATA (dados inicializados) e BSS (dados não inicializados), inicializa a placa (registradores, periféricos, etc) e chama a função main().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/* main.c */ ... int main (void) { /* set system tick for 1ms interrupt */ SystemCoreClockUpdate(); if (SysTick_Config(SystemCoreClock / 1000)) { while (1); } /* set led GPIO port */ LPC_GPIO1->FIODIR = (1<<LED_GPIO); /* blink led */ while(1) { LPC_GPIO1->FIOSET = (1<<LED_GPIO); Delay (300); LPC_GPIO1->FIOCLR = (1<<LED_GPIO); Delay (300); } } |
A função main() inicializa o clock, configura o SysTick para gerar interrupção a cada 1 milisegundo, e entra em um loop para piscar o led.
O MAKEFILE
Já o Makefile é bem mais extenso, mas nada muito complexo. Segue um trecho dele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# project name PRJNAME = blinkled # toolchain configuration PATH := ${PATH}:/opt/toolchain/arm-bare_newlib_cortex_m3_nommu-eabi/bin/ TOOLCHAIN_PREFIX = arm-cortexm3-bare- # cpu configuration THUMB = -mthumb MCU = cortex-m3 ... # C source files C_SRC := $(CMSISSRCDIR)/core_cm3.c $(CMSISSRCDIR)/system_LPC17xx.c $(SRCDIR)/main.c $(SRCDIR)/startup.c # C flags CFLAGS += -mcpu=$(MCU) ... # Linker flags LDFLAGS +=-T$(PRJDIR)/lpc1768.ld ... # Define programs and commands. CC = $(TOOLCHAIN_PREFIX)gcc OBJCOPY = $(TOOLCHAIN_PREFIX)objcopy OBJDUMP = $(TOOLCHAIN_PREFIX)objdump NM = $(TOOLCHAIN_PREFIX)nm ... all: createdirs build createdirs: @mkdir -p $(OUTDIR) @mkdir -p $(OUTDIR)/dep # targets for the build-sequence build: elf lss sym hex bin ... # Link: create ELF output file from object files. .SECONDARY : $(PRJNAME).elf .PRECIOUS : $(ALL_OBJ) %.elf: $(ALL_OBJ) $(BUILDONCHANGE) $(CC) $(THUMB) $(CFLAGS) $(ALL_OBJ) --output $@ -nostartfiles $(LDFLAGS) ... # compile C source code define COMPILE_C_TEMPLATE $(OUTDIR)/$(notdir $(basename $(1))).o : $(1) $(BUILDONCHANGE) $(CC) -c $(THUMB) $$(CFLAGS) $$< -o $$@ endef $(foreach src, $(C_SRC), $(eval $(call COMPILE_C_TEMPLATE, $(src)))) ... |
Configure o PATH e o TOOLCHAIN_PREFIX de acordo com o seu toolchain. Perceba que para compilar são passadas para o GCC as opções -mcpu=cortex-m3 e -mthumb. Desta forma o cross-compiler sabe que queremos gerar código para um cortex-m3. No linker, indicamos que não queremos usar arquivos de inicialização com a opção -nostartfiles (linha 48), e passamos o script de linker lpc1768.ld (linha 22).
LINKER SCRIPT
O linker script indica como o linker irá organizar as diversas seções de código e dados do programa (TEXT, DATA, BSS, etc) na memória do microcontrolador. Segue a listagem completa do script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
/* lpc1313.dld */ ENTRY(Reset_Handler) MEMORY { sram : ORIGIN = 0x10000000, LENGTH = 32k flash : ORIGIN = 0x00000000, LENGTH = 512k } _end_stack = 0x10007FF0; SECTIONS { . = ORIGIN(flash); vectors : { *(.vectors) } >flash .text : { *(.text) *(.rodata) *(.rodata*) *(.ARM.extab* .gnu.linkonce.armextab.*) *(.eh_frame_hdr) *(.eh_frame) } >flash __exidx_start = .; .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >flash __exidx_end = .; _end_text = .; .data : { _start_data = .; *(.data) _end_data = .; } >sram AT >flash .bss : { _start_bss = .; *(.bss) _end_bss = .; } >sram . = ALIGN(4); _start_stack = .; } _end = .; PROVIDE(end = .); |
Agora é só compilar!
$ make arm-cortexm3-bare-gcc -c -mthumb -mcpu=cortex-m3 -Wall -Wextra -Werror -Wcast-align -Wpointer-arith -Wredundant-decls -Wcast-qual -Wa,-adhlns=/home/sprado/workspace/dev/mbed/projects/blinkled/out/core_cm3.lst -MMD -MP -MF /home/sprado/workspace/dev/mbed/projects/blinkled/out/dep/core_cm3.o.d -I. -I/home/sprado/workspace/dev/mbed/projects/blinkled/inc -I/home/sprado/workspace/dev/mbed/projects/blinkled/../../cmsis//inc/ /home/sprado/workspace/dev/mbed/projects/blinkled/../../cmsis//src//core_cm3.c -o /home/sprado/workspace/dev/mbed/projects/blinkled/out/core_cm3.o arm-cortexm3-bare-gcc -c -mthumb -mcpu=cortex-m3 -Wall -Wextra -Werror -Wcast-align -Wpointer-arith -Wredundant-decls -Wcast-qual -Wa,-adhlns=/home/sprado/workspace/dev/mbed/projects/blinkled/out/system_LPC17xx.lst -MMD -MP -MF /home/sprado/workspace/dev/mbed/projects/blinkled/out/dep/system_LPC17xx.o.d -I. -I/home/sprado/workspace/dev/mbed/projects/blinkled/inc -I/home/sprado/workspace/dev/mbed/projects/blinkled/../../cmsis//inc/ /home/sprado/workspace/dev/mbed/projects/blinkled/../../cmsis//src//system_LPC17xx.c -o /home/sprado/workspace/dev/mbed/projects/blinkled/out/system_LPC17xx.o arm-cortexm3-bare-gcc -c -mthumb -mcpu=cortex-m3 -Wall -Wextra -Werror -Wcast-align -Wpointer-arith -Wredundant-decls -Wcast-qual -Wa,-adhlns=/home/sprado/workspace/dev/mbed/projects/blinkled/out/main.lst -MMD -MP -MF /home/sprado/workspace/dev/mbed/projects/blinkled/out/dep/main.o.d -I. -I/home/sprado/workspace/dev/mbed/projects/blinkled/inc -I/home/sprado/workspace/dev/mbed/projects/blinkled/../../cmsis//inc/ /home/sprado/workspace/dev/mbed/projects/blinkled/src/main.c -o /home/sprado/workspace/dev/mbed/projects/blinkled/out/main.o arm-cortexm3-bare-gcc -c -mthumb -mcpu=cortex-m3 -Wall -Wextra -Werror -Wcast-align -Wpointer-arith -Wredundant-decls -Wcast-qual -Wa,-adhlns=/home/sprado/workspace/dev/mbed/projects/blinkled/out/startup.lst -MMD -MP -MF /home/sprado/workspace/dev/mbed/projects/blinkled/out/dep/startup.o.d -I. -I/home/sprado/workspace/dev/mbed/projects/blinkled/inc -I/home/sprado/workspace/dev/mbed/projects/blinkled/../../cmsis//inc/ /home/sprado/workspace/dev/mbed/projects/blinkled/src/startup.c -o /home/sprado/workspace/dev/mbed/projects/blinkled/out/startup.o arm-cortexm3-bare-gcc -mthumb -mcpu=cortex-m3 -Wall -Wextra -Werror -Wcast-align -Wpointer-arith -Wredundant-decls -Wcast-qual -Wa,-adhlns=/home/sprado/workspace/dev/mbed/projects/blinkled/out/core_cm3.lst -MMD -MP -MF /home/sprado/workspace/dev/mbed/projects/blinkled/out/dep/blinkled.elf.d -I. -I/home/sprado/workspace/dev/mbed/projects/blinkled/inc -I/home/sprado/workspace/dev/mbed/projects/blinkled/../../cmsis//inc/ /home/sprado/workspace/dev/mbed/projects/blinkled/out/core_cm3.o /home/sprado/workspace/dev/mbed/projects/blinkled/out/system_LPC17xx.o /home/sprado/workspace/dev/mbed/projects/blinkled/out/main.o /home/sprado/workspace/dev/mbed/projects/blinkled/out/startup.o --output /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.elf -nostartfiles -Wl,-Map=/home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.map,--cref -L. -L/home/sprado/workspace/dev/mbed/projects/blinkled/lib -lc -lgcc -T/home/sprado/workspace/dev/mbed/projects/blinkled/lpc1768.ld arm-cortexm3-bare-objdump -h -S -C -r /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.elf > /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.lss arm-cortexm3-bare-nm -n /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.elf > /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.sym arm-cortexm3-bare-objcopy -O ihex /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.elf /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.hex arm-cortexm3-bare-objcopy -O binary /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.elf /home/sprado/workspace/dev/mbed/projects/blinkled/out/blinkled.bin |
Será gerado o arquivo blinkled.bin que você pode copiar e testar no kit de desenvolvimento mbed.
CÓDIGO-FONTE
O código-fonte completo da aplicação esta disponível no github.
TRABALHANDO COM OUTROS CORTEX-M3
Os testes aqui foram feitos com o mbed, que usa o LPC1768 da NXP, mas o processo é o mesmo em qualquer outro chip, você só vai precisar substituir o linker script, ajustar o arquivo de inicialização e reescrever o main.c. Normalmente, o arquivo de inicialização e o script de linker são fornecidos nas aplicações de exemplo disponibilizadas pelo fabricante.
Próximo passo? Colocar o FreeRTOS para rodar no kit!
Até lá!
Um abraço,
Sergio Prado