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.