Betrachtet wird hier die Anwendung zur Konvertierung von Zahlensystemen und zur menschenlesbaren Zahlendarstellung.
Die Zahlenkonvertierung in Zeichen ist eigentlich nicht schwer:
Dummerweise entstehen dabei die Ziffern von rechts nach links. Das mag für rechtsbündige Ausgabe in einen Speicherbereich ganz zweckmäßig sein.
Zum Vertauschen der Reihenfolge wird gern der Stack verwendet,
so wie auch im ATmega-Beispiel (SendNumber) angegeben.
avr-libc verwendet dazu die Funktion strrev()
.
Die Ausgabe negativer Zweierkomplementzahlen erfordert die Negation der Zahl. Dummerweise existiert bei keinem bekannten Mikrocontroller ein NEG-mit-Carry-Befehl, deshalb lässt sich NEG nicht kaskadieren, und man muss „zu Fuß“ von Null subtrahieren. Deshalb das Makro negc — vielleicht geht's auch eleganter:
.macro negc ;Register ldi r16,0 sbc r16,@0 mov @0,r16 .endm
Führende Leerzeichen sorgen für rechtsbündige Ausgabe, und löschen ggf. Zeichenmüll einer zuvor ausgegebenen Zahl.
Führende Nullen werden für Uhrzeit-Ausgaben sowie für Hexadezimalzahlen gern verwendet.
C-Programmierer verwenden oftmals Gleitkommazahlen nur für die Ausgabe
mit printf()
. Make Program Look Big.
Clevere C-Programmierer geben Festkommazahlen schon so aus:
printf("%d,%u", Zahl/10, Zahl%10); printf("%d,%02u", Zahl/100, Zahl%100); //usw.Das Punkt-Komma-Problem wird gleich mit erschlagen.
Diese Vorgehensweise erspart das Einbinden einer Gleitkomma-Bibliothek,
wobei die printf
-Unterstützung einen großen Teil ausmacht.
Division mit Rest - das macht die Ausgaberoutine sowieso. Deshalb ist es besser, die Komma-Ausgabe nach oder in die Ausgabe einzubauen.
Weil man's doch öfter braucht, habe ich einige Include-Dateien zusammengestellt, mit denen man die typischen Problemchen per Rückgriff lösen kann.
Um das Rad nicht jedesmal neu erfinden zu müssen und trotz Low-Level (Assembler) voran zu kommen.
Die Ausgaberoutine SendNumber hat folgende Features:
putchar
-Routine
Einfach
.include "itoa.i90"und anschließend
;R2..R5, R18, R19 laden rcall SendNumberDezaufrufen. (Na gut,
putchar
muss noch irgendwo aufrufbar sein.)
Hier kann man doch noch den div ab
-Befehl verwenden.
Außerdem kommt der selten verwendete Befehl xchd
sinnvoll zum Einsatz.
;DWORD (4 Byte) durch 10 teilen: in 8 Nibbles! ;PE: R0 zeigt auf die Zahl in Big-Endian-Anordnung im iRAM ;PA: A = Rest (0..9) ; R0 zeigt hinter das Divisionsergebnis ; Zahl durch 10 dividiert ;VR: A,B=A,R0+=4,R2=0 ldiv10: mov r2,#8 ;Anzahl Nibbles clr a divl: swap a ;Rest (0..9) in High-Nibble mov b,a mov a,@r0 ;hole nächstes Nibble swap a mov @r0,a ;anderes Nibble retten bzw. richtig positionieren anl a,#0Fh orl a,b ;vorhergehenden Rest einsetzen mov b,#10 div ab xchd a,@r0 ;Ergebnis-Nibble abspeichern mov a,r2 jnb ACC.0,div1 inc r0 ;nächstes Byte div1: mov a,b ;Rest (0..9) liefern oder in nächste Runde djnz r2,divl ret ;DWORD (4 Byte) auf Null testen ;PE: R0 zeigt hinter das letzte Byte im iRAM ;PA: A = 0 wenn alle Bytes Null sind ; R0 zeigt auf das erste Byte ;VR: A,R0+=4,R2=0 lck0: mov r2,#4 clr a ck0l: dec r0 orl a,@r0 djnz r2,ck0l ckFe: ret ;Dezimal-Ausgabe; zerstört die auszugebende Zahl! ;PE: R0 zeigt auf die vzl. 32-bit-Zahl in Big-Endian-Anordnung im iRAM ;VR: A,B,R2=0,R3=0 SendNumberDez: mov r3,#0 ;Ziffernzähler snl: call ldiv10 push ACC inc r3 call lck0 ;Ergebnis Null? jnz snl ;nein, nächste Ziffer generieren snn: pop ACC add a,#'0' call putchar ;Ziffern in umgekehrter Reihenfolge ausgeben djnz r3,snn ret
Der Divisionsalgorithmus mit den Nibbles funktioniert für Divisoren (= Zahlenbasis) von 2 bis 16.