Olá caro leitor, tudo bem? Neste artigo gostaria de compartilhar o resultado dos meus estudos, relacionado ao desenvolvimento de kernel. A inspiração para o estudo surgiu após ver o conteúdo da palestra do professor Rodrigo Almeida fez para a ESC Embedded Systems Conference 2017 Silicon Valley e pelo Felipe Neves, que vem desenvolvendo o seu próprio sistema operacional de tempo real (RTOS), o uLipeRTOS.
Para os meus estudos utilizei o material que o professor Rodrigo Almeida disponibilizada aqui no Embarcados e na sua página pessoal. Outra fonte que consultei foi o livro The Definitive Guide to the ARM Cortex-M0 do autor Joseph Yiu, além de estudar o código fonte de diferentes RTOS’s.
Para este artigo vou apresentar um kernel multi-tarefas cooperativo, bem simples, com níveis de prioridade na execução das tarefas. As principais características de um sistema com núcleo cooperativo são:
- o código fonte pode ser todo escrito em linguagem C, não necessitando de nenhuma característica especial do hardware;
- necessita que todas tarefas/processos terminem, para possibilitar que outra tarefa possa ser executada;
- rotina com loop infinito podem travar o sistema (para o kernel aqui apresentado, loop infinito vai travar a aplicação em um determinado processo).
Para mais informações sobre kernel e sistemas operacionais recomendo a leitura dos seguintes artigos:
- Desenvolvendo um RTOS: Introdução;
- Desenvolvendo um RTOS: processos e tarefas;
- Desenvolvendo um RTOS: Utilizando buffer circular como gestor de processos.
Justificativa
Para quem já está familiarizado com o desenvolvimento de firmware com RTOS, sabe como é cômodo estruturar a sua aplicação, dividindo em diversas tarefas, deixando a cargo do RTOS o escalonamento delas.
Como é de conhecimento de muitos desenvolvedores, existem uma quantidade enorme de aplicações que não necessitam das características de um sistema de tempo real. E os RTOS’s são aplicados mesmo assim, só pelas facilidades em estruturar o firmware.
O ponto negativo dessa abordagem é que os RTOS’s sempre ocupam uma boa fatia de memória RAM e memória ROM, e a maiorias dos microcontroladores possui uma quantidade bem reduzida desses recursos. Com a proposta do kernel aqui apresentado, é possível ter as mesmas características do escalonador existente em um RTOS, assim facilitando a estruturação do firmware.
O kernel
O kernel desenvolvido conta com escalonador de tarefas e diversas APIs (funções) para manipular os processos. Então, basicamente o kernel se resume em apenas no escalonador de tarefas.
Projeto
O projeto de demonstração utiliza a Freedom Board KE06Z, que possui um microcontrolador com núcleo ARM Cortex-M0. E uma característica interessante dessa arquitetura é o temporizador “SysTick“. Trata-se de uma interrupção periódica, própria do núcleo. É através dela que o kernel obtém a sua base de tempo.
Nota: Para arquiteturas diferentes, é necessário reservar um “Timer”, para fornecer a base de tempo para o kernel.
Basicamente o projeto conta com vetor, de estrutura previamente definida, onde são inseridas as tarefas e suas configurações. E num segundo momento o escalonador assume o controle e passa a gerenciar este vetor para decidir qual tarefa deve ser executada.
A seguir é apresentado o código fonte do kernel desenvolvido, para a melhor compreensão do projeto (kernel.c e kernel.h).
kernel.c
/***********************************************************************************************
* kernel.c *
* *
* Created on: 02/01/2018 *
* Author: Evandro Teixeira *
**********************************************************************************************/
#include "kernel.h"
/***********************************************************************************************
* *
**********************************************************************************************/
//static strFlag vFlag;
static strKernel vKernel;
static kernel_tick ticks = 0;
extern uint32_t SystemCoreClock;
static kernel_tick kernel_ticks_get(void) ;
static void kernel_setup_systick(void);
static void kernel_task_idle(void);
static void (*IdleTask)(void);
/***********************************************************************************************
* *
**********************************************************************************************/
void SysTick_Handler(void)
{
ticks++;
}
/***********************************************************************************************
* *
**********************************************************************************************/
static kernel_tick kernel_ticks_get(void)
{
return ticks;
}
/***********************************************************************************************
* *
**********************************************************************************************/
static void kernel_setup_systick(void)
{
uint32_t ticks = SystemCoreClock/1000;
SysTick->LOAD = ticks - 1;
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
NVIC_EnableIRQ(SysTick_IRQn);
}
/***********************************************************************************************
* *
**********************************************************************************************/
kernel_status kernel_init(void)
{
strTask task_ilde = {kernel_task_idle,Task_Ready,Priority_Idle,0,kernel_task_waiting};
vKernel.task_vetor[ID_IDLE] = task_ilde;
vKernel.position_index = 0;
vKernel.counter_task = 0;
vKernel.index_high = 1;
vKernel.index_medium = 1;
vKernel.index_low = 1;
kernel_setup_systick();
return kernel_ok;
}
/***********************************************************************************************
* *
**********************************************************************************************/
kernel_status kernel_add_task(ptrTask task,task_priority priority,task_state state, id_Task *id)
{
if((vKernel.counter_task < NUMBER_TASK) && (task != NULL))
{
// Nota: task idle na posicao zero vetor
vKernel.counter_task++;
vKernel.task_vetor[vKernel.counter_task].task = task;
vKernel.task_vetor[vKernel.counter_task].priority = priority;
vKernel.task_vetor[vKernel.counter_task].state = state;
vKernel.task_vetor[vKernel.counter_task].pausedtime = 0;
vKernel.task_vetor[vKernel.counter_task].kernel_task_state = kernel_task_waiting;
*id = vKernel.counter_task;
return kernel_ok;
}
else return kernel_fail;
}
/***********************************************************************************************
* *
**********************************************************************************************/
void kernel_run(void)
{
uint8_t i = 0;
while(1)
{
//
// Checa se tem alguma em pausa
//
for(i=1;i<(vKernel.counter_task + 1);i++)
{
if(vKernel.task_vetor[i].state == Task_Paused)
{
if(kernel_ticks_get() > vKernel.task_vetor[i].pausedtime)
{
vKernel.task_vetor[i].state = Task_Ready;
vKernel.task_vetor[i].pausedtime = 0;
}
}
}
//
// busca tarefa de alta prioridade pronta para ser executada
//
for(i=vKernel.index_high;i<=(vKernel.counter_task + 1);i++)
{
if( (vKernel.task_vetor[i].priority == Priority_High) &&
(vKernel.task_vetor[i].state == Task_Ready) &&
(vKernel.task_vetor[i].kernel_task_state == kernel_task_waiting))
{
vKernel.index_high = i;
vKernel.index_high++;
if(vKernel.index_high>=(vKernel.counter_task + 1))
vKernel.index_high = 1;
break;
}
}
if(i > (vKernel.counter_task + 1))
{
// busca tarefa de media prioridade para ser executada
for(i=vKernel.index_medium;i<=(vKernel.counter_task + 1);i++)
{
if( (vKernel.task_vetor[i].priority == Priority_Medium) &&
(vKernel.task_vetor[i].state == Task_Ready) &&
(vKernel.task_vetor[i].kernel_task_state == kernel_task_waiting))
{
vKernel.index_medium = i;
vKernel.index_medium++;
if(vKernel.index_medium>=(vKernel.counter_task + 1))
vKernel.index_medium = 1;
break;
}
}
if(i > (vKernel.counter_task + 1))
{
// busca tarefa de baixa prioridade para ser executada
for(i=vKernel.index_low;i<=(vKernel.counter_task + 1);i++)
{
if( (vKernel.task_vetor[i].priority == Priority_Low) &&
(vKernel.task_vetor[i].state == Task_Ready) &&
(vKernel.task_vetor[i].kernel_task_state == kernel_task_waiting))
{
vKernel.index_low = i;
vKernel.index_low++;
if(vKernel.index_low>=(vKernel.counter_task + 1))
vKernel.index_low = 1;
break;
}
}
if(i > (vKernel.counter_task + 1))
{
vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
vKernel.position_index = ID_IDLE; // Executa tarefa ilde
}
else
{
vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
vKernel.position_index = i;
}
}
else
{
vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
vKernel.position_index = i;
}
}
else
{
vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_waiting;
vKernel.position_index = i;
}
// muda estado da tarefa
vKernel.task_vetor[vKernel.position_index].kernel_task_state = kernel_task_running;
// executa tarefa
(*vKernel.task_vetor[vKernel.position_index].task)();
}
}
/***********************************************************************************************
* *
**********************************************************************************************/
static void kernel_task_idle(void)
{
IdleTask();
}
/***********************************************************************************************
* *
**********************************************************************************************/
void kernel_task_delay(kernel_tick time)
{
time += kernel_ticks_get();
vKernel.task_vetor[vKernel.position_index].pausedtime = time;
vKernel.task_vetor[vKernel.position_index].state = Task_Paused;
}
/***********************************************************************************************
* *
**********************************************************************************************/
void kernel_task_set_priority(task_priority priority)
{
if((priority > ID_IDLE) && (priority <= Priority_High))
vKernel.task_vetor[vKernel.position_index].priority = priority;
}
/***********************************************************************************************
* *
**********************************************************************************************/
kernel_status kernel_add_task_idle(void (*task)(void))
{
if(task != NULL)
{
IdleTask = task;
return kernel_ok;
}
else return kernel_fail;
}
/***********************************************************************************************
* *
**********************************************************************************************/
void kernel_task_delete(void)
{
vKernel.task_vetor[vKernel.position_index].state = Task_Deleted;
}
/***********************************************************************************************
* *
**********************************************************************************************/
kernel_status kernel_task_blocks(id_Task id)
{
if(id < vKernel.counter_task)
{
vKernel.task_vetor[id].state = Task_Blocked;
return kernel_ok;
}
else return kernel_fail;
}
/***********************************************************************************************
* *
**********************************************************************************************/
kernel_status kernel_task_unlock(id_Task id)
{
if(id < vKernel.counter_task)
{
vKernel.task_vetor[id].state = Task_Ready;
return kernel_ok;
}
else return kernel_fail;
}
/***********************************************************************************************
* *
**********************************************************************************************/
void kernel_error(void)
{
while(1)
{
__asm ("NOP");
}
}
/***********************************************************************************************
* *
**********************************************************************************************/
kernel.h
/***********************************************************************************************
* kernel.h *
* *
* Created on: 02/01/2018 *
* Author: Evandro Teixeira *
**********************************************************************************************/
#ifndef SOURCES_KERNEL_KERNEL_H_
#define SOURCES_KERNEL_KERNEL_H_
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include "extern.h"
#define NUMBER_TASK 8
#define ID_IDLE 0
#ifndef kernel_tick
typedef uint32_t kernel_tick;
#endif
#ifndef id_Tack
typedef uint8_t id_Task;
#endif
#ifndef id_Flag
typedef uint16_t id_Flag;
#endif
#ifndef ptrTask
typedef void(*ptrTask)(void);
#endif
typedef enum
{
Flag_False = false,
Flag_True = true,
}Kernel_Flag;
typedef enum
{
kernel_fail = false,
kernel_ok = true,
}kernel_status;
typedef enum
{
kernel_task_running = true,
kernel_task_waiting = false,
}kernel_task;
typedef enum
{
//Task_Running = 0,//
Task_Ready = 0,
Task_Blocked,
Task_Paused,
Task_Deleted,
}task_state;
typedef enum
{
Priority_Idle = 0,
Priority_Low,
Priority_Medium,
Priority_High,
}task_priority;
typedef struct
{
ptrTask task;
uint8_t state;
uint8_t priority;
kernel_tick pausedtime;
kernel_task kernel_task_state;
}strTask;
typedef struct
{
strTask task_vetor[NUMBER_TASK];
uint8_t position_index;
uint8_t counter_task;
uint8_t index_high;
uint8_t index_medium;
uint8_t index_low;
}strKernel;
typedef struct
{
id_Flag flag;
id_Flag flag_index;
id_Task number_task;
bool flag_status;
}strFlag;
kernel_status kernel_init(void);
kernel_status kernel_add_task(ptrTask task,task_priority priority,task_state state, id_Task *id);
void kernel_task_delay(kernel_tick time);
void kernel_task_set_priority(task_priority priority);
void kernel_run(void);
kernel_status kernel_add_task_idle(void (*task)(void));
void kernel_task_delete(void);
kernel_status kernel_task_blocks(id_Task id);
kernel_status kernel_task_unlock(id_Task id);
void kernel_error(void);
#endif /* SOURCES_KERNEL_KERNEL_H_ */
Tarefas
As tarefas no kernel possuem 4 estados, que são:
- pronta (Task_Ready);
- em pausa (Task_Paused);
- bloqueada (Task_Blocked);
- deletada (Task_Deleted).
As tarefas também são agrupadas em prioridades. O núcleo do sistema disponibiliza 3 níveis de prioridades: baixa (Priority_Low), média (Priority_Medium) e alta (Priority_High). Além das prioridades citadas, o kernel possui a prioridade dedicada à tarefa ociosa (Task_Idle com prioridade Priority_Idle). Trata-se da tarefa que é executada quando não houver nenhuma tarefa no estado pronta. A mesma pode e deve ser utilizada pela aplicação, sendo que o kernel não executa nenhum processo nesta tarefa.
O kernel, entre aspa, acaba criando listas de tarefas para cada nível de prioridade. E assim, o escalonador de tarefas seleciona qual tarefa deve ser executada pela CPU. Outra característica do núcleo é, o escalonamento entras as tarefas de mesma prioridade, que trabalham no modo FIFO (First In, First Out – quer dizer que a primeira tarefa da fila será a primeira a ser executada). A imagem a seguir demonstra com maiores detalhes o agrupamento de tarefas feito pelo o escalonador.
API’s do Sistema
Principais Funções:
Como dito o kernel possui algumas API’s para manipular os processos. Como não poderia faltar, tem a API “kernel_init”, é a primeira função do sistema que deve ser executada. É nesta função que serão inicializadas as variáveis de controle do sistema.
A segunda função a ser executada é a “kernel_add_task”, através da qual são adicionadas as tarefas da aplicação ao kernel. E, além de inserir as tarefas ao sistema, é por ela que são configurados os parâmetros das tarefas (prioridade e estado inicial).
E por fim temos a API “kernel_run”, onde se encontra o escalonador das tarefas do sistema.
Funções Adicionais:
Como dito, o kernel aqui apresentado é cooperativo, e é necessário que cada processo termine para que outra tarefa seja executada. O mesmo provê algumas API’s para manipular os parâmetros das tarefas. Assim possibilitando que as tarefas possam ser; pausadas, bloqueadas, desbloqueadas e deletadas.
A primeira API é “kernel_task_delay”, através da qual é possível pausar a execução da tarefa por um determinado tempo. Ao final desse período, o kernel se encarrega de alterar o seu estado, para o status pronta (Task_Ready).
Outra API para manipular o estado da tarefa é a função “kernel_task_delete”, que faz com que a tarefa não seja mais executada pelo kernel.
Nota: Quando é executada essa API, o kernel não exclui a tarefa do vetor. Portanto, não libera espaço em memória.
A funções “kernel_task_blocks” e “kernel_task_unlock” servem para bloquear e desbloquear a execução da tarefa pelo sistema.
O kernel também possui API para a troca de prioridade das tarefas, a função “kernel_task_set_priority”.
E como foi dito anteriormente, o kernel possui a tarefa ociosa (task_Idle), one o usuário poder estar adicionando um processo. Isso é feito através da função “kernel_add_task_idle”.
Projeto de Demonstração
O Projeto que escolhi para demonstração, é bem simples. O algoritmo consiste em verificar os Push Buttons (SW2, SW3) e acionar o LED RGB que estão presente na Freedom Board KE06Z.
A seguir é apresentado o código fonte da aplicação (main.c, app.c e app.h).
main.c
/**********************************************************************************************
* main.c *
* *
* Created on: 02/01/2018 *
* Author: Evandro Teixeira *
**********************************************************************************************/
#include "MKE06Z4.h"
#include "extern.h"
extern id_Task id_task_led_red;
extern id_Task id_task_led_blue;
extern id_Task id_task_sw2;
extern id_Task id_task_sw3;
int main(void)
{
app_init();
printf("\n\rAplicacao de Demonstracao do Kernel");
if(kernel_init() == kernel_fail )
{
printf("\n\rFalha em iniciar o kernel");
kernel_error();
}
if(kernel_add_task(app_task_led_red,Priority_Low,Task_Ready,&id_task_led_red) == kernel_fail)
{
printf("\n\rFalha em add tarefa ao kernel");
kernel_error();
}
if(kernel_add_task(app_task_led_blue,Priority_Low,Task_Ready,&id_task_led_blue) == kernel_fail)
{
printf("\n\rFalha em add tarefa ao kernel");
kernel_error();
}
if(kernel_add_task(app_task_sw2,Priority_High,Task_Ready,&id_task_sw2) == kernel_fail)
{
printf("\n\rFalha em add tarefa ao kernel");
kernel_error();
}
if(kernel_add_task(app_task_sw3,Priority_High,Task_Ready,&id_task_sw3) == kernel_fail)
{
printf("\n\rFalha em add tarefa ao kernel");
kernel_error();
}
if(kernel_add_task_idle(app_task_led_green) == kernel_fail)
{
printf("\n\rFalha em add tarefa ao kernel");
kernel_error();
}
kernel_run();
for (;;)
{
printf("\n\rFalha em iniciar o kernel");
kernel_error();
}
/* Never leave main */
return 0;
}
app.c
/************************************************************************************
* app.c *
* *
* Created on: 02/01/2018 *
* Author: Evandro Teixeira *
************************************************************************************/
#include "app.h"
/************************************************************************************
* *
************************************************************************************/
id_Task id_task_led_red;
id_Task id_task_led_blue;
id_Task id_task_led_green;
id_Task id_task_sw2;
id_Task id_task_sw3;
extern uint32_t SystemCoreClock ;
/************************************************************************************
* *
************************************************************************************/
void app_init(void)
{
UART_ConfigType sConfig;
SystemCoreClockUpdate();
sConfig.u32SysClkHz = SystemCoreClock;
sConfig.u32Baudrate = 9600;
UART_Init(UART1,&sConfig);
GPIO_PinInit(BUTTOM_SW2);
GPIO_PinInit(BUTTOM_SW3);
RED_Init();
BLUE_Init();
GREEN_Init();
}
/************************************************************************************
* *
************************************************************************************/
void app_task_led_red(void)
{
RED_Toggle();
kernel_task_blocks(id_task_led_red);
}
/************************************************************************************
* *
************************************************************************************/
void app_task_led_blue(void)
{
BLUE_Toggle();
kernel_task_blocks(id_task_led_blue);
}
/************************************************************************************
* *
************************************************************************************/
void app_task_led_green(void)
{
GREEN_Toggle();
}
/************************************************************************************
* *
************************************************************************************/
void app_task_sw2(void)
{
if( (GPIO_Read(BUTTOM) & READ_BUTTOM_SW2) == 0)
kernel_task_unlock(id_task_led_red);
else
RED_Set();
kernel_task_delay(100);
}
/************************************************************************************
* *
************************************************************************************/
void app_task_sw3(void)
{
if( (GPIO_Read(BUTTOM) & READ_BUTTOM_SW3) == 0)
kernel_task_unlock(id_task_led_blue);
else
BLUE_Set();
kernel_task_delay(100);
}
/************************************************************************************
* *
************************************************************************************/
app.h
/************************************************************************************ * app.h * * Created on: 02/01/2018 * Author: Evandro Teixeira *************************************************************************************/ #ifndef SOURCES_APP_APP_H_ #define SOURCES_APP_APP_H_ #include "extern.h" #define BUTTOM_SW3 GPIO_PTH3,GPIO_PinInput #define BUTTOM_SW2 GPIO_PTH4,GPIO_PinInput #define READ_BUTTOM_SW2 GPIO_PTH4_MASK #define READ_BUTTOM_SW3 GPIO_PTH3_MASK #define BUTTOM GPIOB void app_init(void); void app_task_led_red(void); void app_task_led_blue(void); void app_task_led_green(void); void app_task_sw2(void); void app_task_sw3(void); #endif /* SOURCES_APP_APP_H_ */
Resultado
O resultado da aplicação desenvolvida é: temos o LED verde piscando em alta frequência, pois o sistema gasta pouco tempo, para verificar os Push Buttons e a CPU do microcontrolador passa grande parte do tempo executando a tarefa ociosa (IdleTask). Então temos a sensação que o LED verde está o tempo todo acionado.
Quando pressionado o Push Button SW2, o LED vermelho começa a piscar. O mesmo vale para o Push Button SW3, que faz com que o LED azul pisque. A seguir temos figura 2, demonstrando o resultado da aplicação.

Conclusão
O kernel aqui apresentado é fruto dos meus estudos sobre o assunto, não tem como objetivo se tornar uma alternativa para substituir sistemas operacionais já existentes no mercado. Mas sim, demonstrar ao caro leitor, que é possível desenvolver soluções simples para estruturar firmware, não se limitando apenas ao uso de um RTOS ou do Super Loop.
Próximos passos… É estudar sobre a troca de contexto e as técnicas existentes para o mesmo. E assim conseguir desenvolver kernel preemptivo. Assim que for evoluindo nos meus estudos, prometo trazer mais artigos relacionados ao assunto.
O código fonte do projeto com aplicação e kernel deixei disponível no meu Github. E fica aqui o meu convite a você caro leitor, que se interessou pelo assunto, a contribuir com o projeto, testando e aperfeiçoando o mesmo.
Referências
The Definitive Guide to the ARM Cortex-M0
Desenvolvendo um RTOS: Introdução
Desenvolvendo um RTOS: processos e tarefas
Desenvolvendo um RTOS: Utilizando buffer circular como gestor de processos









