DÍA 223 DE 365

Fase 4: FreeRTOS y Conectividad IoT

📖 Teoría a Estudiar

PRO_CPU y APP_CPU: Los Gemelos del ESP32

A nivel de hardware, los dos procesadores Xtensa LX6 del ESP32 se denominan oficialmente:

Asignación Explícita con FreeRTOS (Pinned Tasks)

En un sistema FreeRTOS tradicional (como nuestro STM32), usábamos xTaskCreate y el Scheduler (Planificador) decidía cuándo ejecutar cada hilo. Pero en ESP-IDF FreeRTOS, existe una función mejorada: xTaskCreatePinnedToCore(). Esta función nos permite obligar a una tarea a vivir exclusivamente en el Core 0 o en el Core 1.

Para nuestro Despachador, crearemos una arquitectura hermética: Una tarea en el Core 1 escuchará ininterrumpidamente el UART del STM32, verificando tramas binarias. Si la trama es válida, se la pasará a la tarea en el Core 0 a través de una Cola de Mensajes de FreeRTOS Inter-Core, la cual se encargará de subirla a la Nube. Así, un pico masivo de datos desde el STM32 jamás asfixiará el envío WiFi.

ASIGNACIÓN DE NÚCLEOS ESP-IDF (ASYMMETRIC MULTIPROCESSING) STM32 Micro Maestro UART RX Trama Bin ESP32 (CO-PROCESADOR IOT) CORE 1 (APP_CPU) Task_UART_RX Desempaqueta Bin JSON_Builder Genera String FreeRTOS Queue CORE 0 (PRO_CPU) LwIP / WiFi MAC Red Base (Hardware) Task_MQTT_Tx AWS TLS Engine WIFI TX JSON Cifrado AWS IoT

⚙️ Ejercicios Prácticos

Circuito: El cableado UART y la antena WiFi se mantienen intactos. La magia ocurre al reestructurar la función app_main() de nuestro proyecto en ESP-IDF.

Ejercicio 1: Crearemos una Cola de Mensajes global (Queue) para intercomunicar ambos núcleos de manera segura. Luego, instanciamos dos tareas, clavando (pinning) explícitamente la de UART al Core 1 y la de Nube al Core 0.


/**
 * @file main.c (ESP-IDF)
 * @brief Multiprocesamiento Asimétrico (AMP) para Arquitectura IoT Dual-Core
 */

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "protocolo_iot.h"  // Nuestra trama binaria (Día 222)

// =======================================================
// PUENTE DE COMUNICACIÓN INTER-CORE
// =======================================================
// Una Queue de FreeRTOS es 100% segura para uso entre núcleos (Thread-Safe).
QueueHandle_t cola_ventas_intercore;

// =======================================================
// TAREA DEL CORE 1 (APP_CPU) - EL MINISTRO DEL INTERIOR
// =======================================================
void Task_UART_Receiver(void *pvParameters) {
    
    Trama_Binaria_t trama_entrante;
    
    printf("[CORE 1] Tarea UART iniciada. Escuchando al STM32...\n");
    
    for (;;) {
        // Simulamos la recepción física del UART (Polling o Interrupción en ESP-IDF)
        // uart_read_bytes(UART_NUM_1, &trama_entrante, sizeof(Trama_Binaria_t), portMAX_DELAY);
        
        // Simulación: Si llegó un paquete válido con CRC correcto:
        if (trama_entrante.header.sync_word == FRAME_SYNC_WORD && trama_entrante.header.comando == CMD_ENVIAR_VENTA) {
            
            printf("[CORE 1] Trama válida recibida. Enviando al Core 0...\n");
            
            // Mandamos la venta a la Cola inter-núcleo
            xQueueSend(cola_ventas_intercore, &trama_entrante.payload.venta, portMAX_DELAY);
        }
        
        // Ceder control al Scheduler (Obligatorio en FreeRTOS)
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

// =======================================================
// TAREA DEL CORE 0 (PRO_CPU) - EL MINISTRO EXTERIOR
// =======================================================
void Task_MQTT_Cloud(void *pvParameters) {
    
    Payload_Venta_t venta_a_subir;
    char json_buffer[128];
    
    printf("[CORE 0] Tarea MQTT iniciada. Esperando órdenes del Core 1...\n");
    
    for (;;) {
        // La tarea en el Core 0 duerme bloqueada hasta que el Core 1 le pase una Venta
        if (xQueueReceive(cola_ventas_intercore, &venta_a_subir, portMAX_DELAY) == pdTRUE) {
            
            printf("[CORE 0] Procesando Venta de Gas: $%.2f\n", venta_a_subir.monto_pago);
            
            // 1. Convertir la estructura C a un JSON ASCII
            snprintf(json_buffer, sizeof(json_buffer), 
                     "{\"folio\":%lu, \"litros\":%.2f, \"monto\":%.2f}", 
                     venta_a_subir.folio_ticket, venta_a_subir.litros_gas, venta_a_subir.monto_pago);
            
            // 2. Ejecutar la subida nativa MQTT vía API de ESP-IDF
            // esp_mqtt_client_publish(cliente_mqtt, "gas_corp/jalisco/ventas", json_buffer, 0, 1, 0);
            
            printf("[CORE 0] Venta publicada exitosamente por WiFi.\n");
        }
    }
}

// =======================================================
// INICIALIZACIÓN DEL SISTEMA
// =======================================================
void app_main(void) {
    printf("--- SISTEMA DUAL-CORE INICIADO ---\n");
    
    // 1. Crear el puente (Cola) capaz de sostener 10 ventas en RAM
    cola_ventas_intercore = xQueueCreate(10, sizeof(Payload_Venta_t));
    
    // 2. Anclar la Tarea de Red al CORE 0 (PRO_CPU)
    // Parámetros: Función, Nombre, Stack(Bytes), Args, Prioridad, Handle, CORE_ID
    xTaskCreatePinnedToCore(
        Task_MQTT_Cloud,    
        "Task_MQTT",        
        8192,               // Stack grande para MbedTLS y WiFi
        NULL,               
        5,                  // Prioridad Alta
        NULL,               
        0                   // <--- Anclado al CORE 0
    );
    
    // 3. Anclar la Tarea de Hardware/UART al CORE 1 (APP_CPU)
    xTaskCreatePinnedToCore(
        Task_UART_Receiver, 
        "Task_UART",        
        4096,               // Stack menor, solo hace parsing binario
        NULL,               
        5,                  // Prioridad Alta
        NULL,               
        1                   // <--- Anclado al CORE 1
    );
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Aislamiento de Procesos (Process Isolation) y Determinismo Absoluto en Telecomunicaciones.

En el mundo real, los problemas ocurren simultáneamente. Imagina que en el instante exacto en que tu Despachador está negociando la pesada matemática del certificado SSL/TLS con Amazon AWS, el microcontrolador maestro (STM32) le dispara por el cable un comando urgente a 921,600 bps para consultar la hora. Si todo esto ocurriera en un solo núcleo (Single-Core), el procesador tendría que pausar el cifrado TLS para atender al STM32, perdiendo los paquetes WiFi y forzando una desconexión general de red.

Al diseñar el firmware con **FreeRTOS Pinned Tasks (Multiprocesamiento Asimétrico)**, has levantado un muro de titanio entre los procesos. El Core 0 de tu ESP32 puede estar sudando al 95% de uso de CPU encriptando datos AES-256 para enviarlos a la nube, y el Core 1 seguirá libre, operando al 1% de uso, contestándole al STM32 con latencia absolutamente nula y transfiriendo paquetes en la sombra a través de la Cola de Mensajes. Tu Despachador de Gas LP ahora goza de la misma concurrencia de hardware que tiene el procesador de un teléfono inteligente, erradicando los "cuellos de botella" de telecomunicaciones.

📝 Resumen del Día