Usando FreeRTOS para aplicações com a Freescale FRDM KL25Z
Sistemas Dinâmicos e Equações de Diferenças com a FRDM KL25Z >>
Este post faz da série Controladores e Filtros Digitais utilizando o FreeRTOS e a KL25Z. Leia também os outros posts da série:
Home Software Usando FreeRTOS para aplicações com a FRDM KL25Z
Utilizando o FreeRTOS
Neste artigo serão apresentados alguns conceitos envolvendo sistemas operacionais de tempo-real (RTOS) e a utilização do FreeRTOS para desenvolvimento de aplicações utilizando a placa Freedom KL25z e a IDE MCUXpresso, incluindo seu SDK (Software Development Kit) KSDK v.2.2.0.
Como base teórica, estão sendo utilizados a série de artigos publicados no Portal Embarcados por Rodrigo Almeida e Marcelo Barros intitulada "Desenvolvendo um RTOS".
Um sistema operacional de tempo-real é uma estrutura programada para gerenciar os recursos da CPU, controlando a sincronização de tarefas, gerenciamento de interrupções, etc. e garantindo os requisitos temporais.
Tendo em vista que essa estrutura consome processamento e memória, sua utilização é indicada quando há o aumento da complexidade do projeto tornando-se inviável sua implementação "crua" utilizando o one single loop.
Kernel é a camada do sistema operacional responsável por abstrair a utilização de memória e o processamento das aplicações. Gerencia a execução de processos, manuseia a memória disponível e faz a intermediação com o hardware.
O Kernel pode ser dividido em duas categorias, cooperativo e preemptivo. No primeiro, um processo (ou tarefa) só é executado após o término do anterior, sendo assim, loops infinitos travam o sistema. O segundo funciona de maneira que todos os processos são implementados como loops infinitos, o RTOS é responsável pela troca de contexto e empilhamento de funções para execução de múltiplas tarefas, pausando um processo para execução de outro baseando-se em ordens de prioridade, por exemplo.
O FreeRTOS é o sistema operacional de tempo-real mais utilizado na atualidade e tem suporte a grande parte dos fabricantes e IDE's.
Figura 1: IDE's e fabricantes suportados
A interface com o hardware é implementada no RTOS e está pronta para ser utilizada. São abstraídas as maneiras com que são realizadas as trocas de contexto entre tarefas, de acordo com a prioridade que é atrelada a elas no momento de sua criação. Todas as tarefas são implementadas como loops infinitos, já que o sistema consegue trocar de contexto durante a execução de um processo para execução de outro.
No Portal Embarcados existe uma série de artigos de Evandro Teixeira onde são implementadas as Bibliotecas de Software para FRDM-KL25Z, portanto, as funções de acesso às portas de entrada e saída (GPIO) utilizadas, por exemplo, serão semelhantes às criadas nesta série.
O primeiro passo é fazer a instalação da IDE (MCUXpresso) e do Software Development Kit (KSDK v.2.2.0) baixados do site do fabricante, que a partir de dezembro de 2015 passou a ser a NXP.
O download da IDE pode ser feito pelo site da NXP, clicando em "download" →"MCUXpresso IDE" e selecionando o sistema operacional.
O download do SDK também pode ser feito pelo site da NXP, clicando em "Select Development Board", selecione sua placa (neste exemplo será utilizada a FRDM-KL25Z) e clique em "Build MCUXpresso SDK". Após isso, é necessário adicionar ao SDK o sistema operacional (FreeRTOS). Clique em "Add software component", selecione o FreeRTOS e faça o download.
Figura 2: Adicionando o FreeRTOS ao SDK
Obs.: Se ao realizar o debug dos exemplos, no momento em que a IDE tentar gravar o código no microcontrolador, surgir uma tela de erro com a mensagem "No Device Available" é provável que seja necessária atualização do bootloader. Este link pode ajudar.
Ao abrir a IDE pela primeira vez o SDK pode ser instalado arrastando a pasta ou o .zip baixado anteriormente a esta aba:
Figura 3: Aba de Instalação do SDK
Após isto é necessário criar um projeto. Uma vez aberta a IDE, vá em File → New → Project → C/C++ → New C/C++ Project, selecione a placa correspondente ao SDK instalado, clique em next.
Figura 4: Criação do Projeto
Escolha o nome do projeto, seu diretório e os recursos a serem utilizados, incluindo o FreeRTOS.
Figura 5: Importação dos recursos
A IDE MCUXpresso realiza todos os includes necessários se os passos anteriores foram seguidos corretamente, se isso não fosse verdade seria necessário incluir às configurações de compilação os diretórios contendo os arquivos header (.h) das bibliotecas a serem utilizadas.
FRDM-KL25Z/rtos/freertos_9.0.0/Source/include
FRDM-KL25Z/rtos/freertos_9.0.0/Source/portable/GCC/ARM_CM0
A inclusão das bibliotecas externas para os periféricos deve ser feita adicionando os arquivos .c que necessitam ser compilados a pasta dentro do projeto de forma linkada. Para isso, clique com o botão direito sobre o projeto New → Folder. Clique no projeto e dê um nome para a pasta que conterá os arquivos a serem incluídos. Em "Advanced", faça com que esta pasta seja uma "Linked Folder". Digite seu endereço e clique em Finish.
Figura 6: Criação da pasta
Para ter certeza que estes arquivos serão compilados, clique com o botão direito sobre a pasta recém importada → Properties → C/C++ Build e desmarque a caixa "Exclude resource from build".
Figura 7: Garantindo a compilação das bibliotecas
O diretório contendo o header (.h) deve ser incluído nas configurações de compilação do projeto clicando com o botão direito sobre ele → Properties → C/C++ Build → Settings → MCU C Compiler → Includes. Clique no ícone "Add…" no canto superior direito e digite o endereço da biblioteca ou navegue utilizando o explorador de arquivos.
Figura 8: Adicionando bibliotecas externas
Uma vez que feitos todos esses passos, o FreeRTOS já está pronto para ser utilizado, basta que a biblioteca FreeRTOS.h seja incluída no arquivo principal, assim como as funcionalidades a serem utilizadas, task (task.h), queue (queue.h), semaphore (semphr.h), etc. O arquivo que contém a função principal é o arquivo (FreeRTOS_Teste.c), localizado na pasta "Sources" do projeto.
Para exemplificar, serão criadas duas tasks, uma responsável por piscar uma das cores do LED RGB presente na placa, outra responsável por ler um botão conectado a uma entrada digital e alterar qual dos LED's será aceso pela primeira task.
Serão criadas duas "bibliotecas", isso pode ser feito clicando com o botão direito sobre a pasta na qual se deseja guardar os arquivos, New->Source e New->Header. Elas serão as seguintes:
#ifndef BUTTN_H #define BUTTN_H #include "fsl_device_registers.h" void bInit(); //Inicializa a porta digital e liga PullUP void bDebounce(); //Rotina de debounce que analisa a estabilidade da porta uint8_t bRead(); //Retorna o valor lido #endif
#ifndef BUTTN_H
#define BUTTN_H
void bInit(); //Inicializa a porta digital e liga PullUP
void bDebounce(); //Rotina de debounce que analisa a estabilidade da porta
uint8_t bRead(); //Retorna o valor lido
#include "gpio_x.h" static uint8_t buttonState = 0; void bInit(){ PORTB_CLK(); //Ativa o clock gate da PORTB PORTB_Mode(0, INPUT); //Configura PTB0 como Input PORTB_PULLUP(0); //Ativa o Pull-up } uint8_t bRead(){ return buttonState; } void bDebounce(){ static uint8_t tempo = 42; static uint8_t anterior = 0; static uint8_t atual = 0; anterior = atual; if(!PORTB_Read(0)){ //Botão ativo por Zero (Pull-up) atual = 1; } else{ atual = 0; } if (atual == anterior){ //Checa a estabilização da porta tempo--; } else{ anterior = atual; tempo = 42; } if (tempo == 0){ //Aguarda a estabilização durante 42 execuções da função buttonState = atual; tempo = 42; } }
#include "gpio_x.h"
void bInit(){
PORTB_CLK(); //Ativa o clock gate da PORTB
PORTB_Mode(0, INPUT); //Configura PTB0 como Input
PORTB_PULLUP(0); //Ativa o Pull-up
uint8_t bRead(){
void bDebounce(){
static uint8_t tempo = 42;
static uint8_t anterior = 0;
static uint8_t atual = 0;
if(!PORTB_Read(0)){ //Botão ativo por Zero (Pull-up)
if (atual == anterior){ //Checa a estabilização da porta
tempo = 42;
if (tempo == 0){ //Aguarda a estabilização durante 42 execuções da função
buttonState = atual;
O propósito da biblioteca "button.h" é tratar da entrada digital onde será conectado um botão.
A função bInit() faz a inicialização da porta, configurando-a como entrada e ativando o resistor interno de pull-up, que faz com que, quando nada conectado à porta, ela tenha nível lógico alto.
A função bDebounce() foi implementada de maneira semelhante à implementada em um de meus artigos intitulado Tinkercad e PQDB: Teclado matricial, em que analisa a estabilidade do botão durante 42 (static uint8_t tempo) chamadas da função bDebounce, já que, por se tratar de um botão físico, sofre do efeito de bouncing, uma oscilação no sinal, já que o contato não é idealmente fechado, exatamente como é explicado pelo professor Rodrigo Almeida no artigo "Leitura de chaves mecânicas e o processo de debounce".
A função bRead() retorna o estado do botão atualizado por bDebounce().
A biblioteca "gpio_x.h" manipula as entradas e saídas digitais do microcontrolador, configurando-as de maneira semelhante à feita por Evandro Teixeira em sua série de artigos onde implementa as Bibliotecas de Software para FRDM-KL25Z.
A biblioteca "fsl_device_registers.h" possui o mapeamento dos registradores do microcontrolador e as definições de tipo como o uint8_t, um inteiro, sem sinal e com 8 bits.
ledState.h
#ifndef LEDSTATE_H_ #define LEDSTATE_H_ #include "fsl_device_registers.h" void setState(uint8_t); uint8_t getState(); #endif
#ifndef LEDSTATE_H_
#define LEDSTATE_H_
void setState(uint8_t);
ledState.c
#include "ledState.h" static uint8_t state = 0; void setState(uint8_t value){ state = value; } uint8_t getState(){ return state; }
#include "ledState.h"
void setState(uint8_t value){
uint8_t getState(){
A biblioteca "ledState.h" é utilizada apenas para evitar utilização de uma variável global e faz com que o valor da variável state seja lido e alterado apenas por meio das funções getState() e setState(), respectivamente, de maneira semelhante como é feito o acesso à variáveis privadas de classes, em linguagens de programação orientadas a objeto.
FreeRTOS_Teste.c
#include "fsl_device_registers.h" #include "FreeRTOS.h" #include "task.h" #include "semphr.h" #include "gpio_x.h" #include "button.h" #include "ledState.h" xSemaphoreHandle xSemaphore = 0; void blink_task (void *parameters){ PORTB_CLK(); //Ativa o clock gate em PORTB PORTB_Mode(18, OUTPUT); PORTB_Mode(19, OUTPUT); PORTD_CLK(); PORTD_Mode(1, OUTPUT); //Configura os LEDs vermelho, verde e azul PORTB_Write(18, HIGH); //Desliga os LEDs anodo comum PORTB_Write(19, HIGH); //Ativos por Zero PORTD_Write(1, HIGH); uint8_t portAtual = 18; while(1){ if(xSemaphoreTake(xSemaphore, (500*configTICK_RATE_HZ)/1000)){ //Espera por 500ms switch (getState()){ case 0: xSemaphoreGive(xSemaphore); portAtual = 18; break; case 1: xSemaphoreGive(xSemaphore); portAtual = 19; break; case 2: xSemaphoreGive(xSemaphore); portAtual = 1; break; default: setState(0); xSemaphoreGive(xSemaphore); portAtual = 18; break; } } if (portAtual == 1){ PORTD_Write(portAtual, LOW); //Acende } else{ PORTB_Write(portAtual, LOW); } vTaskDelay(500*configTICK_RATE_HZ/1000); //500 ms if (portAtual == 1){ PORTD_Write(portAtual, HIGH); //Apaga } else{ PORTB_Write(portAtual, HIGH); } vTaskDelay(500*configTICK_RATE_HZ/1000); //500 ms } } void button_task (void *parameters){ bInit(); uint8_t anterior = 0, atual = 0; while(1){ bDebounce(); anterior = atual; atual = bRead(); if (atual == 1 && anterior == 0){ //botao foi pressionado if(xSemaphoreTake(xSemaphore, portMAX_DELAY)){ //Espera indefinidamente a liberação do mutex setState((getState() + 1)%3);//Incrementa e garante que seja < 3 xSemaphoreGive(xSemaphore);//Libera o mutex } } } } int main(void){ xSemaphore = xSemaphoreCreateMutex(); xTaskCreate(blink_task, "blink", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); xTaskCreate(button_task, "button", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); vTaskStartScheduler(); //Should never reach here for (;;); return 0; }
xSemaphoreHandle xSemaphore = 0;
void blink_task (void *parameters){
PORTB_CLK(); //Ativa o clock gate em PORTB
PORTB_Mode(18, OUTPUT);
PORTB_Mode(19, OUTPUT);
PORTD_CLK();
PORTD_Mode(1, OUTPUT); //Configura os LEDs vermelho, verde e azul
PORTB_Write(18, HIGH); //Desliga os LEDs anodo comum
PORTB_Write(19, HIGH); //Ativos por Zero
PORTD_Write(1, HIGH);
uint8_t portAtual = 18;
if(xSemaphoreTake(xSemaphore, (500*configTICK_RATE_HZ)/1000)){ //Espera por 500ms
portAtual = 18;
portAtual = 19;
portAtual = 1;
if (portAtual == 1){
PORTD_Write(portAtual, LOW); //Acende
PORTB_Write(portAtual, LOW);
vTaskDelay(500*configTICK_RATE_HZ/1000); //500 ms
PORTD_Write(portAtual, HIGH); //Apaga
PORTB_Write(portAtual, HIGH);
void button_task (void *parameters){
uint8_t anterior = 0, atual = 0;
bDebounce();
atual = bRead();
if (atual == 1 && anterior == 0){ //botao foi pressionado
if(xSemaphoreTake(xSemaphore, portMAX_DELAY)){ //Espera indefinidamente a liberação do mutex
setState((getState() + 1)%3);//Incrementa e garante que seja < 3
xSemaphoreGive(xSemaphore);//Libera o mutex
xTaskCreate(blink_task, "blink", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL);
xTaskCreate(button_task, "button", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL);
A função main, cria um semáforo do tipo mutex (mutuamente exclusivo), que garante que o acesso à variável state, que indica qual dos LED's será ativado, seja feito apenas por uma tarefa por vez. São criadas duas tasks (tarefas), blink_task e button_task, ambas com o tamanho de pilha mínimo, sem parâmetros, mas com prioridades diferentes. A macro tskIDLE_PRIORITY representa a prioridade da Idle Task, que é a task com menor prioridade, a qual é executada quando nenhuma outra é.
A task blink_task tem prioridade (tskIDLE_PRIORITY + 2), e a task button_task tem prioridade (tskIDLE_PRIORITY + 1), isso faz com que, quando o kernel tem que tomar uma decisão para definir qual das tarefas será executada no momento, blink_task será executada, já que possui maior prioridade.
vTaskStartScheduler() dá início ao scheduler do RTOS, dando ao kernel o controle sobre qual das tarefas é executada e quando.
A tarefa blink_task inicializa as saídas digitais correspondentes e, em seu loop infinito, realiza acesso à variável state e pisca o LED correspondente a seu valor. Ao fazer esse acesso, ela liga o mutex (xSemaphoreTake) de maneira que, se alguma outra task necessitar realizar o acesso à mesma variável, ele seja bloqueado, até que este semáforo seja desbloqueado (xSemaphoreGive). Após esta decisão, a task é colocada em modo sleep(vTaskDelay) até que, após 500 ms, sua execução seja retomada, piscando o LED.
A tarefa button_task inicializa o botão e, em seu loop infinito, executa a rotina de debounce, analisando o comportamento da porta digital a qual o botão está conectado, atualizando a variável state no momento em que é detectado o pressionamento do botão, protegendo, novamente, o acesso à variável state por meio do semáforo mutex.
Para compilar o código e enviá-lo ao microcontrolador, podem ser utilizadas tanto a opção de flash, que apenas envia o código, como a opção de debug, que possui algumas funcionalidades como a parada em break points, o acompanhamento da execução do código linha a linha, etc.
Figura 9: Flash Tool
Figura 10: Debug
Obs.: Como o bootloader instalado na placa é o PEmicro, esta opção deve ser selecionada em ambos os modos de execução.
Figura 11: Funcionamento
Um RTOS facilita o desenvolvimento de aplicações embarcadas, mas em contrapartida, ao abstrair as camadas de processamento, acaba gastando mais recursos, portanto é indicado para projetos cuja complexidade esteja de acordo.
Neste artigo foram apresentados alguns conceitos básicos com relação ao funcionamento de um sistema operacional de tempo-real. Foram também exploradas particularidades sobre a utilização do FreeRTOS, criação de tarefas e mutexes, e sua utilização na implementação de códigos com a Freedom Board KL25Z, Kinetis Software Development Kit (KSDK v2.2.0) e a IDE MCUXpresso.
No próximo artigo abordaremos a utilização da biblioteca de acesso ao DAC e ADC, bem como a implementação da execução de sistemas dinâmicos baseados em equações de diferenças.
Desenvolvendo um RTOS
https://github.com/thiagopprado/
Graduando em Engenharia de Controle e Automação pela Universidade Federal de Itajubá. Aficionado por Tecnologia, Programação e Sistemas Embarcados. Desde cedo era curioso por saber como as coisas funcionavam. Desmontava vários drivers de DVD e carrinhos de controle remoto atrás dos motores DC. Atualmente pesquiso na área de sistemas dinâmicos, controladores e filtros digitais.