Pfad: Home =>
AVR-Übersicht =>
Binäres Rechnen => Fließkomma
Umgang mit Festkommazahlen in AVR Assembler
Oberster Grundsatz: Verwende keine Fließkommazahlen, es sei denn, Du brauchst
sie wirklich. Fließkommazahlen sind beim AVR Ressourcenfresser, lahme Enten
und brauchen wahnsinnige Verarbeitungszeiten. So oder ähnlich geht es einem, der
glaubt, Assembler sei schwierig und macht lieber mit Basic und seinen höheren
Genossen C und Pascal herum.
Nicht so in Assembler. Hier kriegst Du gezeigt, wie man bei 4 MHz Takt in
gerade mal weniger als 60 Mikrosekunden, im günstigsten Fall in 18 Mikrosekunden,
eine Multiplikation einer Kommmazahl abziehen kann. Ohne extra
Fließkommazahlen-Prozessor oder ähnlichem Schnickschnack für Denkfaule.
Wie das geht? Zurück zu den Wurzeln! Die meisten Aufgaben mit Fließkommazahlen
sind eigentlich auch mit Festkommazahlen gut zu erledigen. Und die kann man nämlich
mit einfachen Ganzzahlen erledigen. Und die sind wiederum in Assembler leicht zu
programmieren und sauschnell zu verarbeiten. Das Komma denkt sich der Programmierer
einfach dazu und schmuggelt es an einem festem Platz einfach in die Strom von
Ganzzahlen-Ziffern rein. Und keiner merkt, dass hier eigentlich gemogelt wird.
Zum Seitenanfang
Als Beispiel folgende Aufgabe: ein 8-Bit-AD-Wandler misst ein Eingangssignal
von 0,00 bis 2,55 Volt und liefert als Ergebnis eine Binärzahl zwischen
$00 und $FF ab. Das Ergebnis, die Spannung, soll aber als ASCII-Zeichenfolge
auf einem LCD-Display angezeigt werden. Doofes Beispiel, weil es so einfach
ist: Die Hexzahl wird in eine BCD-kodierte Dezimalzahl zwischen 000 und 255
umgewandelt und nach der ersten Ziffer einfach das Komma eingeschmuggelt.
Fertig.
Leider ist die Elektronikwelt manchmal nicht so einfach und stellt schwerere
Aufgaben. Der AD-Wandler tut uns nicht den Gefallen und liefert für
Eingangsspannungen zwischen 0,00 und 5,00 Volt die 8-Bit-Hexzahl $00 bis $FF.
Jetzt stehen wir blöd da und wissen nicht weiter, weil wir die Hexzahl
eigentlich mit 500/255, also mit 1,9608 malnehmen müssten. Das ist eine
doofe Zahl, weil sie fast zwei ist, aber nicht so ganz. Und so ungenau
wollten wir es halt doch nicht haben, wenn schon der AD-Wandler mit einem
Viertel Prozent Genauigkeit glänzt. Immerhin zwei Prozent Ungenauigkeit
beim höchsten Wert ist da doch zuviel des Guten.
Um uns aus der Affäre zu ziehen, multiplizieren wir das Ganze dann eben
mit 500*256/255 oder 501,96 und teilen es anschließend wieder durch 256.
Wenn wir jetzt anstelle 501,96 mit aufgerundet 502 multiplizieren (es lebe
die Ganzzahl!), beträgt der Fehler noch ganze 0,008%. Mit dem können
wir einstweilen leben. Und das Teilen durch 256 ist dann auch ganz einfach,
weil es eine bekannte Potenz von Zwei ist, und weil der AVR sich beim Teilen
durch Potenzen von Zwei so richtig pudelwohl fühlt und abgeht wie Hund.
Beim Teilen einer Ganzzahl durch 256 geht er noch schneller ab, weil wir dann
nämlich einfach das letzte Byte der Binärzahl weglassen
können. Nix mit Schieben und Rotieren wie beim richtigen Teilen.
Die Multiplikation einer 8-Bit-Zahl mit der 9-Bit-Zahl 502 (hex 01F6) kann
natürlich ein Ergebnis haben, das nicht mehr in 16 Bit passt. Hier
müssen deshalb 3 Bytes oder 24 Bits für das Ergebnis vorbereitet
sein, damit nix überläuft. Während der Multiplikation der
8-Bit-Zahl wird die 9-Bit-Zahl 502 immer eine Stelle nach links geschoben
(mit 2 multipliziert), passt also auch nicht mehr in 2 Bytes und braucht
also auch drei Bytes. Damit erhalten wir als Beispiel folgende Belegung
von Registern beim Multiplizieren:
| Zahl | Wert (Beispiel) | Register |
| Eingangswert | 255 | R1 |
| Multiplikator | 502 | R4:R3:R2 |
| Ergebnis | 128.010 | R7:R6:R5 |
Nach der Vorbelegung von R4:R3:R2 mit dem Wert 502 (Hex 00.01.F6) und dem
Leeren der Ergebnisregister R7:R6:R5 geht die Multiplikation jetzt nach
folgendem Schema ab:
- Testen, ob die Zahl schon Null ist. Wenn ja, sind wir fertig mit der
Multiplikation.
- Wenn nein, ein Bit aus der 8-Bit-Zahl nach rechts heraus in das
Carry-Flag schieben und gleichzeitig eine Null von links hereinschieben.
Der Befehl heißt Logical-Shift-Right oder LSR.
- Wenn das herausgeschobene Bit im Carry eine Eins ist, wird der Inhalt
des Multiplikators (beim ersten Schritt 502) zum Ergebnis hinzu addiert.
Beim Addieren auf Überläufe achten (Addition von R5 mit R2 mit
ADD, von R6 mit R3 sowie von R7 mit R4 mit ADC!). Ist es eine Null,
unterlassen wir das Addieren und gehen sofort zum nächsten Schritt.
- Jetzt wird der Multiplikator mit zwei multipliziert, denn das
nächste Bit der Zahl ist doppelt so viel wert. Also R2 mit LSL links
schieben (Bit 7 in das Carry herausschieben, eine Null in Bit 0
hineinschieben), dann das Carry mit ROL in R3 hineinrotieren (Bit 7 von
R3 rutscht jetzt in das Carry), und dieses Carry mit ROL in R4 rotieren.
- Jetzt ist eine binäre Stelle der Zahl erledigt und es geht bei
Schritt 1 weiter.
Das Ergebnis der Multiplikation mit 502 steht dann in den Ergebnisregistern
R7:R6:R5. Wenn wir jetzt das Register R5 ignorieren, steht in R7:R6 das
Ergebnis der Division durch 256. Zur Verbesserung der Genauigkeit können
wir noch das Bit 7 von R5 dazu heranziehen, um die Zahl in R7:R6 zu runden. Und
unser Endergebnis in binärer Form braucht dann nur noch in dezimale
ASCII-Form umgewandelt werden (siehe Umwandlung
binär zu Dezimal-ASCII). Setzen wir dem Ganzen dann noch einen Schuss
Komma an der richtigen Stelle zu, ist die Spannung fertig für die Anzeige
im Display.
Der gesamte Vorgang von der Ausgangszahl bis zum Endergebnis in ASCII dauert
zwischen 79 und 228 Taktzyklen, je nachdem wieviel Nullen und Einsen die
Ausgangszahl aufweist. Wer das mit der Fließkommaroutine einer
Hochsprache schlagen will, soll sich bei mir melden.
Zum Seitenanfang
Das bisher beschriebene Programm ist, noch ein wenig optimiert, in
HTML-Form oder als
Assembler-Quelltext zugänglich.
Der Quelltext enthält alle nötigen Routinen der Umrechnung in kompakter
Form, zum leichteren Import in andere Programme. Als Kopf ist ein Testabschnitt
hinzugefügt, so dass der Programmablauf im Studio simuliert werden kann.
Zum Seitenanfang
8-Bit-AD-Wandler sind selten, 10-Bit ist da schon gebräuchlicher. Weil 10 Bits
noch etwas genauer sind, dürfen es diesmal vier dezimale Stellen sein. Wobei
die letzte dieser Stellen naturgemäß klappert. Hier geht es zur
HTML-Form und hier zum
Assembler-Quelltext des Programmes.
Aufgrund des größeren Darstellungsbereichs braucht das Programm etwas
mehr untere Register zum Rumrechnen. Die Ausführungszeit ist etwas länger
als mit 8 Bits und 3 Stellen, aber auch nicht übermäßig.
Zum Seitenanfang
©2003 by http://www.avr-asm-tutorial.net