#include <Arduino.h>
// Pinos de Hardware
#define LED_LUZ_PIN 18 // Verde
#define LED_ALERTA_PIN 19 // Vermelho
#define LED_STATUS_PIN 21 // Azul
#define LED_RECURSO_PIN 22 // Amarelo
#define BOTAO_ISR_PIN 23 // Botão 1 (Interrupção)
#define BOTAO_RECURSO_PIN 25 // Botão 2 (Libera Recurso)
#define BOTAO_LUZ_PIN 26 // Botão 3 (Liga/Desliga Luz - usado em outra tarefa)
// Handles para os objetos do FreeRTOS
TaskHandle_t handleLeituraSensor;
TaskHandle_t handleProcessaDados;
TaskHandle_t handleControleAlerta;
TaskHandle_t handleAcaoInterrupcao;
TaskHandle_t handleControleLuz;
TaskHandle_t handleUsaRecurso;
QueueHandle_t filaTemperatura;
EventGroupHandle_t grupoDeEventos;
SemaphoreHandle_t semaforoBinarioISR;
SemaphoreHandle_t semaforoContadorRecursos;
SemaphoreHandle_t mutexSerial;
TimerHandle_t timerLiberaRecurso;
// Constantes para o Grupo de Eventos
#define EVENTO_TEMPERATURA_ALTA (1 << 0) // Bit 0
// Protótipo da ISR
void IRAM_ATTR isrBotao1();
// Protótipos das Tarefas
void taskLeituraSensor(void *pvParameters);
void taskProcessaDados(void *pvParameters);
void taskControleAlerta(void *pvParameters);
void taskAcaoInterrupcao(void *pvParameters);
void taskControleLuz(void *pvParameters);
void taskUsaRecurso(void *pvParameters);
// Protótipo do Callback do Timer
void timerCallback(TimerHandle_t xTimer);
//================================================================
// SETUP - Inicialização do Sistema
//================================================================
void setup() {
Serial.begin(115200);
pinMode(LED_LUZ_PIN, OUTPUT);
pinMode(LED_ALERTA_PIN, OUTPUT);
pinMode(LED_STATUS_PIN, OUTPUT);
pinMode(LED_RECURSO_PIN, OUTPUT);
// Configura botões com pull-up interno. Pressionado = LOW.
pinMode(BOTAO_ISR_PIN, INPUT_PULLUP);
pinMode(BOTAO_RECURSO_PIN, INPUT_PULLUP);
pinMode(BOTAO_LUZ_PIN, INPUT_PULLUP);
// 1. CRIAÇÃO DOS OBJETOS FREERTOS
// FILA: Armazena até 5 inteiros (temperaturas)
filaTemperatura = xQueueCreate(5, sizeof(int));
// GRUPO DE EVENTOS
grupoDeEventos = xEventGroupCreate();
// SEMÁFORO BINÁRIO: Para ser liberado pela ISR
semaforoBinarioISR = xSemaphoreCreateBinary();
// SEMÁFORO CONTADOR: Simula 3 recursos disponíveis
semaforoContadorRecursos = xSemaphoreCreateCounting(3, 3); // Max=3, Inicial=3
// MUTEX: Para proteger o acesso ao Serial Monitor
mutexSerial = xSemaphoreCreateMutex();
// TIMER POR SOFTWARE: Callback a cada 15 segundos
timerLiberaRecurso = xTimerCreate("TimerRecurso", pdMS_TO_TICKS(15000), pdTRUE, (void *)0, timerCallback);
// Verificação de criação dos objetos
if (filaTemperatura == NULL || grupoDeEventos == NULL || semaforoBinarioISR == NULL || semaforoContadorRecursos == NULL || mutexSerial == NULL || timerLiberaRecurso == NULL) {
Serial.println("Erro ao criar objetos do FreeRTOS!");
while(1);
}
// 2. CONFIGURAÇÃO DA INTERRUPÇÃO DE HARDWARE
attachInterrupt(digitalPinToInterrupt(BOTAO_ISR_PIN), isrBotao1, FALLING);
// 3. CRIAÇÃO DAS TAREFAS
xTaskCreate(taskLeituraSensor, "LeituraSensor", 2048, NULL, 1, &handleLeituraSensor);
xTaskCreate(taskProcessaDados, "ProcessaDados", 2048, NULL, 2, &handleProcessaDados);
xTaskCreate(taskControleAlerta, "ControleAlerta", 2048, NULL, 2, &handleControleAlerta);
xTaskCreate(taskAcaoInterrupcao, "AcaoInterrupcao", 2048, NULL, 4, &handleAcaoInterrupcao); // Prioridade alta
xTaskCreate(taskControleLuz, "ControleLuz", 2048, NULL, 3, &handleControleLuz);
xTaskCreate(taskUsaRecurso, "UsaRecurso", 2048, NULL, 1, &handleUsaRecurso);
// 4. INICIALIZAÇÃO DO TIMER
xTimerStart(timerLiberaRecurso, 0);
Serial.println("Sistema inicializado. Pressione os botões para interagir.");
}
//================================================================
// LOOP - Não utilizado, o scheduler do FreeRTOS assume
//================================================================
void loop() {
vTaskDelay(pdMS_TO_TICKS(1000)); // O loop principal pode ser usado para tarefas de baixa prioridade ou simplesmente suspenso.
}
//================================================================
// IMPLEMENTAÇÃO DAS TAREFAS
//================================================================
/**
* @brief Tarefa 1: Lê um sensor simulado a cada 2 segundos e envia para a fila.
*/
void taskLeituraSensor(void *pvParameters) {
int temperaturaSimulada = 20;
while(1) {
temperaturaSimulada = random(15, 45); // Gera um valor entre 15 e 45
// Envia o valor para a FILA
if (xQueueSend(filaTemperatura, &temperaturaSimulada, portMAX_DELAY) == pdPASS) {
Serial.printf("Sensor: Temperatura de %dC enviada para a fila.\n", temperaturaSimulada);
}
vTaskDelay(pdMS_TO_TICKS(2000)); // Aguarda 2 segundos
}
}
/**
* @brief Tarefa 2: Aguarda dados da fila, processa e controla um LED de status.
* Se a temperatura for alta, define um bit no GRUPO DE EVENTOS.
*/
void taskProcessaDados(void *pvParameters) {
int temperaturaRecebida;
while(1) {
// Aguarda indefinidamente por um item na FILA
if (xQueueReceive(filaTemperatura, &temperaturaRecebida, portMAX_DELAY) == pdPASS) {
digitalWrite(LED_STATUS_PIN, HIGH); // Liga LED de status durante o processamento
Serial.printf("Processamento: Temperatura recebida de %dC.\n", temperaturaRecebida);
if (temperaturaRecebida > 35) {
Serial.println("Processamento: ALERTA! Temperatura alta. Setando evento.");
// Define o bit de "temperatura alta" no GRUPO DE EVENTOS
xEventGroupSetBits(grupoDeEventos, EVENTO_TEMPERATURA_ALTA);
} else {
// Limpa o bit caso a temperatura normalize
xEventGroupClearBits(grupoDeEventos, EVENTO_TEMPERATURA_ALTA);
}
vTaskDelay(pdMS_TO_TICKS(500)); // Simula tempo de processamento
digitalWrite(LED_STATUS_PIN, LOW); // Desliga LED de status
}
}
}
/**
* @brief Tarefa 3: Aguarda pelo evento de temperatura alta para ligar o LED de alerta.
*/
void taskControleAlerta(void *pvParameters) {
while(1) {
// Aguarda pelo bit EVENTO_TEMPERATURA_ALTA ser setado.
// O último parâmetro (pdTRUE) faz com que o bit seja limpo automaticamente ao sair da função.
xEventGroupWaitBits(grupoDeEventos, EVENTO_TEMPERATURA_ALTA, pdTRUE, pdFALSE, portMAX_DELAY);
// Se chegou aqui, o evento ocorreu
Serial.println("Alerta: Evento de temperatura alta recebido! Ligando LED de alerta.");
digitalWrite(LED_ALERTA_PIN, HIGH);
// Deixa o alerta ligado por 10 segundos
vTaskDelay(pdMS_TO_TICKS(10000));
Serial.println("Alerta: Fim do período de alerta.");
digitalWrite(LED_ALERTA_PIN, LOW);
}
}
/**
* @brief Tarefa 4: Aguarda pelo SEMÁFORO BINÁRIO liberado pela ISR.
* Ao receber, envia uma NOTIFICAÇÃO DE TAREFA para taskControleLuz.
*/
void taskAcaoInterrupcao(void *pvParameters) {
while(1) {
// Aguarda o semáforo ser liberado pela ISR
if(xSemaphoreTake(semaforoBinarioISR, portMAX_DELAY) == pdPASS) {
Serial.println("ISR Handler: Semáforo recebido. Enviando notificação...");
// Envia uma notificação direta para a outra tarefa.
xTaskNotify(handleControleLuz, 0, eNoAction);
}
}
}
/**
* @brief Tarefa 5: Aguarda por uma notificação e controla o LED da luz principal.
* Usa um MUTEX para acesso seguro ao Serial Monitor.
*/
void taskControleLuz(void *pvParameters) {
bool estadoLuz = false;
while(1) {
// Aguarda indefinidamente por uma notificação
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Pega o MUTEX para usar o Serial
if (xSemaphoreTake(mutexSerial, portMAX_DELAY) == pdPASS) {
Serial.println("Controle Luz: Notificação recebida!");
estadoLuz = !estadoLuz;
digitalWrite(LED_LUZ_PIN, estadoLuz);
Serial.printf("Controle Luz: Luz Principal %s.\n", estadoLuz ? "LIGADA" : "DESLIGADA");
// Libera o MUTEX
xSemaphoreGive(mutexSerial);
}
}
}
/**
* @brief Tarefa 6: Tenta obter um recurso do SEMÁFORO CONTADOR.
* Pisca um LED se conseguir. Usa o Botão 2 para liberar um recurso manualmente.
*/
void taskUsaRecurso(void *pvParameters) {
while(1) {
// Libera um recurso se o Botão 2 for pressionado
if(digitalRead(BOTAO_RECURSO_PIN) == LOW) {
xSemaphoreGive(semaforoContadorRecursos);
Serial.println("Recurso: Botão 2 pressionado. Recurso liberado!");
vTaskDelay(pdMS_TO_TICKS(200)); // debounce
}
// Tenta pegar um recurso do semáforo contador, com timeout de 1s
if (xSemaphoreTake(semaforoContadorRecursos, pdMS_TO_TICKS(1000)) == pdPASS) {
// Pega o MUTEX para usar o Serial
if (xSemaphoreTake(mutexSerial, portMAX_DELAY) == pdPASS) {
Serial.println("Recurso: Recurso adquirido com sucesso! Usando...");
// Libera o MUTEX
xSemaphoreGive(mutexSerial);
}
// Sinaliza que o recurso está em uso
digitalWrite(LED_RECURSO_PIN, HIGH);
vTaskDelay(pdMS_TO_TICKS(4000)); // Simula o uso do recurso por 4 segundos
digitalWrite(LED_RECURSO_PIN, LOW);
// Libera o recurso de volta para o semáforo.
// Comentado para que a liberação seja via Botão 2 ou Timer
// xSemaphoreGive(semaforoContadorRecursos);
// Serial.println("Recurso: Recurso devolvido.");
} else {
// Pega o MUTEX para usar o Serial
if (xSemaphoreTake(mutexSerial, portMAX_DELAY) == pdPASS) {
Serial.println("Recurso: Nao foi possivel adquirir um recurso. Tentando novamente...");
// Libera o MUTEX
xSemaphoreGive(mutexSerial);
}
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
//================================================================
// ISR e CALLBACKS
//================================================================
/**
* @brief Rotina de Serviço da Interrupção (ISR) para o Botão 1.
* É MUITO IMPORTANTE que a ISR seja rápida. Apenas libera o semáforo.
*/
void IRAM_ATTR isrBotao1() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Libera o semáforo para acordar a tarefa de alta prioridade
xSemaphoreGiveFromISR(semaforoBinarioISR, &xHigherPriorityTaskWoken);
// Se uma tarefa de maior prioridade foi acordada, força a troca de contexto
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
/**
* @brief Callback do Timer por Software. Executado a cada 15s.
* Libera um recurso para o semáforo contador.
*/
void timerCallback(TimerHandle_t xTimer) {
// Pega o MUTEX para usar o Serial
if (xSemaphoreTake(mutexSerial, portMAX_DELAY) == pdPASS) {
Serial.println("TIMER: Liberando um recurso (timeout).");
// Libera o MUTEX
xSemaphoreGive(mutexSerial);
}
xSemaphoreGive(semaforoContadorRecursos);
}