Cuando imprimes un ticket (Día 136), el humano necesita leer caracteres ASCII: "20.00 Lts". Esos caracteres ocupan 9 bytes de memoria (incluyendo el espacio). Sin embargo, computacionalmente, el número 20000 (expresado en mililitros) cabe perfectamente en un entero de 16 bits (2 bytes).
Al guardar las ventas (Logs) en la Flash (Día 148) o transmitirlas por Modbus, jamás debes usar `sprintf` ni texto. Debes transmitir la estructura (struct) de lenguaje C en su forma binaria original pura (Serialización). Una venta completa que en texto tomaría 100 bytes, en binario puro solo toma 16 bytes.
Los procesadores ARM de 32 bits odian leer datos desalineados. Si creas una estructura en C con una variable de 1 byte (uint8_t) seguida de una de 4 bytes (uint32_t), el compilador GCC inyectará 3 bytes basura (Padding) en medio silenciosamente para que la variable de 4 bytes quede "alineada" a direcciones múltiplos de 4. Esto acelera el CPU, pero engorda tu estructura arruinando tus transmisiones RS485 o el espacio en la Flash.
En el software industrial Bare-Metal, debemos usar el atributo __attribute__((packed)) para obligar al compilador a compactar la estructura byte con byte, sin inyectar basura, asegurando que un paquete de 16 bytes ocupe matemáticamente 16 bytes en la red.
Ejercicio 1: Definiremos la estructura de nuestro registro TransactionLog_t empaquetada estrictamente para que pese exactamente 16 bytes. Construiremos la función que captura las variables globales al finalizar la venta de gas (Fecha BCD, Litros, Importe), calcula un CRC-16 interno (para proteger el registro) y lo deposita como un paquete inmutable en la memoria Flash usando nuestro algoritmo de Wear Leveling (Día 148).
/**
* @file transaction_log.c
* @brief Estructura de Paquetes Binarios (Packed) y Serialización en Flash
*/
#include "stm32f401xe.h"
#include <stdint.h>
// Dependencias de días anteriores
extern uint16_t calcular_crc16_modbus(uint8_t *buffer, uint16_t longitud); // Día 149
extern void wear_leveling_guardar_bloque(uint8_t* datos, uint16_t len); // Día 148 modificado
// =========================================================================
// DEFINICIÓN DE ESTRUCTURA BINARIA (PACKED STRUCT)
// =========================================================================
// Forzar al compilador ARM GCC a no inyectar padding (relleno).
// Las variables se agruparán exactamente byte tras byte.
#pragma pack(push, 1)
typedef struct {
uint32_t ticket_id; // Número de folio único (4 Bytes)
uint32_t rtc_timestamp; // Fecha y Hora en Formato BCD (4 Bytes)
uint32_t litros_x1000; // Litros multiplicados por mil (4 Bytes)
uint16_t importe_cents; // Dinero cobrado en centavos (2 Bytes)
uint16_t crc_firma; // Firma de validación (2 Bytes)
} __attribute__((packed)) TransactionLog_t;
#pragma pack(pop)
// En este momento, sizeof(TransactionLog_t) es EXACTAMENTE 16 bytes.
// =========================================================================
// VARIABLES DEL SISTEMA (Estado Actual de la Venta)
// =========================================================================
extern uint32_t venta_actual_litros_x1000;
extern uint16_t venta_actual_importe_cents;
extern uint32_t rtc_obtener_fecha_hora_combinada(void); // Combina DR y TR (Día 141)
// Usamos el Dominio BKP para que el consecutivo de tickets jamás se reinicie
#define BKP_TICKET_COUNTER RTC->BKP5R
/**
* @brief Empaqueta la venta finalizada y la dispara a la memoria persistente
*/
void Log_GuardarVentaFinalizada(void) {
// 1. Crear el paquete binario temporal en la RAM (Local Stack)
TransactionLog_t paquete;
// 2. Llenar los campos con los datos de la venta recién terminada
// Incrementamos el folio fiscal usando el backup register
BKP_TICKET_COUNTER++;
paquete.ticket_id = BKP_TICKET_COUNTER;
paquete.rtc_timestamp = rtc_obtener_fecha_hora_combinada();
paquete.litros_x1000 = venta_actual_litros_x1000;
paquete.importe_cents = venta_actual_importe_cents;
// 3. SELLO CRIPTOGRÁFICO
// Calculamos el CRC-16 de todo el paquete EXCEPTO los últimos 2 bytes (el propio crc).
// Serializamos el struct tratándolo como un simple arreglo de uint8_t (Bytes).
uint16_t longitud_sin_crc = sizeof(TransactionLog_t) - 2;
paquete.crc_firma = calcular_crc16_modbus((uint8_t*)&paquete, longitud_sin_crc);
// 4. GUARDADO FÍSICO
// Enviamos el bloque binario de 16 bytes completos al gestor de Flash (Append-Only)
wear_leveling_guardar_bloque((uint8_t*)&paquete, sizeof(TransactionLog_t));
// Nota: Cuando el Servidor Modbus (Día 140) nos pregunte "Dame la venta 1402",
// simplemente sacaremos el paquete de 16 bytes de la Flash, comprobaremos
// el CRC internamente, y enviaremos esos 16 bytes crudos por UART RS485.
}
int main(void) {
// Inicialización del contador fiscal al primer encendido de la máquina
// if (BKP_TICKET_COUNTER == 0xFFFFFFFF || BKP_TICKET_COUNTER == 0) {
// BKP_TICKET_COUNTER = 1000; // Empezar folios en 1000
// }
while(1) {
// Simulación: La rutina del PID (Día 152) cerró la válvula al 0%
// if (bandera_venta_terminada) {
//
// // ¡Transacción confirmada en silicio!
// Log_GuardarVentaFinalizada();
//
// // Reiniciar variables para el siguiente cliente
// venta_actual_litros_x1000 = 0;
// venta_actual_importe_cents = 0;
// bandera_venta_terminada = false;
// }
}
return 0;
}
Objetivo del día: Optimización de Memoria Fiscal (Registros Históricos).
En el sector energético, auditar es ley. La Secretaría de Energía requiere que el Despachador conserve en memoria local un historial de los últimos miles de despachos para combatir el "huachicol" o fraudes. Si usaras cadenas de texto ASCII para guardar "Ticket: 1024, Litros: 15.5", llenarías el Sector 6 de tu memoria Flash (128 KB) en solo 2,000 ventas (apenas unos días de trabajo).
Al aplicar la disciplina de la **Serialización Binaria Empaquetada**, has logrado meter toda la información vital de una venta, incluyendo su marca de tiempo criptográfica, en apenas **16 Bytes**. Con este diseño, ese mismo Sector 6 de Flash ahora puede alojar **8,192 transacciones completas**. Tu máquina puede almacenar el historial detallado de varios meses de operaciones comerciales sin sobreescribir datos viejos ni recurrir a costosas tarjetas MicroSD. Esta es la esencia de la arquitectura Bare-Metal industrial.
structs de lenguaje C para alinear variables a direcciones de 32 bits, optimizando velocidad pero destruyendo la densidad de almacenamiento y la compatibilidad de red.__attribute__((packed)) (o las directivas #pragma pack) para suprimir el Padding, garantizando que el paquete ocupe el mínimo espacio matemático real.(uint8_t*)&struct) permite que algoritmos genéricos como el CRC o el driver UART/SPI puedan devorar y procesar cualquier estructura compleja de C tratándola simplemente como una tira de bytes.