DÍA 336 DE 365

Fase 5: Diseño Industrial y Hardware

📖 Teoría a Estudiar

El Pecado Mortal de la Librería Estándar (`sprintf`)

Cuando necesitas mostrar el precio "$120.50" en la pantalla LCD, el atajo fácil es usar sprintf(buffer, "$%.2f", precio);. El problema es que sprintf es una monstruosidad de propósito general. Al usarla una sola vez, obligas al compilador a importar miles de líneas de código de formateo, arrastrando las librerías de punto flotante. Solo por usar sprintf, el tamaño de tu archivo binario en la Flash se infla entre 10 KB y 15 KB, y quema valiosos ciclos de reloj empaquetando strings.

En el despachador de gas LP, nosotros ya usamos matemáticas de punto fijo y variables `stdint.h`. En lugar de `sprintf`, escribiremos una función ligera que convierte números enteros a cadenas ASCII realizando divisiones básicas. Cero punto flotante, cero librerías pesadas.

Optimización de Búferes Circulares (Módulo vs Bitwise AND)

Tu módulo Wi-Fi (ESP32) recibe comandos seriales. Al almacenarlos en un Búfer Circular (Ring Buffer), seguro utilizaste el operador de módulo matemático para reiniciar el índice: head = (head + 1) % BUFFER_SIZE;. El operador módulo (%) es internamente una división. En micros sin divisor por hardware, esto toma decenas de ciclos de reloj. La optimización radical exige que el tamaño de tu búfer sea siempre una **potencia de 2** (ej. 64, 128, 256 bytes). Al hacerlo, puedes reemplazar la pesada división por una operación binaria AND súper rápida: head = (head + 1) & (BUFFER_SIZE - 1);, lo cual se ejecuta en un solo ciclo de CPU.

IMPACTO DE LA REFACTORIZACIÓN EN HARDWARE HUELLA DE MEMORIA FLASH (CODE SIZE) CÓDIGO CON SPRINTF() Core Logic +14 KB stdlib / float CÓDIGO LIGERO (ITOA) Core Logic Custom Ahorro Masivo SRAM: ORPHAN VARIABLES & BUFFERS Var No Usada Buffer[1024] (Solo usa 50) Antes (Deuda) Buffer[64] (Power of 2) LIBRE PARA RTOS STACKS Después (Optimizado) RING BUFFER WRAP-AROUND (CPU CYCLES) idx = (idx + 1) % 64; División matemática. Costo: ~12 a 20 ciclos de reloj VS idx = (idx + 1) & (64 - 1); Enmascaramiento de bits (Bitwise AND). Costo: 1 solo ciclo de reloj

⚙️ Procedimiento Práctico: Limpieza de RAM y Flash

Paso 1: Auditar con GCC Flags.
Activa los avisos del compilador en tu IDE: -Wall -Wextra -Wunused-variable -Wunused-function. El compilador escupirá una lista de todas las variables que declaraste en tus primeros meses de prueba y que ya no usas. Bórralas. Cada arreglo char temp_buf[256]; que no se usa está robando memoria valiosísima.

Paso 2: Alinear Buffers a Potencias de 2.
Busca todos tus Buffers Circulares (UART RX, Logs de SD). Si declaraste un tamaño de 100, cámbialo a 128. Si era 50, cámbialo a 64. Sustituye todas las operaciones de módulo % SIZE por el enmascaramiento binario & (SIZE - 1).

💻 Firmware: Aniquilando `sprintf`

Para la metrología de nuestro Despachador de Gas LP, usamos aritmética de Punto Fijo (Fixed-Point Math) desde el Día 309 para evitar los *floats*. Escribiremos nuestra propia función, específica y súper rápida, para convertir el volumen y el dinero a una cadena de texto lista para la pantalla LCD.


/**
 * @file utils_fast_string.c
 * @brief Funciones ligeras de manipulación de cadenas para eliminar Deuda Técnica
 */

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

// =========================================================================
// REEMPLAZO LIGERO DE SPRINTF() PARA DINERO Y VOLUMEN
// =========================================================================
/**
 * @brief Convierte un entero a una cadena ASCII de forma ultra rápida.
 * @param value: El número a convertir (Ej. 12550 centavos)
 * @param buffer: El arreglo donde se guardará el texto
 * @param decimal_places: Cuántos decimales fingir (Ej. 2 para "$125.50")
 * @return Cantidad de caracteres escritos
 */
uint8_t Fast_IntToStr_FixedPoint(uint32_t value, char* buffer, uint8_t decimal_places) {
    char temp[12]; // Buffer inverso temporal (Un uint32_t tiene max 10 dígitos)
    uint8_t i = 0;
    uint8_t chars_written = 0;
    
    // Caso especial para 0
    if (value == 0) {
        buffer[chars_written++] = '0';
        if (decimal_places > 0) {
            buffer[chars_written++] = '.';
            for (uint8_t d = 0; d < decimal_places; d++) buffer[chars_written++] = '0';
        }
        buffer[chars_written] = '\0';
        return chars_written;
    }

    // Extraer dígitos en orden inverso mediante divisiones sucesivas
    while (value > 0 || i < decimal_places + 1) { // Asegurar que llegamos a los decimales
        if (i == decimal_places && decimal_places > 0) {
            temp[i++] = '.'; // Insertar punto decimal virtual
        } else {
            temp[i++] = (value % 10) + '0'; // Convertir residuo a carácter ASCII
            value /= 10;
        }
    }

    // Invertir el arreglo en el buffer final
    while (i > 0) {
        buffer[chars_written++] = temp[--i];
    }
    
    buffer[chars_written] = '\0'; // Terminador nulo de C
    return chars_written;
}

// =========================================================================
// OPTIMIZACIÓN DE RING BUFFER (POTENCIA DE 2)
// =========================================================================
// Antes (Deuda): #define UART_RX_SIZE 100
#define UART_RX_SIZE 128 // DEBE ser potencia de 2 (2, 4, 8, 16... 128)

volatile char UART_Rx_Buffer[UART_RX_SIZE];
volatile uint32_t Rx_Head = 0;

/**
 * @brief Empuja un byte al búfer circular (Llamado desde la interrupción UART)
 */
void UART_Buffer_Push_Optimized(char data) {
    
    // Antes (Módulo pesado):
    // Rx_Head = (Rx_Head + 1) % UART_RX_SIZE;  <-- LENTO! Involucra división.
    
    // Ahora (Bitwise rápido):
    // Al restar 1 a 128 obtenemos 127 (0111_1111 en binario). 
    // Un AND bit a bit actúa como un módulo perfecto sin dividir.
    Rx_Head = (Rx_Head + 1) & (UART_RX_SIZE - 1); // <-- RÁPIDO! Un solo ciclo de reloj.
    
    UART_Rx_Buffer[Rx_Head] = data;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Saneamiento del Mapa de Memoria y Preparación para el RTOS.

El código de un ingeniero se vuelve sagrado no por lo que añade, sino por lo que tiene el coraje de quitar. Al eliminar `sprintf()` y las librerías de punto flotante (`math.h`) de tu proyecto, tu archivo `.bin` compilado redujo su peso en **14 Kilobytes de memoria Flash**. Esto no solo libera espacio, sino que acelera drásticamente el proceso de inyección en la fábrica (Día 330) y el tiempo que tarda el Despachador en descargarse un parche inalámbrico (OTA - Día 324).

Además, al purgar los arreglos (arrays) masivos que creaste "por las dudas" durante el prototipado, y al refactorizar tus búferes de UART a tamaños de potencia de 2 (`128` en lugar de `100`), has recuperado valiosísimos Kilobytes de SRAM. Esta memoria RAM que acabamos de rescatar de la basura de la Deuda Técnica no se quedará vacía. Mañana inicia la Fase 6, y necesitaremos cada uno de esos bytes para crear los *Task Stacks* y el *Heap* del kernel de FreeRTOS. Has limpiado la casa; el invitado puede entrar.

📝 Resumen del Día