Forum: Compiler & IDEs Timer0 mit abfallender Flanke


von Firebird (Gast)


Angehängte Dateien:

Lesenswert?

Hallo zusammen

Ich möchte eine LED blinken lassen. Die Idee ist mit einem vorgegebenen 
Wert j den Timer0 fallend zählen zu lassen bis er 0 erreicht und einen 
Interrupt auslöst. Diese Lösung ist mir sympathischer als aufwärts 
zählen zu lassen wobei man noch den Vorlauf bestimmen muss. Eine Routine 
mit _delay_ms() will ich vermeiden.
1
{
2
while(0<j--);    
3
LEDS_OFF;
4
j=112;             // ~500ms
5
}

Leider bekomme ich die Routine nicht zum Laufen (siehe Anhang).
Hat jemand Rat? ... Bestimmt ;-)

Gruss
Firebird

von Karl H. (kbuchegg)


Lesenswert?

Firebird schrieb:
> Hallo zusammen
>
> Ich möchte eine LED blinken lassen. Die Idee ist mit einem vorgegebenen
> Wert j den Timer0 fallend zählen zu lassen bis er 0 erreicht und einen
> Interrupt auslöst.

Der Timer zählt immer aufwärts. Das ist Hardwaremässig vorgegeben und es 
gibt nichts was du daran ändern könntest.

> Diese Lösung ist mir sympathischer als aufwärts
> zählen zu lassen wobei man noch den Vorlauf bestimmen muss.

Muss man doch gar nicht.
Man muss nur den richtigen Timer nehmen und den auf CTC stellen.

> Eine Routine
> mit _delay_ms() will ich vermeiden.

Löblich. Allerdings bei dir reichlich sinnlos, wenn du den Timer wie 
einen _delay_ms Ersatz benutzt.

>
>
1
{
2
> while(0<j--);
3
> LEDS_OFF;
4
> j=112;             // ~500ms
5
> }
6
>

und wie soll da jetzt der Timer ins Spiel kommen?

von Firebird (Gast)


Angehängte Dateien:

Lesenswert?

Ich muss ein bischen ausholen.

Im Anhang ist der funktionierende Code wo ich _delay_ms() verwendet 
habe. Jetzt möchte ich _delay_ms() durch eine Timer Routine ersetzen. 
Erstens weil ich LEDs habe die dauern an sind und weil ich später noch 
eine PWM Funktion implementieren möchte um die LED Helligkeiten 
anzupassen.

Gruss
Firebird

von Helfer (Gast)


Lesenswert?

Eine Art das zu machen ist eigentlich im Artikel [[AVR-GCC-Tutorial/Die 
Timer und Zähler des AVR]] erklärt. Dort wird mit Interrupts und einem 
signalisierendem Interruptflag zwischen ISR und Hauptprogramm 
gearbeitet.

Alternativ kannst du auch einen Timer starten und interruptfrrei 
arbeitet, indem du "händisch" in einem Anweisungsblock (while...) oder 
einer Wartefunktion abfragst, ob der Timerzähler einen bestimmten Wert 
erreicht hat.

In deinem Fall würde ich mit der ersten Variante arbeiten. Dabei würde 
ich zwei Zeitschienen anlegen und den Timer in der ISR bei bestimmten 
Zeitpunkten Aktionen ausführen lassen. Die Zeitschienen kann man z.B. 
durch Felder (Arrays) automatisch finden und abarbeiten lassen.

Bsp. Zeitschiene für 0xc1

Zeit   Aktion
====================
   0   POSLEDS_OFF
 500   BEACONLEDS_ON
 580   LEDS_OFF
1000   TIMER_RESET

Bei der zweiten Zeitschiene (0xc3)

Zeit   Aktion
=====================
   0   POSLEDS_ON
       STROBETAILLEDS_ON
  80   LEDS_OFF
 200   STROBELEDS_ON
 280   LEDS_OFF
 500   BEACONLEDS_ON
 580   LEDS_OFF
 960   TIMER_RESET

Damit diese Zeitpunkte "getroffen" werden, könnte der Timer mit einem 
zeitlichen Abstand von 20ms laufen

von Firebird (Gast)


Lesenswert?

Gemäss deiner 'Alternative' interruptfrei könnte es in etwa so aussehen, 
wobei j gemäss Timertakt angepasst werden muss:
1
while(1);
2
{ 
3
  input = ~PINC;      
4
      
5
      if (input == 0xc1)    // PINC0 als Pulldown
6
      {
7
      do
8
      {
9
        PORTD=0x00;  // LEDS_OFF
10
        j=112;       // ~500ms
11
      }
12
      while(0<j--);  // Zählt von 112 runter auf 0
13
      }
14
      {
15
      do
16
      {
17
        PORTD=0xff; // LEDS_ON   
18
        j=22;       // ~100ms
19
      }
20
      while(0<j--); // Zählt von 22 runter auf 0       
21
      }
22
  }

Den Artikel im Tutorial habe ich mir angeschaut, der aha Effekt fehlt 
noch.

von Helfer (Gast)


Lesenswert?

> Gemäss deiner 'Alternative' interruptfrei könnte es in etwa so aussehen,
> wobei j gemäss Timertakt angepasst werden muss:

Nein, so meinte ich das nicht.

Bei modernen Toolchains so wie AVR-GCC für AVRs kann vom Optimierer eine 
einfache Zählschleife mit j u.U. gnadenlos weggeworfen werden (wenn j 
nicht volatile ist) und/oder der Code kann so umstrukturiert werden, 
dass das Hinfummeln eines exakten Zeitablaufs Glückssache wird und bei 
der nächsten Sourcecodeänderung alles den Bach runter geht.

Die interruptfreie Alternative würde statt j genau den Timerzähler, also 
z.B. bei Timer0 TCNT0 abfragen. Dabei muss der Timer vorher ins Laufen 
gebracht werden (Taktquelle, Prescaler...) und der Start-/Endwert müssen 
bekannt und berechnet werden.

Eine Ersatzwartefunktion mit Timerabfrage statt vorberechneten 
Warteschleifen (delay..) könnte in etwa so aussehen:
1
// Atmega8
2
void timer0_warten(uint8_t ticks)
3
{
4
  TCCR0 = (1<<CS00); // Prescaler: CPU-Takt/1 und Timer0 starten
5
  TCNT0 = 0; // Timer0 Zähler resetten
6
  while(TCNT0 != TICKS) {} // warten
7
}

Den Prescaler dann so anpassen, dass längere Zeiten als 1 CPU-Tick 
möglich sind.

Du gewinnst damit aber nichts gegeüber einer _delay_ms() Funktion. Das 
Programm trödelt während ticks Takten immer noch in der Warteschleife 
und fragt z.B. nicht "parallel" die Eingaben an PINC ab.

Deine Programmlogik verlangt dringend nach einer nebenläufigen 
Zeitkontrolle per Interrupt. Der Timerteil des Programms kümmert sich um 
die LED-Ansteuerung und der Hauptteil deines Programms kümmert sich um 
die Eingaben und macht die LED-Vorgaben.

von Helfer (Gast)


Lesenswert?

>  while(TCNT0 != TICKS) {} // warten
  while(TCNT0 != ticks) {} // warten

von Firebird (Gast)


Lesenswert?

Helfer schrieb:
> Deine Programmlogik verlangt dringend nach einer nebenläufigen
> Zeitkontrolle per Interrupt. Der Timerteil des Programms kümmert sich um
> die LED-Ansteuerung und der Hauptteil deines Programms kümmert sich um
> die Eingaben und macht die LED-Vorgaben.

Danke für die Hinweise.
Hier ist mein erneuter Versuch die Programmlogik umzusetzen.
Die 'Timer0' Einstellungen muss ich überprüfen und die 'counter' Werte 
nachrechnen.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define LEDS_ON    PORTD=0x44
5
#define LEDS_OFF   PORTD=0x00
6
7
volatile unsigned int counter = 0;
8
9
ISR (TIMER0_OVF_vect)
10
{
11
  counter++;
12
}
13
14
int main(void)
15
{
16
  unsigned char input;
17
18
  DDRC = 0x00;            // Port C is Input
19
  PORTC = 0xff;           // Port C is "high" <-- pull-ups active
20
21
  DDRD = 0xff;            // Port D is Output
22
  PORTD = 0x00;           // Port D is "low"
23
   
24
  // initiate Timer0
25
  TCCR0 = (1<<CS00) | (1<<CS01);    // Prescaler 64
26
  TIMSK = (1<<TOIE0);               // Overflow Interrupt Enable
27
  
28
  sei();                  //Enable Global Interrupts
29
  
30
  while(1);
31
  { 
32
    input = ~PINC;        // LEDs on Anode (+5V) and Cathode on GRD; inverted
33
      
34
      if (input == 0xc1)  // PINC0 als Pulldown
35
      {
36
      if (counter>600)
37
      {
38
      counter = 0
39
      }
40
      while(counter>=0 && counter < 500);    
41
        LEDS_OFF;
42
       }
43
      {
44
      while(counter>=500 && counter <600);  
45
        LEDS_ON; 
46
      }
47
  }
48
}

Hoffentlich siehts jetzt besser aus.

von Helfer (Gast)


Lesenswert?

Kannst du so testen:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/atomic.h>
4
5
#define LEDS_ON    PORTD=0x44
6
#define LEDS_OFF   PORTD=0x00
7
8
volatile unsigned int counter = 0;
9
10
ISR (TIMER0_OVF_vect)
11
{
12
  counter++;
13
}
14
15
int main(void)
16
{
17
  unsigned char input;
18
19
  DDRC = 0x00;            // Port C is Input
20
  PORTC = 0xff;           // Port C is "high" <-- pull-ups active
21
22
  DDRD = 0xff;            // Port D is Output
23
  PORTD = 0x00;           // Port D is "low"
24
   
25
  // initiate Timer0
26
  TCCR0 = (1<<CS00) | (1<<CS01);    // Prescaler 64
27
  TIMSK = (1<<TOIE0);               // Overflow Interrupt Enable
28
  sei(); 
29
  
30
  while(1)
31
  { 
32
    unsigned int tcounter;
33
    input = ~PINC;       
34
    if (input == 0xc1) {
35
/*
36
http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Zugriff_auf_eine_16-Bit_Variable      
37
*/
38
      ATOMIC_BLOCK(ATOMIC_FORCEON)
39
      {
40
        // 16-Bit Zuweisung ist nicht atomar deshalb ATOMIC_BLOCK
41
        tcounter = counter;
42
      }
43
      if( tcounter >= 0 && tcounter < 500) {
44
        LEDS_OFF;
45
      } else if( tcounter >= 500 && tcounter < 600 ) {
46
        LEDS_ON; 
47
      } else {
48
        counter = 0
49
        ATOMIC_BLOCK(ATOMIC_FORCEON)
50
        {
51
          // 16-Bit Zuweisung ist nicht atomar deshalb ATOMIC_BLOCK
52
          counter = 0; 
53
        }
54
      }
55
    }
56
  }
57
}

Wenn das zufriedenstellend läuft kannst du den while-Anweisungsblock in 
die ISR reinziehen. Dann sparst du das Kapseln der 16-Bit-Zugriffe mit 
ATOMIC_BLOCK und dein Hauptprogramm (while-schleife) wird schlanker. 
Achtung: Wenn in der ISR input abgefragt wird, muss diese Variable 
auch volatile sein.

von Helfer (Gast)


Lesenswert?

>        counter = 0
>        ATOMIC_BLOCK(ATOMIC_FORCEON)

nur die letzte Zeile nehmen!

von Karl H. (kbuchegg)


Lesenswert?

Dein Problem sind Konstrukte wie dieses
1
     while(counter>=0 && counter < 500);

genau die willst du NICHT haben.
Du möchtest keine Warteschleifen haben.

Im Moment machst du folgendes:
Du stehst in der Küche und kochst Gulasch. Wie wir alle wissen, dauert 
das 2 Stunden. In diesen 2 Stunden stehst du die ganze Zeit am Herd um 
wartest jeweils 5 Minuten ab um einmal umzurühren.
Bist du mit dem Gulasch fertig, startest du die Waschmaschine. Die 
braucht 1 Stunde 15 Minuten. In dieser Zeit stehst du ebenfalls neben 
der Waschmaschine und wartest darauf, dass die fertig wird.

Genau das willst du eben nicht tun!

Was du tun willst:

Du bringst das Gulasch auf den Weg (schaltest den Herd ein).
Dann wechselst du zur Waschmaschine und startest die ebenfalls.

Die ganze restliche Zeit pendelst du ständig zwischen Herd und 
Waschmaschine hin und her. Bist du beim Herd, siehst du dort (auf einer 
Uhr) nach, ob seit dem letzten Umrühren 5 Minuten vergangen sind und 
wenn ja rührst du kurz um und startest den 5 Minuten Wecker neu. Du 
schaust auch nach, ob die 2 Stunden um sind und wenn ja, schaltest du 
den Herd ab.
Auf jeden Fall aber belibst du nicht beim Herd stehen sondern du 
erfüllst deine KURZE Kontrollaufgabe (mit möglicherweise einer kurzen 
Aktion - Umrühren), und danach wechselst du zur Waschmaschine, um 
nachzusehen ob die fertig ist. Dann beginnt das Spielchen wieder von 
vorne: Herd - Waschmaschine - Herd - Waschmaschine.

Deine Vorgehensweise ist also
du schaust dir reihum alle Dinge an, die du überwachen sollst und siehst 
nach ob es etwas zu tun gibt. Gibt es etwas zu tun, dann machst du das. 
Aber auf keinen Fall wartest du darauf, dass irgendetwas fertig wird 
sondern du wechselst sofort zur nächsten potentiellen Aufgabe und siehst 
dort nach, ob es etwas zu tun gibt.

Und siehe da: plötzlich braucht kein Mensch mehr Warteschleifen! Auf 
nichts und niemanden wird mehr gewartet, sondern wenn es etwas zu tun 
gibt wird das erledigt. D.h. die beiden Dinge, Gulasch und 
Waschmaschine, laufen nebeneinander her, ohne das eines der beiden 
vernachlässigt wird. Wenn du umrührst, kann es sein, dass das Ende der 
Waschmaschine mal kurz etwas warten muss, das ist aber nicht weiter 
schlimm, denn auf die 5 Sekunden kommt es nicht an (und bei einem µC ist 
das noch viel weniger schlimm, denn im Regelfall dauert eine Aktion ein 
paar µs; andere Aufgaben werden also maximal mit ein paar µs Verzögerung 
angegangen).


Dein Werkzeug, ist der Timer. Aber anders als du denkst. Mit dem Timer 
baust du dir eine Zeitbasis, die du in der Hauptschleife als Taktgeber 
benutzt. Aktionen, die du hast sind: LED einschalten, LED ausschalten. 
UNd die definierst du jetzt in Zeitpunkten, die dein Taktgeber vorgibt. 
Ist der jeweilige Zeitpunkt da, dann machst du die Aktion.

Beispiel: du möchtest 2 LED blinken lassen, aber die eine LED soll 1 
Sekunden an und 2 Sekunden aus sein, während die andere 2 Sekunden an 
und 2 Sekunden aus sein soll.

Als Zeitbasis wählst du einen Zähler, der von 0 bis 11 zählt. das 
eergibt dir 12 Zeitstempel, die du wie folgt verteilst

0    LED 1 ein,   LED 2 ein
1    LED 1 aus
2                 LED 2 aus
3    LED 1 ein
4    LED 1 aus    LED 2 ein
5
6    LED 1 ein    LED 2 aus
7    LED 1 aus
8                 LED 2 ein
9    LED 1 ein
10   LED 1 aus    LED 2 aus
11
0    LED 1 ein,   LED 2 ein
1    LE.....

nach diesen 12 Takten ist ein kompletter Zyklus durch. Wenn nach dem 12 
wieder mit dem Takt 0 weitergemacht wird, läuft alles korrekt weiter. 
Beide LED blinken im gewünschten Takt.

im Programm sieht das dann so aus

  while( 1 ) {
    if( time == 0 || time == 3 || time == 6 || time == 9 )
       LED1_ON;

    if( time == 1 || time == 4 || time == 7 || time == 10 )
       LED1_OFF;

    if( time == 0 || time == 4 || time == 8 )
       LED2_ON;

    if( time == 2 || time == 6 || time == 10 )
       LED2_OFF;
  }


(time ist eine Variable, die zb in einer ISR jede Sekunde um 1 erhöht 
wird und bei 12 wieder auf 0 zurückgesetzt wird)

Das kann man so machen, muss man aber nicht. Es spricht auch nichts 
dagegen, sich mit dem Timer in der ISR 2 Zeitgeber zu bauen, die genau 
den gewünschten LED Zeiten entsprechen. Der eine zählt ständig von 0 bis 
2 (3 Zeittakte) und der andere von 0 bis 3 (4 Zeittakte).

in der Hauptschleife wird dann jeder Zeitgeber geprüft, ob sein Ereignis 
vorliegt
1
uint8_t time_LED1;
2
uint8_t time_LED2;
3
4
ISR( .... )   Overflow Routine, wird zb jede Sekunde aufgerufen
5
{
6
  time_LED1++;
7
  if( time_LED1 == 2 )
8
    time_LED1 = 0;
9
10
  time_LED2++;
11
  if( time_LED2 == 3 )
12
    time_LED2 = 0;
13
}
14
15
int main()
16
{
17
  ....
18
19
  while( 1 ) {
20
21
    if( time_LED1 == 0 )
22
      LED1_ON;
23
24
    else if( time_LED1 == 1 )
25
      LED1_OFF;
26
27
    if( time_LED2 == 0 )
28
      LED2_ON;
29
    else if( time_LED2 == 2 )
30
      LED2_OFF;
31
  }
32
}

auch das ergibt das gewünschte asymetrische Blinken der LED. Und wieder: 
nirgends wird gewartet. Diese Warteschleifen (egal ob du sie mit Timer 
realsierst oder ob du _delay_ms nimmst), die sind dein Grundübel. Die 
musst du loswerden! Und dazu musst du deine Denkweise umstellen. Nicht 
mehr: Ich schalte ein, warte eine Zeitlang, dann schalte ich aus.
Statt dessen muss es lauten: In jedem Schleifendurchlauf ist ein bischen 
Zeit vergangen; was gibt es jetzt, an diesem speziellen Zeitpunkt, alles 
zu tun? Welche Ereignisse sind genau jetzt zu diesem Zeitpunkt 
eingetreten und müssen bearbeitet werden?
Und wenn dieses Ereigniss behandelt wurde, dann wird der nächste 
Ereignisgeber untersucht, ob es etwas zu tun gibt. Und dann geht das 
ganze Spiel wieder von vorne los: ein Ereignisgeber nach dem anderen 
wird abgeklappert ob es etwas zu tun gibt.

von Firebird (Gast)


Lesenswert?

Guten Abend, hier ist ein erneuter Versuch. Leider bring ich das 
Programm nicht zum laufen :-(
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define LED1_ON   PORTB=0x15
5
#define LED1_OFF  PORTB=0x00
6
#define LED2_ON   PORTB=0x2a
7
#define LED2_OFF  PORTB=0x00
8
9
volatile unsigned int counter;
10
11
ISR (TIMER0_OVF_vect)
12
{
13
  counter++;
14
}
15
16
int main(void)
17
{
18
    DDRB = 0xff;     // Port B is Output
19
    PORTB = 0x00;    // Port B is "low"
20
21
  // Initiate Timer0
22
  
23
    /* Interrupt Aktion alle
24
    (8000000/64)/250 = 500Hz
25
    bzw. 1/500s = 2,0ms  
26
    */
27
  
28
    TCCR0 = (1<<CS00) | (1<<CS01);   // Prescaler 64
29
    TIMSK |= (1<<TOIE0);             // Overflow Interrupt Enable
30
    TCNT0 = 6;                       // Preload 256-250 = 6               
31
  
32
    sei();       // Enable Global Interrupts
33
34
    while(1);
35
    {
36
    
37
    unsigned int time;
38
    {
39
    time = counter;
40
    }
41
    
42
    if( time == 0 || time == 80 || time == 160 || time == 240 )      // 0ms,160ms,320ms,480ms
43
    LED1_ON;
44
45
    if( time == 40 || time == 120 || time == 200 || time == 280 )    // 80ms,240ms,400ms,560ms
46
    LED1_OFF;
47
48
    if( time == 0 || time == 1000 || time == 2000 || time == 3000 )  // 0s,2s,4s,6s
49
    LED2_ON;
50
51
    if( time == 500 || time == 1500 || time == 2500 || time == 3500 ) // 1s,3s,5s,7s
52
    LED2_OFF;
53
    
54
    if( time == 4000 )    // 8sec
55
    counter = 0;          // Reset counter = 0
56
    }
57
}

von Karl H. (kbuchegg)


Lesenswert?

#define LED1_ON   PORTB=0x15
#define LED1_OFF  PORTB=0x00
#define LED2_ON   PORTB=0x2a
#define LED2_OFF  PORTB=0x00


Das wird so nichts.
Du kannst nicht mehr den ganzen Port einfach so umstellen. Du musst 
schon gezielt ein einzelnes Bit (das mit der jeweiligen LED 
korrespondiert) entweder setzen oder löschen.

Wenn du zb LED1_OFF ausführen lässt

   POTB = 0x00;

dann löscht du damit ja auch gleichzeitig die LED2. Und das darf 
natürlich nicht sein. LED2 ist ja unabhängig von LED1


Das hier

    if( time == 4000 )    // 8sec
    counter = 0;          // Reset counter = 0

sprich das Rücksetzen des counter, das verlagerst du besser in die ISR 
selber. Das ist dort besser aufgehoben.

ISR (TIMER0_OVF_vect)
{
  counter++;
  if( counter == 4000 )
    counter = 0;
}

Und bei diesen Abfragen

    if( time == 40 || time == 120 || time == 200 || time == 280 )    // 
80ms,240ms,400ms,560ms

solltest du auch mal darüber nachdenken, ob man die nicht zuverlässiger 
machen kann. (Ich weiß, ich hab dir das so vorgegeben).
Überleg mal: angenommen time ist bei einem Schleifendurchgang 39, d.h. 
da passiert jetzt nichts. Aus irgendeinem Grund geht sich das jetzt aber 
so aus, dass dein Hauptprogramm etwas zu viel Zeit braucht, so dass beim 
nächsten Durchlauf durch die Schleife time schon 41 ist. Sprich: dein 
Programm hat den exakten Zählerstand 40 gerade ein klein wenig verpasst. 
Das bedeutet aber auch, dass die zugehörige Aktion dann nicht ausgeführt 
wird. Das kann man auch so formulieren, dass es da kein Problem gibt. 
Die LED1 muss ausgeschaltet sein, wenn der Zählerstand zwischen 40 und 
80 liegt (40 inklusive, 80 exclusive) und natürlich bei den anderen 
Zeiten entsprechend gleich


   if ( ( time >=  40 && time <  80 ) ||
        ( time >= 120 && time < 160 ) ||
        ( time >= 200 && time < 240 ) ||
        ( time >= 280 && time < 320 ) )
     LED1_OFF;

(und natürlich bei den anderen Abfragen genau gleich.)
Programme immer so formulieren, dass sich Problemstellen nicht 
auswirken. Abfragen auf genaue Werte, überhaupt dann wenn diese Werte 
durch einen Timer verändert werden, sind aber immer etwas problematisch. 
Eine kleine Verzögerung im Programm genügt und man verpasst einen 
bestimmten Zählerstand. Also immer so formulieren, dass sich dieses 
Problem nicht auswirkt. Machst du im realen Leben ja auch: Wenn du umm 
13:20:00 die Gartenbewässerung einschalten sollst und du verpasst den 
Zeitpunkt um 2 Sekunden, dann drehst du trotzdem das Wasser auf, auch 
wenn es eigentlich nicht mehr exakt 13:20:00 sondern schon 13:20:02 ist.

von Firebird (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe das Programm angepasst.

Folgende Zeile habe ich entfernt:
1
PORTB = 0x00

Die einzelnen LEDs direkt ansprechen:
1
#define LED1_ON   PORTB |= (1<<PB0)
2
#define LED1_OFF  PORTB &= ~(0<<PB0)
3
#define LED2_ON   PORTB |= (1<<PB1) | (1<<PB3)
4
#define LED2_OFF  PORTB &= ~((0<<PB1) | (0<<PB3))

Die anderen Anpassungen habe ich auch gemacht.

Jetzt müsste theoretisch etwas geschehen, aber die LEDs bleiben dunkel. 
Kann es sein das ich beim Timer0 etwas übersehen habe?

Danke und Gruss
Firebird

von Karl H. (kbuchegg)


Lesenswert?

#define LED1_OFF  PORTB &= ~(0<<PB0)


so kann man aber kein Bit löschen!

Als Merkregel kannst du dir merken:
eine 1 kann man entsprechend oft nach links schieben, dann wandert die 1
aber eine 0 kannst du nach links schieben so oft du willst, das Ergebnis 
bleibt trotdem immer 0.

Oder etwas mahr mathematisch:
einmal nach links schieben entspricht einer Multiplikation mit 2, 2 mal 
schieben wäre eine Multiplikation mit 4, 3 mal schieben wäre mal 8, etc.

Bei einem 1 Bit:        1 * 2   ergibt 2
                        1 * 4   ergibt 4
                        1 * 8   ergibt 8
                        ....

Aber: bei einem 0 Bit   0 * 2   ergibt immer noch 0
                        0 * 4   ist ebenfalls 0
                        0 * 8   ist erst recht wieder 0
                        ....    ist immer 0

von Firebird (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> #define LED1_OFF  PORTB &= ~(0<<PB0)
>
>
> so kann man aber kein Bit löschen!

Meine Konzentration lässt zu später Stunde nach ... Ich habe die Fehler 
korrigiert. Irgenwo ist trotzdem noch der Wurm drin.

von Karl H. (kbuchegg)


Lesenswert?

Schau mal genau
1
    while(1);

was macht der ; da am Zeilenende?

von Firebird (Gast)


Lesenswert?

Jetzt funktioniert es :-)

Besten Dank für deine Hilfe.

Gruss
Firebird

von Firebird (Gast)


Lesenswert?

Ich hätte noch eine theoretische Frage.

Der Takt ist so berechnet, dass alle 2ms ein Overflow Interrupt erfolgt.
1
/* Interrupt Aktion alle
2
   (8000000/64)/250 = 500Hz
3
   bzw. 1/500s = 2,0ms  
4
*/
5
6
TCCR0 |= (1<<CS01) | (1<<CS00); // Prescaler 64
7
TIMSK |= (1<<TOIE0);            // Overflow Interrupt Enable
8
TCNT0 = 6;                      // Preload 256-250 = 6

Werden die LEDs genau nach den definierten Zeitvorgaben geschaltet oder 
benötigt das Programm noch Takte die man bei zeitkritischen Abläufe 
theoretisch berücksichtigen müsste? Abgesehen von kleine 
Verzögerung/Abweichungen die weiter oben schon erklärt sind.

von Helfer (Gast)


Lesenswert?

> Der Takt ist so berechnet, dass alle 2ms ein Overflow Interrupt erfolgt.

Der erste Overflowinterrupt kommpt nach 250*64/8000000 = 2 ms.

Die weiteren Overflowinterrupts kommen nach 256*64/8000000 = 2,048 ms, 
weil TCNT0 in der ISR nicht auf 6 gesetzt wird.

von Firebird (Gast)


Lesenswert?

Helfer schrieb:
>> Der Takt ist so berechnet, dass alle 2ms ein Overflow Interrupt erfolgt.
>
> Der erste Overflowinterrupt kommpt nach 250*64/8000000 = 2 ms.
>
> Die weiteren Overflowinterrupts kommen nach 256*64/8000000 = 2,048 ms,
> weil TCNT0 in der ISR nicht auf 6 gesetzt wird.

Aha, das leuchtet ein.
Was passiert wenn ich jetzt TCNT0 von 'int main(void)' in die ISR 
verschiebe?
Gemäss meinen Überlegungen ist dann der erste Overflowinterrupt 
immernoch die gewollten 2 ms.

von Helfer (Gast)


Lesenswert?

TCNT0 ist nach dem Reset 0. Wenn du die Zeile TCNT0 = 6; aus main in die 
ISR verschiebst, kommt der erste Overflowinterrupt nach 256 
Zählschritten, d.h. 2,048 ms.

von Firebird (Gast)


Lesenswert?

Ok, für mein Verständnis:
Beim Starten des Programms wird 'TCNT0 = 6' im 'int main()' gesetzt.
Danach geht es in die 'while(1)' Schleife.
Nach dem ersten Overflowinterrupt wird der 'counter' auf 0 gesetzt und 
TCNT0 beginnt mit 0.
Weil wir uns in der 'while(1)' Schleife befinden muss 'TCNTO = 6' auch 
im ISR definiert werden.

von Helfer (Gast)


Lesenswert?

> Nach dem ersten Overflowinterrupt wird der 'counter' auf 0 gesetzt und
> TCNT0 beginnt mit 0.

Ja. Bei jedem Overflowinterrupt wird TCNT0 von der AVR Hardware auf 0 
gesetzt und zwar bevor die Anweisungen in der ISR ausgeführt werden.

Wenn du haben möchtest, dass der TCNT0 immer bei 6 startet (damit der 
Overflow alle 250 Zählschritte = 2 ms kommt), muss der Code in der ISR 
dem TCNT0 diesen Wert zuweisen.

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.