DÍA 363 DE 365

Fase 5: Diseño Industrial y Hardware

📖 Teoría a Estudiar

La Falla Estructural del Estándar IEEE 754

En el lenguaje C, una variable float ocupa 32 bits de memoria. Utiliza el estándar IEEE 754, que divide el número en un bit de signo, un exponente y una mantisa (fracción). El problema fatal es que los ordenadores piensan en potencias de 2 (base binaria), mientras que el dinero y el volumen se miden en potencias de 10 (base decimal). Ciertas fracciones decimales perfectas como 0.1 o 0.015 (la equivalencia volumétrica de tu sensor Hall) no tienen una representación binaria exacta. Son el equivalente binario de 1/3 (0.3333... infinito).

El Desastre Metrológico Acumulativo

Cuando escribes float volumen = pulsos * 0.1;, el procesador realmente multiplica por 0.10000000149.... En un pulso, el error es invisible. Pero al acumular millones de pulsos durante meses en una estación de gas, ese error microscópico se acumula. La pantalla mostrará 10,000 Litros, pero la matemática subyacente se habrá desviado, provocando discrepancias de auditoría millonarias.

Aritmética de Punto Fijo (Fixed-Point Math)

La industria aeroespacial y metrológica prohíbe el uso de floats para sumas acumulativas. En su lugar, utilizamos Tipos Enteros de Ancho Exacto (uint32_t, uint64_t). Elevamos la escala mentalmente: Si un pulso equivale a 15 mililitros (0.015 L), no usamos decimales. Guardamos el valor estrictamente como 15 en un entero sin signo. Toda la matemática se ejecuta en números enteros puros (imposibles de redondear mal). La conversión a decimal ($V = Entero \times 10^{-N}$) solo ocurre en el último milisegundo, exclusivamente como un truco visual para el display del usuario.

REPRESENTACIÓN METROLÓGICA: FLOAT VS FIXED-POINT FLOAT 32-BIT (IEEE 754) float precio = 10.45; S Exp Mantisa (Fracción) REALIDAD FÍSICA EN RAM: 10.4499998092651367 Base 2 no puede representar fracciones decimales exactas. PENALIZACIÓN DEL COMPILADOR Si no hay FPU: +200 Ciclos por multiplicación. FIXED-POINT MATH (stditn.h) uint32_t precio_x100 = 1045; 0000 0000 0000 0000 0000 0100 0001 0101 REALIDAD FÍSICA EN RAM: 1045 (Exacto) 1 Ciclo de Reloj. Error de redondeo = 0.00% ILUSIÓN PARA EL USUARIO (LCD) Entero: 1045 / 100 = 10 Fracción: 1045 % 100 = 45

⚙️ Procedimiento Práctico: Implementando "Fixed-Point"

Paso 1: Eliminar el sprintf del Proyecto.
Las funciones de la librería estándar (stdio.h) que manejan cadenas de texto formateadas están diseñadas para sistemas operativos pesados. En un microcontrolador (como tu NXP Kinetis o STM32), llamar a sprintf(buffer, "%f", volumen); inyecta hasta 15 KB de código basura en tu memoria Flash. Debemos usar rutinas manuales de formato ASCII que aíslen la parte entera mediante división (/) y la parte fraccionaria mediante el operador módulo (%).

Paso 2: Acumuladores Seguros en 64 bits.
Al no usar floats, nuestras multiplicaciones producirán números masivos. Si tenemos 200,000 mililitros (200 L) y el precio es de \$1,045 centavos, multiplicar ambos ($200,000 \times 1045 = 209,000,000$) entra peligrosamente cerca del límite de un registro de 32 bits ($4.2 \times 10^9$). Todas las ecuaciones temporales de costo deben moldearse momentáneamente a uint64_t para eludir desbordamientos.

💻 Firmware C: Motor Metrológico Exacto

Este es el núcleo de facturación de tu Despachador. Nota la absoluta ausencia de variables con coma flotante, respetando el canon de programación industrial crítica que has venido forjando a lo largo del año.


/**
 * @file fixed_point_metrology.c
 * @brief Algoritmos de Precisión: Cálculo de Volumen y Costo en Punto Fijo
 * Diseñado para inmunidad total contra errores IEEE 754 y latencia de FPU.
 */

#include <stdint.h>

// =========================================================================
// DEFINICIÓN DE ESCALAS Y CONSTANTES (Sin decimales)
// =========================================================================
// Escala volumétrica a 3 decimales (Resolución en Mililitros)
#define ESCALA_VOLUMEN      1000U 
// Escala monetaria a 2 decimales (Resolución en Centavos)
#define ESCALA_MONEDA       100U  

// Factor de Calibración Físico: 1 Pulso del sensor Hall = 15 Mililitros (0.015 L)
#define MILILITROS_POR_PULSO 15U

// Variables críticas almacenadas en SRAM
uint32_t pulsos_acumulados_totales = 0;
uint32_t precio_por_litro_centavos = 1045; // Equivale a $10.45

// =========================================================================
// MÁQUINA DE CÁLCULO ALGORÍTMICO
// =========================================================================
/**
 * @brief Realiza el cálculo del volumen y costo actual con precisión perfecta.
 * @param[out] vol_ml  Puntero para retornar el volumen exacto en mililitros.
 * @param[out] costo_c Puntero para retornar el costo exacto en centavos.
 */
void Calcular_Despacho_Exacto(uint32_t* vol_ml, uint32_t* costo_c) {
    
    // 1. CÁLCULO DE VOLUMEN (100% Preciso)
    // Una multiplicación entera de 1 ciclo de reloj.
    *vol_ml = pulsos_acumulados_totales * MILILITROS_POR_PULSO;
    
    // 2. CÁLCULO DE COSTO (Prevención de Overflow temporal)
    // Para calcular el dinero, la ecuación teórica es: Costo = Volumen(L) * Precio(L)
    // En punto fijo es: Costo_c = (Volumen_ml * Precio_c) / ESCALA_VOLUMEN
    
    // Forzamos un casting explícito a 64 bits para el cálculo intermedio 
    // y evitamos un Integer Overflow silencioso (Día 359).
    uint64_t calculo_temporal = (uint64_t)(*vol_ml) * (uint64_t)precio_por_litro_centavos;
    
    // Dividimos para ajustar el factor cruzado de volumen y recuperar la escala de dinero
    *costo_c = (uint32_t)(calculo_temporal / ESCALA_VOLUMEN);
}

// =========================================================================
// FORMATEADOR ASCII AGNÓSTICO (El reemplazo de sprintf)
// =========================================================================
/**
 * @brief Inserta el punto decimal ilusorio para visualización humana.
 */
void Formatear_Pantalla_LCD(uint32_t valor_escalado, uint32_t divisor, char* string_salida) {
    
    // Separación matemática
    uint32_t parte_entera = valor_escalado / divisor;
    uint32_t parte_fraccional = valor_escalado % divisor;
    
    // En un sistema real, aquí llamaríamos a nuestras funciones ligeras (itoa).
    // Fast_IntToStr(parte_entera, string_entero);
    // Fast_IntToStr(parte_fraccional, string_fraccional); // Con padding de ceros a la izquierda
    
    // Lógica esquemática de concatenación:
    // strcpy(string_salida, string_entero);
    // strcat(string_salida, ".");
    // strcat(string_salida, string_fraccional);
}
        

🚀 Impacto en el Proyecto: La Defensa Académica

Objetivo del día: Proporcionar la retórica técnica para destruir cualquier crítica a la complejidad algorítmica del firmware.

Es muy probable que, durante tu presentación, alguien observe tu código fuente y te cuestione: *"¿Por qué estás dividiendo el número para mostrarlo en pantalla en lugar de usar un simple `sprintf("%0.2f")`?"*. Esta es tu oportunidad de brillar como un Arquitecto de Silicio.

La respuesta estructurada es: *"Evito los números flotantes por tres razones críticas. Primero, la metrología del gas LP no tolera el error de representación infinita del estándar IEEE 754; un mililitro perdido en la memoria es dinero perdido. Segundo, las librerías `stdio.h` que habilitan `sprintf` inyectan Kilobytes de basura en mi memoria Flash, violando las reglas de austeridad del software embebido. Y tercero, para mantener la predictibilidad del tiempo de ejecución (Hard Real-Time) de mi procesador de 32 bits y asegurar que jamás pierda un pulso de los flujómetros, la Unidad Aritmético-Lógica (ALU) debe operar exclusivamente con enteros nativos de ciclo único. El punto decimal es una mera ilusión gráfica para la pantalla"*. Cuando digas esto, el jurado no tendrá más preguntas.

📝 Resumen del Día