Forum: Mikrocontroller und Digitale Elektronik [C & µC] Verwendet ihr printf/scanf?


von Ralf (Gast)


Lesenswert?

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

von holger (Gast)


Lesenswert?

>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.

von vöslauernockerl (Gast)


Lesenswert?

Ich verwende printf ausschließlich zum debuggen.. bei einen realease 
kommt mir das nicht ins pgm!

von vöslauernockerl (Gast)


Lesenswert?

Wie könnte man scanf verwenden?

von xfr (Gast)


Lesenswert?

Ich habe mir selbst nur die beiden uart-Funktionen geschrieben:
1
int uart_putc(char c, FILE* stream);
2
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, ... 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.

von Ralf (Gast)


Lesenswert?

@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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von xfr (Gast)


Lesenswert?

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
static FILE uart_stdio;
2
3
int main(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
static FILE usb_stdio;
2
static USB_ClassInfo_CDC_Device_t cdc_device = [...]
3
4
int main(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.

von vöslauernockerl (Gast)


Lesenswert?

Wie würde so ein fgets() oder sscanf() aussehen?

von Ralf (Gast)


Lesenswert?

@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

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von xfr (Gast)


Lesenswert?

> 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:
1
static FILE uart_stream;
2
static FILE usb_cdc_stream;
3
static USB_ClassInfo_CDC_Device_t cdc_device = [...]
4
5
int main(void)
6
{
7
  uart_init();
8
  uart_create_stream(&uart_stream);
9
  
10
  USB_Init();
11
  CDC_Device_CreateStream(&cdc_device, &usb_cdc_stream);
12
  
13
  int c;
14
  while (1) {
15
    c = fgetc(&uart_stream);     // Zeichen von UART lesen
16
    if (c != EOF) {              // Gültiges Zeichen von UART empfangen
17
      fputc(c, &usb_cdc_stream); // Zeichen an USB senden
18
    }
19
  
20
    c = fgetc(&usb_cdc_stream);  // Zeichen von USB lesen
21
    if (c != EOF) {              // Gültiges Zeichen von USB empfangen
22
      fputc(c, &uart_stream);    // Zeichen an UART senden
23
    }
24
  }
25
}

> 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
int display_putc(char c, 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.

von Abstrakt (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Ralf (Gast)


Lesenswert?

@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

von Karl H. (kbuchegg)


Lesenswert?

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.

von skink (Gast)


Lesenswert?

Ich benutze gerne printf von Georges Menie, da diese Implementierung 
sehr schlank ist.

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von xfr (Gast)


Lesenswert?

Nochmal kurz zu den FILE-Handles. Alles was man machen muss, um etwas 
per printf() auf dem UART auszugeben, ist folgendes:
1
// uart.c (für den ATxmega)
2
3
void uart_create_stream(FILE* stream)
4
{
5
  fdev_setup_stream(stream, uart_putc, uart_getc, _FDEV_SETUP_RW);
6
}
7
8
static int uart_putc(char c, FILE* stream)
9
{
10
  while (bit_is_clear(USARTD1_STATUS, USART_DREIF_bp)) {
11
    // Busy waiting
12
  }
13
  USARTD1_DATA = c;
14
  return c;
15
}
16
17
static int uart_getc(FILE* stream)
18
{
19
  if (bit_is_set(USARTD1_STATUS, USART_RXCIF_bp)) {
20
    return USARTD1_DATA;
21
  } else {
22
    return EOF;
23
  }
24
}
1
// main.c
2
3
static FILE uart_stdio;
4
5
int main(void)
6
{
7
  uart_init();
8
  uart_create_stream(&uart_stdio);
9
  stdout = &uart_stdio;
10
  
11
  printf("Hallo Welt aus dem RAM an stdout!\r\n");
12
  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.

von xfr (Gast)


Lesenswert?

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.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von µC Anarchist (Gast)


Lesenswert?

W.S. schrieb:
> Der Programmierer weiß jedoch schon von vornherein, was er
> haben will.

Und wie steht es um freie Benutzereingaben?

von Karl H. (kbuchegg)


Lesenswert?

µ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.

von µC Anarchist (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Da ist scanf sowieso nicht sehr hilfreich.

Ne, ne, es geht auch um printf. Unbekannte Texte wollen auch formartiert 
werden.

von Karl H. (kbuchegg)


Lesenswert?

µ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?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von ецтаьэр (Gast)


Lesenswert?

Man kann den Resourcenverschleiss noch toppen, indem man zu 
Dokumentationszwecken alles in ein pdf einbindet und als pdf rauslaesst.

:-)

von Ralf (Gast)


Lesenswert?

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

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Roland H. (batchman)


Lesenswert?

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, 
4
    buf=0x20000df8 "[Hard fault handler]\n ... "..., n=21)
5
    at ../../../../../../newlib-1.19.0/newlib/libc/stdio/stdio.c:97
6
#5  0x08017dc2 in _fflush_r (fp=0x20000380, ptr=0x2000002c)
7
    at ../../../../../../newlib-1.19.0/newlib/libc/stdio/fflush.c:214
8
#6  _fflush_r (ptr=0x2000002c, fp=0x20000380)
9
    at /home/gcc-arm-4.6.2/data/gcc-builds/builds/arm-elf/4.6.2-1/newlib-1.19.0/newlib/libc/include/stdio.h:372
10
#7  0x08018bd4 in __sfvwrite_r (ptr=0x80194e1, fp=0x20000380, uio=0x2001ffa0)
11
    at ../../../../../../newlib-1.19.0/newlib/libc/stdio/fvwrite.c:257
12
#8  0x080194e0 in _puts_r (ptr=0x2000002c, s=0x802bc58 "[Hard fault handler]")
13
    at ../../../../../../newlib-1.19.0/newlib/libc/stdio/puts.c:94
14
#9  0x08019508 in puts (s=<optimized out>)
15
    at ../../../../../../newlib-1.19.0/newlib/libc/stdio/puts.c:103
16
#10 0x08004a06 in hard_fault_handler_c ()

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.

von ецтаьэр (Gast)


Lesenswert?

Sinnvollerweise spart man sich solche Klimmzuege, schreibt alles jeweils 
neu in einen Screenbuffer und beschreibt das display per Timer zyklisch 
neu.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.