Mbed – Integrando o FreeRTOS em um Cortex-M3
- por Sergio Prado
Aqui estamos nós novamente, lutando por um mundo (open-source) melhor… :)
No último artigo mostrei à vocês que conseguimos ter um ambiente de compilação decente para microcontroladores Cortex-M3 usando apenas ferramentas de código aberto.
Neste artigo vamos dar continuidade ao trabalho, e embarcar um sistema operacional de tempo real no nosso querido kit mbed. É claro que para esta tarefa escolhi o RTOS de código aberto mais usado no mundo, o FreeRTOS!
Mas antes, vamos aos porquês…
POR QUE UM RTOS?
Existem diversas vantagens no uso de um sistema operacional de tempo real. A vantagem mais clara é a possibilidade de priorizar tarefas para garantir que as restrições de tempo real da aplicação sejam atendidas. Mas existem outras vantagens muito menos percebidas pelo desenvolvedor de aplicações embarcadas.
Uma delas é que o RTOS facilita o desenvolvimento de aplicações orientadas à eventos, evitando por exemplo o uso excessivo de rotinas de pooling. Como consequência, você consome menos ciclos de CPU, melhorando a eficiência do sistema e diminuindo o consumo de energia.
Outra vantagem também pouco percebida é a abstração fornecida pelo uso de um sistema operacional. Sua aplicação fica muito melhor estruturada, o que facilita manter o código, principalmente se a equipe do projeto é grande. A mesma abstração vai facilitar seu trabalho se precisar portar o código para uma outra plataforma de hardware por exemplo.
À propósito, escrevi dois artigos sobre o tema um tempo atrás. O primeiro artigo apresenta uma introdução sobre o funcionamento de um RTOS e o segundo exemplifica os conceitos através da implementação de uma aplicação.
Enfim, o ministério da saúde adverte:
“Usar um RTOS é viciante, e depois de um tempo você vai querer usar em todos os seus projetos!”
E POR QUE O FREERTOS?
Primeiro porque é o RTOS de código aberto mais usado no mundo. É simples, muito bem escrito e documentado, e suporta mais de 30 arquiteturas diferentes.
E segundo porque o FreeRTOS é livre de royalties e pode ser usado em soluções comerciais. A não ser que você precise mexer no próprio kernel do FreeRTOS, poderá manter sua aplicação proprietária (mais detalhes sobre a licença do FreeRTOS aqui).
É lógico que existem outras opções muito boas por aí, como uC/OS-III da Micrium, VxWorks da WindRiver/Intel, Nucleos da Mentor Graphics, ThreadX da ExpressLogic, etc. Mas dependendo do projeto, os custos de uma solução comercial podem ser proibitivos. É aí que entra o FreeRTOS.
Vamos então por a mão na massa?
NOSSO AMBIENTE
Para os testes, utilizaremos novamente o kit de desenvolvimento mbed.
No artigo “Mbed — Desenvolvendo em Cortex-M3 com o GCC” eu descrevo como preparar um ambiente de desenvolvimento em Linux com o GCC e utilizá-lo para desenvolver uma aplicação para o LPC1768.
Usaremos como ponto de partida a aplicação desenvolvida neste artigo para piscar um led, e iremos alterá-la, integrando os fontes do FreeRTOS e alterando a aplicação para piscar um led em alto nível!
ESTUDANDO OS FONTES DO FREERTOS
O primeiro passo é baixar os fontes do FreeRTOS, que estão hospedados lá no SourceForge. Descompacte-o na sua máquina e liste o diretório principal:
freertos/ ├── Demo/ ├── License/ ├── readme.txt └── Source/ |
Ele possui três diretórios principais. Dentro do diretório “Licence” esta o arquivo que descreve a licença do FreeRTOS. O diretório “Demo” possui aplicações de demonstração para uma infinidade de plataformas diferentes (111 demos na versão 7.1.0 do FreeRTOS). É nesta pasta que você deve dar uma olhada se quiser verificar a disponibilidade de uma aplicação de exemplo para a sua plataforma. E se não existir um aplicação de exemplo, você pode usar a mais próxima como ponto de partida.
Já o diretório “Source” contém os fontes do kernel:
Source/ ├── croutine.c ├── include/ ├── list.c ├── portable/ ├── queue.c ├── readme.txt ├── tasks.c └── timers.c |
Os arquivos “.c” desta pasta são os fontes portáveis (que rodam em qualquer plataforma) do FreeRTOS. Três deles são essenciais, e você deverá incluí-los em seu projeto:
- list.c: implementa uma biblioteca com rotinas de tratamento de lista ligada, usadas por exemplo para manter a fila de queues ou de tarefas em execução.
- queue.c: contém toda a implementação dos mecanismos de sincronização (queues, semáforos, etc).
- tasks.c: possui todas as rotinas de gerenciamento das tarefas em execução, incluindo o escalonador de tarefas.
Além disso, dois arquivos são opcionais, e você deverá incluir em seu projeto apenas se for utilizar suas funcionalidades:
- croutine.c: implementa a funcionalidade de co-routines, uma espécie de tarefa muito mais simples, projetada para usar menos memória RAM, mas que possui algumas limitações.
- timers.c: implementa rotinas de software timer, possibilitando configurar uma função para ser executada em determinado momento no futuro.
O diretório “include” contém todos os arquivos de cabeçalho do FreeRTOS, e você deve também incluir o caminho deste diretório nas diretivas de compilação no Makefile.:
include/ ├── croutine.h ├── FreeRTOS.h ├── list.h ├── mpu_wrappers.h ├── portable.h ├── projdefs.h ├── queue.h ├── semphr.h ├── StackMacros.h ├── task.h └── timers.h |
Já o diretório “portable” contém todos os portes implementados do FreeRTOS.
portable/ ├── BCC ├── CCS4 ├── CodeWarrior ├── GCC │ ├── ARM_CM3 │ ├──── port.c │ └──── portmacro.h │ .... ├── IAR ├── Keil ├── MemMang ├── MPLAB ├── MSVC-MingW ├── oWatcom ├── Paradigm ├── readme.txt ├── Renesas ├── Rowley ├── RVDS ├── SDCC ├── Softune └── WizC |
O porte do FreeRTOS esta organizado por compilador. Dentro de cada pasta estão todas as arquiteturas suportadas pelo correspondente compilador. No nosso caso, o compilador é o GCC e a arquitetura é Cortex-M3, então o caminho é “portable/GCC/ARM_CM3“.
Todo porte deve fornecer pelo menos os arquivos “port.c” e “portmacro.h“, contendo a implementação do porte do FreeRTOS para determinada plataforma. Você deve incluir estes arquivos no seu Makefile também.
Por último, todo projeto com o FreeRTOS deve fornecer uma implementação das funções de alocação de memória. Você pode fornecer a sua implementação, ou usar uma das três fornecidas pelo FreeRTOS, que ficam em “portable/MemMang“:
portable/MemMang/ ├── heap_1.c ├── heap_2.c └── heap_3.c |
A implementação em heap_1.c apenas aloca, mas não libera memória, e deve ser usada quando todas as tarefas são criadas estaticamente na função main(). Já a implementação em heap_2.c aloca e desaloca memória, mas não controla fragmentação. Deve ser usada em aplicações que criam e destroem tarefas dinamicamente. E o arquivo heap_3.c possui uma implementação completa das funções malloc() e do free(), incluindo controle de fragmentação, mas adiciona overhead de processamento e uso de memória.
ALTERANDO O MAKEFILE
Em resumo, você precisa incluir em seu Makefile os fontes do kernel (list.c, queue.c, tasks.c), a implementação das funções de alocação de memória (ex: heap_2.c) e os fontes do porte da sua plataforma (port.c e portmacro.h). Você deve também incluir nas diretivas de compilação o diretório de includes do FreeRTOS.
Segue um trecho do Makefile com as alterações realizadas (no fim do artigo deixei um link para você baixar a aplicação completa):
... # FreeRTOS directories FRTDIR := $(PRJDIR)/../../freertos FRTSRCDIR := $(FRTDIR)/Source FRTINCDIR := $(FRTSRCDIR)/include FRTMEMDIR := $(FRTSRCDIR)/portable/MemMang/ FRTPORDIR := $(FRTSRCDIR)/portable/GCC/ARM_CM3 # C source files C_SRC := $(CMSISSRCDIR)/core_cm3.c $(CMSISSRCDIR)/system_LPC17xx.c \ $(SRCDIR)/main.c $(SRCDIR)/startup.c $(FRTSRCDIR)/list.c \ $(FRTSRCDIR)/queue.c $(FRTSRCDIR)/tasks.c $(FRTPORDIR)/port.c \ $(FRTMEMDIR)/heap_2.c # include directories INCDIRS = $(INCDIR) $(CMSISINCDIR) $(FRTINCDIR) $(FRTPORDIR) $(SRCDIR) ... |
ARQUIVO DE CONFIGURAÇÃO
O próximo passo é incluir no seu projeto o arquivo de configuração do FreeRTOS, o FreeRTOSConfig.h.
Este arquivo de configuração define diversos parâmetros do SO, como a quantidade máxima de prioridades disponíveis, o clock da CPU e o tamanho mínimo do stack, e permite também habilitar ou desabilitar funcionalidades como mutexes, rotinas de delay, etc.
Você pode usar como ponto de partida um arquivo de configuração de dentro de uma das aplicações de demonstração, e alterá-lo de acordo com o seu projeto.
Este é um trecho do arquivo:
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 |
... #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configMAX_PRIORITIES ( ( unsigned portBASE_TYPE ) 5 ) #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( ( unsigned long ) SystemCoreClock ) #define configTICK_RATE_HZ ( ( portTickType ) 1000 ) #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 80 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 19 * 1024 ) ) #define configMAX_TASK_NAME_LEN ( 12 ) #define configUSE_TRACE_FACILITY 0 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 0 #define configUSE_CO_ROUTINES 0 #define configUSE_MUTEXES 1 #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) #define configUSE_COUNTING_SEMAPHORES 0 #define configUSE_ALTERNATIVE_API 0 #define configCHECK_FOR_STACK_OVERFLOW 0 #define configUSE_RECURSIVE_MUTEXES 1 #define configQUEUE_REGISTRY_SIZE 10 #define configGENERATE_RUN_TIME_STATS 0 ... |
ALTERANDO A TABELA DE INTERRUPÇÕES
O último passo é alterar a tabela de vetores de interrupção. O FreeRTOS usa as seguintes interrupções do Cortex-M3:
- SysTick (System Tick Timer): É uma interrupção periódica usada pelo kernel para executar tarefas e gerenciar o sistema de tempo em tempo. No FreeRTOS, é ela que força a troca de contexto, setando o registrador PENDSV do NVIC, e consequentemente habilitando a exceção PendSV.
- PendSV (Pended System Call): Esta exceção fica pendente e é executada assim que outras exceções com maior prioridade forem tratadas. No FreeRTOS, é esta exceção que realiza a troca de contexto.
- SVCall (System Service Call): É uma interrupção de software que pode ser usada para gerar chamadas de sistema. Por exemplo, em vez de uma aplicação acessar diretamente um recurso do sistema (ex: porta serial), ela irá realizar uma chamada de sistema usando a instrução SVC, passando o controle para o kernel tratar e prover o serviço requisitado pela aplicação. É um mecanismo comum que os sistemas operacionais usam para prover serviços para as aplicações. O FreeRTOS implementa a chamada de serviço zero, e usa esta chamada para iniciar a primeira tarefa do sistema.
Altere então a tabela de vetores de interrupção no arquivo de inicialização (startup.c) e insira as rotinas de manipulação de interrupção do FreeRTOS para o Cortex-M3 (linhas 18, 21 e 22):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
... /* interrupt vector table */ void *vector_table[] __attribute__ ((section(".vectors"))) = { /* ARM Cortex-M3 interrupt vectors */ &_end_stack, Reset_Handler, NMI_Handler, HardFault_Handler, MemManage_Handler, BusFault_Handler, UsageFault_Handler, 0, 0, 0, 0, vPortSVCHandler, // FreeRTOS SVC handler DebugMon_Handler, 0, xPortPendSVHandler, // FreeRTOS PendSV handler xPortSysTickHandler, // FreeRTOS SysTick handler ... |
DESENVOLVENDO A APLICAÇÃO
Agora é so escrever sua aplicação usando o FreeRTOS:
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 |
#include "LPC17xx.h" #include "FreeRTOS.h" #include "task.h" #define LED1_GPIO 18 #define LED2_GPIO 20 #define LED3_GPIO 21 #define LED4_GPIO 23 #define LED_GPIO LED1_GPIO void init_hardware() { /* set system tick for 1ms interrupt */ SystemCoreClockUpdate(); /* set led GPIO port */ LPC_GPIO1->FIODIR |= (1<<LED1_GPIO); LPC_GPIO1->FIODIR |= (1<<LED2_GPIO); LPC_GPIO1->FIODIR |= (1<<LED3_GPIO); LPC_GPIO1->FIODIR |= (1<<LED4_GPIO); } void blink_led_task(void *pvParameters) { while(1) { LPC_GPIO1->FIOSET = (1<<LED_GPIO); vTaskDelay(500/portTICK_RATE_MS); LPC_GPIO1->FIOCLR = (1<<LED_GPIO); vTaskDelay(500/portTICK_RATE_MS); } } int main(void) { /* initialize hardware */ init_hardware(); /* create task to blink led */ xTaskCreate(blink_led_task, (signed char *)"Blink led", configMINIMAL_STACK_SIZE, (void *)NULL, tskIDLE_PRIORITY, NULL); /* Start the scheduler. */ vTaskStartScheduler(); /* should never reach here! */ for(;;); } |
Na linha 39 inicializamos o hardware, incluindo os GPIOs responsáveis pelos leds do kit. Na linha 42 criamos a tarefa que irá fazer o led piscar, e na linha 45 iniciamos o escalonador. A partir deste momento, o FreeRTOS assume o controle da CPU. A tarefa que pisca o led, na linha 24, é basicamente um loop infinito que muda o estado do led a cada 500ms.
Disponibilizei o projeto completo no github. Se você for compilar e testar, vai precisar baixar também os fontes do FreeRTOS. Eu usei a versão 7.1.0, mas o projeto deve funcionar com qualquer versão que possua o porte para Cortex-M3 com o GCC. Não se esqueça também de configurar os diretórios no Makefile de acordo com seu ambiente.
A partir daí é só estudar a API do FreeRTOS. A documentação do site do projeto é bem decente. Ou então você pode contribuir (financeiramente) com o projeto, comprando um dos livros que descrevem a API e ensinam como usá-la com exemplos bem didáticos.
E se eu fosse você, não perderia minha apresentação sobre o FreeRTOS no ESC Brasil 2012! Prometo muito live coding! :)
Um abraço,
Sergio Prado