Debugger

(Bild: Micrium)

| von Jean J. Labrosse

Die Entwickler von Embedded-Software sind recht gut mit der Verwendung eines Code-Editors, eines Compilers, Linkers, Debuggers und natürlich eines Evaluierungsboards vertraut. Und meist reichen diese Tools völlig zur Entwicklung und zur Fehlerbehebung eines Embedded-Systems aus. Was aber, wenn man den Betrieb von dynamischen Systemen wie beispielsweise einer Motorregelung, einer Prozesssteuerung oder von Flugsystemen verifizieren möchten? In einer solchen Situation ist ein tiefer Einblick in ein laufendes System erforderlich.

Glücklicherweise sind moderne Prozessoren mit einer speziellen Debugging-Hardware ausgerüstet, die es Tools ermöglicht, bei laufendem Betrieb des Zielsystems Speicherplätze anzuzeigen und zu ändern. In diesem Beitrag geht es um die Frage, wie man mithilfe einer derartigen Debugging-Hardware den Status eines Embedded-Systems mit wenig oder keinerlei CPU-Eingriffen sowie bei laufendem Betrieb des Zielsystems visualisieren kann.

Wenn man schon eine Zeitlang Embedded-Systeme entwickelt hat, weiß man, wie komplex die Bausteine inzwischen geworden und wie schwer sie zu debuggen sind. Mikrocontroller (MCUs) sind eigenständige Bausteine (Blackboxes) mit einem Speicher auf dem Chip, der mit buchstäblich Hunderten oder sogar Tausenden von Registern vollgepackt ist, die zur Steuerung des Betriebs unterschiedlicher Peripheriegeräte Verwendung finden.

Debugger

Bild 1: Beobachtungsfenster eines Debuggers in einer Eclipse-Umgebung. Micrium

Zu jeder Tool-Kette gehört ein Debugger, der es mindestens erlaubt, das Zielsystem anzuhalten und Variable sowie I/O-Register im Beobachtungsfenster zu untersuchen (Bild 1). Diese Fähigkeit ist zwar zum Debuggen von Algorithmen, die keine Echtzeitkomponenten enthalten, durchaus nützlich. Sie bietet freilich wenig Nutzen, wenn man es sich nicht leisten kann, das Ziel wie zum Beispiel eine Motorregelung oder Prozesssteuerung anzuhalten.

Zur Überwachung des einwandfreien Betriebs eines laufenden Embedded-Systems bedienen sich Entwickler einer Reihe von Techniken, die im Folgenden näher beschrieben werden.

LEDs als Anzeige

Im Allgemeinen haben Entwickler von Embedded-Systemen Zugriff auf mindestens eine LED, die anzeigt, dass irgendetwas arbeitet: Wenn das Licht grün wird, hat die CPU beispielsweise eine Verbindung zum Netz hergestellt. LEDs eignen sich hervorragend zur Anzeige eines Go-/No-Go-Zustands. Wenn man jedoch den Status weiterer Operationen verifizieren möchte, braucht man dazu entweder mehr LEDs, oder man muss mit denen, die man hat, kreativ werden: zum Beispiel in Form von Unterbrechungsmustern (Blip) oder Blinkfrequenzen.

7-Segment-Anzeigen

Debugger

Bild 2: Eine 7-Segment-Anzeige kommt häufig bei günstigen Embedded-Systemen zum Einsatz. Micrium

Preisgünstige Embedded-Systeme sind zur Verwendung durch den Endverbraucher oft entweder mit LED- oder mit 7-Segment-LCD-Anzeigen ausgestattet (Bild 2). Der Embedded-Entwickler kann sich die Anzeige während der Entwicklung „ausborgen“, um Hinweise zu erhalten, was im Embedded-System passiert. Eine 7-Segment-Anzeige kann numerische Werte binär, dezimal, hexadezimal oder begrenzt alphanumerisch darstellen.

Im Allgemeinen ist man auf den Wertebereich eingeschränkt, der sich – basierend auf der Anzahl der zur Verfügung stehenden Stellen – anzeigen lässt. Außerdem braucht man, wenn man verschiedene Werte anzeigen möchte, eine Methode, um zwischen den unterschiedlichen Darstellungen zu wechseln. Benötigt das Embedded-Design an sich keine Anzeige, könnte man nur für Testzwecke ein Display hinzufügen. Damit dieses jedoch funktioniert, muss man allerdings speziell für diesen Zweck zusätzlichen Code schreiben.

 

Themen auf der nächsten Seite: Zeichenmodule, printf()-Funktion und Grafikdisplay.

Zeichenmodule

Unter Zeichenmodulen (LEDs oder LCDs) versteht man verhältnismäßig preisgünstige Bausteine, die sich als Debugging-Tool verwenden lassen (Bild 3). Es stehen Module zur Verfügung, die entweder über einen parallelen Port (erfordert 6 bis 10 Ausgangsleitungen) oder eine serielle Schnittstelle (normalerweise UART) verbunden sind. Zeichenmodule gibt es in Konfigurationen von 1 x 8  (1 Zeile mal 8 Spalten) bis hin zu 4 x 40. Diese Displays sind anwenderfreundlich und ermöglichen die Wiedergabe alphanumerischer Zeichen.

Wie bei der 7-Segment-Anzeigetechnik muss man sowohl einen Code schreiben, um die interessierenden Variablen zu formatieren und zu positionieren, als auch eine Methode zur Auswahl verschiedener Werte programmieren, wenn das gewählte Display nicht genug Zeichen für die gewünschten Erfordernisse aufweist. Zeichenmodule haben den Vorteil, dass sie auch Balkendiagramme anzeigen können.

printf()-Funktion

Die Funktion printf() gehört zu überbeanspruchsten und problematischsten Tools, die Entwickler verwenden können. Jedes Mal dann, wenn sie das Auftreten eines Ereignisses oder den Wert von Variablen anzeigen möchten, müssen sie einen String formatieren, den Code umbauen, ihn herunterladen und die Anwendung neu starten. printf()-Ausgänge werden in der Regel an eine Debugger-Textkonsole, einen RS-232C-Port oder eine USB-Schnittstelle gesandt. Allerdings „fliegen“ Werte vom Bildschirm, sobald die Zahl von Reihen erreicht ist, die angezeigt werden können – was oft eher lästig als nützlich ist. Außerdem benötigt printf() nicht nur ziemlich viel Code, sondern wirkt sich auch negativ auf das Timing des Systems aus.

Grafikdisplay

Wenn das Endprodukt ein Grafikdisplay enthält, lässt sich dieses während der Fehlersuche zur Anzeige großer Datenmengen sowie grafischer Darstellungen einsetzen. Der dafür notwendige Debug-Code müsste allerdings danach weggeworfen oder in der freigegebenen Version des Codes versteckt werden. Eine Grafikbibliothek benötigt -zig bis Hunderte Kilobyte Programmspeicher sowie eine Menge RAM (abhängig von der Auflösung des Displays), verbraucht CPU-Zyklen und verkompliziert die Anwendung. Es gibt bessere Alternativen.

Die obengenannten Optionen sind vor allem dann ungeeignet, wenn man versucht, eine große Menge von Daten anzuzeigen. Noch schlimmer ist, wenn man vergessen hat, einige kritische Werte einzubeziehen, die man zur Anzeige einer Prozesssteuerung braucht. Der Entwickler muss dann eine neue Struktur editieren/kompilieren/herunterladen und ablaufen lassen sowie die Anwendung in den Zustand versetzen, den man beobachten möchte. Außerdem: Es ist zwar recht schön, Daten anzuzeigen. Aber was unternimmt man, wenn man die Werte von Variablen wie Sollwerten, Grenzwerten, Verstärkungen oder Verschiebungen auch verändern will?

Graphical Live Watch

Debugger

Bild 4: So ist µC/Probe in die Entwicklungsumgebung eingebunden. Micrium

Moderne Prozessor-Cores wie der ARM Cortex-M oder der Renesas RX enthalten einen Hardware-Debugging-Port, der für direkten Zugriff auf Speicher und Peripherie sorgt, ohne dass ein CPU-Eingriff erforderlich wäre. Mit anderen Worten: Speicher und Lage der I/O lassen sich während der Laufzeit anzeigen und verändern – ohne dass man eine einzige Zeile Ziel-Code schreiben muss.

Micriums Tool „µC/Probe“ verwendet den Debugger-Port, der sich etwa auf Cortex-M- oder RX- MCUs befindet, und ermöglicht auf diese Weise die Anzeige oder Änderung des Werts von Variablen oder I/O-Port-Registern bei laufendem Betrieb des Zielsystems. Werte lassen sich anzeigen, indem man sie grafischen Objekten wie Messgeräten, numerischen Indikatoren, LEDs oder Thermometern zuordnet (Graphical Live Watch). Darüber hinaus lässt sich der Wert von Variablen durch Zuordnung dieser Variablen zu Schiebern, Schaltern, numerischen Eingängen und mehr verändern. µC/Probe kann zudem über RS232C, TCP/IP oder USB mit dem Zielsystem in Verbindung stehen. Dazu ist allerdings ein kleiner Monitor im Zielsystem erforderlich. Die bei weitem bequemste und am wenigsten einschneidende Option ist die Verwendung des Debugger-Interfaces „J-Link“ von Segger.

Bild 4 zeigt in einer Übersicht, wie µC/Probe an die gesamte Entwicklungsumgebung angebunden ist und in welchen Schritten ein Debug-Prozess abläuft:

  • Schreiben von Code mithilfe eines Editors, der dann kompiliert und verlinkt wird (1).
  • Verbindung des Debuggers mit dem Debug-Port des Zielsystems beispielsweise durch ein Segger J-Link-Interface (2)
  • Herunterladen des Codes an die MCU des Zielsystems entweder auf Flash oder RAM. Dann wird dem Debugger befohlen, den Code zum Beginn des Testvorgangs ablaufen zu lassen (3).
  • µC/Probe liest die ausführbare und linkbare Formatdatei, die durch den Compiler generiert wurde, und extrahiert den Namen der Variablen, ihre Datenarten, ihren physikalischen Platz im Speicher (das heißt ihre Adresse) und erstellt anschließend eine Symboltabelle, die zur Zuordnung der Variablen zu einer in µC/Probe eingebauten graphischen Objektbibliothek verwendet wird (4).
  • Drag-and-Drop der Grafikobjekte (Messgeräte, LEDs, Schieber…) und deren Zuordnung zu Variablen, die von der Symboltabelle zur Verfügung stehen. µC/Probe kennt zudem die Namen und Adressen von I/O-Ports durch darin eingebaut Chip-Definitions-Dateien (CDFs). Das ermöglicht es dem Entwickler, rohe Zählerstände von Analog-Digital-Wandlern zu betrachten, Digital-Analog-Wandler (DACs) zu aktualisieren, den Wert von GPIO-Ports nachzuschlagen oder zu ändern, und so weiter (5).
  • Sobald Variable oder I/O-Ports grafischen Objekten zugeordnet sind, ist der „RUN“-Knopf von µC/Probe zu betätigen, und das Tool wird mit der Abfrage des derzeitigen Werts dieser Variablen und I/O-Ports über die J-Link-Schnittstelle beginnen, so schnell es die Schnittstelle erlaubt (6).
  • J-Link wandelt diese Anfragen entweder in Auslesungen oder Einschreibungen in den Speicher um, die gleichzeitig auftreten, während die CPU die Zielapplikation ausführt (7).

Um den Wert zusätzlicher Variablen zu überwachen, hält man µC/Probe einfach an, fügt die Grafikobjekte hinzu, ordnet sie den gewünschten Variablen zu, drückt RUN, und das Tool zeigt diese Variablen an – oder erlaubt deren Änderung. Das Zielsystem braucht dabei nicht angehalten zu werden, und die Entwickler müssen keinen Anwendungscode editieren, kompilieren, herunterladen und neu starten.

 

Auf der nächsten Seite geht es um den Einsatz in der Praxis sowie das Erkennen und Beheben von Anomalien.

Einsatz in der Praxis

Debugger

Bild 5: In µC/Probe ist ein digitales 8-Kanal-Speicheroszilloskop eingebaut. Micrium

Bei einem konkreten Einsatz von µC/Probe stellt sich beispielsweise die Frage, wie sich die Zwischenwerte einer PID-Regelschleife (Proportional-Integral-Derivativ) beobachten lassen, wenn die Update-Rate der Regelschleife bei 1 kHz liegt. Wie in Bild 5 dargestellt, ist in µC/Probe ein digitales Speicheroszilloskop mit 8 Kanälen eingebaut. Auch hier gilt: Man braucht das Zielsystem nicht anzuhalten. Steht die Variable im Symbol-Browser zur Verfügung, lässt sie sich ohne weiteres einem der Kanäle zuordnen. Es ist möglich, auf der positiven oder negativen Flanke jedes beliebigen Kanals zu triggern, Trigger zu verzögern, Vor- oder Nach-Trigger auszuführen, hinein oder heraus zu Zoomen und so weiter. Ohne µC/Probe müsste ein Entwickler die Variable skalieren und sie an die verfügbaren DAC-Ports ausgeben (vorausgesetzt, es sind welche vorhanden), um diese Signale zu betrachten. Dies wäre ein sehr einschneidender Vorgang, und man müsste womöglich die Anwendungen jedes Mal neu aufbauen, wenn man verschiedene Kurven betrachten möchte.

Debugger

Bild 6: µC/Probe besitzt eine eingebaute Kernel-Awareness für die üblichen RTOSs und zeigt diese Information zusammen mit anderen Variablen auch an. Micrium

Auf dem Embedded-Zielsystem kann fabrikneuer Code laufen oder es kann in Verbindung mit einem Echtzeitbetriebssystem-Kernel (RTOS) arbeiten. µC/Probe besitzt eine eingebaute Kernel-Awareness für die üblichen RTOSs, und auch diese Information wird ebenso wie andere Variablen live angezeigt (Bild 6). Der Status jeder Aufgabe wird in einer Zeile angezeigt und enthält deren Name, Task-Priorität, CPU-Nutzung, Run-Zähler, maximale Interrupt-Sperrzeit, maximale Sperrzeit des Schedulers sowie – als die weitaus wertvollste Information – die Stack-Nutzung jedes Tasks. Speziell bei der Entwicklung RTOS-basierter Embedded-Systeme zählt die Einrichtung des für jeden Task benötigten Stapelspeichers zu einem der schwierigsten Aspekte. µC/Probe zeigt die maximale Stack-Nutzung mithilfe eines Balkendiagramms an, das einen sehr schnellen visuellen Eindruck vermittelt, wie nahe oder wie weit entfernt ein Overflow des Stacks ist. Das eingebaute Kernel-Awareness-Feature von µC/Probe versetzt Entwickler darüber hinaus in die Lage, den Status anderer Kernel-Objekte zu überwachen, beispielsweise von Semaphoren, Warteschlangen oder Timern.

Anomalien erkennen und beheben

Das Testen und Debuggen von eingebetteter Echtzeit-Software kann also eine große Herausforderung darstellen. Jedes Tool, das sofortigen Einblick in den inneren Arbeitsablauf einer Anwendung bietet, ist daher eine Betrachtung wert. Die Vorsorge von Chip-Designern, vielseitige Debug-Schnittstellen bereitzustellen, wie sie sich auf modernen Prozessoren wie ARM Cortex-M- und Renesas RX-Prozessoren befinden, macht es den Tools leichter, in laufende Embedded-Systeme hineinzublicken, ohne in die CPU einzugreifen.

Tools zur Datenvisulisierung wie µC/Probe eröffnen Entwicklern damit eine Möglichkeit, einen Blick in ein Embedded-System zu werfen, um ohne Aufwand entweder das einwandfreie Funktionieren des Designs zu bestätigen oder aber um Anomalien zu erkennen und zu beheben.

Eck-DATEN

Moderne Prozessor-Cores enthalten einen Hardware-Debugging-Port, der für einen direkten Zugriff auf Speicher und Peripherie sorgt, ohne dass ein CPU-Eingriff erforderlich wäre. Micriums Tool „µC/Probe“ verwendet diesen Debugger-Port und ermöglicht so die Anzeige oder Änderung des Werts von Variablen oder I/O-Port-Registern bei laufendem Betrieb des Zielsystems. Dies ist Voraussetzung für das Debuggen von dynamischen Systemen wie beispielsweise einer Motorregelung oder einer Prozesssteuerung.

Jean J. Labrosse

(Bild: Micrium)
Gründer und Chief Architect von Micrium Software

(ku)

Kostenlose Registrierung

Der Eintrag "freemium_overlay_form_all" existiert leider nicht.

*) Pflichtfeld

Sie sind bereits registriert?