Cuando usas comunicación binaria, envías los bytes exactos tal como existen en la memoria RAM del procesador. Un número flotante de 32 bits (como 25.50) ocupa exactamente 4 bytes en memoria. Si lo envías como ASCII ("25.500000"), ocupa 9 bytes. Si tienes una trama de telemetría completa, pasar de texto a binario reduce el uso de memoria y ancho de banda en más de un 60%.
A diferencia del texto, donde buscas un salto de línea (\n) para saber que terminó el mensaje, en un protocolo binario una trama tiene una estructura posicional matemática estricta:
0xAA 0x55) que le dice al receptor: "¡Ojo! Aquí empieza un paquete válido, no es ruido eléctrico".struct cruda de C con los datos del gas.Struct PaddingLos compiladores modernos de C (GCC) están diseñados para ser rápidos, no eficientes en espacio. Por defecto, agregan bytes de "basura" (Padding) entre las variables de tus structs para alinearlas en direcciones de memoria pares de 32 bits. Si envías un struct normal por UART, enviarás basura, y el ESP32 lo decodificará mal.
La regla de oro de los protocolos binarios es obligar al compilador a empacar los bytes sin dejar espacios vacíos, usando __attribute__((packed)) o #pragma pack(1).
Circuito: Físicamente, nuestro STM32 y nuestro ESP32 se siguen conectando por los mismos pines UART (TX/RX), y deben compartir Tierra (GND). Solo aumentaremos la velocidad del puerto (Baud Rate) de los obsoletos 115,200 bps a veloces 921,600 bps, aprovechando que ya no manejaremos textos.
Ejercicio 1: Crearemos el archivo de cabecera compartido (protocolo_iot.h). Este archivo será incluido exactamente igual en el proyecto de CubeIDE del STM32 y en el proyecto ESP-IDF del ESP32, asegurando que ambos hablen el mismo lenguaje binario.
/**
* @file protocolo_iot.h
* @brief Cabecera Universal Compartida STM32 <--> ESP32
*/
#ifndef PROTOCOLO_IOT_H
#define PROTOCOLO_IOT_H
#include <stdint.h>
// =======================================================
// 1. CONSTANTES DEL PROTOCOLO DE CAPA DE ENLACE
// =======================================================
#define FRAME_SYNC_WORD 0xAA55 // Firma de inicio de trama
#define PROTOCOL_VERSION 0x01
// Definición de Comandos (CMD_ID)
typedef enum {
CMD_ACK = 0x00, // Respuesta general de acuse de recibo
CMD_ENVIAR_VENTA = 0x01, // STM32 pide a ESP32 subir venta a MQTT
CMD_HEARTBEAT = 0x02, // STM32 envía signos vitales
CMD_RECIBIR_PRECIO = 0x03, // ESP32 notifica a STM32 nuevo precio de nube
CMD_WIFI_STATUS = 0x04 // STM32 pregunta al ESP32 si hay internet
} Comando_IoT_t;
// =======================================================
// 2. EMPAQUETADO DE ESTRUCTURAS (CERO PADDING)
// =======================================================
// El macro pragma pack(push, 1) le indica al compilador GCC que alinee
// los bytes de las variables de a 1, eliminando la basura intermedia.
#pragma pack(push, 1)
// --- A) EL ENCABEZADO (HEADER) DE LA TRAMA ---
typedef struct {
uint16_t sync_word; // 2 bytes (Siempre 0xAA55)
uint8_t longitud; // 1 byte (Tamaño en bytes del payload)
uint8_t comando; // 1 byte (De la enumeración Comando_IoT_t)
} Trama_Header_t; // Peso Total: 4 bytes exactos
// --- B) PAYLOADS ESPECÍFICOS ---
// Payload para CMD_ENVIAR_VENTA (Lo que medimos del gas)
typedef struct {
uint32_t folio_ticket; // 4 bytes
float litros_gas; // 4 bytes
float monto_pago; // 4 bytes
uint32_t timestamp; // 4 bytes (Tiempo UNIX)
} Payload_Venta_t; // Peso Total: 16 bytes exactos
// Payload para CMD_RECIBIR_PRECIO (Lo que viene de AWS)
typedef struct {
float nuevo_precio; // 4 bytes
} Payload_Precio_t; // Peso Total: 4 bytes exactos
// --- C) LA TRAMA COMPLETA UNIFICADA ---
typedef struct {
Trama_Header_t header;
// Usamos una Unión C para que la trama ocupe el espacio del payload más grande
// sin desperdiciar memoria, dependiendo del comando enviado.
union {
Payload_Venta_t venta;
Payload_Precio_t precio;
// ... otros payloads ...
} payload;
uint8_t crc8; // 1 byte (Firma de seguridad final)
} Trama_Binaria_t;
#pragma pack(pop) // Restauramos el comportamiento normal del compilador C
#endif // PROTOCOLO_IOT_H
Objetivo del día: Optimización Extrema del Ancho de Banda Local y Determinismo de Ejecución.
En el viejo modelo ASCII, para decirle al ESP32 que querías subir una venta, tenías que generar una larga cadena: `AT+MQTTPUB=0,"gas/ventas","{\"ticket\":145, \"litros\":25.5, \"monto\":267.75}",1,0\r\n`. Esta cadena pesa aproximadamente 85 bytes. El STM32 invertía valiosos milisegundos ejecutando `snprintf` para construirla. El puerto UART tardaba casi 1 milisegundo entero en escupirla. El ESP32 pasaba más tiempo buscando las comillas dobles y el salto de línea que procesando los datos.
Al diseñar el **Protocolo Custom Binario Empaquetado**, has obliterado el overhead. Para la misma venta, el STM32 ahora toma su estructura en C pura de 16 bytes, le pega un encabezado de 4 bytes y un checksum, dando un total de **21 bytes exactos**. No hay conversión de `float` a texto. El STM32 dispara el puntero de memoria directamente al DMA (Acceso Directo a Memoria) del UART. En microsegundos, la trama está en el ESP32. El ESP32 la recibe, verifica el CRC, revisa el campo `comando` y en tiempo constante (`O(1)`) sabe exactamente qué hacer. El uso de RAM y CPU entre ambos procesadores ha caído drásticamente, incrementando la inmunidad del sistema al ruido eléctrico industrial. Has diseñado el protocolo de una verdadera red interna (Intranet de Hardware).
struct dentro de las directivas #pragma pack(1) o __attribute__((packed)), forzando un empaquetado de memoria hermético y libre de huecos..h) entre los proyectos de CubeIDE (STM32) y VS Code (ESP-IDF) asegura que ambos procesadores concuerden exactamente en el peso y significado de la trama, eliminando la principal fuente de bugs en sistemas de Cómputo Distribuido asimétricos.