Forum: Mikrocontroller und Digitale Elektronik Problem mit PWM (while) und Uhr


von Michael P. (mpl)


Lesenswert?

Hallo,

hoffe ihr könnt mir weiterhelfen.


Ich hab für mein EVG nen dimmer gebastelt der ab einer bestimmten 
Uhrzeit das EVG auf bzw Runter dimmen soll.


Den Code hab ich zusammenkopiert von einer Seite...
1
#include <avr/io.h>
2
#include <stdio.h>
3
#include <inttypes.h>
4
#define F_CPU 1000000 // CPU-Frequenz in Hz
5
#include <util/delay.h>
6
#include <avr/interrupt.h>
7
#include "lcd-routines.h"
8
9
volatile int timer;
10
11
ISR(TIMER1_OVF_vect){
12
  TCNT1 = 49911;
13
  timer++;
14
}
15
16
17
int main(void){
18
19
        int  stunden=21, minuten=59, sekunden=00;   //hier bitte die Uhrzeit einstellen
20
  char sekundenstring[3], minutenstring[3], stundenstring[3];
21
22
    DDRB |= (1 << DDB3);
23
    TCCR2 |= (1 << COM21);
24
    // set none-inverting mode
25
26
    TCCR2 |= (1 << WGM21) | (1 << WGM20);
27
    // set fast PWM Mode
28
29
    TCCR2 |= (1 << CS21);
30
  
31
  //Timer 1 initialisieren:
32
  TIMSK |= _BV(TOIE1);   //aktivieren des Überlaufinterrupts von Timer1 
33
  TCCR1B = _BV(CS11) |  _BV(CS10);   //Prescaler = 64
34
  TCNT1 = 0xFFFF;     //Zählregister vorladen mit FFFF zum Sofortstart 
35
  
36
      int a = 255;
37
  char ausgabestring[5];
38
39
  
40
  sei();  //Interrupts aktivieren
41
  
42
  lcd_init();
43
  lcd_clear();
44
  
45
  set_cursor (0, 1); lcd_string ("Uhrzeit");
46
  set_cursor (2, 2);lcd_string (":");set_cursor (5, 2);lcd_string (":");    
47
  
48
  sprintf(stundenstring, "%d", stunden);
49
  set_cursor (0, 2);
50
  if (stunden<10) lcd_string ("0");
51
  lcd_string (stundenstring);
52
  
53
  sprintf(minutenstring, "%d", minuten);
54
  set_cursor (3, 2);
55
  if (minuten<10) lcd_string ("0");
56
  lcd_string (minutenstring);
57
        
58
  sprintf(sekundenstring, "%d", sekunden);
59
  set_cursor (6, 2);
60
  if (sekunden<10) lcd_string ("0");  
61
  lcd_string (sekundenstring);
62
63
64
  
65
  while(1){
66
      
67
    if (sekunden!=timer) {
68
      
69
      sekunden=timer;
70
      
71
      if (sekunden==60){
72
        timer=0;  sekunden=0; minuten++;
73
        
74
        if (minuten==60){
75
          minuten=0; 
76
          stunden++;
77
          if (stunden==24) stunden=0;
78
        }
79
        sprintf(minutenstring, "%d", minuten);
80
        set_cursor (3, 2);
81
        if (minuten<10) lcd_string ("0");
82
        lcd_string (minutenstring);
83
        
84
        
85
        sprintf(stundenstring, "%d", stunden);
86
        set_cursor (0, 2);
87
        if (stunden<10) lcd_string ("0");
88
        lcd_string (stundenstring);
89
      }
90
            
91
      sprintf(sekundenstring, "%d", sekunden); 
92
      set_cursor (6, 2);
93
      if (sekunden<10) lcd_string ("0");
94
      lcd_string (sekundenstring);
95
          
96
    }
97
    
98
    
99
    
100
    if (stunden==21 && minuten ==59 && sekunden==10){
101
        
102
      while(a>0){
103
            //OCR1A = a;
104
            OCR2 = a; 
105
      set_cursor(10, 1); lcd_string("runter");
106
      sprintf(ausgabestring, "%i",a); // Wandelt x nach String - das LCD kann nur Strings!
107
      set_cursor(10, 2); lcd_string("             ");  
108
      set_cursor(10, 2);lcd_string(ausgabestring);
109
            _delay_ms(2000);
110
            a--;
111
        } 
112
      
113
      }
114
  
115
    
116
117
  }  
118
 
119
  return(0);
120
}

aber jetzt das Problem.

Sobald die Zeit zum Handeln erreicht ist bleibt die Uhr auf dem LCD 
stehen weil die PWM in einer while() stattfindet

erst wenns auf 0 Runter gedimmt ist läuft alles weiter.

Wie muss die Anweisung lauten das die Uhr während dem Handeln auch 
weiterlauft?
1
    if (stunden==21 && minuten ==59 && sekunden==10){
2
        
3
      while(a>0){
4
            //OCR1A = a;
5
            OCR2 = a; 
6
      set_cursor(10, 1); lcd_string("runter");
7
      sprintf(ausgabestring, "%i",a); // Wandelt x nach String - das LCD kann nur Strings!
8
      set_cursor(10, 2); lcd_string("             ");  
9
      set_cursor(10, 2);lcd_string(ausgabestring);
10
            _delay_ms(2000);
11
            a--;
12
        } 
13
      
14
      }

von bitte löschen (Gast)


Lesenswert?

Lagere den Teil, der die Uhrzeit ausgibt in eine Funktion aus, und rufe 
diese auch aus der Herunterdimm-Schreife aus, die Du noch so 
umfrunzelst, dass während des _delay_ms(2000) nicht nichts passiert:
1
   ...
2
   set_cursor(10, 2);lcd_string(ausgabestring);
3
   for (abroesm = 0; abroesm < 20; ++abroesm)
4
   {
5
      MachDatMitDeUhrzeitAufsDisplay();
6
      _delay_ms(100);
7
   }
8
   a--;
9
   ...

von Karl H. (kbuchegg)


Lesenswert?

> Wie muss die Anweisung lauten das die Uhr während dem Handeln
> auch weiterlauft?

Falsche Frage.
Die ganze Vorgehensweise mit der while Schleife und dem _delay_ms ist 
nicht dafür geeignet, mehrere Dinge (Uhrzeit aktualisieren + LED dimmen) 
gleichzeitig zu machen. Du musst das Programm komplett umkrempeln und 
dazu musst du erst mal deine Denkweise umstellen.

Im ganzen Programm gibt es nur eine einzige Schleife und das ist die 
zentrale while(1) Endlos-Hauptschleife. Alles andere muss eventgesteuert 
funktionieren.

von Karl H. (kbuchegg)


Lesenswert?

* anstatt des Timer vorladens nimmst du den CTC Modus.
  Damit kannst du dann später mal die genau Taktrate auf 1 Quarz-
  schwingung genau einstellen. Mit dem interen RC-Generator wirst du
  auf Dauer sowieso nicht glücklich. Für eine länger laufende Uhr
  ist der ungeeignet.

* In der ISR zählst du die komplette Uhrzeit hoch, nicht nur die
  Sekunden. Du willst verhindern, dass dir eine Sekundenerhöhung
  durch die Lappen geht weil der µC gerade an einer etwas komplexeren
  GLCD Ausgabe rummacht. 'Die Zeit' ist eine integrale Einheit,
  bestehend aus Stunden#Minuten#Sekunden. Wenn du 'die Zeit' erhöhst,
  dann mach das gleich vollständig.
  Wichtig: Du erhöhst nur die Zeit! Die Anzeige der Zeit findet nach
  wie vor in der Hauptschleife statt. Das erhöhen von Sekunden, Minuten
  und Stunden ist schnell gemacht und kann als solches ruhig in der
  ISR bleiben.
  Kurze ISR bedeutet nicht, dass man gar nichts in einer ISR machen
  darf!

* Wenn du schon sprintf benutzt (wogegen erst mal nichts zu sagen ist),
  dann lass sprintf für dich arbeiten. sprintf kann dir perfekte
  führende 0-en in die Ausgabe reinmachen. Du musst sie nur anfordern.

* Die Dimmung kannst du am einfachsten mit einer Hilfsvariablen machen.
  Die Hilfsvariable ist der Sollwert der Helligkeit. In der ISR prüfst
  du ganz einfach ob die momentane Ist-Einstellung kleiner oder größer
  des Sollwerts ist und wenn ja, stellst du den Ist-Wert um 1 nach.
  Dadurch wird automatisch die PWM jede Sekunde um 1 Wert in Richtung
  des Sollwertes geführt. In Summe ergibt sich daraus die gewünschte
  Dimmung. Du brauchst nur den Sollwert auf den Wert stellen, den du
  am Ende haben willst und die ISR dimmt die Lampe langsam auf diesen
  Wert.

* In der Hauptschleife machst du nur noch die Anzeige (die ISR sagt
  dir, wann das notwendig ist) und machst dann auch noch den Test
  ob deine Lampe aufzudimmen oder abzuschalten ist. Das Aufdimmen
  bzw. Abschalten geschieht ganz einfach, in dem man einen neuen
  Helligkeitswert vorgibt. Die ISR sorgt dann dafür, dass die PWM
  im Laufe der Zeit jede Sekunde entsprechend nachgestellt wird.


Ich hab hier mal eine Steuerung gebaut, die die PWM zu jeder vollen 
Minute entweder aufdimmt oder abdimmt (bei den geraden Minuten wird 
aufgedimmt).
Das Programm ist für einen Mega16 geschrieben. Ich denke die Timer 
Einstellungen sind beim Mega8 identisch. Ich hab eine LED am Ausgabepin 
(OCR2 ist bei mir PortD/PD7), die anders rum angeschlossen ist, daher 
ist der Wert für Dunkel bei mir größer als der Wert für Hell. Musst du 
bei dir anpassen. Bedenke: Wenn du die Helligkeit von 0 bis 255 
variieren lässt, dann dauert das vollständige Auf/Ab-dimmen 255 
Sekunden, oder knapp 4 Minuten. Drum hab ich mich auf eine kleinere 
Zahlendifferenz geeinigt. Ich will ja um die Uhrezeit nicht noch ewig 
für der Schaltung sitzen und zusehen wie die LED in 4 Minuten einmal von 
'aus' bis 'volle Helligkeit' durchdimmt :-).
1
#define F_CPU 1000000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <stdio.h>
5
#include "../Lcd/lcd.h"
6
7
#ifndef TRUE
8
#define TRUE 1
9
#define FALSE 0
10
#endif
11
12
13
#define PWM_PIN   PB7     // PB3
14
#define PWM_PORT  PORTD   // PORTB
15
#define PWM_DDR   DDRD    // DDRB
16
17
#define HELL   230
18
#define DUNKEL 255
19
20
volatile uint8_t TimeTick;
21
volatile uint8_t Sekunden;
22
volatile uint8_t Minuten;
23
volatile uint8_t Stunden;
24
25
volatile uint8_t Helligkeit;
26
27
ISR( TIMER1_COMPA_vect )
28
{
29
  //
30
  // Uhr erhöhen
31
  //
32
  Sekunden++;
33
  if( Sekunden == 60 ) {
34
    Sekunden = 0;
35
36
    Minuten++;
37
    if( Minuten == 60 ) {
38
      Minuten = 0;
39
40
      Stunden++;
41
      if( Stunden == 24 ) {
42
        Stunden = 0;
43
      }
44
    }
45
  }
46
47
  //
48
  // PWM eventuell in Richtung Sollwert nachstellen
49
  //
50
  if( OCR2 > Helligkeit )
51
    OCR2--;
52
  if( OCR2 < Helligkeit )
53
    OCR2++;
54
55
  //
56
  // Für alle die's interessiert: 1 Sekunde ist rum
57
  //
58
  TimeTick = TRUE;
59
}
60
61
int main()
62
{
63
  char Buffer[10];
64
65
  TimeTick = TRUE;
66
  Helligkeit = DUNKEL;
67
68
  //
69
  // PWM Ausgang
70
  //
71
  PWM_DDR = ( 1 << PWM_PIN );
72
  TCCR2 |= (1 << WGM21) | (1 << WGM20);    // Fast Pwm
73
  TCCR2 |= (1 << COM21);                   // non inverting
74
  OCR2 = Helligkeit;
75
  TCCR2 |= (1 << CS21);                    // Prescaler 1
76
77
  //
78
  // Uhr
79
  //
80
  TCCR1B |= (1 << WGM12);                   // CTC
81
  OCR1A = ( F_CPU / 64 ) - 1;               // Für 1 Sekunde
82
  TIMSK = (1 << OCIE1A);
83
  TCCR1B |= (1 << CS11) | (1 << CS10);      // Prescaler: 64
84
85
  lcd_init( LCD_DISP_ON );
86
87
  sei();
88
89
  while( 1 ) {
90
    if( TimeTick ) {      // schon wieder 1 Sekunde um? Kinder, wie die Zeit vergeht!
91
      cli();
92
      TimeTick = FALSE;
93
      sprintf( Buffer, "%02d:%02d:%02d", Stunden, Minuten, Sekunden );
94
      sei();
95
96
      lcd_gotoxy( 5, 0 );
97
      lcd_puts( Buffer );
98
99
      if( Minuten % 2 == 0 )   // bei geraden Minuten: LED ist hell
100
        Helligkeit = HELL;
101
102
      if( Minuten % 2 == 1 )   // bei ungeraden Minuten: LED ist dunkel
103
        Helligkeit = DUNKEL;
104
    }
105
  }
106
}

von Michael P. (mpl)


Lesenswert?

Ohje ohje... bin anfänger komm noch net mal mit der Syntax klar von C :D

von Karl H. (kbuchegg)


Lesenswert?

Michael P. schrieb:
> Ohje ohje... bin anfänger komm noch net mal mit der Syntax klar von C :D

Dann wirds aber Zeit. So schwer hab ich dir die Syntax auch wieder nicht 
gemacht. Wenn du dein erstes Beispiel durch zusammenkopieren erstellen 
konntest, wirst du auch dieses Beispiel verstehen.

Da musst du durch. Wer nach Australien auswandert, wird Englisch lernen 
müssen. Die Ausrede "Bin neu, kann noch kein Englisch" zieht nicht.

PS: meine LCD Funktionen heißen ein wenig anders. Aus dem Zusammenhang 
sollte sich aber ergeben, welche Funktion was macht.

von KlausK (Gast)


Lesenswert?

Hallo Karl Heinz,

Karl Heinz Buchegger schrieb:
> * In der ISR zählst du die komplette Uhrzeit hoch, nicht nur die
>   Sekunden.

Äh... wäre es da nicht bedeutend einfacher, einfach nur die Sekunden 
seit 00:00:00 Uhr zu zählen? Wenn man daraus dann die Uhrzeit braucht 
oder umgekehrt eine Uhrzeit in die Sekundenzahl ausrechnen will, geht 
das ja flott:
1
/** convert seconds to timestring */
2
void s2t(unsigned int time, char* res) {
3
    unsigned int hr = (time - (time % 3600)) / 3600;
4
    unsigned int sc = time - (hr * 3600);
5
    unsigned int mn = (sc - (sc % 60)) / 60;
6
    sc = sc - (mn * 60);
7
    sprintf(res, "%02d:%02d:%02d", hr, mn, sc);
8
}
9
10
/** convert timestring to seconds */
11
int s2s(char* str) {
12
    unsigned int hr = 0;
13
    unsigned int mn = 0;
14
    unsignet int sc = 0;
15
    sscanf(str, "%i:%i:%i", &hr, &mn, &sc);
16
    return hr * 3600 + mn * 60 + sc;
17
}

Dann bleibt der betreffende Teil in der ISR schön klein:
1
volatile uint64 daytime;
2
ISR(TIMER1_COMPA_vect) {
3
    daytime++;
4
    if(daytime == 86400) {
5
        daytime = 0;
6
    }
7
}

Bei dem konkreten Beispiel hier wäre auch die LCD-Ausgabe einfacher, 
weil der ganze Timestring in einem Rutsch ausgegeben werden kann.

Was spricht dagegen?

Beste Grüße,
Klaus

von Werner (Gast)


Lesenswert?

KlausK schrieb:
> Was spricht dagegen?

Alleine schon die Divisionen. Das dauert auf einem ATmega 
Größenordnungen länger als die Vergleiche mit bedarfsweiser 
Inkrementierung. Solange die Dauer der Hauptschleife sicher unter 1 
Sekunde bleibt, gibt es sowieso keinen Grund, die Sek-Min-Hour Überträge 
in die ISR zu packen.

Karl Heinz Buchegger schrieb:
>   Damit kannst du dann später mal die genau Taktrate auf 1 Quarz-
>   schwingung genau einstellen. Mit dem interen RC-Generator wirst du
>   auf Dauer sowieso nicht glücklich. Für eine länger laufende Uhr
>   ist der ungeeignet.

Selbst mit einem Quartz wird die Uhr ohne einen Abgleich nicht genau 
genug laufen. Ein Beipiel für die genaue Erzeugung eines Sekundentaktes 
hat Peter hier gezeigt:
Beitrag "Die genaue Sekunde / RTC"

von Karl H. (kbuchegg)


Lesenswert?

KlausK schrieb:

> Dann bleibt der betreffende Teil in der ISR schön klein:
>
>
1
> volatile uint64 daytime;
2
> ISR(TIMER1_COMPA_vect) {
3
>     daytime++;
4
>     if(daytime == 86400) {
5
>         daytime = 0;
6
>     }
7
> }
8
>

Hast du das mal verglichen?

Ich denke ich trau mich sogar wetten, dass das Laden, Inkrementieren und 
Vergleichen eines uint64_t länger dauert, als 3 Bytes bei Bedarf 
hochzuzählen.

Lass dich nicht vom C-Code blenden. Der sieht realtiv umfangreich aus. 
Wenn man sich aber mal überlegt, wie der zugehörige Assemblercode 
aussieht, dann kommt man drauf, dass das weniger als ca. 20 Takte sind. 
Ist ja praktisch nichts zu tun. Byte holen inkrementieren, vergleichen, 
zurückspeichern. Und das (möglicherweise) 3 mal. Da das restliche 
Programm in keiner Weise zeitkritisch ist, sind diese Takte völlig 
problemlos zu verschmerzen.

In einem Punkt hast du allerdings recht: Wenn man mit der Zeit 
Vergleiche machen muss, ist es einfacher, wenn man eine laufende Zahl 
seit Mitternacht hat. Wobei du den Datentyp aber auch nicht so hoch 
drehen willst, dass du mit der Rechnerei den µC lahm legst. Höher als 
einen uint16_t würde ich nicht gehen, zumal bei solchen Schaltuhr Sachen 
nur ganz selten Sekunden gebraucht werden. Normalerweise reichen Minuten 
locker aus.

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.