• Util

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

  1. Faça o código por partes e DEBUG!! Não tente implementar tudo de uma única vez.
  2. Leia a documentação!!
  3. Antes de "sair fazendo" desenho no papel um diagrama do que pretende implementar.

Interrupção

  1. Toda interrupção de HW deve interagir com as tarefas via um dos recursos do RTOS (semáforo, fila, ...)
  2. Todo recurso que for usado na interrupção (semáforo, fila) deve ser iniciado antes do uso!
  3. 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:

  1. Criar uma função que será a task
    • Essa função não deve retornar while(1)
  2. 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:

  1. Criar a variável global que representará o timer
    • TimerHandle_t xTimer
  2. Criar a função de callback: void vTimerCallback(TimerHandle_t xTimer) { ... }
  3. Na task, criar o timer:
    • xTimer = xTimerCreate("Timer", 100, pdTRUE, 0, vTimerCallback);
  4. 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 100ms
  • uxAutoReload: pdTRUE se o timer for recorrente e pdFALSE se for executar uma única vez
  • pvTimerID: 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.

Para criarmos e usarmos um semáforo é necessário:

  1. Criar a variável global que representará o semáforo
    • SemaphoreHandle_t xSemaphore;
  2. Criar o semáforo (na função main)
  3. Liberar o semáforo
  4. 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

  1. 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.
  2. Você deve usar fromISR SEMPRE que liberar um semáforo de uma interrupção, caso contrário usar a função xSemaphoreGive()

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:

  1. Criar a variável global que representará a fila
    • QueueHandle_t xQueueButId;
  2. 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)).
  3. Colocar dados na fila:
    • xQueueSendFromISR(xQueueButId, &id, 0); (se for de uma ISR)
    • xQueueSend(xQueueButId, &id); (se for de uma outra task)
  4. 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");
}