Mbed – Desenvolvendo em Cortex-M3 com o GCC

- por Sergio Prado

Categorias: Linux, Mbed Tags: , , , ,

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 cri­ada pelos engen­heiros da própria ARM para diminuir a curva de apren­diza­gem em pro­je­tos com ARM. O kit é baseado no NXP LPC1768, um ARM Cortex-M3 com 512K de memória Flash, 32K de RAM e diver­sas inter­faces como CAN, SPI, I2C e Eth­er­net.

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

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.