Forum: Mikrocontroller und Digitale Elektronik Laufzeitverhalten main-Funktion


von Ste R. (ste_r)


Lesenswert?

Hallo Community,

ich habe ein komisches Laufzeit-Verhalten festgestellt bei der 
main-Funktion bzw. der darin befindlichen while(1) Schleife.

Einfaches Ziel: Timer0 löst alle 1ms einen Interrupt aus -> ein Flag 
wird gesetzt -> das Flag wird in der while-Schleife abgefragt und eine 
Aktion ausgeführt.

Hier mein Test-Programm:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define PRELOAD_TIMER_FOR_1ms  131u
5
6
bool trigger_1ms;
7
8
static void init_system(void)
9
{
10
  DDRC = 0x18;  // Test-Outputs
11
  
12
  // Timer 0 Interrupt Konfig (1ms)
13
  TCCR0 |= (1<<CS01) | (1<<CS00);  // Presc 64
14
  TCNT0 = PRELOAD_TIMER_FOR_1ms;  // 8MHz / 64 = 125kHz -> Overflow @ 256 (=131+125) -> 1kHz (=1ms)
15
  TIMSK |= (1<<TOIE0);      // Overflow Interrupt Ein
16
  
17
  sei();              // Interrupts global Ein
18
}
19
20
int main(void)
21
{
22
    init_system();
23
24
    while(1)
25
    {
26
      if (trigger_1ms == true)
27
      {
28
        PORTC ^= (1<<PC3);
29
      }
30
      trigger_1ms = false;
31
    }
32
}
33
34
// Timer 0 @1ms
35
ISR (TIMER0_OVF_vect)  // Overflow 8-Bit timer
36
{
37
  PORTC ^= (1<<PC4);
38
  trigger_1ms = true;
39
  TCNT0 = PRELOAD_TIMER_FOR_1ms;
40
}

Wenn ich jetzt ein Oszilloskop an die Ausgänge anschließe, erhalte ich 
folgendes Verhalten:

PC4: (im 1-ms Takt)
1
  _   _   _   _   _
2
_| |_| |_| |_| |_| |  ....

PC3: (das ist das komische)
1
  _   _____   _
2
_| |_|     |_| |____  .... Das Muster wiederholt sich ständig.

CPU-Takt ist 8MHz interner Oszillator.

Ich hätte erwartet, dass PC5 dem PC4 folgt, denn 1ms sind ja Welten, um 
die 3 Zeilen Code auszuführen.

Hat jemand eine Erklärung dafür? An welcher Stelle stehe ich auf dem 
Schlauch?

Danke und viele Grüße

: Bearbeitet durch User
von Helfer (Gast)


Lesenswert?

Hi,
wie sieht denn das Verhalten aus, wenn du das zurücksetzen von 
trigger_1ms mit in die Klammmer der if-Abfrage ziehst?
Vermutlich wird nämlichhäufig dein trigger_1ms zurückgesetzt bevor die 
if-Abfrage überhaupt stattfindet.

von Roland E. (roland0815)


Lesenswert?

Ich habe mal gelernt, wenn an Registern eines Moduls herumgespielt wird, 
stellt man den IRQ aus. Ich kann mir vorstellen, dass das Schreiben des 
Zählregisters bei laufendem Timer mit aktivem IRQ zu komischen Effekten 
führen kann. Zb ein erneutes auslösen des IRQ.

von Spess53 (Gast)


Lesenswert?

Hi

Wozu dieses

>TCNT0 = PRELOAD_TIMER_FOR_1ms; ?

Nimm einfach CTC. Damit ersparst du noch das ganze Interrupt Gedödel.

MfG Spess

von Ste R. (ste_r)


Lesenswert?

Aahh ja klar, wenn die ISR zwischen If-Abfrage und der folgenden 
Anweisung kommt. Das macht Sinn. Allerdings ist es dafür aber seeehr 
regelmäßig.

Wenn ich das Rücksetzen von trigger_1ms in die if-Abfrage rein ziehe, 
geht PC3 gar nicht mehr an.... Warum das denn nun wieder?!?

von Werner P. (werner4096)


Lesenswert?

und trigger_1ms muss volatile sein.

von Teo (Gast)


Lesenswert?

Ste R. schrieb:
> if (trigger_1ms == true)
>       {
>         PORTC ^= (1<<PC3);
>       }
>       trigger_1ms = false;
>     }

Man sollte das Flag erst nach Nutzung löschen, nicht dann wann es 
auftritt....
1
if (trigger_1ms == true)
2
      {
3
        PORTC ^= (1<<PC3);
4
        trigger_1ms = false;
5
      }

von Ste R. (ste_r)


Lesenswert?

Perfekt, jetzt läuft es.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define PRELOAD_TIMER_FOR_1ms  131u
5
6
volatile bool trigger_1ms;
7
8
static void init_system(void)
9
{
10
  DDRC = 0x18;
11
  
12
  // Timer 0 Interrupt Konfig (1ms)
13
  TCCR0 |= (1<<CS01) | (1<<CS00);  // Presc 64
14
  TCNT0 = PRELOAD_TIMER_FOR_1ms;  // 8MHz / 64 = 125kHz -> Overflow @ 256 (=131+125) -> 1kHz (=1ms)
15
  TIMSK |= (1<<TOIE0);      // Overflow Interrupt Ein
16
  
17
  sei();              // Interrupts global Ein
18
}
19
20
int main(void)
21
{
22
    init_system();
23
24
    while(1)
25
    {
26
      if (trigger_1ms == true)
27
      {
28
    PORTC ^= (1<<PC3);
29
    trigger_1ms = false;
30
      }
31
    }
32
}
33
34
// Timer 0 @1ms
35
ISR (TIMER0_OVF_vect)  // Overflow 8-Bit timer
36
{
37
  //cli();
38
  TCNT0 = PRELOAD_TIMER_FOR_1ms;
39
  //sei();
40
  PORTC ^= (1<<PC4);
41
  trigger_1ms = true;  
42
}

Ich habe
- trigger_1ms volatile gemacht
- ins if reingezogen
- und das neu Laden des Timers als erstes in der ISR ausgeführt 
(funktioniert mit und ohne das En-/Disablen der Interrupts)

Danke für die schnelle Hilfe!

Danke auch für die Hinweise! Für CTC müsste ich den Timer1 nehmen (wobei 
ich den bis jetzt mangels besseren Wissens für anderes aufsparen 
wollte). Nun bin ich schlauer :-)

von foobar (Gast)


Lesenswert?

> Perfekt

Nicht wirklich, da sind immer noch zwei Probleme:

a) ^= ist eine evtl nicht-atomare Read-Modify-Write-Operation, die von 
einer ISR unterbrochen werden kann.  Wenn die die gleichen Daten 
verändert, geht's schief: lese Register, ISR(toggelt Bit 4), schreibe 
Register mit altem Bit 4.  Wird zwar beim AVR oft zu einer atomaren 
Operation optimiert, aber eben nicht immer.

b) Die Abfrage in der main klappt nur, solange die Schleife oft genug 
abgearbeitet wird.  Wenn da mal Verzögerungen auftreten, verlierst du 
schnell einen Interrupt.


Btw, cli/sei in der ISR?  Besser nicht ...

von Spess53 (Gast)


Lesenswert?

Hi

Deinen Prozessor hast du ja nicht genannt. Aber z.B. bei einem ATMega328 
haben alle drei Timer CTC. Wie bei vielen anderen auch.

MfG Spess

von Wolfgang (Gast)


Lesenswert?

Ste R. schrieb:
> - und das neu Laden des Timers als erstes in der ISR ausgeführt
> (funktioniert mit und ohne das En-/Disablen der Interrupts)

Damit werden deine Zeiten ungenau, falls aus anderen Gründen die 
Ausführung der ISR um eine Timertaktperiode oder mehr verzögert wird.

von Oliver S. (oliverso)


Lesenswert?

Ste R. schrieb:
> Ich habe
> - trigger_1ms volatile gemacht
> - ins if reingezogen
> - und das neu Laden des Timers als erstes in der ISR ausgeführt

Wenn du jetzt noch verstehst, was das Problem überhaupt war, und wodurch 
es letztendlich gelöst wurde, hast du was gelernt.

Bisher nicht.

Oliver

von Falk B. (falk)


Lesenswert?

Oliver S. schrieb:
> Ste R. schrieb:
>> Ich habe
>> - trigger_1ms volatile gemacht
>> - ins if reingezogen
>> - und das neu Laden des Timers als erstes in der ISR ausgeführt
>
> Wenn du jetzt noch verstehst, was das Problem überhaupt war, und wodurch
> es letztendlich gelöst wurde, hast du was gelernt.
>
> Bisher nicht.

In der Tat.

@ OP

Siehe Interrupt.

von Falk B. (falk)


Lesenswert?

Ste R. schrieb:
> Danke auch für die Hinweise! Für CTC müsste ich den Timer1 nehmen (wobei
> ich den bis jetzt mangels besseren Wissens für anderes aufsparen
> wollte). Nun bin ich schlauer :-)

Die meisten "moderneren" AVRs können CTC  auch am Timer 0 und Timer 2, 
siehe Datenblatt.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Ste R. schrieb:
> Ich habe
> - trigger_1ms volatile gemacht
> - ins if reingezogen
> - und das neu Laden des Timers als erstes in der ISR ausgeführt
> (funktioniert mit und ohne das En-/Disablen der Interrupts)
>
> Danke für die schnelle Hilfe!

Diese Lösung funktioniert zwar zufällig in diesem Fall, ist jedoch immer 
noch nicht wirklich interrupsicher.
Es ist immer noch möglich dass zwischen dem Setzen von PORTC und dem 
löschen von trigger_1ms der 1ms Interrupt erneut kommt. Es funktioniert 
nur zufällig in diesem Beispiel weil fast kein Code drin ist. Bei 
größeren Programmen kommt es sporadisch (1x in paar Tagen) zu 
Fehlfunktionen und man sucht sich einen Wolf.

Daher besser das machen:
1
volatile int trigger_1ms;
2
volatile int trigger_1ms_main;
3
int main(void)
4
{
5
  trigger_1ms = trigger_1ms_main = 0;
6
: : :
7
  while(1)
8
  {
9
    if (trigger_1ms != trigger_1ms_main)
10
    {
11
      PORTC ^= (1<<PC3);
12
      trigger_1ms_main++;
13
    }
14
  }
15
}
16
17
// Timer 0 @1ms
18
ISR (TIMER0_OVF_vect)  // Overflow 8-Bit timer
19
{
20
  //cli();
21
  TCNT0 = PRELOAD_TIMER_FOR_1ms;
22
  //sei();
23
  PORTC ^= (1<<PC4);
24
  trigger_1ms++;
25
}

Zur Erklärung:
Nur im Interrupt wird "trigger_1ms" geschrieben, in der Main nur 
gelesen. Damit umgeht man das Read-Modify-Write Problem und dass da 
dazwischen immer  Interrupts auftreten können. Schließlich kann ein 
Interrupt zu jeder Zeit unterbrechen, z.B. wenn eine C Codezeile in 
mehrere Assembler Befehle unterteilt wird, kann der Interrupt zwischen 
jedem einzelnen Assembler Befehl die main Routine unterbrechen.

PS: Der Portzugriff "C" ist damit immer noch nicht Interruptsicher.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Ste R. schrieb:
> Hier mein Test-Programm:
> ...
> bool trigger_1ms;

Da C den Datentyp bool nicht kennt, ist das entweder nicht das Programm, 
was du tatsächlich hast laufen lassen, oder du benutzt kein C.

Oliver

von Einer K. (Gast)


Lesenswert?

Markus M. schrieb:
> PS: Der Portzugriff "C" ist damit immer noch nicht Interruptsicher.

Wenn ich wissen dürfte, welcher AVR da zum Einsatz kommt, würde ich 
sagen:
1
PINC=(1<<PC3);
Toggelt den Pin auf eine wiedereintritsfähige Art und weise.

von Einer K. (Gast)


Lesenswert?

Oliver S. schrieb:
> Da C den Datentyp bool nicht kennt, ist das entweder nicht das Programm,
> was du tatsächlich hast laufen lassen, oder du benutzt kein C.

c99 kennt _Bool
Und hier <stdbool.h> wird dann auch bool kreiert.

Beitrag #6552199 wurde vom Autor gelöscht.
von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Arduino Fanboy D. schrieb:
> Wenn ich wissen dürfte, welcher AVR da zum Einsatz kommt, würde ich
> sagen:PINC=(1<<PC3);
> Toggelt den Pin auf eine wiedereintritsfähige Art und weise.

Was ist "PC3"? Eine Konstante?

Dann toggelt da nichts.

von Einer K. (Gast)


Lesenswert?

Markus M. schrieb im Beitrag #6552199:
> jedoch garantiert mindestens aus 2
Du täuscht dich.
Das Kommando ist Atomar

von (prx) A. K. (prx)


Lesenswert?

Markus M. schrieb im Beitrag #6552199:
> Arduino Fanboy D. schrieb:
>> Wenn ich wissen dürfte, welcher AVR da zum Einsatz kommt, würde ich
>> sagen:PINC=(1<<PC3);
>> Toggelt den Pin auf eine wiedereintritsfähige Art und weise.
>
> Nein. Ich weiß jetzt nicht aus wie vielen Assembler Befehlen diese eine
> Codezeile besteht, jedoch garantiert mindestens aus 2 und damit ist es
> nicht Interruptsicher.

Da es um einen AVR geht:
  ldi r,PC3
  out PINC,r
Und das ist interruptfest.

Der Witz daran ist die atomare Eigenheit nicht zu alter AVRs, beim 
Schreiben auf den Input-Port jene Output-Pins zu toggeln, in die eine 1 
geschrieben wird.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Aber der TO wollte das haben:

PORTC ^= (1<<PC4);

und nicht das:

PORTC = (1<<PC4);

Und "PORTC ^= (1<<PC4);" ist nicht interruptsicher

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Markus M. schrieb:
> Aber der TO wollte das haben:
> PORTC ^= (1<<PC4);

In der Funktion entspricht bei AVRs ab 2004
  PINC = 1<<PC4;
dem klassischen
  PORTC ^= (1<<PC4);
Aber eben nicht im exakten Code.

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

> PORTC ^= (1<<PC3);
Ist nicht nicht atomar

> PINC = (1<<PC3);
Ist atomar

Tun beide das gleiche, mit dem Pin wackeln

-


Markus M. schrieb:
> Was ist "PC3"? Eine Konstante?
>
> Dann toggelt da nichts.
Du irrst.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Arduino Fanboy D. schrieb:
>> PINC = (1<<PC3);
> Ist atomar
>
> Tun beide das gleiche, mit dem Pin wackeln

OK, dann habe ich auch was gelernt :-)
Avr ist nicht mein "Spezialgebiet"

von Einer K. (Gast)


Lesenswert?

Markus M. schrieb:
> OK, dann habe ich auch was gelernt :-)
Genau das ist die richtige Einstellung!

Dafür bekommst du auch direkt eine negative Bewertung von mir.
Denn hier werden auch mal Irrtümer und falsche Aussagen belohnt, 
richtige  und sinnhafte Aussagen bestraft.

von Falk B. (falk)


Lesenswert?

Markus M. schrieb:
> Daher besser das machen:
> volatile int trigger_1ms;
> volatile int trigger_1ms_main;

Sicher nicht. Die 2. Variable ist überflüssig und erzeugt nur Stress und 
Chaos. Es reicht EINE Variable. Siehe 
Multitasking: Verbesserter Ansatz mit Timer

von Ste R. (ste_r)


Lesenswert?

Hi :-)

Ich habe gelernt:
- dass meine Trigger-Variable voaltile sein muss, weil sie in der ISR 
verändert wird und in der zyklischen Main-Funktion benutzt wird. 
Offensichtlich hat der Compiler gemeint, hier optimieren zu dürfen, 
deshalb lief es zwischendurch nicht. Mit volatile ist das verhindert und 
läuft wie gewünscht.
- dass Interrupts en- und disablen in einer ISR Blödsinn ist (gut, um 
13:40Uhr merke ich das eher als 1:00Uhr nachts ;-)
- dass bei CTC Funktion ich nix am eigentlichen Zählerwert rumwurschteln 
muss, was die Fehleranfälligkeit verringert/verhindert. Deshalb CTC 
vorziehen vor Zähler vorbelegen
- Dass beim Toggeln von IO-Ports in Betracht gezogen werden muss, dass 
in der Anweisung eine ISR zuschlagen kann --> allerdings weniger 
relevant, da das Port-getoggle ja nur meinem Debugging diente und im 
finalen Programm nicht mehr drin ist. Aber trotzdem gut zu wissen.

Ansonsten weil die Fragen kamen:
- ich benutze den ATmega8
- Das Programm ist in C++

Das "Perfekt" oben galt den Hinweisen, nicht dass ich das Programm für 
perfekt hielt ;-)

VG

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Ste R. schrieb:
> Ich habe gelernt:

Das fehlt noch eine Erkenntnis.
1
while(1)
2
    {
3
      if (trigger_1ms == true)
4
      {
5
        PORTC ^= (1<<PC3);
6
      }
7
      trigger_1ms = false;
8
    }

Das dauerhafte, bedingungslose Löschen von trigger_1ms war falsch, denn 
das löscht die Varible oft, ohne daß die ausgewertet wurde! In diesem 
Beispiel dauert die while(1) Endllosschleife nur wenige Prozessortakte, 
sowas die Variable vielleicht mit 1 MHz immer wieder gelöscht wird! Aber 
auch bei deutlich langsamerer Schleife wäre es falsch, denn das 
Handshake zwischen ISR und Hauptprogramm ist dadurch im Eimer.

Merke. Ein Flag wird nur dann gelöscht, wenn es vorher als aktiv erkannt 
wurde!

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Es kommt auf das Design drauf an. Sollte die Main Schleife mal länger 
benötigen als 1ms dann bleibt das ganze mit "while(1);" "Hängen".

Für kleine Programme sollte es kein Problem sein dass mindestens 1x je 
ms der Main-Loop läuft. Hingegen bei großen kann es durchaus mal länger 
dauern. Und dann funktioniert die 1-Variablen Variante nicht mehr.
Hingegen die 2-Variablen Variante zeigt einem wie viele Interrupts 
dazwischen waren und macht die Funktion so oft wie der Zähler != Zähler 
aus dem Interrupt ist.

Es kommt darauf an was man genau machen möchte, daher ist zwar die 
1-Variable-Variante gut, jedoch immer noch nicht 100% Interruptsicher, 
je nach Menge von Code der aus dem Main Loop sonst noch aufgerufen wird.

Beispiel Ablauf:

Main Programmbearbeitung aktiv...
Timer: Timer setzt signal
Main: Main Programm immer noch aktiv wird nach 999,9999µs jedoch fertig.
Main: Im Main wird das "Signal" erkannt -> springe in if()
Timer: Genau jetzt sind 1000,000µs vorbei und der Interrupt kommt erneut
Timer: das "signal" ist immer noch gesetzt
Main: ist jetzt kurz nach dem if() und löscht das "signal"
-> Somit ist das zweite Signal nicht mehr bearbeitet.

Dieses "Multitasking" Beispiel bleibt mit "while(1)" hängen, wenn die 
Bearbeitung der main länger als 1ms dauert, kann ja sein dass es 
beabsichtigt ist um zu sehen dass man "zu viel" programmiert hat.

Der TO wollte dass jeder Timer ISR auch in der main Schleife ausgegeben 
wird, also die gleiche Anzahl der Pulse. In dem Fall ist die 1-Variablen 
Variante nicht optimal.

Ob er dennoch die 1-Variablen Variante einsetzen möchte, da ihm der 
Seiteneffekt mit der Zykluszeit Überwachung nützlich ist, ist ihm 
überlassen.

(PS zu diesem Artikel:
https://www.mikrocontroller.net/articles/Multitasking#Verbesserter_Ansatz_mit_Timer)

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Markus M. schrieb:
> Es kommt auf das Design drauf an. Sollte die Main Schleife mal länger
> benötigen als 1ms dann bleibt das ganze mit "while(1);" "Hängen".

Nö, dann wird ein Timerzyklus übersprungen. Wie man es richtig macht, 
steht in meinem Link.

> Für kleine Programme sollte es kein Problem sein dass mindestens 1x je
> ms der Main-Loop läuft. Hingegen bei großen kann es durchaus mal länger
> dauern. Und dann funktioniert die 1-Variablen Variante nicht mehr.

Unfug!

> Hingegen die 2-Variablen Variante zeigt einem wie viele Interrupts
> dazwischen waren und macht die Funktion so oft wie der Zähler != Zähler
> aus dem Interrupt ist.

Jaja, du und deine Parallelwelt, alles schon 100mal diskutiert.

> Dieses "Multitasking" Beispiel bleibt mit "while(1)" hängen, wenn die
> Bearbeitung der main länger als 1ms dauert, kann ja sein dass es
> beabsichtigt ist um zu sehen dass man "zu viel" programmiert hat.

[ ] Du hast das Beispiel verstanden.

Probier's noch mal.

> Der TO wollte dass jeder Timer ISR auch in der main Schleife ausgegeben
> wird, also die gleiche Anzahl der Pulse. In dem Fall ist die 1-Variablen
> Variante nicht optimal.

Jaja, immer schön weiter plappern, bloß nicht mal zugeben, daß man sich 
geirrt hat. Erbärmlich!

von foobar (Gast)


Lesenswert?

>> Daher besser das machen:
>> volatile int trigger_1ms;
>> volatile int trigger_1ms_main;
>
> Sicher nicht. Die 2. Variable ist überflüssig und erzeugt nur Stress und
> Chaos.

Die Methode ist schon gut, wenn man sporadisch "verlorene" Interrupts 
nachholen möchte.  Ok, "volatile int" ist blöd (uint8_t wäre ok) und 
"trigger_1ms_main" braucht nicht volatile zu sein, aber ansonsten ok.

> Es reicht EINE Variable. Siehe Multitasking: Verbesserter Ansatz mit Timer

Das ist holprig und eigentlich nur zum Debuggen zu gebrauchen ...

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

1
    // Endlose Hauptschleife
2
    while (1) {
3
        if (flag_1ms) {
4
            flag_1ms=0;
5
            taste = taste_lesen();
6
            led_blinken(taste);
7
            uart_lesen();
8
            if (flag_1ms) {
9
                // Laufzeit der Tasks >1ms, Fehlersignalisierung
10
                // PB1 auf HIGH, Programm stoppen
11
                PORTB |= (1<<PB1);
12
                while(1);
13
            }
14
        }
15
    }
16
17
18
// Interruptserviceroutine für Timer 0
19
// hier 1ms
20
ISR(TIMER0_COMP_vect) {
21
    if (flag_1ms) {
22
        // Laufzeit der Tasks >2ms, Fehlersignalisierung
23
        // PB1 auf HIGH, Programm stoppen
24
        PORTB |= (1<<PB1);
25
        while(1);
26
    }
27
    flag_1ms = 1;
28
}

Falk B. schrieb:
> [ ] Du hast das Beispiel verstanden.

Ja, ich habe das Beispiel aus dem verlinkten Artikel verstanden. Damit 
wir auch wirklich vom gleichen reden habe ich mal den Codeabschnitt aus 
dem Artikel kopiert.
Durch das "while(1);" bleibt das dann "hängen".

Und nun erkläre mir doch bitte wie das ganze zu dem Wunsch vom TO passt.

Zu Erinnerung: TO wollte nur genau gleich viele Impulse wie im 
Interrupt, egal wie viel Arbeit der Main-Loop zu bewältigen hat.
Hingegen eine Zykluszeitüberwachung war nicht gefordert.

Nach wie vor:
Die Lösung von mir funktioniert immer.
Die Lösung von dir nur so lange der Main-Loop nicht zu viel Arbeit hat.

Klar, beide Lösungen funktionieren im aktuellen Quellcode, in dem man 
ein paar Codezeilen zum Test programmiert, jedoch wird man damit böse 
Erlebnisse erfahren wenn man mal was sehr großes macht.

von Falk B. (falk)


Lesenswert?

So ist es korrekt.
1
#include <stdbool.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
#define F_CPU 8000000   // CPU Takt, /Hz
6
#define F_TIMER 1000    // Timer-Takt, /Hz
7
8
volatile bool trigger_1ms;
9
10
static void init_system(void) {
11
12
  DDRC = (1<<PC4) | (1<<PC3);  // Test-Outputs
13
  
14
  // Timer 0 init
15
  // mode 2, CTC  Prescaler 64
16
  TCCR0  = (1<<WGM01) | (1<<CS01) | (1<<CS00);
17
  OCR0   = (F_CPU/(64L*F_TIMER)) - 1; 
18
  TIMSK |= (1<<OCIE0);      // Output compare Interrupt  
19
  sei();                    // Interrupts global Ein
20
}
21
22
int main(void) {
23
  init_system();
24
25
  while(1) {
26
    if (trigger_1ms) {
27
      trigger_1ms = false;
28
      // atomares Umschalten eine IO-Pins, funktioniert nur bei halbwegs "neuen" AVRs
29
      // mit PINx Toggle Funktion beim Schreibzugriff, siehe Datenblatt
30
      PINC = (1<<PC3);
31
      /*
32
      // atomares Umschalten für "alte" AVRs ohne PINx Toggle Funktionalität
33
      cli();
34
      PORTC ^= (1<<PC3);
35
      sei();
36
      */                
37
    }
38
  }
39
}
40
41
// Timer ISR, 1kHz
42
43
ISR (TIMER0_COMP_vect)  {
44
  PORTC ^= (1<<PC4);
45
  trigger_1ms = true;
46
}

von foobar (Gast)


Lesenswert?

> Die Lösung von mir funktioniert immer.

Naja, mit "immer" wäre ich vorsichtig ;-)  Wenn main konstant zu langsam 
ist fällt sie auf die Schnauze - es muß schon genügend Zeit vorhanden 
sein, die Interrupts nachzuholen.

Allerdings versucht sie, das Problem zu behandeln anstatt das Problem 
nur zu erkennen und dann den Dienst einzustellen.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

foobar schrieb:
> Naja, mit "immer" wäre ich vorsichtig ;-)  Wenn main konstant zu langsam
> ist fällt sie auf die Schnauze

Stimmt ... dann hat man jedoch ein viel größeres grundlegendes 
Konzeptproblem.

von Oliver S. (oliverso)


Lesenswert?

Arduino Fanboy D. schrieb:
> c99 kennt _Bool
> Und hier <stdbool.h> wird dann auch bool kreiert.

Würde es, wenn’s im Code stehen würde. Tut es aber nicht.

Ste R. schrieb:
> Ich habe gelernt:
> - dass meine Trigger-Variable voaltile sein muss, weil sie in der ISR
> verändert wird und in der zyklischen Main-Funktion benutzt wird.
> Offensichtlich hat der Compiler gemeint, hier optimieren zu dürfen,
> deshalb lief es zwischendurch nicht. Mit volatile ist das verhindert und
> läuft wie gewünscht.

Sowohl gcc 7.2.0 als auch 10.2.0 optimieren die Variable nicht weg. Das 
war also in dem Fall nicht das Problem.

Ste R. schrieb:
> - dass Interrupts en- und disablen in einer ISR Blödsinn ist (gut, um
> 13:40Uhr merke ich das eher als 1:00Uhr nachts ;-)

Es verbrät zwei Prozessorzyklen für nichts, Mahr geht dadurch aber nicht 
kaputt. Das war also in dem Fall nicht das Problem.


> - dass bei CTC Funktion ich nix am eigentlichen Zählerwert rumwurschteln
> muss, was die Fehleranfälligkeit verringert/verhindert. Deshalb CTC
> vorziehen vor Zähler vorbelegen

Das „rumwurschteln“ am Zähler wird dann ein Problem, wenn es direkt vor 
oder gleichzeitig mit Compare-Matches stattfindet. Da du die aber nicht 
nutzt, und den Zähler in einer OVF-ISR setzt, macht das bei dir nichts. 
Das war also in dem Fall nicht das Problem. Richtig ist natürlich, daß 
man für die Aufgabenstellung besser Den CTC-Mode benutzt, wenn 
vorhanden.

> - Dass beim Toggeln von IO-Ports in Betracht gezogen werden muss, dass
> in der Anweisung eine ISR zuschlagen kann --> allerdings weniger
> relevant, da das Port-getoggle ja nur meinem Debugging diente und im
> finalen Programm nicht mehr drin ist.

Das sollte man beachten, wobei bei allen neueren AVRs nach dem 
steinalten Mega8 die Operation atomar toggelt.

Das eigentliche Problem war die Behandlung des Flags.

Oliver

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.