Forum: Mikrocontroller und Digitale Elektronik ATMega2560 FastPWM DutyCycle Umschaltung


von Alex Z. (alexander_z49)


Lesenswert?

Hallo Leute,

ich sitze jetzt schon seit gestern Abend an einer FastPWM, bei der ich 
das Tastverhältnis bei einem Timer Compair Match umschalten will.

Anbei der Code:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define F_CPU 8000000UL
5
6
volatile uint16_t WS2812B0CODE = 20000;
7
volatile uint16_t WS2812B1CODE = 10000;
8
volatile uint16_t WS2812BTSET = 0;
9
volatile uint8_t Banging = 0x1;
10
11
ISR(TIMER1_COMPB_vect){
12
  if((Banging & 0x1)== 0x1){
13
    OCR1B = 0x0F11;
14
    Banging ^= (1<<0);
15
    PORTB ^= (1<<PB7);
16
  }else{
17
    OCR1B = 0xF111;
18
    Banging ^= (1<<0);
19
    PORTB ^= (1<<PB7);
20
  }
21
}
22
void PWMsetup(){
23
  DDRB = (1<<PB6);
24
  // Timer einschalten
25
  PRR0 = (0<<PRTIM1);
26
  // Verhalten des OC1B bei einem Compair Match
27
  TCCR1A = (1<<WGM11)|(1<<WGM10)|(1<<COM1B1) | (0<<COM1B0);
28
  //Fast PWM Mode und Clock Prescaler
29
  TCCR1B = (1<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(0<<CS10);
30
  //Timer Interrupt Flags aktivieren
31
  TIMSK1 = (1<<OCIE1B)|(1<<TOIE1);
32
  // Output Compare Register Timer 1A auf Top
33
  OCR1A = 0xFFFF;
34
  // Output Compare Register Timer 1B Matchwert
35
  OCR1B =0x0FFF;
36
}
37
38
int main(void)
39
{
40
  DDRB = (1<<PB7);
41
  PWMsetup();
42
  sei();
43
  while (1) 
44
    {
45
46
    };
47
}

Programmiert wird im Atmel Studio 7 mit einem AVR ISP mkII, das Flashen 
geht ohne Probleme.

Am Oszi sehe ich auch die 0x0FFF, für OCR1B, die in PWMsetup gesetzt 
werden. Tastverhältnis und Frequenz passen soweit nach der 
initialisierung.
Es ist PWM Mode 15 eingestellt.

Mit
1
PORTB ^= (1<<PB7)
 sehe ich auch das Umschalten auf dem Oszi, wenn der µC in die ISR geht.

Was jetzt nicht funktioniert, ist
1.:
In der ISR wird immer nur der True If-Zweig abgearbeitet. Das Toggeln 
von Banging funktioniert nicht.

Je nach dem, wie ich Banging bei der Definition initialisiere, springt 
es bei 1 immer in den oberen Teil und bei 0 immer in den unteren Teil 
der If-Abfrage.

2.:
Der Wert für OCR1B wird nicht neu gesetzt, wenn die ISR abgearbeitet 
wird.

Hier habe ich auch schon versucht OCR1BH und OCR1BL getrennt von 
einander zu beschreiben, bringt aber auch nichts.

Sieht jemand einen Fehler und kann mir hier weiterhelfen?

Alex

von Harry L. (mysth)


Lesenswert?

Alex Z. schrieb:
> ISR(TIMER1_COMPB_vect){
>   if((Banging & 0x1)== 0x0){
>     OCR1B = 0x0F11;   //^^^ war bereits 1 und wurde erneut auf 1 gesetzt - muß 
umgekehrt sein
>     Banging ^= (1<<0);
>     PORTB ^= (1<<PB7);
>   }else{
>     OCR1B = 0xF111;
>     Banging ^= (1<<0);
>     PORTB ^= (1<<PB7);
>   }
> }

von S. Landolt (Gast)


Lesenswert?

Wo ist die ISR für Timer1-Overflow?

von c-hater (Gast)


Lesenswert?

Alex Z. schrieb:

> ich sitze jetzt schon seit gestern Abend an einer FastPWM, bei der ich
> das Tastverhältnis bei einem Timer Compair Match umschalten will.

Das geht nicht. Die OCRnx-Werte werden in allen PWM-Modi durch die 
Hardware gepuffert und erst beim Overflow übernommen. Deswegen ist es 
grundsätzlich ziemlich tödlich, sie in den OCnx-ISRs zu setzen, dann 
verzögert sich nämlich das Eintreten der Wirksamkeit mal um einen 
Zyklus, mal um zwei, je nachdem ob die OCnx-ISR noch zum Zuge kommt, 
bevor der Overflow auftritt oder erst danach, was wiederum von der 
Systemlast durch konkurrierende ISRs und vom zuletzt wirksam gewordenen 
Duty-Wert abhängt. Das ist SCHEISSE.

Merke also: Duty-Werte in PWM-Modi IMMER in der OVF-ISR setzen. Dann 
ist (ausser bei Überlast des Systems) garantiert, dass sie im NÄCHSTEN 
Zyklus wirksam werden.

Aber diese Sache scheint garnicht wirklich dein Problem zu sein, sondern 
vielmehr dies:

> In der ISR wird immer nur der True If-Zweig abgearbeitet. Das Toggeln
> von Banging funktioniert nicht.

Das kann nicht sein, jedenfalls nicht mit dem gezeigten Code. Es muss 
also noch anderen Code geben, der an Banging rumhantiert und diesen Code 
hast du uns nicht gezeigt. Das sagen die Gesetze der Logik. Sehr 
wahrscheinlich gibt es also noch eine OVF-ISR, deren Fehlen S.Landolt ja 
auch schon angemeckert hat.

Und sehr wahrscheinlich steht da auch wieder drin:
Banging ^= (1<<0);

Damit wird erklärlich, warum "immer" nur ein Zweig deiner OC-ISR 
abgearbeitet wird. Tatsächlich kann du aber auch den anderen Zweig 
abarbeiten lassen, eben wegen des am Anfang dargestellten Sachverhalts. 
Du mußt nur Duty auf eine Wert sehr knapp vor dem Überlauf setzen, dann 
kehrt sich nämlich zumindest kurzzeitig die Phasenlage dieses Bullshits 
um, den du da programmiert hast...

von S. Landolt (Gast)


Lesenswert?

Also meine Vermutung war eher, dass die Timer1-OVF-ISR tatsächlich gar 
nicht existiert und das Programm immer von vorne beginnt; bin aber kein 
C-Programmierer.

von c-hater (Gast)


Lesenswert?

S. Landolt schrieb:

> Also meine Vermutung war eher, dass die Timer1-OVF-ISR tatsächlich gar
> nicht existiert und das Programm immer von vorne beginnt; bin aber kein
> C-Programmierer.

^= in C entspricht eor in Asm (im Falle von Portpins ggf. auch einer 
Ausgabe auf PIN), also: letztlich wird das Bit getoggelt.

Dementsprechend ist durch die Gesetze der Logik klar, dass das 
beobachtete Verhalten nur dann eintreten kann, wenn an anderer Stelle 
synchron (aber zeitversetzt) getoggelt wird. Dafür kommt ernsthaft nur 
ein Interrupt desselben Timers in Frage. Sehr wahrscheinlich halt der 
OVF. Es könnte aber theoretisch aber auch der andere OC-Kanal des 
gleichen Timers sein...

von Alex Z. (alexander_z49)


Angehängte Dateien:

Lesenswert?

Hallo liebe Leute,

ich habe mir das ganze nun 3 mal durchgelesen und das Programm nochmal 
abgeändert zum Testen.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define F_CPU 8000000UL
5
6
volatile uint16_t WS2812B0CODE = 20000;
7
volatile uint16_t WS2812B1CODE = 10000;
8
volatile uint16_t WS2812BTSET = 0;
9
volatile uint8_t Banging = 0x0;
10
11
ISR(TIMER1_OVF_vect){
12
  if(Banging & 0x1){
13
    OCR1B = 9000;
14
    Banging ^= (1<<0);
15
  }else{
16
    OCR1B = 12000;
17
    Banging ^= (1<<0);
18
  }
19
}
20
void PWMsetup(){
21
  DDRB = (1<<PB6);
22
  // Timer einschalten
23
  PRR0 = (0<<PRTIM1);
24
  // Verhalten des OC1B bei einem Compair Match
25
  TCCR1A = (1<<WGM11)|(1<<WGM10)|(1<<COM1B1) | (0<<COM1B0);
26
  //Fast PWM Mode und kein Clock Prescaler
27
  TCCR1B = (1<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(0<<CS10);
28
  //Timer Interrupt Flags aktivieren
29
  TIMSK1 = (1<<TOIE1);
30
  // Output Compare Register Timer 1A auf Top
31
  OCR1A = 19000;
32
  // Output Compare Register Timer 1B Matchwert
33
  OCR1B = 6000;
34
}
35
36
int main(void)
37
{
38
  DDRB = (1<<PB7);
39
  PORTB ^= (1<<PB7);
40
  PWMsetup();
41
  sei();
42
  while (1) 
43
    {
44
45
    };
46
}

Der Code funktioniert auch, siehe das Bild vom Scope.

Zu den einzelnen Kommentaren:

Im ersten Code gab es einfach keine Timer1_OVF ISR, nur das Enable Bit 
wurde gesetzt.

Ich habe mir das Datasheet nochmal zu Rate gezogen. Bei PWM Mode 15 
werden die OCRxn bei Bottom aktualisiert, so wie c-hater das geschrieben 
hat. d.h. wenn die OVF ISR ausgeführt wird, kann ich schon den Wert für 
den nächsten Zyklus schreiben. Bei einer Zielfrequenz von 800kHz sind 
das ca. 20 Takte.

Ich hatte einfach einen Denkfehler. Das ganze beim Compair Match zu 
setzen war nicht zielführend.

Das mit dem Banging funktioniert jetzt auch. Ka warum das vorher nicht 
ging.

Alex

von S. Landolt (Gast)


Lesenswert?

> Im ersten Code gab es einfach keine Timer1_OVF ISR,
> nur das Enable Bit wurde gesetzt.

Was passiert dann beim Overflow? Also zumindest mein C-Compiler setzt an 
eine solche Stelle 'jmp 0' (mit dem Kommentar "bad_interrupt"), folglich 
beginnt das Programm immer wieder von vorne.

von Deutsch Leera (Gast)


Lesenswert?

Alex Z. schrieb:
> Ich habe mir das Datasheet nochmal zu Rate gezogen.

Vielleicht solltest du auch mal einen Duden oder ähnliches
zu Rate ziehen um deine Rechtschreibung zu compairen.

von Alex Z. (alexander_z49)


Lesenswert?

Hi,

also der Compiler hat zumindest keinen Fehler angezeigt angezeigt. 
Warnungen oder Informationen bin ich mit grad nicht sicher. Ist das 
Atmel Studio 7.


S. Landolt schrieb:
>> Im ersten Code gab es einfach keine Timer1_OVF ISR,
>> nur das Enable Bit wurde gesetzt.
>
> Was passiert dann beim Overflow? Also zumindest mein C-Compiler setzt an
> eine solche Stelle 'jmp 0' (mit dem Kommentar "bad_interrupt"), folglich
> beginnt das Programm immer wieder von vorne.


Eine Frage hat sich aber noch ergeben:

Der µC wird auf dem Arduino-Board mit einem 16MHz Quarz versorgt. In den 
Fuse Bits ist der externe Quarz eingestellt. Laut Datenblatt berechnet 
sich die Frequenz bei der Fast PWM mit f_pwm= f_clockIO/(N*(1+TOP)), das 
kommt auch hin bei den 800kHz. Leider reichen die 20 Zählvorgänge nicht 
um die neuen Tastverhältnise ins OCR1B zu schreiben. Gibt eine 
Möglichkeit zu berechnen, wie lange eine einzelne Operation im C-Code 
braucht?

Alex


P.S.: Rechtschreibfehler dürfen behalten werden.

von S. Landolt (Gast)


Lesenswert?

> der Compiler hat zumindest keinen Fehler angezeigt

Das "bad_interrupt" wird auch nicht angezeigt, sondern steht im 
erzeugten Assembler-listing, und zwar bei allen ISR-Adressen, die nicht 
definiert wurden, in unserem Fall also auch bei 'OVF1addr  = 0x0028 ; 
Timer/Counter1 Overflow'.

> Gibt eine Möglichkeit zu berechnen ...

Da fällt mir jetzt wieder nur das Assembler-listing ein; aber wie schon 
oben einmal erwähnt, bin ich eigentlich kein C-Programmierer.

von S. Landolt (Gast)


Lesenswert?

PS:
Der "bad_interrupt" wird ja erst in dem Augenblick zu einem solchen, 
wenn er freigegeben ist und ausgelöst wird.

von S. Landolt (Gast)


Lesenswert?

PPS:
Was mir noch unklar ist: woher kommen die 800 kHz resp. 20 Takte? Ich 
sehe nur etwas im ms-Bereich.

von Alex Z. (alexander_z49)


Lesenswert?

Hallo,


S. Landolt schrieb:
> PPS:
> Was mir noch unklar ist: woher kommen die 800 kHz resp. 20 Takte? Ich
> sehe nur etwas im ms-Bereich.

Der µC taktet mit 16MHz und die PWM Frequenz berechnet sich zu f_pwm= 
f_clockIO/(N*(1+TOP)). Damit ich die 800kHz für die Ansteuerung der 
WS2812 bekomme ist der Top Wert 19. Somit komme ich auf 20 Takte.

Alex

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Alex Z. schrieb:

> kommt auch hin bei den 800kHz. Leider reichen die 20 Zählvorgänge nicht
> um die neuen Tastverhältnise ins OCR1B zu schreiben. Gibt eine
> Möglichkeit zu berechnen, wie lange eine einzelne Operation im C-Code
> braucht?

Nicht wirklich. Deswegen nimmt man für derart zeitkritische Sachen auch 
Assembler. Da kann man das nämlich "berechnen".

20 Takte sind schon sehr knapp, denn tatsächlich stehen nur 19 davon für 
den Interrupt zur Verfügung, wenn das Hauptprogramm auch noch was tun 
soll (das würde dann aber effektiv mit einer "virtuellen Taktrate" von 
nur noch 800kHz laufen und müsste obendrein ebenfalls bestimmte 
Timing-Restriktionen erfüllen, die in C nur schwer bis garnicht 
kontrollierbar sind.

Dazu kommt, dass von den 19 Takten für den Interrupt allein 8 für den 
minimalen Interruptrahmen benötigt werden und in C auch noch zwingend 2 
Takte für den Sprung vom Interruptvektor zur ISR. Es bleiben also nur 
maximal 9 Takte für Nutzcode in der ISR. Das ist wahrlich nicht viel für 
die an dieser Stelle nötige Funktionalität. Das kann nur funktionieren, 
wenn man einige Register global für die ISR reserviert, was in C nur 
sehr eingeschränkt möglich ist, in Assembler allerdings kinderleicht.

Fazit: was du machen willst, geht nur in Assembler wirklich. Also mach's 
in Assembler.

von S. Landolt (Gast)


Lesenswert?

Und WS2812 muss/soll unbedingt mit 800 kHz angesteuert werden? Nun gut, 
ich kenne mich da überhaupt nicht aus.
  20 Takte für eine solche Aktion - c-hater hat alles Nötige dazu 
gesagt. Ich könnte allenfalls noch vorschlagen, den kleinen Bruder 
ATmega1284 zu verwenden, der erlaubt laut Datenblatt 20 MHz, bei mir 
werden zwei seit längerem mit 24 MHz übertaktet; ist aber in diesem Fall 
weder der große Sprung nach vorn noch besonders schön.

von S. Landolt (Gast)


Lesenswert?

> in C auch noch zwingend 2 Takte für den Sprung
> vom Interruptvektor zur ISR

Ich sollte mir wohl doch einen neueren/anderen C-Compiler beschaffen, 
meiner macht an dieser Stelle sogar ein 'jmp' mit 3 Takten.

von c-hater (Gast)


Lesenswert?

S. Landolt schrieb:

>   20 Takte für eine solche Aktion - c-hater hat alles Nötige dazu
> gesagt.

Nö, leider habe ich das nicht.

> Ich könnte allenfalls noch vorschlagen, den kleinen Bruder
> ATmega1284 zu verwenden

Das Wörtchen "kleiner" in deiner Formulierung hat mich nämlich stutzen 
lassen.

Es ging um einen Mega2560, das war mir doch glatt durchgerutscht. Beim 
dem sieht die Sachen nämlich sogar noch schlechter aus, als von mir 
beschrieben, weil bei dem wegen des 22Bit-PC der minimale Interuptframe 
nicht 8, sondern 10 Takte braucht. Also bleiben nur noch 7 Takte für den 
Nutzcode der ISR, keine 9.

Mit 9 hätte ich es vielleicht noch realisieren können, mit 7 traue ich 
es mir ganz sicher nicht mehr zu. Aus meiner Sicht bleibt für einen 
Mega2560@16MHz bloß die Busy-Loop zur Lösung des Problems, also nix 
Interrupt.

Die Ausgabe per PWM kann man dabei beibehalten, das entschärft die 
Timing-Anforderungen für die Busy-Loop und senkt damit den Aufwand  für 
deren Entwicklung. Das könnte dann auch schon wieder in C funktionieren. 
Aber Interruptsperre während der Ausgabe bleibt unumgänglich.

Mega1284P@20Mhz ist aber trotzdem keine schlechte Idee. Der hat nämlich 
gebufferten SPI-Power in seinen UARTs. Damit kann man das anders/besser 
lösen und es bleibt dann sogar genug Luft im Timing für einen (!) 
konkurrierenden Interrupt und für den Code in main bleiben sogar über 
40% der Rechenzeit verfügbar. Entsprechenden Code habe ich vor einiger 
Zeit hier schonmal gepostet. Und zwar sowohl für die Ansteuerung der 
WS28xx @800kHz als auch für eine gleichzeitige interruptbasierte 
Ausgabe von DDS-Sound@78kHz.

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.