Niveles de registro para una depuración eficaz de sistemas embebidos
La depuración con printf satura rápidamente la consola con registros, especialmente en sistemas complejos. Los niveles de registro resuelven este problema al permitir un control dinámico de la verbosidad mediante la CLI en tiempo de ejecución. Esta ha sido una práctica estándar desde la era de Unix SysLog, donde a cada módulo se le asignan niveles: crítico, error, información, depuración y paranoico.
Comandos como ll usb debug habilitan la depuración para el módulo USB, mientras que ll pll info configura el módulo PLL en modo informativo. Al calcular los coeficientes del PLL, el nivel info muestra únicamente el resultado final, mientras que debug revela cada paso del algoritmo iterativo.
Implementación de la interfaz CLI y macros de registro
La aplicación se estructura en módulos, cada uno con un identificador único facility_t. La CLI (shell/TUI) procesa los comandos LogLevel (ll) para configurar la verbosidad por módulo.
En el código, se utilizan macros como LOG_ERROR, LOG_WARNING, etc., que funcionan de manera similar a printf, pero incluyen un parámetro de facilidad:
void log_write(log_level_t level, facility_t facility, const char* format, ...) {
#ifdef HAS_STREAM
bool res = log_write_prefix(level, facility);
if(res) {
va_list va;
va_start(va, format);
cli_vprintf(format, va);
va_end(va);
log_write_end();
}
#endif
}
void LOG_WARNING(facility_t facility, const char* format, ...) {
#ifdef HAS_STREAM
if(log_write_prefix(LOG_LEVEL_WARNING, facility)) {
va_list va;
va_start(va, format);
cli_vprintf(format, va);
va_end(va);
log_write_end();
}
#endif
}
void LOG_DEBUG(facility_t facility, const char* format, ...) {
#ifdef HAS_STREAM
if(log_write_prefix(LOG_LEVEL_DEBUG, facility)) {
va_list va;
va_start(va, format);
cli_vprintf(format, va);
va_end(va);
log_write_end();
}
#endif
}
La directiva HAS_STREAM elimina por completo el registro en las compilaciones de lanzamiento sin necesidad de modificar el código; las macros simplemente se resuelven a nada durante la compilación.
Definición de facilidades y mapeo de cadenas
Una facilidad es un enum que asigna identificadores únicos a los módulos (SYS=1, GPIO, SPI, etc.), compilado condicionalmente según las características del hardware:
typedef enum {
SYS= 1 ,
UNKNOWN_FACILITY = 2,
#ifdef HAS_ACC
LG_ACC,
#endif
#ifdef HAS_GPIO
GPIO,
#endif
#ifdef HAS_SPI
SPI,
#endif
ALL_FACILITY, /*must be last in enum*/
} facility_t;
El arreglo FacilityInfo_t mapea estos identificadores a cadenas legibles para la salida de los registros:
typedef struct{
facility_t facility;
char* name;
}FacilityInfo_t;
#ifdef HAS_MPU
#define MPU_FACILITY_DIAG { .facility = LG_MPU, .name = "Mpu" },
#else
#define MPU_FACILITY_DIAG
#endif
static const FacilityInfo_t FacilityInfo[] = {
// ...
EEPROM_FACILITY_DIAG
MPU_FACILITY_DIAG
// ...
};
Serializadores para diagnósticos de estructuras
Para interpretar constantes y estructuras de datos, se requieren serializadores en archivos dedicados xxx_diag.c. Estos convierten valores binarios sin procesar en cadenas legibles:
const char* DacLevel2Str(uint8_t code){
const char *name="?";
switch(code){
case DAC_LEV_CTRL_INTERNALY: name="internally"; break;
case DAC_LEV_CTRL_LOW: name="low"; break;
case DAC_LEV_CTRL_MEDIUM: name="medium"; break;
case DAC_LEV_CTRL_HIGH: name="high"; break;
default: name="??"; break;
}
return name;
}
const char* SpiConfigToStr(const SpiConfig_t* const Config) {
strcpy(text,"");
if(Config) {
sprintf(text, "SPI%u", Config->num);
snprintf(text, sizeof(text), "%sRate:%u Hz,", text, Config->bit_rate_hz);
// ... additional fields
}
return text;
}
La CLI invoca estas funciones durante la inicialización y el registro para volcar los estados de configuración.
Niveles estándar log_level_t
Los niveles de registro se definen como un enum utilizando valores negativos para garantizar un ordenamiento correcto:
typedef enum {
LOG_LEVEL_UNKNOWN = -5,
LOG_LEVEL_PARANOID = -4,
LOG_LEVEL_DEBUG = -3,
LOG_LEVEL_PROTECTED = -2,
LOG_LEVEL_NOTICE = -1,
LOG_LEVEL_INFO = 0,
LOG_LEVEL_WARNING = 1,
LOG_LEVEL_ERROR = 2,
LOG_LEVEL_CRITICAL = 3,
LOG_LEVEL_COVERAGE = 4,
LOG_LEVEL_DISABLE = 5,
LOG_LEVEL_LAST = LOG_LEVEL_DISABLE
} log_level_t;
- PARANOID (-4): Verbosidad máxima, con un alto riesgo de saturar el registro.
- DEBUG (-3): Detalles de depuración y datos de estado temporales.
- NOTICE (-1): Eventos notables que requieren atención.
- INFO (0): Operación estándar del sistema.
- WARNING (1): Comportamiento inesperado o posibles precursores de fallos.
- ERROR (2): Fallos que requieren corrección inmediata.
- CRITICAL (3): Fallos graves que pueden detener el sistema.
- COVERAGE (4): Rastreo de rutas de ejecución para análisis de cobertura de código.
Conclusiones clave
- Los niveles de registro se integran perfectamente con la CLI para ajustes en tiempo de ejecución sin necesidad de recompilar.
- Los identificadores
facility_ty los serializadores garantizan que los registros sean legibles, con nombres claros de módulos y valores de parámetros. - La compilación condicional (
#ifdef HAS_STREAM) minimiza la sobrecarga en las compilaciones de producción. - La colocación estratégica de las macros
LOG_XXXes fundamental: usaERRORpara fallos yDEBUGpara el rastreo durante el desarrollo. - Los serializadores automatizan el diagnóstico de estructuras, acelerando significativamente la migración de código heredado cuando se combinan con herramientas de IA.
— Editorial Team
Aún no hay comentarios.