// Grupo M3 de Sistemas Operativos Avanzados
// Bibliotecas
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <freertos/semphr.h>
// Pines
#define RED_PIN 2
#define GREEN_PIN 4
#define BLUE_PIN 5
#define BUZZER_PIN 15
#define ONE_WIRE_BUS 18
#define POT_PIN 34
#define BUTTON_PIN 12
// Configuracion
LiquidCrystal_I2C lcd(0x27, 16, 2);
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// Estados del sistema
enum States {
IDLE,
Monitoring,
Alarm
};
// Eventos del sistema
enum Event {
EV_NONE,
// Eventos de boton
EV_BUTTON_SHORT, // Presion corta del boton
EV_BUTTON_LONG, // Presion larga del boton
// Eventeos para setear la temperatura en estado Monitoring
EV_TEMP_LOW, // temp objetivo seteada en low 5
EV_TEMP_MEDIUM, // temp objetivo seteada en medium 1
EV_TEMP_HIGH, // temp objetivo seteada en high -1
EV_TEMP_REACHED,
EV_TEMP_PASS, // evento nuevo que representa temp pasandose
// Evento para indicar que se alcanzo el tiempo limite de la alarma
EV_TIMEOUT
};
// Cola de eventos
QueueHandle_t colaEventos;
// Semaforo
SemaphoreHandle_t mutexDatos;
// Variables para el control del sistema
Event currentEvent = EV_NONE;
States systemState = IDLE;
States previousState = IDLE;
// Variables globales
float tempActual = NAN; // temperatura actual de la bebida
float setTemp = NAN; // temp objetivo inicia sin un valor asignado
int lecturaAnterior = -1; // lectura de potenciometro
// Umbrales de potenciometro para elegir temp
int defUmbralLOW = 1365;
int defUmbralMedium = 2730;
// Temperaturas objetivo para setTemp
float defTempLow = 5; // Frio
float defTempMedium = 1; // Muy frio
float defTempHigh = -1; // A punto de helarse
float defUmbralTolerancia = 2; // Umbral de tolerancia maxima, representa peligro de pasarse de la temperatura objetivo
bool flagAlarm = false; // bandera para disparar una unica vez el estado alarma
bool timerAlarmaActivo = false; // bandera para controlar el timer de la alarma
unsigned long tiempoInicioAlarma = 0;
unsigned long tiempoBuzzer = 10000; // buzzer sonara durante 10 segundos
// Variables de configuraciones extra
int defValorErrorSensorTemp = -127;
int defSerial = 9600;
int defTonoBuzzer = 1500;
int defUmbralButtonShort = 1000; // oprimir mas de un segundo el boton es button long
int defMargenToleranciaPotencriometro = 20; // con esta variable podemos definir que rango se tolera de cambio en el potenciometro
// Variables de configuracion de delay de tasks
int defDelayTaskBoton = 50;
int defDelaytaskPotenciometro = 100;
int defDelayTaskTimer = 100;
int defDelayTaskTemperatura = 1000;
// ---------------------- SETEO DE COLOR LED --------------
void setColor(int r, int g, int b)
{
digitalWrite(RED_PIN, r);
digitalWrite(GREEN_PIN, g);
digitalWrite(BLUE_PIN, b);
}
// ---------------------- TASK BOTON ----------------------
void taskBoton(void *pvParameter)
{
bool estadoAnterior = HIGH;
unsigned long tiempoPresionado = 0;
while(1)
{
// Leemos el estado anterior y el tiempo que fue presionado para determinar el evento que debemos devolver
bool estadoActual = digitalRead(BUTTON_PIN);
if (estadoAnterior == HIGH && estadoActual == LOW)
{
// El boton fue apretado, comenzamos a contar
tiempoPresionado = millis();
}
if (estadoAnterior == LOW && estadoActual == HIGH)
{
// Se dejo de apretar, por lo tanto la duracion es el tiemp actual menos el tiempo desde que se comenzo a contar
unsigned long duracion = millis() - tiempoPresionado;
Event evt;
if (duracion < defUmbralButtonShort)
{
// Pulsacion corta
evt = EV_BUTTON_SHORT;
}
else
{
// Pulsacion larga
evt = EV_BUTTON_LONG;
}
// Enviamos el evento a la cola
xQueueSend(colaEventos, &evt, 0);
}
// Actualizamos estado del boton
estadoAnterior = estadoActual;
vTaskDelay(pdMS_TO_TICKS(defDelayTaskBoton));
}
}
// ---------------------- TASK POTENCIONEMTRO ----------------------
void taskPotenciometro(void *pvParameter)
{
while (1)
{
int lectura = analogRead(POT_PIN);
// Controlamos si hay cambios en el potenciometro teniendo en cuenta un margen de tolerancia
if (lecturaAnterior == -1 || abs(lectura - lecturaAnterior) > defMargenToleranciaPotencriometro)
{
Event evt;
if (lectura < defUmbralLOW)
{
evt = EV_TEMP_LOW;
}
else if (lectura < defUmbralMedium)
{
evt = EV_TEMP_MEDIUM;
}
else
{
evt = EV_TEMP_HIGH;
}
xQueueSend(colaEventos, &evt, 0);
lecturaAnterior = lectura;
}
vTaskDelay(pdMS_TO_TICKS(defDelaytaskPotenciometro));
}
}
// ---------------------- TASK TEMPERATURA ----------------------
void taskTemperatura(void *pvParameter)
{
while(1)
{
// Obtenemos la temperatura actual
sensors.requestTemperatures();
float lectura = sensors.getTempCByIndex(0);
// Si no hay error de lectura de sensor, actualizamos
if (lectura != defValorErrorSensorTemp)
{
// Evitamos RC con semaforo para escribir en tempActual
xSemaphoreTake(mutexDatos, portMAX_DELAY);
tempActual = lectura;
float objetivo = setTemp;
xSemaphoreGive(mutexDatos);
// Controlamos temperatura objetivo
// En el futuro debemos utilizar tolerancia: abs(lectura - objetivo) <= 0.5
if(!isnan(objetivo) && lectura == objetivo)
{
Event evt;
evt = EV_TEMP_REACHED; // Evento temp alcanzada
xQueueSend(colaEventos, &evt, 0);
}
else if(!isnan(objetivo) && lectura == (objetivo - defUmbralTolerancia))
{
Event evt = EV_TEMP_PASS; // Evento temperatura pasandose
xQueueSend(colaEventos, &evt, 0);
}
}
vTaskDelay(pdMS_TO_TICKS(defDelayTaskTemperatura));
}
}
// ---------------------- TASK TIMER ----------------------
void taskTimer(void *pvParameter)
{
while (1)
{
bool alarmaActiva;
unsigned long inicio;
xSemaphoreTake(mutexDatos, portMAX_DELAY);
alarmaActiva = timerAlarmaActivo;
inicio = tiempoInicioAlarma;
xSemaphoreGive(mutexDatos);
if (alarmaActiva)
{
if (millis() - inicio >= tiempoBuzzer)
{
Event evt = EV_TIMEOUT;
xQueueSend(colaEventos, &evt, 0);
}
}
vTaskDelay(pdMS_TO_TICKS(defDelayTaskTimer));
}
}
// ---------------------- FUNCIONES DE ALARMA ----------------------
// Funcion para iniciar alarma
void init_alarm()
{
xSemaphoreTake(mutexDatos, portMAX_DELAY);
timerAlarmaActivo = true;
tiempoInicioAlarma = millis();
xSemaphoreGive(mutexDatos);
}
// Funcion para detener alarma
void stop_alarm()
{
xSemaphoreTake(mutexDatos, portMAX_DELAY);
timerAlarmaActivo = false;
xSemaphoreGive(mutexDatos);
}
// ---------------------- GET EVENT ----------------------
void getEvent()
{
if (xQueueReceive(colaEventos, ¤tEvent, 0) != pdTRUE)
{
currentEvent = EV_NONE;
}
}
// ---------------------- MAQUINA DE ESTADOS ----------------------
void stateMachine()
{
switch(systemState)
{
// =======================
// ESTADO IDLE
// =======================
case IDLE:
// EVENTOS DEL ESTADO IDLE
switch(currentEvent)
{
case EV_NONE:
break;
// Si la pulsacion del boton es corta, encendemos el sistema pasando a monitoring
case EV_BUTTON_SHORT:
systemState = Monitoring;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ThermoGuard ON");
setColor(0, 0, 1); // LED en azul
break;
default:
break;
}
break;
// =======================
// ESTADO MONITORING
// =======================
case Monitoring:
// EVENTOS DEL ESTADO MONITORING
switch(currentEvent)
{
// Cuando no hay eventos sucediendo nos encargamos de refrescar el display
case EV_NONE:
{
// Accedemoscon semaforo para evitar RC
xSemaphoreTake(mutexDatos, portMAX_DELAY);
float temp = tempActual;
float objetivo = setTemp;
xSemaphoreGive(mutexDatos);
lcd.setCursor(0, 1);
lcd.print("TO:");
lcd.print(objetivo,1);
lcd.print(" TA:");
lcd.print(temp,1);
lcd.print(" "); // limpia basura
}
break;
// Potenciometro en modo frio
case EV_TEMP_LOW:
{
// Accedemos con semaforo para evitar RC
xSemaphoreTake(mutexDatos, portMAX_DELAY);
setTemp = defTempLow; // escribimos set temp 5 grados
float msjTemp = tempActual; // guardo lectura de la bebida
xSemaphoreGive(mutexDatos);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Modo: FRIO");
lcd.setCursor(0, 1);
lcd.print("TO: ");
lcd.print(defTempLow, 1);
lcd.print(" TA: ");
lcd.print(msjTemp, 1);
}
break;
// Potenciometro en modo muy frio
case EV_TEMP_MEDIUM:
{
// Accedemos con semaforo para evitar RC
xSemaphoreTake(mutexDatos, portMAX_DELAY);
setTemp = defTempMedium; // escribimos set temp 1 grado
float msjTemp = tempActual; // guardo lectura de la bebida
xSemaphoreGive(mutexDatos);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Modo: MUY FRIO");
lcd.setCursor(0, 1);
lcd.print("TO: ");
lcd.print(defTempMedium, 1);
lcd.print(" TA: ");
lcd.print(msjTemp, 1);
}
break;
// Potenciometro en modo a punto de helarse
case EV_TEMP_HIGH:
{
// Accedemos con semaforo para evitar RC
xSemaphoreTake(mutexDatos, portMAX_DELAY);
setTemp = defTempHigh; // escribimos set temp -1 grado
float msjTemp = tempActual; // guardo lectura de la bebida
xSemaphoreGive(mutexDatos);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Modo: HELARSE");
lcd.setCursor(0, 1);
lcd.print("TO: ");
lcd.print(defTempHigh, 1);
lcd.print(" TA: ");
lcd.print(msjTemp, 1);
}
break;
// Temperatura objetivo alcanzada
case EV_TEMP_REACHED:
{
systemState = Alarm;
setColor(0,1,0); // led en verde
tone(BUZZER_PIN, defTonoBuzzer); // iniciamos buzzer
init_alarm();
// Salida por display
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Temp alcanzada");
lcd.setCursor(0,1);
lcd.print("Ya puede tomar");
}
break;
// Temperatura actual se esta pasando de la temperatura objetivo
case EV_TEMP_PASS:
{
setColor(1,0,0); // LED en rojo
}
break;
// Reinicio del sistema
case EV_BUTTON_LONG:
systemState = IDLE; // pasamos a idle por mantener boton
xSemaphoreTake(mutexDatos, portMAX_DELAY);
setTemp = NAN; // reiniciamos la temperatura set
tempActual = NAN; // reiniciamos la temperatura sensada
xSemaphoreGive(mutexDatos);
setColor(0, 0, 0); // apagamos led
// Salida por display
lcd.clear();
lcd.setCursor(0,0);
lcd.print("ThermoGuard");
lcd.setCursor(0,1);
lcd.print("Presione boton");
break;
default:
break;
}
break;
// =======================
// ESTADO ALARM
// =======================
case Alarm:
// EVENTOS DEL ESTADO ALARM
switch(currentEvent)
{
case EV_NONE:
break;
// Si la pulsacion del boton es corta, apagamos la alarma y pasamos a monitoring
case EV_BUTTON_SHORT:
stop_alarm(); // apagamos timer
noTone(BUZZER_PIN); // apagamos buzzer
setColor(0,0,1); // led en azul y pasamos a monitoring
systemState = Monitoring;
break;
// Timeout alarma
case EV_TIMEOUT:
setColor(0,0,1); // led en azul
noTone(BUZZER_PIN); // apagamos buzzer
systemState = Monitoring;
break;
// Si la pulsacion del boton es larga, apagamos el sistema
case EV_BUTTON_LONG:
noTone(BUZZER_PIN); // apagamos buzzer
systemState = IDLE;
stop_alarm();
xSemaphoreTake(mutexDatos, portMAX_DELAY);
setTemp = NAN; // reiniciamos la temperatura set
tempActual = NAN; // reiniciamos la temperatura sensada
xSemaphoreGive(mutexDatos);
break;
default:
break;
}
break;
}
}
// ---------------------- SETUP ----------------------
void setup()
{
Serial.begin(defSerial);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
sensors.begin();
Wire.begin(21,22);
lcd.begin(16,2);
lcd.backlight();
colaEventos = xQueueCreate(10, sizeof(Event));
// Mutex para control de RC en variables setTemp, tempActual y variables del timer
mutexDatos = xSemaphoreCreateMutex();
// Tareas
xTaskCreate(taskBoton, "Boton", 2048, NULL, 1, NULL);
xTaskCreate(taskTemperatura, "Temp", 2048, NULL, 1, NULL);
xTaskCreate(taskTimer, "Timer", 2048, NULL, 1, NULL);
xTaskCreate(taskPotenciometro, "Potenciometro", 2048, NULL, 1, NULL);
// Pantalla inicial
lcd.clear();
lcd.setCursor(0,0);
lcd.print("ThermoGuard");
lcd.setCursor(0,1);
lcd.print("Presione boton");
}
// ---------------------- LOOP ----------------------
void loop()
{
getEvent();
stateMachine();
}