Forum: Mikrocontroller und Digitale Elektronik AVR Helligkeitsregulierung per Interrupt


von Philipp F. (nerdture)


Lesenswert?

Hallo,
ich bin noch immer an meinem Wecker-Projekt (ATMEGA8, intern 4MHz RC, 
LED-7Seg) und wollte jetzt ein feature einbauen, sodass man die 
Helligkeit vom Display regulieren kann.
Das soll sozusagen wie PWM funktionieren.

Da ich nicht soviele Pins zur verfuegung habe und auch keine weiteren 
ICs verwenden wollte wird das Display momentan gepulst. Es ist immer nur 
eine der 4 Ziffern an, und die werden bisher einfach im Kreis 
durchlaufen, bei jedem Interrupt gehts eins weiter. Der Interrupt ist 
der OutputCompareMatch vom 16-bit Timer 1.

Zur Helligkeitsregulierung habe ich es jetzt so gemacht, dass er bei 
jedem Interrupt den "status" wechselt (0/1). Und jedes mal das 
OutputCompareMatch Register aendert. Im Status 0 waere das Display dann 
komplett an, im Status 1 wuerde er eine Ziffer weiter gehen und diese 
anschalten.
Man soll dann das Verhaeltnis der Dauer von An und Aus umstellen 
koennen.

Prinzipiell funktioniert das auch, aber ich vermute stark, dass hier 
irgendwelche Interrupts verschluckt werden, wenn das Verhaeltnis zu 
stark von 1 abweicht, weil dann das Display teilweise kurz flackert.
Ich koennte natuerlich das Verhaeltnis einfach strenger begrenzen, aber 
dann bringt die Helligkeitseinstellung nicht mehr soviel, wie loest man 
sowas also professionell?

Ich hoffe ihr koennt mir helfen. Wenn ich was wichtiges vergessen habe 
einfach fragen.
Philipp

von Hannes L. (hannes)


Lesenswert?

Ich würde den zweiten Compare-Interrupt des Timers nehmen, um das Licht 
auszuschalten. Also der eine Compare-Int macht das Multiplexing 
(Datenquelle umschalten, Digit umschalten, nächsten Multiplex-Termin 
festlegen und jetzt zusätzlich den Ausschalt-Termin des anderen 
Interrupts festlegen). Der andere Compare-Int erfolgt nun mit 
(einstellbarer) Verzögerung dem ersten Comp-Int und macht das Licht 
wieder aus. Somit kann man die Einschaltdauer feinstufig einstellen.

...

von Philipp F. (nerdture)


Lesenswert?

Ja stimmt, gar nicht gesehen, dass es zwei CompareRegister gibt. Aber 
mein Problem bleibt dann immer noch.
Das Problem ist naemlich der Grenzfall, wo Aus- und Einschalten zeitlich 
ganz nah zusammenrücken,wenn ich das Display sehr hell oder dunkel 
stelle. Also sehr dicht hintereinander die Interrupts kommen. Und in der 
einen Interruptroutine zum setzen der Segmente brauche ich schon ein 
paar kleine Rechnungen die kurz dauern.
An sich kein problem, das interrupt flag bleibt ja gesetzt, bis die 
routine fertig ist, aber evtl. wird dann mein wichtigster Interrupt 
verschluckt, der naemlich die Zeit zaehlt...

von Hannes L. (hannes)


Lesenswert?

Philipp F. wrote:
> Ja stimmt, gar nicht gesehen, dass es zwei CompareRegister gibt. Aber
> mein Problem bleibt dann immer noch.

Dann machst Du was Grundlegendes falsch...

> Das Problem ist naemlich der Grenzfall, wo Aus- und Einschalten zeitlich
> ganz nah zusammenrücken,wenn ich das Display sehr hell oder dunkel
> stelle. Also sehr dicht hintereinander die Interrupts kommen. Und in der
> einen Interruptroutine zum setzen der Segmente brauche ich schon ein
> paar kleine Rechnungen die kurz dauern.

Aha... Du rechnest in der ISR? Du solltest lieber in der Mainloop 
rechnen und in der ISR die bereitstehenden Werte nur kopieren. Dies 
verkürzt die ISRs dermaßen, dass es keine Konflikte mehr gibt.

> An sich kein problem, das interrupt flag bleibt ja gesetzt,

gesetzt?? Während der ISR ist es meiner Meinung nach gelöscht...

> bis die
> routine fertig ist, aber evtl. wird dann mein wichtigster Interrupt
> verschluckt, der naemlich die Zeit zaehlt...

Das lässt sich alles durch das Befolgen von drei wichtigen Regeln 
verhindern:

1. ISRs möglichst kurz halten
2. ISRs möglichst kurz halten
3. ISRs möglichst kurz halten

...

von Philipp F. (nerdture)


Lesenswert?

Ich weiss ja nicht was du mit kurz meinst. Also ich habe keine Schleifen 
o.ae. drin:

1
SIGNAL(SIG_OUTPUT_COMPARE1A)
2
{
3
  TCCR1B = 0; //stop (no new interrupts please)
4
  
5
  disp_state ^= 0b1;
6
  
7
  if(!disp_state) {
8
    PORTD = 0b1111111 | cur_playl; //Turn off all segments
9
    PORTC = (0b100<<cur_digit); //Switch to next digit (no dots)
10
    
11
    OCR1A = 1000-(clk_settings[SET_BRIGHTNESS]*150);
12
  } else {
13
    PORTC ^= dispdots; //Set dots to state wanted
14
    
15
    blink_cnt++;
16
    if(blink_cnt >= BLINKINTERVAL) {
17
      cur_blinkstate = cur_blinkstate?0:1;
18
      blink_cnt = 0;
19
    }
20
    
21
    if(!blink_mask[cur_digit] || cur_blinkstate)
22
      PORTD = transtbl[dispcont[cur_digit]] | cur_playl; //Set the new segments
23
    else
24
      PORTD = 0b1111111 | cur_playl; //We are Blinking and digit is currently off
25
    
26
    cur_digit = (cur_digit+1)%4;
27
28
    OCR1A = 63+(clk_settings[SET_BRIGHTNESS]*150);
29
  }
30
  
31
  TCNT1 = 0;
32
  TCCR1B = 0b1010; //prescaler = 8 //TOP = OCR1A, update immediatly
33
}


clk_settings[SET_BRIGHTNESS] kann man von 0 bis 5 einstellen

von Hannes L. (hannes)


Lesenswert?

Sorry, für C bin ich nicht zuständig, ich werkele in Assembler.

...

von Philipp F. (nerdture)


Lesenswert?

Also,
ich habe jetzt den Fehler gefunden. Das leichte Flackern lag naemlich 
gar nicht an diesem Interrupt sondern an einem anderen, der wirklich zu 
lang war, den ich aber leider auch nicht so einfach auslagern konnte.
Also habe ich den langen jetzt mit sei() "nestable" gemacht und es 
klappt wunderbar.

Ich habe es aber trotzdem noch mit zwei Compares (A und B) geloest. Ist 
einfach schoener

von Hannes L. (hannes)


Lesenswert?

Man kann in der ISR ein Flag (Semaphore) setzen und dieses in der 
Mainloop abfragen und bei Bedarf den (angemeldeten) Job erledigen. Damit 
bekommt man die ISRs schön klein und schnell. Das Freigeben des 
Interruptes in der ISR sollte man nur in Ausnahmefällen anwenden, da es 
recht störanfällig ist und zu Stackfehlern führen kann. Meist gibt es 
eine bessere Lösung, nämlich kurze schnelle ISRs.

...

von Ulrich P. (uprinz)


Lesenswert?

Also das geht eigentlich auch ohne Software ganz gut. Da Du ja eine 
Matrix verwendest, hast Du alle Segmente auf einem Treiber und alle 
Spalten auf einem Anderen. Nun müsstest Du also nur die Spalten Treiber 
noch mal über einen gemeinsamen FET schalten, der von einem der PWM 
Generatoren geschaltet wird. Wenn Du für die PWM ein vielfaches Deiner 
Spaltenfortschaltung verwendest kannst Du ohne Software Aufwand das 
ganze Display in vielen Stufen dimmen. Du musst nur das PWM Verhältnis 
durch das setzen des PWM Registers verändern.
Du müsstest lediglich einen LDR oder eine Photodiode an einen der ADC 
Eingänge klemmen.
Da gehen dann auch komplexere Dinge, wie z.B. eine verzögerte 
Nachführung der Helligkeit oder eine Änderung der Helligkeit bei Alarm 
oder was auch immer.

Gruß, Ulrich

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.