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
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...
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.
> 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
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...
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 ?
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).
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.
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
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).
> 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".
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.