A nivel de hardware, los dos procesadores Xtensa LX6 del ESP32 se denominan oficialmente:
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.
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
);
}
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.