Forum: Mikrocontroller und Digitale Elektronik Ansatz zum rechnen in Assembler gesucht...


von AVRli (Gast)


Lesenswert?

Hi,

ich habe hier im Forum mal geschaut was ich so finde was das rechnen in
Assembler anbelangt.
Nur irgendwie passt nichts glaube ich...
Kann die Verbindung zu meinem Problem nicht herstellen.

Deshalb schildere ich mal was ich machen will...

Der Bereich den ich habe ist 0-1024 und möchte das nun auf 0-450
bekommen.

Also in Delphi, wo ich eigendlich zu Hause bin, würde ich es so
machen.

1024 / 450 * x

ok 1024 / 450 kann man als Konstante hinterlegen weil das ja "gleich"
bleibt.
In dem Fall währe sie 2,276

Und da wird es jetzt "spannend"... wie macht man das denn am
einfachsten in Assembler ?

Nimmt man die Werte alle erstmal mal 100 damit man das Komma
wegbekommt?
Oder geht es irgendwie leichter.

Gruß AVRli...

von Christof Krüger (Gast)


Lesenswert?

Ist es denn unbedingt nötig, dass es von 0-450 ist? Warum nicht 0-512?
;)

von AVRli (Gast)


Lesenswert?

Ja es gibt sogar noch ein weterer Bereich den ich berücksichtigen muss.
0-360...

Ein "Poti" als Himmelsrichtungszeiger... also es soll die Gradanzahl
angezeigt werden.

Da ich auch lernen will wie man richtig rechnet in Assembler fragte
ich.

Gruß AVRli

von brulli (Gast)


Lesenswert?

warum nimmst du nicht einfach avr-gcc. Dann ist das problem schon
gelöst.

brulli

von Rufus T. Firefly (Gast)


Lesenswert?

Was Du durchführen möchtest, nennt man Division. Sofern Du keinen
Prozessor mit floating-point-Unterstützung verwendest, wirst Du mit
Ganzzahlarithmetik auskommen müssen.

Einige Prozessoren unterstützen Operationen wie Division und
Multiplikation, andere tun das nicht, so daß man für diese
Grundoperationen schon Funktionen schreiben muss.

Du müsstest also schon erwähnen, um welchen Prozessor es Dir geht.

(avr-gcc hat nur dann einen Sinn, wenn der betreffende Prozessor ein
AVR ist und der betreffende Anwender C programmieren könnte. Da das
Wort "Delphi" auftauchte, ist zumindest letzteres nicht sicher.)

von Khani (Gast)


Lesenswert?

Hallo AVRli,

Wenn Du Informationen zum Rechnen (interessant sind ja wohl
hauptsächlich Multiplikation und Division) suchst, dann kannst Du Dich
ja mal bei www.atmel.com nach der Application-Note zu dem Thema
umschauen - die ist ganz nett, ich habe sie auch schon verwendet.

MfG, Khani

von Toto (Gast)


Lesenswert?

Schau dir mal diese Seite an
http://www.avr-asm-tutorial.net/avr_de/rechnen/index.html
Recht gute Erklärung.

von AVRli (Gast)


Lesenswert?

Hi,

ja sorry es ist der ATMEGA8 den ich verwende...
Mit den Codebeispielen muss ich mich mal beschäftigen.

Ich hoffe es geht irgendwie "leichter"...

Auf der Assembler Ebene würde ich unbedingt bleiben wollen.
In BASCOM oder C geht das bestimmt mit einer Zeile...

Danke für die Infos...

von Hagen (Gast)


Lesenswert?

>Nimmt man die Werte alle erstmal mal 100 damit man das Komma
>wegbekommt?

Du solltest sie mal 256 oder 65535 multiplizieren, das macht es
einfacher.Das was du suchst nennt sich Fest-Komma-Zahlen. Bei * 256
wäre das Komma also bei Bit 8. Somit ist dein Zahlen bereich von 1024 *
256 bis 0, wenn du ohne Vorzeichen arbeiten willst. Du müsstest also
eine 24 Bit Division durchführen, 16Bit für den Vorkommateil und 8 Bit
für den gebrochenen Teil. Die Auflösung im Nachkommteil wäre dann 1/256
= 0.004.

Also

  X = (1024 * 256) / 450 = 582
  X div 256 = 2
  X mod 256 = 70

somit
  V = 345 * (2 * 256 + 70) = 200790
  200790 div 256 = 784

  345 * (1024 / 450) = 785.0667

Nimmst du aber 16Bit Vorkomma +16Bit Nachkomma so wäre

  (1024 * 2^16) / 450 = 149131

  345 * 149131 = 51450195
  51450195 div 2^16 = 785.0676

Du arbeitest also am besten mit 32Bit Multiplikation und Divisione und
speicherst den Wert 149131 als Fixpoint Konstante für 1024 / 450. Den
Input von 345 einfach damit Multiplizieren und das Resultat durch 2^16
dividiren per virtuellem Rechtsshift (du wertest also nur du 2 MSB's
des 32Bit Resultates aus).

Gruß Hagen

von Gunter (Gast)


Lesenswert?

Hi,

zumindest, falls Du nicht weiterkommst, kannst Du auch
folgendes versuchen:

besorg Dir die Demo des Pascal AVRco Compilers,
http://www.e-lab.de
und schreibe Dir eîn minimales Programm mit Division.
Nach dem Übersetzen hast Du auch ein sehr gut lesbares
Assembler-Listing wo Du die eigentliche Division sehr leicht
finden kannst.

hth
Gunter

von Hagen (Gast)


Lesenswert?

Nochmal einfacher erklärt:

1.) du berechnest mit dem Taschenrechner deine Konstanten, also
Round(1024 * 2^16 / 450) = 149131
2.) diese Konstanten speicherst du fest in dein Program als 32Bit
Zahlen
3.) willst du nun X = Input * (1024 / 450) rechnen so brauchst du nur
eine 32Bit Multiplikation im Code ausführen mit der gespeicherten
Konstanten. Also X = Input * Konstante. In X stehen in den obersten
16Bits der Vorkommateil und in den unteren 16Bit der gebrochene Teil.
4.) falls der gebrochene Teil >= 2^(16 -1) ist so wird normalerweise
der 16Bit Vorkommateil um +1 erhöht. Dies entspricht einer Rundung. Im
Vorkommateil steht dein gesuchtes Resultat.

Du benötigst also nur eine 32Bit Multiplikation und fertig.

Gruß hagen

von Hagen (Gast)


Lesenswert?

Um sauber zu runden musst du also nach der 32Bit Multiplikation einfach
2^15 auf das Resulat addieren. Die obersten 2 Bytes sind dann das
korrekte Resultat.

Gruß Hagen

von AVRli (Gast)


Lesenswert?

Hallo,

ich habe mir die Appnotes
AVR202 16-Bit Arithmetics und
AVR204 BCD Arithmetics geladen und nix verstanden... hahaha...

Mir fehlen die Grundkenntnisse beim rechnen in Assembler so wie ich das
sehe.

Das wird noch. :-)

@Hagen
Genial der Ansatz...

>Du solltest sie mal 256 oder 65535 multiplizieren,
>das macht es einfacher.

Ok mal 256 sollte dicke reichen.

>Das was du suchst nennt sich Fest-Komma-Zahlen.

GLEICH MERKEN !!! ;-)

>Bei * 256 wäre das Komma also bei Bit 8.
>Somit ist dein Zahlen bereich von 1024 * 256 bis 0,
>wenn du ohne Vorzeichen arbeiten willst.

Ja erstmal ohne Vorzeichen...
Möchte dann doch erstmal "kleine Brötchen" backen...

262144-0 -> 1 Grad entspricht dann 728 (bei 360° Ausgang).

>Du müsstest also eine 24 Bit Division durchführen,
>16Bit für den Vorkommateil und 8 Bit für den gebrochenen Teil.
>Die Auflösung im Nachkommteil wäre dann >1/256 = 0.004.

Ich danke...
das sollte mich weiter bringen...

Hagen, dein LED Projekt unter Codesammlung ist ja wohl voll genial!
Das bau ich mir mal um zu spielen.
Aber erst ein Projekt fertig machen.

Gruß AVRli

von Hagen (Gast)


Lesenswert?

Ähm, danke.

Falls du keine Multiplikation in Assembler hinbekommst, obwohl ja in
den Application Notes schon fertige Sourcen für 32Bit drinnen sind, so
könntest du ja per Schleife dividieren.
Dein Input wird dann * 256 expandiert. Nun subtrhierst du davon solange
728 bis der Input <= 0 wird. Die Anzahl der Schleifendurchläufe ergibt
dann deinen gesuchten Wert.

Das LED Projekt ist schön und gut, hat aber einen Nachteil: du kannst
keine Treiber zwischenschalten damit du den LED Strom erhöhen kannst.
Und die 200mA maximal pro AVR begrenzt die ganze Geschichte auf 10 LEDs
on maximal.

Gruß Hagen

von AVRli (Gast)


Lesenswert?

Hi Hagen,

ok soweit.
Meinen Input mal 256 nehmen.
Macht man das in dem man auch in einer Schleife immer
input:=input+input macht oder geht da was mit "schieben".

Das mit dem Teilen würde ich hinbekommen.

Fertige Routinen haben einen Nachteil... ;-(
Da steigt man erst dahinter wenn man weiß was passieren soll.

Gruß AVRli...

von Hagen (Gast)


Lesenswert?

Die Multiplikation ist eigentlich einfach. Ich versuche dir das mal zu
erklären. Als erstes müssen wir ausrechnen wie groß unsere
Zahlenbereiche eigentlich wären, das bestimmt nämlich wieviele Bytes
unsere Zahlen groß sein müssen und wie der Algo. auszusehen hat.

Also auf den neueren AVRs wird die 8x8 Mul unterstützt, d.h. per
Hardware kann man zwei Bytes miteinander multiplizieren und bekommt ein
Word = 16 Bit Wert raus. Wollen wir größere Zahlen multiplizieren müssen
wir also die gesammte Multiplikation aus diesen kleinen 8x8 Muls
zusammenbauen.

Nun zu den Zahlenbereichen. Du sagtest 8 Bit Nachkommagenauigkeit
reicht dir. Also schon mal 1 Byte.Gehen wir davon aus das im Vorkomma
ebenfalls nur 1 Byte benutzt werden soll so hiese dies das der Divisor
der Konstanten (1024 / x) X eben >= 4 sein muß. Ich schätze mal das
dürfte gegeben sein. Sprich deine Konstanten sind ja 360 und 450, also
größer als 4. Somit benötigen unsere Fixpoint Konstanten nur 2 Bytes an
Speicher, ergo wir Multiplizieren eine X Bit Zahl mit einer 16 Bit
Festkommazahl. Nun müssen wir ermitteln wie groß denn X in Bits sein
könnte. Da 1024 deine obere Grenze wäre sind die Inputs <= 1024, ergo 2
Bytes. D.h. wir müssen 2 16Bit Zahlen miteinander Multiplizieren und
bekommen ein 32Bit Resultat.

Die unterste Schranke für die Konstanten ist
(1024 * 256) / 4 = 65536 -> [256, 0] in den 2 Bytes, da aber 256 im
Vorkomma > 255 ist überschreitet dies unseren Wertebereich. Wir wählen
also 5 als minimalsten Wert für die Konstante.
Die oberste Schranke wäre dann
(1024 * 256) / 1024 = 256 -> [1, 0] in den 2 Bytes. Logisch es muß 1.0
sein.

Nun die Schranken vom Input:
1024 * (1024 * 256 / 4) = [4, 0, 0, 0] also 4 Bytes für das Resultat
sind nötig !

Wir benötigen also tatsächlich eine 16Bit * 16Bit = 32Bit
Multiplikation.

Dazu wird nun die 16Bit Multiplikation mit jeweils den obersten 8Bit
und den untersten 8 Bit des Multiplikaten multipliziert. Es entstehen
also 2 mal eine 16Bit * 8Bit Multiplikation. Da aber der AVR nur 8x8
Muls besitzt müssen wir also diese beiden 16x8 Muls nochmals zerlegen.
Insgesammt benötigen wir also 4 mal eine 8x8 Mul.

Als Beispiel imDezimalen Zahlenbereich multiplizieren wir mal

 12 * 34 auf einem Blatt Papier, die Schulmethode wäre dies.

   12 * 34

    2 *  4
+  1  *  4
+  2  * 3
+ 1   * 3
-----------
    8
+  4
+  6
+ 3
----------
  408

Exakt so bauen wir unsere 16x16 Bit Mul zusammen, mit dem Unterschied
das wir als Zahlenbasis nicht 10 sondern 256 = 1 Byte benutzen.


I0 = r16 = Input Low
I1 = r17 = Input High
K0 = r18 = Konstante Low
K1 = r19 = Konstante High
T0 = r20 = Resultat LSB 0
T1 = r21 = Resultat LSB 1
T2 = r22 = Resullat MSB 0
T3 = r23 = Resultat MSB 1

Zero = r24 = 0

die Mul im AVR gibt ihr 16Bit Resultat in [r1:r0] zurück

  clr   Zero

  mul   I0, K0

  mov   T0, r0
  mov   T1, r1

  mul   I1, K1

  mov   T2, r0
  mov   T3, r1

  mul   I0, K1

  add   T1, r0
  adc   T2, r1
  adc   T3, Zero

  mul   I1, K0

  add   T1, r0
  adc   T2, r1
  adc   T3, Zero

so T = I * K, unser 32 Bit Resultat.

Nun noch runden

  add  T0, 0x80
  adc  T1, Zero
  adc  T2, Zero
  adc  T3, Zero

In T steht jetzt eine 24Bit Vorkomma + 8Bit Nachkommazahl gerundet.
Dich interessiert aber nur noch T1 bis T3.

Gruß Hagen

von AVRli (Gast)


Lesenswert?

Klasse!!! ;-D

Ich drucke mir das gerade mal aus...
Ich denke das werde ich begreifen!

Ich melde mich wieder.
Vielen Dank für die ausführliche Beschreibung!
Vielleicht steigt man ja damit auch hinter die APPNOTES.

Gruß AVRli...

von Hagen (Gast)


Lesenswert?

Es gibt natürlich noch einige Optimierungen, sprich man kann Register
einsparen und sogar Multiplikationen.

clr  T0

mul  I0, K0
mov  T0, r1
ldi  T1, 0x80
add  r0, T1
clr  T1
adc  T0, T0
adc  T1, T1

mul  I1, K0
add  T0, r0
adc  T1, r1

mul  I0, K1
add  T1, r0


In T1:T0 stände nun das gerundete 16 Bit Resultat. Dazu muß aber eben
sichergestellt werden das Input <= 1024 und die Konstante = Dividend >
16 ist.

d.h. 1024 / X * Input, wobei X > 16 und Input <= 1024.

Denn 1024 / 16 = 64 und 64 * 1024 = 256 * 256, somit wäre die 2 Bytes
Grenze für das Resultat überschritten.

Mit obigem Code solltest du also hinkommen. Falls X > 16 und Input <=
1024.

Gruß Hagen

von Hagen (Gast)


Lesenswert?

Shit natürlich ein Fehler drinnen, sorry ;)



mul  I0, K0
mov  T0, r1
ldi  T1, 0x80
add  r0, T1
clr  T1
adc  T0, T1
adc  T1, T1

mul  I1, K0
add  T0, r0
adc  T1, r1

mul  I0, K1
add  T0, r0
adc  T1, r1

mul  I1, K1
add  T1, r0


Gruß Hagen

von AVRli (Gast)


Lesenswert?

Hi Hagen,

lass uns mal bei dem Beispiel bleiben ;-)

.nolist
.include "D:\Atmel\AVR Tools\AvrAssembler\Appnotes\m8def.inc"
.list

.def wrH = r25
.def I0 = r16 ; Input Low
.def I1 = r17 ; Input High
.def K0 = r18 ; Konstante Low
.def K1 = r19 ; Konstante High
.def T0 = r20 ; Resultat LSB 0
.def T1 = r21 ; Resultat LSB 1
.def T2 = r22 ; Resullat MSB 0
.def T3 = r23 ; Resultat MSB 1
.def Zero = r24 ; 0

;* INTERRUPT EINSPRUNGADRESSEN FÜR DEN AVR AT90S8515
.cseg
.org 0
  rjmp  Initial  ;RESET

;* INITIAL ABSCHNITT
Initial:
  ldi wrH,High(RamEnd)
  out sph,wrH
  ldi wrH,Low(RamEnd)
  out spl,wrH      ; Stack initialisiert

; die Mul im AVR gibt ihr 16Bit Resultat in [r1:r0] zurück
  clr Zero
  ldi I0,low(512)
  ldi I1,high(512)

;  ldi I0,low(725)
;  ldi I1,high(725)

  ldi K0,low(255)
  ldi K1,high(255)

  mul I0, K0

  mov T0,r0
  mov T1,r1

  mul I1,K1

  mov T2,r0
  mov T3,r1

  mul I0,K1

  add T1,r0
  adc T2,r1
  adc T3,Zero

  mul I1,K0

  add T1,r0
  adc T2,r1
  adc T3,Zero

;so T = I * K, unser 32 Bit Resultat.
;Nun noch runden

  ldi wrH, $80 ; DEZ 128
  add T0, wrH
  adc T1, Zero
  adc T2, Zero
  adc T3, Zero

; T3 T2 T1 T0
; 00 01 FE 80


Hm irgendwas stimmt hier nicht... eine Zuweisung auf die T's...? :-I
also richtig währe...

01FE00 80

was ist passiert? Die Werte ansich stimmen ja... ;-)
Ich probiere mal noch nen bischen...

Gruß AVRli...

von Hagen (Gast)


Lesenswert?

ldi I0,low(512)
  ldi I1,high(512)

Input also 1024

  ldi K0,low(255)
  ldi K1,high(255)

Die Konstante ist 1024/1028 ?? das ist doch nicht richtig oder ?

Round(1024/1028 * 256) * 512 + $80 = $0001FE80 / 256 = $01FE = 510 =
1024/1028 * 512 == 510.


256 * 1024 / 255 = 1028.

Stimmt also alles, nur deine Konstante ist falsch oder eben richtig, je
nachdem wie man es betrachtet ;)

Gruß Hagen

von Hagen (Gast)


Lesenswert?

Nehmen wie mal 360 also Konstante und als Input 345.

K = 1024/360 * 256 = 728 = $02D8
I = 345 = $0159

raus kommt

$03D518 + $80 = $03D598 div 256 = $03D5 = 981.

zur Überprüfung

1024/360 * 345 = 981.3333

Gruß Hagen

von AVRli (Gast)


Lesenswert?

Ohh neee... peinlich'st...
Ich nimm alles zurück und behaupte das Gegenteil...  ;-D

Klasse!
Jaja einer zählt von 0 der andere von 1...

Hagen handshakeDANKE ;-)

Gruß AVRli...

von Quark (Gast)


Lesenswert?

Hi,

@Hagen
 1000 Dank, sehr schöne Anleitung, wäre toll für
 Codesamlung/Wiki

Grüße

Quark

von Stefan (Gast)


Lesenswert?

Moment mal. Du hast einen 10 bit Wert (0 - 1024) mit dem ADC eingelesen
und willst den nun umrechnen, so dass du einen Wert zwuschen 0 und 450
erhälst den du dann auf einem Display anzeigen kannst?
Dann wäre das doch aber 450 / 1024 * x und nicht umgekehrt oder habe
ich da jetzt was falsch verstanden.
Sagen wir mal das Display hat 4 Stellen. 3 vor dem Komma und eine
danach.
Dann wäre die einachste Lösung wohl
1. multiplikation der 10bit Zahl mit 45.
Das ergibt nen 16bit Wert
2. MSB des Ergebnis auf den ersten beiden Stellen des Displays
anzeigen.
3. LSB mit 100 multiplizieren.
4. MSB des Ergebnis auf den letzten beiden Stellen anzeigen.

3 und 4 kann man um Rechenzeit in der Ausgaberoutine zu sparen auch
noch folgendermassen abändern.

3. LSB mit 10 multiplizieren.
4 MSB des Ergebnis ist jetzt kleiner als 10 und kann daher mit deutlich
weniger Rechenaufwand auf der 3. Stelle ausgegeben werden.
5. LSB des neuen Ergebnisses wieder mit 10 multiplizieren und MSB des
Ergebnis auf Stelle 4 ausgeben.

von Hagen (Gast)


Lesenswert?

oben schrieb er:

"
Der Bereich den ich habe ist 0-1024 und möchte das nun auf 0-450
bekommen.
Also in Delphi, wo ich eigendlich zu Hause bin, würde ich es so
machen.
1024 / 450 * x
"

womit Stefan wohl recht hat. Wenn du Zahlen im Bereich 0-1024 in den
Bereich 0-450 oder 0-360 übersetzen willst so muß es tatsächlich

450 / 1024 * x heisen.
Bei meinem Ansatz spielt das nur insofern eine Rolle das man nun
versuchen sollte die Bitanzahl der Nachkommastellen gegenüber den
Vorkommastellen zu erhöhen. Statt 16,8Bit also 8,16Bit um genügend
Genauigkeit zu bekommen. Grundsätzlich kann man aber obige
Multiplikation denoch benutzen, nur die Konstante ändert sich.

@Stefan, so ganz konnte ich deinen Ausführungen nicht folgen. Primär
wurde doch nur ein einfacher Weg gesucht um linear zwischen den
Zahlenbereichen zu konvertieren, und nichts auf einem Display
auszugeben.

Gruß hagen

von AVRli (Gast)


Lesenswert?

Hi,

ich hab's soweit... ;-) grinsbisüberbeideohren

@Stefan
hast recht mit der Formel... war mein Fehler... ;-)
In der Tat will ich das Ergebniss auf einen Display ausgeben.
Vielleicht gibt es einen einfacheren Weg, kann schon sein.
Ich habe mir Deinen Lösungsansatz auch gespeichert.
Ich muss mit den Zahlen aber noch weiter rechnen.
Ich denke es ist über die "Hagen" Methode einfacher.
Ich kann mich aber auch irren.

@Hagem
Ich freue mich das ich gelernt habe mit Fest-Komma-Zahlen zu rechnen.
Denn vor diesem "Problem" wird man wohl noch öffter stehen.

Eine Frage zu den T's habe ich noch...
Ich Dividiere nun wie folgt...

SUB_AGAIN:
  subi T0,Low(728)
  sbci T1,Byte2(728)
  sbci T2,Byte3(728)
  sbci T3,Byte4(728)

  brlt SUB_FINISH

  ldi wrH,1
  add ZL, wrH
  ldi wrH,0
  adc ZH, wrH

  rjmp SUB_AGAIN
SUB_FINISH:

Das klappt soweit gut...

Du schreibst...
> In T steht jetzt eine 24Bit Vorkomma + 8Bit Nachkommazahl gerundet.
> Dich interessiert aber nur noch T1 bis T3.

Heist das das ich nun mit der Nachkommazahl rechne?
Wenn ja wie läst man die zur "probieren" mal weg?

Gruß AVRli...

von Chris (Gast)


Lesenswert?

siehe auch hier einige fertige Unterprogramme:
http://mirror01.users.i.com.ua/~birua/math32.html

Gruß

von Stefan (Gast)


Lesenswert?

Ich hatte in meiner Beschreibung einen Fehler.
Das Ergebnis der Multiplikation mit 45 müsste zuerst noch um 2 Stellen
nach rechts geschoben werden ( mit lsr und ror ).

Ich glaube die Methode erklärt man am besten an einem Beispiel. Ich
habe dort aber statt mit 45 mit 4.5 multipliziert.
Gehen wir mal davon aus der 10bit Wert befindet sich in r17:r16
und die 4 Stellen des Ergebnisses - davon ist die 4. die
Nachkommastelle - werden in r16 - r19 gespeichert.
Auf Rechenzeit optimiert könnte das so aussehen

.include "m8def.inc"

  ldi r16, LOW(897)
  ldi r17, HIGH(897) ; r17: r16 enthält jetzt den Wert 897

  ldi r22, 9
  mul r17, r22
  mov r21, r0
  mul r16, r22
  mov r20, r0
  add r21, r1  ;r21:r20 = r17: r16 * 9
  lsr r21
  ror r20    ;r21:r20 ist nun gleich r17:r16 * 4.5

  mov r16, r21
  lsr r16
  lsr r16    ; r16 enthält jetzt die erste Stelle

  ldi r22, 0b00000011
  and r21, r22

  ldi r22, 10
  mul r21, r22
  mov r21, r0
  mul r20, r22
  add r21, r1
  mov r20, r0

  mov r17, r21
  lsr r17
  lsr r17    ; r17 enthält nun die zweite Stelle

  lsr r21
  ror r20
  lsr r21
  ror r20

  mul r20, r22
  mov r18, r1
  mul r0, r22
  mov r19, r1  ; r18 enthält nun die 3. und r19 die 4. Stelle
      ; Ergebnis = 897 / 1024 * 450 = 394.1 (Dieses Programm rundet 
immer
ab)

Wenn man die ersten beiden Zahlen Dieses Programms nicht mitzählt kommt
man auf 36 CPU Takte.

Wesentlich scneller ginge es wenn man sich mit 8bit Genauigkeit
zufrieden gibt.
Beispiel:
Nehmen wir an der 8bit Wert, den wir umrechnen wollen, befindet sich in
r16. Diesmal machen 4 Stellen beim Ergebnis keinen Sinn mehr also
errechnen wir nur 3 Stellen die in r16 - r18 gespeichert werden.

.include "m8def.inc"

  ldi r16, 224

  ldi r22, 9
  mul r16, r22
  lsr r1
  ror r0    ;r1:r0 ist nun gleich r16 * 4.5

  mov r16, r1  ; erste Stelle
  ldi r22, 10
  mul r0, r22
  mov r17, r1  ;zweite Stelle
  mul r0, r22
  mov r18, r1  ;dritte Stelle

Das wären dann (ohne die erste Zeile) 13 Takte

von Hagen (Gast)


Lesenswert?

> Heist das das ich nun mit der Nachkommazahl rechne?

Ja,

> Wenn ja wie läst man die zur "probieren" mal weg?

das könntest du machen, nur verliert dann alles seinen Sinn.
Unter deiner ersten,falschen Annahme von 1024 / 450, bekommt man ja
2.28 raus. Ohne Nachkommastellen also einfach 2.
Aber bei 450 / 1024 = 0.44 also 0 ohne Nachkomma. Wenn du also den
Algo. ohne Nachkommastellen benutzen würdest wirst du immer mit 0
multiplizieren. Ich meinte mit "interessiert dich nur noch T1 -T3"
was anders. Du musst schon mit gebrochenen Zahlen rechnen, aber nach
der Rundung bekommst du dein Resulat sofort als Ganzzahl. Und nur diese
dürfte von Interesse sein, wenn ich dich richtig verstanden habe. Unter
diesem Aspekt interessiert die T1 bis T3 nur weil dort eben der
Ganzzahlteil drinnensteht.

Sogesehen kannst du die eigentliche Berechnung mit gebrochenen Zahlen
nicht weglassen, da sonst alles mathematisch falsch wäre.

Dein jetziger Algo. ist die von mir vorgeschlagene
Subtraktions-Schleife als Ersatz für eine Divison. Diesen Algo. würde
ich nur nutzen falls der AVR keine HW-Muls unterstützt. Denn er ist im
Durchschnitt wesentlich ineffizienter und im Codeverbrauch nur
unwesentlich besser. Warum willst du die HW-Fähigkeiten der MCU nicht
benutzen wenn sie schonmal vorhanden sind ?


@Stefan: dein Vorschlag ist identisch mit meinem. Es gibt Unterschiede,
richtig, aber mathematisch ist es das gleiche.
Du "verlegst" nur die Kommastelle an eine andere Stelle.

Man möchte X * 4.5 rechnen. Du biegst dir die 4.5 so zurecht das eine
Ganzzahl entsteht -> 4.5 * 2 = 9. Somit  (x * 2) * (4.5 * 2) = (r * 2)

Das hat natürlich den Vorteil das für die Konstante 450 der zur
Verfügung stehen Zahlenbereich am besten ausgenutzt wird. Hat aber den
großen Nachteil das bei einer anderen Konstante wie zB. 360 der
komplette Algo. anders programmiert werden muß.

Die obige "universeller" Methode benötigt auch nur 19 MCU Takte.

Gruß Hagen

von Stefan (Gast)


Lesenswert?

Sie benötigt zwar nur 19 Takte, trennt das Ergebnis aber nicht in die
einzelnen Stellen auf, was für eine Ausgabe aber nötig ist, ist daher
also nicht gleich.

von Hagen (Gast)


Lesenswert?

Sorry Stefan ich bezog mich auf die Ausgangsfrage, wie man mathematisch
eine Division im AVR durchführen kann. Dazu gab es ja mehrere Lösungen,
bis hin zum Reverse Engeeniering von Compilaten. Mathematisch sind
denoch unserere Lösungen identisch.
Natürlich geht deine Routine einen Schritt weiter und drösselt das
Resultat gleich so auf das man eine BCD Konvertierung drinnen hat. Also
sofort auf einem Display ausgeben kann. Mir ging es aber nur primär
darum darsustellen wie man es allgemeingültig macht.

Gruß hagen

von AVRli (Gast)


Lesenswert?

Hi ihr beiden,

es ist wirklich interessant wie das alles geht.
Vielen Dank für die Geduld mit mir.

@Stefan
Danke für die Mühe mir das zu erläutern.
Aber wie Hagen schon schrieb, ging es primär schon darum
zu verstehen wie man mit dem AVR rechnet.
Hagen bestätigte ja das das Ziel das selbe ist.
Nix für ungut aber es war soviel "input" gestern
das ich erstmal an Hagen's Ansatz festhalten möchte.
Denn ich glaube es nun 100%tig verstanden zu haben.
In diesem Projekt geht es nicht um Geschwindigkeit.

@Hagen
Ok mit den T's war nämlich so wie Du schreibst... immer 0.
Ich hatte eine falsche Vorstellung von den Inhalten.
Aber das haste ja nun "gerade" gerückt...
Nun habe ich festgestellt das ich noch mit einem offset arbeiten darf.
Denn 450 ist 1024 sondern nur 870-900... soll heißen meine Spannung
die ich AD einlese ist von 0-4,7 oder so ähnlich.
Das aber nur am Rande.
Die Konstante

895 * 256 / 450

werde ich dann auf "Knopfdruck" einmal berechnen und in den EEprom
schreiben. So ne Art "Setup".

>Dein jetziger Algo. ist die von mir vorgeschlagene
>Subtraktions-Schleife als Ersatz für eine Divison.
>Diesen Algo. würde ich nur nutzen falls der AVR keine
>HW-Muls unterstützt.

Ja Hagen da bin ich wohl "betriebsblind" wenn man es so sagen darf.
Meine AVR-Vorgänger waren aus der AT90S family... 2323, 2343, 2313
Das waren alle AVR mit denen ich bis jetzt gespielt habe.
Die haben sowas glaube ich nicht... dann habe ich noch das Buch
AVR-RISC von Trapert.
MUL kenne ich seit gestern... :-)
Da ich da mal subtrahiert hatte wuste ich noch wie es ging und das habe
ich hier wieder verwendet. :-)

Nun kommt bestimmt die "datasheet-Rüge".
...ich bin schon auf dem Weg ins PDF... ;-)

>Warum willst du die HW-Fähigkeiten der MCU nicht
>benutzen wenn sie schonmal vorhanden sind ?

Moment muss erstmal lesen... hahaha

Gruß und danke... AVRli

von AVRli (Gast)


Lesenswert?

...gelesen und nicht gerad viel schlauer...
Nicht lachen... :-I

>Diesen Algo. würde ich nur nutzen falls der AVR keine HW-Muls
>unterstützt.

HW-Muls ?
Ich dachte es gibt nun auch nen Befehl mit dem man 2 Register
teilen kann.
HW-Muls bezeichnet aber wohl eher eine bestimmte Technik.

>Warum willst du die HW-Fähigkeiten der MCU nicht
>benutzen wenn sie schonmal vorhanden sind ?

Nun habe ich das ganze ATmega8 "Instruction Set Summary" durch.
Ich glaub ich bin zu blöd dazu...

Gruß AVRli...

von Stefan (Gast)


Lesenswert?

mit HW-Mul meinte er sicher Hardware Multiplikation.
Das ist der mul Befehl.
Eine Hardware Division unterstützt dieser uC nicht.

von Hagen (Gast)


Lesenswert?

Ja, HW-Muls = mul Mnemonic des AVRs.
Division muß man selber bauen, und ein Weg dazu ist eben die
Reziprokale Division. Wat'n dat nu wieder ?
Die Reziprokale Divison ist eine Multiplikation mit dem Kehrwert. Also
X / Y = X * (1 / Y), und wenn du genau hinschaust hast du mit obigen
Postings gelernt wie man eine solche Division durchführt, denn obige
Vorschläge sind nichts anderes als reziprokale Divisionen.
Dieses Verfahren ist immer dann ideal wenn die Hardware die Divisionen
nicht direkt unterstützt und wenn man durch eine Konstante dividiert.
Es gibt aber auch Algorithmen die die reziprokale Divisionen mit
variablen Werten durchführen können.

Für gebrochene Zahlen gibt es zwei häufig verwendete Verfahren, eg.
Datentypen. Das einfachste sind die Festkommazahlen, so wie oben. Die
meisten RTL's der heutigen Compiler nutzen aber Fließkommazahlen.
Deren Aufbau ist komplizierter, ermöglicht aber größere Wertebereiche
und sind somit universeller. Sie sind aber niemals so effizient und
auch leicht zuschneidbar wie die Festkomma-Arithmetik. Mit ein bischen
Nachdenken und dem Wissen wie Festkommazahlen funktioieren, wird es ein
leichtes mit gebrochenen Zahlen auf AVRs zu rechnen. Siehe vorherige
Postings.

Obwohl die ganze Thematik im Grunde sehr leicht ist, findet man im WEB
denoch nur wenige gute Seiten die das alles exakt erklären. Das
verführt dann auch zu den Aussagen "baue in BASIC deine Berechnungen,
compiliere sie, und nehme den Assemblercode des Compilat als
Ausgangsbasis". Nur, verstanden hat man es dann denoch nicht.

Gruß Hagen

von AVRli (Gast)


Lesenswert?

Hi Hagen,

>Reziprokale Division. Wat'n dat nu wieder ?

ohne Worte... ;-D

>Die Reziprokale Divison ist eine Multiplikation mit dem Kehrwert.
>Also X / Y = X * (1 / Y), ...

Ja das kenne ich noch von der Berechnung von "Wiederständen".
Da kam auch sowas mit 1/X vor...

>Mit ein bischen Nachdenken und dem Wissen wie Festkommazahlen
>funktioieren, wird es ein leichtes mit gebrochenen Zahlen auf
>AVRs zu rechnen.

Ein bischen Vertraut habe ich mich mit dem Verfahren schon gemacht.
Ein Gleichung wie diese "C = (A/0.9) + (B/0.1)" habe ich mit dem
Wissen schon realisieren können.

>Obwohl die ganze Thematik im Grunde sehr leicht ist, findet man
>im WEB denoch nur wenige gute Seiten die das alles exakt erklären.

doch eine... :-) www.mikrocontroller.net

>Das verführt dann auch zu den Aussagen
>"baue in BASIC deine Berechnungen, compiliere sie, und
> nehme den Assemblercode des Compilat als Ausgangsbasis".
> Nur, verstanden hat man es dann denoch nicht.

Das ist der Punkt!
Ich finde auch die AppNotes von ATMEL und kann den Quellcode auch
nutzen doch nutzen und verstehen sind zwei verschiedene Sachen.

Ich danke und denke über den Kehrwert nach... ;-)

Gruß AVRli...

von Hagen (Gast)


Lesenswert?

> "C = (A/0.9) + (B/0.1)"

Diese Gleichung würde ich umstellen nach

C * 10 = A * 8 + A + B

Damit fällt nur eine komplizierte Mul an.
A * 8 +A +B sind 5 Additionen, und um C aus C * 10 zu bekommen eine Mul
-> C = 0.1 * C'.

Aber, bei AVR's mit Hardware Multiplikation kann sich das schnell
relativieren, da 2 Taktzyklen für eine beliebige 8Bit Multiplikation im
Grunde extrem gut sind. Intel CPUs zb. benötigen abhängig von den Werten
die man multipliziert eine unterschiedliche Anzahl von Takten.

Gruß hagen

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.