7. FreeRTOS: Instalação e Programa Hello World

No limite, o TaskSwitcher desenvolvido na seção anterior pode ser considerado um sistema operacional extremamente simples. Afinal, o escalonamento de tarefas é um dos componentes importantes de um sistema operacional. No caso de sistemas operacionais de tempo real (RTOS), o algoritmo de escalonamento é o principal, uma vez que o seu objetivo primordial é garantir os requisitos temporais na execução das tarefas.

Comparado com o TaskSwitcher, no entanto, os RTOSes possuem algoritmos muito mais complexos, além de outras funcionalidades. Nesta disciplina, utilizaremos o FreeRTOS, que possui código aberto e é uma alternativa bastante popular.

7.1 Instalação do FreeRTOS

No Arduino, a instalação do FreeRTOS é muito fácil, podendo ser obtido diretamente no repositório de biblioteca.

Infelizmente, o Tinkercad suporta apenas uma quantidade limitada de bibliotecas, o que não inclui o FreeRTOS. Desse modo, para realizarmos o estudo do FreeRTOS, vamos utilizar o port do FreeRTOS para desktop (Windows, Linux e MacOS). Nesta apostila, focaremos na versão Windows com MinGW, mas o procedimento para os outros SOs não é muito diferente.

Sendo assim, antes de iniciar a instalação, verifique se o MinGW está instalado. Caso não esteja, siga os passos da apostila de C. Outra opção para executar os códigos deste e dos próximos capítulos é rodar no Google Colab a partir do notebook disponibilizado na página da disciplina. Note que o processo de instalação no Linux/MacOS é semelhante ao empregado no Google Colab.

7.2 Obtendo o código fonte

O modo padrão de utilização do FreeRTOS é incluir os arquivos fonte (.c e .h) diretamente no projeto da nossa aplicação. Então, o primeiro passo consiste em obter o código fonte do FreeRTOS, que é disponibilizado no repositório:

https://github.com/FreeRTOS/FreeRTOS

Para não baixar todos os submódulos (o que implicaria em quase 1GB de código), vamos clonar apenas o kernel, que basicamente é o programa núcleo de um sistema operacional:

$ git clone https://github.com/FreeRTOS/FreeRTOS-Kernel.git

7.3 Criando um projeto novo: copiando os arquivos do código fonte

Crie uma pasta nova para o seu projeto novo, que pode chamar freertos-tutorial com o comando

$ mkdir freertos-tutorial

Agora vamos copiar os arquivos .c necessários com o comando:

$ cp ./FreeRTOS-Kernel/tasks.c ./freertos-tutorial
$ cp ./FreeRTOS-Kernel/queue.c ./freertos-tutorial
$ cp ./FreeRTOS-Kernel/list.c ./freertos-tutorial
$ cp ./FreeRTOS-Kernel/timers.c ./freertos-tutorial
$ cp ./FreeRTOS-Kernel/event_groups.c ./freertos-tutorial
$ cp ./FreeRTOS-Kernel/portable/MSVC-MingW/*.c ./freertos-tutorial
$ cp ./FreeRTOS-Kernel/portable/MemMang/heap_4.c ./freertos-tutorial

Agora vamos copiar os headers:

$ mkdir freertos-tutorial/include
$ cp ./FreeRTOS-Kernel/include/*.* ./freertos-tutorial/include
$ cp ./FreeRTOS-Kernel/portable/MSVC-MingW/*.h ./freertos-tutorial

Se preferir, pode copiar manualmente os arquivos ao invés de utilizar os comandos acima. No final do processo, a sua estrutura de diretórios deve ser:

📦freertos-tutorial
 ┣ 📂include
 ┃ ┣ 📜atomic.h
 ┃ ┣ 📜croutine.h
 ┃ ┣ 📜deprecated_definitions.h
 ┃ ┣ 📜event_groups.h
 ┃ ┣ 📜FreeRTOS.h
 ┃ ┣ 📜list.h
 ┃ ┣ 📜message_buffer.h
 ┃ ┣ 📜mpu_prototypes.h
 ┃ ┣ 📜mpu_wrappers.h
 ┃ ┣ 📜portable.h
 ┃ ┣ 📜projdefs.h
 ┃ ┣ 📜queue.h
 ┃ ┣ 📜semphr.h
 ┃ ┣ 📜StackMacros.h
 ┃ ┣ 📜stack_macros.h
 ┃ ┣ 📜stdint.readme
 ┃ ┣ 📜stream_buffer.h
 ┃ ┣ 📜task.h
 ┃ ┗ 📜timers.h
 ┣ 📜event_groups.c
 ┣ 📜heap_4.c
 ┣ 📜list.c
 ┣ 📜port.c
 ┣ 📜portmacro.h
 ┣ 📜queue.c
 ┣ 📜tasks.c
 ┗ 📜timers.c

O procedimento acima expande as instruções dadas no tutorial do FreeRTOS disponível no site oficial em

https://www.freertos.org/Documentation/RTOS_book.html

7.4 Criando um projeto novo: criando o arquivo de configuração FreeRTOSConfig.h

O arquivo FreeRTOSConfig.h permite personalizar o FreeRTOS para sua aplicação, por meio de várias configurações, incluindo as do escalonador. Para os exercícios desta apostila, uma configuração padrão pode ser utilizada.

Crie o arquivo freertos-tutorial/FreeRTOSConfig.h com o seguinte conteúdo:

/*
 * FreeRTOS V202104.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.  See
* http://www.freertos.org/a00110.html
*----------------------------------------------------------*/

#define configUSE_PREEMPTION                       1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION    0
#define configUSE_IDLE_HOOK                        0
#define configUSE_TICK_HOOK                        0
#define configUSE_DAEMON_TASK_STARTUP_HOOK         0
#define configTICK_RATE_HZ                         ( 1000 )                  /* In this non-real time simulated environment the tick frequency has to be at least a multiple of the Win32 tick frequency, and therefore very slow. */
#define configMINIMAL_STACK_SIZE                   ( ( unsigned short ) 70 ) /* In this simulated case, the stack only has to hold one small structure as the real stack is part of the win32 thread. */
#define configTOTAL_HEAP_SIZE                      ( ( size_t ) ( 65 * 1024 ) )
#define configMAX_TASK_NAME_LEN                    ( 12 )
#define configUSE_TRACE_FACILITY                   1
#define configUSE_16_BIT_TICKS                     0
#define configIDLE_SHOULD_YIELD                    1
#define configUSE_MUTEXES                          1
#define configCHECK_FOR_STACK_OVERFLOW             0
#define configUSE_RECURSIVE_MUTEXES                1
#define configQUEUE_REGISTRY_SIZE                  20
#define configUSE_APPLICATION_TASK_TAG             1
#define configUSE_COUNTING_SEMAPHORES              1
#define configUSE_ALTERNATIVE_API                  0
#define configUSE_QUEUE_SETS                       1
#define configUSE_TASK_NOTIFICATIONS               1
#define configSUPPORT_STATIC_ALLOCATION            0

/* Software timer related configuration options.  The maximum possible task
 * priority is configMAX_PRIORITIES - 1.  The priority of the timer task is
 * deliberately set higher to ensure it is correctly capped back to
 * configMAX_PRIORITIES - 1. */
#define configUSE_TIMERS                           1
#define configTIMER_TASK_PRIORITY                  ( configMAX_PRIORITIES - 1 )
#define configTIMER_QUEUE_LENGTH                   20
#define configTIMER_TASK_STACK_DEPTH               ( configMINIMAL_STACK_SIZE * 2 )

#define configMAX_PRIORITIES                       ( 7 )

/* Run time stats gathering configuration options. */
unsigned long ulGetRunTimeCounterValue( void ); /* Prototype of function that returns run time counter. */
void vConfigureTimerForRunTimeStats( void );    /* Prototype of function that initialises the run time counter. */
#define configGENERATE_RUN_TIME_STATS             0

/* Co-routine related configuration options. */
#define configUSE_CO_ROUTINES                     0
#define configMAX_CO_ROUTINE_PRIORITIES           ( 2 )

/* This demo can use of one or more example stats formatting functions.  These
 * format the raw data provided by the uxTaskGetSystemState() function in to human
 * readable ASCII form.  See the notes in the implementation of vTaskList() within
 * FreeRTOS/Source/tasks.c for limitations. */
#define configUSE_STATS_FORMATTING_FUNCTIONS      0

/* Enables the test whereby a stack larger than the total heap size is
 * requested. */
#define configSTACK_DEPTH_TYPE                    uint32_t

/* Set the following definitions to 1 to include the API function, or zero
 * to exclude the API function.  In most cases the linker will remove unused
 * functions anyway. */
#define INCLUDE_vTaskPrioritySet                  1
#define INCLUDE_uxTaskPriorityGet                 1
#define INCLUDE_vTaskDelete                       1
#define INCLUDE_vTaskCleanUpResources             0
#define INCLUDE_vTaskSuspend                      1
#define INCLUDE_vTaskDelayUntil                   1
#define INCLUDE_vTaskDelay                        1
#define INCLUDE_uxTaskGetStackHighWaterMark       1
#define INCLUDE_uxTaskGetStackHighWaterMark2      1
#define INCLUDE_xTaskGetSchedulerState            1
#define INCLUDE_xTimerGetTimerDaemonTaskHandle    1
#define INCLUDE_xTaskGetIdleTaskHandle            1
#define INCLUDE_xTaskGetHandle                    1
#define INCLUDE_eTaskGetState                     1
#define INCLUDE_xSemaphoreGetMutexHolder          1
#define INCLUDE_xTimerPendFunctionCall            1
#define INCLUDE_xTaskAbortDelay                   1

#endif /* FREERTOS_CONFIG_H */

Detalhes sobre as opções de configuração podem ser encontradas na documentação oficial em

https://www.freertos.org/Documentation/RTOS_book.html

7.5 Criando um projeto novo: finalizando a aplicação "Hello World"

O nosso projeto está quase pronto, falta apenas o código principal do nosso programa. Para inseri-lo, crie o arquivo freertos-tutorial/main.c com o conteúdo

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"

/*** SEE THE COMMENTS AT THE TOP OF THIS FILE ***/
int main( void )
{
    /* Perform any hardware setup necessary. (Not necessary for Desktop) */
    /* prvSetupHardware(); */
 
    /* --- APPLICATION TASKS CAN BE CREATED HERE --- */
 
    /* Start the created tasks running. */
    vTaskStartScheduler();
 
    /* Execution will only reach here if there was insufficient heap to
    start the scheduler. */
    for( ;; );
    return 0;
}
/*-----------------------------------------------------------*/

Estamos pronto para compilar! Primeiro, vamos fazer esse processo manualmente, compilando os arquivos .c um de cada vez para gerar os arquivos objetos .o. Para tal, execute os comandos:

$ cd freertos-tutorial
$ gcc -Wall -I. -I./include -c main.c -o main.o
$ gcc -Wall -I. -I./include -c tasks.c -o tasks.o
$ gcc -Wall -I. -I./include -c queue.c -o queue.o
$ gcc -Wall -I. -I./include -c list.c -o list.o
$ gcc -Wall -I. -I./include -c timers.c -o timers.o
$ gcc -Wall -I. -I./include -c event_groups.c -o event_groups.o
$ gcc -Wall -I. -I./include -c heap_4.c -o heap_4.o
$ gcc -Wall -I. -I./include -c port.c -o port.o

Agora vamos executar o linker para gerar o executável. A entrada do linker deve ser feita em todos os arquivos objetos, então o comando é

$ gcc main.o tasks.o queue.o list.o timers.o event_groups.o heap_4.o port.o -lWinmm -o freertos-tutorial.exe

Se não ocorreu nenhum erro de compilação ou linker, a sua instalação deve estar correta! Você pode tentar executar a aplicação com:

$ .\freertos-tutorial.exe

Mas nada deve ocorrer, pois nossa aplicação fica em laço infinito. Caso queira observar um programa de verdade em execução, copie um dos códigos do próximo capítulo, compile e execute.

7.6 Criando um projeto novo: automatizando a construção com Makefile (opcional)

O processo de construção do executável feito na seção anterior é bastante trabalhoso. Imagine que toda vez que modificar algum arquivo é preciso repetir o processo, pelo menos parcialmente. Para facilitar o desenvolvimento, vamos utilizar o sistema de build Makefiles, como feito na Apostila de C, na seção 9.1.

Para tal, primeiro apague todos os arquivos de saída da compilação com o comando:

No powershell:
$ rm *.o, *.

Ou, no prompt de comando:
$ del *.o *.exe

Depois, crie o arquivo freertos-tutorial/Makefile com o conteúdo

# Compilador
CC := gcc

# Nome do executavel
TARGET = freertos-tutorial.exe

# Arquivos fonte .c
SOURCES = main.c \
	tasks.c \
	queue.c \
	list.c \
	timers.c \
	event_groups.c \
	heap_4.c \
	port.c \

# Diretorios de busca para #include
INCLUDE_DIRS := -I.
INCLUDE_DIRS += -I./include

# Flags para a compilacao (geracao dos arquivos .o)
CFLAGS := -Wall

# Flags para o linker (.o's para executavel)
LDFLAGS := -lWinmm

OBJS = $(SOURCES:.c=.o)

.PHONY: build
build: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(OBJS) $(LDFLAGS) -o $@

%.o : %.c
	$(CC) $(CFLAGS) $(INCLUDE_DIRS) -c $< -o $@

.PHONY: clean
clean:
	del $(OBJS) $(TARGET)
#	For non Windows users:
#	rm -f $(OBJS) $(TARGET)

Com isso, para construir o seu projeto com o make, execute:

$ mingw32-make build

Que deve imprimir as seguintes mensagens:

gcc -Wall -I. -I./include -c main.c -o main.o
gcc -Wall -I. -I./include -c tasks.c -o tasks.o
gcc -Wall -I. -I./include -c queue.c -o queue.o
gcc -Wall -I. -I./include -c list.c -o list.o
gcc -Wall -I. -I./include -c timers.c -o timers.o
gcc -Wall -I. -I./include -c event_groups.c -o event_groups.o
gcc -Wall -I. -I./include -c heap_4.c -o heap_4.o
gcc -Wall -I. -I./include -c port.c -o port.o
gcc main.o tasks.o queue.o list.o timers.o event_groups.o heap_4.o port.o -lWinmm -o freertos-tutorial.exe

e gerar o executável freertos-tutorial/freertos-tutorial.exe.

Note que os comandos foram os mesmos executados na seção anterior. Uma vantagem de utilizar um sistema de build é que, ao modificar um arquivo, o make detecta automaticamente qual(is) arquivo(s) precisa(m) ser recompilado(s).

Por fim, é possível remover todos os arquivos do build com o comando:

$ mingw32-make clean

Bibliografia para este capítulo