Forum: Mikrocontroller und Digitale Elektronik asm 16 Bit in BCD


von Bruno M. (brumay)


Lesenswert?

Hallo,
ich habe im Internet folgenden Code gefunden:

There is a more efficient method "shift-plus-3".

I have written the sample code as below. The input hex value
put in R17:R16, and this function will return R20:R19:R18
with the BCD value.
1
; r17:r16 = HEX value
2
; r20:r19:r18 = BCD value
3
hexToBcd:
4
    push    r16
5
    push    r17
6
    push    r21
7
    push    r22
8
    push    xl
9
    push    xh
10
    clr     r18
11
    clr     r19
12
    clr     r20
13
    clr     xh
14
    ldi     r21, 16
15
hexToBcd1:
16
    ldi     xl, 20 + 1
17
hexToBcd2:
18
    ld      r22, -x
19
    subi    r22, -3
20
    sbrc    r22, 3
21
    st      x, r22
22
    ld      r22, x
23
    subi    r22, -0x30
24
    sbrc    r22, 7
25
    st      x, r22
26
    cpi     xl, 18
27
    brne    hexToBcd2
28
    lsl     r16
29
    rol     r17
30
    rol     r18
31
    rol     r19
32
    rol     r20
33
    dec     r21
34
    brne    hexToBcd1
35
    pop     xh
36
    pop     xl
37
    pop     r22
38
    pop     r21
39
    pop     r17
40
    pop     r16
41
    ret
Ich komme leider nicht dahinter, wie der funktioniert. Kann mich jemand 
aufklären?

von Achim M. (minifloat)


Lesenswert?

Welchen Assembler hast du da? Welche Plattform/CPU?
Vielleicht hilft es dir, C als pseudocode neben die Anweisungen zu 
schreiben.
mfg mf

von Bruno M. (brumay)


Lesenswert?

Der Code war ganz allgemein für AVR. C hilft mir nicht weiter, da ich 
schon in ASM zu Hause bin.

von S. Landolt (Gast)


Angehängte Dateien:

Lesenswert?

Tietze/Schenk

von Peter D. (peda)


Lesenswert?

Bruno M. schrieb:
> There is a more efficient method "shift-plus-3".

Zahlenausgaben müssen ja auch wahnsinnig schnell erfolgen.
Typisch nimmt man eine Division / 10 mit Rest.
Ein Tempo von 3..5 Ausgaben ist ergonomisch, sonst sieht man nur 
Flackern.
BCD macht heute keiner mehr.

von Bruno M. (brumay)


Lesenswert?

S. Landolt schrieb:
> Tietze/Schenk

Danke für die Erklärung!
Das muß ich erst noch verdauen:-)

von S. Landolt (Gast)


Angehängte Dateien:

Lesenswert?

Hier meine Umsetzung des Algorithmus von Tietze/Schenk; ohne diesen 
indizierten Arbeitsregisterzugriff im "Internet...Code" - m.E. 
funktioniert dieser bei den neueren AVR8 nicht.

von Eberhard H. (sepic) Benutzerseite


Lesenswert?

Tietze/Schenk gibt nur das TI-Datenblatt SN74185 und zugehörige 
App-Notes wieder.

S. Landolt hat den dort beschriebenen Algorithmus in AVR-Assembler sehr 
elegant mit einer Sequenz von SBRS-Befehlen realisiert.

Dennoch könnte man auch hier noch zwei Dinge verbessern:

Für das oberste BCD-Register sind die Korrekturabfragen nicht nötig, da 
der Wert vor dem letzten Schieben maximal 3 ist. Das spart jeweils 3 
Befehle pro Schleifendurchgang (insgesamt 45 CPU-Zyklen).

Genau genommen muss man mit den Korrekturabfragen erst beginnen, nachdem 
die ersten drei Bits durch die Register geschoben wurden (und nicht 
schon ab dem ersten Bit).

Das benötigt zwar 10 zusätzliche Befehle (damit möglichst schnell), aber 
nur 14 statt 16 Schleifendurchläufe und ist dadurch insgesamt nochmals 
einige CPU-Zyklen schneller (falls das wichtig ist).

Eine solche Version würde in strukturiertem AVR-Assembler (s'AVR) so 
ausschauen (ohne Rettung der verwendeten Register):
1
Bin16_BCD5:                 ; von 16-Bit-Binär BIN1/BIN0 (2 AVR-Register beliebig)
2
                            ; nach BCD BCD4/BCD32/BCD10 (3 obere AVR-Register)
3
                            ; entsprechend 5 BCD-Nibbles BCD4:BCD3:BCD2:BCD1:BCD0
4
    clr     BCD10
5
    clr     BCD32
6
    clr     BCD4
7
8
    lsl     BIN0            ; immer durch alle 5 Register schieben, 1. Bit
9
    rol     BIN1
10
    rol     BCD10
11
    rol     BCD32
12
    rol     BCD4
13
14
    lsl     BIN0            ; 2. Bit
15
    rol     BIN1
16
    rol     BCD10
17
    rol     BCD32
18
    rol     BCD4
19
20
    ldi     COUNT,14        ; 14x LOOP (statt ursprünglich 16x) 
21
    LOOP
22
        lsl     BIN0        ; 3. Bit bis 16. Bit
23
        rol     BIN1
24
        rol     BCD10
25
        rol     BCD32
26
        rol     BCD4
27
        dec     COUNT
28
        EXITIF Z            ; nach dem letzten Schieben muss nicht mehr korrigiert werden,
29
                            ; da das niederwertigste Bit bei binär und BCD identisch ist
30
        subi    BCD10,-$33  ; auf Verdacht Addition BCD10 + 0x33 (gleich beide Nibbles)
31
        sbrs    BCD10,3     ; überprüfen, ob sich ein Überlauf beim 1. Nibble ergeben hat
32
        subi    BCD10,$03   ; Addition zurück, war nicht nötig bei BCD0
33
        sbrs    BCD10,7     ; überprüfen, ob sich ein Überlauf beim 2. Nibble ergeben hat
34
        subi    BCD10,$30   ; Addition zurück, war nicht nötig bei BCD1
35
        subi    BCD32,-$33  ; auf Verdacht Addition BCD32 + 0x33 (gleich beide Nibbles)
36
        sbrs    BCD32,3     ; überprüfen, ob sich ein Überlauf beim 1. Nibble ergeben hat
37
        subi    BCD32,$03   ; Addition zurück, war nicht nötig bei BCD2
38
        sbrs    BCD32,7     ; überprüfen, ob sich ein Überlauf beim 2. Nibble ergeben hat
39
        subi    BCD32,$30   ; Addition zurück, war nicht nötig bei BCD3
40
                            ; BCD4 muss für 16-bit-Binärzahlen nicht korrigiert werden, 
41
                            ; da der Wert vor dem letzten Schieben maximal 3 ist
42
    ENDL
43
    ret

Diese Variante läuft als Unterprogramm auf dem ATmega328 in 17,41 µsec 
@16MHz (inkl. rcall, ret und einem I/O-Befehl zum Messen per Scope). In 
der usprünglichen Version sind es 23,17 µsec. Diese Zeiten sind 
unabhängig von der zu konvertierenden Binärzahl.

Der vom TO erwähnte Code stammt von hier: 
https://www.avrfreaks.net/forum/16bit-binary-bcd (Post #4).

Dieser Code realisiert genau dasselbe Prinzip, nur deutlich 
umständlicher mit einem zusätzlichen Arbeitsregister und Indizierung der 
BCD-Register per xh:xl, die im Speicheradressbereich 0...31 liegen 
müssen.

Letzteres gilt nicht für alle 8-bit AVR, nur z. B. für sehr alte 
ATtiny13/24/44/84/26, aber auch für ATmega328 und ein paar weitere. Für 
andere müsste man nur xh:xl passend für die tatsächlichen 
Speicheradressen der verwendeten BCD-Register initialisieren (statt 
20+1).

Eine solche Indizierung erlaubt den Zugriff auf die BCD-Register in der 
inneren Schleife hexToBcd2, ohne dass man die Registernamen verwenden 
muss.

Man kann diese innere Schleife (wird nur 3x durchlaufen) aber auch 
entrollen und benötigt dann kein xh:xl mehr, aber immer noch das 
zusätzliche Register.

Generell ist dieser Code deutlich langsamer, da er alle BCD-Register 
unnötigerweise 16x per aufwendiger Korrekturabfrage durchläuft.

Natürlich gibt es deutlich schnellere Verfahren für die 8-bit-AVR, die 
dann aber auch etwas mehr Programmspeicher und zusätzliche Register 
benötigen.

von Axel S. (a-za-z0-9)


Lesenswert?

Hier meine handgeklöppelte bin2bcd Routine. Ist sogar 32 Bit / 8 Digits. 
Ein Großteil ist Housekeeping, um es aus C heraus aufrufen zu können. 
Für avr-gcc (-x assember-with-cpp)

Ab Label 1: läuft die Konversion, ab Label 2: die BCD-Korrektur.

1
/* void mybin2bcd(void *buf)
2
 *
3
 * buf[0..3] = bcd(buf[0..3])
4
 *
5
 * relies on the input not being bigger than 99999999 = 0x5f5e0ff
6
 *
7
 */
8
9
.global mybin2bcd
10
.func mybin2bcd
11
                
12
bcd0=           16      /* 4 byte bcd result */
13
bcd1=           17
14
bcd2=           18
15
bcd3=           19
16
                
17
bin0=           20      /* 4 byte binary argument */
18
bin1=           21
19
bin2=           22
20
bin3=           23
21
                
22
tmp=            27
23
                
24
mybin2bcd:
25
                /* save work registers */
26
                push bcd0
27
                push bcd1
28
29
                /* load 32 bits from arg1 */
30
                movw zreg, arg1
31
                ld bin0, z+
32
                ld bin1, z+
33
                ld bin2, z+
34
                ld bin3, z+
35
36
                /* initialize result */
37
                clr bcd0
38
                clr bcd1
39
                clr bcd2
40
                clr bcd3
41
42
                clr zreg+1
43
                ldi loop, 32
44
45
1:              lsl bin0
46
                rol bin1
47
                rol bin2
48
                rol bin3
49
                rol bcd0
50
                rol bcd1
51
                rol bcd2
52
                rol bcd3
53
                dec loop
54
                breq 3f
55
56
                /* decimal correction "Dreierkorrektur" */
57
                ldi zreg, bcd3+1
58
2:              ld tmp, -z
59
                subi tmp, -0x03
60
                sbrc tmp, 3
61
                st z, tmp
62
                ld tmp, z
63
                subi tmp, -0x30
64
                sbrc tmp, 7
65
                st z, tmp
66
                cpi zreg, bcd0
67
                brne 2b
68
                rjmp 1b
69
70
                /* store back bcd result */
71
3:              movw zreg, arg1
72
                st z+, bcd0
73
                st z+, bcd1
74
                st z+, bcd2
75
                st z+, bcd3
76
77
                /* restore work registers */
78
                pop bcd1
79
                pop bcd0
80
                ret
81
.endfunc

von S. Landolt (Gast)


Lesenswert?

Eberhard H. schrieb:
> Dennoch könnte man auch hier noch zwei Dinge verbessern

Danke für den Hinweis.
Ich hatte das vorgestellte nib4_BCD5 aus meinem nib5_BCD6 
extrahiert, und mir dabei offenbar nicht genug Mühe gegeben, hatte mich 
lediglich vergewissert, dass es noch korrekt läuft. Hauptgrund war, 
Bruno M. auf das eventuelle Problem mit dieser
> Indizierung der BCD-Register per xh:xl
hinzuweisen.

Danke auch an Axel S., ich werde mir die Verfahren/Varianten bei 
nächster Gelegenheit genauer anschauen.

von Peter D. (peda)


Lesenswert?

Axel S. schrieb:
> Ein Großteil ist Housekeeping, um es aus C heraus aufrufen zu können.

Da würde mich mal der recht spezielle Anwendungsfall interessieren, wo 
man das gewohnte und komfortable printf nicht mehr verwenden kann.

von Eberhard H. (sepic) Benutzerseite


Lesenswert?

Axel S. schrieb:
> Hier meine handgeklöppelte bin2bcd Routine. Ist sogar 32 Bit / 8 Digits.

Das ist im Prinzip die vom TO zitierte Variante für mehr Stellen, jedoch 
mit den Binär- und BCD-Werten komplett im SRAM (typunabhängig 
programmiert) und die äußere Schleife rechtzeitig abgebrochen.

Allerdings sind auch hier die Korrekturadditionen nicht ganz so elegant 
ausgeführt wie bei der Version von S. Landolt.

Leicht getunt könnte es dann so ausschauen (Kommentare siehe oben):
1
         /* decimal correction "Dreierkorrektur" */
2
         ldi  zreg, bcd3+1
3
2:       ld   tmp, -z
4
         subi tmp, -0x33
5
         sbrs tmp, 3
6
         subi tmp, 0x03
7
         sbrs tmp, 7
8
         subi tmp, 0x30
9
         st   z, tmp
10
         cpi  zreg, bcd0
11
         brne 2b
12
         ...

Axel S. schrieb:
> relies on the input not being bigger than 99999999 = 0x5f5e0ff

Bei dieser Einschränkung würde
1
        ldi loop, 27 ; statt 32
ausreichen. Oder man spendiert besser gleich 5 BCD-Bytes für alle 32 
Bits.

Peter D. schrieb:
> Da würde mich mal der recht spezielle Anwendungsfall interessieren, wo
> man das gewohnte und komfortable printf nicht mehr verwenden kann.

Bruno M. schrieb:
> C hilft mir nicht weiter, da ich
> schon in ASM zu Hause bin.

Oder wenn man für C zu wenig Programmspeicher hat, denn printf frisst 
reichlich davon.

von Peter D. (peda)


Lesenswert?

Hier noch die Subtraktionsmethode für 16bit zu ASCII:
Beitrag "Re: effiziente Division durch 10"

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

https://www.mikrocontroller.net/articles/AVR_Arithmetik#Addition_von_.2433
Ich habe das Verfahren damals (13.7.2010 laut Versionsgeschichte) 
"Addition von $33" genannt.

Hier wurde es schon 1999 angewendet, etwa auf der Hälfte der langen 
Seite:
" Bin4BCD == 32-bit Binary to BCD conversion  [ together with Bin2BCD ]"
https://avr-asm.tripod.com/math32x.html

AVR-Assemblercode für zwei BCD-Stellen:
subi  tBCD0,-0x33  ; add 0x33 to digits 1 and 0
sbrs  tBCD0,3   ; if bit 3 clear
subi  tBCD0,0x03  ; sub 3
sbrs  tBCD0,7   ; if bit 7 clear
subi  tBCD0,0x30  ; sub $30

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

Peter D. schrieb:
> Axel S. schrieb:
>> Ein Großteil ist Housekeeping, um es aus C heraus aufrufen zu können.
>
> Da würde mich mal der recht spezielle Anwendungsfall interessieren, wo
> man das gewohnte und komfortable printf nicht mehr verwenden kann.

Das ist ein Teil der Frequenzzählermodul Firmware. sprintf() hätte 
den Programmspeicher des ATMega8 gesprengt. Klar könnte man einen 
ATMega328 nehmen, aber wo bleibt denn da der sportliche Gedanke?

Die Vorlage für den Z8 (U882) paßt übrigens in 512 Bytes.


Eberhard H. schrieb:
> Axel S. schrieb:
>> relies on the input not being bigger than 99999999 = 0x5f5e0ff
>
> Bei dieser Einschränkung würde        ldi loop, 27 ; statt 32
> ausreichen. Oder man spendiert besser gleich 5 BCD-Bytes für
> alle 32 Bits.

Stimmt auch wieder. Aber die Anwendung ist nicht zeitkritisch.
Und die Einschränkung kann man als gegeben ansehen.

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

>BCD macht heute keiner mehr
Zur Umwandlung in ASCII zur Ausgabe einer Binärzahl auf einem 
Textdisplay ist das ein Zwischenschritt vor dem OR 0x30

von Eberhard H. (sepic) Benutzerseite


Lesenswert?

Eberhard H. schrieb:
> Bei dieser Einschränkung würde
1
        ldi loop, 27 ; statt 32
> ausreichen.

Das ist natürlich so nicht richtig (sorry für die Verwirrung), denn es 
müssen insgesamt immer alle 32 Bit geschoben werden, auch wenn die 
Binärzahl <2^27 ist. Es gehen mangels ausreichend Speicher/Register im 
vorliegenden Fall halt nur die oberen beiden BCD-Nibbles verloren.

Das bringt mich aber bei so vielen Registern auf eine neue Idee: Man 
könnte die 32 Bit zunächst solange nur in das erste BCD-Arbeitsregister 
(BCD0) schieben bis dort eine Eins in Bit 2 auftaucht. Erst dann geht es 
mit den Korrekturadditionen und dem Schieben aller Register weiter, bis 
alle 32 Bit abgearbeitet sind.

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.