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?
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
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.
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.
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.
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.
Ö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.
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.
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.
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 ;)
>>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.
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.
@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.
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
@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!
@Simon: Ok, das wusste ich nicht. So ist die Sache natürlich anders. Da ist die Idee echt gut, geht einiges leichter...
Arnobär: Könntest du vielleicht nochmal erklären wie sich diese Formel zusammensetzt? Wie kommst du auf die festen Werte in der Formel?
@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. ) ^^
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.
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()
@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.
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)
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
>>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)
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.
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... ...
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.