Zurück zur Startseite

Log-Ebenen in Embedded: Debugging ohne Wasserfall

Der Artikel beschreibt die Implementierung von Log-Ebenen in eingebetteten Systemen mit CLI-Schnittstelle. Umfasst LOG_*-Makros, Facility-Enum, Serialisierer für Diagnostik. Vorteile gegenüber printf: dynamische Ausführlichkeitseinstellung ohne Neukompilierung.

Firmware-Debugging: Log-Ebenen und CLI
Advertisement 728x90

Protokollebenen für effektives Debugging von Embedded-Systemen

Printf-Debugging überflutet die Konsole schnell mit Logs, besonders in komplexen Systemen. Protokollebenen lösen dieses Problem, indem sie eine dynamische Steuerung der Ausführlichkeit über die CLI zur Laufzeit ermöglichen. Dies ist seit der Unix-SysLog-Ära Standardpraxis, bei der jedem Modul Ebenen zugewiesen werden: kritisch, Fehler, Info, Debug, paranoid.

Befehle wie ll usb debug aktivieren das Debugging für das USB-Modul, während ll pll info das PLL-Modul auf den Informationsmodus setzt. Bei der Berechnung von PLL-Koeffizienten gibt die info-Ebene nur das Endergebnis aus, wohingegen debug jeden Schritt des iterativen Algorithmus offenlegt.

Implementierung der CLI-Schnittstelle und Logging-Makros

Die Anwendung besteht aus Modulen, die jeweils eine eindeutige facility_t-ID besitzen. Die CLI (Shell/TUI) verarbeitet LogLevel-Befehle (ll), um die Ausführlichkeit pro Modul zu konfigurieren.

Google AdInline article slot

Im Code platziert man Makros wie LOG_ERROR, LOG_WARNING usw., die ähnlich wie printf funktionieren, aber einen Facility-Parameter enthalten:

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
}

Die HAS_STREAM-Direktive entfernt das Logging aus Release-Builds, ohne dass Codeänderungen nötig sind – die Makros werden einfach zu nichts kompiliert.

Definition von Facilities und String-Mapping

Eine Facility ist ein Enum, das Modulen eindeutige IDs zuweist (SYS=1, GPIO, SPI usw.), bedingt kompiliert basierend auf Hardware-Features:

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;

Das FacilityInfo_t-Array ordnet diese IDs menschenlesbaren Strings für die Log-Ausgabe zu:

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

Serialisierer für Strukturdiagnosen

Um Konstanten und Datenstrukturen zu interpretieren, benötigen Sie Serialisierer in dedizierten xxx_diag.c-Dateien. Sie wandeln rohe Binärwerte in lesbare Zeichenketten um:

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

Die CLI ruft diese Funktionen während der Initialisierung und beim Logging auf, um Konfigurationszustände auszugeben.

Google AdInline article slot

Standard-log_level_t-Ebenen

Die Protokollebenen sind als Enum mit negativen Werten definiert, um eine korrekte Sortierung zu gewährleisten:

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): Maximale Ausführlichkeit mit hohem Risiko einer Log-Überflutung.
  • DEBUG (-3): Debugging-Details und temporäre Zustandsdaten.
  • NOTICE (-1): Bemerkenswerte Ereignisse, die Aufmerksamkeit erfordern.
  • INFO (0): Standard-Systembetrieb.
  • WARNING (1): Unerwartetes Verhalten oder potenzielle Vorboten von Fehlern.
  • ERROR (2): Fehler, die sofortige Korrekturen erfordern.
  • CRITICAL (3): Schwere Ausfälle, die das System zum Stillstand bringen können.
  • COVERAGE (4): Ausführungspfadverfolgung zur Analyse der Codeabdeckung.

Wichtige Erkenntnisse

  • Protokollebenen integrieren sich nahtlos in die CLI zur Laufzeitanpassung ohne Neukompilierung.
  • facility_t-IDs und Serialisierer sorgen dafür, dass Logs mit klaren Modulnamen und Parameterwerten lesbar bleiben.
  • Bedingte Kompilierung (#ifdef HAS_STREAM) minimiert den Overhead in Produktions-Builds.
  • Strategische Platzierung von LOG_XXX-Makros ist entscheidend: Verwenden Sie ERROR für Fehler und DEBUG für die Entwicklungsnachverfolgung.
  • Serialisierer automatisieren die Strukturdiagnose und beschleunigen die Migration von Legacy-Code erheblich, wenn sie mit KI-Tools kombiniert werden.

— Editorial Team

Advertisement 728x90

Weiterlesen