Pfad: Home => AVR-Übersicht => Binäres Rechnen => Fließkomma
AT90S8515

Umgang mit Festkommazahlen in AVR Assembler

Sinn und Unsinn von Fließkommazahlen

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

Lineare Umrechnungen

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:
ZahlWert (Beispiel)Register
Eingangswert255R1
Multiplikator502R4:R3:R2
Ergebnis128.010R7: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:
  1. Testen, ob die Zahl schon Null ist. Wenn ja, sind wir fertig mit der Multiplikation.
  2. 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.
  3. 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.
  4. 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.
  5. 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

Beispiel 1: 8-Bit-AD-Wandler mit Festkommaausgabe

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

Beispiel 2: 10-Bit-AD-Wandler mit Festkommaausgabe

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