Forum: Mikrocontroller und Digitale Elektronik Atmega8 Lichtdimmer, Timing Problem mit Volatile


von Oliver D. (dewaldo)


Lesenswert?

Ich hoffe ihr könnt mir ein bischen Detailwissen vermitteln mit 
folgendem Problem.

Schritt1:
Ich habe einen Lichtdimmer als Phasenanschnitt mit einem Atmega8 und 
Triaczündung über Optodiac. Alles inkl. Netznulldurchgang wird 
zuverlässig erkannt und der Dimmalgorhytmus funktioniert eigentlich 
tadellos.

Schritt2:
Nun habe ich den Leistungsteil doppelt ausgeführt, also 2 Optodiacs und 
Triacs, um unabhängig 2 Lampen zu schalten und zu dimmen.
Das Problem ist nun die Zündzeitpunkte der beiden Triacs, die unabhängig 
geschaltet werden sollen.

Die vorherige Variante mit nur 1 Triac habe ich über Timer1 Overflow 
Interrupt gelöst, in dem ich bei Erkennung des Nulldurchgangs in der 
INT0 ISR den Timer1 vorgeladen habe und bei dessen Überlauf das Triac 
gezündet hat.
Nun habe ich aber 2 verschiedene Zündzeitpunkte und kann den Timer1 OVL 
nicht mehr benutzen.

Nun habe ich folgendes gemacht:

1.) Nulldurchgang wird erkannt -> INT0 Service Routine wird ausgeführt.
2.) In der INT0 ISR warte ich mit der Funktion _delay_us nun eine 
Zeitspanne und danach setze ich den entspr. Ausgang, zünde also das 
Triac. Somit könnte ich 2 Triacs zu verschiedenen Zeitpunkten zünden. 
Getestet habe ich das und es funktionert mit fest vorgegebenen werten.
3.) Ich möchte aber in der MAIN Schleife eine globale Variable 
(volatile) verändern, quasi den Helligkeitswert für das Licht. In der 
INT0 ISR soll dann dieser globale Wert als Vorgabe für die _delay_us 
Funktion genutzt werden.
-> Dies klappt leider nicht. Setze ich z.B. in der ISR einen festen 
Wert, z.B. _delay_us(8000), dann habe ich ein schön konstant gedimmtes 
Licht.
Gehe ich den Weg über eine globale Variable, z.B. "volatile int16_t 
Test=8000" und schreibe in der ISR dann _delay_us(Test); dann klappt es 
nicht mehr. Das Licht flackert wie verrückt, weil das Timing nicht mehr 
stimmt. D.h. die Übergabe einer globalen Variable an die ISR scheint 
sehr viel Zeit zu benötigen.

Welche Möglichkeiten gibt es, die beschriebene Funktion umzusetzen, also 
einen gewünschten Wert an die ISR zu übergeben, aber ohne dass es dabei 
zu zeitlichen Verzögerungen kommt. Eine statische Variable ohne Volatile 
habe ich auch getestet, dann klappt es wieder ohne Flackern, nur kann 
ich dann im Main Programm die Variable nicht verändern und das bringt 
mich nicht weiter.

P.s. Verwendet wird ein Atmega8 mit internem Oszillator 1 MHz.

von XXXX (Gast)


Lesenswert?

Hallo

In fast jedem Beitrag mit Wartezeiten steht, benutze
_delay_us
IMMER mit einem konstanten Wert als Parameter, benutze NIEMALS eine
Variable, weil das nicht funktioniert. Das Ergebnis siehst du.

Gruß

von Karl H. (kbuchegg)


Lesenswert?

Oliver Dewald schrieb:

> P.s. Verwendet wird ein Atmega8 mit internem Oszillator 1 MHz.

Wenn du den Takt auf 8Mhz hochdrehen würdest, gäbe es auch noch eine 
andere Möglichkeit
Grund: delays willst du eigentlich nicht haben, schon gar nicht 
innerhalb einer ISR

Der Weg geht so

Du benutzt den Timer 0 als interne Uhr.

Wenn du den 8 Bit Timer benutzt, dann generiert dir der bei 8Mhz Takt 
und einem Vorteiler von 1 exakt 31250 Interrupts in der Sekunde oder 
anders ausgedrückt: Alle 0.000032 Sekunden einen.

Das ist dein Basistakt

Kommt dein INT0 daher, so setzt du eine gloable Variable für jede Lampe 
auf einen entsprechenden Wert zwischen 0 und 625 ( == 31250 / 50)

In der ISR werden nacheinander die jeweiligen Zähler wieder 
heruntergezählt und wenn 0 erreicht wird, wird der Triac gezündet.

-> Du kannst nahzu beliebig viele Dimmstufen bauen ohne einen einzigen 
delay zu benötigen.

Einzige Einschränkung: Deine ISR muss innerhalb von 256 Taktzyklen ihre 
Berechnungen fertig haben, weil dann schon der nächste Timer Interrupt 
kommt.
1
uint16_t dimmer1;
2
uint16_t dimmer2;
3
volatile uint16_t vorladeWert1;
4
vaoltile uint16_t vorladeWert2;
5
6
ISR( TIMER0_OVF_vect )
7
{
8
  if( dimmer1 > 0 ) {
9
    dimmer1--;
10
    if( dimmer1 == 0 )
11
      zünde Triac 1
12
  }
13
14
  if( dimmer2 > 0 ) {
15
    dimmer2--;
16
    if( dimmer2 == 0 )
17
      zünde Triac 2
18
  }
19
}
20
21
ISR( INT0_vect )
22
{
23
  dimmer1 = vorladeWert1;
24
  dimmer2 = vorladeWert2;
25
}
26
27
int main()
28
{
29
  ...
30
  Timer initialisieren auf  Vorteiler 1 / Overflow Interrupt
31
32
  sei();
33
34
  while( 1 ) {
35
    ...
36
    vorladeWert1 = .... berechne Wert für Lampe 1 ( 0..625)
37
    ...
38
    vorladeWert2 = .... berechne Wert für Lampe 2 ( 0..625)
39
    ...
40
  }
41
}

von Oliver D. (dewaldo)


Lesenswert?

_delay_us immer mit festen Werten benutzen mache ich normalerweise auch, 
ich habe nur nicht verstanden, warum eine lokale Variable, die ich an 
die Funktion _delay_us übergebe einen Unterschied hat zur einer globalen 
Variable, die ich dort übergebe. Ich könnte mir zwar denken, dass der 
Compiler das sieht und intern dann gleich einen festen Wert einträgt, es 
sich deswegen genauso verhält wie eine Konstante, aber es ist ja 
trotzdem eine lokalte VARIABLE, die ihren Wert zumindest in der ISR 
ändern könnte.
Naja ...

Dann zum Beitrag von kbuchegg:

Vielen Dank für die ausführliche Antwort. Ich muss zugeben, dass sich 
der Ansatz garnicht schlecht anhört. Ich muss damit mal ein bischen 
experimentieren, evtl. kann man es wirklich auf diese Art angehen. Ich 
muss schauen, wie ich das mit der Triac Zündung dann mache, da ich das 
Signal auch wieder wegnehmen muss, damit der Triac sich im Nulldurchgang 
auch selbst löscht und nicht sofort wieder leitet. Es sind auch nicht 50 
sondern 100 INT0 Interrupts pro Sekunde wegen der Sinuswelle. Ich denke 
ich probiere es so wie beschrieben und lege noch eine 3. Variable an, 
z.B. Dimmer1, Dimmer2, Loeschzeit. Diese wird dann auf den max. 
spätesten Löschzeitpunkt vor dem Nulldurchgang eingestellt und setzt bei 
Erreichen die Ausgänge zurück. Ich muss dann die beiden anderen, Dimmer1 
und Dimmer2 bei ==1 abfragen und anschließend nochmal um 1 erniedrigen, 
damit nur bei ==1 das Triac zündet.

Ich werde mal nach dem Wochenende berichten, ob ich damit zum Erfolg 
komme. Falls es noch andere Vorschläge gibt, ich bin für alles gerne 
offen.

Rein Interesse halber interessiert mich aber dennoch, warum die 
_delay_us Funktion solche Schwierigkeiten bei globalen Variablen hat ?

von Karl H. (kbuchegg)


Lesenswert?

Oliver Dewald schrieb:

> Vielen Dank für die ausführliche Antwort. Ich muss zugeben, dass sich
> der Ansatz garnicht schlecht anhört.

Alles, mit dem man einen delay eliminieren kann, hört sich nicht nur gut 
an, sondern ist eine ausgesprochen gute Idee

> Ich muss damit mal ein bischen
> experimentieren, evtl. kann man es wirklich auf diese Art angehen. Ich
> muss schauen, wie ich das mit der Triac Zündung dann mache, da ich das
> Signal auch wieder wegnehmen muss, damit der Triac sich im Nulldurchgang
> auch selbst löscht und nicht sofort wieder leitet.

Gib das Signal bei einem Zählerstand von 1 auf den Triac und bei 0 
nimmst du es wieder weg.

Oder bei 0 ein, so wie im Pseudocode angegeben, und bei jedem ISR Aufruf 
am Anfang aus.
1
ISR( TIMER0_OVF_vect )
2
{
3
  Triac1 aus
4
  Triac2 aus
5
6
  if( dimmer1 > 0 ) {
7
    dimmer1--;
8
    if( dimmer1 == 0 )
9
      zünde Triac 1
10
  }
11
12
  if( dimmer2 > 0 ) {
13
    dimmer2--;
14
    if( dimmer2 == 0 )
15
      zünde Triac 2
16
  }
17
}

auch dann haben die Triacs den Zündimpuls nur über den Zeitraum von 
einem ISR Aufruf bis zum nächsten anliegen und danach ist er wieder weg.

> Es sind auch nicht 50
> sondern 100 INT0 Interrupts pro Sekunde wegen der Sinuswelle.

OK.
Wusste nicht, ob du den Interrupt auf aufsteigende/absteigende oder 
beide Flanken hast.

Ist ja kein Problem.
Dann hast du anstelle von 6-hundertirgendwas dimmstufen nur noch 
3-hundertirgendwas. Sollte kein Beinbruch sein :-)

> ich probiere es so wie beschrieben und lege noch eine 3. Variable an,
> z.B. Dimmer1, Dimmer2, Loeschzeit.

Kannst du machen.
Ist aber nicht notwendig.
Wenn du den Ausgang bei 1 ein und bei 0 wieder ausschaltest, tuts das 
auch.

> Rein Interesse halber interessiert mich aber dennoch, warum die
> _delay_us Funktion solche Schwierigkeiten bei globalen Variablen hat ?

Hat sie nicht.
Du kannst ganz einfach keine Variablen benutzen. Weder lokale noch 
globale.
Die delay Funktionen sind darauf angewiesen, dass der Compiler alles 
wegoptimieren kann. Kann er das nicht, dann stimmt das alles hinten und 
vorne nicht. Je nachdem wieviel der Optimizer noch optimieren konnte, 
kriegst du dann unterschiedliche Ergebnisse.

Und nein: 'Sieht so als als ob' ist noch kein Nachweis für 'funktioniert 
zuverlässig'

von Oliver D. (dewaldo)


Lesenswert?

Karl heinz Buchegger schrieb:
>> Ich muss damit mal ein bischen
>> experimentieren, evtl. kann man es wirklich auf diese Art angehen. Ich
>> muss schauen, wie ich das mit der Triac Zündung dann mache, da ich das
>> Signal auch wieder wegnehmen muss, damit der Triac sich im Nulldurchgang
>> auch selbst löscht und nicht sofort wieder leitet.
>
> Gib das Signal bei einem Zählerstand von 1 auf den Triac und bei 0
> nimmst du es wieder weg.

Mmmh, erstmal vielen Dank für die Anwort mit dem _delay_us, werde mir 
das dann für spätere Projekte im Hinterkopf behalten.

Das mit dem Triacsignal zurücknehmen bei Wert ==0, ist das lange genug 
für das Triac zum Zünden ? Nach obiger Berechnung und einem Takt von 8 
MHz wären dann zwischen Zündzeitpunkt und Löschzeitpunkt ohne 
Verarbeitungszeit des Optodiacs nur knapp 30 us. Ich weiß nicht ob das 
ausreicht, das Triac definiert zu zünden. Alternativ verzichte ich aber 
vielleicht einfach noch auf 50 Dimmstufen und schalte das Triac beim 
Wert == 50 und bei == 0 nehme ich den Ausgang zurück, dann reicht es auf 
jeden Fall und mit gut 250 Dimmstufen komme ich auch noch ausreichend 
genug hin um fließende Dimmverläufe zu erzeugen. Bei meiner momentanen 1 
Triac Lösung und Timer1 OVF habe ich etwa 1000 Dimmstufen bei einem 1 
MHz Takt.

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.