Die übliche Methode ist printf(). Leider wird dabei eine Menge Programmspeicher und Rechenzeit benötigt. Auch kann printf() Kommazahlen nicht in der technischen Schreibweise ausgeben. Deshalb habe ich mir meine eigenen Routinen geschrieben. Für die Ausgabe von Ganzzahlen ist auf CPUs ohne Divisionsbefehl die Subtraktionsmethode am schnellsten und Code sparendsten. Die Subtraktionswerte für die einzelnen Digits werden in einer Tabelle definiert. Damit ist es ein leichtes, die Routine auf long zu erweitern. Die Kommazahlen werden in der technischen Schreibweise (Zehnerpotenzen durch 3 teilbar) dargestellt. In meiner Anwendung werden 10^-3 (mA) und 10^3 (kV) benötigt. Die Ausgabe erfolgt über putchar(), d.h. standardmäßig über die UART. Mit einem eigenen putchar() kann man z.B. auch auf ein LCD ausgeben. Man kann aber genauso gut in den RAM schreiben. Die Routinen wurden auf einem 8051 implementiert. Für die Verwendung auf dem AVR sind 2 Dinge zu beachten: Beim Keil C51 gibt es die genialen Generic Pointer. D.h. man kann definieren in welchen Speicherbereich Variablen abgelegt werden und der Compiler kümmert sich automatisch darum, welche Zugriffsbefehle richtig sind. Beim AVR-GCC muß man dagegen für den Flash spezielle Zugriffsmacros bzw. extra Funktionen verwenden. Beim Keil kann man alle Bibliotheksfunktionen durch eigene ersetzen. D.h. der Linker löst die Referenzen zuerst anhand des eigenen Codes auf und erst danach über die Bibliotheken. Beim AVR-GCC muß man dagegen für putchar() einen anderen Namen verwenden, wenn die Ausgabe über eigene Funktionen erfolgen soll. Peter
Gast
#79590
Hallo, ich habe mal die guten Funktionen auf AVRGCC potiert. Ich hoffe jemanden hilft es weiter. Es sollten keine Fehler versteckt sein. Wuerde mich aber trotzdem freuen wenn eine erfahrene Person drueberschaun wuerde. Gruß, Dirk
Hi Leutz, bin mal so frei und poste den Link mit meinem 180Byte / 4Byte printf hier ;-) Beitrag "Re: smartes printf" Peter war so nett und hat mich drauf hingewiesen. Ein paar stellen sind böse (zeigervergawaltigung), dafür ists klein und schnell ;-) Greetz, th.
Thorsten Db wrote: > Ein paar stellen sind böse (zeigervergawaltigung), dafür ists klein und > schnell ;-) Meine ist aber noch kleiner und schneller, wenn Du die float Ausgabe wegläßt. Du mußt ja noch die Divisionsfunktion bei Dir mit dazu zählen. Und wie schon oben gesagt, Subtraktion ist schneller als Division. Und Zeigeroperanden sind auf dem AVR grundsätzlich langsamer und aufwendiger als Registeroperanden, da der AVR nicht im SRAM rechnen kann. Er muß also immer erst den Wert aus dem SRAM in Register holen, rechnen und wieder zurück speichern. Peter
Gast
#445199
@Peter Dannegger: Deine main3.c scheint ja wirklich interessant zu sein, aber teilweise ist mir der Code zu hoch um ihn zu verstehen. Könntest du ihn z.B. ausführlicher kommentieren? Beispielsweise verstehe ich schon einmal nicht, was bei deinen Konstanten PROGMEM bedeutet. Die Shift und Verknüpfungsoperationen sind ja auch nicht ohne...
Gast
#445223
Ist irgendjemanden schonmal aufgefallen das der Herr Dannegger an fremden codes immer was zu kritisieren hat wenn er etwas ähnliches programmiert hat? Mir schon.
Ganz ehrlich: Der Code ist sogar ziemlich einfach zu verstehen. PROGMEM bedeutet einfach nur, dass besagte Variable (eigentlich Konstante) im Flash anstatt im SRAM abgelegt wird. Demenstrechend muss dann mittels spezieller Funktionen auf die Werte zugegriffen werden. Wie die Hex Ausgabe funktioniert sollte unmittelbar einsichtig sein. Die Int-Ausgabe funktioniert nach dem Prinzip: Ziehe solange 10000 ab bis die Zahl kleiner als 10000 geworden ist und zähle mit wieviele Subtraktionen das waren. Diese Anzahl ist dann unmittelbar die 10000er Ziffer. Dann das ganze mit 1000, mit 100 und mit 10 Die Float-Ausgabe ist ebenfalls nicht schwer zu verstehen. Nach einem Vorgeplänkel, indem festgestellt wird ob ein Exponent extra ausgegeben werden soll oder nicht (Variable ep) wird dir Zahl mal normiert, sodass der Vorkommaanteil kleiner 10 aber größer 0 ist und sich alle anderen Stellen rechts vom Komma befinden. Mittels einem (impliziten) cast nach int, wird der Vorkommaanteil abgetrennt und ausgegeben. Danach wird der Nachkommaanteil vom Vorkommaanteil abgetrennt und mit einer Multiplikation mit 10 wird die nächste Stelle vor das Komma geholt. Und so gehts in der Schleife weiter und weiterk, bis genug Stellen ausgegeben sind. Die Dinger sind wirklich nicht schwer zu verstehen. Bau sie in ein Testprogramm ein und steppe sie im Debugger durch. Danach sollte im Detail klar sein, was wann wo passiert.
Gast
#445268
Ach das macht PROGMEM, aber was ist dann mit defines, werden die nicht auch in den Flashspeicher geschrieben?
Gast
#445278
Was macht DIGITS genau? Was müsste ich z.B. tun, wenn ich outfloat(123.3253) aufrufe, aber nur eine Nachkommastelle ausgeben möchte?
Gast
#445288
Wo ist DIGITS definiert? Der Compiler findet es gar nicht.
Aufmerksamer wrote: > Ist irgendjemanden schonmal aufgefallen das der Herr Dannegger an > fremden codes immer was zu kritisieren hat wenn er etwas ähnliches > programmiert hat? Mir schon. Es ist vollkommen natürlich, wenn sich jemand auch schon mit dem gleichen Thema beschäftigt hat, daß dieser einschätzen kann, welche Lösung welche Vorteile hat. Und da wäre es doch grob unhöflich mit den Vorteilen hinter dem Berg zu halten. Nur durch Kommunikation kann sich auch was verbessern. Ohne Kommunikation säßen wir immer noch in Höhlen und würden Beeren sammeln. Es ist also überhaupt nichts daran, was einem extra auffallen muß. Zu kritisieren wäre es höchstens, wenn einer Kommentare abgibt, ohne was von der Sache zu verstehen. Peter
bst wrote: > Ach das macht PROGMEM, aber was ist dann mit defines, werden die nicht > auch in den Flashspeicher geschrieben? defines sind Präprozessoranweisungen. Mit Flash hat das überhaupt nichts zu tun. Peter
Gast
#445429
@DIGITS: Hat sich erledigt schäm @PROGMEM: Rentiert sich das aber nur bei Controllern die wenig SRAM haben, oder? Denn Zugriffe aufs Flash sind ja langsamer? Oder gibt es da noch was, was ich nicht sehe?
Gast
#445435
Nein, ganz lässt mich DIGITS doch nicht in Ruhe: Was ist, wenn ich der Funktion outfloat eine Zahl übergebe, die sehr viele Nachkommastellen hat? Was passiert dann beim "Normieren"? Fallen die hintersten Nachkommastellen raus?
Gast
#445576
Wie viele Nachkommastellen hat ein double eigentlich beim avr-gcc? Ist das irgendwo definiert?
> Wie viele Nachkommastellen hat ein double eigentlich beim avr-gcc? Ist > das irgendwo definiert? Nein. Zuallererst: Auf einem AVR sind double und float gleich gross. Beide belegen 4 Bytes. Mit 4 Byte Floating Point Arithmetik kannst du davon ausgehen dass du in etwa 4 bis 5 bis 6 signifikante Stellen hast. Achtung: signifikante Stellen sind nicht gleich Nachkommastellen, sonder das ist quasi 'alles in allem'. Bei einer Zahl 2345.6789 sind also vor dem Komma bereits 4 Stellen aufgebraucht worden, sodass nur noch in etwa 1 bis 2 Nachkommastellen (wenn überhaupt) einen sinnvollen Wert aufweisen. Alles was danach kommt ist sowieso 'gelogen'.
Gast
#445862
OK, aber wie soll man DIGITS im Code definieren?
Gast
#446286
Hallo zusammen, ich lerne ja auch gerade ein wenig und habe mir die Lösung für den AVR aus main.c angeschaut. Ich frage mich nun, ob auf uCs bei der Division von Integern auch das ganzzahlige Ergebnis zurückkommt - sprich, ob man es auch wie folgt lösen könnte: uint16 feld[4] = {10, 100, 1000, 10000}; for (i=3, d='0'; i>=0; i--, d='0') { uint16 j = uval / feld[i]; if (j>0) { d += (uint8)j; uval -= j*feld[i]; uart_putc(d); } } uart_putc((uint8)uval + '0'); Damit ich weiterlernen kann, erwarte ich natürlich Kritik/Analyse... Vielen Dank schonmal...
@Aubacke Probier doch einfach mal selber aus im Simulator, was schneller geht. Die Division im Binärsystem erfolgt bitweise, d.h. die Routine für eine 16Bit-Division ist eine Schleife mit 16 Durchläufen. Daher sollte eigentlich eine Subtraktionsschleife mit max 9 Durchläufen schneller sein. Peter
bst wrote:
> OK, aber wie soll man DIGITS im Code definieren?
Die Gesamtzahl der gewünschten Stellen.
Peter
Peter Dannegger wrote: > bst wrote: >> OK, aber wie soll man DIGITS im Code definieren? > > Die Gesamtzahl der gewünschten Stellen. > > > Peter Wenn ich das richtig gesehen habe, dann sollte DIGIT nicht kleiner als 3 gewählt werden. Ansonsten kann es sein, dass die Schleife abgebrochen wird noch ehe der Dezimalpunkt ausgegeben wird. 999 würde bei DIGIT = 2 als 99 ausgegeben. Ich würde auch noch folgende kleine Modifikation vornehmen: while( val < 1 ){ val *= 1000; ep -= 3; } while( val >= 1000 ){ val *= 0.001; ep += 3; } OK. Auf einem µC hat man meist keine wirklich großen Zahlen. Wenn aber doch, dann ist man so auf der sicheren Seite. Und der while anstelle des if kostet ja auch nicht die Welt.
Hi Leutz, um mal wieder etwas Bewegung in diesen Thread zu bringen ... ich suche immer noch nach einer besseren Lösung für dieses Codefragment:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
Mir fällt allerdings so recht keine Variante ein, die die Stellen eines int von "links nach rechts" abrechnen könnte, ohne diesen begrenzenden 10^irgendwas einzufügen und abzudividieren, oder die max. Stellenzahl über eine vorgesetzte Schleife zu ermitteln. btw.: Gibtz eigendlich in C ne Möglichkeit, um diese beiden Ausdrücke zusammenzuführen?
1 | |
2 | |
a'la #define MAX_NUM_DEC 10E0MAX_NUM_LEN o.ä.? Greetz, /th.
Thorsten De buhr wrote: > Mir fällt allerdings so recht keine Variante ein, die die Stellen eines > int von "links nach rechts" abrechnen könnte, ohne diesen begrenzenden > 10^irgendwas einzufügen und abzudividieren, oder die max. Stellenzahl > über eine vorgesetzte Schleife zu ermitteln. Du könntest so wie hier http://www.mikrocontroller.net/articles/FAQ#Eigene_Umwandlungsfunktionen die Generierung von rechts nach links machen lassen und den entstehenden String danach spiegeln. Spart ein paar Divisionen ein.
Thorsten De buhr wrote: > btw.: Gibtz eigendlich in C ne Möglichkeit, um diese beiden Ausdrücke > zusammenzuführen? > >
1 | |
2 | |
3 | |
4 | |
5 | |
> > a'la > #define MAX_NUM_DEC 10E0MAX_NUM_LEN o.ä.? > Ich hab eine Lösung. Schön ist sie aber nicht
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
danke für die idee, probier ich gleich mal aus :-) Es geht auch nicht um schön, sondern nur um klein g (Wenns schön sein soll, sähe es anders aus und wäre nicht voll mit so fiesen verpointerungen) Sieht irgendwie aus, als wenn du den parser veräppelst :) ## war irgendwas mit string zusammenbauen über den parser, richtig? wenn ich diesen ausdruck FORM(10E, MAX_NUM_LEN) in der for-schleife einsetze, müsste doch nach dem parsen da 1000000 rauskommen, oder wird da doch (weil ich was übersehen habe) was komplizierteres raus? Sinn der sache ist, dass ich da nicht MAX_NUM_DEC reinschreiben kann, sondern eine Stellenzahl als Ziffer angeben kann. --- zu string spiegeln: ein paar divisionen sind nicht weiter wild. Ich möchte keine 12Bytes ausgeben, um eine 4G-Zahl incl. '\0' als string zu speichern. In dem Fall wärs ja einfach, in jedem durchlauf %10 und /10, das spart auch die sache mit dem MAX_NUM_DEC und den "verschwendeten" Divionen auf die führenden Nullen. Man könnte das auch rekursiv lösen, was aber mit jeder rekursion speicher im heap belegt. Sinn der sache ist, (auch aus spass an der sache) möglichst kleinen C-code zu schreiben, der ein minimales, aber ausreichendes printf darstellt. Ist ausserdem auch interessant, was man alles machen kann, und zu schauen, was der Compiler draus macht. So hab ich bei meinen Spielereien heraisgefunden, dass (Keil µVision, ARM RealView Compiler, -O3)
1 | |
den Code 2 Bytes länger macht als:
1 | |
2 | |
3 | |
4 | |
(auch wenn man davon absieht, dass der Einzeiler den String nicht korrekt ausgibt :-) (Spielerei, Compilertest) Anm.: Eigentlich darf man dem fputc keine 0 als zweiten Parameter übergeben, M$ VC z.B. schimpft dann (Nullpointer). Da ich die fputc aber selbst geschrieben habe und den FP ignoriere (retarget auf uart) ist das egal, muss halt nur stdio-konform sein mit der parameterliste. Sauberer ist's über die retarget.c von Keil µVision. Greetz, /th.
bin ich eigendlich zu do*f nen anhang anzuhängen? ...irgendwie krieg ichs nciht hin, ne simple .c mit anzufügen... geht net! korr: Nu ist das ding dran ... versteh das einer ... oder gehts nur beim bearbeiten nciht ?!?
mit den ## und TOKEN ... Keil µVision und ARM RV Compiler schimpft: src\thprintf.c(65): error: #166: invalid floating constant bei: const double MaxNum = FORM(10E, MAX_NUM_LEN); :( --- ähnliche Geschichte hier:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
Gibt es eine Möglichkeit, ein konstantes Zeichen '\' und eine Variable (z.B. t für tab oder n für newline) zu einem (hier sonder-)zeichen zusammenzuführen? Oder ist das vom code her aufwänsdiger als ein stumpfes switch? Greetz, /th.
Thorsten De buhr wrote: > mit den ## und TOKEN ... > > Keil µVision und ARM RV Compiler schimpft: > > src\thprintf.c(65): error: #166: invalid floating constant > > bei: > const double MaxNum = FORM(10E, MAX_NUM_LEN); > > :( Hmm. Ich habs mit MS VC++ probiert und solange rumprobiert, bis ich s hin gekriegt habe.
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.