Forum: Mikrocontroller und Digitale Elektronik Timer1 LED Uhr test mit modulo


von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Habe den Timer1 mit 64 als Vorteiler aktiv und verwende 7.3728MHz.
Interrupts werden ca alle 0,569 Sekunden ausgelöst.

Nun möchte ich aber einen separaten Counter die Interrupts hochzählen 
und später z.B. nach dem 10 Interrupt was anderes ausführen.

Daher dachte ich an dem Modulo Operator.
Ganz banal sollen die LEDs nach und nach aufblinken und wieder ausgehen.
1
#ifndef F_CPU
2
#   define  F_CPU   7372800    /* 7.3728 MHz */
3
#endif
4
#include <avr/io.h> // Registernamen
5
#include <stdlib.h>
6
#include <util/delay.h>
7
#include <inttypes.h>
8
#include <avr/wdt.h>
9
#include <avr/interrupt.h>
10
11
#include <string.h>
12
13
volatile uint16_t counter = 0;
14
15
void timer1 ()
16
{
17
  TCCR1B = (1<<CS11)|(1<<CS10);  // Vorteiler: 64
18
  TIMSK = (1<<TOIE1); // Overflow Interrupt aktiviert  
19
} 
20
21
ISR (TIMER1_OVF_vect)
22
{
23
  cli();
24
  counter++;
25
  PORTD ^= (1<< PD0);  
26
  sei();
27
}
28
29
void long_delay(uint16_t ms)
30
{
31
    for(; ms>0; ms--) _delay_ms(1);
32
}
33
34
int main(void)
35
{
36
  DDRC = 0x00; // Eingänge
37
  DDRC = ((1 << DDC0) | (1 << DDC1) | (1 << DDC2)) ; // PC0-3 Ausgang
38
  PORTC = ((1 << PC0) | (1 << PC1) | (1 << PC2)) ;
39
  
40
  DDRD = 0xFF;  // Alle Pins von Port D als Ausgang
41
  DDRD &= ~ (1<<DDD7); // Pin 7 als Eingang
42
  PORTD = (1<<PD0)| (1<<PD1)|(1<<PD2)|(1<<PD3)|(1<<PD4)|(1<<PD5)|(1<<PD6); 
43
  
44
  timer1();
45
  
46
  while(1)
47
  {
48
    if(counter%7==1) PORTD ^= (1<< PD1);  
49
    if(counter%7==2) PORTD ^= (1<< PD2);  
50
    if(counter%7==3) PORTD ^= (1<< PD3);  
51
    if(counter%7==4) PORTD ^= (1<< PD4);  
52
    if(counter%7==5) PORTD ^= (1<< PD5);  
53
    if(counter%7==6) PORTD ^= (1<< PD6);
54
        }
55
}

Aber es leuchtet nur die LED in 0,5 Sekunden Takt an PD1 und PD2, welche 
nicht konsequent im Richtigen Moment leuchtet.

Woran könnte es liegen?

von Peter II (Gast)


Lesenswert?

Leider keine Lösung für dein Problem, dafür ein paar Anmerkungen:

 cli();
 sei();

habe nichts in der ISR zu suchen, du schaffst damit nur neue Probleme.


#ifndef F_CPU
#   define  F_CPU   7372800    /* 7.3728 MHz */
#endif

ist auch fehleranfällig, du bekommst nicht mit wenn F_CPU auf einen 
andere Wert gesetzt ist. Lass es komplett weg und setze es im Projekt.

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Peter II schrieb:
> Leider keine Lösung für dein Problem, dafür ein paar Anmerkungen:
>
>  cli();
>  sei();
>
> habe nichts in der ISR zu suchen, du schaffst damit nur neue Probleme.

Da ich parallel noch die ADC Eingänge nutze und den Analog Comperator, 
muss ich doch bei einem Interrupt andere zunächst ausblenden?
Oder geraten die anderen Interrupts dann in Warteschleife?

: Bearbeitet durch User
von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Meine LED PD1 müsste prinipiell dann leuchten wenn Interrupt das zweite 
mal ausgeführt wird. Also quasi wenn erste LED an PD0 aus ist.
Aber es ist nicht ganz rund. Mal leuchten 5 Sekunden lang beide im Takt 
an und aus und mal eben richtig versetzt.

von Peter II (Gast)


Lesenswert?

Rene Müller schrieb:
> Da ich parallel noch die ADC Eingänge nutze und den Analog Comperator,
> muss ich doch bei einem Interrupt andere zunächst ausblenden?
> Oder geraten die anderen Interrupts dann in Warteschleife?

die Atmels können nur eine ISR gleichzeitig bearbeiten. Diese Sperre 
hebelst du mit deinem Code aus. Damit wird im schlimmsten Fall die 2.ISR 
schon ausgeführt obwohl die 1.ISR nicht zu ende ist.

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Peter II schrieb:
> die Atmels können nur eine ISR gleichzeitig bearbeiten. Diese Sperre
> hebelst du mit deinem Code aus. Damit wird im schlimmsten Fall die 2.ISR
> schon ausgeführt obwohl die 1.ISR nicht zu ende ist.

Genau deswegen cli() um keine Interrupts zuerlauben während ein 
Interrupt läuft oder nicht?
Ging davon aus,dass man genau aus diesem Grund in einem Interrupt andere 
Interrupts nicht erlaubt.

von Peter II (Gast)


Lesenswert?

Rene Müller schrieb:
> Genau deswegen cli() um keine Interrupts zuerlauben während ein
> Interrupt läuft oder nicht?
> Ging davon aus,dass man genau aus diesem Grund in einem Interrupt andere
> Interrupts nicht erlaubt.

cli() am Anfang ist unnötig, weil das schon die Hardware selber macht -> 
siehe Datenblatt

sei() ist das Problem, denn damit schaltest du die Interrupts ein, 
obwohl du noch in der ISR bist. Es müssen ja noch Daten von Stack 
gelöscht werden und das Prozessorregister widerherstellt werden.

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Danke!
Wenn ich in der While Schleife nach jedem Durchlauf ein Port auslese um 
dementsprechend das Programmlaufen lassen möchte: Hier Analog Comperator 
oder ADC Einlesung.
Ist es für diese Abfrage sinnvoll Interrupts nicht zu zulassen?
1
     while(1)
2
  {
3
    cli();
4
    menu = (PINC & 0b00100000); // PIN C5 auslesen
5
    // PIN C5 = 1: ADC, C5 = 0 Analog Comperator
6
    if(menu)ADC_Init();
7
    else AC_Init();
8
    sei();
9
    
10
    if(menu)
11
    // ADC Aktiv
12
    {....

von M. S. (elpaco)


Lesenswert?

Ich sehe dafür kein
1
sei();
nach Aktivieren des Timers.

von holger (Gast)


Lesenswert?

>Meine LED PD1 müsste prinipiell dann leuchten wenn Interrupt das zweite
>mal ausgeführt wird. Also quasi wenn erste LED an PD0 aus ist.
>Aber es ist nicht ganz rund. Mal leuchten 5 Sekunden lang beide im Takt
>an und aus und mal eben richtig versetzt.

Du hast ein Problem mit atomaren Zugriffen.
counter kann sich ändern wenn du in der main()
darauf zugreifst. Genauso ist ^= beim Port nicht atomar.
Der ganze Block muss unter Interruptsperre:
1
    cli();
2
    if(counter%7==1) PORTD ^= (1<< PD1);  
3
    if(counter%7==2) PORTD ^= (1<< PD2);  
4
    if(counter%7==3) PORTD ^= (1<< PD3);  
5
    if(counter%7==4) PORTD ^= (1<< PD4);  
6
    if(counter%7==5) PORTD ^= (1<< PD5);  
7
    if(counter%7==6) PORTD ^= (1<< PD6);
8
sei();

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

holger schrieb:
> Du hast ein Problem mit atomaren Zugriffen.
> counter kann sich ändern wenn du in der main()
> darauf zugreifst. Genauso ist ^= beim Port nicht atomar.
> Der ganze Block muss unter Interruptsperre:

In der Tat. Danke, habe dennoch gelegentlich aussetzer. Liegt das daran, 
dass ein Interrupt ausgelöst werden möchte gerade wenn es gesperrt wird? 
Müsste das nicht ziemlich unwahrscheinlich sein bei über 7MHz.
Vorallem wenn nun wirklich nur noch dieser Quellcode genutzt wird:
1
int main(void)
2
{
3
  DDRB = 0xFF; // Blaue LED: Ausgang
4
  DDRB &= ~( (1 << DDB2) | (1 << DDB3) ); // PB2 und PB3 Eingänge
5
  DDRC = 0x00; // Eingänge
6
  DDRC = ((1 << DDC0) | (1 << DDC1) | (1 << DDC2)) ; // PC0-3 Ausgang
7
  PORTC = ((1 << PC0) | (1 << PC1) | (1 << PC2)) ;
8
  
9
  DDRD = 0xFF;  // Alle Pins von Port D als Ausgang
10
  DDRD &= ~ (1<<DDD7); // Pin 7 als Eingang
11
  PORTD = (1<<PD0)| (1<<PD1)|(1<<PD2)|(1<<PD3)|(1<<PD4)|(1<<PD5)|(1<<PD6); 
12
  
13
  timer1();
14
  sei();
15
  
16
  while(1)
17
  {
18
                cli();
19
    if(counter%7==1) PORTD ^= (1<< PD1);  
20
    if(counter%7==2) PORTD ^= (1<< PD2);  
21
    if(counter%7==3) PORTD ^= (1<< PD3);  
22
    if(counter%7==4) PORTD ^= (1<< PD4);  
23
    if(counter%7==5) PORTD ^= (1<< PD5);  
24
    if(counter%7==6) PORTD ^= (1<< PD6);    
25
    sei();  
26
  }  
27
}

: Bearbeitet durch User
von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Nach 2 Wellen ist alles Chaos. Wird doch irgendwie machbar sein eine 
Lichterwelle konstant und sicher laufen zu bekommen?

von Omg (Gast)


Lesenswert?

Ersetze das letzte
1
sei();

durch
1
cli();
2
uint16_t counter_old = counter;
3
while (counter_old == counter) sei();

;)

von holger (Gast)


Lesenswert?

>Nach 2 Wellen ist alles Chaos. Wird doch irgendwie machbar sein eine
>Lichterwelle konstant und sicher laufen zu bekommen?

Natürlich geht das. Frag dich mal ob du wirklich jeden
main() Zyklus deine Ports xoren willst? Oder vieleicht
nur wenn counter sich ändert?
1
  uint16_t last_counter = 0;
2
  while(1)
3
  {
4
5
    cli();
6
    if(last_counter != counter)
7
    {
8
      last_counter = counter;
9
      if(last_counter%7==1) PORTD ^= (1<< PD1);  
10
      if(last_counter%7==2) PORTD ^= (1<< PD2);  
11
      if(last_counter%7==3) PORTD ^= (1<< PD3);  
12
      if(last_counter%7==4) PORTD ^= (1<< PD4);  
13
      if(last_counter%7==5) PORTD ^= (1<< PD5);  
14
      if(last_counter%7==6) PORTD ^= (1<< PD6);
15
    }    
16
    sei();  
17
  }

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Omg schrieb:
> ;)

Für den Zweck an sich sehr nice.
Nur möchte ich in Zukunft kontinuierlich 8 ADC Eingänge permanent 
einlesen und bei einem Tastendruck einen Timer aktivieren, während 
dessen soll bei einem gewissen Zeitintervall wo der Knopf erneut 
gedrückt wird das Eingangssignal switchen, welches gerade am Display 
ausgegeben wird. Und zuletzt nach einer gewissen Zeit ohne jeglichem 
Tastendruck in den Ursprungsmodus/Anzeige zurückswitcht.

Kurz: Wenn möglich sicher 2 Prozesse parllel laufen.

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

holger schrieb:
> Natürlich geht das. Frag dich mal ob du wirklich jeden
> main() Zyklus deine Ports xoren willst? Oder vieleicht
> nur wenn counter sich ändert?

So funktioniert aktuell dann gar nichts. Ich denke mal, dass sich Deine 
Idee für ein Programm eignet, wo auch deutlich mehr Inhalt ist als der 
Timer an sich. So sperrt er fast durchgehend die Interrupts :(.

von holger (Gast)


Lesenswert?

>So funktioniert aktuell dann gar nichts.

Was funktioniert nicht?

> Ich denke mal, dass sich Deine
>Idee für ein Programm eignet, wo auch deutlich mehr Inhalt ist als der
>Timer an sich. So sperrt er fast durchgehend die Interrupts :(.

Dann mach halt ein _delay_ms(10); in die Mainloop mit rein;)
Die Interruptsperre ist dann nur zu 0,00002% aktiv und den
Rest der Zeit wird heisse Luft erzeugt.

von holger (Gast)


Lesenswert?

>>So funktioniert aktuell dann gar nichts.
>
>Was funktioniert nicht?

Bei mir funktioniert es nach meinem Vorschlag. Allerdings an PORTB.
PB0 blinkt perfekt. PB1-6 gehen nacheinander an und
dann nacheinander aus. Also was erzählst du für einen Blödsinn?

von holger (Gast)


Lesenswert?

Und hier eine Version mit minimaler Interruptsperre
zum nachdenken:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
volatile uint8_t counter = 1;
6
7
void timer1 (void)
8
{
9
  TCCR1B = (1<<CS11)|(1<<CS10);  // Vorteiler: 64
10
  TIMSK = (1<<TOIE1); // Overflow Interrupt aktiviert  
11
} 
12
13
ISR (TIMER1_OVF_vect)
14
{
15
  counter++;
16
  if(counter == 7) counter = 1;
17
  PORTB ^= (1<< PB0);  
18
}
19
20
int main(void)
21
{
22
  DDRB = 0xFF;  // Alle Pins von Port D als Ausgang
23
  PORTB = (1<<PB0)| (1<<PB1)|(1<<PB2)|(1<<PB3)|(1<<PB4)|(1<<PB5)|(1<<PB6); 
24
  
25
  timer1();
26
  sei();
27
  
28
  uint8_t last_counter = 0, tmp_counter;
29
  while(1)
30
  {
31
    tmp_counter = counter;
32
    
33
    if(last_counter != tmp_counter)
34
    {
35
      last_counter = tmp_counter;
36
      tmp_counter %= 7;
37
      cli();
38
      if(tmp_counter==1) PORTB ^= (1<< PB1);  
39
      if(tmp_counter==2) PORTB ^= (1<< PB2);  
40
      if(tmp_counter==3) PORTB ^= (1<< PB3);  
41
      if(tmp_counter==4) PORTB ^= (1<< PB4);  
42
      if(tmp_counter==5) PORTB ^= (1<< PB5);  
43
      if(tmp_counter==6) PORTB ^= (1<< PB6);
44
      sei();  
45
    }    
46
  }  
47
}

von Peter II (Gast)


Lesenswert?

die neuere Atmels könnne auch einen Pin Togglen und das Atomar, dann 
braucht man die sperre auch nicht.

von holger (Gast)


Lesenswert?

>die neuere Atmels könnne auch einen Pin Togglen und das Atomar, dann
>braucht man die sperre auch nicht.

Das geht dann aber auch nur für neuere Atmels.

Schade das da ein Post gelöscht wurde.
Den ganzen Kram im Interrupt zu machen entspricht
auch einer Interruptsperre weil in Interrupts
Interrupts disabled sind;)

von LostInMusic (Gast)


Lesenswert?

Noch mehr zum Nachdenken: Es ist ganz normal, dass es für einen µC 
(kurzzeitig) nichts zu tun gibt. Damit er solche Zeitspannen nicht 
pseudo-beschäftigt verbringen muss, sondern wirklich ruhen kann, hat man 
den Sleep-Modus erfunden. Mach Dich darüber mal schlau.

von Thomas E. (thomase)


Lesenswert?

Rene Müller schrieb:
> cli();
>     if(counter%7==1) PORTD ^= (1<< PD1);
>     if(counter%7==2) PORTD ^= (1<< PD2);
>     if(counter%7==3) PORTD ^= (1<< PD3);
>     if(counter%7==4) PORTD ^= (1<< PD4);
>     if(counter%7==5) PORTD ^= (1<< PD5);
>     if(counter%7==6) PORTD ^= (1<< PD6);
>     sei();

Hör mit dieser blödsinnigen Interrupt-Sperrerei auf und setz diesen 
Piffelkram in die ISR. Es gibt zwar die allgemeine 
So-Kurz-Wie-Möglich-Empfehlung, das heisst aber nicht, dass man in der 
ISR gar nichts machen darf. Und das bisschen ist so gut wie gar nichts.

Dein ganzes Timing ist völliger Mist.
1
TCCR1B = (1<<CS10);  // Vorteiler: 1
2
3
ISR(TIMER1_OVF_vect)
4
{
5
  static unsigned char PreCounter = 0;
6
7
  Precounter++;
8
  if(Precounter == 64) 
9
  {
10
    PreCounter = 0;
11
    Counter++;
12
  }
13
  switch(Counter % 7)
14
  {
15
    case 1: PORTD ^= (1<< PD1); break;
16
    case 2: PORTD ^= (1<< PD2); break;
17
    case 3: PORTD ^= (1<< PD3); break;
18
    case 4: PORTD ^= (1<< PD4); break;
19
    case 5: PORTD ^= (1<< PD5); break;
20
    case 6: PORTD ^= (1<< PD6); break;
21
  }
22
}

mfg.

von Peter II (Gast)


Lesenswert?

LostInMusic schrieb:
> Noch mehr zum Nachdenken: Es ist ganz normal, dass es für einen µC
> (kurzzeitig) nichts zu tun gibt. Damit er solche Zeitspannen nicht
> pseudo-beschäftigt verbringen muss, sondern wirklich ruhen kann, hat man
> den Sleep-Modus erfunden. Mach Dich darüber mal schlau.

und wozu? Ihm wird schon nicht langweile wenn er in einer schleife NOP 
ausführt.

Bei einen Gerät was nicht grade an einer Batterie hängt, macht der Sleep 
wenig sinn. Denn Sleep wurde zum Energiesparen eingebaut.

Die ersten PCs haben auch nicht geschlafen, weil es einfach keinen Sinn 
gemacht hat.

von Thomas E. (thomase)


Lesenswert?

holger schrieb:
> Schade das da ein Post gelöscht wurde.

Der ist jetzt wieder da.

mfg.

von holger (Gast)


Lesenswert?

>  switch(Counter % 7)

Beim Überlauf von counter geht wieder alles durcheinander;)
Schon mal drüber nachgedacht?

von Thomas E. (thomase)


Lesenswert?

holger schrieb:
>>  switch(Counter % 7)
>
> Beim Überlauf von counter geht wieder alles durcheinander;)
> Schon mal drüber nachgedacht?

Nö. Aber dann lässt man ihn einfach nicht überlaufen.
1
  ...
2
  Counter++;
3
  if (Counter == 253) Counter = 0;

16 Bit für den Counter ist sowieso Quatsch. Genauso wie Modulo. von 0 - 
6 durchzählen reicht für das Programm oben auch. Genauso wie es Perlen 
vor die Säue werfen ist, den 16-Bit Timer zu benutzen. Dafür reicht ein 
8-Bit Timer dicke.

mfg.

von Thomas E. (thomase)


Lesenswert?

Rene Müller schrieb:
> Ganz banal sollen die LEDs nach und nach aufblinken und wieder ausgehen.
1
//Timer0 Prescaler = 64
2
ISR(TIMER0_OVF_vect)
3
{
4
  static unsigned char PreCounter = 0;
5
  static unsigned char Counter = 0;
6
  
7
  switch(Counter)
8
  {
9
    case 0: TOGGLE_D1; break;
10
    case 1: TOGGLE_D2; break;
11
    case 2: TOGGLE_D3; break;
12
    case 3: TOGGLE_D4; break;
13
    case 4: TOGGLE_D5; break;
14
    case 5: TOGGLE_D6; break;
15
  }
16
17
  PreCounter++;
18
  if(PreCounter == 128) 
19
  {
20
    PreCounter = 0;
21
    Counter++;
22
    if(Counter == 6) Counter = 0;
23
  }
24
}

mfg.

: Bearbeitet durch User
von LostInMusic (Gast)


Lesenswert?

>macht der Sleep wenig sinn.

Für mich macht das, was der µC ohne Sleep tun muss, noch weniger Sinn. 
Schaden nimmt der µC in beiden Fällen keinen.

>Denn Sleep wurde zum Energiesparen eingebaut.

Ja. Deshalb sind anderweitige Verwendungen aber nicht verboten.

>Die ersten PCs haben auch nicht geschlafen, weil es einfach keinen Sinn
>gemacht hat.

Oder weil es mangels Sleepmodus keine Alternative gab? ;-)

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Wow, so viel Antworten, Danke. Werde dies versuchen umzusetzen.

Prinzipiell spricht doch nicht gegen einen 16Bit Timer1 anstelle 8Bit 
Timer0?

Der Mikrocontroller wird später keinen Leerlauf mehr haben. Er soll 
permanent von PA0 bis PA7 die ADC Werte der Signale einlesen und 
vergleichen. Je nach Schwellwert einen ASCI Code entsprechend am 
Bildschirm ausgeben.
Daher sollte der Timer und die Interrupts so unabhängig wie möglich sein 
und keinen Einfluss auf Interpretation der Signale haben.

von Rene M. (Firma: RWTH Aachen) (rene_m)


Lesenswert?

Thomas Eckmann schrieb:
> ISR(TIMER0_OVF_vect)
> {
>   static unsigned char PreCounter = 0;
>   static unsigned char Counter = 0;

Besser die Variablen nicht im Interrupt immer neu deklarieren sondern 
einmal außerhalb der ISR.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Rene Müller schrieb:
> Besser die Variablen nicht im Interrupt immer neu deklarieren sondern
> einmal außerhalb der ISR.

 Was der Compiler auch macht - kein Unterschied, nur ist die Deklaration
 in der ISR übersichtlicher.

von Karl H. (kbuchegg)


Lesenswert?

Rene Müller schrieb:
> Thomas Eckmann schrieb:
>> ISR(TIMER0_OVF_vect)
>> {
>>   static unsigned char PreCounter = 0;
>>   static unsigned char Counter = 0;
>
> Besser die Variablen nicht im Interrupt immer neu deklarieren sondern
> einmal außerhalb der ISR.

Besser erst mal ergründen, was 'static' eigentlich macht und dann 
darüber nachdenken, warum wohl Thomas den Scope der Variablen auf die 
ISR beschränken möchte.
Das hat nämlich dann auch was mit sog. 'defensivem Programmieren' zu 
tun.

: 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.