Forum: Mikrocontroller und Digitale Elektronik PIC Assembler Division 8Bit


von Dieter L. (dlog43)


Lesenswert?

Hallo zusammen,

ich suche nach einer Möglichkeit mit einem PIC in Assembler eine 
Division durchzuführen. Konkret geht es darum, einen 8Bit Wert, der 
allerdings nur Werte zwischen 5 und 90 liefert, durch einen ebenso 8Bit 
Wert, genauer dez. 10 zu dividieren. Also in etwa so:
Ausgangswert Bsp1: 0000|0101 (dez. 5)
Weitere Werte 6...89
Ausgangswert Bsp2: 0101|1010 (dez. 90)
sollen jeweils durch den
Divisor: 0000|1010 (dez. 10)
geteilt werden. Für die weitere Verwendung werden sowohl das Ergebnis 
als auch ein möglicher Rest benötigt.

Ich habe ein Beispiel gefunden, um 24Bit durch 16Bit zu dividieren, habe 
jedoch Probleme, es auf 8Bit durch 8Bit umzuwandeln.
1
Div16                  
2
                clrf REST_H
3
                clrf REST_L
4
                movlw D'24'
5
                movwf DIV_COUNTER
6
div16_loop
7
                rlf ZAEHLER_L, F
8
                rlf ZAEHLER_M, F
9
                rlf ZAEHLER_H, F
10
                rlf REST_L, F
11
                rlf REST_H, F
12
                rlf DIV_COUNTER, F
13
                movwf NENNER_L, W
14
                subwf REST_L, F
15
                movf NENNER_H, W
16
                btfss STATUS, C
17
                incfsz NENNER_H, W
18
                subwf REST_H, W
19
                btfsc STATUS, C
20
                bsf DIV_COUNTER, 0
21
                btfs DIV_COUNTER, 0
22
                goto div16_jump
23
                movf NENNER_L, W
24
                addwf REST_L, F
25
                movf REST_H, W
26
div16_jump
27
                movwf REST_H
28
                bcf STATUS, C
29
                rrf DIV_COUNTER, F
30
                decfsz DIV_COUNTER, F
31
                goto div16_loop
32
                rlf ZAEHLER_L, F
33
                rlf ZAEHLER_M, F
34
                rlf ZAEHLER_H, F
35
                return
ZAEHLER_H, ..M, ..L und NENNER_H, ..L müssen vorher als Variable 
eingetragen und mit Werten geladen werden. Mein bisheriger Versuch das 
ganze auf 8/8Bit umzuwandeln, sieht wie folgt aus, klappt jedoch nicht 
wirklich. Zudem bin ich mir unsicher, welche H und L Werte ich nun 
ersetzen muss (ich habe ja kein H oder L, mir reicht ja "eins" wg. 
8Bit).
1
Div8          
2
                clrf REST
3
                movlw D'8'
4
                movwf DIV_COUNTER
5
div8_loop
6
                rlf ZAEHLER, F
7
                rlf REST, F
8
                rlf DIV_COUNTER, F    ; bis hier sollte es noch stimmen?
9
                movf NENNER, W        ; ab hier bin ich mir unsicher
10
                btfss STATUS, C
11
                incfsz NENNER, W
12
                subwf REST, W
13
                btfsc STATUS, C
14
                bsf DIV_COUNTER, 0
15
                btfsc DIV_COUNTER, 0
16
                goto div8_jump
17
                movf NENNER_L, W     
18
                addwf REST_L, F
19
                movf REST_H, W
20
div8_jump                             ; ab hier sollte es wieder passen?
21
                movwf REST
22
                bcf STATUS, C
23
                rrf DIV_COUNTER, F
24
                decfsz DIV_COUNTER, F
25
                goto div8_loop
26
                rlf ZAEHLER, F
27
                return

Über eine nette und hilfreiche Antwort freue ich mich sehr :-)

von Martin M. (ats3788)


Lesenswert?

Hallo
Mache die Division in C
und schaue dann im Disassemblier
nach dem Assembler Code.

von Noch einer (Gast)


Lesenswert?

Oder suche nach AN617. Da gibt es ein PDF und ein Zip mit Beispielcode 
für alle Varianten.

von John (Gast)


Lesenswert?

Dieter L. schrieb:
> nur Werte zwischen 5 und 90 liefert, durch einen ebenso 8Bit
> Wert, genauer dez. 10 zu dividieren

Wenn Dein Divisor immer 10 ist, dann reicht eine Binär zu BCD Wandlung: 
BCD_high ist das Ergebnis und BCD_low ist der Rest.

Gruß
John

von W.S. (Gast)


Lesenswert?

Dieter L. schrieb:
> 5 und 90 liefert, durch einen ebenso 8Bit
> Wert, genauer dez. 10 zu dividieren.

Normalerweise sollte man für sowas ein Gleitkommapaket auf der 
Hinterhand haben - ich hatte sowas für die PIC16Fxxx hier schon mal 
gepostet - aber wenn's nur für diesen Spezialfall ist, dann geht das 
auch durch schlichtes Abzählen:

    akku:= inputwert;
    counter:= 0;
    goto hierhinein;
nochmal:
    inc(counter);
hierhinein:
    akku:= akku - 10;
    if akku >= 0 then goto nochmal;
    rest:= akku + 10
    ergebnis:= counter;

Die Anzahl der Schleifen ist ja überschaubar und der Schleifeninhalt 
kurz.

W.S.

von Dieter L. (dlog43)


Lesenswert?

Mit C habe ich mich noch gar nicht befasst, aber vllt. versuche ich es 
mal. Bin noch nicht solange mit Mikrocontrollern dran, wollte erstmal 
mit Assembler herumspielen, da ich dazu viele Beispiele gefunden habe.

John:
Wie führe ich eine Binär-BCD-Wandlung am einfachsten durch? Durch 
Schiebeoperationen? Sprich 4x rechts schieben = BCD_H und den 
ursprünglichen Wert 4x links schieben für BCD_L? Oder muss ich 5x nach 
links schieben wegen Carry-Bit?

Vielen Dank schonmal :)

von Noch einer (Gast)


Lesenswert?

Du kannst die 8bit/8bit Routine aus der an617 nehmen, die Schleife 
ausrollen und die BTFSC rauswerfen. Dann bleiben die richtige Anzahl an 
RLF und SUBWF übrig.

von John (Gast)


Lesenswert?

Dieter L. schrieb:
> Wie führe ich eine Binär-BCD-Wandlung am einfachsten durch?

Hallo Dieter,
ich hab da mal kurz was zusammengetippt, ich hoffe es funktioniert.
Das macht praktisch das, was W.S. auch geschrieben hat.

1
; Variablen:
2
        BCD_high              ; Ergebnis
3
        BCD_low               ; Ergebnis
4
        BIN                   ; vor "call BIN2BCD" Wert in BIN speichern
5
------------------------------------------------------------------------
6
7
BIN2BCD                       ; BIN zu BCD (00..99)
8
         clrf   BCD_high
9
BIN2BCD_1
10
         movlw  0x0A          ; BCD_high
11
         subwf  BIN,W         ; BIN - 10
12
         btfss  STATUS,C      ; Ergebnis ist positiv oder negativ?
13
         goto   BIN2BCD_2     ;   - negativ
14
         movwf  BIN           ;   - positiv (oder Null) geänderten Wert speichern
15
         incf   BCD_high,F    ; BCD_high +1
16
         goto   BIN2BCD_1
17
BIN2BCD_2
18
         movf   BIN,W         ; BCD_low = Rest
19
         movwf  BCD_low
20
         return

Gruß
John

von Witkatz :. (wit)


Lesenswert?

W.S. schrieb:
> Die Anzahl der Schleifen ist ja überschaubar

An eine Schleife hab ich auch gedacht, hier auf die Schnelle mein 
Vorschlag:
1
div_10
2
    movwf rest              ; rest = w
3
    clrf quotient           ; quotient = 0;
4
    movlw d'10'             ;
5
div_10_loop                 ; do {
6
    subwf rest, f           ;   rest -= 10;
7
    btfss STATUS,C          ;   if(rest < 0)
8
    goto div_10_ret         ;       break;
9
    incf quotient, f        ;   quotient++;
10
    goto div_10_loop        ; } while(1)
11
div_10_ret
12
    addwf rest, f           ; rest += 10;
13
    movf quotient, w        ; return(quotient);
14
    return

: Bearbeitet durch User
von Stephan (Gast)


Lesenswert?

wenn du immer durch denselben Wert teilst, dann kannst du auch mit 
Rechtsshifts und Subtraktion arbeiten.

von Possetitjel (Gast)


Lesenswert?

Stephan schrieb:

> wenn du immer durch denselben Wert teilst, dann kannst du
> auch mit Rechtsshifts und Subtraktion arbeiten.

Statt durch 10 zu teilen könnte man ja auch mit 0.1
multiplizieren.

von Christian M. (Gast)


Lesenswert?

Mit einer Tabelle? Von welchem PIC reden wir?

Gruss Chregu

von Volker S. (vloki)


Lesenswert?

Dieter L. schrieb:
> Hallo zusammen,
>
> ich suche nach einer Möglichkeit mit einem PIC in Assembler eine
> Division durchzuführen. Konkret geht es darum, einen 8Bit Wert, der
> allerdings nur Werte zwischen 5 und 90 liefert, durch einen ebenso 8Bit
> Wert, genauer dez. 10 zu dividieren.

Was für ein PIC ist es denn genau ?
Hat der einen "HARDWARE MULTIPLIER" ?

Dann könntest du deine Zahl mit 26 multiplizieren und im PRODH würde das 
Ergebnis stehen. Den "Rest" müsstest du dann noch berechnen ...

von blubdidup (Gast)


Lesenswert?

1
;  RCS Header $Id: fxd88.a16 2.3 1996/10/16 14:23:57 F.J.Testa Exp $
2
;  $Revision: 2.3 $
3
4
;       8/7 Bit Unsigned Fixed Point Divide 8/7 -> 08.07
5
6
;       Input:  8 bit unsigned fixed point dividend in AARGB0
7
;               7 bit unsigned fixed point divisor in BARGB0
8
9
;       Use:    CALL    FXD0807U
10
11
;       Output: 8 bit unsigned fixed point quotient in AARGB0
12
;               7 bit unsigned fixed point remainder in REMB0
13
14
;       Result: AARG, REM  <--  AARG / BARG
15
;       Max Timing:     1+85+2 = 88 clks
16
;       Min Timing:     1+85+2 = 88 clks
17
;       PM: 1+19+1 = 21         DM: 4
18
19
FXD0807U        CLRF            REMB0
20
                UDIV0807L
21
                RETLW           0x00
22
23
UDIV0807L       macro
24
;       Max Timing:     7+6*11+10+2 = 85 clks
25
;       Min Timing:     7+6*11+10+2 = 85 clks
26
;       PM: 19                                  DM: 4
27
28
                RLF             AARGB0,W
29
                RLF             REMB0, F
30
                MOVF            BARGB0,W
31
                SUBWF           REMB0, F
32
                RLF             AARGB0, F
33
34
                MOVLW           7
35
                MOVWF           LOOPCOUNT
36
37
LOOPU0807       RLF             AARGB0,W
38
                RLF             REMB0, F
39
                MOVF            BARGB0,W
40
41
                BTFSC           AARGB0,LSB
42
                SUBWF           REMB0, F
43
                BTFSS           AARGB0,LSB
44
                ADDWF           REMB0, F
45
                RLF             AARGB0, F
46
47
                DECFSZ          LOOPCOUNT, F
48
                GOTO            LOOPU0807
49
50
                BTFSS           AARGB0,LSB
51
                ADDWF           REMB0, F
52
53
                endm

von Amateur (Gast)


Lesenswert?

Wenn Du fix mit 10 Multiplizieren musst (z.B. 12):
1. Einmal Linksschieben (*2) [24]
2. Sichern
3. 1. zweimal Linksschieben (*2*4=*8) [96]
4. Zur Sicherung hinzuaddieren (*2+*8=*10) [24+96=120]

Also: 3x Schieben; 1x Speichern; 1x Addieren

von Possetitjel (Gast)


Lesenswert?

Volker SchK schrieb:

> Dann könntest du deine Zahl mit 26 multiplizieren

Zu groß; das geht bei 89 schief: (89 * 26) / 256 = 9.039
(Genauer: Es geht bei 69, 79, 89 schief.)

Mit 205 multiplizieren und das High-Byte 3 bit rechtsschieben
geht aber:

(89 * 205) / 2048 = 8.909

Lohnt natürlich nur, wenn ein Hardware-Multiplizierer
vorhanden ist.

von Volker S. (vloki)


Lesenswert?

Mist, ja ab 69 müsste man dann wohl vorher eins abziehen.
(ab 128 -2, 197 -3)

<edit> ab 64 (wenn bit6) würde auch gehen ...

: Bearbeitet durch User
von Possetitjel (Gast)


Lesenswert?

Volker SchK schrieb:
> Mist, ja ab 69 müsste man dann wohl vorher eins abziehen.
> (ab 128 -2, 197 -3)

Geht natürlich. Hat aber den Nachteil, dass man mit
Fallunterscheidungen herumlaborieren muss.

Die Methode, einfach einen genaueren Näherungsbruch zu
verwenden (205/2048) hat den Charme, ohne Fallunterscheidungen
auf 16 Bit verallgemeinerbar zu sein.
Der Bruch 52429/524288 ist auf ungefähr 17 Bit genau.

von Volker S. (vloki)


Lesenswert?

Possetitjel schrieb:
> Geht natürlich. Hat aber den Nachteil, dass man mit
> Fallunterscheidungen herumlaborieren muss.

In diesem speziellen Fall wäre die Fallunterscheidung ja nur
1
btfsc   Zahl,6
2
decf    Zahl

: Bearbeitet durch User
von Possetitjel (Gast)


Lesenswert?

Volker SchK schrieb:

> Possetitjel schrieb:
>> Geht natürlich. Hat aber den Nachteil, dass man mit
>> Fallunterscheidungen herumlaborieren muss.
>
> In diesem speziellen Fall wäre die Fallunterscheidung ja nur
>
> btfsc   Zahl,6
> decf    Zahl

Ja, stimmt schon.

Meine Bemerkung hatte nix mehr mit der Ursprungsfrage zu tun.
Ich finde es einfach elegant, dass man mit einem Multiplizierer
auch in voller Genauigkeit dividieren kann :-)

von Volker S. (vloki)


Lesenswert?

Possetitjel schrieb:
> Ich finde es einfach elegant, dass man mit einem Multiplizierer
> auch in voller Genauigkeit dividieren kann :-)

Da hast du natürlich absolut recht ...

von Dieter L. (dlog43)


Lesenswert?

Oh, ist ja ganz schön was los hier, ich versuche mal alles wichtige der 
Reihe nach zu kommentieren seit meinem letzten Post.

Noch einer:
Die Routine aus der AN617 habe ich mir angeschaut, bin jedoch nicht ganz 
schlau daraus geworden. Auf den ersten Seiten gibt es ja diese Tabellen 
je nachdem welchen PIC man benutzt, da war meiner nichtmal 
aufgeführt...da stand PIC16C...., ich verwende allerdings einen 
PIC16F887.

John und Wit G:
Werde eure Ideen bald mal ausprobieren, vielen Dank dafür.

Stephan/Possetitjel/Chregu:
An eine Multiplikation mit 0.1 hatte ich noch nicht gedacht, wobei ich 
nicht weiß, ob das viel einfacher ist? Nochmal die Info: PIC16F887

Volker SchK:
So wie ich das gelesen habe, scheint mein PIC (s.o.) keinen Hardware 
Multiplier zu besitzen.

Und an alle nochmal ein großes Dankeschön, hat mir schon viel geholfen 
:)

von W.S. (Gast)


Angehängte Dateien:

Lesenswert?

Dieter L. schrieb:
> Mit C habe ich mich noch gar nicht befasst, aber vllt...

Dieter, das was ich da gepostet habe, ist KEIN C, sondern ein 
pascalartig formuliertes Prinzip. Du kannst es fast 1:1 in 
Maschinenbefehle umsetzen.
1
    akku:= inputwert;               MOVWF  akku
2
    counter:= 0;                    CLRF   counter
3
                                    MOVLW  256-10
4
    goto hierhinein;                GOTO   hierhinein
5
nochmal:                           nochmal:
6
    inc(counter);                   INCF   counter,f
7
hierhinein:                        hierhinein:
8
    akku:= akku - 10;               ADDWF  akku,f
9
    if akku >= 0                    SKIP   C
10
      then goto nochmal;            GOTO   nochmal
11
    rest:= akku + 10                MOVF   akku,w
12
                                    ADDLW  10
13
                                    MOVWF  rest
14
    ergebnis:= counter;             MOVF   counter,w
15
                                    MOVWF  ergebnis
Tips:
1. SKIP ist BTFSS
2. C ist das Carrybit
3. akku + 246 ergibt C wenn das Ergebnis positiv oder 0 ist

Ich hänge dir mal ne steinalte, aber gut funktionierende 
Gleitkomma-Routine in Assembler für die PIC16 dran - zum verstehenden 
Lesen. Mit dem MicroChip-Assembler kannst du sie nämlich nur übersetzen, 
wenn du sie passend editierst. Aber zum Verstehen des Prinzips ist sie 
gut, weil gut lesbar.

W.S.

: Bearbeitet durch User
von Stephan (Gast)


Lesenswert?

Hmmm, ich habe gerade nochmal überlegt und bin nicht darauf gekommen, 
wie man das mit Rechtsshifts machen kann. Man kann ja sehr gut den 
Booth-Algorithmus für die Multiplikation mit Konstanten verwenden, für 
die Division scheint das nicht so einfach zu funktionieren.

Wenn du aber immer nur durch 10 dividieren willst, kannst du den 
Dividend auch in BCD umwandeln und dann einen Rechtsshift machen und das 
Ergebnis wieder in eine Binärzahl zurückwandeln. Der Vorteil wäre, das 
du den Rest dann auch gleich mit einsammeln kannst und keinen 
Rechenfehler hast.
Eine Abwandlung von der BCD-Darstellung wird heutzutage ja immernoch von 
Großbanken verwendet.
BCD hätte aber auch den Vorteil, dass du das Ergebniss auch gleich auch 
einen Characterdisplay darstellen kannst.

Für die Umwandlung von Binär- nach BCD-Zahl gibt es übrigens den 
Double-Dabble-Algorithmus.

von Possetitjel (Gast)


Lesenswert?

Stephan schrieb:

> Hmmm, ich habe gerade nochmal überlegt und bin nicht darauf
> gekommen, wie man das mit Rechtsshifts machen kann. Man kann
> ja sehr gut den Booth-Algorithmus für die Multiplikation mit
> Konstanten verwenden, für die Division scheint das nicht so
> einfach zu funktionieren.

Naja, durch 10 teilen ist dasselbe wie mit 0.1 multiplizieren.
Dezimal 0.1 ist aber gerade binär 0.00011001100110011...

Multipliziere also einfach Deine Zahl mit 51, schiebe 9 Bit
nach rechts - und Du hast (näherungsweise) durch 10 dividiert.

Richtig gut ist, mit 205 malzunehmen und 11 Bit nach rechts
zu schieben.

von Chris S. (schris)


Lesenswert?

Hier ein Code fuer bin->bcd

BinBCD ; returns ones, packed bcd in tmp
  clrf     tmp
    incf     tmp
  addlw    .246
        skpnc
  goto    BinBCD+1
        decf     tmp
  addlw    .10
  swapf    tmp
  addwf    tmp ; ,w for packed pcb as return value
  return
;

von Dieter L. (dlog43)


Lesenswert?

Hallo zusammen,

habe es nicht früher geschafft zu antworten, aber besser spät als nie. 
Habe mich letztendlich für die Idee von John entschieden, sprich immer 
10 subtrahieren und dabei mitzählen, wie oft das geht bis das Ergebnis 
<=0 ist und dann den Rest als Einerstelle nehmen. Danke aber auch an 
alle anderen, auch an die letzten neuen Antworten hier mit diesem 
Algorithmus, interessante Sache auf jeden Fall! Weiter so!

Viele Grüße

von Jobst M. (jobstens-de)


Lesenswert?

Noch eine Möglichkeit:

zu teilender Wert in ein Register (A)
160 in weiteres Register (B)
Ergebnis in Register (E)


CLR E
x:
Schiebe E 1 nach oben
Wenn A > B, dann A = A - B und E++
Schiebe B 1 nach unten in Carry
Wenn Carry nicht gesetzt springe zu x

In E befindet sich nun :
0  0  160er 80er 40er 20er 10er 5er

Für nur 10er E wieder ein bit nach unten schieben (5er fliegt raus)
Wenn sichergestellt ist, dass A nicht größer als 90 ist, kann B auch mit 
80 initialisiert werden.



Gruß

Jobst

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.