Warum ist printf () schlecht für das Debuggen eingebetteter Systeme?

16

Ich denke, es ist eine schlechte Sache, ein Mikrocontroller-basiertes Projekt mit zu debuggen printf().

Ich kann verstehen, dass Sie keinen vordefinierten Ort für die Ausgabe haben und dass dieser wertvolle Stifte verbrauchen könnte. Gleichzeitig habe ich gesehen, dass Leute einen UART-TX-Pin für die Ausgabe an das IDE-Terminal mit einem benutzerdefinierten DEBUG_PRINT()Makro verwenden.

Tarabyte
quelle
12
Wer hat dir gesagt, dass es schlecht ist? "Normalerweise nicht das Beste" ist nicht dasselbe wie ein unqualifiziertes "Schlecht".
Spehro Pefhany
6
Das ganze Gerede darüber, wie viel Overhead es gibt. Wenn Sie nur "Ich bin hier" -Nachrichten ausgeben müssen, brauchen Sie überhaupt kein printf, sondern nur eine Routine, um eine Zeichenfolge an einen UART zu senden. Dazu kommt, dass der Code zum Initialisieren des UART wahrscheinlich unter 100 Byte Code liegt. Das Hinzufügen der Fähigkeit, ein paar Hex-Werte auszugeben, erhöht das Ganze nicht so sehr.
Tcrosley
7
@ChetanBhargava - C-Header-Dateien fügen der ausführbaren Datei normalerweise keinen Code hinzu. Sie enthalten Erklärungen; Wenn der Rest des Codes die deklarierten Dinge nicht verwendet, wird der Code für diese Dinge nicht verknüpft. Wenn Sie printfnatürlich den gesamten Code verwenden, der zur Implementierung benötigt wird, printfwird er mit der ausführbaren Datei verknüpft. Aber das liegt daran, dass der Code es verwendet hat, nicht an der Kopfzeile.
Pete Becker
2
@ChetanBhargava Sie müssen nicht einmal <stdio.h> einschließen, wenn Sie Ihre eigene einfache Routine rollen, um eine von mir beschriebene Zeichenfolge auszugeben (geben Sie Zeichen an den UART aus, bis Sie eine '\ 0' sehen). '
tcrosley
2
@tcrosley Ich denke, dieser Rat ist wahrscheinlich umstritten, wenn Sie einen guten modernen Compiler haben, wenn Sie printf im einfachen Fall ohne Format-String gcc verwenden und die meisten anderen ihn durch einen effizienteren, einfachen Aufruf ersetzen, der viel tut, wie Sie beschreiben.
Vality

Antworten:

24

Ich kann mit ein paar Nachteilen der Verwendung von printf () kommen. Denken Sie daran, dass "eingebettetes System" von einigen hundert Byte Programmspeicher bis hin zu einem vollwertigen QNX-RTOS-System für den Rack-Einbau mit Gigabyte RAM und Terabyte nichtflüchtigem Speicher reichen kann.

  • Zum Senden der Daten ist ein Ort erforderlich. Vielleicht haben Sie bereits einen Debug- oder Programmierport auf dem System, vielleicht auch nicht. Wenn Sie dies nicht tun (oder derjenige, den Sie haben, funktioniert nicht), ist es nicht sehr praktisch.

  • Es ist keine einfache Funktion in allen Kontexten. Dies kann eine große Sache sein, wenn Sie einen Mikrocontroller mit nur wenigen KB Arbeitsspeicher haben, da das Verknüpfen in printf möglicherweise 4 KB allein aufzehrt. Wenn Sie einen 32K- oder 256K-Mikrocontroller haben, ist dies wahrscheinlich kein Problem, geschweige denn, wenn Sie über ein großes Embedded-System verfügen.

  • Das Auffinden bestimmter Probleme im Zusammenhang mit der Speicherzuweisung oder Interrupts ist wenig oder gar nicht sinnvoll und kann das Verhalten des Programms ändern, wenn Anweisungen enthalten sind oder nicht.

  • Es ist ziemlich nutzlos für den Umgang mit zeitkritischen Sachen. Mit einem Logikanalysator und einem Oszilloskop oder einem Protokollanalysator oder sogar einem Simulator sind Sie besser dran.

  • Wenn Sie ein großes Programm haben und viele Male neu kompilieren müssen, während Sie printf-Anweisungen ändern, können Sie viel Zeit verschwenden.

Wofür es gut ist - es ist eine schnelle Möglichkeit, Daten auf eine vorformatierte Weise auszugeben, mit der jeder C-Programmierer die Null-Lernkurve zu verwenden weiß. Wenn Sie eine Matrix für den Kalman-Filter ausspucken müssen, den Sie debuggen, ist es möglicherweise hilfreich, sie in einem Format auszuspucken, das MATLAB einlesen kann. Dies ist sicherlich besser, als die RAM-Speicherorte einzeln in einem Debugger oder Emulator zu betrachten .

Ich denke nicht, dass es ein nutzloser Pfeil im Köcher ist, aber er sollte sparsam zusammen mit GDB oder anderen Debuggern, Emulatoren, Logikanalysatoren, Oszilloskopen, statischen Code-Analyse-Tools, Code-Coverage-Tools usw. verwendet werden.

Spehro Pefhany
quelle
3
Die meisten printf()Implementierungen sind nicht thread-sicher (dh nicht re-entrant), was kein Deal Killer ist, aber etwas, das Sie beachten sollten, wenn Sie es in einer Multithread-Umgebung verwenden.
JRobert
1
@JRobert bringt einen guten Punkt auf den Punkt. Selbst in einer Umgebung ohne Betriebssystem ist es schwierig, ein nützliches direktes Debuggen von ISRs durchzuführen. Wenn Sie printf () oder Gleitkomma-Mathematik in einem ISR ausführen, ist der Ansatz wahrscheinlich deaktiviert.
Spehro Pefhany
@JRobert Über welche Debugging-Tools verfügen Softwareentwickler, die in einer Umgebung mit mehreren Threads arbeiten (in einer Hardware-Umgebung, in der der Einsatz von Logikanalysatoren und Oszilloskopen nicht praktikabel ist)?
Minh Tran
1
In der Vergangenheit habe ich mein eigenes threadsicheres printf () gerollt; verwendet Barfuß-Puts () oder Putchar (), um sehr präzise Daten an ein Terminal auszuspucken; gespeicherte Binärdaten in einem Array, das ich nach dem Testlauf ausgegeben und interpretiert habe; einen I / O-Port verwendet, um eine LED zu blinken oder Impulse zu generieren, um Zeitmessungen mit einem Oszilloskop durchzuführen; Ausspucken einer Nummer auf einen D / A & gemessen mit VOM ... Die Liste ist so lang wie Ihre Vorstellungskraft und umgekehrt so groß wie Ihr Budget! :)
JRobert
19

Zusätzlich zu einigen anderen guten Antworten kann das Senden von Daten an einen Port mit seriellen Baudraten in Bezug auf Ihre Schleifenzeit geradezu langsam sein und sich auf die weitere Funktionsweise Ihres Programms auswirken (ebenso wie JEDES Debugging Prozess).

Wie andere Leute Ihnen gesagt haben, ist die Verwendung dieser Technik nichts "Schlechtes", aber sie hat, wie viele andere Debug-Techniken, ihre Grenzen. Solange Sie diese Einschränkungen kennen und bewältigen können, ist es äußerst praktisch, Ihnen dabei zu helfen, Ihren Code richtig zu machen.

Eingebettete Systeme haben eine gewisse Undurchsichtigkeit, die das Debuggen im Allgemeinen zu einem Problem macht.

Scott Seidman
quelle
8
+1 für "eingebettete Systeme haben eine gewisse Deckkraft". Obwohl ich befürchte, dass diese Aussage nur für diejenigen nachvollziehbar ist, die Erfahrung mit Embedded haben, ergibt sie eine schöne, prägnante Zusammenfassung der Situation. Tatsächlich kommt es einer Definition von "eingebettet" sehr nahe.
Njahnke
5

Bei der Verwendung printfauf einem Mikrocontroller treten hauptsächlich zwei Probleme auf.

Erstens kann es schwierig sein, den Ausgang zum richtigen Port zu leiten. Nicht immer. Einige Plattformen sind jedoch schwieriger als andere. Einige der Konfigurationsdateien sind möglicherweise schlecht dokumentiert, und es sind möglicherweise umfangreiche Experimente erforderlich.

Der zweite ist die Erinnerung. Eine vollständige printfBibliothek kann BIG sein. Manchmal benötigen Sie jedoch nicht alle Formatspezifizierer, und es können spezielle Versionen verfügbar sein. Zum Beispiel enthält stdio.h die von AVR bereitgestellte drei verschiedene printfvon unterschiedlicher Größe und Funktionalität.

Da die vollständige Implementierung aller genannten Funktionen ziemlich umfangreich wird, können drei verschiedene Geschmacksrichtungen vfprintf()unter Verwendung von Linker-Optionen ausgewählt werden. Der Standard vfprintf()implementiert alle genannten Funktionen mit Ausnahme von Gleitkommakonvertierungen. Es ist eine minimierte Version von vfprintf()verfügbar, die nur die grundlegenden Funktionen zur Konvertierung von Ganzzahlen und Zeichenfolgen implementiert. Es kann jedoch nur die #zusätzliche Option mithilfe von Konvertierungsflags angegeben werden (diese Flags werden in der Formatspezifikation korrekt analysiert, aber dann einfach ignoriert).

Ich hatte eine Instanz, in der keine Bibliothek verfügbar war, und ich hatte nur minimalen Speicher. Ich hatte also keine andere Wahl, als ein benutzerdefiniertes Makro zu verwenden. Die Verwendung von printfoder nicht ist jedoch eine der Möglichkeiten, die Ihren Anforderungen entsprechen.

embedded.kyle
quelle
Könnte der Downvoter bitte erläutern, was in meiner Antwort falsch ist, damit ich meinen Fehler in zukünftigen Projekten vermeiden kann?
embedded.kyle
4

Um hinzuzufügen, was Spehro Pefhany über "Timing-sensitive Sachen" sagte: Nehmen wir ein Beispiel. Angenommen, Sie haben ein Gyroskop, mit dem Ihr eingebettetes System 1.000 Messungen pro Sekunde durchführt. Sie möchten diese Messungen debuggen und müssen sie daher ausdrucken. Problem: Beim Ausdrucken ist das System zu beschäftigt, um 1.000 Messungen pro Sekunde zu lesen. Dadurch läuft der Puffer des Gyroskops über, und beschädigte Daten werden gelesen (und gedruckt). Wenn Sie also die Daten drucken, haben Sie sie beschädigt und glauben, dass beim Lesen der Daten ein Fehler aufgetreten ist, obwohl dies möglicherweise tatsächlich nicht der Fall ist. Ein sogenannter Heisenbug.

njahnke
quelle
lol! Ist "heisenbug" wirklich ein Fachbegriff? Ich denke, es hat mit der Messung des Partikelzustands und dem Heisenburgschen Prinzip zu tun ...
Zeta.Investigator
3

Der Hauptgrund für das Nicht-Debuggen mit printf () ist, dass es normalerweise ineffizient, unangemessen und unnötig ist.

Ineffizient: printf () und kin verbrauchen viel Flash und RAM im Vergleich zu dem, was auf einem kleinen Mikrocontroller verfügbar ist, aber die größere Ineffizienz liegt beim eigentlichen Debuggen. Das Ändern des Protokolls erfordert das Neukompilieren und Neuprogrammieren des Ziels, wodurch der Prozess verlangsamt wird. Es wird auch ein UART verbraucht, mit dem Sie sonst nützliche Arbeit leisten könnten.

Unangemessen: Es gibt nur so viele Details, die Sie über eine serielle Verbindung ausgeben können. Wenn das Programm hängt, wissen Sie nicht genau, wo, nur die letzte Ausgabe, die abgeschlossen wurde.

Unnötig: Viele Mikrocontroller können remote getestet werden. JTAG- oder proprietäre Protokolle können verwendet werden, um den Prozessor anzuhalten, Register und RAM zu überprüfen und sogar den Status des aktiven Prozessors zu ändern, ohne ihn neu kompilieren zu müssen. Aus diesem Grund sind Debugger im Allgemeinen eine bessere Methode zum Debuggen als Ausdrucke, selbst auf einem PC mit viel Speicherplatz und Leistung.

Es ist bedauerlich, dass die häufigste Mikrocontroller-Plattform für Neulinge, Arduino, keinen Debugger hat. Der AVR unterstützt Remote-Debugging, aber das DebugWIRE-Protokoll von Atmel ist proprietär und nicht dokumentiert. Sie können ein offizielles Entwickler-Board verwenden, um mit GDB zu debuggen, aber wenn Sie das haben, sind Sie wahrscheinlich nicht mehr zu besorgt über Arduino.

Theran
quelle
Könnten Sie nicht Funktionszeiger verwenden, um mit dem zu spielen, was protokolliert wird, und eine ganze Reihe von Flexibilität hinzufügen?
Scott Seidman
3

printf () funktioniert nicht alleine. Es ruft viele andere Funktionen auf, und wenn Sie nur über wenig Stapelspeicher verfügen, können Sie diese möglicherweise überhaupt nicht zum Debuggen von Problemen verwenden, die nahe an Ihrem Stapelspeicherlimit liegen. Je nach Compiler und Mikrocontroller wird die Formatzeichenfolge möglicherweise auch im Speicher abgelegt, anstatt von Flash aus darauf zu verweisen. Dies kann sich erheblich summieren, wenn Sie Ihren Code mit printf-Anweisungen aufpeppen. Dies ist ein großes Problem in der Arduino-Umgebung - Anfänger, die Dutzende oder Hunderte von printf-Anweisungen verwenden, stoßen plötzlich auf scheinbar zufällige Probleme, weil sie ihren Haufen mit ihrem Stapel überschreiben.

Adam Davis
quelle
2
Ich schätze das Feedback, das die Ablehnung selbst liefert, aber es wäre für mich und andere hilfreicher, wenn diejenigen, die anderer Meinung sind, die Probleme mit dieser Antwort erklären würden. Wir sind alle hier, um zu lernen und Wissen zu teilen.
Adam Davis
3

Selbst wenn man Daten auf eine Art Protokollierungskonsole ausspucken möchte, ist die printfFunktion im Allgemeinen keine sehr gute Methode, da sie den übergebenen Format-String untersuchen und zur Laufzeit analysieren muss. Selbst wenn der Code niemals einen anderen Formatbezeichner als verwendet %04X, muss der Controller im Allgemeinen den gesamten Code enthalten, der zum Parsen beliebiger Formatzeichenfolgen erforderlich wäre. Abhängig von dem genauen Controller, den Sie verwenden, kann es wesentlich effizienter sein, Code wie den folgenden zu verwenden:

void log_string(const char *st)
{
  int ch;
  do
  {
    ch = *st++;
    if (ch==0) break;
    log_char(ch);
  } while(1);
}
void log_hexdigit(unsigned char d)
{
  d&=15;
  if (d > 9) d+=7;
  log_char(d+'0');
}
void log_hexbyte(unsigned char b)
{ log_hexdigit(b >> 4); log_hexdigit(b); }
void log_hexi16(uint16_t s)
{ log_hexbyte(s >> 8); log_hexbyte(s); }
void log_hexi32(uint32_t i)
{ log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); }
void log_hexi32p(uint32_t *p) // On a platform where pointers are less than 32 bits
{ log_hexi32(*p); }

Bei einigen PIC-Mikrocontrollern werden log_hexi32(l)wahrscheinlich 9 Befehle benötigt und 17 (wenn lsich in der zweiten Bank befindet), während log_hexi32p(&l)2 benötigt werden. Die log_hexi32pFunktion selbst kann so geschrieben werden, dass sie ungefähr 14 Befehle lang ist, sodass sie sich bei zweimaligem Aufruf bezahlt macht .

Superkatze
quelle
2

Ein Punkt, den keine der anderen Antworten erwähnt hat: In einem einfachen Mikro (IE gibt es nur die main () - Schleife und möglicherweise ein paar ISRs, die zu einem beliebigen Zeitpunkt ausgeführt werden, kein Multi-Thread-Betriebssystem), wenn es abstürzt / stoppt / abruft In einer Schleife stecken, wird Ihre Druckfunktion einfach nicht passieren .

Außerdem haben die Leute gesagt, dass "nicht printf verwenden" oder "stdio.h viel Platz beansprucht", aber nicht viele Alternativen angegeben - embedded.kyle nennt vereinfachte Alternativen, und genau das sollten Sie wahrscheinlich auch sein Selbstverständlich auf einem einfachen eingebetteten System. Eine grundlegende Routine zum Herausspritzen einiger Zeichen aus dem UART könnte ein paar Byte Code sein.

John U.
quelle
Wenn Ihre printf nicht passiert, haben Sie viel darüber gelernt, wo Ihr Code problematisch ist.
Scott Seidman
Vorausgesetzt, Sie haben nur ein printf, das passieren könnte, ja. Aber Interrupts können hunderte Male in der Zeit ausgelöst werden, die ein printf () - Aufruf benötigt, um etwas aus dem UART herauszuholen
John U