DÍA 131 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

El Costo Oculto de `printf` y `sprintf`

El estándar de C fue diseñado en los años 70 para computadoras grandes, no para microcontroladores. La función sprintf() es increíblemente poderosa porque puede parsear textos, flotantes, punteros y hexadecimales dinámicamente. Pero ese poder tiene un costo: **Peso y Tiempo**.

Al invocar #include <stdio.h> e inyectar un sprintf en tu código Bare-Metal, el compilador GCC se ve obligado a arrastrar miles de líneas de código de parseo interno. Tu archivo binario (.bin) puede engordar de **2 KB a 20 KB** instantáneamente. Además, sprintf requiere el uso silencioso de la memoria dinámica interna (Heap) y la función _sbrk, lo que puede causar fallos catastróficos de memoria (Heap Collision) en el Súper Bucle si no se gestiona con un Sistema Operativo (RTOS).

El Motor de Logs: Cadena de Bloques (Chaining)

La alternativa Bare-Metal es dividir la cadena. En lugar de decirle al CPU: *"Interpreta este texto largo y averigua dónde insertar la variable"*, le damos al CPU comandos atómicos:

  1. Imprime la palabra "INFO".
  2. Imprime la palabra "Litros: ".
  3. Convierte el número 1450 a texto "1450" (usando un itoa rápido).
  4. Imprime "1450".
  5. Imprime el salto de línea \r\n.

Esto reduce el peso del formateo de 18 Kilobytes a apenas 150 Bytes en la memoria Flash, ejecutándose hasta 10 veces más rápido.

ANÁLISIS DE HUELLA EN MEMORIA: SPRINTF VS CUSTOM LOGGER Librería Estándar (stdio.h) sprintf(buf, "L: %f", litros); ROM Bloat (+ 18 KB) Parser de String Dinámico Librería IEEE 754 (Floats) Gestión Heap (_sbrk, malloc) Velocidad: ~8,000 ciclos CPU Motor Bare-Metal (Encadenado) LOG_Info("L: ", litros_int); ROM Bloat (+ 150 Bytes) Custom_ITOA() estático UART_TX() directo Velocidad: ~400 ciclos CPU VS

⚙️ Ejercicios Prácticos

Ejercicio 1: Construiremos nuestro motor de logs. Primero crearemos la función entero_a_string() que transforma matemáticamente un número (ej. 1045) en un array de caracteres ('1', '0', '4', '5') de forma estática. Luego envolveremos esto en macros de telemetría como LOG_INFO(), LOG_WARN() y LOG_ERROR() que concatenarán el mensaje y lo empujarán por el USART2 (Día 130).


/**
 * @file uart_logger.c
 * @brief Motor Ligero de Logs Formateados para Telemetría Bare-Metal
 */

#include "stm32f401xe.h"

// Dependencia de la función TX bloqueante del Día 130
extern void uart_escribir_cadena(const char *texto);

/**
 * @brief Convierte un entero sin signo a String ASCII (Reemplazo de sprintf)
 * @param valor: Número a convertir (ej. 1450)
 * @param buffer: Puntero al arreglo donde se guardará el texto.
 */
void u32_a_string(uint32_t valor, char* buffer) {
    char temp[12]; 
    int i = 0;
    
    // Extraer los dígitos uno por uno (Módulo 10)
    if (valor == 0) {
        temp[i++] = '0';
    } else {
        while (valor > 0) {
            temp[i++] = (valor % 10) + '0'; // Convertir a carácter ASCII
            valor /= 10;
        }
    }
    
    // Los dígitos se extrajeron al revés. Ahora los invertimos en el buffer final.
    int j = 0;
    while (i > 0) {
        buffer[j++] = temp[--i];
    }
    buffer[j] = '\0'; // Terminador nulo vital de C
}

/**
 * @brief Función base del Motor de Logs. Encadena textos y valores estáticos.
 * @param nivel: Etiqueta del log (INFO, ERROR, WARN)
 * @param mensaje: Descripción del evento
 * @param valor: Valor métrico asociado
 */
void Telemetria_Log(const char* nivel, const char* mensaje, uint32_t valor) {
    
    // 1. Imprimir etiqueta de nivel
    uart_escribir_cadena("[");
    uart_escribir_cadena(nivel);
    uart_escribir_cadena("] ");
    
    // 2. Imprimir el mensaje descriptivo
    uart_escribir_cadena(mensaje);
    uart_escribir_cadena(": ");
    
    // 3. Imprimir el valor convertido (Sin usar floats ni stdio.h)
    char buffer_num[12];
    u32_a_string(valor, buffer_num);
    uart_escribir_cadena(buffer_num);
    
    // 4. Salto de línea universal (Carriage Return + Line Feed)
    uart_escribir_cadena("\r\n");
}

// Macros elegantes para el desarrollador
#define LOG_INFO(msg, val)  Telemetria_Log("INFO", msg, val)
#define LOG_WARN(msg, val)  Telemetria_Log("WARN", msg, val)
#define LOG_ERROR(msg, val) Telemetria_Log("ERR!", msg, val)

int main(void) {
    
    // inicializar_usart2_115200();
    
    LOG_INFO("Sistema Iniciado. Version", 2);
    
    while(1) {
        
        // Simulación: Eventos de la máquina
        uint32_t presion_actual = 1450; // Equivale a 145.0 PSI (Punto fijo)
        
        if (presion_actual > 1400) {
            // Mandar log de advertencia al servidor central
            LOG_WARN("Presion Alta (x10 PSI)", presion_actual);
        }
        
        // Si hay una falla grave de hardware (AWD)
        // LOG_ERROR("Sensor Desconectado", 404);
        
        // Retardo simulado
        // for(volatile int i=0; i<500000; i++);
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Telemetría Diagnóstica y Trazabilidad Operativa.

Cuando instalas una máquina expendedora industrial en medio de la carretera a 100 kilómetros del ingeniero más cercano, los Logs (Registros) son la única forma de saber qué está fallando. Si la máquina se reinicia de repente, el servidor central necesita revisar el historial para diagnosticar si fue un apagón (PVD), un ruido eléctrico (IWDG) o un corto circuito en el ADC.

Al construir este Motor de Logs Bare-Metal, has asegurado que tu máquina pueda reportar su estado métrico al exterior mediante el cable USART (y futuramente RS485). Al abstenerte de usar la pesada librería `stdio.h`, garantizas que el envío de una advertencia de sobrepresión (`LOG_WARN`) se procese atómicamente rápido sin fragmentar la memoria RAM. Tu firmware no engorda, los ciclos de CPU se conservan, y la terminal del administrador gasero recibe el estatus de la máquina perfectamente empaquetado en cadenas ASCII impecables.

📝 Resumen del Día