Haben Sie einen Mikrocontroller, aber keinen Debugger? Oder vielleicht einen Mikrocontroller, der keinen Debugger unterstützt? Dann sind diese Tipps genau das Richtige für Sie! Während Debugger und ihre integrierten Entwicklungsumgebungen (IDE) clever und außergewöhnlich hilfreich sind, gibt es Probleme, für die sie wenig oder gar keine Hilfe sind. Diese Tipps zum Debuggen unterstützen Sie herauszufinden was in Ihrem Mikrocontroller vor sich geht.

Während die meisten Mikrocontroller-Entwicklungsboards mit einem Onboard-Debugger ausgestattet sind, gibt es immer noch viele ohne diese Möglichkeit. Einer der Vorteile eines Debuggers ist die Möglichkeit, die Codeausführung zu stoppen, den Inhalt von Variablen zu untersuchen und Zeile für Zeile durch den Code zu gehen. Einige Boards, wie Arduino Uno und Mega, verfügen jedoch nicht über eine integrierte Debugging-Unterstützung. Es gibt auch kleine 8-Bit-Mikrocontroller mit niedriger Pin-Anzahl, beispielsweise einige der Microchip PIC10/12/16-Serie, die nicht einmal einen Debugger unterstützen (dafür gibt es einen Workaround mit einem Debug-Header-Board).

Selbst mit einem Debugger können einige Anwendungen während des Betriebs nicht angehalten werden. Geräte, die über USB oder Ethernet kommunizieren, können während der Kommunikation nicht von einem Debugger angehalten werden, da dies die hergestellte Verbindung unterbricht. Gleiches gilt für Motorsteuerungen oder digitale Stromversorgungen, bei denen das Anhalten des Prozessors zu einem Kurzschluss oder einer hohen Stromaufnahme durch einen MOSFET führen kann.

    Haben Sie schon einmal ohne Debugger debuggt?


    Die Blink-LED

    Das mit Abstand einfachste Debug-Tool ist ein Widerstand und eine LED Ihrer Wahl. Verbunden mit einem Standard I/O-Pin (GPIO) kann die LED als Anzeiger (Pointer) an einem strategischen Punkt im Code verwendet werden, ähnlich einem elektronischen Brotkrümel. Es gibt ein paar Beispiele, wo dies helfen kann:

    • Interrupt-Routinen: Datenblätter sind notorisch schwierig zu interpretieren und besonders ist dies mit Allem was mit Interrupts zu tun hat. Viele Peripheriegeräte haben mehrere Interrupts. Eine serielle Schnittstelle kann einen Interrupt erzeugen, wenn Daten gesendet werden, wenn sie empfangen werden, wenn ihr Puffer voll ist, bei einem Pufferüberlauf und vielen anderen Bedingungen. Da ist es kein Wunder, dass es so schwierig ist das richtige Signal mit dem dazugehörigen Code zu aktivieren. Eine Bestätigung, ob ihr Code aufgerufen wurde, können Sie durch einfaches Setzen eines GPIO-Pins in den oberen Ziel-Interrupt-Routinen erreichen.
    • switch und if/else-Anweisungen: Das Schreiben von Logik in C/C++ ist fehleranfällig. Wer hat nicht versehentlich  „&“  anstelle von „&&“ verwendet? Ohne einen Debugger fragen Sie sich möglicherweise, ob ein Codezweig erreicht wird oder ob eine Entscheidungsanweisung dekodiert wird. Auch hier kann das Setzen eines Ausgangssignals am GPIO-Pin anzeigen, ob der Code erreicht wurde.
     
    Eine LED genügt zur Anzeige, ob ein Codeabschnitt erreicht wurde.

    Wenn man nur die Anzeige über GPIOs benutzt, dann optimiert man den Code und dieser ist dann an Einfachheit und Geschwindigkeit unübertrefflich. Die meisten Prozessoren können diese Aufgabe in einem oder zwei Befehlszyklen ausführen, wodurch sichergestellt wird, dass das Ausführungstiming Ihres Codes nicht wesentlich beeinträchtigt wird.

    Leider sind einige Debug-Herausforderungen weit komplexer. In solchen Fällen könnten Sie den verwendeten Ausgang an mehreren Stellen im Code  ein/ausschalten. Dann ist es jedoch unwahrscheinlich, dass Sie die Anzeige der LED immer noch sehen können. Wenn Sie glücklicherweise ein Oszilloskop oder einen Logikanalysator zur Verfügung haben, können sie es am Ausgang anschließen, um zu sehen wie der Code ausgeführt wird.
     

    Der Modem-Ansatz – Wired for Sound!

    Abhängig von der Komplexität der Problemstellung können Sie versuchen weitere Ausgänge ein- bzw. ausschalten, um die Problemstelle zu ermitteln. Bei Controllern mit niedriger Pin-Anzahl haben Sie vielleicht jedoch keine Pins mehr übrig. Wenn Ihr Mikrocontroller  jedoch über ein PWM-Peripherie (Pulsweitenmodulation) verfügt, haben Sie möglicherweise eine weitere Lösungsalternative.

    PWMs haben in der Regel zwei wichtige Konfigurationsoptionen: Frequenz und Tastverhältnis. Nach dem Einschalten schaltet die Peripherie eines Prozessors einen GPIO automatisch auf eine vom Oszillator abhängige Frequenz. Eine Änderung der Frequenz erfordert in der Regel einen Schreibvorgang in ein einzelnes Register. Das Hinzufügen eines Debug-Codes hat somit nur minimale Auswirkungen auf die Codeausführungszeit, ähnlich wie beim LED-Ansatz. Die Konfiguration des Tastverhältnisses kann bei 50% belassen oder variiert werden, je nachdem ob dies zusätzlich beim Debuggen unterstützt.
     

    Ein Piezo Sounder kann über einen kleinen Kondensator an einen Mikrocontroller-Pin angeschlossen werden. Alternativ kann ein Lautsprecher oder Kopfhörer über einen Transistor angeschlossen werden.

    Um Ihren Code nun zu debuggen, müssen Sie einen Piezo-Sounder an den PWM-Ausgangspin anschließen oder Sie können eine Verstärkerschaltung mit Transistor verwenden, um einen Lautsprecher oder Kopfhörerhörer zu betreiben. Zusätzlicher Code, der die Frequenz an strategischen Problempunkten ändert, erzeugt bei der Ausführung eine Melodie. Wenn Sie verschiedene Abschnitte des Codes durchlaufen, ändert sich die "Musik", und Sie erfahren, was mit der Zeit richtig und falsch klingt. Die Älteren unter uns erinnert dies an die alten Modems, mit denen wir uns vor dem DSL-Zeitalter ins Internet gewählt haben!
     

    Taktzeit halten

    Mit einigen Mikrocontrollern ist es einfach die Länge der Ausführung eines Codeabschnitts zu bestimmen. Zum Beispiel benötigen die meisten Assembler-Anweisungen eines PIC10/12 nur einen Taktzyklus, mit Ausnahme von Anweisungen zur Änderung des Programmzählers. Wenn Sie dies Wissen können Sie berechnen, wie lange die Ausführung eines Codeabschnitts unter Ihrer Testbedingung dauern sollte und können diese dann mit einem Oszilloskop oder Logikanalysator vergleichen.

    Das Hardware-Setup für diesen Ansatz ist der gleiche wie für die LED-Lösung, nur dass Sie hier keine LED benötigen. Einziger Unterschied in der Analyse liegt hier in der Vorbereitung zur Bestimmung der Codeausführungszeit.
     

    Ausgangspins bestimmter Codeabschnitte können mit einem Logikanalysator erfasst werden und damit  der Ausführungspfad des Codes verfolgt werden.

    Wenn Sie den Assembler nicht decodieren möchten, können Sie stattdessen die Codeausführungszeit zwischen verschiedenen Punkten messen. Sie werden ein Gefühl dafür bekommen, was möglicherweise schief läuft, indem Sie verschiedene Pfade durch Ihren Code laufen. Wenn beispielsweise eine for- oder while-Schleife außergewöhnlich lang oder kurz ist, kann es sein, dass eine Variable falsch initialisiert wurde oder dass in einer Entscheidungsbedingung das Ende einer Schleife falsch codiert ist.
     

    Ausgeben von Nachrichten

    Sollte Ihr Mikrocontroller einer höheren Klasse angehören, so verfügt er vermutlich über eine serielle Schnittstelle z. B. U(S)ART oder SPI. Wie die PWM sind auch diese voreingerichtet. Die Schnittstelle sollte so konfiguriert werden, dass Daten nicht gepuffert werden müssen und deshalb mit der höchsten Frequenz arbeitet, auch wenn es sich um eine eigenartige, nicht standardmäßige Baud- oder Signalrate handelt. Dies spielt in unserem Fall keine Rolle, da wir den Microcontroller nicht mit einem anderen Gerät verbinden werden. Stattdessen verwenden wir einfach einen Logikanalysator.

    USB-basierte Logikanalysatoren sind heutzutage wirklich billig und die mitgelieferte Software kann durch entsprechende Konfiguration serielle Daten dekodieren. Wenn Sie keinen zur Verfügung haben, können Sie sich sogar Ihren eigenen erstellen. Mit einem Arduino, unterstützt mit einem schnellen, leistungsstarken Arm-Prozessor, sollte dieser Debugging-Ansatz möglich werden.

    Diese Schnittstellen erfordern in der Regel nur das Schreiben eines Datenbytes im Übertragungsregister, was wiederum typischerweise in einem einzigen Taktzyklus geschieht. Danach werden die Bits entsprechend der konfigurierten Datenrate verschoben. Alles, was Sie tun müssen, ist verschiedene Werte an verschiedene Stellen in Ihrem Code unterzubringen. Wenn die Daten gestreamt werden, erhalten Sie eine Ablaufverfolgung des Ausführungspfads Ihres Codes. Wenn die Schnittstelle schnell genug ist, dann sollten auch keine Daten verloren gehen.

    Der serielle Monitor der Arduino IDE kann auch empfangene Nachrichten mit einem Zeitstempel versehen, wodurch unerwartet lange Abschnitte der Codeausführung ermittelt werden können. Die Auflösung des verwendeten Zeitgebers (Timers) reicht jedoch möglicherweise nicht aus, um kurze Abschnitte der Codeausführung aufzulösen.
     

    Der serielle Monitor von Arduino kann Nachrichten mit einem Zeitstempel versehen. Hier sehen wir einen längeren Zeitraum zwischen '2' und '0', der auf ein Codeausführungsproblem hinweisen kann.

    Dieser Ansatz könnte auch verwendet werden, um Werte aus Variablen oder Registern auszulesen. Jedoch kann die Ausgabe aufgrund der begrenzten Informationen schnell zur Verwirrung führen.
     

    Kein Debugger? Es gibt immer einen Weg!

    Nur weil Sie keinen Debugger haben, bedeutet das nicht, dass Sie Ihren Code nicht debuggen können. Sie müssen nur ein wenig querdenken. Die hier beschriebenen Methoden, gepaart mit einem Verständnis Ihres Codes und des verwendeten Mikrocontrollers, können helfen, eine Vielzahl von Problemen zu lösen. Sie können sogar helfen, wenn ein Debugger es nicht kann! Und wenn keines der oben genannten Methoden für Sie geeignet ist, finden Sie vielleicht ein anderes Peripheriegerät oder eine andere Lösung, die Sie bei Ihrer Debugging-Session unterstützen kann.

     
    Übersetzung: Jürgen Kellner