Forum: Mikrocontroller und Digitale Elektronik Wie funktionnieren uart_putc und printf


von Bernd (Gast)


Lesenswert?

Hallo,

ich sehe immer wieder, dass mit funktionen wie uart_putc oder printf auf 
den UART Zeichen oder Strings ausgegben werden. Ist irgendwo erläutert 
wie das ganze funktionniert?

Ich habe bisher immer in Assembler programmiert, und da war es nötig 
Input und Output Buffer bereitzustellen, sowie ISRs um die Zeichen 
nacheinander an den Hardware UART zu übergeben, bzw, um eingehende 
Zeichen in den Eingangspuffer zu kopieren.

Stellt der Compiler bei Verwendung der oben angegebenen Funktionen diese 
automatisch zur Verfügung, oder muss man diese selber bereitstellen, 
oder blockiert printf solange den Programmablauf, bis alle Zeichen 
gesendet sind?

Danke,
Bernd

von Johannes M. (johnny-m)


Lesenswert?

Bernd wrote:
> Hallo,
>
> ich sehe immer wieder, dass mit funktionen wie uart_putc oder printf auf
> den UART Zeichen oder Strings ausgegben werden. Ist irgendwo erläutert
> wie das ganze funktionniert?
In der Bibliotheksdokumentation Deines Compilers steht alles 
Wissenswerte...

von Uhu U. (uhu)


Lesenswert?

uart_putc und printf sind Bibliotheksfunktionen, deren Ausgabemedium 
implizit definiert ist.

uart_putc übergibt das Zeichen an das UART-Ausgabesystem, das ebenfalls 
in der Bibliothek vorhanden ist. Ein Aufruf von uart_putc erzeugt eine 
Referenz auf den Einsprungpunkt des UART-Ausgabesystems und der Linker 
versucht dann - wie üblich - die offene Referenz aus den angebotenen 
Bibliothek zu erfüllen und die betreffenden Module in das Programm 
einzubauen

printf benutzt einen internen Puffer, in den die Parameter gemäß Format 
zusammengebaut und - wie in C üblich - mit NUL abgeschlossen wird.

Mit diesem internen puffer wird dann puts (bei dir evtl. sowas wie 
uart_puts) aufgerufen, die für jedes Zeichen im Puffer uart_putc ruft 
und terminiert, wenn NUL gefunden wird.

Es passiert also nichts wesentlich anderes, als wenn du die Funktion 
zufuß in ASM programmierst - nur daß dir in C der Compiler die 
'Drecksarbeit' abnimmt.

von Bernd (Gast)


Lesenswert?

> In der Bibliotheksdokumentation Deines Compilers steht alles
> Wissenswerte...

> Es passiert also nichts wesentlich anderes, als wenn du die Funktion
> zufuß in ASM programmierst - nur daß dir in C der Compiler die
> 'Drecksarbeit' abnimmt.

Nun ja, so richtig wissen wo ich suchen soll, weiß ich ehrlich gesagt 
nicht. Ich habe mir jetzt mal den Ordner mit WinARM angesehen. Klar 
finde ich da viel zu printf, aber nur, wie man es benutzt, nicht, was im 
Hintergrund läuft, und ob die Ausführung synchron (warten bis Ausgabe 
abgeschlossen) oder asynchron (Datenübergabe in Puffer und ISR im 
Hintergrund) ist.

Vielleicht klingen meine Fragen etwas unbeholfen, aber in Assembler weiß 
ich was ich tue, hier nicht so richtig. Kenne C/C++ bisher nur vom PC 
und da sorgt was Betriebssystem für den Rest.
Nur auf dem ARM habe ich kein Betriebssystem.

> Mit diesem internen puffer wird dann puts (bei dir evtl. sowas wie
> uart_puts) aufgerufen, die für jedes Zeichen im Puffer uart_putc ruft
> und terminiert, wenn NUL gefunden wird.

Hier wieder meine Frage. Was mach uart_puts denn nun jetzt genau. In 
Assembler gibt es auch zwei Möglichkeiten Daten auf den UART auszugeben.
1. Ich schreibe ein Byte, WARTE bis der UART wieder frei ist und 
schreibe das nächste Byte. Wenn der String zu Ende ist, springe ich 
zurück ins Hauptprogramm.
2. Ich schreibe alle Bytes in einen zweiten Puffer und kehre sofort ins 
Hauptprogramm zurück. Um die Ausgabe kümmert sich jetzt die ISR im 
Hintergrund.

Und genau zu diesen Abläufen suche ich nähre Informationen (da neu in C 
auf Mikrocontroller).

Wisst Ihr vielleicht ein Dokument für ARM-GCC mit Stellenangabe (kann 
auch PIC-C oder AVR-C sein) wo die Hintergründe ausführlich erläutert 
werden?

Danke,
Bernd

von Johannes M. (johnny-m)


Lesenswert?

Die Bibliotheksfunktionen arbeiten i.d.R. nicht interruptgesteuert, 
sondern sie fragen die entsprechenden Flags per Polling ab. Speziell 
printf und seine Verwandtschaft (also v.a. sprintf) sind 
Standard-C-Funktionen, die jeder ANSI-C-Compiler mitbringt und die auch 
auf allen Plattformen ähnlich implementiert sein dürften. uart_puts 
hingegen ist eine Plattform-spezifische Funktion, die aber vermutlich 
ähnlich implementiert ist wie die Standard-Funktion puts. Für alle 
Funktionen muss der Programmierer selbstverständlich entsprechenden 
Speicherraum für Daten (also z.B. einen Puffer) zur Verfügung stellen, 
sofern die Funktion so etwas benötigt. sprintf benötigt z.B. einen 
Zielstring, in den es die Ausgabe schreiben kann.

> ...aber in Assembler weiß ich was ich tue, hier nicht so richtig.
Das ist der Nachteil der Hochsprachen-Programmierung. Und dabei ist C 
noch durchaus als "Hardware-nah" zu bezeichnen...

von Joe (Gast)


Lesenswert?

Einfach gesagt, printf übergibt Zeichen für Zeichen an die UART Ausgabe. 
Wenn du also "printf ("hallo");" schreibst dann wird Zeichen für Zeichen 
"h", "a" ... an die UART Routine übergeben.

Die eigentliche Senderoutine muß also Zeichen aufnehmen können.
1
void USART_Transmit (uint8_t c)  {
2
  while (!(UCSRA & (1 << UDRE)));     // Transmitter ready/busy ?
3
  UDR = c;                            // Char. Ausgabe -> UART Register
4
}

Das wäre die einfache Erklärung.

printf erwartet beim WINAVR aber ein bischen mehr.
1
static int uart_putchar(char c, FILE *stream)  {
2
  if (c == '\n')
3
  uart_putchar ('\r', stream);
4
  loop_until_bit_is_set(UCSRA, UDRE);
5
  UDR = c;
6
  return 0;
7
}

Bedenke, die UART muß natürlich noch irgendwo initialisiert werden 
(Baudrate, 8N1) z.B.

printf wird nun folgendermaßen verwendet
1
  fdevopen (uart_putchar, NULL);
2
  printf ("test");

alles klar ?

von Simon K. (simon) Benutzerseite


Lesenswert?

Bernd wrote:
> 2. Ich schreibe alle Bytes in einen zweiten Puffer und kehre sofort ins
> Hauptprogramm zurück. Um die Ausgabe kümmert sich jetzt die ISR im
> Hintergrund.

Du kannst die Funktion, die printf benutzt um Daten über das UART 
auszugeben auch so abändern, dass sie in einen Puffer schreibt.
Aber von Haus aus ist das meistens nicht der Fall (wie schon gesagt).

von Uhu U. (uhu)


Lesenswert?

Zu GCC müßten doch die Bibliotheksquellen für die 
Prozessorimplementierung, die du benutzt, verfügbar sein. Sie doch 
einfach mal nach - dann weißt du, wie es funktioniert.

von Bernd (Gast)


Lesenswert?

Das Problem bei den Open Source Tools dürfte sein, dass zwar alle 
Informationen irgendwo stehen, aber ich habe bis heute nicht gelernt, 
mich in diesem Informationsjungle zurechtzufinden. Früher gab es mal zu 
allen Programmen die man gekauft hat ein dickes Handbuch, wo alles schön 
sortiert vorgestellt wurde. Nun ja, früher :-). Dafür ist's heute um 
sonst...

So, nachdem ich nun weiß, dass ich die UART Routinen doch selber 
schreiben muss (sie sollen ja im Interrupt Modus laufen und nicht auf 
jedes Zeichen warten), heißt das für mich wohl, dass ich C als besseren 
Assembler verwenden werde.

Mal eine Frage am Rand, verwendet Ihr für die Hardwareansteuerung ehr 
Compiler-Build-In Routinen oder selbstgeschreibene?

Bernd

von Simon K. (simon) Benutzerseite


Lesenswert?

Bernd wrote:
> So, nachdem ich nun weiß, dass ich die UART Routinen doch selber
> schreiben muss (sie sollen ja im Interrupt Modus laufen und nicht auf
> jedes Zeichen warten), heißt das für mich wohl, dass ich C als besseren
> Assembler verwenden werde.

Sehe ich genauso. C für Mikrocontroller ist definitiv nicht das gleiche, 
wie unter Betriebssystemen. Auf Mikrocontrollern musst du ja fast alles 
selber machen, während es sonst das OS machen würde. Insofern stimme ich 
dir zu, dass C ein "besserer" Assembler ist, der - meiner Meinung nach - 
Code lesbarer macht, manche Sachen überflüssig (Benutzung von 
Labels/Gotos) und sogar noch deinen selbstbackenen Code optimieren kann.

> Mal eine Frage am Rand, verwendet Ihr für die Hardwareansteuerung ehr
> Compiler-Build-In Routinen oder selbstgeschreibene?

Ich benutze den GCC für AVRs und muss sagen, dass ich eigentlich alle 
"Treiber" selber schreibe. Ist ja auch nicht wirklich komplex (in der 
Regel).

von Joe (Gast)


Lesenswert?

> Mal eine Frage am Rand, verwendet Ihr für die Hardwareansteuerung ehr
> Compiler-Build-In Routinen oder selbstgeschreibene?

selber schreiben, meißt vermeidet man einen gewaltigen "overhead".

von Johannes M. (johnny-m)


Lesenswert?

Joe wrote:
>> Mal eine Frage am Rand, verwendet Ihr für die Hardwareansteuerung ehr
>> Compiler-Build-In Routinen oder selbstgeschreibene?
>
> selber schreiben, meißt vermeidet man einen gewaltigen "overhead".
Richtig. Die Bibliotheksroutinen (gerade was (s)printf angeht) sind 
i.d.R. eierlegende Wollmilchsäue, die viele Funktionen mitbringen, die 
man meist für die konkrete Anwendung gar nicht braucht, die aber 
Speicher und andere Ressourcen fressen. Eine Ausgaberoutine für ein 
einzelnes Zeichen über UART (also eine putchar-artige Funktion) ist z.B. 
im einfachsten Fall ein Fünfzeiler, den man eben an die Hardware 
angepasst schreiben kann. Eine selbstverfasste puts-Funktion, die 
wiederum eben erwähnte putchar-Funktion aufruft, ist dann kein Akt mehr. 
Auch das Umwandeln von Zahlenwerten in ASCII-Strings, wofür unbedarfte 
Einsteiger in Unkenntnis immer gerne sprintf nehmen (und sich dann 
wundern, wenn der Programmspeicher schon voll ist, obwohl sie ja 
eigentlich noch gar nix großes programmiert haben) geht mit ein paar 
Zeilen Code.

Bei den ARMs haste zwar schon deutlich mehr Ressourcen als bei 
8-Bittern, weshalb da möglicherweise auch die Verwendung von (s)printf 
nicht so tragisch wäre, aber sinnvoll sind angepasste Routinen 
eigentlich immer.

von Uhu U. (uhu)


Lesenswert?

Je nach dem, wie die Bibliothek modularisiert ist, kannst du die 
Lowlevel-Routinen auch selbst schreiben. Sie müssen nur dieselbe 
Schnittstelle haben, wie die, die in der Bibliothek vorhanden sind.

Der Linker sieht dann, daß die betreffenden Symbole bereits in deinem 
Code definiert sind und läßt die Bibliotheksmodule, die die Symbole auch 
definieren, links liegen.

Allerdings gibt es eine Fußangel: Du mußt alle Symbole, die in dem 
betreffenden Lib-Modul definiert werden, selbst definieren, sonnst kann 
es passieren, daß der Linker den Lib-Modul trotzdem dazupackt und dann 
wegen Doppeldefinitionen den Löffel wirft.

Du kannst so eine Interruptgetriebene puts unterschieben, mußt 
allerdings dann auch selbst für Synchronisation sorgen.

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.