Complete implementation of Spanish (es-419) language support: - Canonical catalogs and data: messages/es.json, data/es/**, lib/data/es/**, public/exemplos/es/** - Canonical slugs: /es/series, /es/catalogo, /es/comparacion, /es/componentes, /es/diagnostico, /es/frameworks, /es/glosario, /es/selector, /es/acerca - Dedicated search index at /api/busca/es with Spanish accent normalization - Strict exclusion of book references in Spanish routes and catalogs - Language selector and root negotiation with es active (ENABLED_LOCALES = ['pt', 'en', 'es']) - Extended verification suite: check-i18n, smoke-imagem and verifica-vazamentos Co-Authored-By: Antigravity <noreply@google.com>
701 lines
35 KiB
JavaScript
701 lines
35 KiB
JavaScript
/**
|
|
* Base de datos estructurada para la guía de diagnóstico, errores comunes y solución de problemas en ESP32.
|
|
* Cubre 6 categorías técnicas con causas raíz, comparación de buenas prácticas vs soluciones temporales y ejemplos de código
|
|
* en Arduino C++, ESP-IDF (C) y MicroPython.
|
|
*/
|
|
|
|
export const categoriasDiagnostico = [
|
|
{
|
|
id: "alimentacao",
|
|
nome: "Alimentación y Hardware",
|
|
icone: "Zap",
|
|
cor: "amber",
|
|
descricao: "Caídas de tensión por picos de RF Wi-Fi/BLE, pines de strapping en conflicto, ruido electromagnético y problemas de LDO.",
|
|
},
|
|
{
|
|
id: "cpu-panics",
|
|
nome: "Pánicos de CPU y Guru Meditation",
|
|
icone: "Cpu",
|
|
cor: "red",
|
|
descricao: "Punteros nulos, violaciones de acceso a memoria (Load/StoreProhibited), instrucciones ilegales y pánicos de caché en ISRs.",
|
|
},
|
|
{
|
|
id: "freertos",
|
|
nome: "FreeRTOS y Multitarea",
|
|
icone: "Layers",
|
|
cor: "purple",
|
|
descricao: "Task Watchdog (TWDT), Interrupt Watchdog (IWDT), desbordamiento de pila (Stack Overflow) y bloqueos del planificador.",
|
|
},
|
|
{
|
|
id: "memoria",
|
|
nome: "Memoria, Heap y PSRAM",
|
|
icone: "Activity",
|
|
cor: "blue",
|
|
descricao: "Fragmentación del heap, fugas de memoria (Memory Leaks), asignaciones DMA y restricciones de velocidad en PSRAM.",
|
|
},
|
|
{
|
|
id: "boot-flash",
|
|
nome: "Bootloader y Grabación",
|
|
icone: "Terminal",
|
|
cor: "emerald",
|
|
descricao: "Fallas de comunicación serie (DTR/RTS), errores de lectura en Flash SPI, discrepancia de reloj y particiones corruptas.",
|
|
},
|
|
{
|
|
id: "conectividade",
|
|
nome: "Conectividad y Protocolos",
|
|
icone: "Wifi",
|
|
cor: "cyan",
|
|
descricao: "Timeouts de baliza Wi-Fi, fallos de handshake TLS/HTTPS por ausencia de reloj SNTP e inestabilidad de señal.",
|
|
},
|
|
];
|
|
|
|
export const sintomasRapidos = [
|
|
{ id: "bootloop", rotulo: "🔄 Bucle de arranque (Crash loop)", categoria: "todos" },
|
|
{ id: "wifi-crash", rotulo: "📶 Se reinicia al activar Wi-Fi/BLE", categoria: "alimentacao" },
|
|
{ id: "upload-fail", rotulo: "🔌 Fallo al subir firmware (Error de grabación)", categoria: "boot-flash" },
|
|
{ id: "random-freeze", rotulo: "⏱️ Se congela tras minutos u horas", categoria: "memoria" },
|
|
{ id: "null-pointer", rotulo: "💥 Guru Meditation (Crash de CPU)", categoria: "cpu-panics" },
|
|
{ id: "wdt-reset", rotulo: "🐕 Watchdog activado (Reset por TWDT)", categoria: "freertos" },
|
|
{ id: "tls-fail", rotulo: "🔒 Fallo en conexión HTTPS / SSL", categoria: "conectividade" },
|
|
];
|
|
|
|
export const errosCatalogo = [
|
|
{
|
|
id: "brownout-reset",
|
|
titulo: "Reinicio por detector de Brownout (Caída de tensión)",
|
|
categoria: "alimentacao",
|
|
sintomas: ["bootloop", "wifi-crash"],
|
|
gravidade: "Alta",
|
|
logSerial: "Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)",
|
|
resumo: "La tensión de alimentación del ESP32 cayó por debajo del umbral de seguridad (aproximadamente 2.8V ~ 2.43V).",
|
|
causaRaiz:
|
|
"Al inicializar las radios Wi-Fi o BLE, el amplificador de potencia (PA) demanda picos de corriente instantáneos superiores a 500 mA en menos de 1 ms. Si la fuente de alimentación, cables USB largos/delgados, pistas de protoboard o el regulador LDO no pueden suministrar esa corriente transitoria con baja impedancia, el voltaje en el riel de 3.3V colapsa momentáneamente, activando el circuito comparador interno de Brownout para evitar la corrupción de la memoria Flash y estados lógicos indefinidos.",
|
|
gambiarra: {
|
|
titulo: "Desactivar el detector de Brownout por software",
|
|
descricao: "Llamar a WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0) para suprimir el error en el código.",
|
|
consequencia: "Extremadamente peligroso: el ESP32 continúa ejecutando instrucciones bajo subtensión, corrompiendo la memoria Flash SPI, bloqueando registros periféricos y causando fallos erráticos en campo sin previo aviso.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Dimensionamiento adecuado de hardware y capacitores de desacoplo",
|
|
passos: [
|
|
"Añadir un capacitor de tantalio o cerámico de baja ESR de 10 µF a 100 µF en paralelo con un capacitor cerámico de 100 nF lo más cerca posible de los pines 3V3 y GND del ESP32.",
|
|
"Utilizar reguladores LDO diseñados para picos de al menos 600 mA a 1 A con excelente respuesta transitoria (ej.: AP2112K, ME6211, TPS73733 o XC6206 robusto). Evitar clones deficientes del AMS1117.",
|
|
"Evitar alimentar el ESP32 a través de pistas largas de protoboard con cables jumper delgados cuando el Wi-Fi transmite a máxima potencia.",
|
|
],
|
|
},
|
|
codigoArduino: `// ¡NO desactive el detector de Brownout! Si necesita reducir picos de corriente en pruebas de banco:
|
|
#include <WiFi.h>
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
// Opcional: Reducir ligeramente la potencia máxima de transmisión Wi-Fi de 20dBm a 17dBm recorta los picos de corriente un ~20%
|
|
WiFi.setTxPower(WIFI_POWER_17dBm);
|
|
WiFi.begin("SSID", "PASSWORD");
|
|
}`,
|
|
codigoEspIdf: `// Configuración en ESP-IDF:
|
|
#include "esp_wifi.h"
|
|
#include "esp_log.h"
|
|
|
|
void app_main(void) {
|
|
// En menuconfig: Component config -> ESP32-specific -> Hardware Brownout Reset (Mantener ACTIVADO)
|
|
// Para mitigar picos transitorios en diseños con restricciones de energía:
|
|
esp_wifi_set_max_tx_power(68); // 68 = 17dBm (unidades de 0.25dBm)
|
|
}`,
|
|
codigoMicroPython: `# Conexión Wi-Fi segura en MicroPython con estabilización de rieles:
|
|
import network
|
|
import time
|
|
|
|
wlan = network.WLAN(network.STA_IF)
|
|
wlan.active(True)
|
|
|
|
# Esperar a que se estabilicen los rieles antes de activar el transmisor
|
|
time.sleep_ms(200)
|
|
wlan.connect("SSID", "PASSWORD")
|
|
|
|
attempts = 0
|
|
while not wlan.isconnected() and attempts < 30:
|
|
time.sleep_ms(200)
|
|
attempts += 1`,
|
|
},
|
|
{
|
|
id: "strapping-pins",
|
|
titulo: "Conflicto en pines de arranque (Strapping Pins) durante el encendido",
|
|
categoria: "alimentacao",
|
|
sintomas: ["bootloop", "upload-fail"],
|
|
gravidade: "Alta",
|
|
logSerial: "rst:0x10 (RTCWDT_RTC_RESET),boot:0x3 (DOWNLOAD_BOOT(UART/SPI))\nwaiting for download",
|
|
resumo: "El ESP32 entra inesperadamente en modo de descarga de firmware o falla al arrancar debido a niveles lógicos forzados en los pines de arranque.",
|
|
causaRaiz:
|
|
"Durante el reinicio por hardware, el circuito interno muestrea los niveles lógicos presentes en pines específicos (ej.: GPIO0, GPIO2, GPIO12, GPIO15 en ESP32 clásico; GPIO8, GPIO9 en ESP32-C3/C6). Si hay periféricos externos conectados (sensores I2C, tiras LED, relés) que fuerzan estos pines a niveles altos o bajos en el instante del arranque, el chip interpreta que debe entrar en modo de descarga UART o altera el voltaje interno del LDO de la memoria Flash (GPIO12 en 3.3V vs 1.8V), impidiendo la ejecución del código.",
|
|
gambiarra: {
|
|
titulo: "Desconectar los cables manualmente antes de cada encendido",
|
|
descricao: "Retirar los módulos externos cada vez que se reinicia o programa la placa.",
|
|
consequencia: "Inviable para cualquier producto en producción o gabinete cerrado, propenso a errores humanos y desgaste de conectores.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Aislamiento de pines de arranque o reasignación a GPIOs seguros",
|
|
passos: [
|
|
"Reservar los pines de arranque exclusivamente para funciones internas o dejarlos sin conexiones externas permanentes.",
|
|
"En ESP32 clásico, evitar usar GPIO0, GPIO2, GPIO12 y GPIO15 para cargas externas que contengan resistencias pull-up o pull-down incorporadas.",
|
|
"Utilizar transistores de aislamiento (búferes tri-state o conmutadores analógicos controlados por un GPIO libre tras el arranque) si es estrictamente necesario compartir esos pines.",
|
|
],
|
|
},
|
|
codigoArduino: `// Mapeo seguro de pines en Arduino para evitar interferir con strapping:
|
|
// En ESP32 clásico: usar GPIO16, 17, 18, 19, 21, 22, 23 (libres de strapping)
|
|
const int PIN_RELE = 22; // SEGURO: GPIO22 no es pin de arranque ni tiene restricciones de boot
|
|
|
|
void setup() {
|
|
pinMode(PIN_RELE, OUTPUT);
|
|
digitalWrite(PIN_RELE, LOW);
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Configuración de GPIOs fuera de strapping
|
|
#include "driver/gpio.h"
|
|
|
|
#define SAFE_GPIO_OUTPUT GPIO_NUM_22
|
|
|
|
void app_main(void) {
|
|
gpio_config_t io_conf = {
|
|
.pin_bit_mask = (1ULL << SAFE_GPIO_OUTPUT),
|
|
.mode = GPIO_MODE_OUTPUT,
|
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
.pull_down_en = GPIO_PULLDOWN_ENABLE,
|
|
.intr_type = GPIO_INTR_DISABLE
|
|
};
|
|
gpio_config(&io_conf);
|
|
}`,
|
|
codigoMicroPython: `# MicroPython: Asignación limpia a GPIO seguro
|
|
from machine import Pin
|
|
|
|
# Usar GPIO seguro que no interfiera con el ciclo de arranque
|
|
pin_rele = Pin(22, Pin.OUT, value=0)`,
|
|
},
|
|
{
|
|
id: "guru-load-prohibited",
|
|
titulo: "Guru Meditation: LoadProhibited / StoreProhibited (Acceso a puntero nulo)",
|
|
categoria: "cpu-panics",
|
|
sintomas: ["null-pointer", "bootloop"],
|
|
gravidade: "Crítica",
|
|
logSerial: "Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.\nCore 1 register dump:\nPC : 0x400d1234 PS : 0x00060030 A0 : 0x800d1567 A1 : 0x3ffb1230\nA2 : 0x00000000 A3 : 0x3ffb5000",
|
|
resumo: "La aplicación intentó leer (LoadProhibited) o escribir (StoreProhibited) en una dirección de memoria protegida o puntero nulo (0x00000000).",
|
|
causaRaiz:
|
|
"Ocurre cuando el software desreferencia un puntero no inicializado, un puntero nulo devuelto por una función que falló (como malloc sin memoria suficiente o WiFiClient no conectado), o intenta acceder a una estructura de datos ya liberada (use-after-free). Al observar el volcado de registros, si A2 o A3 contienen 0x00000000 o valores cercanos a cero, se confirma la desreferencia nula.",
|
|
gambiarra: {
|
|
titulo: "Reinicio automático sin depuración mediante ESP.restart()",
|
|
descricao: "Configurar el sistema para reiniciar ante fallos sin investigar la causa raíz del crash.",
|
|
consequencia: "El error persiste en producción, provocando reinicios cíclicos en campo, pérdida de estado de usuario y posibles corrupciones de archivos.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Validación estricta de punteros y decodificación de la traza de llamadas (Backtrace)",
|
|
passos: [
|
|
"Comprobar siempre el retorno de funciones de asignación y conexión (ej.: if (ptr == NULL) return;).",
|
|
"Utilizar la herramienta ESP Exception Decoder en Arduino IDE o 'idf.py monitor' en ESP-IDF para convertir la dirección del PC (Program Counter) y la traza de llamadas en el archivo y número de línea exactos del código fuente.",
|
|
"Establecer los punteros a NULL tras invocar free() para evitar dobles liberaciones y accesos a memoria caducada.",
|
|
],
|
|
},
|
|
codigoArduino: `// Práctica segura: Comprobación defensiva de punteros
|
|
void procesarMensaje(const char *mensaje) {
|
|
if (mensaje == nullptr) {
|
|
Serial.println(F("[ERROR] Puntero de mensaje nulo recibido. Operación abortada."));
|
|
return;
|
|
}
|
|
// Procesamiento seguro garantizado
|
|
Serial.printf("Longitud del mensaje: %d\n", strlen(mensaje));
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Verificación defensiva y aserciones
|
|
#include "esp_log.h"
|
|
#include "esp_check.h"
|
|
|
|
static const char *TAG = "APP_SAFETY";
|
|
|
|
esp_err_t inicializar_buffer(char **buf_out, size_t tamano) {
|
|
ESP_RETURN_ON_FALSE(buf_out != NULL, ESP_ERR_INVALID_ARG, TAG, "Puntero de salida nulo");
|
|
|
|
*buf_out = (char *)malloc(tamano);
|
|
ESP_RETURN_ON_FALSE(*buf_out != NULL, ESP_ERR_NO_MEM, TAG, "Fallo de memoria al asignar buffer");
|
|
|
|
return ESP_OK;
|
|
}`,
|
|
codigoMicroPython: `# MicroPython maneja excepciones en lugar de pánicos de bajo nivel:
|
|
def procesar_datos(sensor):
|
|
if sensor is None:
|
|
print("[WARN] Objeto sensor no inicializado")
|
|
return None
|
|
try:
|
|
return sensor.read()
|
|
except Exception as e:
|
|
print("Error en lectura:", e)
|
|
return None`,
|
|
},
|
|
{
|
|
id: "guru-iram-cache",
|
|
titulo: "Fallo de caché e InstructionFetchError en rutinas de interrupción (ISRs)",
|
|
categoria: "cpu-panics",
|
|
sintomas: ["null-pointer", "random-freeze"],
|
|
gravidade: "Crítica",
|
|
logSerial: "Guru Meditation Error: Core 0 panic'ed (Cache disabled but cached memory region accessed)\nCore 0 register dump:\nPC : 0x40081234 PS : 0x00060034",
|
|
resumo: "Ocurrió una interrupción (ISR) mientras la memoria Flash SPI estaba ocupada escribiendo o borrando datos con la caché desactivada.",
|
|
causaRaiz:
|
|
"Durante operaciones de escritura o borrado en la memoria Flash SPI (ej.: guardado en NVS, LittleFS o actualizaciones OTA), el controlador de hardware desactiva temporalmente la memoria caché de instrucciones. Si se dispara una interrupción por hardware y su rutina de atención (o cualquier función o constante de cadena a la que llame) reside en la memoria Flash en lugar de la memoria RAM interna (IRAM), la CPU no puede leer la siguiente instrucción y genera un pánico inmediato.",
|
|
gambiarra: {
|
|
titulo: "Desactivar interrupciones durante toda la escritura en Flash",
|
|
descricao: "Bloquear globalmente las interrupciones durante períodos prolongados de escritura.",
|
|
consequencia: "Genera desbordamientos en búferes de UART, pérdida de paquetes de red Wi-Fi y dispara el Interrupt Watchdog (IWDT).",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Colocar la ISR y sus datos en memoria IRAM y DRAM usando atributos",
|
|
passos: [
|
|
"Declarar todas las funciones de interrupción con el atributo IRAM_ATTR (en Arduino/ESP-IDF).",
|
|
"Evitar llamar a funciones que residan en Flash (como Serial.print, sprintf, malloc o FreeRTOS API estándar sin sufijo FromISR) dentro de la ISR.",
|
|
"Almacenar constantes accedidas por la ISR con DRAM_ATTR si están fuera de la pila local.",
|
|
],
|
|
},
|
|
codigoArduino: `// Rutina de interrupción correcta con IRAM_ATTR:
|
|
volatile uint32_t contadorPulsos = 0;
|
|
|
|
void IRAM_ATTR manejarInterrupcion() {
|
|
// Prohibido usar Serial.print() aquí adentro (reside en Flash)
|
|
contadorPulsos++;
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
pinMode(4, INPUT_PULLUP);
|
|
attachInterrupt(digitalPinToInterrupt(4), manejarInterrupcion, FALLING);
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Rutina ISR segura en IRAM
|
|
#include "esp_attr.h"
|
|
#include "driver/gpio.h"
|
|
|
|
static volatile uint32_t pulsos = 0;
|
|
|
|
static void IRAM_ATTR gpio_isr_handler(void* arg) {
|
|
pulsos++;
|
|
// Usar únicamente APIs de FreeRTOS terminadas en FromISR
|
|
}
|
|
|
|
void app_main(void) {
|
|
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
|
gpio_isr_handler_add(GPIO_NUM_4, gpio_isr_handler, NULL);
|
|
}`,
|
|
codigoMicroPython: `# MicroPython: Reservar memoria para excepciones en interrupciones
|
|
import micropython
|
|
from machine import Pin
|
|
|
|
micropython.alloc_emergency_exception_buf(100)
|
|
|
|
contador = 0
|
|
def handler(p):
|
|
global contador
|
|
contador += 1 # Operaciones atómicas y simples únicamente
|
|
|
|
pin = Pin(4, Pin.IN, Pin.PULL_UP)
|
|
pin.irq(trigger=Pin.IRQ_FALLING, handler=handler)`,
|
|
},
|
|
{
|
|
id: "freertos-twdt",
|
|
titulo: "Reinicio por Task Watchdog Timer (TWDT Timeout en Core 0 o Core 1)",
|
|
categoria: "freertos",
|
|
sintomas: ["wdt-reset", "bootloop"],
|
|
gravidade: "Alta",
|
|
logSerial: "E (10543) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:\nE (10543) task_wdt: - loopTask (CPU 1)\nE (10543) task_wdt: Tasks currently running on other CPUs:\nE (10543) task_wdt: - IDLE0 (CPU 0)",
|
|
resumo: "Una tarea de FreeRTOS acaparó la CPU de forma continua durante más de 5 segundos sin ceder tiempo a otras tareas.",
|
|
causaRaiz:
|
|
"El planificador de FreeRTOS requiere que las tareas de baja prioridad o la tarea IDLE del sistema se ejecuten periódicamente para alimentar el perro guardián (Watchdog). Si un bucle 'while(1)' o 'for' ejecuta cálculos intensivos o espera una condición sin llamar a vTaskDelay o ceder el procesador (yield), el temporizador del TWDT expira y reinicia el SoC para evitar congelamientos completos.",
|
|
gambiarra: {
|
|
titulo: "Aumentar el tiempo del Watchdog a 60 segundos o desactivarlo",
|
|
descricao: "Elevar el umbral en menuconfig para ocultar el bloqueo de la tarea.",
|
|
consequencia: "Si el código se congela realmente en campo, el sistema tardará minutos en recuperarse o quedará bloqueado indefinidamente consumiendo batería.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Introducir cesión explícita de CPU (vTaskDelay) o arquitectura dirigida por eventos",
|
|
passos: [
|
|
"Añadir al menos 'vTaskDelay(pdMS_TO_TICKS(1))' o 'delay(1)' dentro de cualquier bucle de procesamiento continuo.",
|
|
"Sustituir bucles de espera activa (polling continuo) por colas de FreeRTOS (xQueueReceive) o semáforos binarios bloqueantes.",
|
|
"Alimentar manualmente el perro guardián con esp_task_wdt_reset() si se realizan tareas largas inevitables.",
|
|
],
|
|
},
|
|
codigoArduino: `// Prevención del TWDT en Arduino:
|
|
void loop() {
|
|
// Procesamiento pesado:
|
|
for (int i = 0; i < 10000; i++) {
|
|
// Procesar elemento...
|
|
|
|
// Ceder CPU a la tarea IDLE cada cierto intervalo:
|
|
if (i % 500 == 0) {
|
|
vTaskDelay(pdMS_TO_TICKS(1)); // O delay(1); alimenta el Watchdog
|
|
}
|
|
}
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Tarea bloqueante basada en colas (0% consumo de CPU en reposo)
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/queue.h"
|
|
|
|
static QueueHandle_t colaEventos;
|
|
|
|
void tarea_procesadora(void *pvParameters) {
|
|
int evento;
|
|
while (1) {
|
|
// Bloquea indefinidamente hasta recibir datos, cediendo el 100% de la CPU:
|
|
if (xQueueReceive(colaEventos, &evento, portMAX_DELAY) == pdTRUE) {
|
|
// Procesar evento de forma segura
|
|
}
|
|
}
|
|
}`,
|
|
codigoMicroPython: `# MicroPython: Uso de asyncio para evitar bloqueos
|
|
import uasyncio as asyncio
|
|
|
|
async def tarea_pesada():
|
|
while True:
|
|
# Ejecutar lógica
|
|
await asyncio.sleep_ms(10) # Cede el bucle de eventos cooperativo`,
|
|
},
|
|
{
|
|
id: "freertos-stack-overflow",
|
|
titulo: "Desbordamiento de pila en tarea FreeRTOS (Stack Overflow)",
|
|
categoria: "freertos",
|
|
sintomas: ["bootloop", "random-freeze"],
|
|
gravidade: "Crítica",
|
|
logSerial: "***ERROR*** A stack overflow in task MiTarea has been detected.\nBacktrace:0x4008b123:0x3ffb7890 0x4008c456:0x3ffb78b0",
|
|
resumo: "La tarea excedió el espacio de memoria de pila (stack) asignado en su creación, sobrescribiendo variables de otras tareas.",
|
|
causaRaiz:
|
|
"Cada tarea de FreeRTOS recibe un bloque fijo de memoria para su pila local. Declarar arreglos o búferes grandes dentro de funciones locales (ej.: char buffer[2048]), anidar llamadas a funciones con múltiples niveles de recursión o invocar bibliotecas pesadas de JSON/TLS puede consumir rápidamente el espacio asignado, rompiendo el centinela (canary) de seguridad al final de la pila.",
|
|
gambiarra: {
|
|
titulo: "Asignar 32 KB de pila a todas las tareas indiscriminadamente",
|
|
descricao: "Colocar valores gigantescos en el parámetro usStackDepth de cada xTaskCreate.",
|
|
consequencia: "Agotamiento prematuro de la memoria SRAM interna, imposibilitando inicializar conexiones Wi-Fi o crear nuevas tareas.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Medir el margen de pila con uxTaskGetStackHighWaterMark y alojar búferes en el heap",
|
|
passos: [
|
|
"Monitorizar periódicamente el remanente mínimo de pila mediante uxTaskGetStackHighWaterMark(NULL).",
|
|
"Trasladar arreglos de más de 256 bytes fuera de la pila local, asignándolos dinámicamente en el heap (malloc) o de forma estática.",
|
|
"Dimensionar la pila de tareas que utilicen TLS/HTTPS con un mínimo de 6 KB a 8 KB.",
|
|
],
|
|
},
|
|
codigoArduino: `// Comprobación de pila remanente en Arduino:
|
|
void tareaMonitoreada(void *pvParameters) {
|
|
while (1) {
|
|
// Lógica de trabajo...
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
|
|
// Devuelve el número mínimo de bytes libres que le quedaron a la pila:
|
|
UBaseType_t remanente = uxTaskGetStackHighWaterMark(NULL);
|
|
Serial.printf("Margen de seguridad de pila: %d bytes libres\n", remanente);
|
|
}
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Creación con dimensionamiento y habilitación de verificación de pila
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
// En menuconfig: Component config -> FreeRTOS -> Enable Stack Overflow Detection (Method 2)
|
|
|
|
void tarea_optimizada(void *pvParameters) {
|
|
while (1) {
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
UBaseType_t espacioLibre = uxTaskGetStackHighWaterMark(NULL);
|
|
// Si espacioLibre < 512 bytes, aumente el stack en xTaskCreate
|
|
}
|
|
}`,
|
|
codigoMicroPython: `# MicroPython: Consulta de estado de memoria
|
|
import gc
|
|
|
|
gc.collect()
|
|
print("Memoria libre en RAM:", gc.mem_free(), "bytes")`,
|
|
},
|
|
{
|
|
id: "memoria-fragmentacao-heap",
|
|
titulo: "Fragmentación del heap y fugas de memoria (Memory Leaks)",
|
|
categoria: "memoria",
|
|
sintomas: ["random-freeze", "wifi-crash"],
|
|
gravidade: "Alta",
|
|
logSerial: "E (45210) esp_netif_lwip: Failed to allocate netif buffer\nE (45210) wifi: malloc 1420 failed, free heap: 42300, max allocable: 1200",
|
|
resumo: "El sistema tiene memoria libre total, pero no puede asignar un bloque contiguo por estar fragmentado en fragmentos pequeños.",
|
|
causaRaiz:
|
|
"El uso recurrente de la clase 'String' en bucles rápidos en Arduino, la manipulación de cadenas grandes mediante concatenación (+), o llamadas a malloc sin su correspondiente free van partiendo la memoria dinámica en islas minúsculas. Cuando el driver de Wi-Fi o TLS solicita un búfer de 2 a 4 KB contiguos para recibir un paquete de red, la asignación falla a pesar de que el informe global indique decenas de kilobytes libres.",
|
|
gambiarra: {
|
|
titulo: "Reiniciar el microcontrolador preventivamente cada noche",
|
|
descricao: "Configurar un temporizador para forzar ESP.restart() a intervalos regulares.",
|
|
consequencia: "Interrupción de operaciones activas, pérdida de estados de conexión y desatención de la causa subyacente.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Uso de búferes estáticos, reserva de memoria previa y monitorización del bloque máximo asignable",
|
|
passos: [
|
|
"Sustituir objetos String dinámicos por arreglos de caracteres tradicionales (char buffer[128]) con snprintf.",
|
|
"Si utiliza la clase String de Arduino, invocar 'str.reserve(tamañoMaximo)' de forma temprana para evitar continuas reasignaciones en el heap.",
|
|
"Supervisar el estado con heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) en lugar de consultar únicamente el heap total.",
|
|
],
|
|
},
|
|
codigoArduino: `// Práctica recomendada en Arduino: Uso de snprintf y búfer fijo
|
|
void formatearDatos(float temp, float hum) {
|
|
char jsonBuffer[64];
|
|
// Seguro, rápido y no produce fragmentación en el heap:
|
|
snprintf(jsonBuffer, sizeof(jsonBuffer), "{\"t\":%.2f,\"h\":%.2f}", temp, hum);
|
|
Serial.println(jsonBuffer);
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Inspección del bloque contiguo más grande
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_log.h"
|
|
|
|
void verificar_salud_memoria(void) {
|
|
size_t totalLibre = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
|
size_t mayorBloque = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
|
|
|
|
ESP_LOGI("HEAP", "Total libre: %d bytes | Mayor bloque contiguo: %d bytes",
|
|
totalLibre, mayorBloque);
|
|
// Si mayorBloque es significativamente menor que totalLibre, existe fragmentación severa
|
|
}`,
|
|
codigoMicroPython: `# MicroPython: Recolección proactiva de basura
|
|
import gc
|
|
|
|
def ciclo_mantenimiento():
|
|
# Forzar compactación y recolección de basura antes de asignaciones grandes:
|
|
gc.collect()
|
|
print("Memoria disponible tras GC:", gc.mem_free())`,
|
|
},
|
|
{
|
|
id: "memoria-dma-psram",
|
|
titulo: "Fallo en asignaciones DMA con memoria externa (PSRAM)",
|
|
categoria: "memoria",
|
|
sintomas: ["null-pointer", "random-freeze"],
|
|
gravidade: "Alta",
|
|
logSerial: "E (1234) spi_master: spi_bus_add_device(345): buffer not in DMA-capable memory\nE (1234) lcd: esp_lcd_panel_draw_bitmap failed (-1)",
|
|
resumo: "Un controlador periférico (SPI, I2S, LCD) intentó transferir datos por DMA directamente desde o hacia una dirección de memoria PSRAM externa no compatible.",
|
|
causaRaiz:
|
|
"Los periféricos de alta velocidad utilizan controladores DMA que transfieren datos entre memorias y líneas de hardware de forma autónoma. En chips clásicos (como ESP32 y ESP32-S2), el motor DMA carece de acceso físico directo al bus SPI externo de la PSRAM. Intentar pasar un búfer alojado en PSRAM a funciones de dibujo LCD o transferencias SPI DMA produce un fallo inmediato de asignación.",
|
|
gambiarra: {
|
|
titulo: "Desactivar transferencias DMA y operar en modo polling por CPU",
|
|
descricao: "Configurar los buses para transferir byte por byte mediante registros directos.",
|
|
consequencia: "Caída catastrófica del rendimiento gráfico (de 30 FPS a menos de 5 FPS en pantallas a color) y uso total de la CPU.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Asignar búferes DMA en memoria interna SRAM con capacidades MALLOC_CAP_DMA",
|
|
passos: [
|
|
"Alojar siempre los descriptores y búferes activos de DMA en la memoria SRAM interna usando 'heap_caps_malloc(tamaño, MALLOC_CAP_DMA)'.",
|
|
"Utilizar arquitecturas de doble búfer (ping-pong): mantener los recursos gráficos en PSRAM y copiar por bloques pequeños a un búfer interno DMA de SRAM antes del envío.",
|
|
"En ESP32-S3 y ESP32-P4, verificar las versiones de revisión de silicio que admiten transferencias EDMA directas desde PSRAM.",
|
|
],
|
|
},
|
|
codigoArduino: `// Asignación segura de memoria compatible con DMA en Arduino:
|
|
#include <esp_heap_caps.h>
|
|
|
|
uint8_t *bufferDma = nullptr;
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
|
|
// Asignar exclusivamente en memoria interna con capacidad DMA:
|
|
bufferDma = (uint8_t *)heap_caps_malloc(4096, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
|
if (bufferDma == nullptr) {
|
|
Serial.println(F("[ERROR] Memoria SRAM interna insuficiente para DMA"));
|
|
}
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Asignación de búfer para transacciones de bus SPI
|
|
#include "esp_heap_caps.h"
|
|
|
|
uint8_t* asignar_buffer_spi(size_t len) {
|
|
return (uint8_t*)heap_caps_aligned_alloc(32, len, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
|
}`,
|
|
codigoMicroPython: `# MicroPython: Asignación de búfer con alineación para periféricos
|
|
import uctypes
|
|
|
|
# MicroPython gestiona la memoria DMA internamente al inicializar SPI o Display con drivers compilados`,
|
|
},
|
|
{
|
|
id: "boot-failed-connect",
|
|
titulo: "Failed to connect to ESP32: Timed out waiting for packet header (Fallo de conexión en subida)",
|
|
categoria: "boot-flash",
|
|
sintomas: ["upload-fail"],
|
|
gravidade: "Media",
|
|
logSerial: "Connecting........_____....._____....._____\nA fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header",
|
|
resumo: "La herramienta esptool en la computadora host no pudo comunicarse con la ROM de arranque del ESP32 a través del puerto serie.",
|
|
causaRaiz:
|
|
"El circuito de reinicio automático formado por transistores y líneas DTR/RTS en la placa de desarrollo no logró colocar a tiempo el pin GPIO0 en nivel bajo en el instante en que el pin EN pasó de bajo a alto. Causas muy frecuentes incluyen: cables USB de solo carga (sin líneas de datos D+/D-), ausencia de capacitor de retardo entre EN y GND, o puertos serie bloqueados por otros programas de terminal abiertos en la computadora.",
|
|
gambiarra: {
|
|
titulo: "Mantener pulsado manualmente el botón BOOT en cada grabación",
|
|
descricao: "Presionar con el dedo el botón físico de BOOT de la placa hasta que el esptool detecte el chip.",
|
|
consequencia: "Tedioso durante sesiones prolongadas de desarrollo e inviable cuando el microcontrolador se ubica en un gabinete sellado o en un rack.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Capacitor de 10 µF entre EN y GND y cable USB de datos de calidad",
|
|
passos: [
|
|
"Soldar o conectar un capacitor cerámico o electrolítico de 1 µF a 10 µF entre el pin EN (CHIP_PU) y el pin GND de la placa DevKit para generar el retardo RC necesario.",
|
|
"Asegurarse de cerrar cualquier monitor serie o software de depuración antes de iniciar el proceso de subida.",
|
|
"Verificar con un polímetro la continuidad de las líneas D+ y D- del cable USB.",
|
|
],
|
|
},
|
|
codigoArduino: `// No requiere código fuente en firmware: Este problema radica en la etapa de hardware y conexión serie.
|
|
// Para placas basadas en ESP32-S3 o C3 con USB Serial/JTAG nativo:
|
|
// En el menú Tools de Arduino IDE:
|
|
// USB CDC On Boot: "Enabled"
|
|
// Upload Mode: "UART0 / Hardware CDC"`,
|
|
codigoEspIdf: `// Verificación rápida desde la consola de terminal con esptool:
|
|
// idf.py -p /dev/ttyUSB0 flash monitor
|
|
// Si falla, reducir la velocidad de baudios en el comando:
|
|
// esptool.py -p /dev/ttyUSB0 -b 115200 chip_id`,
|
|
codigoMicroPython: `# En caso de error de conexión al flashear el firmware binario de MicroPython:
|
|
# esptool.py --port /dev/ttyUSB0 --baud 115200 erase_flash
|
|
# esptool.py --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 esp32-firmware.bin`,
|
|
},
|
|
{
|
|
id: "tls-sntp-clock-fail",
|
|
titulo: "Fallo en handshake TLS/HTTPS por reloj desincronizado (Año 1970)",
|
|
categoria: "conectividade",
|
|
sintomas: ["tls-fail"],
|
|
gravidade: "Alta",
|
|
logSerial: "E (5210) esp-tls-mbedtls: mbedtls_ssl_handshake returned -0x2700\nE (5210) esp-tls: Failed to verify peer certificate! (0x00000008 - BADCERT_EXPIRED / NOT_YET_VALID)",
|
|
resumo: "Las solicitudes HTTPS, MQTTS o WebSockets seguros fallan al verificar el certificado de seguridad del servidor.",
|
|
causaRaiz:
|
|
"Al encenderse, el reloj de tiempo real (RTC) interno del ESP32 se inicializa en la época Unix estándar (01 de enero de 1970). Los certificados digitales SSL/TLS emitidos por autoridades certificadoras (como Let's Encrypt o DigiCert) definen un período de validez temporal estricto (ej.: válido de 2024 a 2026). Al comprobar la fecha del certificado con su reloj fijado en 1970, la pila MbedTLS rechaza la conexión con el error BADCERT_NOT_YET_VALID.",
|
|
gambiarra: {
|
|
titulo: "Desactivar la verificación de certificados con setInsecure()",
|
|
descricao: "Configurar el cliente TLS para aceptar cualquier certificado sin validación.",
|
|
consequencia: "Grave vulnerabilidad de seguridad: deja al dispositivo expuesto a ataques Man-in-the-Middle (MitM), donde un atacante en la red local puede interceptar credenciales y datos privados.",
|
|
},
|
|
solucaoEngenharia: {
|
|
titulo: "Sincronizar la hora vía protocolo SNTP antes de iniciar conexiones seguras",
|
|
passos: [
|
|
"Iniciar el cliente SNTP inmediatamente después de que el ESP32 obtenga dirección IP de la red Wi-Fi.",
|
|
"Esperar a que la hora del sistema sea posterior al año 2024 antes de instanciar clientes HTTP/MQTT seguros.",
|
|
"Cargar los certificados de las Autoridades Certificadoras raíz (Root CA) de confianza en formato PEM.",
|
|
],
|
|
},
|
|
codigoArduino: `// Sincronización horaria previa obligatoria antes de peticiones HTTPS:
|
|
#include <WiFi.h>
|
|
#include <WiFiClientSecure.h>
|
|
#include <time.h>
|
|
|
|
void sincronizarHora() {
|
|
// Servidores NTP estándar globales:
|
|
configTime(0, 0, "pool.ntp.org", "time.google.com");
|
|
|
|
Serial.print("Sincronizando reloj con servidores NTP");
|
|
time_t ahora = time(nullptr);
|
|
while (ahora < 1704067200) { // Umbral: 01 de enero de 2024
|
|
delay(500);
|
|
Serial.print(".");
|
|
ahora = time(nullptr);
|
|
}
|
|
Serial.println("\nHora sincronizada correctamente. HTTPS listo.");
|
|
}`,
|
|
codigoEspIdf: `// ESP-IDF: Sincronización horaria mediante SNTP nativo
|
|
#include "esp_sntp.h"
|
|
#include "esp_log.h"
|
|
#include <time.h>
|
|
|
|
void inicializar_sntp(void) {
|
|
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
|
esp_sntp_setservername(0, "pool.ntp.org");
|
|
esp_sntp_init();
|
|
|
|
time_t now = 0;
|
|
struct tm timeinfo = { 0 };
|
|
int reintentos = 0;
|
|
while (timeinfo.tm_year < (2024 - 1900) && ++reintentos < 20) {
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
time(&now);
|
|
localtime_r(&now, &timeinfo);
|
|
}
|
|
}`,
|
|
codigoMicroPython: `# MicroPython: Ajuste de reloj mediante ntptime
|
|
import network
|
|
import ntptime
|
|
import time
|
|
|
|
wlan = network.WLAN(network.STA_IF)
|
|
if wlan.isconnected():
|
|
try:
|
|
ntptime.settime() # Sincroniza el RTC interno con servidores NTP
|
|
print("Hora actual sincronizada:", time.localtime())
|
|
except Exception as e:
|
|
print("Fallo de sincronización NTP:", e)`,
|
|
},
|
|
];
|
|
|
|
export const ERROS_COMUNS = errosCatalogo;
|
|
|
|
export const analisarLogSerial = [
|
|
{
|
|
padrao: /Brownout detector was triggered/i,
|
|
titulo: "Reinicio por subtensión (Brownout)",
|
|
tipo: "Alimentación / Hardware",
|
|
explicacao:
|
|
"La tensión en el riel de 3.3V cayó por debajo del límite operativo seguro durante picos de corriente del módem RF.",
|
|
acaoRecomendada:
|
|
"Añada un capacitor de desacoplo de 10 µF a 100 µF entre 3V3 y GND, o sustituya el cable USB y la fuente de alimentación.",
|
|
},
|
|
{
|
|
padrao: /Guru Meditation Error.*LoadProhibited/i,
|
|
titulo: "Pánico de CPU: LoadProhibited",
|
|
tipo: "Software / Puntero Nulo",
|
|
explicacao:
|
|
"El código intentó leer una dirección de memoria inválida o puntero desreferenciado con valor NULL.",
|
|
acaoRecomendada:
|
|
"Compruebe siempre los punteros antes de acceder a sus miembros y utilice el decodificador de excepciones para localizar la línea.",
|
|
},
|
|
{
|
|
padrao: /Guru Meditation Error.*StoreProhibited/i,
|
|
titulo: "Pánico de CPU: StoreProhibited",
|
|
tipo: "Software / Escritura Inválida",
|
|
explicacao:
|
|
"El código intentó escribir en una dirección de memoria de solo lectura o en un puntero nulo.",
|
|
acaoRecomendada:
|
|
"Verifique las operaciones de escritura en cadenas de texto constantes y la asignación válida de búferes.",
|
|
},
|
|
{
|
|
padrao: /Cache disabled but cached memory region accessed/i,
|
|
titulo: "Fallo de caché en interrupción (ISR)",
|
|
tipo: "Software / Flash & RAM",
|
|
explicacao:
|
|
"Una rutina de interrupción intentó ejecutar código alojado en Flash mientras la memoria caché estaba deshabilitada por escritura.",
|
|
acaoRecomendada:
|
|
"Declare la rutina de interrupción con la macro IRAM_ATTR y no invoque funciones que residan en Flash dentro de la ISR.",
|
|
},
|
|
{
|
|
padrao: /Task watchdog got triggered/i,
|
|
titulo: "Timeout del Task Watchdog (TWDT)",
|
|
tipo: "FreeRTOS / Bloqueo de CPU",
|
|
explicacao:
|
|
"Una tarea ejecutó un bucle sin ceder tiempo al planificador del sistema durante más de 5 segundos.",
|
|
acaoRecomendada:
|
|
"Añada vTaskDelay(1) dentro de los bucles intensivos para permitir que la tarea IDLE alimente el perro guardián.",
|
|
},
|
|
{
|
|
padrao: /A stack overflow in task/i,
|
|
titulo: "Desbordamiento de pila (Stack Overflow)",
|
|
tipo: "FreeRTOS / Memoria de Tarea",
|
|
explicacao:
|
|
"La tarea agotó el espacio de pila asignado en su creación, corrompiendo el centinela de seguridad.",
|
|
acaoRecomendada:
|
|
"Incremente el tamaño de pila en xTaskCreate (ej.: de 2048 a 4096 bytes) y traslade arreglos grandes al heap.",
|
|
},
|
|
{
|
|
padrao: /Timed out waiting for packet header|Failed to connect to ESP32/i,
|
|
titulo: "Fallo de conexión serie en subida de firmware",
|
|
tipo: "Bootloader / Conexión",
|
|
explicacao:
|
|
"La computadora host no logró colocar el ESP32 en modo de descarga de firmware vía líneas DTR/RTS.",
|
|
acaoRecomendada:
|
|
"Conecte un capacitor de 10 µF entre EN y GND, verifique que el cable USB tenga líneas de datos o mantenga presionado el botón BOOT.",
|
|
},
|
|
{
|
|
padrao: /The certificate is not yet valid|-0x2700/i,
|
|
titulo: "Fallo de certificado SSL/TLS (Reloj en 1970)",
|
|
tipo: "Conectividad / TLS",
|
|
explicacao:
|
|
"El ESP32 intentó validar un certificado HTTPS/TLS con su reloj interno desincronizado en la época de 1970.",
|
|
acaoRecomendada:
|
|
"Sincronice la hora mediante el protocolo SNTP (configTime en Arduino o ntptime en MicroPython) antes de iniciar peticiones TLS.",
|
|
},
|
|
{
|
|
padrao: /rst:0x1 \(POWERON_RESET\),boot:0x[0-9a-fA-F]+/i,
|
|
titulo: "Reinicio por encendido / Muestreo de Strapping Pins",
|
|
tipo: "Hardware / Boot",
|
|
explicacao:
|
|
"El microcontrolador se reinició y registró sus pines de arranque. Si se congela de inmediato, un pin de strapping tiene un nivel incorrecto.",
|
|
acaoRecomendada:
|
|
"Compruebe que los pines de arranque GPIOs no estén forzados a niveles incompatibles por circuitos externos en el instante del arranque.",
|
|
},
|
|
];
|