Olá, caro leitor. Tudo bem? Daremos sequência à série de artigos abordando o desenvolvimento de aplicação utilizando o sistema operacional de tempo real, FreeRTOS. No último artigo da série, tratamos a respeito de semáforo binário, vendo que esse recurso é utilizado como mecanismo de sincronismo de processos e exclusão mútua. Neste post, será apresentado o recurso de Mutex, oferecido pelo FreeRTOS.
Mutex é acrônimo para “mutual exclusion”, o termo inglês para “exclusão mútua”. É uma técnica de programação utilizada em programa concorrente, para impedir que dois ou mais processos tenham acesso de maneira simultânea a um determinado recurso compartilhado.
Mutex, no FreeRTOS basicamente são semáforos binário equipados com o mecanismo de herança de prioridade. Essa funcionalidade faz com que a tarefa que obtém o mutex tenha a sua prioridade elevada temporariamente se outra tarefa de maior prioridade tentar obter o mesmo mutex. A tarefa que está em posse do mutex, após terminar de utiliza-lo deve o liberar, permitindo, assim, que outras tarefas tenham a oportunidade de acessar tal recurso. Essas características tornam o mutex a melhor escolha para implementar técnica de exclusão mútua simples.
É importante salientar que mutexes no FreeRTOS não devem ser usados a partir de uma interrupção porque:
Outra característica do FreeRTOS é que as funções de API de acesso ao mutex e semáforo são compartilhadas. Isso permite que um tempo de bloqueio seja especificado. O tempo de bloqueio indica o número máximo de “Ticks” que uma tarefa deve entrar no estado de bloqueio ao tentar “obter” um mutex se o mutex não estiver disponível.
Conforme citado nos artigos anteriores, o FreeRTOS contém duas abordagens para manipular a memória RAM; a primeira utilizando o método de alocação da memória RAM dinâmica, a partir do Heap, e a segunda 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 vs 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 mutex, é necessário declarar uma variável do tipo “SemaphoreHandle_t”. Sim, usa o mesmo tipo de variável utilizada para semáforo. Como dito anteriormente, semáforo e mutex compartilham as mesmas API’s. Essa variável será utilizada como o identificador do mutex.
Protótipo da função:
SemaphoreHandle_t xSemaphoreCreateMutex(void)
Retorno:
Conforme citado anteriormente, as funções de mutex e semáforo compartilham as mesmas API’s. Então, a função para obtenção do mutex é a mesma utilizada para obter o semáforo. Porém, diferente dos semáforos, mutexes não podem ser executados a partir de funções de interrupções, sendo usados apenas em tarefas. Para manipular o mutex, temos que utilizar as funções de uso genérico, não a função de uso especializado.
xSemaphoreTake é a função genérica para obtenção do mutex.
Protótipo da função:
xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
Parâmetros:
Retorno:
Uma vez obtido um determinado mutex, executado o algoritmo do recurso compartilhado, é necessário liberar o mesmo para que outras funções da aplicação possam utilizar. Para liberar o mutex, é recomendado o uso da função genérica.
xSemaphoreGive é função genérica para liberar o mutex.
Protótipo da função
xSemaphoreGive( SemaphoreHandle_t xSemaphore );
Parâmetro:
Retorno:
Como qualquer objeto no FreeRTOS, o mutex pode ser deletado. Quando é excluído um mutex, é liberada toda a memória RAM alocada.
Nota: É recomendado não excluir um mutex que tenha tarefas bloqueadas nele (tarefas que estão no estado bloqueado aguardando a disponibilidade do mutex).
Protótipo da função
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
Parâmetro:
A aplicação desenvolvida tem como objetivo demonstrar o uso compartilhado do barramento I2C por diferentes tarefas. O projeto utiliza o Módulo Real Time Clock RTC DS1307 (RTC Tiny), dispositivo que contém o circuito integrado (CI) DS1307 e o CI AT24C32. O DS1307 é um RTC (Real Time Clock – relógio de tempo real), componente que, como próprio no sugere, é relógio e também um calendário. O segundo CI presente no módulo é memória do tipo EEPROM de 32 KBits.
Além do módulo RTC Tiny, a aplicação conta com o push-button. O algoritmo desenvolvido consiste em um contador de pulsos que registra na memória EEPROM a data e hora da ocorrência de cada pulso.
O projeto desenvolvido conta com três tarefas e uma rotina para o tratamento da interrupção dedicada aos eventos do push-button. A primeira é a “Tarefa LED”, é responsável pelo acionamento do LED “on-board”. A segunda, a “Tarefa Relógio” que, por sua vez, é encarregada pela a comunicação com o CI DS1307. Por último, temos a “Tarefa Contador de Pulso”, responsável por efetuar a contagem de pulso e salvar os dados na memória EEPROM.
Para exemplificar, o projeto possui dois mutexes. O primeiro é para gerenciar o acesso do barramento da comunicação I2C. O segundo mutex é dedicado a controlar o acesso de variável global utilizada na aplicação, uma struct que contém data e hora.
Circuito elétrico do projeto
/** * @file main.cpp * @author Evandro Teixeira * @brief * @version 0.1 * @date 14-02-2022 * * @copyright Copyright (c) 2022 * */ #include <Arduino.h> #include <freertos/semphr.h> #include <freertos/task.h> #include "RTCTiny.hpp" // https://github.com/evandro-teixeira/RTC_Tiny #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 AT24C32_ADDRESS 0x50 #define DS1307_ADDRESS 0x68 #define BUTTON 15 // Pino do botão #define LED_BOARD 2 // Pino do LED #define DEBOUNCE_BUTTON 1000 // Tempo do debounce do botão /** * @brief */ const char MesesDoAno[12][4] = {"Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"}; const char DiasDaSemana[7][14] = {"Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sabado"}; /** * @brief Cria objeto RTC Tiny * * @return RtcTiny */ RtcTiny ModuleRTC(AT24C32_ADDRESS, DS1307_ADDRESS); /** * @brief */ void Tarefa_LED(void *parameters); void Tarefa_Relogio(void *parameters); void Tarefa_ContadorPulso(void *parameters); void SetVarRTC(DS1307Data_t Data); DS1307Data_t GetVarRTC(void); /** * @brief */ SemaphoreHandle_t xSemaphore_Pulso = NULL; SemaphoreHandle_t xMutex_I2C = NULL; SemaphoreHandle_t xMutex_Var = NULL; DS1307Data_t RTCData; /** * @brief Função da interrupção botão */ void IRAM_ATTR Button_ISR() { // tempo da ultima leitura do botão static uint32_t last_time = 0; // Algoritmo de debounce do botão if( (millis() - last_time) >= DEBOUNCE_BUTTON) { last_time = millis(); xSemaphoreGiveFromISR(xSemaphore_Pulso, (BaseType_t)(pdFALSE)); } } /** * @brief */ void setup() { // Inicializa a Serial Serial.begin( 115200 ); Serial.printf("\n\rFreeRTOS - Mutex\n\r"); ModuleRTC.Init(); // Inicializa pino 15 como entra e inicializa interrupção do botão pinMode(BUTTON, INPUT); attachInterrupt(BUTTON, Button_ISR, RISING); // Inicializa pino do LED on Board pinMode(LED_BOARD,OUTPUT); digitalWrite(LED_BOARD,LOW); // Cria semafaro binario xSemaphore_Pulso vSemaphoreCreateBinary( xSemaphore_Pulso ); if(xSemaphore_Pulso == NULL) { Serial.printf("\n\rFalha em criar o semafaro para Contador Pulso"); } // Obtem o semafaro xSemaphore_Pulso xSemaphoreTake(xSemaphore_Pulso,(TickType_t)100); // Cria Mutex para gestão do barramento I2C xMutex_I2C = xSemaphoreCreateMutex(); if(xMutex_I2C == NULL) { Serial.printf("\n\rFalha em criar o Mutex para I2C"); } xMutex_Var = xSemaphoreCreateMutex(); if(xMutex_Var == NULL) { Serial.printf("\n\rFalha em criar o Mutex para variavel global"); } // Cria tarefas da aplicação xTaskCreate(Tarefa_LED, "LED", configMINIMAL_STACK_SIZE * 2, NULL, tskIDLE_PRIORITY + 1, NULL); xTaskCreate(Tarefa_Relogio, "Relogio", configMINIMAL_STACK_SIZE * 3, NULL, tskIDLE_PRIORITY + 2, NULL); xTaskCreate(Tarefa_ContadorPulso, "Contador Pulso", configMINIMAL_STACK_SIZE * 3, NULL, tskIDLE_PRIORITY + 3, NULL); } /** * @brief */ void loop() { Serial.printf("\n\rSupende tarefa LOOP"); vTaskSuspend(NULL); } /** * @brief * * @param parameters */ void Tarefa_LED(void *parameters) { static int valueOld = 0xFF; int value = 0; while (1) { // le o valor do botão value = digitalRead(BUTTON); // detecta borda de subida if((value != valueOld) && (value == HIGH)) { digitalWrite(LED_BOARD,LOW); Serial.print(COLOR_BLUE); Serial.printf("\n\rLED OFF"); Serial.print(COLOR_RESET); } else { // detecta borda de descida if((value != valueOld) && (value == LOW)) { digitalWrite(LED_BOARD,HIGH); Serial.print(COLOR_BLUE); Serial.printf("\n\rLED ON"); Serial.print(COLOR_RESET); } } // Update valueOld = value; vTaskDelay(10/portTICK_PERIOD_MS); } } /** * @brief * * @param parameters */ void Tarefa_Relogio(void *parameters) { DS1307Data_t Data; while (1) { // Obtem o Mutex-I2C xSemaphoreTake(xMutex_I2C,portMAX_DELAY ); // Le os dados do RTC via barramento I2C ModuleRTC.ReadRTC(&Data); // libera o Mutex-I2C xSemaphoreGive(xMutex_I2C); // salva dados na memoria global SetVarRTC(Data); // Imprimi no barramento serial Data e Hora Serial.printf("\n\r%02d:%02d:%02d - ",Data.Hours, Data.Minutes, Data.Seconds); Serial.printf("%s, %02d de %s de %d ",DiasDaSemana[ (Data.Day-1) ], Data.Date , MesesDoAno[ (Data.Month-1) ], (2000 + Data.Year) ); vTaskDelay(1000/portTICK_PERIOD_MS); } } /** * @brief * * @param parameters */ void Tarefa_ContadorPulso(void *parameters) { const uint16_t endProximo = 0x0000; uint16_t contadorPulso = 0; uint16_t endMemoriaROM = 0; uint8_t buffer[2] = {0}; DS1307Data_t dataRTC; // Obtem o Mutex-I2C xSemaphoreTake(xMutex_I2C,portMAX_DELAY ); // Salva o endereço proximo dado ModuleRTC.WriteROM(endProximo, 0x00 ); ModuleRTC.WriteROM(endProximo + 1, 0x00 ); // libera o Mutex-I2C xSemaphoreGive(xMutex_I2C); while (1) { if(xSemaphoreTake(xSemaphore_Pulso,portMAX_DELAY) == pdTRUE) { // obtem data e hora dataRTC = GetVarRTC(); // incrementa o contador de pulso contadorPulso++; Serial.print(COLOR_YELLOW); Serial.printf("\n\rContador de Puslo: %d",contadorPulso); // Obtem o Mutex-I2C xSemaphoreTake(xMutex_I2C,portMAX_DELAY ); // Le dados no barramento I2C // Obtem o endereço do proximo dado a ser salvo ModuleRTC.ReadROM(endProximo, &buffer[0] ); ModuleRTC.ReadROM(endProximo + 1, &buffer[1] ); endMemoriaROM = (uint16_t)(buffer[0] << 8); endMemoriaROM += (uint16_t)(buffer[1] ); Serial.printf("\n\r-->endMemoriaROM: %d",endMemoriaROM); /* ________ ________ | Add | Data | |--------|--------| | 0x0000 | XXXX | Proximo endereço |--------|--------| | 0x---- | XXXX | Numero de pulso MSB | 0x---- | XXXX | Numero de pulso LSB | 0x---- | XXXX | Hora | 0x---- | XXXX | Min | 0x---- | XXXX | Sec | 0x---- | XXXX | Dia | 0x---- | XXXX | Mes |________|________| Ano */ // Escreve no barramento I2C // Salva os dados na memoria ModuleRTC.WriteROM(endMemoriaROM++, ((contadorPulso & 0xFF00) >> 8) ); ModuleRTC.WriteROM(endMemoriaROM++, ((contadorPulso & 0x00FF) >> 0) ); ModuleRTC.WriteROM(endMemoriaROM++, dataRTC.Hours ); ModuleRTC.WriteROM(endMemoriaROM++, dataRTC.Minutes ); ModuleRTC.WriteROM(endMemoriaROM++, dataRTC.Seconds ); ModuleRTC.WriteROM(endMemoriaROM++, dataRTC.Date ); ModuleRTC.WriteROM(endMemoriaROM++, dataRTC.Month ); ModuleRTC.WriteROM(endMemoriaROM++, dataRTC.Year ); // Salva o endereço proximo dado ModuleRTC.WriteROM(endProximo, ((endMemoriaROM & 0xFF00) >> 8) ); ModuleRTC.WriteROM(endProximo + 1, ((endMemoriaROM & 0x00FF) >> 0) ); Serial.printf("\n\r<--endMemoriaROM: %d",endMemoriaROM); Serial.print(COLOR_RESET); // libera o Mutex-I2C xSemaphoreGive(xMutex_I2C); } } } /** * @brief Set the Global Variable RTC object * * @param Data */ void SetVarRTC(DS1307Data_t Data) { // Obtem o Mutex Variavel Global xSemaphoreTake(xMutex_Var,portMAX_DELAY ); RTCData = Data; // libera o Mutex Variavel Global xSemaphoreGive(xMutex_Var); } /** * @brief Get the Global Variable RTC object * * @return DS1307Data_t */ DS1307Data_t GetVarRTC(void) { DS1307Data_t Data; // Obtem o Mutex Variavel Global xSemaphoreTake(xMutex_Var,portMAX_DELAY ); Data = RTCData; // libera o Mutex Variavel Global xSemaphoreGive(xMutex_Var); return Data; }
A seguir, temos a imagem com as mensagem de cada tarefa impressas no terminal serial.
LOG das mensagens no barramento serial
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 Mutex. Esse é um dos tipos de mutex oferecidos pelo sistema operacional. Mutex é um dos principais mecanismos empregados para controle de acesso a recursos compartilhados entre processos.
Detalhamos as principais API’s para o manuseio de mutex disponível no FreeRTOS e foi apresentado uma aplicação de demonstração. O projeto desenvolvido traz dois exemplos de uso de mutex; um faz o controle de acesso a uma variável global da aplicação e o outro gerencia o acesso ao barramento I2C, utilizado para acessar os CI’s presentes no módulo RTC Tiny.
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.
FreeRTOS Mutexes
https://www.freertos.org/Real-time-embedded-RTOS-mutexes.html
FreeRTOS Semaphore / Mutexes
https://www.freertos.org/a00113.html
Wikipedia – Programação concorrente
https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_concorrente
Wikipedia – Processo (informática)
https://pt.wikipedia.org/wiki/Processo_(inform%C3%A1tica)
Wikipedia – Exclusão mútua
Github – RTC Tiny
https://github.com/evandro-teixeira/RTC_Tiny
|
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.