mikrocontroller.net

Forum: Compiler & IDEs Schwerer Fehler in PWM Routine


Autor: Flo K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: johnny.m (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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?

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Flo K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Habs doch gefunden: PORTB.
Naja, die RCALLs lassen ja nicht mehr den Programmfluß erkennen.


Peter

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Autor: Flo K (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Karl heinz Buchegger (kbucheg)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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?

Autor: Flo K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: peter dannegger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
"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

Autor: Flo K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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?

Autor: Peter Dannegger (peda)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
"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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.