Criando um projeto no IAR com o FreeRTOS - Embarcados
Crie um projeto no IAR com o FreeRTOS. É apresentado e demonstrado o uso desse RTOS, sendo o exemplo prático implementado com um microcontroaldor LPC2368.
O intuito deste artigo é criar um projeto no IAR com o FreeRTOS, apresentando e demonstrando o uso desse RTOS. O exemplo prático apresentado foi implementado com um microcontroaldor LPC2368 utilizando um kit de desenvolvimento MCB2300.
Para evitar ser repetitivo, está sendo assumido que o leitor já conhece os conceitos básicos de um RTOS. Para os que desejam revisar o tema, recomendo a série de artigos escrita por Henrique Puhlmann. Também merecem atenção os artigos de Rodrigo Almeida sobre a construção de um RTOS.
O FreeRTOS é um sistema operacional de tempo real e de código aberto, gratuito e disponível para download aqui. Devido à sua simplicidade e disponibilidade para diversas plataformas de hardware, este RTOS tem sido amplamente adotado em projetos de sistemas embarcados.
O FreeRTOS permite a fácil implementação de multitarefa preemptiva ou não preemptiva com diversos níveis de prioridade de tarefas. Além disto, o FreeRTOS também oferece suporte a funcionalidades como:
Comunicação entre tarefas através de filas;
Timers de software;
Semáforos e mutexes para sincronização de acesso a recursos compartilhados;
Diversos algoritmos de gerenciamento de memória;
Gerenciamento de notificações e eventos.
Mais de 90% do código fonte do FreeRTOS é escrito em linguagem C (o restante do código é escrito em assembly). A arquitetura do FreeRTOS é dividida em camadas conforme a Figura 1.
Figura 1: Camadas de software do FreeRTOS
Todo o código que tem qualquer interação direta com o hardware do processador está contido na camada dependente de hardware. Desta forma é possível portar o FreeRTOS para novas arquiteturas reescrevendo apenas o código desta camada. Devido a esta simplicidade, o FreeRTOS já foi portado para mais de 35 arquiteturas diferentes e é suportado pelos principais compiladores disponíveis no mercado, como GCC, Keil, IAR, etc.
O download da versão mais recente do FreeRTOS pode ser feito de forma gratuita aqui. Este download pode ser feito na forma de um arquivo ZIP ou um arquivo EXE, que nada mais é do que um auto extrator. Não se assuste com o tamanho e a quantidade de arquivos que serão extraídos (são mais de 10.000 arquivos totalizando 275MB!). A grande maioria desses arquivos contém códigos de demonstração e exemplos de uso do FreeRTOS nas diversas arquiteturas suportadas. Antes de prosseguir, é importante entender a organização do código fonte para não ficar perdido nesta grande quantidade de arquivos. Recomendo este artigo em que o Henrique Rossi explica a organização do código fonte do FreeRTOS.
Na época que este artigo foi escrito (abril/2016) a versão mais atual era a 8.2.3.
A IAR Systems fornece um ambiente completo para desenvolvimento de firmware para microcontroladores: o IAR Embedded Workbench. O pacote disponibilizado pela IAR contém, além do compilador, um ambiente de desenvolvimento (IDE) gráfico e suporte a ferramentas para depuração de código. Esta IDE é disponibilizada pela IAR sob vários e diferentes modelos de licenciamento. A versão Kickstart é uma versão gratuita que permite desenvolvimento de qualquer software desde que o tamanho do código esteja limitado a 32KB. Apesar desta limitação é possível implementar muitos projetos interessantes e, por este motivo, a versão Kickstart foi escolhida para ser usada aqui.
Ao fazer o download no site da IAR tome o cuidado de selecionar a versão do Embedded Workbench for ARM (EWARM) pois a IAR oferece a mesma IDE com suporte a outras arquiteturas.
Todos exemplos apresentados neste artigo foram testados na versão 6.50 do EWARM.
Criando um projeto novo no EWARM
Após concluir a instalação do EWARM e com a IDE já em execução, e clique em Project->create new project para criar um projeto novo. A Figura 2 mostra a janela que será aberta. Selecione a opção C->main. O EWARM irá solicitar um nome e um diretório para salvar o projeto. Recomendo que o projeto seja salvo em um diretório criado para esta finalidade. O EWARM salvará todos os códigos fontes e demais arquivos de projeto neste diretório.
Figura 2: Criação de um novo projeto no EWARM
A Figura 3 ilustra a tela do EWARM após a criação do projeto. A janela da esquerda contém uma lista de todos os arquivos que fazem parte do projeto. Neste momento apenas o arquivo main.c foi adicionado ao projeto. Também é possível ver que o EWARM criou uma função main vazia.
Figura 3: Projeto vazio criado automaticamente pelo EWARM
Para deixar o código fonte mais organizado e concentrar todos os arquivos necessários em um único local, eu copiei o diretório Source do FreeRTOS para dentro do diretório em que o projeto foi salvo. Desta forma, o diretório do projeto terá a estrutura mostrada na Figura 4.
Figura 4: Estrutura do diretório do projeto
O próximo passo é incluir os arquivos que compõem o código fonte do FreeRTOS no projeto do EWARM. Devem ser incluídos, no mínimo, os seguintes arquivos:
List.c – Contém o código que implementa listas usadas internamente pelo FreeRTOS;
Queue.c – Contém o código que implementa filas usadas para comunicação entre as tarefas de software. As filas também são usadas internamente pelo FreeRTOS;
Tasks.c – Contém a parte mais importante do FreeRTOS: o escalonador. O escalonador é responsável por controlar a execução das diversas tarefas de software implementando o multitarefa;
Portable/IAR/LPC2000/port.c – Contém o código dependente de hardware. Neste exemplo será usado um microcontrolador LPC2368. O port LPC2000 é o que mais se aproxima do microcontrolador a ser usado e por isto foi escolhido. Como existem algumas pequenas diferenças entre o LPC2000 e o LPC2368, será necessário fazer alguns ajustes neste arquivo;
Portable/MemMang/heap_1.c – Contém o código que realiza o gerenciamento da memória RAM. Aqui são implementadas variantes das funções malloc e free usadas pelo FreeRTOS. O gerenciamento de memória do FreeRTOS será detalhado melhor em um artigo futuro;
Portable/IAR/LPC2000/portasm.s79 – Contém código assembly dependente de hardware, relacionada à troca de contexto entre as tarefas.
Para incluir um arquivo no projeto clique em Project->Add Files e, na janela que irá abrir, selecione os arquivos list.c, queue.c e tasks.c. Lembre-se que estes arquivos estão dentro do diretório Source. Após isto, repita o processo para os arquivos heap_1.c (que está dentro de source/portable/MemMang) e port.c (que está dentro de source/portable/IAR/LPC2000).
Um último arquivo importante a ser incluído no projeto é um arquivo assembly que contém a definição dos vetores de interrupções/exceções do processador. Neste caso vamos copiar o arquivo lpc2xxx_cstartup.s localizado no diretório Demo/ARM7_LPC2129_IAR/SrcIAR/ do código fonte original do FreeRTOS para o diretório raiz do projeto. O arquivo utilizado é do processador que mais se assemelha ao LPC2368 entre os exemplos apresentados no código fonte do FreeRTOS. Mais adiante será necessário fazer alguns ajustes neste arquivo.
A Figura 5 mostra uma visão do projeto após a inclusão dos arquivos do FreeRTOS no projeto.
Figura 5: Visão do projeto após a inclusão do código fonte do IAR
Antes de compilar o projeto, é necessário fazer alguns ajustes nas suas configurações. Para editar as configurações do projeto clique em Project->options. A janela que será aberta permite configurar diversas opções do projeto. Vamos começar selecionando o modelo do microcontrolador, que no nosso exemplo será o LPC2368 (Figura 6). Para selecionar o microcontrolador, clique no botão à direita do label "Device" e selecione o fabricante (NXP), depois a família de microcontroladores (LPC2300) e por fim o modelo do microcontrolador (LPC2368).
Figura 6: Seleção do modelo do microcontrolador nas configurações do projeto
Um outro ponto importante é a seleção correta do mapa de memória do microcontrolador. O mapa de memória é um arquivo texto que contém instruções para o compilador alocar o código em diversas regiões da memória flash do microcontrolador. Através deste mapa, é possível também reservar regiões da memória para usos específicos. Para verificar qual o mapa de memória em uso, clique em Linker. Pela Figura 7 é possível observar que o EWARM selecionou um arquivo LPC2368.icf que é o mapa de memória padrão para este microcontrolador. Por enquanto vamos utilizar este mapa de memória padrão.
Figura 7: Seleção do mapa de memória
Para informar ao compilador os diretórios em que ele deve procurar por arquivos .h (esta configuração é conhecida como include path) clique em C/C++ Compiler, selecione a aba Preprocessor e insira um diretório por linha no campo "Additional include directories". Devem ser incluídos os seguintes diretórios:
Source/include – Contém todos os arquivos de cabeçalho do FreeRTOS que são independentes de hardware;
Source/portable/IAR/LPC2000 – Contém os cabeçalhos do código dependente de hardware;
O diretório raiz do projeto.
A Figura 8 mostra a inclusão do destes diretórios na janela de configuração. Observe que foi utilizada a macro $PROJ_DIR$ que o compilador irá substituir pelo nome do diretório raiz do projeto. A grande vantagem de usar esta macro é que, desta forma, o projeto irá utilizar caminhos relativos. Isso significa que se este projeto for copiado para outro diretório ou, até mesmo, outro computador, não será necessário fazer ajustes no nome dos diretórios para refletir o novo local.
Figura 8: Configuração do include path para o compilador C/C++
A inserção do include path deve ser repetida na tela de configuração do assembler. Para isto, clique em Assembler, selecione a aba Preprocessor e insira os mesmos diretórios conforme a Figura 9.
Figura 9: Configuração do include path para o assembler
Feitos os ajustes acima, clique em OK para fechar a janela de configuração.
Agora vamos fazer os ajustes necessário no arquivo port.c. Lembrando que as famílias LPC2000 e LPC2368 são microcontroladores que utilizam núcleos ARM7 idênticos (consequentemente possuem o mesmo conjunto de instruções assembly), mas existe uma diferença na implementação de seus controladores de interrupções (conhecido como VIC – Vectored Interrupt Controller). Devido a isto, é necessário fazer um ajuste na função prvSetupTimerInterrupt. Nesta função, é feita a configuração do timer 0 que é utilizado pelo FreeRTOS e do VIC para habilitar e as interrupções deste timer. As principais diferenças entre os VICs destas duas famílias são as seguintes:
No LPC23xx o timer 0 é associado ao vetor de interrupção 4 enquanto que no LPC2000 é possível escolher o vetor ao qual o timer 0 será associado. Desta forma é necessário alterar todas as referências ao número do vetor para 4;
No LPC23xx o registrador VICVectCntl controla a prioridade da interrupção.
A seguir é exibido o código fonte da função prvSetupTimerInterrupt com as modificações para o microcontrolador LPC2368. O código original do LPC2000 está comentado logo acima do código que deve ser usado para o LPC2368.
static void prvSetupTimerInterrupt( void ) { uint32_t ulCompareMatch; /* A 1ms tick does not require the use of the timer prescale. This is defaulted to zero but can be used if necessary. */ T0PR = portPRESCALE_VALUE; /* Calculate the match value required for our wanted tick rate. */ ulCompareMatch = configCPU_CLOCK_HZ / configTICK_RATE_HZ; /* Protect against divide by zero. Using an if() statement still results in a warning - hence the #if. */ #if portPRESCALE_VALUE != 0 { ulCompareMatch /= ( portPRESCALE_VALUE + 1 ); } #endif T0MR0 = ulCompareMatch; /* Generate tick with timer 0 compare match. */ T0MCR = portRESET_COUNT_ON_MATCH | portINTERRUPT_ON_MATCH; /* Setup the VIC for the timer. */ VICIntSelect &= ~( portTIMER_VIC_CHANNEL_BIT ); VICIntEnable |= portTIMER_VIC_CHANNEL_BIT; /* The ISR installed depends on whether the preemptive or cooperative scheduler is being used. */ #if configUSE_PREEMPTION == 1 {	extern void ( vPortPreemptiveTickEntry )( void ); //*************** //VICVectAddr0 = ( uint32_t ) vPortPreemptiveTickEntry; // Codigo para LPC2000 //*************** VICVectAddr4 = ( uint32_t ) vPortPreemptiveTickEntry; //Código para o LPC2368 } #else { extern void ( vNonPreemptiveTick )( void ); //*************** //VICVectAddr0 = ( int32_t ) vPortNonPreemptiveTick; //Codigo para LPC2000 //*************** VICVectAddr4 = ( int32_t ) vPortNonPreemptiveTick; //Código para o LPC2368 } #endif //VICVectCntl0 = portTIMER_VIC_CHANNEL | portTIMER_VIC_ENABLE; //LPC2000 VICVectCntl4 = 1;	//Código para LPC2368 /* Start the timer - interrupts are disabled when this function is called so it is okay to do this here. */ T0TCR = portENABLE_TIMER; }
uint32_t ulCompareMatch;
T0MCR = portRESET_COUNT_ON_MATCH | portINTERRUPT_ON_MATCH;
//VICVectAddr0 = ( uint32_t ) vPortPreemptiveTickEntry; // Codigo para LPC2000
VICVectAddr4 = ( uint32_t ) vPortPreemptiveTickEntry; //Código para o LPC2368
//VICVectAddr0 = ( int32_t ) vPortNonPreemptiveTick; //Codigo para LPC2000
VICVectAddr4 = ( int32_t ) vPortNonPreemptiveTick; //Código para o LPC2368
//VICVectCntl0 = portTIMER_VIC_CHANNEL | portTIMER_VIC_ENABLE; //LPC2000
VICVectCntl4 = 1; //Código para LPC2368
O próximo passo é ajustar os vetores de interrupção do processador no arquivo lpc2xxx_cstartup.s. Sempre ocorre uma interrupção de hardware, o controlador de interrupção disponibiliza o endereço da rotina de tratamento da interrupção em um registrador chamado VICAddress. O endereço deste registrador no LPC2368 é diferente do LPC2129 e, por isto, é necessário fazer um ajuste no código. Lembre-se que o arquivo lpc2xxx_cstartup.s que utilizamos não é do LPC2368 pois o FreeRTOS não possui um exemplo para este processador. Este ajuste é necessário pois as interrupções periódicas do timer 0 serão tratadas pelo vetor IRQ. A seguir é exibido um trecho do código do arquivo lpc2xxx_cstartup.s com destaque para o trecho de código alterado para o LPC2368.
SECTION .intvec:CODE:NOROOT(2) PUBLIC __vector PUBLIC __vector_0x14 PUBLIC __iar_program_start EXTERN	vPortYieldProcessor ARM __vector: ;; ldr pc,[pc,#+24] ;; Reset ldr pc,[pc,#+24] ;; Undefined instructions ;; ldr pc,[pc,#+24] ;; Software interrupt (SWI/SVC) b vPortYieldProcessor ldr pc,[pc,#+24] ;; Prefetch abort ldr pc,[pc,#+24] ;; Data abort __vector_0x14 DC32 0 ;; RESERVED ;;************************* ;;Código do LPC2126 que deve ser removido ;; ldr pc, [PC, #-0xFF0] ;; IRQ ;;************************* ;;************************* ;;A linha abaixo deve ser inserida para se usar o LPC2368 ldr pc, [PC, #-0x120] ;; IRQ ;;************************* ldr pc,[pc,#+24] ;; FIQ DC32 __iar_program_start ;; Reset DC32 undef_handler ;; Undefined instructions DC32 0 ;; Software interrupt (SWI/SVC) DC32 prefetch_handler ;; Prefetch abort DC32 data_handler ;; Data abort DC32 0 ;; RESERVED DC32 0 ;; IRQ DC32 fiq_handler ;; FIQ
;;Código do LPC2126 que deve ser removido
;; ldr pc, [PC, #-0xFF0] ;; IRQ
;;A linha abaixo deve ser inserida para se usar o LPC2368
O FreeRTOS possui diversos parâmetros de configuração que controlam o comportamento do escalonador e outras funcionalidades do sistema operacional. Através da configuração é possível também desabilitar funcionalidades que não são utilizadas, reduzindo, assim, o total de memória ocupada pelo FreeRTOS. O arquivo de configuração do FreeRTOS é o FreeRTOSConfig.h que, por ser um arquivo especifico do projeto e não ser dependente de hardware, não deve ficar dentro da pasta portable. Além deste arquivo de configuração, precisamos também do arquivo que contém as definições do hardware do processador, como, por exemplo, os endereços dos registradores. Cada um dos exemplos apresentados na pasta demo no código fonte original do FreeRTOS contém um arquivo de configuração e um arquivo de cabeçalho com as definições do processador. Para o nosso exemplo, vamos copiar os arquivos FreeRTOSConfig.h e lpc23xx.h do diretório demo/ARM7_LPC2368_Eclipse/RTOSDemo para o diretório raiz do projeto.
Se tentarmos compilar o projeto neste momento, receberemos mensagens de erro que podem ser eliminadas editando o arquivo FreeRTOSConfig.h. Uma configuração importante é a constante configCPU_CLOCK_HZ que informa ao FreeRTOS o valor do clock do processador. Este valor será usado para calcular todas as bases de tempo do FreeRTOS. Como o cristal utilizado na placa MCB2300 é de 12MHz, vamos ajustar esta constante de acordo com isso. A seguir é exibida a parte do arquivo FreeRTOSConfig.h com comentários nos trechos alterados. As linhas modificadas estão destacadas em amarelo.
Tudo que está após a linha que contém #endif /* FREERTOS_CONFIG_H */ (linha 136) deve ser apagado ou comentado. Alguns erros de compilação ocorrerão se estas linhas não forem removidas.
#ifndef FREERTOS_CONFIG_H #define FREERTOS_CONFIG_H //************* //Comentar a linha abaixo //#include <stdio.h> #include "lpc23xx.h" /*----------------------------------------------------------- * Application specific definitions. * * These definitions should be adjusted for your particular hardware and * application requirements. * * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. * * See http://www.freertos.org/a00110.html. *----------------------------------------------------------*/ /* Value to use on old rev '-' devices. */ //#define configPINSEL2_VALUE	0x50151105 /* Value to use on rev 'A' and newer devices. */ //************* //Descomentar abaixo #define configPINSEL2_VALUE 0x50150105 #ifndef configPINSEL2_VALUE #error Please uncomment one of the two configPINSEL2_VALUE definitions above, depending on the revision of the LPC2000 device being used. #endif #define configUSE_PREEMPTION	1 #define configUSE_IDLE_HOOK 0 //Alterar o valor abaixo de 1 para 0 #define configUSE_TICK_HOOK 0 //Alterar o valor abaixo para 12000000 #define configCPU_CLOCK_HZ ( ( unsigned long ) 12000000 )/* =12Mhz xtal multiplied by 5 using the PLL. */ #define configTICK_RATE_HZ ( ( TickType_t ) 1000 ) #define configMAX_PRIORITIES	( 4 ) #define configMINIMAL_STACK_SIZE	( ( unsigned short ) 104 ) #define configTOTAL_HEAP_SIZE	( ( size_t ) ( 18 * 1024 ) ) #define configMAX_TASK_NAME_LEN	( 10 ) #define configUSE_TRACE_FACILITY	1 #define configUSE_16_BIT_TICKS	0 #define configIDLE_SHOULD_YIELD	1 #define configUSE_MUTEXES	1 /* Co-routine definitions. */ #define configUSE_CO_ROUTINES 0 #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) /* Set the following definitions to 1 to include the API function, or zero to exclude the API function. */ #define INCLUDE_vTaskPrioritySet 1 #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskCleanUpResources 0 #define INCLUDE_vTaskSuspend 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_xTaskGetCurrentTaskHandle	1 /* This demo makes use of one or more example stats formatting functions. These format the raw data provided by the uxTaskGetSystemState() function in to human readable ASCII form. See the notes in the implementation of vTaskList() within FreeRTOS/Source/tasks.c for limitations. */ #define configUSE_STATS_FORMATTING_FUNCTIONS	1 #endif /* FREERTOS_CONFIG_H */
//Comentar a linha abaixo
//#define configPINSEL2_VALUE 0x50151105
//Descomentar abaixo
#define configPINSEL2_VALUE 0x50150105
//Alterar o valor abaixo de 1 para 0
//Alterar o valor abaixo para 12000000
#define configCPU_CLOCK_HZ ( ( unsigned long ) 12000000 )/* =12Mhz xtal multiplied by 5 using the PLL. */
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 104 )
Após salvar o arquivo podemos compilar o código fonte. A compilação pode ser feita apertando a tecla F7 ou clicando em Project->Rebuild All. Se tudo correr bem, a compilação deve gerar apenas alguns warnings que, por enquanto, serão ignorados.
A rotina principal (main) de um software com FreeRTOS deve conter, no mínimo, a inicialização do hardware do processador (dependendo da aplicação isso pode não ser necessário), a criação de algumas tarefas e a inicialização do escalonador. O código mostrado a seguir mostra um exemplo de uma rotina main com a criação de uma tarefa que foi chamada de vGPIOTask.
void main( void ) { /* Inicialização de uma tarefa */ xTaskCreate( vGPIOTask, /* Função que executa a tarefa */ ( const char * ) "GPIO", /* String que define o nome da tarefa */ 34, /* tamanho da pilha da tarefa */ NULL, /* parametros para a tarefa */ tskIDLE_PRIORITY + 1, /* prioridade da tarefa */ NULL ); /* handle da tarefa */ /* Inicialização do escalonador */ vTaskStartScheduler(); /* Como o escalonador fica em loop infinito, o codigo abaixo nunca será executado */ return; }
/* Inicialização de uma tarefa */
xTaskCreate( vGPIOTask, /* Função que executa a tarefa */
( const char * ) "GPIO", /* String que define o nome da tarefa */
34, /* tamanho da pilha da tarefa */
/* Como o escalonador fica em loop infinito, o codigo abaixo nunca será executado */
No FreeRTOS, cada tarefa deve ser implementada como uma função em C que fica em loop infinito. O escalonador se encarregará de selecionar e executar as tarefas criadas pelo usuário. A seguir é exibido o código fonte de uma tarefa simples que faz piscar os LEDs da placa MCB2300. Observe que toda inicialização específica da tarefa é feita antes do loop infinito.
static void vGPIOTask( void *pvParameters ) { /* Inicializa todos os pinos da porta 2 como saida */ FIO2DIR = 0xFFFFFFFF; while ( 1 ) { /* zera todos os bits da porta apagando os LEDs */ FIO2CLR = 0x000000FF; /* aguarda 100ms*/ vTaskDelay(100); /* acende todos os LEDs */ FIO2SET = 0x000000FF; vTaskDelay(100); } }
static void vGPIOTask( void *pvParameters )
/* Inicializa todos os pinos da porta 2 como saida */
/* zera todos os bits da porta apagando os LEDs */
/* aguarda 100ms*/
/* acende todos os LEDs */
FIO2SET = 0x000000FF;
Com este exemplo chegamos ao fim deste artigo. Espero ter ajudado os leitores a terem uma ideia sobre o uso do EWARM e do FreeRTOS.
Comunicação entre tarefas no FreeRTOS: Filas >>
AnteriorAnteriorTop 10: Abril de 2016
PróximoLançada Nova Versão do Yocto Project: Yocto 2.1 KrogothPróximo
belo artigo. creio que vai ajudar na minha empreitada também: http://hardwaresw.blogspot.com.br/
Olá Bruno. Seu artigos estão muito bons! Fico feliz em saber que há outras pessoas interessadas em divulgar o FreeRTOS.