Programando com FreeRTOS pt.1


{getToc} $title={Índice}

Introdução

O presente artigo tem como finalidade apresentar o sistema operacional de tempo real FreeRTOS e algumas de suas funcionalidades, para isso trara exemplos práticos utilizando FreeRTOS no ESP32. 

Um SO nada mais é que um software que gerencia os recursos de Hardware e do Firmware de um sistema computacional, para microcontroladores são utilizados os sistemas operacionais RTOS (sistema operacional em tempo real), onde se diferem dos SO no tempo de resposta a eventos externos.

O Sistema operacional de código aberto FreeRTOS foi desenvolvido para ser integrado a microcontroladores, pois facilita o desenvolvimento de aplicações com multitarefas, onde o próprio faz toda a gerencia do ciclo de vida da tarefas (time slicing, prioridade, todo o escalonamento dos processos com algorítimos preemptivos).

Porque utilizar o FreeRTOS?

Alguns dos benefícios de utilizar o FreeRTOS em seus projetos:

  • Simplicidade na programação.
  • Kernel é bem desenvolvido.
  • Tamanho pequeno, pode ser embarcado em microcontroladores com pouca memoria.
  • Comunidade e documentação muito boa.
  • Universal, roda em praticamente qualquer MCU.
  • Código aberto.
  • Lançamentos do suporte de longo prazo (LTS).
FreeRTOS Conceitos
FreeRTOS Conceitos.


Exemplos de implementação no esp32

Para os exemplos de demonstração foi utilizado o microcontrolador esp32, com a versão de IDF v4.4.2.

Task FreeRTOS

Para criarmos tarefas no FreeRTOS utilizamos como exemplo o seguinte código:

```c
#include <stdlib.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"

#define TASK_1_NAME             "task 1"
#define TASK_1_STACK_SIZE       (configMINIMAL_STACK_SIZE * 4)
#define TASK_1_PRIORITY         (tskIDLE_PRIORITY  + 1)

#define TASK_2_NAME             "task 2"
#define TASK_2_STACK_SIZE       (configMINIMAL_STACK_SIZE * 4)
#define TASK_2_PRIORITY         (tskIDLE_PRIORITY + 2) 

//declaração das funções
static void task_1(void* arg);
static void task_2(void* arg);

//declaração dos handles
static TaskHandle_t xtask_handle_1 = NULL;
static TaskHandle_t xtask_handle_2 = NULL;

void app_main(void)
{
    BaseType_t xReturn = pdPASS;

    xReturn = xTaskCreate(&task_1,
                            TASK_1_NAME,
                            TASK_1_STACK_SIZE,
                            NULL,
                            TASK_1_PRIORITY,
                            &xtask_handle_1);

    if(xReturn != pdPASS || xtask_handle_1 == NULL)
    {
       printf("Erro ao criar a task 1\n");
    }

    xReturn = xTaskCreate(&task_2,
                            TASK_2_NAME,
                            TASK_2_STACK_SIZE,
                            NULL,
                            TASK_2_PRIORITY,
                            &xtask_handle_2);

    if(xReturn != pdPASS || xtask_handle_2 == NULL)
    {
       printf("Erro ao criar a task 2\n");
    }

    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(10000));
    }
}

static void task_1(void* arg)
{
    while(true)
    {
        ESP_LOGW("main", "%s tick :  %d", __func__, xTaskGetTickCount());
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

static void task_2(void* arg)
{
    for(;;)
    {
        ESP_LOGI("main", "%s tick :  %d", __func__, xTaskGetTickCount());
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}
```
Para facilitar a explicação utilizaremos o número da linha como referência :

  • 3-4 : Inclusão das libs do FreeRTOS.
  • 09 : Define o apelido dado a task criada.
  • 10 : Define o tamanho de memoria alocado para a tarefa.
  • 11 : Cada tarefa recebe uma prioridade de 0 a ( configMAX_PRIORITIES - 1 ), onde configMAX_PRIORITIES é definido dentro do FreeRTOSConfig.h.
  • 20 : Declaração da tarefa, tem que ser do tipo void e com um ponteiro de void de argumento.
  • 26 : Declaração do handle (identificador da tarefa).
  • 31 : Declaração da variável para receber o retorno da função de criação de tarefas.
  • 34 : Função de criação da tarefa
  • 69 : Implementação da tarefa.(contem um loop infinito e não pode ter retorno).
  • 74 : Tempo de pausa da tarefa, onde é passado uma macro de conversão (mili para tick), nas duas tarefas esse tempo é de 500 milisegundos.

xTaskCreate

Como apresentado no exemplo de implementação, foi utilizada a função de criação de tasks, xTaskCreate, a seguir sua implementação na IDF.

```c
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

    static inline IRAM_ATTR BaseType_t xTaskCreate(
                            TaskFunction_t pvTaskCode,
                            const char * const pcName,     /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                            const uint32_t usStackDepth,
                            void * const pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t * const pxCreatedTask) PRIVILEGED_FUNCTION
    {
        return xTaskCreatePinnedToCore( pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, tskNO_AFFINITY );
    }

#endif
```

Notamos que ela propaga o método "xTaskCreatePinnedToCore", onde nele é possível especificar o core onde a task será executada, por padrão o xTaskCreate utiliza o argumento "tskNO_AFFINITY", que permite que a tarefa seja executada em qualquer core livre.

No exemplo de implementação as linhas 37 e 51 recebem o valor NULL como argumento de pvParameters, porém pode-se passar qualquer ponteiro na criação, esse será recebido em *arg, na implementação da tarefa.

Saída do exemplo

Ao compilar e gravar o exemplo obtemos a seguinte saída:

```terminal
<pre><font color="#4E9A06">I (311) cpu_start: Starting scheduler on PRO CPU.</font>
<font color="#4E9A06">I (0) cpu_start: Starting scheduler on APP CPU.</font>
<font color="#C4A000">W (0) main: task_1 tick :  0</font>
<font color="#4E9A06">I (0) main: task_2 tick :  0</font>
<font color="#4E9A06">I (1336) main: task_2 tick :  51</font>
<font color="#C4A000">W (1336) main: task_1 tick :  51</font>
<font color="#4E9A06">I (1836) main: task_2 tick :  101</font>
<font color="#C4A000">W (1836) main: task_1 tick :  101</font>
<font color="#4E9A06">I (2336) main: task_2 tick :  151</font>
<font color="#C4A000">W (2336) main: task_1 tick :  151</font>
<font color="#4E9A06">I (2836) main: task_2 tick :  201</font>
<font color="#C4A000">W (2836) main: task_1 tick :  201</font>
<font color="#4E9A06">I (3336) main: task_2 tick :  251</font>
<font color="#C4A000">W (3336) main: task_1 tick :  251</font>
<font color="#4E9A06">I (3836) main: task_2 tick :  301</font>
<font color="#C4A000">W (3836) main: task_1 tick :  301</font>
<font color="#4E9A06">I (4336) main: task_2 tick :  351</font>
<font color="#C4A000">W (4336) main: task_1 tick :  351</font>
<font color="#4E9A06">I (4836) main: task_2 tick :  401</font>
<font color="#C4A000">W (4836) main: task_1 tick :  401</font>
<font color="#4E9A06">I (5336) main: task_2 tick :  451</font>
<font color="#C4A000">W (5336) main: task_1 tick :  451</font></pre>
```

Note que:

  • A primeira tarefa a ser executada é a task_1, pois foi criada primeiro.
  • Na segunda interação a ordem de execução é alterada, pois a prioridade da task_2 é maior.
  • Ambas tem no print o mesmo valor de tick, isso porque o esp32 possui dois núcleos e como foi utilizado o argumento tskNO_AFFINITY como padrão cada uma foi executada em um core paralelamente. 

Caso seja um iniciante em RTOS é indicado que leia a documentação completa contida na guia Referências. Pratique e mude os argumentos, troque as prioridades, mude o tempo de delay e faça uma análise das mudanças na execução. 

 

Referências



Bruno Lima

Engenheiro de computação atuando em desenvolvimento de sistemas embarcados (Firmware) com microcontroladores e processadores (Linux Embarcado). Contribuidor de projetos públicos e fóruns de c/c++. linkedin github

Postar um comentário

Deixe seu comentário ou sua sugestão!

Postagem Anterior Próxima Postagem

Formulário de contato