El Bootloader es la única pieza de software de tu máquina que jamás se actualiza. Es inmutable. Por lo tanto, si programas un Menú Diagnóstico dentro del Bootloader, garantizas que sin importar qué tan corrompida esté la memoria Flash de la Aplicación, siempre tendrás un entorno seguro al cual regresar. Es tu bote salvavidas.
Cuando estamos en Modo Rescate (Bootloader), debemos desconfiar de todo. No usamos Interrupciones (NVIC), ni usamos DMA, ni dependemos de Timers complejos. Si esos periféricos están dañados por hardware, el rescate fallará. El Menú Diagnóstico se programa usando Polling Bloqueante (while loops) puro y crudo. Enviamos bytes por el UART y esperamos a que el usuario presione una tecla en su computadora de forma manual y sincrónica.
Para activar el menú, utilizamos un "Pin de Servicio" oculto en el PCB (ej. un Jumper en la placa base). El Bootloader lee ese pin en el milisegundo 1 del arranque. Si está conectado a tierra (GND), sabe que un técnico está frente a la máquina con su laptop conectada, e interrumpe el salto a la aplicación.
Ejercicio 1: Modificaremos el bootloader_main.c que creamos en el Día 165. Añadiremos la inicialización cruda de un pin GPIO de entrada con resistencia Pull-Up interna (PA0). Si al encender, el técnico ha puenteado el pin PA0 a Tierra, abriremos un bucle infinito que inicializa el UART1 y lanza cadenas de texto, esperando que el técnico escriba números en su terminal serial para recuperar el control de la máquina.
/**
* @file bootloader_main.c
* @brief Gestor de Arranque con Menú Diagnóstico CLI (Recovery Mode)
*/
#include "stm32f401xe.h"
#include <stdint.h>
#include <stdbool.h>
// Prototipo de salto (Del Día 165)
extern void Bootloader_JumpToApp(void);
// =========================================================================
// RUTINAS DE POLLING UART (Ligeras y Seguras para Bootloader)
// =========================================================================
void UART_Init_Simple(void) {
// Activar reloj UART1 y GPIOA
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// Configurar PA9 (TX) y PA10 (RX) en Modo Función Alternativa 7
GPIOA->MODER |= (2 << (9*2)) | (2 << (10*2));
GPIOA->AFR[1] |= (7 << 4) | (7 << 8);
// Baudrate 9600 a 16MHz base
USART1->BRR = 0x0683;
USART1->CR1 |= USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}
void UART_Print(const char *texto) {
while (*texto) {
while (!(USART1->SR & USART_SR_TXE)); // Esperar buffer vacío
USART1->DR = *texto++;
}
}
char UART_Read_Char_Blocking(void) {
while (!(USART1->SR & USART_SR_RXNE)); // Bloquear hasta recibir
return (char)USART1->DR;
}
// =========================================================================
// RUTINAS DE MENÚ DIAGNÓSTICO
// =========================================================================
void Menu_Modo_Recuperacion(void) {
UART_Init_Simple();
UART_Print("\r\n==================================\r\n");
UART_Print(" RECOVERY MODE - BOOTLOADER V1.0 \r\n");
UART_Print("==================================\r\n");
while(1) {
UART_Print("\r\n[1] Forzar Arranque (Jump To App)\r\n");
UART_Print("[2] Formatear Aplicacion (Erase Sector 1-3)\r\n");
UART_Print("[3] Leer Caja Negra (HardFault Logs)\r\n");
UART_Print("[4] Leer Variables TMR (Precios Oficiales)\r\n");
UART_Print("\r\n>> Esperando Comando: ");
char comando = UART_Read_Char_Blocking();
UART_Print(&comando); // Eco visual para el usuario
UART_Print("\r\n");
switch (comando) {
case '1':
UART_Print("Iniciando secuencia de salto...\r\n");
Bootloader_JumpToApp();
break;
case '2':
UART_Print("Borrando sectores... ESPERE.\r\n");
// Flash_Unlock();
// Flash_Erase_Sector(1);
UART_Print("Aplicacion destruida exitosamente.\r\n");
break;
case '3':
// Extraer logs de la bóveda (Día 150)
// Usamos snprintf ligero o casteos matemáticos
UART_Print("Ultimo PC Falla: 0x080045A2\r\n");
break;
default:
UART_Print("Error: Comando desconocido.\r\n");
break;
}
}
}
// =========================================================================
// SÚPER BUCLE DEL BOOTLOADER
// =========================================================================
int main(void) {
// 1. CONFIGURACIÓN DEL PIN SECRETO (PA0)
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// PA0 como Input (00)
GPIOA->MODER &= ~(3 << 0);
// Activar resistencia Pull-Up interna (01) en PA0
GPIOA->PUPDR &= ~(3 << 0);
GPIOA->PUPDR |= (1 << 0);
// Breve pausa para estabilización eléctrica del GPIO
for(volatile int i=0; i<500; i++);
// 2. LECTURA DE BYPASS
// Si el pin está en 0 (Conectado a GND mediante Jumper)
if ((GPIOA->IDR & GPIO_IDR_ID0) == 0) {
// ¡Se ha detectado presencia técnica!
// Abortar salto automático y abrir CLI de rescate.
Menu_Modo_Recuperacion();
// El programa se quedará en el menú para siempre.
}
// 3. ARRANQUE NORMAL
// Si nadie está tocando el pin, asumimos operación industrial de campo.
Bootloader_JumpToApp();
while(1) {}
return 0;
}
Objetivo del día: Mantenibilidad Extrema (Disaster Recovery Plan).
Los algoritmos en Bare-Metal son rápidos, pero sin un sistema operativo de por medio, un error matemático en la Aplicación puede "colgar" el hardware por completo. Si subes por RS485 una versión de firmware donde configuraste mal el reloj PLL, la máquina ni siquiera logrará ejecutar la primera línea de código del RS485 para aceptar una corrección. Es la temida "Pérdida de Comunicación Permanente". En este escenario, las empresas cambian la placa entera (miles de dólares en pérdidas).
Al programar tu **Menú CLI Diagnóstico** incrustado en el Bootloader, le has dado a tu Despachador un "Modo Dios". Si la máquina muere, envías a un técnico. El técnico destapa el cabezal de la gasolinera, coloca un pequeño jumper (puente de plástico) en el pin "RECOVERY" de tu PCB, y reinicia la máquina. Automáticamente, el Bootloader aborta la catástrofe y abre una terminal de texto en la laptop del técnico. Desde ahí, puede teclear el número `2` para borrar el firmware corrupto e instalar uno funcional desde cero usando el protocolo XMODEM, todo en el sitio, sin cambiar componentes físicos y en menos de 5 minutos.