Forum: Mikrocontroller und Digitale Elektronik 16-Bit Zahl durch eine Konstante dividieren (Assembler)


von Anton (Gast)


Lesenswert?

Hallo Leute,
ich bräuchte für Umrechnung der Entfernung in cm eine Divisionsroutine, 
die mir eine 16-bit Zahl durch eine konstante 8-Bit Zahl dividieren 
würde (genauer gesagt ist diese Konstante 58).
Ich programmiere in Assembler, auf dem Atmega8.
Habe sehr viele Beiträge bereits gelesen, konnte jedoch keine passende 
Lösung finden.
mfg Anton

von Pitz (Gast)


Lesenswert?

Von Atmel gibts ne Appnote für Divisionen/Multiplikationen. Schau dir 
die an, entsprechende Routinen sind dabei (in ASM).

von Peter D. (peda)


Lesenswert?

Da der ATmega einen Mul-Befehl hat, ist es einfacher mit dem Reziproken 
zu multiplizieren und das Ergebnis entsprechend nach rechts zu schieben.


Peter

von Anton (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Da der ATmega einen Mul-Befehl hat, ist es einfacher mit dem Reziproken
> zu multiplizieren und das Ergebnis entsprechend nach rechts zu schieben.
>
>
> Peter

könntest du das bitte näher erläutern, habe es nicht so ganz verstanden, 
wie es mit mul Befehl gehen soll ..
mfg Anton

von hsb (Gast)


Lesenswert?


von Peter D. (peda)


Lesenswert?

Anton schrieb:
> könntest du das bitte näher erläutern

1/58 * 65536 = 1130

D.h. mit 1130 multiplizieren und um 16 Bits nach rechts schieben.


Peter

von ... (Gast)


Lesenswert?

Anton schrieb:
> habe es nicht so ganz verstanden, wie es mit mul Befehl gehen soll ..

* 565 shr 15

von eProfi (Gast)


Lesenswert?

Du dividierst zuerst durch 64 und addierst einen Korrekturfaktor.

z.B.
10000/58=172,413
10000/64=156   156,25/8=19  /8=2  /4=0 /2=0
156+19-2-0-0=173

anderes Beispiel:
65536/58=1129,931
65536/64=1024   /8=128  /8=16  /4=4  /2=2
1024+128-16-4-2=1130


Oder wie Peter beschrieben hat, nur statt shr16  vom 32-bit-Ergebnis nur 
das upper-word verwenden.


Abhängig vom Eingangsbereich kannst Du vorher noch erweitern, um besser 
runden zu können.

von Martin (Gast)


Lesenswert?

Es ginge auch ganz klassisch:
Vom Dividend solange den Divisor abziehen und mitzählen bis das 
Subtraktionsergebnis negativ wird. Dann wieder einen Schritt rückgängig 
machen und du hast dein Ergebnis + Rest.

von Benjamin S. (recycler)


Lesenswert?

8bit = 256 Werte
Wenn du genügend Speicher dafür hast, dann kannst du es auch über eine 
Lookuptabelle machen. Ist schneller, erfordert aber Vorarbeit.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Anton schrieb:
> Peter Dannegger schrieb:
>> Da der ATmega einen Mul-Befehl hat, ist es einfacher mit dem Reziproken
>> zu multiplizieren und das Ergebnis entsprechend nach rechts zu schieben.
>>
>> Peter
>
> könntest du das bitte näher erläutern, habe es nicht so ganz verstanden,
> wie es mit mul Befehl gehen soll ..
> mfg Anton

Einfach nem guten C-Compiler bei der Arbeit zuschauen ;-)
 
1
unsigned int div58 (unsigned int x)
2
{
3
    return x/58;
4
}
 
übersetzt  avr-gcc zu (r24 = r24 / 58):
 
1
div58:
2
  movw r18,r24   ;  6  *movhi/1  [length = 1]
3
  ldi r26,lo8(-97)   ;  7  *movhi/5  [length = 2]
4
  ldi r27,lo8(70)
5
  call __umulhisi3   ;  8  *umulhi3_highpart_call  [length = 2]
6
  swap r25   ;  25  *lshrhi3_const/5  [length = 6]
7
  swap r24
8
  andi r24,0x0f
9
  eor r24,r25
10
  andi r25,0x0f
11
  eor r24,r25
12
  ret   ;  24  return  [length = 1]
 
Für __umulhisi3 siehe

http://gcc.gnu.org/viewcvs/trunk/libgcc/config/avr/lib1funcs.S?view=markup#l357

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Und falls es allgemeiner sein soll und langsamer sein darf:
1
#define VAL     24
2
#define REST    22
3
#define CNT     20
4
#define DIVISOR 21
5
6
;; REST <- VAL % DIVISOR
7
;; VAL  <- VAL / DIVISOR
8
9
udivmod16_8:
10
    clr     REST
11
    ldi     CNT,    16
12
13
2:  lsl     VAL
14
    rol     VAL+1
15
    rol     REST
16
    cp      REST, DIVISOR
17
    brlo 3f
18
    inc     VAL
19
    sub     REST, DIVISOR
20
3:  dec     CNT
21
    brne 2b
22
    ret

von Jürgen (Gast)


Lesenswert?

Meine Version.
Die 10 unten in 58 ändern

1
;--------------------------------------------------------------------------------
2
  ;    16Bit Division durch 8Bit Konstante. In diesem Fall 10.
3
  ;    Übergaberegister & Ergebniss in temp2/temp3 (low/high), Rest = temp1
4
  ;--------------------------------------------------------------------------------
5
div:ldi    temp4,16    ;16* schieben
6
  clr    temp1      ;Rest =0
7
dv1:lsl    temp2      
8
  rol    temp3
9
  rol    temp1
10
  cpi    temp1,10    ;Solange Bits in temp1 (Rest) schieben bis >= 10
11
  brcs  dv2        ;Wenn der Rest <10 ist, das 0-Bit in temp2 (LSL, oben) stehen lassen
12
  inc    temp2      ;Ansonsten Bit0 setzen und 10 vom Rest abziehen
13
  subi  temp1,10    
14
dv2:dec    temp4      ;Das ganze 16 mal
15
  brne  dv1  
16
  ;-------------------------------------------------
17
  ;        Ende Div Routine.

von Thomas G. (tomatos666)


Lesenswert?

Hallo,

sorry das ich das alte Thema ausgrabe.
Der Code von Jürgen Funktioniert aber nur mit Werten unter 100 oder?
Mit 158 kommt bei mir ein Falsches Ergebis raus nämlich statt 414 kommt 
388.

von Peter D. (peda)


Lesenswert?

Thomas Gruber schrieb:
> Der Code von Jürgen Funktioniert aber nur mit Werten unter 100 oder?

Nur bis 127.
Willst Du bis 255, mußt Du noch den Übertrag bei "rol temp1" 
berücksichtigen.

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.