Olá, caro leitor, tudo bem? Continuemos com a série de artigos abordando o uso do FreeRTOS. No último post, tratamos sobre a fila (em inglês, queue), demos uma breve explicação de seu funcionamento e apresentamos as funções mais comuns para manipular queue’s disponíveis no FreeRTOS. Vimos que o FreeRTOS permite que o recurso de fila seja utilizado como mecanismo de sincronismo entre tarefas. Neste artigo, será apresentado mais um recurso dedicado a sincronizar tarefas e gerenciar recursos compartilhados.
Semáforo é o mecanismos de controle de acesso a recursos compartilhados entre processos. Esses recursos podem ser um conjunto de dados, periféricos, dispositivos e qualquer outro item ao qual se deseja controlar o acesso. No FreeRTOS, os semáforos são utilizados para fins de sincronização e exclusão mútua. Essa sincronização pode ser realizada entre tarefas ou entre tarefas e interrupções. O kernel do FreeRTOS oferece dois tipos de semáforos; do tipo binários e de contagem. Neste artigo, abordaremos apenas o semáforo binário. Nos próximos artigos da série, teremos um post dedicado a explicar o semáforo de contagem.
Como o próprio nome sugere, esse semáforo possui apenas dois valores (disponível ou indisponível). Para o melhor entendimento, podemos pensar o semáforo binário como uma fila que possui apenas um único item. Portanto, a fila só pode estar vazia ou cheia. As tarefas ou funções de interrupções que estão utilizando essa fila não querem saber do conteúdo da fila, estão interessadas apenas em saber se a fila está vazia ou cheia. Esse recurso pode ser executado, por exemplo, para sincronizar a execução de uma tarefa com uma função responsável por tratar uma interrupção.
Conforme citado nos artigos anteriores, o FreeRTOS contém duas abordagem para manipular a memória RAM. A primeira utiliza o método de alocação da memória RAM dinâmica, a partir do Heap. O segundo modo é por alocação de memória RAM estática em tempo de compilação. Para entender melhor a diferença entre alocação de memória estática e dinâmica, consulte o link. Para esse e os demais artigos, vamos explicar as funções do FreeRTOS que trabalham com alocação de memória RAM dinâmica.
Para criar uma semáforo é necessário declarar uma variável do tipo “SemaphoreHandle_t”. Essa variável será utilizada como o identificador do semáforo.
Protótipo da função:
SemaphoreHandle_t xSemaphoreCreateBinary(void);
Retorno:
Conforme visto no artigo anterior, no qual falamos a respeito de fila (queue), existem duas tratativas distintas na utilização das API’s do FreeRTOS: funções especializadas e funções genéricas, semáforos binários obedecem à mesma regra. Quando a obtenção do semáforo é feita a partir de uma tarefa, deve-se usar a função genérica. Se for obter o semáforo a partir de interrupção ou em uma função de callback, é recomendado utilizar API especializada.
xSemaphoreTake é a função genérica para obtenção do semáforo.
Protótipo da função:
xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
Parâmetros
Retorno
xSemaphoreTakeFromISR é a função especializada recomendada para interrupções e funções de callback.
Protótipo da função
xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken );
Parâmetro
Retorno
Uma vez obtido um determinado semáforo, executado o algoritmo, é necessário liberar o mesmo para que outras funções da aplicação possam utilizar. Como visto na obtenção do semáforo, o FreeRTOS possui duas abordagens, oferecendo funções genéricas e especializadas. Para liberar o semáforo, a tratativa é a mesa, função genérica para ser utilizada a partir de uma tarefa e API especializada para uso em interrupções e funções de callback.
xSemaphoreGive é a função genérica para liberar o semáforo.
Protótipo da função
xSemaphoreGive( SemaphoreHandle_t xSemaphore );
Parâmetro:
Retorno
xSemaphoreGiveFromISR é a função especializada recomendada para interrupções e funções de callback.
Protótipo da função:
xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken);
Parâmetro:
Retorno
Como qualquer objeto no FreeRTOS, o semáforo binário pode ser deletado. Quando é excluído um semáforo, é liberada toda a memória RAM alocada.
Nota: É recomendado não excluir um semáforo que tenha tarefas bloqueadas nele (tarefas que estão no estado bloqueado aguardando a disponibilidade do semáforo).
Protótipo da função
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
Parâmetro:
xSemaphore: O identificador do semáforo que está sendo deletado.
A aplicação desenvolvida para o projeto de demonstração conta com circuito integrado (CI) PCF8574. Esse CI é um expansor de I/O, que se comunica com o módulo ESP32 por meio do barramento I2C. Além dos sinais do I2C, necessário para comunicação com o CI, o pino INT está conectado ao ESP32. Esse é o pino de interrupção do CI PCF8574, utilizado para notificar quando há mudança de valor em suas portas. O projeto desenvolvido utiliza o expansor de I/O PCF8574 com cada porta conectada a um push-button. Então, a cada vez que algum botão é pressionado, o valor do pino INT é alterado. Este, por sua vez, no algoritmo da aplicação, provoca a execução da função de interrupção. Para gerar o sincronismo entre a função que trata interrupção e tarefa responsável por interagir com CI, foi criado um semáforo.
Foram criadas três tarefas e dois semáforos binários, um dos semáforos é utilizado como mecanismo de sincronização entre função que trata a interrupção e a tarefa responsável por comunicar com o CI PCF8574. O segundo semáforo é encarregado da gestão na escrita no barramento serial, já que esse recurso é compartilhado com todas as tarefas da aplicação. As demais tarefas criadas ficam periodicamente escrevendo no barramento serial. A seguir, temos a figura com o circuito elétrico do projeto de demonstração.
Materiais necessários para o projeto Compartilhamento com Semáforo Binário
/** * @file main.cpp * @author Evandro Teixeira * @brief * @version 0.1 * @date 21-01-2022 * * @copyright Copyright (c) 2022 * */ #include <Arduino.h> #include <freertos/queue.h> #include <freertos/task.h> #include <freertos/semphr.h> /* https://github.com/RobTillaart/PCF8574 */ #include "PCF8574.h" #define COLOR_BLACK "\e[0;30m" #define COLOR_RED "\e[0;31m" #define COLOR_GREEN "\e[0;32m" #define COLOR_YELLOW "\e[0;33m" #define COLOR_BLUE "\e[0;34m" #define COLOR_PURPLE "\e[0;35m" #define COLOR_CYAN "\e[0;36m" #define COLOR_WRITE "\e[0;37m" #define COLOR_RESET "\e[0m" #define TASK_DELAY 1000 #define PRIMARY_CORE PRO_CPU_NUM #define SECONDARY_CORE APP_CPU_NUM #define ENABLE 1 #define DISABLE 0 #define ENABLE_SEMAPHORE_SERIAL DISABLE #define PIN_IRQ_EXP_IO 15 #define ADDRESS_I2C_EXPIO 0x20 #define DEBONCE_IRQ 500 /** * @brief */ void Tarefa_A(void *parameters); void Tarefa_B(void *parameters); void Tarefa_ExpIO(void *parameters); /** * @brief */ const char txt[] = {"Contribua com a comunidade Maker"}; const char txt1[] = {"Torne-se um Eletrogater Expert"}; /** * @brief */ SemaphoreHandle_t xSemaphore_Serial = NULL; SemaphoreHandle_t xSemaphore_ExpIO = NULL; /** * @brief Cria objeto PCF8574 */ PCF8574 ExpIO(ADDRESS_I2C_EXPIO); /** * @brief */ void IRAM_ATTR ExpIO_ISR() { static uint32_t last_time = 0; if((millis() - last_time) >= DEBONCE_IRQ) { #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTakeFromISR(xSemaphore_Serial,pdFALSE); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.printf("\n\rIRQ Libera o Semaforo Binario xSemaphore_ExpIO"); #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGiveFromISR(xSemaphore_Serial,pdFALSE); #endif /* ENABLE_SEMAPHORE_SERIAL */ last_time = millis(); xSemaphoreGiveFromISR(xSemaphore_ExpIO, (BaseType_t)(pdFALSE)); } } /** * @brief * */ void setup() { // Inicializa a Serial Serial.begin(115200); Serial.printf("\n\rFreeRTOS - Semafaro Binario \n\r"); // Inicializa pino 15 como entra e inicializa interrupção pinMode(PIN_IRQ_EXP_IO, INPUT_PULLUP); attachInterrupt(PIN_IRQ_EXP_IO, ExpIO_ISR, FALLING); // Inicializar PCF8574 ExpIO.begin(); #if ENABLE_SEMAPHORE_SERIAL // Cria semafaro binario xSemaphore_Serial vSemaphoreCreateBinary( xSemaphore_Serial ); if(xSemaphore_Serial == NULL) { Serial.printf("\n\rFalha em criar o semafaro para Serial"); } #endif /* ENABLE_SEMAPHORE_SERIAL */ // Cria semafaro binario xSemaphore_ExpIO vSemaphoreCreateBinary( xSemaphore_ExpIO ); if(xSemaphore_ExpIO == NULL) { Serial.printf("\n\rFalha em criar o semafaro para Expansor I/O"); } // Obtem o semafaro xSemaphore_ExpIO xSemaphoreTake(xSemaphore_ExpIO,(TickType_t)100); // Cria tarefas da aplicação xTaskCreatePinnedToCore(Tarefa_A, "Tarefa_A", configMINIMAL_STACK_SIZE * 3, NULL, tskIDLE_PRIORITY + 1, NULL, SECONDARY_CORE); xTaskCreatePinnedToCore(Tarefa_B, "Tarefa_B", configMINIMAL_STACK_SIZE * 3, NULL, tskIDLE_PRIORITY + 1, NULL, PRIMARY_CORE); xTaskCreatePinnedToCore(Tarefa_ExpIO, "Tarefa_ExpIO", configMINIMAL_STACK_SIZE * 3, NULL, tskIDLE_PRIORITY + 2, NULL, tskNO_AFFINITY); } /** * @brief * */ void loop() { #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTake(xSemaphore_Serial,portMAX_DELAY); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.printf("\n\rTarefa Loop"); vTaskDelay((TASK_DELAY/5)/portTICK_PERIOD_MS); #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGive( xSemaphore_Serial ); #endif /* ENABLE_SEMAPHORE_SERIAL */ } /** * @brief * * @param parameters */ void Tarefa_A(void *parameters) { #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTake(xSemaphore_Serial,portMAX_DELAY); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.print(COLOR_RED); // altera para vermelho impressão da mensagem na serial Serial.printf("\n\r%s",pcTaskGetTaskName(NULL)); Serial.printf("\n\r%s",txt); Serial.printf("\n\r%s",txt1); Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGive( xSemaphore_Serial ); #endif /* ENABLE_SEMAPHORE_SERIAL */ while (1) { #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTake(xSemaphore_Serial,portMAX_DELAY); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.print(COLOR_RED); // altera para vermelho impressão da mensagem na serial Serial.printf("\n\r%s",pcTaskGetTaskName(NULL)); Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGive( xSemaphore_Serial ); #endif /* ENABLE_SEMAPHORE_SERIAL */ vTaskDelay((TASK_DELAY)/portTICK_PERIOD_MS); // Pausa a execução da tarefa por 2000 milessegundos } } /** * @brief * * @param parameters */ void Tarefa_B(void *parameters) { #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTake(xSemaphore_Serial,portMAX_DELAY); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.print(COLOR_YELLOW); // altera para vermelho impressão da mensagem na serial Serial.printf("\n\r%s",pcTaskGetTaskName(NULL)); Serial.printf("\n\r%s",txt); Serial.printf("\n\r%s",txt1); Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGive( xSemaphore_Serial ); #endif /* ENABLE_SEMAPHORE_SERIAL */ while (1) { #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTake(xSemaphore_Serial,portMAX_DELAY); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.print(COLOR_YELLOW); // altera para vermelho impressão da mensagem na serial Serial.printf("\n\r%s",pcTaskGetTaskName(NULL)); Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGive( xSemaphore_Serial ); #endif /* ENABLE_SEMAPHORE_SERIAL */ vTaskDelay(TASK_DELAY/portTICK_PERIOD_MS); // Pausa a execução da tarefa por 2000 milessegundos } } /** * @brief * * @param parameters */ void Tarefa_ExpIO(void *parameters) { uint8_t value = 0; #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTake(xSemaphore_Serial,portMAX_DELAY); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.print(COLOR_PURPLE); // altera para roxo impressão da mensagem na serial Serial.printf("\n\r%s",pcTaskGetTaskName(NULL)); Serial.printf("\n\r%s",txt); Serial.printf("\n\r%s",txt1); Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGive( xSemaphore_Serial ); #endif /* ENABLE_SEMAPHORE_SERIAL */ while (1) { if(xSemaphoreTake(xSemaphore_ExpIO,portMAX_DELAY) == pdTRUE) { value = ExpIO.read8(); #if ENABLE_SEMAPHORE_SERIAL xSemaphoreTake(xSemaphore_Serial,portMAX_DELAY); #endif /* ENABLE_SEMAPHORE_SERIAL */ Serial.print(COLOR_PURPLE); // altera para roxo impressão da mensagem na serial Serial.printf("\n\rPCF8574 Value: %d",value); Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial #if ENABLE_SEMAPHORE_SERIAL xSemaphoreGive( xSemaphore_Serial ); #endif /* ENABLE_SEMAPHORE_SERIAL */ } } }
Para melhor exemplificar a utilização de semáforo binário como mecanismo de controle de acesso de recurso compartilhado, optei em executar a aplicação com e sem o semáforo habilitado. A imagem a seguir exibe o LOG de mensagem no barramento serial da execução proposta.
As mensagens na figura foram enumeradas para detalhar o comportamento do algoritmo implementado no projeto de demonstração:
Neste artigo da série sobre o FreeRTOS, foi apresentado o recurso de semáforo binário. Esse é um dos tipos de semáforo oferecidos pelo sistema operacional. Semáforo é um dos principais mecanismos para sincronização de tarefas, também podendo ser empregado para controle de acesso a recursos compartilhados entre processos. Detalhamos as principais API’s para o manuseio de semáforo disponível no FreeRTOS e foi apresentado uma aplicação de demonstração. O projeto desenvolvido traz dois modos de utilização do semáforo binário, a primeira como mecanismo de sincronização entre uma função que trata interrupção com uma tarefa, e o segundo para controle de acesso de recurso compartilhado entre processos. Ao final, analisamos o LOG de mensagem impressa no barramento serial para o melhor entendimento do algoritmo. Para os próximos artigos serão apresentados mais recursos do FreeRTOS. O que você achou? Você já utiliza o FreeRTOS em seus projetos? Deixe o seu comentário abaixo.
Semaphores
https://www.freertos.org/a00113.html
Wikipedia – Exclusão mútua
https://pt.wikipedia.org/wiki/Exclus%C3%A3o_m%C3%BAtua
Wikipedia – Semáforo (computação)
https://pt.wikipedia.org/wiki/Sem%C3%A1foro_(computa%C3%A7%C3%A3o)
Conheça a Metodologia Eletrogate e ofereça aulas de robótica em sua escola!
|
A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.
Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!