Hallo Leute, ich versuche schon den ganzen Tag meine Anfangs in C programmierte PWM Routine in eine externe Assembler Datei "auszulagern". Nur habe ich ein kleines Problem das ich nicht ganz gebacken bekomme. Hier erst mal der Quellcode beider Dateien: <c> // RGB-Module.c // Hauptroutinen des RGB-Farbmoduls // #include "avr/io.h" #include "avr/Interrupt.h" volatile uint8_t PWM1,PWM2,PWM3 = 10; volatile uint8_t count = 0; extern void superFunc(void); extern void SIG_OVERFLOW0(void); int main(void) { cli(); DDRB |= ((1<<DDB3) | (1<<DDB4)); PORTB &= ~((1<<DDB3) | (1<<DDB4)); TCCR0B |= (1<<CS00); TIMSK |= (1<<TOIE0); sei(); while(1); } </c> und // Interrupts.S #include "avr/io.h" temp = 27 temp2= 29 port = 0 .extern PWM1 .extern PWM2 .extern PWM3 .extern count .global SIG_OVERFLOW0 .func SIG_OVERFLOW0 SIG_OVERFLOW0: PUSH temp // wichtig: Benutzte Register und das IN temp,_SFR_IO_ADDR(SREG) // Status-Register (SREG) sichern! PUSH temp PUSH temp2 PUSH port LDS temp, count // Aktuellen Zählerstand initialisieren CPI temp, 0 // Vergleich mit 0 BREQ test // Wenn gleich dann PWM-Ports LOW con0: LDS temp2, PWM1 // PWM1 Vorgabe in temp initialisieren CP temp, temp2 // Vergleich counter mit PWM1 Vorgabe BREQ pwm1 con1: LDS temp2, PWM2 // PWM1 Vorgabe in temp initialisieren CP temp, temp2 // Vergleich counter mit PWM2 Vorgabe BREQ pwm2 con2: LDS temp2, PWM3 // PWM1 Vorgabe in temp initialisieren CP temp, temp2 // Vergleich counter mit PWM3 Vorgabe BREQ pwm3 con3: OUT PORTB, port // aktullen Portwert ausgeben LDS temp, count INC temp STS count, temp // um 1 erhöhter counter zurückschreiben POP port // die benutzten Register wiederherstellen POP temp2 POP temp, OUT _SFR_IO_ADDR(SREG),temp POP temp RETI test: RCALL con0 pwm1: RCALL con1 pwm2: RCALL con2 pwm3: RCALL con3 .endfunc .end Der Debugger zeigt mir im Disassambler-Modus, dass er wieder auf den Reset Interruptvector springt und somit einen Restart erzwingt. Hat das vielleicht etwas mit dem Stack zu tun? Oder kennt jemand eine elegantere Methode einen Assembler PWM zu implementieren? P.S. Kenn mich noch nicht so gut mit Assembler aus.
> POP temp, OUT _SFR_IO_ADDR(SREG),temp Die Zeile dürfte sowieso Probleme ergeben... > temp = 27 > temp2= 29 > port = 0 ...und das wahrscheinlich auch, oder irre ich mich?
Du hast da einen seltsamen Ablauf:
CPI temp, 0
nehmen wir mal an, temp ist tatsächlich 0, dh.
BREQ test
wird gemacht.
test:
RCALL con0
d.h. hier wird ein RCALL ausgeführt. Die Returnadresse wandert
auf den Stack. Wo ist con0? con0 ist gleich unter dem BREQ
BREQ test
con0: LDS temp2, PWM1 // PWM1 Vorgabe in temp initialisieren
Dein Programm landet in jedem Fall bei dem LDS, entweder
über den direkten Weg, wenn der BREQ nicht zuschlägt, oder
über den Umweg über test:. Nur das in diesem Fall zusätzlich
noch eine Returnadresse am Stack übrigbleibt, die niemand von
dort wegholt (da ist weder ein RET noch ein POP der das tun würde).
> P.S. Kenn mich noch nicht so gut mit Assembler aus.
Warum fängst du dann nicht mit was einfacherem an?
PS. Was gefällt dir an der C-Lösung nicht. Wenn sie funktioniert
ist es doch gut?
> POP temp, OUT _SFR_IO_ADDR(SREG),temp
-> Ok, hat sich ein Fehler beim Copy & Paste passiert (natürlich ohne
das letzte temp
die 3 Register zum zwischenspeichern sollten keine Probleme machen da
im Debugger keine Veränderungen außer bei aufrauf der Assembler-Routine
passieren.
-> RCALL con0
-> OK, noch mal erwischt. Wahr eigentlich nur eine Verzweiflungstat,
da ich keinen Rat mehr wusst. Eigentlich hatte ich ein <RET> Kommando
vorgesehen, doch da kam der gleiche Fehler.
Bezüglich der einfacheren C-Variante:
Es stimmt zwar das diese Variante einfacher ist, nur der Overhead beim
Aufruf der Funktion sprengt den Rahmen den ich für die Funktion
vorgesehen habe (Möchte noch eine State-Machine + I²C Schnittstellen
implementieren). Zudem ist die Ausführungsgeschwindigkeit in C nicht
ausreichend.
Ich kann da nirgends ne PWM erkennen, d.h. nirgends wird ein Portpin gesetzt. Schreib doch mal den richtigen Code, dann sehen wir weiter. "Zudem ist die Ausführungsgeschwindigkeit in C nicht ausreichend." Das halte ich für ein Gerücht. Wie schnell ist Dein Quarz und wie schnell soll die PWM sein ? Wesentlich mehr Beschleunigung kannst Du in C erhalten, wenn die Variablen als Register deklariert werden (R2 ... R17 sind frei). "dass er wieder auf den Reset Interruptvector springt und somit einen Restart erzwingt." Das kommt zu 99,99% daher, daß entweder der falsche Interrupt freigegeben, der falsche Vektor angegeben oder das falsche Target gewählt ist. Peter
Habs doch gefunden: PORTB. Naja, die RCALLs lassen ja nicht mehr den Programmfluß erkennen. Peter
Zu früh gefreut, PORTB wird mit "port" geladen, aber wo wird "port" vernünftig gesetzt ??? Peter P.S.: Ich versteh nicht, warum die Leute immer irgendwelche garantiert nicht laufenden Debugleichen posten müssen. Immer den original Code posten !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Hallo Peter, port wird während der Abarbeitung des Codes entweder Bitweise gesetzt oder gelöscht. // Interrupts.S #include "avr/io.h" temp = 16 temp2= 17 port = 18 .extern PWM1 .extern PWM2 .extern PWM3 .extern count .global SIG_OVERFLOW0 .func SIG_OVERFLOW0 SIG_OVERFLOW0: PUSH temp // wichtig: Benutzte Register und das IN temp,_SFR_IO_ADDR(SREG) // Status-Register (SREG) sichern ! PUSH temp PUSH temp2 PUSH port LDS temp, count // Aktuellen Zählerstand initialisieren CPI temp, 0 // Vergleich mit 0 BRNE con0 // Wenn gleich dann PWM-Ports LOW RCALL reset con0:LDS temp2, PWM1 // PWM1 Vorgabe in temp initialisieren CP temp, temp2 // Vergleich counter mit PWM1 Vorgabe BRNE con1 SBR port, PB3 con1:LDS temp2, PWM2 // PWM1 Vorgabe in temp initialisieren CP temp, temp2 // Vergleich counter mit PWM2 Vorgabe BRNE con2 SBR port, PB4 con2:LDS temp2, PWM3 // PWM1 Vorgabe in temp initialisieren CP temp, temp2 // Vergleich counter mit PWM3 Vorgabe BRNE con3 SBR port, PB5 con3:OUT PORTB, port // aktullen Portwert ausgeben LDS temp, count INC temp STS count, temp // um 1 erhöhter counter zurückschreiben POP port // die benutzten Register wiederherstellen POP temp2 POP temp OUT _SFR_IO_ADDR(SREG),temp POP temp RETI reset: CBR port, PB3 CBR port, PB4 CBR port, PB5 RET .endfunc .end Hoffe der Code sieht schon ein bisschen besser aus? Hab auch noch etwas am Programmfluss verändert. Nun funktioniert das ganze zumindest schon mal bis zu dem Punkt, an dem er den Wert des Zwischenspeicher eigentlich an den PORTB schreiben sollte. Tut er aber leider nicht :-(. Woran kann das liegen. Im Debugger siehts eigentlich bis zu dem Punkt gut aus. Bezüglich der Geschwindigkeit kann ich nur sagen, das ich bei einem Takt von 8Mhz (internet Vorteiler) und einem ähnlichen Software PWM bei C auf eine nicht sehr hohe Frequent gekommen bin (Flackern wahr noch zu erkennen). Auch brauche ich noch ein bisschen Leistung für die restlichen Aufgaben.
> Flackern wahr noch zu erkennen Dann hast du irgendwas falsch gemacht im C-Code. Bei 8Mhz stehen die LED's aber sowas von ruhig. Hast du ev. den Optimizer nicht aktiviert?
Stimmt, den Optimizer habe ich nicht aktiviert. Aber ich glaube nicht, dass er mir viel gebracht hätte? Habe meinen jetzigen Code nochmal mit eingeschalteten Optimizer programmiert, hat aber nur etwas beim Speicherverbrauch gebracht. Hab jetzt eine Frequenz von 125 Hz (schon besser). Laut Simulator hab ich noch genügend Zeit für andere Funktionen.
"port wird während der Abarbeitung des Codes entweder Bitweise gesetzt oder gelöscht." Nun ja. Aber nicht in dem verquarksten Mist ganz oben. Nächstes mal ein bischen mitdenken, bevor man postet. "Tut er aber leider nicht :-(." Lies Dir nochmmal meine Lösung für 99,99% durch. "Hab jetzt eine Frequenz von 125 Hz" Das hat mit dem Interrupt nichts zu tun, sondern ist Mathematik: 8MHz 256 256 = 122Hz. Lade einfach TCNT0 im Interrupt etwas vor und schon gehts schneller. "Auch brauche ich noch ein bisschen Leistung für die restlichen Aufgaben." Wenn die alle ohne Interrupt sind, kein Problem. Ansonsten brauchst Du ne 2 Priorität. Sonst flackert die PWM, wenn sich andere Interrupts mal Zeit lassen. Ich hab mir mal ne 2 Priorität von hinten durch die Brust ins Auge für den AVR gebastelt. Peter
Danke für die Mathestunde, nur messe ich lieber die Ausgangsfrequenz (zwecks vergessenen Vorteiler). Bezüglich des Timer-Vorladens: Geht mit der CTC Funktion eleganter. Einfach den Timer0 des Attiny45 in den Compare-Modus schalten sowie den Matchwert vorgeben und fertig. Jetzt würde mich nur noch die Sache mit den 2 Prioritäten interressieren. Meines Wissens gibt des doch beim AVR keine Priorisierten Interrupts oder liege ich da falsch. Wie hast du es gelöst?
"Wie hast du es gelöst?" Wie gesagt, von hinten durch die Brust ins Auge, aber es funktioniert. Du brauchst einen weiteren Timer, der als INTERRUPT deklariert wird, also sofort nach dem Eintritt mit sei() den PWM-Interrupt wieder freigibt. Selber sei() einzufügen gibt Jitter, da das erst nach der ganzen PUSH-Arie erfolgt. Dieser Interrupt pollt nun alle restlichen Interruptquellen und behandelt diese, während die PWM ständig dazwischenhaut (= höhere Priorität). Zum Schluß wird der Timer wieder gestartet und das Main hat 256 Zyklen Zeit, auch was zu machen. Peter
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.