Hi,
mich interessiert, wie das Verhältnis zwischen denen, die printf/scanf
verwenden und denen, die's nicht tun, ist.
Aus der 8051 8-Bit-Welt kommend, die Cortex-M 32-Bit-Welt erkundend habe
ich eigentlich immer einen Bogen um die Standard-C-Funktionen gemacht
und mir eigene geschrieben.
Mal den verwendeten Compiler und die Qualität der
printf/scanf-Implementierung ausser acht gelassen, ist beispielsweise
printf mit seinen vielfältigen Formatierungs- und Ausgabemöglichkeiten
für meine Anwendungen i.d.R. zu groß und zu langsam.
Hinzu kommt, dass ich mir absolut nicht vorstellen kann, dass man
beispielsweise eine UART-Kommunikation mit Protokoll (z.B. Header,
Daten, CRC, etc.) gescheit mit printf realisieren kann.
Was ich beispielsweise bei der LPCxpresso IDE gut finde ist die
Möglichkeit, per printf Ausgaben auf die Debug-Konsole leiten zu können,
das mag für's Debuggen eben nützlich sein. Aber für die eigentliche
Applikation ziehe ich self-made-Funktionen, die von Grund auf erstmal
einzelne Bytes schicken vor. Weitere Ausgabemöglichkeiten bauen eben
darauf auf.
Ähnlich ist es, wenn der verwendete µC mehrere UARTs unterstützt, wenn
man das per printf managen will muss man doch schon gehörig aufpassen.
Hat das schon jemand so gemacht? Das finde ich per selbstgeschriebenen
Funktionen doch etwas einfacher.
Wie macht ihr das? Oder andersrum gefragt, übersehe ich einen
exorbitanten Vorteil von printf?
Ralf
>Hinzu kommt, dass ich mir absolut nicht vorstellen kann, dass man>beispielsweise eine UART-Kommunikation mit Protokoll (z.B. Header,>Daten, CRC, etc.) gescheit mit printf realisieren kann.
Echt nicht? Warum sollte das nicht gehen? Mach ich jeden Tag
mit printf.
Ich habe mir selbst nur die beiden uart-Funktionen geschrieben:
1
intuart_putc(charc,FILE*stream);
2
intuart_getc(FILE*stream);
Die rufe ich im Programm aber nicht direkt auf, sondern registriere sie
als stdio-Stream. Alle Ausgaben im Programm mache ich mit den
Standard-Funktionen: putchar, getchar, puts, printf, ... Auf dem AVR
gibt es die alle auch mit "_P", um aus dem Flash zu lesen.
Wenn man statt printf die einfacheren Funktionen nimmt, braucht das nur
minimal mehr Programmspeicher. Mit printf kann man dafür superbequem
Debug-Ausgaben oder halt komplexere Formatierungen erzeugen. Den Platz
im Flash habe ich normalerweise auch noch übrig. Warum also das Rad
immer neu erfinden. UART ist eh ziemlich lahm im Vergleich zu anderen
Schnittstellen, wirkliche Performancevorteile sind also mit eigenen
Implementierungen auch nicht zu erwarten.
Und das aus meiner Sicht wichtigste: Das Programm ist portabler. Die
Standardfunktionen hat man am PC und auf größeren Controllern mit Linux
sowieso und auf anderen Mikrocontroller sind die beiden Basisfunktionen
schnell implementiert. Man kann außerdem problemlos von UART auf andere
Schnittstellen wechseln, ohne alle seine print-Funktionen ändern zu
müssen.
@Holger:
> Echt nicht? Warum sollte das nicht gehen? Mach ich jeden Tag> mit printf.
Sieht das dann nicht ziemlich wirr im Code aus?
@xfr:
> Ich habe mir selbst nur die beiden uart-Funktionen geschrieben:> int uart_putc(char c, FILE* stream);> int uart_getc(FILE* stream);> Die rufe ich im Programm aber nicht direkt auf, sondern registriere sie> als stdio-Stream. Alle Ausgaben im Programm mache ich mit den> Standard-Funktionen: putchar, getchar, puts, printf, ...> ...> Und das aus meiner Sicht wichtigste: Das Programm ist portabler. Die> Standardfunktionen hat man am PC und auf größeren Controllern mit Linux> sowieso und auf anderen Mikrocontroller sind die beiden Basisfunktionen> schnell implementiert.
Naja, das vielleicht schon, aber wie sieht's aus wenn's mal SPI oder gar
I2C ist?
Ralf
printf() ist überhaupt kein Problem.
scanf() ist eine Fehlgeburt schlechthin, da man die noch im Eingabestrom
verbleibenden Zeichen bei einem Konvertierungsfehler nicht unter
Kontrolle bekommt.
Aber gegen fgets() & sscanf() ist nichts einzuwenden.
Klar, wenn man sehr ressourcenlimitiert ist, will man das alles nicht
haben. Dann erkauft man sich halt Hardware-Einsparungen, indem man
ein maßgeschneidertes Software-Fahrrad nochmal erfinden muss, statt
das Fahrrad von der Stange zu nehmen.
Ralf schrieb:> Naja, das vielleicht schon, aber wie sieht's aus wenn's mal SPI oder gar> I2C ist?>> Ralf
Kommt halt drauf an, was man macht. Zum Ansteuern von Sensoren ist
printf natürlich Quatsch. Da überträgt man ja auch nur Binärdaten. Ich
hatte jetzt hauptsächlich Anwendungen im Kopf, bei denen man Texte für
den Menschen lesbar ausgibt. Da ist schon alleine wegen der menschlichen
Lesegeschwindigkeit die Datenrate ja typischerweise sehr gering.
Ich arbeite z.B. zur Zeit mit zwei verschiedenen Mikrocontrollern. Einer
ist ein ATxmega mit klassischem RS232, der andere ein Atmega mit USB,
der per LUFA-Bibliothek einen virtuellen COM-Port bereitstellt. Auf
beiden läuft zur Zeit genau das gleiche Programm. Der einzige
Unterschied ist, dass auf dem Xmega steht:
1
staticFILEuart_stdio;
2
3
intmain(void)
4
{
5
[...]
6
uart_init();
7
uart_create_stream(&uart_stdio);
8
stdin=&uart_stdio;
9
stdout=&uart_stdio;
10
[...]
11
}
Und auf dem ATmega mit USB:
1
staticFILEusb_stdio;
2
staticUSB_ClassInfo_CDC_Device_tcdc_device=[...]
3
4
intmain(void)
5
{
6
[...]
7
USB_Init();
8
CDC_Device_CreateStream(&cdc_device,&usb_stdio);
9
stdin=&uart_stdio;
10
stdout=&uart_stdio;
11
[...]
12
}
Alles andere ist gleich, es sind die gleichen C-Dateien für beide
Programme. Die gleiche Anwendung könnte ihre Texte z.B. auch auf einem
Display oder per Funk ausgeben. Man muss nur einmal stdout bzw. stdin
festlegen. Wenn man sich für alles eigene Funktionen baut, geht das
nicht so schön.
@Jörg Wunsch:
> scanf() ist eine Fehlgeburt schlechthin, da man die noch im Eingabestrom> verbleibenden Zeichen bei einem Konvertierungsfehler nicht unter> Kontrolle bekommt.
Das ist einer der Gründe, warum ich diese Funktionen mehr oder weniger
ignoriert habe.
> Aber gegen fgets() & sscanf() ist nichts einzuwenden.
Die kenne ich nicht im Detail, da ich wie gesagt einen Bogen um die
Sachen gemacht habe. Ich werd mal danach suchen, was diese Funktionen
konkret machen.
@xfr:
> Kommt halt drauf an, was man macht. Zum Ansteuern von Sensoren ist> printf natürlich Quatsch. Da überträgt man ja auch nur Binärdaten.
Das meinte ich, für so was halte ich printf etc. für zu die falsche
Wahl.
> Ich hatte jetzt hauptsächlich Anwendungen im Kopf, bei denen man Texte> für den Menschen lesbar ausgibt. Da ist schon alleine wegen der> menschlichen Lesegeschwindigkeit die Datenrate ja typischerweise sehr> gering.
Dann sind's aber i.d.R. eh Debugausgaben. Sonst könnte ich mir keinen
echten Anwendungsfall vorstellen, ausser vielleicht die Konfiguration
zur Laufzeit über die serielle Schnittstelle.
> Ich arbeite z.B. zur Zeit mit zwei verschiedenen Mikrocontrollern. Einer> ist ein ATxmega mit klassischem RS232, der andere ein Atmega mit USB,> der per LUFA-Bibliothek einen virtuellen COM-Port bereitstellt. Auf> beiden läuft zur Zeit genau das gleiche Programm. Der einzige> Unterschied ist, dass auf dem Xmega steht:> ...
Das ist normales C, soweit ich sehen kann? Wie würdest du es dann
machen, wenn du auf mehreren Schnittstellen arbeiten musst?
> Alles andere ist gleich, es sind die gleichen C-Dateien für beide> Programme. Die gleiche Anwendung könnte ihre Texte z.B. auch auf einem> Display oder per Funk ausgeben. Man muss nur einmal stdout bzw. stdin> festlegen. Wenn man sich für alles eigene Funktionen baut, geht das> nicht so schön.
Klar, man kann die untersten Zugriffsfunktionen ummappen. Und wie setzt
du dann beispielsweise auf einem Display die Cursorsteuerung um? Dann
musst du per Escape-Sequenzen die Cursorsteuerung in der
Zugriffsfunktion erledigen, oder?
Ralf
Ralf schrieb:> Wie würdest du es dann> machen, wenn du auf mehreren Schnittstellen arbeiten musst?
Die streambasierten Varianten nutzen, denen noch ein Filehandle
mitgegeben wird. Also fprintf. Alternativ bastelt man sich eine
eigene Variante, der statt des vielleich doch mit arg viel Overhead
belasteten Filehandles eine Schnittstellennummer mitgegeben wird, die
dann wiederum vprintf o.ä. nutzt.
> Das ist normales C, soweit ich sehen kann? Wie würdest du es dann> machen, wenn du auf mehreren Schnittstellen arbeiten musst?
Pro Schnittstelle hat man ein FILE. Die kann man natürlich alle
nebeneinander nutzen, man nimmt dann fprintf, fputs, fputc, usw..
Mit stdout, stdin, stderr legt man nur die Standardschnittstelle fest,
über die Benutzerein- und ausgaben bzw. Fehlermeldungen gesendet werden
sollen. Also im Prinzip, welche Schnittstelle genommen wird, wenn man
keine angibt (also printf statt fprintf nimmt).
Angenommen ich hätte einen Mikrocontroller mit RS232 und USB und würde
mir selber einen RS232-USB-Adapter bauen wollen. Dann sähe der Code ca.
so aus:
> Klar, man kann die untersten Zugriffsfunktionen ummappen. Und wie setzt> du dann beispielsweise auf einem Display die Cursorsteuerung um? Dann> musst du per Escape-Sequenzen die Cursorsteuerung in der> Zugriffsfunktion erledigen, oder?
Das hätte man dann in der Funktion:
1
intdisplay_putc(charc,FILE*stream);
Die bekommt ja einzelne Zeichen geliefert. Da kann man also leicht eine
Fallunterscheidung einbauen, die die Steuerzeichen in die entsprechenden
Displaybefehle umsetzt. Also '\n' neue Zeile, '\r' an den Anfang der
Zeile gehen usw..
Wenn es für die Anwendung sinnvoll ist, bestimmte Spezialfunktionen des
Displays zu nutzen, hält einen aber ja auch keiner davon ab, dafür
Extra-Funktionen zu haben. Nur muss man ja nicht alle Varianten von
String aus RAM ausgeben, String aus Flash ausgeben, Zahl in String
umwandeln, Zahl formatieren usw. für das Display neu schreiben, wenn es
das in stdio.h schon millionenfach bewährt und getestet gibt.
Ralf schrieb:> übersehe ich einen> exorbitanten Vorteil von printf?
Ich kenne das so: printf greift zur Ausgabe auf putc zurück. putc
implementiere ich für die gewünschte Schnittstelle. So kann mein
Programm schnell für UART, SPI, LCD, usw. konfigriert werden.
Wenn ich den Platz im Flash habe, dann benutze ich ganz gerne sprintf ob
seiner vielfältigen Formatiermöglichkeiten. Die vollständige
Streamanbindung, um printf nutzen zu können, ist mir allerdings zuviel
Action.
Mit sprintf und einer Funktion, die einen String ausgibt (auf welchem
Gerät auch immer) finde ich locker das auslangen.
@Rufus:
>> Wie würdest du es dann>> machen, wenn du auf mehreren Schnittstellen arbeiten musst?> Die streambasierten Varianten nutzen, denen noch ein Filehandle> mitgegeben wird. Also fprintf. Alternativ bastelt man sich eine> eigene Variante, der statt des vielleich doch mit arg viel Overhead> belasteten Filehandles eine Schnittstellennummer mitgegeben wird, die> dann wiederum vprintf o.ä. nutzt.
Verstehe. vprintf verkraftet im Gegensatz zu printf eine
Argumentenliste?
@xfr:
> Pro Schnittstelle hat man ein FILE. Die kann man natürlich alle> nebeneinander nutzen, man nimmt dann fprintf, fputs, fputc, usw..
Und wie wird festgelegt, wie ein FILE zu handhaben ist? Das wird dann in
der fputc-Routine auseinander genommen oder wie?
> Mit stdout, stdin, stderr legt man nur die Standardschnittstelle fest,> über die Benutzerein- und ausgaben bzw. Fehlermeldungen gesendet werden> sollen. Also im Prinzip, welche Schnittstelle genommen wird, wenn man> keine angibt (also printf statt fprintf nimmt).
Ah, ich könnte also stdout auf die eigentliche Applikationsschnittstelle
umbiegen und mit fprintf Debugausgaben auf einer anderen Schnittstelle
machen. Wenn ich die dann in der fertigen Applikation nicht haben will
nehme ich per #define die Ausgabe aus der fputc raus?
> Angenommen ich hätte einen Mikrocontroller mit RS232 und USB und würde> mir selber einen RS232-USB-Adapter bauen wollen. Dann sähe der Code ca.> so aus:> ...
Okay, das erklärt dann die Frage oben (fputc bestimmt anhand des
FILE-Handles wohin die Daten sollen).
>> Klar, man kann die untersten Zugriffsfunktionen ummappen. Und wie setzt>> du dann beispielsweise auf einem Display die Cursorsteuerung um? Dann>> musst du per Escape-Sequenzen die Cursorsteuerung in der>> Zugriffsfunktion erledigen, oder?> Das hätte man dann in der Funktion:> int display_putc(char c, FILE* stream);
Diese Funktion wird von fputc anhand des FILE-Handles aufgerufen?
> Die bekommt ja einzelne Zeichen geliefert. Da kann man also leicht eine> Fallunterscheidung einbauen, die die Steuerzeichen in die entsprechenden> Displaybefehle umsetzt. Also '\n' neue Zeile, '\r' an den Anfang der> Zeile gehen usw..> Wenn es für die Anwendung sinnvoll ist, bestimmte Spezialfunktionen des> Displays zu nutzen, hält einen aber ja auch keiner davon ab, dafür> Extra-Funktionen zu haben.
Das meinte ich damit, dass printf nicht für alles gut sein kann ;)
Ich kann mir eben nicht vorstellen, dass man dann solche Sachen wie
'Cursor blinken' (beispielsweise bei einem HD44780-Display) dann auch
über printf aktiviert. Und das ist ja dann noch eine sehr grundlegende
Funktion.
Bei einem Grafikdisplay würde ich auch nicht unbedingt die Funktion
für's Kreis zeichen o.ä. über printf aufrufen, sondern direkt und damit
printf "umfahren". Das ist es, was mir dann auch ein bisschen
Nachdenklichkeit beschert, ich hab bis jetzt allgemein Funktionen immer
so geschrieben, dass es immer nur einen strikten Weg gibt, um etwas
anzusteuern etc.
Die Kombination aus "Standard" (printf) und "Self-made" Funktionen für
bestimmte Aufgaben will mir halt nicht so ganz runter, deswegen hab ich
bisher auf "Standard" eher verzichtet.
@ Abstrakt:
> Ich kenne das so: printf greift zur Ausgabe auf putc zurück. putc> implementiere ich für die gewünschte Schnittstelle.
So kannte ich's vom 8-Bit-C-Compiler auch. Aber der GCC für Cortex-M den
ich hier hab verwendet kein putc, sondern irgendein sys_write o.ä. und
gibt die Ausgaben erstmal (korrekterweise) auf die Debugkonsole aus.
> So kann mein Programm schnell für UART, SPI, LCD, usw. konfigriert> werden.
Ich sehe nach wie vor Probleme zwischen Schnittstellen und
Anzeigeeinheiten.
UART, SPI, kein Thema. Byteweiser Datenstrom, fertig. LCD ist ne andere
Sache. Bei Character-LCD das oben erwähnte Steuern des Cursors, bei
Grafik-LCD nochmal verschärft (Zeichenfunktionen, etc.). Da könnte ich
mir nur vorstellen, dass printf bzw. putc dann eben Parameter "sammelt"
und dann die entsprechenden Funktionen zur Darstellung aufruft.
@Karl-Heinz:
> Wenn ich den Platz im Flash habe, ...
Das wäre ein separates Thema, ich bin da etwas vorbelastet ;)
> ...dann benutze ich ganz gerne sprintf ob seiner vielfältigen> Formatiermöglichkeiten. Die vollständige Streamanbindung, um printf> nutzen zu können, ist mir allerdings zuviel Action.
Ja, das scheint eine größere Sache zu sein (bzw. sein zu können).
> Mit sprintf und einer Funktion, die einen String ausgibt (auf welchem> Gerät auch immer) finde ich locker das auslangen.
Womit man wieder bei den Debugausgaben wäre bzw. einer
Laufzeitkonfiguration, andere Anwendungsfälle erschließen sich mir da
nicht so recht (ausser vielleicht noch Ausgaben auf einem kleinen
einfachen Display).
Ralf
Ralf schrieb:>> Das hätte man dann in der Funktion:>> int display_putc(char c, FILE* stream);> Diese Funktion wird von fputc anhand des FILE-Handles aufgerufen?
Da wird überhaupt nichts von alleine aufgerufen.
Es ist dein Bier, das du dir in der fputc, bzw. eigentlich in der
Funktion die #du# zur Zeichenausgabe bereitstellst, den File-Handle
ansiehst und je nachdem auf die diversen Geräte verteilst.
Du stellst dir das ein bischen mit zuviel Komfort vor.
Dein File.Handle ist deine 'Kennung' für das Ausgabegerät. Du stopfst
die oben in die High-Level Funktionen hinein und der wird bei allen
Funktionsaufrufen intern im I/O System überall mitgeschleift, so dass er
unten, bei den Low-Level Funktionen (die du schreibst!) wieder
herauskommt und du in den Low-Level Funktionen dann dementsprechend auf
die Geräte verteilen kannst.
That's it. Mehr steckt da nicht dahinter.
Und ein printf() ist nichts anderes als ein fprintf(), bei dem das
Streamargument im Grunde eine Default-Belegung hat. Nämlich stdout -
damit man das nicht jedesmal tippen muss.
> Ich kann mir eben nicht vorstellen, dass man dann solche Sachen> wie 'Cursor blinken' (beispielsweise bei einem HD44780-Display) dann> auch über printf aktiviert. Und das ist ja dann noch eine sehr> grundlegende Funktion.
Nichts und niemand hindert dich daran, dir selber irgendwelche spezielle
Codes zu definieren, die du zb in den Formatstring einbaust, und die
dann in der Low-Level Funktion von deiner Ausgaberoutine entsprechend zu
dem umgesetzt werden, was das Display sehen möchte, damit es den Cursor
blinken lässt. Für das Standard-I/O System sind das einfach nur Zeichen,
die es durchschleust und die letzten Endes irgendwann bei der
Treiber-Funktion landen, die sich dann um die Ausgabe kümmert.
Wie schon gesagt: Du hast da eine Vorstellung von 'Komfort', die so
nicht existiert. Das I/O System kümmert sich nicht darum, was es tun
muss um am Display einen Cursor einzuschalten oder einen Kreis zu malen.
Alles was es tut: Im Format String die Argumente je nach
Formatieranweisungen einzusetzen und so einen String zu generieren, den
es zeichenweise über die von dir angegebene Funktion auf den Weg bringt.
Was diese Zeichen dann letzten Endes für eine Bedeutung haben,
interessiert das I/O System nicht.
printf_P(PSTR("Hallo Welt aus dem Flash an stdout!\r\n"));
13
fprintf(&uart_stdio,"Hallo Welt an UART!\r\n");
14
}
Die Standard-IO-Funktionen kümmern sich also darum, dass die zu FILE
passende Funktion aufgerufen wird. Man muss nur einmal mit
fdev_setup_stream() festlegen, welche putc- und getc-Funktion zu dem
FILE gehört. In den selber implementierten Funktionen kann man den
Parameter FILE einfach ignorieren. Er muss halt dabei sein, damit die
Signatur der Funktion stimmt.
Ralf schrieb:> Das meinte ich damit, dass printf nicht für alles gut sein kann ;)> Ich kann mir eben nicht vorstellen, dass man dann solche Sachen wie> 'Cursor blinken' (beispielsweise bei einem HD44780-Display) dann auch> über printf aktiviert. Und das ist ja dann noch eine sehr grundlegende> Funktion.
Das kann man schon machen. Es gibt ja einige nicht-sichtbare Zeichen im
ASCII-Code, die man für so etwas benutzen kann. War früher auch so
üblich: http://en.wikipedia.org/wiki/ANSI_escape_code> Bei einem Grafikdisplay würde ich auch nicht unbedingt die Funktion> für's Kreis zeichen o.ä. über printf aufrufen, sondern direkt und damit> printf "umfahren". Das ist es, was mir dann auch ein bisschen> Nachdenklichkeit beschert, ich hab bis jetzt allgemein Funktionen immer> so geschrieben, dass es immer nur einen strikten Weg gibt, um etwas> anzusteuern etc.> Die Kombination aus "Standard" (printf) und "Self-made" Funktionen für> bestimmte Aufgaben will mir halt nicht so ganz runter, deswegen hab ich> bisher auf "Standard" eher verzichtet.
Kreisfunktionen auf einem Grafikdisplay über printf fände ich auch sehr
schräg. :D Finde aber nicht, dass etwas dagegen spricht, die
Standardfunktionen für Textausgabe zu benutzen und für Grafik- und
Spezialfunktionen eine eigene Bibliothek anzubieten. Kurz gesagt: Das
richtige Werkzeug für den richtigen Zweck benutzen.
Die Standard-IO-Funktionen sind imo dafür gemacht, für Menschen lesbaren
Text auszugeben. Sei es live auf dem Display, per UART, in Logdateien
oder auch in textbasierten Netzwerkprotokollen. Für andere Dinge wie
Grafiken anzeigen sind die nicht gedacht und auch nicht geeignet.
Ich würde sie auch nicht zum Senden von reinen Binärdaten benutzen (z.B.
per SPI oder I2C). Denn da hat man ja in der Regel einfach Binärzahlen
mit fester Länge, die in einem Array oder Struct stehen, das man dann
byteweise ausgibt. Da gibt es nichts mehr zu formatieren. Zudem arbeiten
die Standard-IO-Funktionen mit nullterminierten Strings und nicht mit
festen Längen. Man wird auch keinen Sensor, der per SPI angesteuert
wird, plötzlich per I2C oder UART ansteuern. Daher machts imo keinen
Sinn, solche Dinge in das Standard-IO-Schema zu quetschen.
Wie gesagt: Das richtige Werkzeug für den richtigen Zweck.
Hier:
printf benutze ich nie, etwaige Sonderformatierungen konvertiere ich zu
Fuss und werf sie dann auf den Ausgang. Für scanf habe ich eine
universelle Routine, die mit seriellen und digitalen (Taster-) Eingängen
klarkommt und auch bis zu 2 Argumente futtert und an den
Kommandointerpreter übergibt. Für meine MC Sachen reicht das.
Ralf schrieb:> Wie macht ihr das? Oder andersrum gefragt, übersehe ich einen> exorbitanten Vorteil von printf?
Auf einem uC ist sowas wie printf fehl am Platze. Der Grund liegt darin,
daß sowas wie printf eben immer einen Textinterpreter nach sich zieht.
Printf verarbeitet einen Formatstring.
Das bringt zwei Nachteile mit sich:
1. Der Textinterpreter kann nicht ahnen, was da alles von ihm in der
konkreten Anwendung verlangt werden wird. Also muß alles nur Denkbare
berücksichtigt und somit ein überflüssiger Wasserkopf eingebaut werden,
der nur Codespeicher verschwendet.
2. Der Formatstring muß interpretiert werden. Das kostet Zeit bei der
Ausführung. Der Programmierer weiß jedoch schon von vornherein, was er
haben will. Es ist also zweckmäßig, Ausgaben schon beim Schreiben der
Quelle so zu gestalten, wie man es haben will und keinen Umweg über zu
interpretierende Zeichenketten zu nehmen.
Kurzum, von printf halte ich auf uC garnix.
W.S.
µC Anarchist schrieb:> W.S. schrieb:>> Der Programmierer weiß jedoch schon von vornherein, was er>> haben will.>> Und wie steht es um freie Benutzereingaben?
Da ist scanf sowieso nicht sehr hilfreich.
Das Problem ist die Fehlerbehandlung. Du kriegst zwar mit, dass scanf
nicht alle Argumente 'besetzen' konnte, aber du hast keine offizielle
Chance die nicht verarbeitete Eingabe weiter zu behandeln, bzw, los zu
werden. Bzw. der Aufwand ist so gross, dass er sich einfach nicht lohnt.
Benutzereingaben werden mit Standard-C so gemacht:
erst mal mit einem fgets eine komplette Eingabezeile als String
einlesen.
Die dann weiter zerlegen, zb auch mit sscanf, strtok bzw. den anderen
String-Funktionen.
Aber mal ehrlich: Auf einem µC hast du eher selten den Fall einer freien
Benutzereingabe. Normalerweise hast du ein paar Taster, die der Benutzer
drückt und so den µC steuert. Und in dem Fall hilft dir weder scanf noch
sscanf oder fscanf irgendetwas.
Fazit: Was auf Desktopsystemen gut funktioniert, ist auf µC-Systemen der
kleinen AVR-Klasse deutlich nicht wirklich zu gebrauchen.
Karl Heinz Buchegger schrieb:> Da ist scanf sowieso nicht sehr hilfreich.
Ne, ne, es geht auch um printf. Unbekannte Texte wollen auch formartiert
werden.
µC Anarchist schrieb:> Karl Heinz Buchegger schrieb:>> Da ist scanf sowieso nicht sehr hilfreich.>> Ne, ne, es geht auch um printf. Unbekannte Texte wollen auch formartiert> werden.
Ach, dann hab ich das falsch verstanden. Ich dachte du redest von
Eingabe.
Welche unbekannte Texte?
Wo kommt sowas vor?
Und warum sollen da ein paar vorgefertigte Routinen nicht genügen?
Karl Heinz Buchegger schrieb:> Aber mal ehrlich: Auf einem µC hast du eher selten den Fall einer freien> Benutzereingabe.
Hängt ganz von der Anwendung ab. Du hast jetzt nur Controller mit
"Knöpfchen" im Blick. Es gibt aber durchaus auch welche, die per
UART oder USB-CDC die komplette Nutzer-Interaktion durchführen, und
wenn man die Ressourcen hat, dann spricht da auch nichts gegen
fgets + sscanf. Hat halt den Vorteil, dass man mit einer dem
C-Programmier gewohnten Umgebung sofort zum Zug kommen kann, ohne
sich in derartigen Nebensächlichkeiten zu verlieren, dass man sich
erst seine eigene (und dann potenziell natürlich immer auch Bugs
enthaltende) Konvertierung zimmern muss.
Ich habe auch ein Projekt, bei dem die Steuerung sowohl über RS-232
als auch über die Gerätetastatur gehen soll. Ich bin mir nur noch
nicht ganz schlüssig, wie ich die "Übergabe" zwischen diesen beiden
vornehmen werde. Aber ich habe dem Projekt (ist sowieso ein Unikat)
damals einen ATmega2560 spendiert, sodass ich mir um die Ressourcen
keine Birne machen muss. Ich werde also das benutzen, mit dem ich
am schnellsten bzw. sichersten zum Ziel gelangen kann.
Guten Morgen,
@Karl Heinz Buchegger:
>>> Das hätte man dann in der Funktion:>>> int display_putc(char c, FILE* stream);>> Diese Funktion wird von fputc anhand des FILE-Handles aufgerufen?> Da wird überhaupt nichts von alleine aufgerufen.> Es ist dein Bier, das du dir in der fputc, bzw. eigentlich in der> Funktion die #du# zur Zeichenausgabe bereitstellst, den File-Handle> ansiehst und je nachdem auf die diversen Geräte verteilst.
Ich hab nicht behauptet, dass da von alleine aufgerufen wird. Ich hab
nur gefragt, ob in der fputc die entsprechende Ausgabefunktion für ein
bestimmtes FILE aufgerufen wird.
> Du stellst dir das ein bischen mit zuviel Komfort vor.> Dein File.Handle ist deine 'Kennung' für das Ausgabegerät. Du stopfst> die oben in die High-Level Funktionen hinein und der wird bei allen> Funktionsaufrufen intern im I/O System überall mitgeschleift, so dass er> unten, bei den Low-Level Funktionen (die du schreibst!) wieder> herauskommt und du in den Low-Level Funktionen dann dementsprechend auf> die Geräte verteilen kannst.
So hatte ich das auch aufgefasst. Und so (ähnlich) schreibe ich auch
meine Funktionen, wenn sie für mehrere (verschiedene) Schnittstellen
geeignet sind.
> Wie schon gesagt: Du hast da eine Vorstellung von 'Komfort', die so> nicht existiert.
Nein, ich glaube, wir haben uns missverstanden, sorry. Meine Vorstellung
von 'Komfort' ist, dass ich nicht unbedingt einen kryptischen String an
printf übergeben möchte, nur damit der Text an einer bestimmten
Displayposition ausgegeben wird -> mit 'kryptisch' meine ich hier, dass
ich beispielsweise vor den eigentlichen String dann noch eine
Escape-Sequenz einleite, gefolgt von Zahlenpaaren, welche die Position
angeben.
Ich halte es eher für komfortabler, die Textposition vorher über eine
eigene Funktion festzulegen. Ob ich dann für die Ausgabe des Textes
printf benutze sei hier mal egal. Bzgl. 'komfortabel' halte ich das auf
diese Weise eben für übersichtlicher/verständlicher, was den Sourcecode
angeht.
Und die Lesbarkeit ist ja auch eine Form von Komfort, oder? ;)
Das fputc von allein gar nicht wissen kann, was mit dem FILE 'LCD' zu
tun ist, ist klar.
@xfr:
> Nochmal kurz zu den FILE-Handles. Alles was man machen muss, um etwas> per printf() auf dem UART auszugeben, ist folgendes:> ...> Die Standard-IO-Funktionen kümmern sich also darum, dass die zu FILE> passende Funktion aufgerufen wird. Man muss nur einmal mit> fdev_setup_stream() festlegen, welche putc- und getc-Funktion zu dem> FILE gehört. In den selber implementierten Funktionen kann man den> Parameter FILE einfach ignorieren. Er muss halt dabei sein, damit die> Signatur der Funktion stimmt.
Danke, das scheint ja wirklich nicht viel zu sein. Ich werde das mal bei
Gelegenheit ausprobieren.
> Das kann man schon machen. Es gibt ja einige nicht-sichtbare Zeichen im> ASCII-Code, die man für so etwas benutzen kann. War früher auch so> üblich: http://en.wikipedia.org/wiki/ANSI_escape_code
Ja, der Kollege hatte das so für die Displayposition gemacht. Ausserdem
musste er das CarriageReturn umbiegen, damit der Cursor an den
Zeilenanfang springt.
> ...> Kurz gesagt: Das richtige Werkzeug für den richtigen Zweck benutzen.
Das richtig abzuschätzen muss ich wohl noch lernen :)
> Die Standard-IO-Funktionen sind imo dafür gemacht, für Menschen lesbaren> Text auszugeben. Sei es live auf dem Display, per UART, in Logdateien> oder auch in textbasierten Netzwerkprotokollen. Für andere Dinge wie> Grafiken anzeigen sind die nicht gedacht und auch nicht geeignet.
Das sehe ich auch so, deswegen meinte ich auch, dass ich nicht wirklich
Kreisposition, -durchmesser, etc. in einen printf-String verpacken
wollte, nur damit printf dann die Ausgabefunktion für einen Kreis
aufruft.
Vom Prinzip her denke ich, dass printf nur Ausgaben auf relativ simple
"Geräte" machen sollte, die sich bzgl.Komplexität/Features nicht sehr
von einer einfachen Konsole unterscheiden.
> Ich würde sie auch nicht zum Senden von reinen Binärdaten benutzen (z.B.> per SPI oder I2C).> ...> Daher machts imo keinen Sinn, solche Dinge in das Standard-IO-Schema zu> quetschen.
Das meinte ich, das macht wirklich keinen Sinn, und würde den Sourcecode
auch unübersichtlich machen.
@W.S.:
> ...> Kurzum, von printf halte ich auf uC garnix.
Okay, das deckt sich ein bisschen mit meiner bisherigen Ansicht. Ganz
ausschließen will ich printf, etc. nicht, nur hab ich's auf µC eben
bisher noch nie gebraucht.
@Jörg Wunsch:
> Ich habe auch ein Projekt, bei dem die Steuerung sowohl über RS-232> als auch über die Gerätetastatur gehen soll. Ich bin mir nur noch> nicht ganz schlüssig, wie ich die "Übergabe" zwischen diesen beiden> vornehmen werde.
Ich hatte mal etwas ähnliches. Es ging um die Parametrierung eines
Geräts. Bei RS232 war das Protokoll definiert, also kein Problem. Bei
der Tastatureingabe habe ich eine feste Eingabemaske auf dem Display
gehabt. Da RS232 bei dem Projekt optional und Tastatur/LCD die
Hauptschnittstelle war, habe ich die RS232-Daten entsprechend
auseinander gepflückt und wie eine Tastatureingabe behandelt. Aufgrund
der festen Eingabemaske wäre auch der umgekehrte Weg (Verpacken der
Tastatureingaben in ein RS232-Kommando) recht einfach gewesen. Weiss
aber nicht ob sich das jetzt auf deinen Fall direkt anwenden lässt.
Ralf
Jörg Wunsch schrieb:> Ich habe auch ein Projekt, bei dem die Steuerung sowohl über RS-232> als auch über die Gerätetastatur gehen soll. Ich bin mir nur noch> nicht ganz schlüssig, wie ich die "Übergabe" zwischen diesen beiden> vornehmen werde.
Bei meiner o.a. 'scanf' Nachbildung habe ich das so gelöst, das Eingaben
über die geräteeigenen Taster immer das oberste Bit gesetzt haben. Da
diese Eingaben ja keine Argumente übergeben, muss man sie im
Kommandointerpreter sowieso anders behandeln als Eingaben über RS232.
Die 'scanf' Routine legt arg1 und arg2 in globals, die dann vom
Interpreter augewertet werden können.
Ralf schrieb:>> Kurzum, von printf halte ich auf uC garnix.> Okay, das deckt sich ein bisschen mit meiner bisherigen Ansicht. Ganz> ausschließen will ich printf, etc. nicht, nur hab ich's auf µC eben> bisher noch nie gebraucht.
Ich habe das bisher auch so gehalten.
Das Einbinden von prinf & Co zieht doch sehr viele Dinge "hinterher", so
dass das Kompilat wesentlich größer wird (und das Flashen länger
dauert).
Auf dem AVR mag das dank der gepflegten Toolchain und der avr-libc das
Verwenden von printf etc. noch relativ einfach sein. Für alle anderen
Plattformen, welche man mit dem GCC und newlib programmieren will, kann
das schwieriger werden, da man sich um die newlib stubs selbst kümmern
muss:
https://sites.google.com/site/stm32discovery/open-source-development-with-the-stm32-discovery/getting-newlib-to-work-with-stm32-and-code-sourcery-lite-eabi
Es wird spätestens dann mühselig, wenn der Debugger von ganz weit
folgendes meldet:
1
#2 0x08004b2e in UsageFault_Handler ()
2
#3 0x080063a2 in _write_r ()
3
#4 0x08019ef2 in __swrite (ptr=0x2000002c, cookie=0x20000380,
Jedoch habe ich vor kurzem eine externe Bibliothek eingebunden. Diese
wiederum hat auf dem Standard aufgesetzt (worauf auch sonst), d. h.
durch diese Hintertür kam die ganze newlib sowieso. Auf dieser
Geräteklasse spielten die Ressourcen keine Rolle, so dass dort nun
beide Varianten enthalten sind, da der Rest der Applikation und die
eigenen Bibliotheken auf die "eigene printf"-Variante aufsetzen (welche
wiederum auch für kleinere Geräte eingesetzt werden sollen).
Wenn der erhöhte Resourcenverbrauch keine Rolle spielt und der Einsatz
von Drittbibliotheken nicht ausgeschlossen werden will, dann sprechen
einige Dinge dafür, gleich auf den Standard aufzusetzen.