/* --------------------------------------
PROYECTO PRIMERA EVALUACIÓN
-------------------------------------- */
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include <stdio.h>
#include <stdint.h>
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include <math.h>
// Recursos necesarios para la comunicación I2C y LCD
#include <stdbool.h>
#include "driver/i2c.h"
// --------------------------------------------------
// Configuración del bus I2C
// --------------------------------------------------
#define I2C_MASTER_SCL_IO 8
// Pin GPIO22 del ESP32 usado como SCL (reloj) del bus I2C
#define I2C_MASTER_SDA_IO 9
// Pin GPIO21 del ESP32 usado como SDA (datos) del bus I2C
#define I2C_MASTER_NUM I2C_NUM_0
// Selecciona el puerto I2C0 del ESP32
#define I2C_MASTER_FREQ_HZ 100000
// Velocidad del bus I2C: 100 kHz (estándar)
// --------------------------------------------------
// Configuración de la LCD
// --------------------------------------------------
#define LCD_ADDR 0x27
// Dirección I2C del módulo LCD con PCF8574 (común en displays 16x2)
#define LCD_BACKLIGHT 0x08
// Valor de control para encender la luz de fondo de la LCD
#define ENABLE 0x04
// Señal Enable del LCD; se activa para que la LCD lea el dato
#define RS 0x01
// Register Select: RS=0 -> comando, RS=1 -> dato
/* =================================================
SENSOR PASOS
================================================= */
#define QUEUE_LENGTH 10 // Máximo 10 datos pendientes
#define QUEUE_ITEM_SIZE sizeof(int)
QueueHandle_t cola_pasos;
#define BOTON_GPIO 0
SemaphoreHandle_t boton_pasos;
void init_gpio7(void)
{
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << GPIO_NUM_7), // selecciona pin 7
.mode = GPIO_MODE_INPUT, // modo entrada
.pull_up_en = GPIO_PULLUP_DISABLE, // deshabilitar pull-up interno
.pull_down_en = GPIO_PULLDOWN_DISABLE,// deshabilitar pull-down interno
.intr_type = GPIO_INTR_DISABLE // sin interrupciones
};
gpio_config(&io_conf);
}
/* =================================================
SENSOR TEMPERATURA y SENSOR RITMO CARDIACO
================================================= */
QueueHandle_t cola_medicionesf;
adc_oneshot_unit_handle_t adc1_handle;
// --------------------------------------------------
// Función para inicializar I2C
// --------------------------------------------------
esp_err_t i2c_master_init(void){
// Función para inicializar el bus I2C como maestro
i2c_config_t conf = {
.mode = I2C_MODE_MASTER, // Configura el ESP32 como maestro
.sda_io_num = I2C_MASTER_SDA_IO,// Pin SDA
.scl_io_num = I2C_MASTER_SCL_IO,// Pin SCL
.sda_pullup_en = GPIO_PULLUP_ENABLE, // Habilita pull-up SDA
.scl_pullup_en = GPIO_PULLUP_ENABLE, // Habilita pull-up SCL
.master.clk_speed = I2C_MASTER_FREQ_HZ // Velocidad del bus
};
i2c_param_config(I2C_MASTER_NUM, &conf);
// Aplica la configuración al puerto I2C seleccionado
return i2c_driver_install(
I2C_MASTER_NUM, // Puerto I2C
conf.mode, // Modo maestro
0, // Tamaño buffer RX (no usado)
0, // Tamaño buffer TX (no usado)
0 // Flags, ninguno
);
// Instala el driver I2C en el ESP32
}
// --------------------------------------------------
// Función para escribir un byte al LCD
// --------------------------------------------------
esp_err_t lcd_write_byte(uint8_t data){
return i2c_master_write_to_device(
I2C_MASTER_NUM, // Puerto I2C a usar
LCD_ADDR, // Dirección del LCD
&data, // Byte que se enviará
1, // Cantidad de bytes (1)
pdMS_TO_TICKS(100) // Timeout de 100 ms en ticks de FreeRTOS
);
// Envía un byte al LCD usando I2C
}
// --------------------------------------------------
// Pulso ENABLE para que la LCD lea el dato
// --------------------------------------------------
void lcd_pulse_enable(uint8_t data){
lcd_write_byte(data | ENABLE);
// Enciende la señal ENABLE
vTaskDelay(pdMS_TO_TICKS(1));
// Pausa 1 ms
lcd_write_byte(data & ~ENABLE);
// Apaga ENABLE
vTaskDelay(pdMS_TO_TICKS(1));
// Pausa 1 ms
}
// --------------------------------------------------
// Enviar un nibble (4 bits) a la LCD
// --------------------------------------------------
void lcd_send_nibble(uint8_t nibble, uint8_t control){
uint8_t data = nibble | control | LCD_BACKLIGHT;
// Combina los 4 bits del nibble, el modo (RS) y backlight
lcd_write_byte(data);
// Envía el nibble al LCD
lcd_pulse_enable(data);
// Pulsa ENABLE para que el LCD lea el dato
}
// --------------------------------------------------
// Enviar un byte completo (8 bits) a la LCD
// --------------------------------------------------
void lcd_send_byte(uint8_t value, uint8_t mode){
uint8_t high = value & 0xF0;
// Obtiene los 4 bits altos del byte
uint8_t low = (value << 4) & 0xF0;
// Obtiene los 4 bits bajos y los coloca en la posición alta
lcd_send_nibble(high, mode);
// Envía primero el nibble alto
lcd_send_nibble(low, mode);
// Envía el nibble bajo
}
// --------------------------------------------------
// Enviar comando al LCD (RS=0)
// --------------------------------------------------
void lcd_send_cmd(uint8_t cmd){
lcd_send_byte(cmd, 0);
// Envía un comando
}
// --------------------------------------------------
// Enviar un carácter al LCD (RS=1)
// --------------------------------------------------
void lcd_send_char(char c){
lcd_send_byte(c, RS);
// Envía un dato (carácter)
}
// --------------------------------------------------
// Imprimir una cadena completa en el LCD
// --------------------------------------------------
void lcd_print(char *str){
while (*str) {
lcd_send_char(*str);
// Envía carácter por carácter
str++;
}
}
// --------------------------------------------------
// Inicialización de la LCD
// --------------------------------------------------
void lcd_init()
{
vTaskDelay(pdMS_TO_TICKS(50));
// Espera 50 ms antes de inicializar
lcd_send_nibble(0x30, 0);
vTaskDelay(pdMS_TO_TICKS(5));
lcd_send_nibble(0x30, 0);
vTaskDelay(pdMS_TO_TICKS(5));
lcd_send_nibble(0x30, 0);
vTaskDelay(pdMS_TO_TICKS(5));
// Secuencia de inicialización según datasheet HD44780
lcd_send_nibble(0x20, 0);
// Configura modo 4 bits
lcd_send_cmd(0x28);
// 4 bits, 2 líneas, fuente 5x8
lcd_send_cmd(0x0C);
// Display ON, cursor OFF, blink OFF
lcd_send_cmd(0x06);
// Modo entrada: cursor avanza a la derecha, sin desplazar pantalla
lcd_send_cmd(0x01);
// Limpia pantalla
vTaskDelay(pdMS_TO_TICKS(5));
// Pausa corta tras limpiar pantalla
}
// --------------------------------------------------
// Función para enviar texto a una fila específica
// --------------------------------------------------
void sendLCD(int fila, char *mensaje)
{
if (fila == 1)
lcd_send_cmd(0x80);
// Mueve cursor a la fila 1, columna 1
else if (fila == 2)
lcd_send_cmd(0xC0);
// Mueve cursor a la fila 2, columna 1
lcd_print(mensaje);
// Escribe la cadena en la fila seleccionada
}
// --------------------------------------------------
// Función para convertir un entero a cadena
// --------------------------------------------------
char* num2str_int(int num)
{
static char buffer[16];
// Buffer estático para mantener la cadena después de salir de la función
sprintf(buffer, "%d", num);
// Convierte el entero en cadena decimal
return buffer;
}
// --------------------------------------------------
// Función para devolver una cadena tal cual
// --------------------------------------------------
char* num2str_str(char *str)
{
return str;
// Devuelve la cadena sin modificarla
}
/* =================================================
ISR
================================================= */
void IRAM_ATTR button_isr_handler(void* arg)
{
static uint32_t last_interrupt_time = 0;
uint32_t current_time = xTaskGetTickCountFromISR();
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Antirrebote de 200 ms
if((current_time - last_interrupt_time) > pdMS_TO_TICKS(200))
{
xSemaphoreGiveFromISR(boton_pasos, &xHigherPriorityTaskWoken);
last_interrupt_time = current_time;
}
if(xHigherPriorityTaskWoken)
portYIELD_FROM_ISR();
}
/* =================================================
TAREA SENSOR PASOS
================================================== */
void tarea_pasos(void *pvParameters)
{
int pas = 0;
while(1)
{
// Espera un mensaje de la cola (evento del botón)
if(xSemaphoreTake(boton_pasos, portMAX_DELAY))
{
pas++;
printf("Los pasos recorridos son: %d pasos\n", pas);
// Enviar dato a la cola (espera si está llena)
xQueueSend(cola_pasos, &pas, portMAX_DELAY);
}
}
}
/* =================================================
TAREA SENSOR TEMPERATURA
================================================== */
void tarea_temperatura(void *pvParameters)
{
int adc_raw;
const float R_fixed = 10000.0;
const float Beta = 3950.0;
const float T0 = 298.15;
const float R0 = 10000.0;
float voltaje;
float R_ntc;
float datocompartido;
while(1)
{
adc_oneshot_read(adc1_handle, ADC_CHANNEL_3, &adc_raw);
voltaje = (adc_raw * 3.3) / 4095.0;
// calcular resistencia del NTC
R_ntc = R_fixed * (voltaje / (3.3 - voltaje));
// ecuación Beta
datocompartido = 1.0 / ((1.0/T0) + (log(R_ntc/R0)/Beta));
datocompartido = datocompartido - 273.15;
printf("Temperatura: %.2f °C\n", datocompartido);
xQueueSend(cola_medicionesf, &datocompartido, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
/* =================================================
TAREA SENSOR BPM
================================================== */
void tarea_potenciometro(void *pvParameters)
{
int adc_raw;
float datocompartido;
while(1)
{
adc_oneshot_read(adc1_handle, ADC_CHANNEL_4, &adc_raw);
datocompartido = 40.0 + ((float)adc_raw / 4095.0) * 130.0;
printf("Ritmo cardiaco: %.1f BPM\n", datocompartido);
xQueueSend(cola_medicionesf, &datocompartido, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
/* =====================================================
TAREA DISPLAY (CONSUMIDOR)
===================================================== */
void tarea_display(void *pvParameters)
{
int pasos = 0;
float datocompartido;
i2c_master_init();
lcd_init();
sendLCD(1, "Hola USUARIX!");
vTaskDelay(pdMS_TO_TICKS(2000));
while(1)
{
int estado_gpio7 = gpio_get_level(GPIO_NUM_7); // lee el pin GPIO7
vTaskDelay(pdMS_TO_TICKS(20)); // espera 20 ms
if(estado_gpio7 == 1) // GPIO7 ALTO -> mostrar pasos
{ static int last_pasos = 0;
// Intentamos leer un nuevo valor de pasos, si hay
if (xQueueReceive(cola_pasos, &pasos, 0)) {
last_pasos = pasos; // actualizamos último valor
}
// Mostrar siempre el último valor en la LCD
char buffer[16];
sprintf(buffer, "%-5d", last_pasos);
sendLCD(1, "Pasos Recorridos:");
lcd_send_cmd(0XC0 + 5);
lcd_send_char(' ');
vTaskDelay(pdMS_TO_TICKS(20)); // espera 20 ms
sendLCD(2, buffer);
printf("Display muestra pasos: %d\n", last_pasos);
}
// GPIO7 BAJO -> mostrar temperatura y BPM
if(estado_gpio7 == 0){
static float last_temp = 0;
static float last_bpm = 0;
static bool mostrar_temp = true; // alterna cada 500 ms
// Leer todos los valores nuevos de la cola (puede haber temp o bpm)
float dato;
while(xQueueReceive(cola_medicionesf, &dato, 0))
{
// Determinar si es temp o bpm según rango esperado
if(dato < 50.0) // asumimos que temperatura < 50°C
last_temp = dato;
else // BPM > 40
last_bpm = dato;
}
lcd_send_cmd(0x01); // limpiar pantalla
vTaskDelay(pdMS_TO_TICKS(50));
if(mostrar_temp)
{
sendLCD(1, "Temp");
char str_temp[16];
sprintf(str_temp, "%.1f C", last_temp);
sendLCD(2, str_temp);
printf("Display muestra temperatura: %.2f C\n", last_temp);
}
else
{
sendLCD(1, "BPM");
char str_bpm[16];
sprintf(str_bpm, "%.1f", last_bpm);
sendLCD(2, str_bpm);
printf("Display muestra BPM: %.1f\n", last_bpm);
}
mostrar_temp = !mostrar_temp; // alternar para la próxima vez
vTaskDelay(pdMS_TO_TICKS(500)); // esperar 500 ms
}
vTaskDelay(pdMS_TO_TICKS(200)); // tiempo entre actualizaciones
}
}
/* ======================================================
FUNCIÓN PRINCIPAL
===================================================== */
void app_main(void){
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_POSEDGE, // interrupción por flanco de subida
.mode = GPIO_MODE_INPUT, // modo entrada
.pin_bit_mask = (1ULL << BOTON_GPIO),
.pull_down_en = 0,
.pull_up_en = 1 // pull-up interno
};
gpio_config(&io_conf);
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
};
adc_oneshot_new_unit(&init_config, &adc1_handle);
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = ADC_ATTEN_DB_11,
};
adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_3, &config);
// Crear cola de pasos
cola_pasos = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE);
// Crear semaforo del boton de pasos
boton_pasos = xSemaphoreCreateBinary();
// Crear cola de temperatura y ritmo cardiaco
cola_medicionesf = xQueueCreate(10, sizeof(float));
// Instalar servicio de ISR
gpio_install_isr_service(0);
// Asociar ISR al botón
gpio_isr_handler_add(BOTON_GPIO, button_isr_handler, NULL);
init_gpio7(); // inicializa GPIO7 como entrada
// Crear tareas de sensores y display
xTaskCreate(tarea_pasos, "Pasos", 4096, NULL, 2, NULL);
xTaskCreate(tarea_temperatura, "Temperatura", 4096, NULL, 2, NULL);
xTaskCreate(tarea_potenciometro, "BPM", 4096, NULL, 2, NULL);
xTaskCreate(tarea_display, "Display", 4096, NULL, 5, NULL);
}