Forum: Mikrocontroller und Digitale Elektronik AVR - Schleifen/Delay


von Petersson (Gast)


Lesenswert?

Habe mal eine kurze Verständnissfrage zur Schleifenerstellung in
Assembler.
Undzwar habe ich mal folgenden Code anhand von Beispielen erstellt, der
bei 1MHz nach meiner Meinung ungefähr 1000ms Pause erzeugen sollte:

delay1000ms:
                ldi     r16,0xFF

count1:
                ldi     r17,0xFF

count2:
                ldi     r18,0x05

count3:         dec     r18
                brne    count3
                dec     r17
                brne    count2
                dec     r16
                brne    count1

                nop
                nop
                ret

Folgendes ist mir jedoch noch etwas unklar. Die Reihenfolge bei der
Ausführung ist doch:
1. Den Registern Werte zuweisen
2. ??

brne steht doch für branch not equal und heisst doch soviel wie spring
wenn nicht 0 oder sehe ich das falsch?
Wie wäre denn die Ausführungsreihenfolge in Worten?

von johnny.m (Gast)


Lesenswert?

brne heißt 'verzweige, wenn Zero-Flag nicht gesetzt'. D.h. wenn bei
der Operation vor dem brne (im ersten Fall dec r18) das Zero-Flag nicht
gesetzt wurde (was bedeutet, dass r18 nicht null ist), wird das Ganze
noch mal gemacht, bis r18 null ist. Anschließend wird das übergeordnete
Register r17 dekrementiert und der ganze Spaß geht von vorne los. Da in
r16 und r17 jeweils FFh steht und in r18 05h, dauert der komplette
Durchlauf FF x FF x 5 (also dezimal ~325000)Taktzyklen.

Gruß

Johnny

von johnny.m (Gast)


Lesenswert?

Sorry, sind natürlich viel mehr Taktzyklen (dec braucht einen, brne bei
wahrer Bedingung, also fast in jedem Durchlauf, zwei Zyklen). Insgesamt
dürften es also ungefähr 1000000 Zyklen sein.

von Petersson (Gast)


Lesenswert?

Dann müsste meine Berechnung ja stimmen bei 1MHz -> 1000msec delay.

ldi ;1 Takt
dec ;1 Takt
brne ;2 Takte zum springen, 1 Takt exit

1(ldi) + [1(dec) +2(brne)]*255(schleifendurchläufe) -1 brne beim
ausstieg +1nop=766

Darum nochmal ne Schleife (255) 766*255 = 195330
Und noch eine Schleife 195330*5 = ~1000000

Wenn ich mich nicht verrechnet habe, sollte es dann ja so stimmen.

von johnny.m (Gast)


Lesenswert?

Jupp, das müsste näherungsweise stimmen. Ganz genau kriegt man es so
natürlich nicht hin. Aber wenns genau sein soll nimmt man sowieso nen
Timer.

von Petersson (Gast)


Lesenswert?

Eine letzte Frage habe ich zu dem Thema nochmal. Hatte grade mal die
Idee nach dem Wälzen von ein paar Infos, das ganze "akustisch" zu
untermalen.
Also mal folgendes Unterprogramm:
.def beep=r19

buzzer1000ms:
                push    r16
                push    r17
                push    r18

                ldi     r16, 0xFF

buzz1:

                ldi     r17, 0xFF

buzz2:

                ldi     r18, 0x05

buzz3:

                dec     r18
                com     beep              ;Complement
                ori     beep,$bf          ;Or conjunction bit 6 only
                out     portd,beep
                brne    buzz3

                dec     r17
                com     beep
                ori     beep,$bf
                out     portd,beep
                brne    buzz2

                dec     r16
                com     beep
                ori     beep,$bf
                out     portd,beep
                brne    buzz1

                nop
                nop

                pop     r18
                pop     r17
                pop     r16

                ret

Aber irgendwie führt das Ganze zu einem totalen Aufhänger des AVRs.

von johnny.m (Gast)


Lesenswert?

Öh...Was sollen das Komplement und das ori bewirken? Wenn Du den Pin
jedes Mal umschalten willst, um eine Tonfrequenz zu erzeugen, dann mit
eor. Warum es konkret zu einem Aufhänger führt kann ich so auf den
ersten Blick nicht sagen.

von johnny.m (Gast)


Lesenswert?

Ach ja, Du musst natürlich das brne direkt nach dem Dekrement machen.
Die Befehle ori und com können das Zero-Flag ändern! Also leg das dec
direkt vor das brne.

von Petersson (Gast)


Lesenswert?

Ich hatte mir das mit dem Komplement und dem ori so gedacht, dass nur
bit6 verändert wird. Die Folge müsste so aussehen:

Beep       00000000
after com: 11111111
OR with:   10111111
-------------------
Result:    11111111
after com: 00000000
OR with:   10111111
-------------------
Result:    10111111

usw. den Befehl eor kenne ich nicht. Werde aber mal eben deinen Rat
befolgen.

von Arnobär (Gast)


Lesenswert?

Ein delay kannst du viel einfacher machen mit nur einer Schleife. Wenn
du mehr als 8bit (=256) mal durchlaufen musst nimm einfach 16Bit
(65536), und wenn das nicht reichen sollte nimm hald 24Bit für den
Schleifenzähler, ist weniger code als diese ewigen verschatelten
Schleifen ;)

Berechnen kann man so:

8Bit  ->   n = (3*v)     + 7   ->   v = (n-7) / 3
16Bit ->   n = (4*v) + 1 + 7   ->   v = (n-8) / 4
24Bit ->   n = (5*v) + 2 + 7   ->   v = (n-9) / 5

n...Anzahl der Takte die die Funktion braucht
v...Wert der ins Zählerregister geladen werden muß

Verzögerungen sind Möglich:
 8Bit: 10 bis 775 Zyklen
16Bit: 12 bis 262.152 Zyklen
24Bit: 14 bis 83.886.089 Zyklen

Man rechnet sich zuerst v aus, wenn eine kommazahl herauskommt rundet
man die Zahl auf eien ganze ab und setzt diese in die formel für n ein.
Jetzt sieht man wie viele Takte die Funktion verzögert mit diesem Wert.
Will man zb. 1000 Takte haben und es kommt 998 heraus fügt man noch 2
NOP vor dem RET ein, fertig ;)

Assembler:

;--------------------------------------------------
; Bsp: Delay für 500 Taktzyklen:
.def  rTemp = r16  ;8 Bit Zählregister definieren
.equ  Value = 164   ;Value für 499 Taktzyklen

Main:
  rcall Delay500  ;Delayroutine aufrufen.
  rcall Main

Delay500:
  ldi rTemp, Value  ;Zählerregister mit Value laden.
Delay500a:
  dec rTemp    ;Zählerregister verringern um eins.
  brne Delay500a    ;So lange ungleich Null, wiederhole Vorgang.
  nop      ;Am Ende noch um 1 Takt verzögern (499+1).
  ret

;-------------------------------------------------------
; Struktur für 16Bit delay
.def  rTempL = r16  ;16 Bit Zählregister definieren
.def  rTempH = r17

Main:
  rcall Delay2k    ;Delayroutine aufrufen.
  ...

Delay2k:
  ldi rTempL, $FF    ;Den 16 Bit Wert Value
  ldi rTempH, $FF   ;in das Zählregister laden.
Delay2ka:
  subi rTempL, $01  ;Das Zählregister um eins
  sbci rTempH, $00  ;dekrementieren.
  brne Delay2ka    ;Solange ungleich Null, Vorgang wiederholen.
        ;hier eventuelle NOP's einfügen
  ret


;-------------------------------------
; Delay mit 24Bit
.def  rTempL = r16 ;24Bit Wert definieren
.def  rTempM = r17
.def  rTempH = r18

Delay:
  ldi rTempL, $FF ;Wert v laden
  ldi rTempM, $FF
  ldi rTempH, $FF
Delay1:
  subi rTempL, $01
  sbci rTempM, $00
  sbci rTempH, $00
  brne Delay1
        ;ev. NOP's
  ret

Tja, ich hab mich damit mal beschäftigt vor einiger zeit, is einfacher
als diese verschatelten delayloops udn weniger code ;)

von Simon K. (simon) Benutzerseite


Lesenswert?

>>Tja, ich hab mich damit mal beschäftigt vor einiger zeit, is einfacher
als diese verschatelten delayloops udn weniger code ;)

Also irgndwie muss ich dir Recht geben.

von johnny.m (Gast)


Lesenswert?

Wenn Du etwas mit 0 veroderst, dann ändert sich gar nichts (11111111 or
10111111 = 11111111). Ein Bit unabhängig von seinem Ausgangswert
umschalten geht mit Exklusiv-Oder. Der AVR-Assembler-Befehl dafür ist
eor.

von Philipp Burch (not logged in) (Gast)


Lesenswert?

@Arnobär:

Also ich bin's jetzt nicht 100%-ig durchgegangen, aber bist du sicher,
dass das so funktioniert bei > 8 Bit? Dein brne springt ja schon aus der
Schleife raus, wenn nur das höchste Register 0 ist. Die anderen sollten
dann aber eigentlich mit 0xFF belegt sein. Ausserdem solltest du die
Takte für den Prozeduraufruf und -rücksprung miteinberechnen, sonst
nützt dir die genaue Berechnerei auch nix.

von johnny.m (Gast)


Lesenswert?

Nachtrag zur Erläuterung:

Wahrheitstabelle vom Exklusiv-Oder:

  B|1   0
A  |
---------
1  |0   1
   |
0  |1   0

D.h. wenn Du ein Bit mit 1 'verexklusivoderst' erhältst Du jedes Mal
das Komplement des Bits, und zwar unabhängig von seinem Ausgangswert.
Es müsste bei Dir also z.B. heißen

.def temp = r15 ;temporäres Register

ldi beep, $40 ;bit 6 gesetzt
;...irgendwas
in temp, portd ;Port D einlesen
eor temp, beep ;Bit 6 toggeln
out portd, temp ;Port D ausgeben

Gruß

Johnny

von Simon K. (simon) Benutzerseite


Lesenswert?

@Philip: Da SBCI auch das Zero Flag beeinflusst (und davon hängt der
Sprung des BRNEs ab) sollte das kein Problem darstellen.

Achso: Was noch hinzuzufügen wäre ist, dass SBCI das Zeroflag nur
insofern beeinflusst, als das SBCI das Zero Flag LÖSCHT, FALLS das
Ergebnis <> 0 ist. Ansonsten bleibt das unverändert.
(Previous value remains unchanged when the result is zero; cleared
otherwise.)

Das heißt, tritt vorher bei einer niederwertigeren Subtraktion eine 0
auf, wird zwar das Flag gesetzt, aber falls bei SBCI was anderes als 0
raus kommt, wird das Flag wieder gelöscht. Da SUBI und SBCI direkt
hintereinander ausgeführt werden gibt das auch keine Probleme. 2. Fall:
Tritt vorher schon die 0 auf, wird das Zero Flag gesetzt, und falls SBCI
auch auf eine 0 trifft, wird das Flag garnicht verändert. Sprich: Es ist
immernoch 1 und BRNE springt nicht mehr.

Sonst hätten ja sämtliche Sprungbefehle keine Wirkung bei
Additionen/Subtraktionen > 8bit!

von Philipp Burch (not logged in) (Gast)


Lesenswert?

@Simon:

Ok, das wusste ich nicht. So ist die Sache natürlich anders. Da ist die
Idee echt gut, geht einiges leichter...

von Petersson (Gast)


Lesenswert?

Arnobär:
Könntest du vielleicht nochmal erklären wie sich diese Formel
zusammensetzt? Wie kommst du auf die festen Werte in der Formel?

von Arnobär (Gast)


Lesenswert?

@Philipp: Ja in der Formel sind alle takte für RCALL + RET mit
einberechnet, du musst aber mit RCALL aufrufen mit CALL würde es 1 Takt
länger werden! 2.) Wenn du von einem 16Bit oder 24Bit Registerpaar 1
Subtrahieren möchtest, dann musst du auch 1 abziehen, natürlich 3x mit
berücksichtigen von Carray (Wert - 0x000001) ;)

@Petersson: Also das war garzschön schwer auf das zu kommen, ich hab
das so gemacht:

Takte = ( [Taktanzahl-der-wiederholenden-Schleifenbefehle] *
[Wert-im-Zählerregister] ) + (zusätzliche-Ladebefehle-beim-init] +
RCALL + RET

bei 8Bit wiederholt sich DEC(1) udn BRNE(2) mit der anzahl des wertes
im Zählerregister, am Anfang brauch ich mit LDI(1) einen Takt, der
kürzt sich aber wieder weg weil der letze BRNE(1) nur einen Takt
braucht wenn die bedingung nicht erfüllt wird (Zählerregister=0). Wenn
ich mit 16Bit oder merh arbeite muss ich merh befehle verwenden zum
decrementieren, daher steigt der multiplikator des Zählerwertes, udn
ich muss die zusätzlichen initialisierungsbefehle (LDI) am anfang
mitberechnen, darum +.

Hab ich 16bit, kommt am anfang beim laden des Zählerwertes noch eiern
dazu, weil für 16bit brauch ich 2xLDI, udn in der schleife muss ich
SUBI und SBCI machen, sind auch 2 Takte, darum erhöht sich der
multiplikator ;)

Bei 24 bit wiederhohlt isch wieder ein Befehl öfters -> multiplikator
+2 und init +2

Bei 32 bit wären es dann +3

Diese routinene habe ich im simulator getestet, sie funktionieren
absolut exakt, ich finde diese Variante viel leichter als die
Verschachtelung. Wenn ich zum spass noch ein paar NOP in die schleife
einfüge, und diese NOPS im multiplikator berücksichtige lasst sich auch
mit 8bit hohe tolle verzögerungen erreichen (hald mit merh code dann
evtl. ) ^^

von Petersson (Gast)


Lesenswert?

Arnobär:
Ich muss nochmal fragen, irgendwie habe ich es noch nicht 100%.

Mal angenommen, ich möchte 1Mio Cycles verzögern.
Dafür benötige ich 24bit.

24Bit ->   n = (5*v) + 2 + 7   ->   v = (n-9) / 5
v = (1000000-9) / 5 = 199998
n = (5*199998) + 2 + 7 = 999999

Also noch 1 NOP und ich habe meine Mio.

Der Code müsste doch dann wenn ich es richtig verstanden habe wie folgt
aussehen:

.def  rTempL = r16
.def  rTempM = r17
.def  rTempH = r18

Delay:
  ldi rTempL, $3E
  ldi rTempM, $0D
  ldi rTempH, $03
Delay1:
  subi rTempL, $01
  sbci rTempM, $00
  sbci rTempH, $00
  brne Delay1
  nop
  ret

Entschuldige bitte die vielen Fragen, aber als Anfänger braucht man
etwas länger zum Verstehen.

von Arnobär (Gast)


Lesenswert?

Nein, ist kein Problem, das Forum ist ja da um Fragen zu beantworten ;)
Das Beispiel ist genau so gerechnet wie es sein muss, ein top
musterbeispiel :)

Wenn du AVRStudio benutzt sollte das auch gehen, was das hantieren mit
großen Zahlen einfacher macht:

.equ VALUE = 199998

[...]

Delay:
  ldi rTempL, BYTE1(VALUE) ;Lädt das erste Byte von Wert Value..
  ldi rTempM, BYTE2(VALUE) ;dies das zweite..
  ldi rTempH, BYTE3(VALUE) ;und das dritte Byte.

[...]

Wenn du nur mit 16Bit arbeitest ist LOW() udn HIGH() lesbarer:
  ldi rZahlL, LOW(12345)  ;==BYTE1()
  ldi rZahlH, HIGH(12345) ;==BYTE2()

von Petersson (Gast)


Lesenswert?

@johnny.m

Ich habe mir nochmal den Kopf zebrochen und ori ergibt meiner Meinung
nach Sinn.

Ich schreibe hier mal die Tabelle etwas weiter:

                ;Beep       00000000
                ;after com: 11111111
                ;OR with:   10111111
                ;-------------------
                ;Result:    11111111
                ;after com: 00000000
                ;OR with:   10111111
                ;-------------------
                ;Result:    10111111
                ;after com: 01000000
                ;OR with:   10111111
                ;-------------------
                ;Result:    11111111
                ;after com: 00000000
                ;OR with:   10111111
                ;-------------------
                ;Result:    10111111

Die Logik sollte doch die gleiche sein wie sie z.B. auch bei
Logikgattern der Fall ist, oder irre ich micht?
Nur beim "Vergleich" von 0 und 0 ist das Ergebnis auch 0.
In allen anderen Fällen (0und1,1und0,1und1) ist der Ergebnis 1.

von Karl H. (kbuchegg)


Lesenswert?

Ich hab jetzt nicht analysiert was da mit dem OR gemacht
wird bzw. wo das in diesem Thread herkommt.

Du kannst folgende Faustregeln verwenden:

Um zu:            macht man

Bit löschen       AND mit einer Maske in der das zu löschende
                  Bit 0 ist, alle anderen Bits in der Maske sind 1

Bit setzen        OR mit einer Maske in der das zu setzende Bit
                  1 ist, alle anderen Bits sind 0

Bit toggeln       XOR mit einer Maske in der das zu toggelnde Bit
                  1 ist, alle anderen Bits sind 0

Alle Fälle auf die du je stossen wirst fallen in eine der
3 Kategorien. Manchmal hat man auch Kombinationen dieser 3
Kategorien und handelt die einfach nacheinander entsprechend ab.

   (umdrehen)

von johnny.m (Gast)


Lesenswert?

OK, hatte das beim ersten Versuch nicht ganz nachvollziehen können, was
da womit verodert wird. Im Prinzip klappt das, was Du jetzt geschrieben
hast, auch. Ist nur nicht die 'übliche' Methode und wirkt auf den
ersten Blick etwas umständlich.

Gruß

Johnny

von Simon K. (simon) Benutzerseite


Lesenswert?

>>Ist nur nicht die 'übliche' Methode und wirkt auf den
ersten Blick etwas umständlich.

Oh doch, das ist eine übliche Methode. Und wenn man weiß was sie macht,
ist sie sogar sehr logisch (erst recht wenn man sich schulmäßig dauernd
mit Digitaltechnik und statischen Logikelementen beschäftigt)

von Simon K. (simon) Benutzerseite


Lesenswert?

PS: Eine nützliche Methode, die ich mal vom Peter Dannegger geklaut habe
(glaube ich) ist folgendes: Man hat einen Zähler, der zB von 0-15 Zählen
soll.

Normalerweise macht man folgendes:

loop:
ldi register, 16
dec register
brne norefresh
ldi register, 16
norefresh:
rjmp loop

oder sowas in der Art. aufjedenfall mit ständigem Vergleich.. nach
Peter Dannegger (glaube ich ;)) gehts so:

loop:
ldi register, 0
inc register
andi register, 15
rjmp loop

fertig..
Wenn die Zahl gerade 15 ist, und auf 16 erhöht wird, wird das Bit mit
der Wertigkeit 16 sofort wieder gelöscht, durch das AND, und beim
nächsten Durchgang wieder von 0 angefangen.

von Hannes L. (hannes)


Lesenswert?

loop:
ldi register, 0
inc register
andi register, 15
rjmp loop
???

oder so:

 ldi register, 0
loop:
 inc register
 andi register, 15
 rjmp loop

dann fängt er nämlich nicht immer bei 0 an...

:-D

Duck & wech...
...

von Simon K. (simon) Benutzerseite


Lesenswert?

jaja, hab nich so genau hingesehen, höhö

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.