Hallo zusammen, ich habe mir mit dem Mega8 (und mit einigen wertvollen Hinweisen aus diesem Forum) ein Lauflicht in Assembler programmiert. Aber der Code hat zehn Codeabschnitte, die sich bis auf ein paar Parameter sehr ähnlich sehen. Das schreit nach einer Prozedur mit drei Übergabeparametern, aber es funktioniert leider dann nicht mehr. Der funktionierende Code: .include "4433def.inc" ;Definitionsdatei einbinden, ggf. durch ;2333def.inc ersetzen .def Laufindex = r18 ;.def Dunkelphase = 15000 ;.def Hellphase = 1000 .macro mdelay ldi r24, low( @0 - 7 ) ldi r25, high( @0 - 7 ) sbiw r24, 3 brcc pc - 1 cpi r24, 0xFE brcs pc + 3 nop brne pc + 1 .endmacro ldi r16, 0b00000011 ;0xFF ins Arbeitsregister r16 laden out DDRB, r16 ;Inhalt von r16 ins IO-Register DDRB ausgeben ldi r16, 0b11111111 ;0xFF ins Arbeitsregister r16 laden out DDRD, r16 ;Inhalt von r16 ins IO-Register DDRB ausgeben Anfang: ; Funktionsprinzip: In der Phase in der z.B. die drei ersten LEDs leuchten ; sollen die sieben restlichen mittels PCM dunkler leuchten ; Zunächst sollen eine lange Zeit lang nur die drei gewünschten LEDs leuchten ; gelassen, anschließend für eine kurze Zeit alle LEDs. ; Dies läuft in einer Schleife 10x Rund. D.h. die drei hellen LEDs leuchten immer ; während die anderen sieben 10x kurz aufflackern. Die Wirkung ist die des ; gewünschten Pilotlichtes. ldi Laufindex, 10 Null: ldi r16, 0b11111111 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Null ldi Laufindex, 10 Eins: ldi r16, 0b11111110 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Eins ldi Laufindex, 10 Zwei: ldi r16, 0b11111100 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Zwei ldi Laufindex, 10 Drei: ldi r16, 0b11111000 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Drei ldi Laufindex, 10 Vier: ldi r16, 0b11110000 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne vier ldi Laufindex, 10 Fuenf: ldi r16, 0b11100000 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Fuenf ldi Laufindex, 10 Sechs: ldi r16, 0b11000000 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Sechs ldi Laufindex, 10 Sieben: ldi r16, 0b10000000 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Sieben ldi Laufindex, 10 Acht: ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111111 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Acht ldi Laufindex, 10 Neun: ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111110 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111110 out PORTB, r16 mdelay 500 dec Laufindex brne Neun ldi Laufindex, 10 Zehn: ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 15000 ldi r16, 0b00000000 out PORTD, r16 ldi r16, 0b11111100 out PORTB, r16 mdelay 500 dec Laufindex brne Zehn Ende: rjmp Anfang Wenn jemand Ideen hat, wie man die zehn immer wieder kehrenden Codeabschnitte in Prozeduren giessen kann, .... Danke schon mal im Voraus Karl
Hi... Ich habe zwar jetzt den Code nicht näher analysiert, aber was hältst du denn davon: - Alle Ausgabebytes in eine Tabelle (im Flash) legen - Timer-Int auf Lauftempo einstellen (evtl. variabel) - In ISR nächsten Wert aus Tabelle holen (LPM) und ausgeben. - Mehrere Tabellen ergeben mehrere verschiedene Lauflichter Oder, falls gedimmt werden muss: - Ausgabebytes in Tabelle - Timer-Int auf Dimmtakt - in ISR neuen Wert holen, hochdimmen, runterdimmen... - also neuen Ausgabewert aus Tabelle holen - dann je einen Wert hochdimmen bis Maximum (255) erreicht ist - dann je einen Wert runterdimmen, bis Minimum (0) erreicht ist - dann neuen Wert aus Tabelle holen Das Dimmer erreicht man durch Software-PWM. - Man zählt dazu in der Timer-ISR ein Register hoch, - legt bei 0 den Ausgabewert an den Port und erhöht/erniedrigt den Helligkeitswert, - vergleicht (in jeder ISR) den PWM-Zähler mit dem Helligkeitswert und schaltet bei Gleichstand den Port aus - Das Erhöhen/Vermindern des Helligkeitswertes geschieht durch Addition mit einem Register, das je nach Dimmrichtung 1 oder -1 enthält. Es wird bei Erreichen von Hellmax auf -1 gesetzt, bei Erreichen von Hellmin auf 1, wobei jetzt der neue Ausgabewert aus der Tabelle geholt wird. ...HanneS...
Hallo Hannes, danke für deinen Verbesserungsvorschlag. Macht wirklich einen sehr durchdachten und universellen Eindruck. Nur bin ich noch etwas erschlagen von den vielen Möglichkeiten, von denen ich bisher noch nicht mal etwas geahnt habe. Im Laufe der Zeit, bzw mit fortschreitender Programmiererfahrung versuche ich dein System umzusetzen. Momentan wäre ich schon froh, wenn ich obiges mit einem Prozeduraufruf vereinfachen könnte. Werde dazu vielleicht heute Abend noch Code hier hinein stellen, der aber wahrscheinlich nicht funzen wird :( Auf jeden Fall schon mal vielen Dank Karl
> Das schreit nach einer Prozedur mit drei Übergabeparametern, > aber es funktioniert leider dann nicht mehr. Stackregister gesetzt?
@Karl... Ich habe mir das jetzt mal (grob) angesehen, jedoch die Bitmuster nicht genau analysiert. - In jeder "Abteilung" brauchst du 4 Bitmuster, die im Original mittels r16 in die Ports geschrieben werden. Das wird geändert! - Nimm dafür nicht r16, sondern 4 Register, das dürfen sogar die ("billigen") unteren Register sein, z.B. r12...r15. - Lege die Bitmuster in eine Tabelle, immer 4 Bytes pro "Datensatz". tabelle: ;Label für Tabelle .db 0b00000000,0b00000001,0bxxxxxxxx,0bxxxxxxxx ;Bitmuster Satz1 .db 0b00000001,0b00000011,0bxxxxxxxx,0bxxxxxxxx ;Bitmuster Satz2 insgesamt 10 mal... - Mache zum Programmbeginn eine Reset-Routine, in der du einmalig die Ports auf Ausgang setzt und diverse Einstellungen machst, auch Stackpointer - Bau eine Hauptschleife, in der du den Z-Pointer auf den Anfang deiner Tabelle mit den Bitmustern setzt: Dann musst du noch einen Zähler einrichten, mit dessen Hilfe du die (abgeänderte) Ausgaberoutine 10 mal aufrufst. Danach beginnt die Hauptschleife von vorn (Pointer setzen). ldi zl,low(tabelle*2) ;L-Byte des Z-Pointers auf Tabellenanfang ldi zh,high(tabelle*2) ;H-Byte auch - Lies in der Ausgaberoutine zuerst die 4 Bytes ein lpm r12,z+ ;erstes Bitmuster von Tabelle holen lpm r13,z+ ;zweites Bitmuster von Tabelle holen lpm r14,z+ ;drittes Bitmuster von Tabelle holen lpm r15,z+ ;viertes Bitmuster von Tabelle holen Der Rest ist dann wie im Originalprogramm, nur dass statt r16 die Register r12...r15 ausgegeben werden. Der Rücksprung innerhalb der Ausgaberoutine muss natürlich hinter den LPM-Block erfolgen! Sonst gibt es Kuddelmuddel mit der Tabelle. Ich hänge mal ein Programm an, das ein ganz einfaches gedimmtes Lauflicht an nur einem Port (8 LED's) erzeugt. Das hat zwar ein ganz anderes Verhalten (ist eben kein "ganz besonderes Lauflicht), du kannst aber einige meiner Vorschläge daran nachvollziehen. Es ist nur im Simulator (AVR-Studio) getestet, nicht in Echtzeit im AVR. Dazu muss mit Sicherheit die Wartezeit erhöht werden. Die LEDs sollten gegen GND geschaltet werden... Viel Erfolg... ...HanneS...
Hallo Hannes, dein Code ist wirklich sehr elegant. Ich mag meinen Primiticode gar nicht mehr anschauen ... Sehr lehrreich. Nicht nur für mein Lauflichtproblem. Vielen Dank Karl P.S. Vielleicht sollte man deinen Code als Beispiel in ein Tutorial aufnehmen.
@Karl: Das mit dem "elegant" sehe ich anders. Jedenfalls weiß ich, dass ich verdammt Vieles nicht weiß und manches Problem nur sehr umständlich löse. Wirklich eleganten Code schreiben einige Andere hier, teils so elegant und optimiert, dass ich ihn (noch) nicht nachvollziehen kann. Wo ich mir allerdings Mühe gebe, das ist das Kommentieren des Codes. Und das mache ich nicht für Andere, sondern für mich, da ich sonst die Übersicht verlieren würde. Denn erst die Kommentare zeigen was eigentlich gemacht wird (und warum so und nicht anders). Gruß... - ...HanneS...
@chris, ähm, tja, wahrscheinlich meinst du das: Ich hatte den Stack nicht initialisiert. Aber mit war auch nicht klar, dass die Rücksprungadresse automatisch dort gespeichert wird. Ist mir erst klar geworden, als mir beim Betrachten eines anderen Beispielcodes dein Einwand eingefallen ist. Mit der Stackinitialsierung klappt es auch mit Unterprogrammmaufrufen. Aber: Wie kann ich Parameter übergeben? Bei Makros scheint das ja ähnlich elegant zu gehen, wie in Hochsprachen: Folgendes Beispiel wurde mir in diesem Forum geliefert: .macro mdelay ldi r24, low( @0 - 7 ) ldi r25, high( @0 - 7 ) sbiw r24, 3 brcc pc - 1 cpi r24, 0xFE brcs pc + 3 nop brne pc + 1 .endmacro Ich kann dieses Makro aufrufen mit: mdelay 1000 Wäre doch super, wenn das auch mit Unterprogrammen funzen würde. Oder geht das sogar?
Hi... Compiler für Hochsprachen legen im SRAM einen weiteren "Stack" an, zu dessen Verwaltung ein Pointer (Doppelregister) verwendet wird. In diesen legen sie die Parameter. Natürlich nicht mit PUSH und PULL, sondern durch indizierte SRAM-Zugriff (ST, LD) mit Auto-In(de)crement des Pointers. Wenn du das nicht möchtest (kostet halt Rechenzeit, SRAM und Pointer), dann musst du schon Register für die Parameterübergabe reservieren. Also das Unterprogramm als Solches sehen, nicht als Funktion. Calls zu Unterprogrammen nutze ich eigentlich nur, wenn es sich wirklich lohnt, bei Kleinigkeiten kostet (R)CALL und RET oft mehr Rechenzeit als das UP selbst. Das UP muss also schon von mehr als zwei Stellen aus aufgerufen werden (oder sehr groß sein), sonst lohnt es sich nicht. Dass "Funktionen" (also Aufruf mit Parameterliste) in ASM das Nonplusultra sind, mag ich nicht so recht glauben, das ist eher eine Sache der Hochsprachen. @Karl: Mein (obiges) Programmbeispiel ist übrigens so strukturiert, dass die Hauptschleife (ohne die Warteschleife) in einem Timer-Int als ISR laufen kann. Dazu ist natürlich noch das Statusregister (SREG) zu sichern und wiederherzustellen und je nach Timer-Int der Timer zu behandeln (Timer.Reload bei Overflov-Int). Ein weiteres Programm zum Analysieren findest du hier: http://www.mikrocontroller.net/forum/read-1-113815.html#115916 Das Projekt ist zwar fragwürdig, es ist auch kein Mega8, aber die Kommentare sind hilfreich, Dinge wie Timer und Interrupt verstehen zu lernen, zumindest die ersten Schritte... ...HanneS...
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.