Rechnen mit dem PIC

(für 12- / 14- / 16-Bit-Kern-PICs)

zurück zu PIC-Prozessoren , Elektronik , Homepage

Allgemeines zur PIC-ALU
ganze Zahlen größer als 255
ganze negative Zahlen (signed integer)
Fließkomma-Zahlen (floating)
32-Bit-Ganzzahl-Addition

Bibliotheken



Vorab
Kenntnisse des binären Zahlensystems setze ich voraus.
Außerdem erwarte ich vom geneigten Leser, dass er in der Lage ist, die Grundrechenarten ohne Taschenrechner zu vollziehen. (Ja, das ist möglich!)


Allgemeines zur ALU

PICs sind 8-Bit Microcontroller. Sie besitzen eine ALU (arithmetic logical unit), mit der sie einfache mathematische Operationen durchführen können. Das sind die Addition (ADDWF), die Subtraktion (SUBWF) sowie Boolsche Operationen (AND, OR, XOR ...). (PIC18Fxxx können auch die Multiplikation.)

All diese Operationen können nur mit ganzzahligen (integer) 8 Bit großen Zahlen durchgeführt werden. Der PIC kennt also nur ganze Zahlen von 0 bis 255. Das ist nicht gerade viel. Auch der gesamte Datenspeicher ist 8-bitig ausgeführt. Somit benötigt man für eine 8-Bit-Zahl genau eine Speicherzelle (Register) des PIC.
 
; Speicherplatz für eine 8-Bit-Zahl festlegen
;  der Speicherplatz hat die Adresse 0x20
;  und bekommt den Namen f0
f0  EQU     0x20    ; Speicherplatz für eine 8-Bit Zahl

Die kleinste Zahl ist 0 und wird im Binärcode  B'00000000'  (hexadezimal 0x00) geschrieben,
die größte Zahl ist 255 und wird im Binärcode B'11111111'  (hexadezimal 0xFF) geschrieben.

nach oben

ganze Zahlen größer als 255

Größere Zahlen lassen sich verarbeiten, wenn man mit 16, 24 oder 32 Bit rechnen kann. Mit 24 Bit langen Zahlen lassen sich alle ganzzahligen Werte von 0 bis 16777215 darstellen. Mit 32 Bit kommt man bis 4294967295. Nun kann der PIC aber so große Zahlen nicht als Ganzes verarbeiten, er kann sie aber in 8 Bit große Schnipsel zerlegt verarbeiten. Eine 16-Bit-Zahl wird einfach in zwei 8-Bit-Hälften zerteilt abgespeichert. Eine  24-Bit-Zahl benötigt 3 Speicherzellen und eine 32-Bit-Zahl derer  4.
 
; Speicherplatz für eine 24-Bit Zahl festlegen
;  bestehend aus 3 Speicherplätzen zu je 8 Bit
f0  EQU     0x20    ; Speicherplatz für die unteren 8-Bit der Zahl
f1  EQU     0x21    ; Speicherplatz für die mittleren 8-Bit der Zahl
f2  EQU     0x22    ; Speicherplatz für die oberen 8-Bit der Zahl

Die größte 32-Bit Zahl ist binär geschrieben  B'11111111111111111111111111111111'. Das ist etwas unübersichtlich, deshalb schreibt man größere Zahlen (und oft auch kleine) nicht als binäre Zahlen sondern als Hexadezimalzahlen. Dabei werden immer 4 Stellen einer Binärzahl als eine Ziffer einer Hexadezimalzahl geschreiben. Als Ziffern verwendet man 0,1,2....9,A,B,C,D,E,F, wobei die Buchstaben A..F für Ziffern der Werte 10 bis 15 stehen. Die größte 32-Bit Zahl schreibt man im hexadezimalen System 'FFFFFFFF'. In der PIC-Welt kennzeichnet man hexadezimale Zahlen mit einem vorgestellten 0x. Also ist unsere Zahl 0xFFFFFFFF.

Natürlich kennt der PIC keinen Befehl um z.B. zwei 32-Bit Zahlen zu addieren. Operationen mit großen Zahlen werden schrittweise mit kleinen Unterprogrammen durchgeführt, die man selber schreiben kann, oder aus einer fertigen Bibliothek nimmt.

nach oben


ganze negative Zahlen (signed integer)

Bisher habe wir uns nur mit positiven Zahlen beschäftigt, es gibt aber auch negative Zahlen. Im täglichen Leben hat sich für negative Zahlen die Verwendung eines Vorzeichens (-) durchgesetzt, in der Rechentechnik verwendet man stattdessen das sogenannte Zweierkomplement.

Um von einer Zahl das Zweierkomplement zu bilden, schreibt man sie binär auf, invertiert dann jedes Bit und addiert zum Ergebnis 1 dazu. (Das kling übermäßig kompliziert, aber es vereinfacht später das Rechnen.) Da eine 8-Bit Zahl nur 256 verschiedene Werte annehmen kann, sind das die Werte -128 bis +127. Die positiven Zahlen sehen als Zweierkomplementzahlen genauso aus, wie normale Zahlen. Negative Zahlen erkennt man leicht darann, das ihr höchstwertiges Bit (MSB) eine 1 ist. Zur Wandlung einer 8 Bit-Zahl in ihr negatives Zweierkomplement dient der Befehl NEGF.

Beispiel:
Gesucht ist die binäre Schreibweise für die negative Zahl -9 im Zweierkomplement:

Ausgangswert ist +9 '0000 1001'
invertiert  ergibt das: '1111 0110'
um 1 erhöhen, und man ist fertig:
'1111 0111'

Eine -9 schreibt man binär also '1111 0111'. Das ist aber auch die Schreibweise für die positive Zahl +247. Es ist leider so, dass die Zweierkomplemente der Zahlen 1 .. 127 genauso aussehen wie die Zahlen 128 .. 255. Um diese Zweideutigkeit zu vermeiden, muss man eine Entscheidung treffen.
Man muss sich entscheiden, ob man negative Zahlen braucht oder nicht. Wenn nicht, dann verwendet man unsigned integer, dass heißt, es gibt kein Zweierkomplement, und mit 8 Bit reicht man von 0 bis 255.

Benötigt man aber auch negative Zahlen, verwendet man das Zweierkomplement und spricht vom signed integer und kann bei 8 Bit mit Zahlen von -128 bis +127 agieren.

Für die beiden unterschiedlichen Zahlenformate benötigt man unterschiedliche Rechenroutinen.

Üblich sind signed integer mit 8, 16, 24 oder 32 Bit.

nach oben

Fließkomma-Zahlen (floating point)

Wenn Zahlen zu groß oder zu klein werden, schaltet jeder gute Taschenrechner in die Fließkommadarstellung. Dabei wird eine eine normale Zahl (Mantisse) mit Dezimalpunkt gefolgt vom Exponenten einer Zehnerpotenz dargestellt. Da bedeutet dann 4,1234567x21 nichts anderes als 4,1234567 x 1021. Im Klartext bedeutet das, dass man das Komma eigentlich um 21 Stellen nach rechts verschieben müsste, wollte man die Zahl normal aufschreiben.

Dieses Zahlenformat gibt es auch bei Prozessoren, allerdings nimmt man dort keine Zehnerpotenzen, sondern Zweierpotenzen. Außerdem setzte man das Komma immer eine Stelle weiter links als im Taschenrechner. Das entspräche also 0,41234567x22. Auf diese Art und Weise beginnt jede Zahl mit '0,' und man kann es sich eigentlich sparen, das extra mit '0,' am Anfang abzuspeichern. Im Prozessor speichert man nur 41234567x22 und denkt sich davor das '0,'.

Während ich hier noch mit Dezimalzahlen und Zehnerpotenzen agiert habe, passiert das Ganze im PIC natürlich mit Binärzahlen und Zweierpotenzen. Der Exponent wird als 8-Bit Zahl im Zweierkomplement abgespeichert. Damit lassen sich Potenzen von 2-128 (etwa 0,000000000000000000000000000000000000003 im Dezimalsystem) bis 2127 (etwa 340000000000000000000000000000000000000 im Dezimalsystem) darstellen. Das reicht wohl zur Beschreibung aller Größen im Universum. Die Genauigkeit dieser Zahlen hängt davon ab, mit wievielen Bits der eigentliche Zahlenwert (genannt die Mantisse) abgespeichert wird. Hier sind 16 Bit und 24 Bit (jeweils im Zweierkomplement) üblich.

Insgesamt benötigt man für Exponent und Mantisse zusammen also 3 Byte (24-Bit Fließkomma) oder 4 Byte (32-Bit Fließkomma).
 

nach oben

32-Bit-Ganzzahl-Addition

Wenn man zwei 8-Bit-Zahlen addieren will, so steht dafür der ADDWF-Befehl zur Verfügung. Die Addition von 16-, 24-, oder 32-Bit-Zahlen ist auch nicht sehr kompliziert. Ich möchte das am Beispiel einer 32-Bit Addition erläutern.

Jede 32-Bit-Zahl besteht aus 4 Schnipseln a 8 Bit. Das ist ähnlich einer 4-stelligen Dezimalzahl, die auch aus 4 einzelnen Ziffern besteht. Wenn man z.B. die beiden Dezimalzahlen 1234 und 8695 addieren möchte, so fängt man bekanntlich mit den jeweils letzten Stellen beider Zahlen an, und addiert sie: 4+5=9.
Damit hat man die letzte Ziffer des Ergebnisses. Dann adiert man die beiden vorletzten Ziffern u.s.w

Das einzige Problem, das dabei auftreten kann, ist, dass die Summe zweier Ziffern größer als 9 ist. In einem solchen Fall wird die Einerstelle der Summe als neue Ziffer des Ergebnisses aufgeschrieben, während die Zehnerstelle (eine 1) bei der Addition der nächsthöheren Ziffer mit hinzuaddiert wird. Das ist in unserem Beispiel bei der Zehnerstelle der Fall: 3+9=12
Die 2 (Einerstelle der 12) wird als Zehnerstelle des Ergebisses aufgeschrieben, die 1 (Zehnerstelle der 12) wird bei der Addition der Hunderterstellen hinzuaddiert: 2+6+1=9

Die Addition der 4-Byte langen 32-Bit-Zahlen erfolgt nach dem gleichen Schema. Anfangs werden jeweils die beiden niederwertigsten Bytes addiert. Ist die Summe kleiner als 256, dann passt sie in das niederwerigste Ergebnisbyte. Die Summe kann aber auch einen Wert zwischen 256 und 511 annnehmen. In diesem Fall haben wir ein 9-Bit langes Ergebnis der 8-Bit Addition. Die unteren 8-Bit bilden das Ergebnisbyte, das 9. Bit (eine 1) wird beim nächsten Byte hinzuaddiert.

Im folgenden Programm werden zwei 32-Bit Integer-Zahlen addiert. Die Zahlen befinden sich in den Registern f3, f2, f1, f0 und xw3, xw2, xw1, xw0. Das 32-Bit Ergebnis wird nach f (also f3, f2, f1, f0) geschrieben. Falls das Ergebnis 33-Bit lang wird, dann steht das 33. Bit zu Schluss im C-Flag des STATUS-Registers. Dieses Flag dient also als Überlaufindikator.

Im Programm werden zuerst die niederwertigsten Bytes aus f0 und xw0 addiert. Das Ergebnis gelangt nach f0. Trat dabei ein Überlauf auf (9. Bit), dann wird xw1 um 1 erhöht. Falls schon dadurch xw1 überlaufen sollte (falls xw1 also zufälligerweise 255 war) wird f1 als Ergebnisbyte behalten, und der Übertrag zur nöchsten Stelle weitergereicht, ansonsten wird f1 und xw1 addiert  u.s.w
 
;32 bit Adition, C-Flag bei Überlauf gesetzt
;*********************************************************************
;* 
;* addiere xw (32-byte value) mit f (32-byte value).
;* Input  variablen: f (32-bit value), xw (32-bit value)
;* Output variablen: f (32-bit value), 33. bit in STATUS,C
;*********************************************************************
Add32                           ; 32-bit add: f = f + xw
        movf    xw0,W           ; low byte
        addwf   f0,F            ; low byte add

        movf    xw1,W           ; next byte
        btfsc   STATUS,C        ; überspringe falls C nicht gesetzt
        incfsz  xw1,W           ; addiere C falls gesetzt
        addwf   f1,F            ; next byte add wenn NZ

        movf    xw2,W           ; next byte
        btfsc   STATUS,C        ; überspringe falls C nicht gesetzt
        incfsz  xw2,W           ; addiere C falls gesetzt
        addwf   f2,F            ; next byte add wenn  NZ

        movf    xw3,W           ; high byte
        btfsc   STATUS,C        ; überspringe falls C nicht gesetzt
        incfsz  xw3,W           ; addiere C falls gesetzt
        addwf   f3,F            ; high byte add wenn  NZ

        return                  ; fertig

Diese Routine lässt sich natürlich leicht auf 24 Bit oder 16 Bit kürzen.
;16 bit Adition, C-Flag bei Überlauf gesetzt
;*********************************************************************
;* 
;* addiere xw (16-byte value) mit f (16-byte value).
;* Input  variablen: f (16-bit value), xw (16-bit value)
;* Output variablen: f (16-bit value), 17. bit in STATUS,C
;*********************************************************************
Add16                           ; 16-bit add: f = f + xw
        movf    xw0,W           ; low byte
        addwf   f0,F            ; low byte add

        movf    xw1,W           ; high byte
        btfsc   STATUS,C        ; überspringe falls C nicht gesetzt
        incfsz  xw1,W           ; addiere C falls gesetzt
        addwf   f1,F            ; high byte add wenn NZ

        return                  ; fertig

nach oben

Bibliotheken

Alles was über eine einfache 8-Bit-Addition/Subtraktion hinausgeht, muss man mit Hilfe von kleinen Programmen erledigen. Für die 32-BitAddition haben wir uns gerade solch ein Programm selber ausgedacht. Aber solche Programme gibt es auch schon fertig in Bibliotheken zusammengefasst bei Microchip zum Download, und können in eigene Programme eingebunden werden.

AN617 (integer)
In der Application note AN617 sind Routinen für das Multiplizieren und Dividieren von 8, 16, 24 und 32 Bit Zahlen enthalten. Wenn man das zur AN617 zugehörige ZIP-File entpackt hat, findet man mehrere Dateien mit der Endung '.A16' vor. Die enthalten Macros für Multiplikationen und Divisionen aller möglicher Kombinationen von 8,16,24,32-Bit langen Zahlen. Natürlich werden eine ganze Reihe von vorher definierten Registern benötigt, z.B. um die Ausgangswerte und das Ergebnis zu speichern. Die Definitionen dafür findet man in der Datei math16.inc
Addition und Subtraktion werden nicht unterstützt, die sind zu einfach, um dafür extra Bibliotheken zu bemühen. Ein praktisches Beispiel für die Nutzung der AN617 findet man hier.

AN575 (floating point)
Diese  Application note enthält Routinen für die Grundrechenarten (Addition, Subtraktion, Multiplikation und Division) mit Fließkommazahlen (32 Bit und 24 Bit). Außerdem sind Routinen enthalten, um integer-Zahlen in floating-point-Zahlen umzuwandeln, und auch um floating-point-Zahlen wieder in integer-Zahlen wieder zurückzuverwandeln.

AN660 (floating point)
Hier findet man eine ganze Reihe von Routinen, die über die Grundrechenarten hinausgehen

Als Zahlenformat wird das Fließkommaformat mit 32-Bit und 24-Bit  verwendet.

AN670 (floating point to ASCII)
Im täglichen Leben begegnet der Mensch eher selten binären Fließkommazahlen, und so ist er den Umgang mit ihnen nicht gewohnt. Wenn man eine Fließkommazahl auf einem Display ausgeben will, so sollte man sie der Freundlichkeit halber vorher in ein lesbares Format umwandeln. Die dafür nötigen Routinen sind in der AN670 enthalten.
So werden aus binären Fließkommazahlen ASCII-Strings (Zeichenketten) im Dezimalformat, die man direkt auf einem Display anzeigen, oder via RS232 an ein Terminalprogramm senden kann.
 

nach oben

zurück zu PIC-Prozessoren , Elektronik , Homepage

Autor: sprut
erstellt am: 18.10.2003
letzte Änderung: 26.06.2007