Ich wollte mal mit WINAVR "Hallo Welt" testen. Trotz keiner Fehler oder Warnungen passiert nichts. Dann habe ich putchar() und puts() selber geschrieben und alles läuft super. Ich hab mal auch ins Listing geschaut, also mit den Bibliotheksfunktione explodiert der Code geradezu von 160Byte auf 916Byte. Da sind dann auch völlig nutzlose Funktionen includiert wie malloc(), free() usw. Wo liegt der Fehler ? Anbei der Code, mit "#define SELBSTGEMACHT" gehts ja. Und auf dem 8051 gehts auch mit den Bibliotheksfunktionen. Peter
Erstens sind malloc() und free() sicherlich nicht nutzlos. ;-) Zweitens ist das ungefähr so: ,,Ich habe hier ein Gerät gefunden, da waren ein roter und ein schwarzer Knopf dran. Ich habe auf beide mal draufgedrückt, aber es passierte nichts.'' Das Handbuch, das die Funktion der Knöpfe beschrieben hat, lag selbstredend daneben... Sprich: typischer Fall von RTFM. Oder wie oder was soll die stdio Bibliothek Deiner Meinung nach erraten, daß die Zeichen nun über Deinen angeschlossenen Lochbandstanzer auszugeben sind? Ach, Du hast eine andere Hardware, über die Du das ausgeben willst? :-)) Vielleicht merkst Du aber mit dem Vergleich, warum ein wenig Setup notwendig ist, bevor stdio auch was Sinnvolles tun kann... Es heißt eben standard IO facilities, und nicht UART0@AT90S4333 output facilities, oder LDC@PORTC output facilities oder sowas. Daß printf() & Co Kollosse sind (naturgemäß) steht übrigens ebenfalls in besagtem Manual. printf("Hello world!\n"); ist halt trotz seiner relativen Beliebtheit alles andere als ein Minimalprogramm, und das merkt man in einer Controller-Umgebung sehr viel deutlicher als auf einem Allerwelts-Computer mit ...zig Megabyte RAM. Das Hello World der Controllerwelt ist eher die blinkende (oder an- und abschwellende wie im Beispielcode der avr-libc) LED: wenig Code, wenig externe Hardware (letzteres praktisch gar keine, da man außer beim ATmega128 sogar den Vorwiderstand der LED sparen kann). Eine RS-232 dagegen braucht sehr viel externe Hardware (RS-232 Treiber) sowie ein paar mehr Gedanken bezüglich Takt-Setup, damit die Baudrate auch stimmt, außerdem einiges mehr an Ansteuer-Code als die Blink-LED. Ein LCD braucht zwar auch nicht viel externe Hardware, aber dafür noch ein bißchen mehr Code. Daher sind eben beides eher nicht ganz die klassischen Minimalprojekte.
,,Ich habe hier ein Gerät gefunden, da waren ein roter und ein schwarzer Knopf dran. Ich habe auf beide mal draufgedrückt, aber es passierte nichts.'' Ganz so is es ja nun nicht, sondern: "Ich habe ein Radio (Keil C51), das geht an, wenn ich auf "An" drücke, das neue Radio (WINAVR) aber nicht." Ich wollte ja auch nicht, daß mir jemand das komplette Manual erklärt. Aber da ja "Hallo Welt" das Standardprogramm ist, dachte ich, daß es eben nur ne Kleinigkeit wäre, worans hakt, die ein GCC-Profi in einer Zeile erklären kann. Wenn Du mal reingeschaut hättest, dann hättest Du gemerkt, daß ich printf() garnicht verwendet habe, sondern puts() und das sollte doch kaum Code fressen (tuts beim Keil C51 ja auch nicht). Mit malloc() ist nutzlos meinte ich das im Zusammenhang mit puts() und putchar(). Das beim GCC ne Menge Sachen komplizierter gemacht sind als beim Keil ist mir auch schon aufgefallen. Da ist eben die UART die Standardausgabe und falls es anders gewünscht ist, muß man nur sein eigenes putchar() einbinden. Peter
Wenn Du das hier meinst: "5.12 Standard IO facilities" Also jetzt bin ich noch ratloser. Deshalb nur noch eine kurze Frage (Ja oder Nein reicht): Wenn ich mein eigenes putchar() und getchar() definiere, kann ich dann printf(), puts() und scanf() verwenden ? Zumindest sprintf() und sscanf() müßten doch immer gehen ? Peter
Jein (sofern Du Dich an das API hälst -- beachte bitte die Rückkehrwerte). Ja insofern, daß Deine eigenen Funktionen dafür ungefähr die richtigen sind. Nein in der Beziehung, daß putchar() und getchar() vordefiniert sind und der Standard vorschreibt, daß Du keine eigenen Funktionen mit diesem Namen haben darfst. (Der Compiler ersetzt intern per Standard gleichwertige Funktionen teilweise durch Alternativen, und der Standard erlaubt sowas ausdrücklich.) Benenne sie my_putchar() und my_getchar(), dann ist es OK. Ja. Letztere brauchen übrigens auch kein malloc(), mit Ausnahme der floating point Varianten. Für die Eingaben mußt Du Dir überlegen, ob Du die Zeile editieren können willst oder nicht. Je nachdem, ist my_getchar() u. U. gar nicht mehr so einfach... Ich hänge mal einen Prototypen dran von etwas, was mal ein etwas besseres stdio-Beispiel werden sollte, den Weg in die offizielle Beispielsammlung aber noch nicht gefunden hat. Wie gesagt, ist unvollendet, aber die physischen EA-Routinen sind soweit Ok und getestet. Der Vergleich mit dem C51 hinkt schon deshalb, weil der AVR u. U. ja zwei USARTs haben kann. Welche soll der Default sein? UART0? Die kollidiert aber beim ATmega128 mit dem ISP-Interface, so daß viele wohl eher UART1 bevorzugen würden... Außerdem wollte ich das stdio wirklich generisch schreiben und nicht auf eine UART festbrennen -- was hätte sonst der nächste Anwender mit seiner LCD gemacht? Und der übernächste mit einem Parallel-Interface à la Centronics? (*) Da aber generisch, ist halt auch ein malloc() vonnöten, sorry. (Ich hatte schon an eine Abrüstvariante ohne malloc() gedacht, die dann nur stdin/stdout/stderr und nicht mehr kann, könnte vielen eventuell ja genügen.) (*) Natürlich auch der Fall, daß stdout ein LCD ist und stderr als Debug-Schnittstelle eine UART... Wie macht man sowas beim Keil C51?
@Joerg, vielen Dank für die Antwort. Sieht ja doch wesentlich komplizierter aus, als ich dachte. Da wird wohl sprintf() + my_putchar() die praktikablere Lösung für mich sein. Anbei die entsprechend modifizierte "hallo.c". "Wie macht man sowas beim Keil C51?" Beim Keil machen die das mit einer einzigen Variablen, d.h. vor printf() muß man diese Variable auf den richtigen Port setzen: http://www.keil.com/support/docs/1324.htm Peter
Na Moment, kompliziert ist doch eigentlich nur die Variante für die Eingabe, da sie auch Zeilenpufferung und primitive Editier- funktionen bietet. Die Variante für die Ausgabe besteht aus: int uart_putchar(char c) { if (c == '\n') uart_putchar('\r'); loop_until_bit_is_set(UCSRA, UDRE); outb(UDR, c); return 0; } Was ist daran kompliziert? (Die Umwandlung von \n in \r\n kannst Du auch weglassen, wenn Du stattdessen in allen Strings, in denen Du das brauchst, \r\n explizit schreibst. Ich habe die Unix-Option bevorzugt, bei der diese Umwandlung erst vom Gerätetreiber vorge- nommen wird.) Wenn Du bei der Eingabe mit Einzelzeicheneingabe leben kannst, sieht deren Funktion nicht viel schlimmer aus. Alles was Du sonst noch brauchst ist halt der Aufruf für fdevopen() (das ich als Variante von fopen() für physische Geräte sehe, aber mit einer anderen Syntax/Semantik, da ich nicht erst noch anfangen wollte, ,,Datei''namen zu verwalten für die Geräte.) Die Variante vom Keil ist erstens Q&D, zweitens was machst Du, wenn Du Dein printf() auf ein LCD oder eine 5x7-LED-Matrix lenken willst? Sorry, sowas wollte ich nicht schreiben. Daß malloc() manch einem ein bißchen zu viel ist, vor allem auf kleinen Controllern, sehe ich ja ein, und wie schon geschrieben, werde ich nach einem Workaround suchen, aber Q&D Code mag ich nicht zum Bestandteil einer offiziellen Library machen, dafür müßte ich mich hinterher schämen. (Q&D? -- quick and dirty)
Ich hab mir das Listing mal etwas näher angesehen und da ne Menge ICALLs gefunden. Ich vermute mal, daß die ganzen I/O-Funktionen erst zur Laufzeit aufgelöst werden und deshalb auch das malloc() benötigt wird. Ich befürchte bloß, daß mich das einen riesen Flash, CPU-Zeit und SRAM-Aufwand kosten wird. Und da ich nicht immer Mega-Boliden, sondern auch Tiny26 / 2313 verwende, dürfte denen dann schnell mal die Puste ausgehen. Deshalb will ich, daß sämtliche Referenzen, die schon zur Compilezeit bekannt sind, dann gefälligst auch fix und fertig aufgelöst werden. Es ist ja nicht wie bei PCs, wo man verschiedene Programme laden und ausführen können muß. D.h. sämtliche Routinen, die laufen müssen und deren RAM-Bedarf sind ja bereits zur Compilezeit bekannt und entweder sie passen rein oder nicht. Es ist mir auch tausendmal lieber, der Linker meckert bereits, als daß dann später beim Kunden ein malloc() gegen den Baum fährt und die ganze Anwendung abschmiert. Nichts gegen Deine Arbeit, aber für die meisten meiner Anwendungen ist das einfach mehrere Nummern zu groß gedacht. Ich hab auch gesehen, das sprintf() fputc() benutzt, was wiederum ICALLs ausführt. Das verstehe ich nicht, sprintf() schreibt doch immer in den SRAM, warum sind dann für jede Byteausgabe umständliche ICALLs notwendig ? Peter
> Ich vermute mal, daß die ganzen I/O-Funktionen erst zur Laufzeit > aufgelöst werden ... Ja. > und deshalb auch das malloc() benötigt wird. Nein. Vielleicht solltest Du ja statt Kaffeesatzlesen aus dem Listing lieber das Lesen des Sourcecodes vorziehen? ;-) Das malloc wird benötigt, damit man keine Beschränkung in der Anzahl offener Filedescriptoren (mit Ausnahme des vorhandenen RAM natürlich) in der Bibliothek implizieren muß. Daher ja auch mein Workaround für die ,,Sparvariante'': Beschränkung auf die stdin, stdout, stderr. Ich gebe natürlich gern zu, daß ich printf() ohnehin als so schwergewichtig ansehe, daß ich davon ausgegangen war, daß in vielen derartigen Applikationen auch bereits in malloc() in Benutzung ist, so daß das egal wäre. Ich habe selbst schließlich schon weit mehr Bedarf für malloc() in meinen Applikationen gehabt als Platz für printf(). ;) Die paar indirect calls machen das Kraut nicht fett, das ist ja kein 1 MHz Z80 und keine alte PDP-11 -- wenngleich die Architektur der jetzigen stdio-Implementierung sehr stark an die der ersten Unixe auf der PDP-11 angelehnt ist. >Ich hab auch gesehen, das sprintf() fputc() benutzt, was wiederum >ICALLs ausführt. Wiederum: UTSL. Dann würdest Du nämlich auch sehen, daß im Falle von sprintf() kein indirect call je erreicht wird. Ich möchte aber nicht den gesamten schwergewichtigen Code für die printf- Formatauswertung einmal für sprintf, einmal für fprintf, einmal für printf usw. haben, weil dann eine Applikation, die sowohl printf als auch sprintf benutzt, den ganzen Salat gleich doppelt hätte. Von daher habe ich mich in der Architektur an gängige Implementierungen gehalten (die im Falle der PDP-11 eben sogar resourcenmäßig durchaus vergleichbar mit einem AVR sind, d. h. ich habe mich vorsätzlich eher an 20 Jahre alten Implementierungen denn an multimegabyte-verwöhnten aktuellen Unixen orientiert), und da liegt die zentrale Formatierroutine dann eben in vfprintf() mit einer Laufzeitbindung für die eigentliche Entscheidung, was dann wohin geschrieben wird. Daß man eventuell aus Performancegründen noch eine Pufferung in stdio haben möchte, habe ich einigermaßen im Hinterkopf behalten, aber in der ersten Version aus Aufwandsgründen noch nicht konkret angedacht. Die Performanceverbesserung wird dabei in jedem Falle mit Codegröße zu bezahlen sein (schon deshalb wollte ich lieber erstmal ungepuffert arbeiten, da das für die gängigen UART, LCD & Co. praktisch ausreicht), aber das um die Ecke lugende Ethernet (in Form preiswerter TCP/IP-Hardwarelösungen) winkt hier natürlich schon mit dem Zaunspfahl... Dort ist es eben nicht mehr egal, ob jedes Byte einzeln in ein TCP-Paket verpackt würde oder stattdessen so viel wie möglich gesammelt werden. Wenn Du aber an derartig kleine Controller denkst, ist printf wohl ohnehin nur maßgeschneidert zu vertragen, d. h. es werden nur die Konvertierungen und Hilfskonstrukte eingebunden, die auch tatsächlich notwendig sind für die Applikation. Das ist praktisch mit einer Bibliothek nicht zu realisieren, wenngleich es Dir natürlich komplett freisteht, den Sourcecode des existierenden vfprintf() zu nehmen und so weit einzukürzen, bis er für Dich paßt. Ich würde auf solchen Controllern aber vermutlich eher zu den vorhandenen Sonder-Konvertierungs-Funktionen greifen wie itoa() & Co.
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.