Freertos
Resumo dos principais recursos do freertos utilizados no curso de computação embarcada.
Referência extra consultar a API do freeRtos: https://www.freertos.org/a00106.html
Dicas gerais
- Faça o código por partes e DEBUG!! Não tente implementar tudo de uma única vez.
- Leia a documentação!!
- Antes de "sair fazendo" desenho no papel um diagrama do que pretende implementar.
Interrupção
- Toda interrupção de HW deve interagir com as tarefas via um dos recursos do RTOS (semáforo, fila, ...)
- Todo recurso que for usado na interrupção (semáforo, fila) deve ser iniciado antes do uso!
- Para interagir com o freeRTOS da interrupção, você deve usar as funcoes especificas que possuem
FromISR
.
Criando uma tarefa
Método de como indicamos ao freertos de que uma tarefa deve ser criada (alocar memória, alocar no escalonador, ...).
Para criarmos uma tarefa é necessário:
- Criar uma função que será a task
- Essa função não deve retornar
while(1)
- Essa função não deve retornar
- Chamar a função
xTaskCreate
Código exemplo:
static void task_led(void *pvParameters){
for (;;) {
LED_Toggle(LED0);
vTaskDelay(300);
}
}
void main (void){
// ---- hiden code block ---- //
if (xTaskCreate(task_led, "Led", 1024, NULL, 0, NULL) != pdPASS) {
printf("Failed to create test led task\r\n");
}
}
xTaskCreate
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,
const char * const pcName
configSTACK_DEPTH_TYPE usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask
);
- par0: ponteiro para a função que será uma task
- par1: uma string para nomear a task
- par2: tamanho da stack reservado para a task
- par3: se desejar passar alguma informação na criação da task
- par4: prioridade
- par5: um handler da tarefa, normalmente
NULL
vTaskDelay
É uma função de delay do próprio RTOS que faz a tarefa em questão entrar em estado bloqueada
, ou seja, não será chamada pelo escalonador do sistema operacional.
void vTaskDelay( const TickType_t xTicksToDelay );
Esse método possui avantagem de 'não ocupar' processamento do CORE. O tempo especificado para a função é a quantidade em ticks na qual a task ficara bloqueada.
Código exemplo:
void vTaskFunction( void * pvParameters ){
// inicializa LED1
init_led1();
/* Block for 500ms. */
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
for( ;; ){
/* Simply toggle the LED every 500ms, blocking between each toggle. */
vToggleLED();
vTaskDelay( xDelay );
}
}
Tip
const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
converte ticks para ms.
Software Timer
Relógios de software são funções de callbacks
que são executados pelo RTOS, os callbacks
podem ser executados de forma recorrente ou uma única vez (quando o tempo tiver passado).
Para usarmos um timer de software é necessário:
- Criar a variável global que representará o timer
TimerHandle_t xTimer
- Criar a função de
callback
:void vTimerCallback(TimerHandle_t xTimer) { ... }
- Na task, criar o timer:
xTimer = xTimerCreate("Timer", 100, pdTRUE, 0, vTimerCallback);
- E então ativar o timer:
xTimerStart(xTimer, 0);
A função possui os seguintes argumentos: xTimerCreate(pcTimerName, xTimerPeriod, uxAutoReload, pvTimerID, pxCallbackFunction)
.
pcTimerName
: É um nome único para o Timer (string).xTimerPeriod
: É o período em ticks que o timer vai executar. Ex:100
executa a aproximadamente 100msuxAutoReload
:pdTRUE
se o timer for recorrente epdFALSE
se for executar uma única vezpvTimerID
: Não faco ideia.. =)pxCallbackFunction
: Função que será chamada quando o timer estourar.
Código exemplo:
TimerHandle_t xTimer;
void vTimerCallback(TimerHandle_t xTimer) {
printf("estourou \n");
}
static void task_foo(void *pvParameters) {
xTimer = xTimerCreate("Timer", 100, pdTRUE, 0, vTimerCallback);
while(1) {
vtaskDelay(1000);
}
}
Semáforo binário - Semaphore
Semáforos são utilizados para sincronização de tarefas, eles podem ser 'liberados' por outra tarefa ou de uma interrupção. Eles devem ser utilizados como substituto a flag.
- Descrição: https://www.freertos.org/Embedded-RTOS-Binary-Semaphores.html
- API: https://www.freertos.org/a00113.html
Para criarmos e usarmos um semáforo é necessário:
- Criar a variável global que representará o semáforo
SemaphoreHandle_t xSemaphore;
- Criar o semáforo (na função
main
) - Liberar o semáforo
xSemaphoreGiveFromISR(xSemaphore, 0);
(se for liberado de uma ISR)xSemaphoreGive(xSemaphore);
(se for liberado de outra task)
- Esperar pelo semáforo
Código exemplo:
// variável global que é o endereço
// do semáforo
SemaphoreHandle_t xSemaphore;
void but_callback(void){
// libera semáforo
xSemaphoreGiveFromISR(xSemaphore, 0);
}
static void task_led(void *pvParameters){
init_led1(); // inicializa LED1
init_but1(); // inicializa botao 1, com callback
for (;;) {
// aguarda por até 500 ms pelo se for liberado entra no if
if( xSemaphoreTake(xSemaphore, 500 / portTICK_PERIOD_MS) == pdTRUE ){
LED_Toggle(LED0);
}
}
}
void main(void) {
// .... //
// cria semáforo binário
xSemaphore = xSemaphoreCreateBinary();
// verifica se semáforo foi criado corretamente
if (xSemaphore == NULL)
printf("falha em criar o semaforo \n");
Tip
- O semáforo deve ser sempre alocado antes do seu uso, caso alguma parte do firmware tente liberar o semáforo antes dele ser criado (
xSemaphoreCreateBinary()
) o código irá travar. - Você deve usar
fromISR
SEMPRE que liberar um semáforo de uma interrupção, caso contrário usar a funçãoxSemaphoreGive()
Fila - Queue
Fila é um recurso do freertos que permite troca de mensagens (qualquer tipo de dado) entre tarefas e entre IRQ e tarefas.
Para criarmos e usarmos um semáforo é necessário:
- Criar a variável global que representará a fila
QueueHandle_t xQueueButId;
- Criar a fila (na função
main
)xQueueButId = xQueueCreate(32, sizeof(char) );
- Ao criar a fila você deve informar a quantidade de itens (
32
) nessa fila e o tipo dos itens (sizeof(char)
).
- Colocar dados na fila:
xQueueSendFromISR(xQueueButId, &id, 0);
(se for de uma ISR)xQueueSend(xQueueButId, &id);
(se for de uma outra task)
- Receber dados da fila:
xQueueReceive( xQueueButId, &id, ( TickType_t ) 500 )
Código exemplo que: Cria uma fila de chars, e cada botão envia para essa fila uma informação referente ao seu id. Uma task fica lendo essa fila e aciona o LED referente ao ID do botão.
// fila
QueueHandle_t xQueueButId;
void but1_callback(void){
// envia o char `1` na fila
char id = '1';
xQueueSendFromISR(xQueueButId, &id, 0);
}
void but2_callback(void){
// envia o char `2` na fila
char id = '2';
xQueueSendFromISR(xQueueButId, &id, 0);
}
static void task_led(void *pvParameters){
init_led1(); // inicializa LED1
init_but1(); // inicializa botao 1, com callback
init_but2(); // inicializa botao 2, com callback
// variável local para leitura do dado da fila
char id;
for (;;) {
// aguarda por até 500 ms pelo se for liberado entra no if
if( xQueueReceive( xQueueButId, &id, ( TickType_t ) 500 )){
if(id == '1')
pin_toggle(LED1_PIO, LED1_PIO_IDX_MASK);
else if(id == '2')
pin_toggle(LED2_PIO, LED2_PIO_IDX_MASK);
}
}
}
void main(void) {
// .... //
// cria fila de 32 slots de char
xQueueButId = xQueueCreate(32, sizeof(char) );
// verifica se fila foi criada corretamente
if (xQueueButId == NULL)
printf("falha em criar a fila \n");
}