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