Zpět na domů

Úrovně logování v embedded: ladění bez vodopádu

Článek popisuje implementaci úrovní logování v embedded systémech s CLI rozhraním. Zahrnuje makra LOG_*, facility enum, serializátory pro diagnostiku. Výhody oproti printf: dynamické nastavení verbosity bez překompilace.

Ladění prošívek: úrovně logování a CLI
Advertisement 728x90

Úrovně logování pro efektivní ladění embedded systémů

Ladění pomocí printf často vede k zahlcení konzole logy, zejména u složitých systémů. Úrovně logování tento problém řeší tím, že umožňují dynamicky řídit úroveň podrobnosti (verbosity) přímo za běhu přes CLI. Jedná se o standardní praxi sahající až do éry Unix SysLog, kde se pro každý modul definují úrovně: critical, error, info, debug, paranoid.

Příkazy jako ll usb debug aktivují ladění pro USB modul, zatímco ll pll info nastaví informativní úroveň pro PLL. Při výpočtu koeficientů PLL se na úrovni info zobrazí pouze výsledek, zatímco na úrovni debug i kroky iteračního algoritmu.

Implementace CLI rozhraní a logovacích maker

Program je sestaven z modulů s unikátními ID typu facility_t. CLI (shell/TUI) zpracovává příkazy LogLevel (ll) pro nastavení úrovně podrobností pro jednotlivé moduly.

Google AdInline article slot

V kódu se používají makra LOG_ERROR, LOG_WARNING atd., která fungují podobně jako printf, ale s parametrem facility:

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
}

Direktiva HAS_STREAM umožňuje vypnout logy v release verzi bez úpravy kódu – makra se při překladu nahradí prázdným obsahem.

Definice facility a mapování na řetězce

Facility je výčet (enum) s unikátními ID pro moduly (SYS=1, GPIO, SPI atd.), které lze podmíněně kompilovat:

Google AdInline article slot
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;

Pole FacilityInfo_t převádí ID na řetězce pro logy:

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
    // ...
};

Serializátory pro diagnostiku struktur

Pro interpretaci konstant a struktur jsou potřeba serializátory v souborech xxx_diag.c. Převádějí binární hodnoty na čitelné řetězce:

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);
        // ... další pole
    }
    return text;
}

CLI je volá při inicializaci a logování pro výpis konfigurací.

Google AdInline article slot

Standardní úrovně log_level_t

Definice úrovní jako enum se zápornými hodnotami pro správné řazení:

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): Maximální podrobnost, riziko „záplavy“.

— Editorial Team

Advertisement 728x90

Číst dál