Forum: Mikrocontroller und Digitale Elektronik AtXmega Software PWM (Bsp aus Forum)


von Stefan F. (stefan1987)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich habe eine Frage zu dem Beispiel Software PWM aus dem Forum :
http://www.mikrocontroller.net/articles/Soft-PWM

Meine erste Frage ist eine Verständnis Frage zu der ISR Compare.
Im Datenblatt steht das der Interrupt Vector aufgerufen wird wenn das 
TOV1 Flag gesetzt wird also in Normal Mode bei erreichen von MAX Value 
des Timer Counters. Hab ich das so richtig verstanden ?

Ich habe nun versucht den intelligenten Lösungsansatz auf einen ATxMega 
256A3 zu portieren. Atmel schreibt für ein Output Compare muss der Timer 
in normal mode laufen und der Compare Output Enable muss aktiviert 
werden.
1
TCC0.CTRLA = TC_CLKSEL_DIV8_gc; // Presacler 8 
2
TCC0.CTRLB = 0b00010000; // select Modus: Normal; Enable Compare Output A
3
TCC0.PER = PWM_STEPS; // Zähler Top-Wert -> PWM-Steps (256)
4
TCC0.CNT = 0x00; // Zähler zurücksetzen
5
TCC0.INTCTRLA = 0b00000011; // Interrupt Highlevel
müsste also so passen oder ?

Hab das ganze dann mal debuggt und das Problem ist nun das die Interrupt 
Routine nicht aufgerufen wird.

Hat damit jemand schon Erfahrungen gemacht und kann mir helfen ?
Hab den auf Xmega abgeänderten Code angehängt.


Vielen Dank schonmal

Gruß Stefan

von Hagen R. (hagen)


Lesenswert?

Stefan Friedrich schrieb:
> Ich habe nun versucht den intelligenten Lösungsansatz auf einen ATxMega
> 256A3 zu portieren.

Das ist dann aber nicht der intelligenteste Lösungsansatz für den 
ATXMega der machbar ist.

Wenn du einen DMA Kanal frei hast dann kannst du mit diesem für einen 
Timer gleich 4 Output Compare Kanäle eines Timers füttern. Damit läuft 
alles in Hardware im Hintergrund ab bis auf den Punkt der Aktualisierung 
der Dutycycles per Software. Diese Werte liegen in einem SRAM Array mit 
4 Elementen und müssen nur dann überschrieben werden wenn du die 
Dutycycles ändern möchtest.

Wenn du statt einem Timer einen 8Bit Port benutzt dann kannst du diesen 
ebenfalls per DMA Kanal permanent füttern und jedes beliebige PWM 
Bitmuster an diesen 8 Bit Port aus einem SRAM Array ausgeben lassen. 
Dann wären dies schon 8 PWMs die mit bis zu 32MHz Frequenz aktualisiert 
werden können.

Also: für eine PWM mit 256 Schritten legst du im SRAM ein Array mit 256 
Elementen an. Dieses Array wird per DMA + Timer als Taktbasis permanent 
in einen Port kopiert. Das geht alles in Hardware ohne Software 
Eingriffe. Also ohne CPU Last. Die Bitmuster der Elemente im SRAM Array 
definieren deine PWM auf jedem der 8 Pins dieses Ports.

Für die Variante mit Timer + 4 Output Compare Register legst du im SRAM 
ein Array auf 4 Elementen an. Der Timer Overflow taktet den DMA per 
Event und der DMA kopiert diese 4 Werte in die 4 OC-Register des 
gleichen Timers.

Das geht also, bis auf das einmalige Einrichten und Aktualisieren der 
Werte, bei diesen Varianten ohne Softwareeingriffe. Die damit 
erreichbare CPU Last ist ungleich geringer als die im Artikel 
beschriebene Sofft-PWM.

Beide Varianten habe ich schon selbst programmiert und funktionieren 
perfekt.

Gruß Hagen

von Vlad T. (vlad_tepesch)


Lesenswert?

Hagen Re schrieb:
> Beide Varianten habe ich schon selbst programmiert und funktionieren
> perfekt.
wirklich?
da sich CPU und DMA einen Bus zum Speicher Teilen kann ich mir nicht 
vorstellen, dass man da nicht einen ziemlich großen Jitter hat, wenn die 
CPU grad viele Speicheruzugriffe hat und die DMA warten muss.

von Hagen R. (hagen)


Lesenswert?

Korrekt, aber bei 32MHz Taktbasis hat man für 256 Schritte genügend 
Zeit. Die Timer Variante blockiert den Bus nur 1/(256/4)'tel der Zeit 
wie die Port Variante. Das hängt dann von den Anforderungen des OTs ab. 
Die wie üblich nicht klar spezifiziert wurden.

Auf alle Fälle ist das Soft-PWM Beispiel nicht die cleverste Wahl für 
den XMega. Denn ob nun der DMA transparent im Hintergrund auf das SRAM 
zugreift oder es letzendlich über ISRs und Software erfolgt ist egal da 
die Datenmengen gleich sein werden.

Gruß hagen

von Bassti (Gast)


Lesenswert?

Find die Idee trotzdem gut... man muss ja nicht gleich mit 125 kHz 
anfangen...
Paar LEDs Dimmen usw. ...ist ne nette Idee mit dem Port und dem DMA! :)

von Hagen R. (hagen)


Lesenswert?

Vlad Tepesch schrieb:
> Hagen Re schrieb:
>> Beide Varianten habe ich schon selbst programmiert und funktionieren
>> perfekt.
> wirklich?
> da sich CPU und DMA einen Bus zum Speicher Teilen kann ich mir nicht
> vorstellen, dass man da nicht einen ziemlich großen Jitter hat, wenn die
> CPU grad viele Speicheruzugriffe hat und die DMA warten muss.

Davon abgesehen greift die CPU beim Programablauf viel häufiger auf den 
FLASH statt SRAM zu. Und der Bus zum FLASH ist unabhängig vom Datenbus 
zum SRAM, eg. hat keinen Einfluß auf das DMA.

von Hagen R. (hagen)


Lesenswert?

Bassti schrieb:
> Find die Idee trotzdem gut... man muss ja nicht gleich mit 125 kHz
> anfangen...
> Paar LEDs Dimmen usw. ...ist ne nette Idee mit dem Port und dem DMA! :)

Wobei man dann im Vergleich zur Timervariante sogar mit der 
Maximalfrequenz runter gehen kann. Denn statt mit einer linearen PWM, 
wie bei Timern möglich, zu arbeiten kann man bei der Portvariante mit 
dem sogenannten Scrambling-PWM arbeiten. Dabei werden die On-Phasen der 
LEDs gleichmäßig als Pulse über den kompletten PWM Zyklus verteilt. 
Somit kann man die Gesamtfrequenz der PWM reduzieren da sich erst später 
ein sichtbares Flackern der LEDs einstellt. Im Vergleich zur normalen 
PWM bei der sich nur eine variable On/Off Zeit einstellen lässt.

Nimmt man 2 Ports und 2 DMA Kanäle als Matrix so kann man 256 LEDs 
ansteuern.

Unter Umständen könnte man sogar die 4 virtuellen Ports benutzten und 
per einem DMA Kanal deren 4*4 Bytes an Konfiguration aus dem SRAM 
kopieren. Davon werden zwar nur die 4 OUT register real benötigt aber 
man hat so mit einem DMA Kanal gleich 4*8 Bits an PWM.

Gruß hagen

von Stefan F. (stefan1987)


Lesenswert?

Danke für die Antworten.

Also Anwendung soll PWM auf 8 Kanälen sein, später vielleicht mal 16.
Die PWM Signale sollten an einem (bzw dann zwei) Ports anliegen und 
erstmal sollten 8 Bit PWM reichen. (Wobei die 16 Bit ja mit diesem 
Ansatz auch schnell nachzurüsten wären)

Der Grund warum ich die Software PWM version weiter verwenden wollte 
ist, dass ich das Programm das ich derzeit noch auf einem Atmega 32 am 
laufen hatte gerne auf diesem Prozessor weiter verwenden wollte. (Also 
die ganzen Funktionsaufrufe und Parameterübergabe nicht großartig 
verändern wollt)
Sollte es mit dieser Variante auf dem Xmega nicht funktionieren bleibt 
mir wohl nichts anderes übrig aber ich wollte es wenigstens versuchen.
Bis jetzt scheitert es allerdings noch daran das die ISR nicht 
aufgerufen wird.

Hagen Re schrieb:
> Wenn du statt einem Timer einen 8Bit Port benutzt dann kannst du diesen
> ebenfalls per DMA Kanal permanent füttern und jedes beliebige PWM
> Bitmuster an diesen 8 Bit Port aus einem SRAM Array ausgeben lassen.
> Dann wären dies schon 8 PWMs die mit bis zu 32MHz Frequenz aktualisiert
> werden können.
>
> Also: für eine PWM mit 256 Schritten legst du im SRAM ein Array mit 256
> Elementen an. Dieses Array wird per DMA + Timer als Taktbasis permanent
> in einen Port kopiert. Das geht alles in Hardware ohne Software
> Eingriffe. Also ohne CPU Last. Die Bitmuster der Elemente im SRAM Array
> definieren deine PWM auf jedem der 8 Pins dieses Ports.

Ansonsten hört sich diese Lösung sehr interessant an, alledings ist mir 
nach lesen des Datenblattes nicht ganz klar wie das zu bewerkstelligen 
ist. Mit der Hardware PWM kann man die Signale ja immer nur an maximal 6 
von 8 pins pro Port ausgeben.
Und wieso muss das Array 256 Elemente haben ? Angenommen ich wollte nur 
eine Sequenz ausgeben von 20% 50% 100% Helligkeit müssten doch 3 
Arrayelemte reichen à 8 Bit, oder habe ich das komplett falsch 
verstanden ?

Grüße Stefan

von Hagen R. (hagen)


Lesenswert?

Stefan Friedrich schrieb:
> Ansonsten hört sich diese Lösung sehr interessant an, alledings ist mir
> nach lesen des Datenblattes nicht ganz klar wie das zu bewerkstelligen
> ist. Mit der Hardware PWM kann man die Signale ja immer nur an maximal 6
> von 8 pins pro Port ausgeben.
> Und wieso muss das Array 256 Elemente haben ? Angenommen ich wollte nur
> eine Sequenz ausgeben von 20% 50% 100% Helligkeit müssten doch 3
> Arrayelemte reichen à 8 Bit, oder habe ich das komplett falsch
> verstanden ?

Eine 8 Bit PWM hat 256 verschiedene DutyCycles die einstellbar sind. Das 
Array hat also 256 Elemente in denen du für jeden Pin des Ports die 
Zustände abspeicherst. Der DMA kopiert dieses Array permanent in den 
Port und das mit der gleichen Frequenz mit der normalerweise dein Timer 
läuft. Wenn du nun auf Pin 0 vom Port einen DutyCycle von 20% einstellen 
möchtest dann muß das unterste Bit aller 256 Bytes im Array zu 20% auf 1 
und zu 80% auf 0 gesetzt sein. Man macht also das was der PWM-Timer 
autom. macht von Hand.

Wenn du die ATXMegaA?U Serie einsetzen würdest dann gibt es dort den 
Timer Modus 2. Damit werden die 4x 16Bit Timer0 eines Ports auf 8x 8Bit 
Timer umprogrammiert. Du hast dann auf 8 Pins des Ports 8 PWMs mit 8Bit 
Auflösung. Dieses neue Feature ist mir auch erst in den letzten Tagen 
aufgefallen und dürfte für dich am passensten sein ;)

Gruß hagen

von Stefan F. (stefan1987)


Lesenswert?

Hagen Re schrieb:
> Eine 8 Bit PWM hat 256 verschiedene DutyCycles die einstellbar sind. Das
> Array hat also 256 Elemente in denen du für jeden Pin des Ports die
> Zustände abspeicherst. Der DMA kopiert dieses Array permanent in den
> Port und das mit der gleichen Frequenz mit der normalerweise dein Timer
> läuft. Wenn du nun auf Pin 0 vom Port einen DutyCycle von 20% einstellen
> möchtest dann muß das unterste Bit aller 256 Bytes im Array zu 20% auf 1
> und zu 80% auf 0 gesetzt sein. Man macht also das was der PWM-Timer
> autom. macht von Hand.


Ah ok, jetzt hab ichs verstanden. Dann ist diese Methode für meine 
Zwecke wohl doch eher ungeeignet, da ich die duty cycles doch recht oft 
ändere bzw in den unterschiedlichen Modi viele verschiedene 
Einstellungen verwende.
Da für jeden Schritt/Einstellung 256 Byte zu reservieren wäre wohl zu 
viel am Ende.

Da es nicht wirklich so auf die Prozessorauslastung ankommt und ich mit 
1-2% Auslastung für die PWM durchaus leben kann werde ich weiter 
versuchen die Routine aus dem Forum zu portieren.

Danke trotzdem für die Antworten :)

Gruß Stefan

von Stefan F. (stefan1987)


Angehängte Dateien:

Lesenswert?

Hello again...

hab nun nochmal die Beispiele von Atmel angeschaut und das Xmega Manual 
mehrmals durchgelesen, also die Abschnitte Timer Counter und Interrupts,
aber bin immernoch ratlos.
Ich habe es zwar geschafft das er mitlerweile einmal den CCA Interrupt 
aufruft, jedoch nur einmal. Obwohl in der ISR der Compare Wert ja immer 
upgedatet wird.

Hab ich den Timer falsch konfiguriert? Oder geht das mit dem 
Outputcompare anders ?

Kann mir jemand einen Tipp geben ?

Grüße Stefan

EDIT: Was die Fehlersuche bzw das debuggen so schwer macht ist das 
einige Variablen wegoptimiert werden beim Kompilieren, zumindest sagt 
das AVR Studio.

von bluematrix (Gast)


Lesenswert?

Hallo Hagen,
könntest du deine DMA PWM Lösung posten?

... bzw wie muss ich den Timer beim Xmega einstellen, dass ich die 
Lösung aus dem Forum verwenden kann. 
(http://www.mikrocontroller.net/articles/Soft-PWM)
Ich habe es soeben schon versucht, aber irgendwie funktioniert es nicht.

von Florian G. (stromflo)


Lesenswert?

Guten Morgen,

probiere es mal mit dieser Timerkonfiguration:
1
void timer_init(void){
2
        // Compare Interrupts A,B,C,D aktivieren High
3
  TCC1.INTCTRLB = TC_CCAINTLVL_HI_gc;
4
  TCC1.CTRLA = TC_CLKSEL_DIV8_gc;
5
  TCC1.PER = 65535;
6
}

Hatte zumindest mit einem ähnlichen Code schon mal eine funktionierende 
Variante....
Hatte den intelligenten Ansatz vom Forum schon mal verwendet. Welchen 
Controller verwendest du?

Gruß Flo

: Bearbeitet durch User
von Timmo H. (masterfx)


Lesenswert?

Also bei mir lüppt dat so, wenn ich Timer folgendermaßen konfiguriere:
1
  TCD1.CTRLA = TC_CLKSEL_DIV8_gc;
2
  TCD1.CTRLB = TC1_CCAEN_bm;
3
  TCD1.CCA = 255;
4
  TCD1.PER = 0xFFFF;
5
  TCD1.INTCTRLB = TC_CCAINTLVL_LO_gc;  // Low Level Interrupt
Ports so:
1
#define F_PWM         100L               // PWM-Frequenz in Hz
2
#define PWM_PRESCALER 8L                  // Vorteiler für den Timer
3
#define PWM_STEPS     1024                // PWM-Schritte pro Zyklus(1..1024)
4
#define PWM_PORT      PORTA.OUT              // Port für PWM
5
#define PWM_DDR       PORTA.DIR               // Datenrichtungsregister für PWM
6
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle
F_CPU ist 32000000

Und die CCA ISR dann so:
1
 
2
ISR(TCD1_CCA_vect) {
3
    static uint16_t pwm_cnt;                     // ändern auf uint16_t für mehr als 8 Bit Auflösung
4
    uint16_t tmp;                                // ändern uint16_t oder uint32_t für mehr Kanäle
5
 
6
    TCD1.CCA += isr_ptr_time[pwm_cnt];
7
    tmp    = isr_ptr_mask[pwm_cnt];
8
 
9
    if (pwm_cnt == 0) {
10
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM
11
                                                // zusätzliche PWM-Ports hier setzen
12
        pwm_cnt++;
13
    }
14
    else {
15
        PWM_PORT &= tmp;                        // Ports löschen
16
                                                // zusätzliche PWM-Ports hier setzen
17
        if (pwm_cnt == pwm_cnt_max) {
18
            pwm_sync = 1;                       // Update jetzt möglich
19
            pwm_cnt  = 0;
20
        }
21
        else pwm_cnt++;
22
    }
23
24
25
}

: Bearbeitet durch User
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
Noch kein Account? Hier anmelden.