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?
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.
Hier meine Umsetzung des Algorithmus von Tietze/Schenk; ohne diesen
indizierten Arbeitsregisterzugriff im "Internet...Code" - m.E.
funktioniert dieser bei den neueren AVR8 nicht.
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)
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.
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
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.
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.
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.
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.htmlAVR-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
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.
> 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.