En la arquitectura de los procesadores ARM Cortex-M (como tu STM32F4), la memoria RAM estática (SRAM) es un bloque contiguo. El enlazador (Linker) distribuye tus datos de la siguiente manera:
.data y .bss). Estas crecen hacia arriba conforme agregas variables al código.En el medio queda un abismo oscuro (la RAM libre). El Stack es dinámico: baja y sube constantemente. Si baja tanto que cruza el límite y choca con tus variables globales... ¡Boom! Tus variables cambian de valor silenciosamente. Un segundo tenías el "Precio del Gas a 10.50", y al siguiente segundo el Stack lo sobrescribe con parte de una cadena UART, poniendo el precio en 12,000.
Imagínate medir hasta dónde subió el agua en un sótano oscuro usando una pared blanca. El agua dejará una marca de lodo (Watermark) en su punto más alto. Para hacer esto en la RAM, justo cuando arranca el microcontrolador (antes de ejecutar el Súper Bucle), pintamos toda la memoria RAM libre con un patrón conocido (ej. 0xDEADBEEF).
A medida que la máquina trabaja, procesa Modbus, imprime tickets y corre PIDs, el Stack bajará destruyendo nuestro patrón 0xDEADBEEF. Para saber qué tan cerca estuvimos del colapso, creamos una función que barre la RAM desde abajo hacia arriba, contando cuántos 0xDEADBEEF sobrevivieron intactos.
Ejercicio 1: Utilizaremos los símbolos mágicos generados por nuestro Script de Enlazador (Linker Script `.ld`) llamado _ebss, que marca la dirección de memoria exacta donde terminan tus variables y empieza la nada. Construiremos la función de pintado que llenará el espacio hasta un umbral seguro, y la función de conteo que será invocada de fondo para reportarle a la red RS485 cuánta memoria vital le queda a la máquina antes de morir.
/**
* @file stack_watermark.c
* @brief Monitor Profiler Bare-Metal para prevención de Stack Overflow
*/
#include "stm32f401xe.h"
#include <stdint.h>
// =========================================================================
// SÍMBOLOS DEL LINKER Y PATRÓN (CANARY)
// =========================================================================
// El Linker Script (.ld) nos provee esta variable. Su dirección en RAM
// (&_ebss) es el final exacto de la zona de variables estáticas.
extern uint32_t _ebss;
// Patrón mágico reconocible: "Dead Beef"
#define STACK_FILL_PATTERN 0xDEADBEEF
// El puntero de pila actual. (Instrucción inline assembly en ARM)
register uint32_t *puntero_pila_actual asm("sp");
/**
* @brief Pinta la memoria RAM libre con el patrón de reconocimiento.
* ¡ADVERTENCIA! Debe ejecutarse al inicio absoluto del main(),
* antes de llamar a funciones profundas.
*/
void Stack_Pintar_MarcaDeAgua(void) {
// Iniciar desde la primera dirección libre después de las variables globales
uint32_t *p = &_ebss;
// Pintamos hacia arriba (hacia direcciones mayores) hasta llegar a
// unos 128 bytes por debajo del Stack Pointer actual, dejándole
// margen para operar esta misma función sin sobreescribir su propio registro.
uint32_t *limite_seguro = puntero_pila_actual - 32; // 32 words = 128 bytes
while (p < limite_seguro) {
*p = STACK_FILL_PATTERN;
p++;
}
}
/**
* @brief Cuenta cuántos bytes de la marca de agua sobrevivieron intactos.
* Representa el mínimo histórico de RAM que jamás fue tocado.
* @return uint32_t Cantidad de Bytes libres absolutos del Stack
*/
uint32_t Stack_Obtener_Bytes_Libres(void) {
uint32_t *p = &_ebss;
uint32_t palabras_intactas = 0;
// Escaneamos desde abajo hacia arriba
// Tan pronto como encontramos un byte que no sea DEADBEEF,
// sabemos que el Stack Pointer bajó hasta aquí y lo destruyó temporalmente.
while (*p == STACK_FILL_PATTERN) {
palabras_intactas++;
p++;
}
// Devolvemos el resultado en Bytes (1 Palabra ARM = 4 Bytes)
return palabras_intactas * 4;
}
// =========================================================================
// IMPLEMENTACIÓN EN EL SISTEMA
// =========================================================================
extern uint16_t holding_registers[20]; // Nuestro mapa Modbus (Día 139)
extern volatile uint32_t ms_ticks;
int main(void) {
// 1. ANTES DE NADA: Pintar el abismo de la RAM
Stack_Pintar_MarcaDeAgua();
// 2. Inicialización del hardware pesado
// inicializar_relojes();
// inicializar_rs485_modbus();
// inicializar_pid();
uint32_t ultima_revision_stack = 0;
while(1) {
// El Súper Bucle ejecuta sus rutinas
// procesar_comunicaciones();
// actualizar_pantalla_lcd();
// 3. Revisión Diagnóstica de Baja Frecuencia (Cada 5 Segundos)
if ((ms_ticks - ultima_revision_stack) >= 5000) {
// Calculamos la salud de la memoria
uint32_t bytes_libres = Stack_Obtener_Bytes_Libres();
// Exponemos el signo vital a la red Modbus Industrial.
// Dividimos entre 4 para enviar el valor en 'Words' (16 bits)
// o usamos dos holding registers para enviar el uint32 completo.
holding_registers[8] = (uint16_t)(bytes_libres & 0xFFFF);
// Alerta Preventiva Local
if (bytes_libres < 256) {
// El colapso es inminente.
// LOG_WARN("STACK CASI LLENO. POSIBLE RECURSIÓN.");
// Encender un LED rojo de System Fault
}
ultima_revision_stack = ms_ticks;
}
// __WFI(); // Descansar
}
return 0;
}
Objetivo del día: Telemetría Diagnóstica y Prevención de Fallos Catastróficos.
En el Día 147 dotamos a la máquina de actualizaciones OTA (Over-The-Air). El problema de las actualizaciones remotas es que el nuevo código a veces tiene Bugs (errores ocultos). Imagina que el nuevo código para procesar tarjetas bancarias declara un arreglo de `uint8_t buffer_crypto[1024]` dentro de la función. De pronto, el Despachador entra en HardFault de forma intermitente.
Al dotar a tu software del algoritmo **Stack Watermark**, transformaste tu máquina en un equipo con autodiagnóstico continuo. El Administrador Central revisa el panel SCADA y nota algo extraño: El registro Modbus `Holding_Reg[8]` (Memoria Libre) solía estar en 8,000 bytes. Desde la actualización de ayer, cae a 150 bytes cada vez que un cliente inserta una tarjeta de crédito. ¡El Stack Overflow ha sido desenmascarado! Con esta telemetría, en lugar de enviar técnicos a cambiar placas a ciegas asumiendo daños eléctricos, el ingeniero de firmware se da cuenta de que el arreglo local es muy grande, lo mueve a la sección Global (`.bss`), y la estabilidad de las 5,000 estaciones de gas del país vuelve al 100%.
0xDEADBEEF) inmediatamente durante el arranque.