DÍA 022 DE 365

Fase 1: Fundamentos de C y Lógica de Software

📖 Teoría Explícita: Estructuras (`struct`) y Memoria Embebida

Una estructura (`struct`) en C es un tipo de dato compuesto definido por el usuario que permite agrupar variables de diferentes tipos bajo un único nombre en un bloque contiguo de memoria. En un despachador industrial, la venta realizada no es un número aislado: es un conjunto indisociable de metadatos (ID de venta, volumen surtido, precio unitario, fecha/hora y estado de la transacción).

1. Alineación de Memoria y Padding (Relleno)

Los microcontroladores de 32 bits como el STM32 (ARM Cortex-M) leen la memoria RAM de manera más eficiente en accesos alineados a palabras de 32 bits (4 bytes). Por defecto, el compilador inserta bytes de relleno invisibles (padding) entre los miembros de una estructura para asegurar que las variables de 32 bits inicien en direcciones de memoria divisibles por 4.

Si intentamos guardar un `struct` directamente a una memoria EEPROM no volátil por I2C/SPI o transmitirlo por radio al ESP32, estos bytes basura malgastarán espacio y corromperán la comunicación. Para evitarlo en almacenamiento y telemetría, utilizamos la directiva de compilador __attribute__((packed)), eliminando todo el padding interno.

2. Paso por Valor vs Paso por Referencia (Punteros a Structs)

Pasar una estructura por valor a una función implica copiar byte por byte todo su contenido dentro del Stack (pila). Si la estructura mide 32 bytes y llamamos esa función con alta frecuencia dentro de una tarea de FreeRTOS, agotaremos la memoria del Stack provocando un HardFault Reset. La regla de oro en embebidos es pasar estructuras siempre mediante punteros constantes (`const TicketDespacho_t *ticket`): se transmite únicamente la dirección de memoria (4 bytes) y el calificador const previene modificaciones accidentales.

MAPA DE MEMORIA DE LA ESTRUCTURA `TicketDespacho_t` Sin Packed (Con Padding - 24 Bytes) id (4B) vol_ml (4B) precio (4B) total (4B) mang PAD(3B) Con __attribute__((packed)) - 18 Bytes Totales id (4B) vol_ml (4B) precio (4B) total (4B) mang stat const TicketDespacho_t *ptr Acceso: ptr->volumen_ml (Solo 4B en Stack) VENTAJAS EN EMBEBITOS 1. Serialización binaria directa por UART. 2. Escritura bloque a bloque en EEPROM. 3. Protección del Stack de FreeRTOS. 4. Modularidad en la arquitectura C.

⚡ Circuitos e Interconexión: Impresora Térmica RS232/TTL

El ticket generado por la estructura debe enviarse físicamente a una impresora térmica de recibos (ej. mecanismo embebido TTL a 9600 baudios) o a un chip EEPROM externo por bus I2C (ej. AT24C256) para auditoría fiscal.

Pin STM32F4 Dispositivo Periférico Función de la Señal Nivel Lógico
PA2 (USART2_TX) Impresora RX / ESP32 UART Envío serial de los bytes formateados del Ticket 3.3V TTL
PB6 (I2C1_SCL) Pin SCL de Memoria EEPROM 24C256 Reloj para almacenamiento del struct empaquetado 3.3V Open-Drain
PB7 (I2C1_SDA) Pin SDA de Memoria EEPROM 24C256 Datos bidireccionales del registro fiscal 3.3V Open-Drain

⚙️ Ejercicios Prácticos: Código de Gestión de Ticket

A continuación desarrollamos el módulo en C para definir la estructura del ticket, calcular montos en punto fijo (sin flotantes) y serializar la estructura hacia el buffer de transmisión de la impresora o telemetría.


#include <stdint.h>
#include <stdbool.h>

// --- 1. DEFINICIÓN DE LA ESTRUCTURA DEL TICKET (EMPAQUETADA) ---
// La directiva __attribute__((packed)) evita que el compilador agregue bytes de relleno (padding)
typedef struct __attribute__((packed)) {
    uint32_t id_transaccion;      // Identificador único (4 bytes)
    uint32_t volumen_ml;          // Volumen surtido en mililitros (4 bytes)
    uint32_t precio_litro_cent;   // Precio por litro en centavos (4 bytes)
    uint32_t total_pagar_cent;    // Importe total calculado en centavos (4 bytes)
    uint8_t  id_manguera;         // Número de manguera (1 byte)
    uint8_t  estado_transaccion;  // 0: OK, 1: Paro Emergencia, 2: Falla Flujo (1 byte)
} TicketDespacho_t;

// --- 2. PROTOTIPOS DE FUNCIÓN ---
void inicializar_ticket(TicketDespacho_t *ticket, uint32_t id, uint8_t manguera);
void calcular_total_ticket(TicketDespacho_t *ticket, uint32_t volumen_ml, uint32_t precio_litro_cent);
void enviar_ticket_uart(const TicketDespacho_t *ticket);

// Auxiliar del Día 19: Conversor a ASCII sin sprintf
static void uint32_a_ascii(uint32_t valor, char *buf);

// --- 3. IMPLEMENTACIÓN ---

/**
 * @brief Inicializa los valores por defecto de la transacción.
 */
void inicializar_ticket(TicketDespacho_t *ticket, uint32_t id, uint8_t manguera) {
    if (ticket == NULL) return;
    
    ticket->id_transaccion = id;
    ticket->volumen_ml = 0;
    ticket->precio_litro_cent = 0;
    ticket->total_pagar_cent = 0;
    ticket->id_manguera = manguera;
    ticket->estado_transaccion = 0; // Estado OK
}

/**
 * @brief Calcula el costo final usando punto fijo exacto.
 * @param ticket Puntero a la estructura original (modificación directa)
 */
void calcular_total_ticket(TicketDespacho_t *ticket, uint32_t volumen_ml, uint32_t precio_litro_cent) {
    if (ticket == NULL) return;

    ticket->volumen_ml = volumen_ml;
    ticket->precio_litro_cent = precio_litro_cent;

    // Cálculo en punto fijo: (ml * centavos_por_litro) / 1000 ml
    // Usamos cast temporal a uint64_t para evitar overflow si la venta es grande
    uint64_t importe_temporal = ((uint64_t)volumen_ml * precio_litro_cent) / 1000;
    ticket->total_pagar_cent = (uint32_t)importe_temporal;
}

/**
 * @brief Procesa y emite el ticket a través de la interfaz serie.
 *        Utiliza paso por referencia 'const' para no clonar memoria en el Stack.
 */
void enviar_ticket_uart(const TicketDespacho_t *ticket) {
    if (ticket == NULL) return;

    // Extraer pesos y centavos para la impresión
    uint32_t pesos = ticket->total_pagar_cent / 100;
    uint32_t centavos = ticket->total_pagar_cent % 100;
    
    uint32_t litros = ticket->volumen_ml / 1000;
    uint32_t mililitros = ticket->volumen_ml % 1000;

    // Simulación de envío de trama limpia para impresora/telemetría
    // En código real se utiliza la HAL_UART_Transmit(&huart2, ...);
}

int main(void) {
    // Instancia en la memoria local (RAM) de la tarea
    TicketDespacho_t mi_venta;

    // 1. Iniciar un despacho en manguera #1 con ID 10452
    inicializar_ticket(&mi_venta, 10452, 1);

    // 2. Simular término de despacho: 12.500 Litros (12500 ml) a $20.50/L (2050 centavos)
    calcular_total_ticket(&mi_venta, 12500, 2050);

    // 3. Emitir recibo
    enviar_ticket_uart(&mi_venta);

    while(1) {
        // Bucle principal del RTOS
    }

    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Hito Logrado: Encapsulamiento y Persistencia de Transacciones.

Con la implementación de TicketDespacho_t, has logrado empaquetar toda la información de un despacho en exactamente 18 bytes contiguos. Esta abstracción permite almacenar registros históricos directamente en bloques de la memoria EEPROM no volátil del sistema, garantizando auditoría fiscal ante cortes repentinos de energía durante el suministro de gas.

📝 Resumen del Día

❓ Cuestionario de Autoevaluación

1. ¿Qué ocurriría si pasas una estructura de 128 bytes por valor dentro de una función ejecutada en un callback de interrupción (ISR) o tarea de bajo stack?
Respuesta: El compilador generará instrucciones para duplicar byte por byte los 128 bytes dentro del Stack. En un entorno con pila reducida, esto causará un desbordamiento inmediato de pila (Stack Overflow), corrompiendo registros adyacentes y provocando un reinicio por HardFault.

2. ¿Por qué es vital usar `__attribute__((packed))` al guardar una estructura directamente en un chip de memoria EEPROM externa?
Respuesta: Sin `packed`, los bytes de relleno (padding) generados por el compilador para alineación de 32 bits se escribirán físicamente en la memoria EEPROM, malgastando espacio no volátil y rompiendo la compatibilidad de lectura si otro sistema o compilador intenta interpretar el registro binario.