Forum: Mikrocontroller und Digitale Elektronik Versuch einer Millis Funktion für AVR


von Mirko (mirkomikro)


Lesenswert?

Hallo,

ich versuche mich gerade daran, eine Art Millis Funktion, wie es auch 
bei Arduino gibt für meine Projekte umzusetzen.

Leider komme ich irgendwie nicht weiter, bzw. habe ich eine Blockade. 
Hat jemand für mich eventuell einen Tipp? Bin ich überhaupt auf dem 
richtigen Weg?


Über den CTC Timer1 wird jede Sekunde die globale Variable millis 
hochgezählt. Diese greife ich ab und vergleiche Sie mit dem gewünschten 
Intervall und möchte dann damit die LED blinken lassen.
1
#define CONFIG_OUTPUTS  { DDRB |= (1 << PB5); }      // Configure outputs (1)
2
#define LED_GN_TOGGLE  { PORTB ^= (1 << PB5); }            // PB5 - LED green toggle
3
4
5
volatile unsigned long millis = 0;
6
7
int main (void)
8
{
9
  CONFIG_OUTPUTS;              // Set LED as output
10
  
11
  TCCR1B |= (1 << WGM12);          // Configure timer 1 for CTC mode
12
  TIMSK1 |= (1 << OCIE1A);        // Enable CTC interrupt
13
  sei ();                  // Enable global interrupts
14
  OCR1A = 31249;              // Set CTC compare value to 1 Hz at 8 MHz AVR clock , with a prescaler of 256
15
  TCCR1B |= (1 << CS12);          // Start timer at F_CPU /256
16
  
17
  const unsigned long eventInterval = 1000;
18
  unsigned long previousTime = 0;
19
  
20
  while (1)
21
  {
22
      // Updates frequently
23
      unsigned long currentTime = millis;
24
      // This is the event
25
      if (currentTime - previousTime >= eventInterval) {
26
        // Event code
27
        LED_GN_TOGGLE;        
28
        // Update the timing for the next time around
29
        previousTime = currentTime;
30
      }
31
  }
32
}
33
34
35
ISR (TIMER1_COMPA_vect)
36
{
37
  millis++;
38
}

von Sebastian W. (wangnick)


Lesenswert?

1. Warum wird millis nur jede Sekunde um 1 erhöht, und nicht jede 
Millisekunde?
2. Der Zugriff auf millis in main() ist nicht atomar. Auf einem AVR sind 
byteweise Zugriffe atomar, millis besteht aber aus 4 Bytes. Eine 
mögliche Abhilfe wäre, vor der Zuweisung an currentTime die Interrupts 
zu sperren, und sie nach der Zuweisung wieder freizugeben.

LG, Sebastian

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Sebastian W. schrieb:
> Eine
> mögliche Abhilfe wäre, vor der Zuweisung an currentTime die Interrupts
> zu sperren, und sie nach der Zuweisung wieder freizugeben.

Oder die Atomic Makros verwenden, dann kann man auch auf das volatile 
verzichten.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

wenn der Timer auf 1Hz konfiguriert ist und man den Sekundenzähler im 
1000er Intervall abfragt, dann wartet man 1000 Sekunden. Konfiguriere 
den Timer auf 1000Hz, dann macht auch der Variablenname millis wieder 
Sinn. Nimm die Formel laut Manual für den CTC Mode, stelle nach TOP um 
und rechne es aus. Berechne es mit allen Prescalern und nimm am Ende 
einen womit ein Ganzzahl TOP Wert rauskommt.

von Werner P. (werner4096)


Lesenswert?

Warum eigentlich so kompliziert mit currentTime und previousTime usw.

Warum nicht eine volatile uint8_t tick_sec variable?
1
  while (1)
2
  {
3
    if (tick_sec) {
4
      tick_sec = 0;
5
    }
6
  }
7
8
ISR (TIMER1_COMPA_vect)
9
{
10
  static int tick = 1000
11
  
12
  tick--;
13
  if (tick > 0) {
14
    tick--;
15
    if (tick == 0) {
16
      tick = 1000;
17
      tick_sec = 1;
18
    }
19
  }
20
}

von Arduino F. (Firma: Gast) (arduinof)


Angehängte Dateien:

Lesenswert?

Werner P. schrieb:
> tick_sec = 0;

Jedes dieser tick_sec macht deine ISR fetter.

Werner P. schrieb:
> Warum eigentlich so kompliziert mit currentTime und previousTime usw.
Weil es funktioniert!
Weil es einfach ist.
Es kann Intervalle bis 49,x Tage.

Bei deinem Tick muss du dafür Sorge tragen, dass diese Ticks in jedem 
Programmteil ankommen.
Und Millis gibts überall.

Sicherlich gehen beide Wege.....
Also einen aussuchen und damit glücklich werden.

von Nemopuk (nemopuk)


Lesenswert?

Mirko schrieb:
> Leider komme ich irgendwie nicht weiter

Funktioniert es nicht? Beschreibe das Problem!

Die millis Variable muss volatile sein, sonst neigt der Compiler dazu, 
ihren Wert in CPU Registern zu cachen und dann bekommt main() die 
Änderungen durch die ISR nicht mit.

Du solltest auch dafür sorgen, dass der Lesezugriff auf die Variable 
millis nicht durch die ISR gestört/unterbrochen wird, sonst liest main 
sporadisch Quatsch:

Beispiel:

- millis ist gerade 255
- main liest das erste Byte als 255
- isr erhöht auf 256
- main liest das zweite Byte als 1
- main liest das dritte Byte als 0
- main liest das vierte Byte als 0

Am Ende sieht main den Wert 511, richtig wäre aber 256.

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Werner P. schrieb:
1
ISR (TIMER1_COMPA_vect)
2
{
3
  static int tick = 1000
4
 
5
  tick--;          // hier
6
  if (tick > 0) {
7
    tick--;        // hier nochmal
8
    if (tick == 0) {
9
      tick = 1000;
10
      tick_sec = 1;
11
    }
12
  }
13
}

Bei jedem Durchlauf der ISR wird "tick" zweimal dekrementiert? Wozu?

von Veit D. (devil-elec)


Lesenswert?

Nemopuk schrieb:

> Die millis Variable muss volatile sein, sonst neigt der Compiler dazu,
> ihren Wert in CPU Registern zu cachen und dann bekommt main() die
> Änderungen durch die ISR nicht mit.
>
> Du solltest auch dafür sorgen, dass der Lesezugriff auf die Variable
> millis nicht durch die ISR gestört/unterbrochen wird, sonst liest main
> sporadisch Quatsch:

Auf volatile kann und sollte man hierfür verzichten.

a) Beitrag "Re: Versuch einer Millis Funktion für AVR"
b) Beitrag "Re: c volatile -> wann bracht mans wirklich?"

von Veit D. (devil-elec)


Lesenswert?

Harald K. schrieb:
> Werner P. schrieb:
>
>
1
> ISR (TIMER1_COMPA_vect)
2
> {
3
>   static int tick = 1000
4
> 
5
>   tick--;          // hier
6
>   if (tick > 0) {
7
>     tick--;        // hier nochmal
8
>     if (tick == 0) {
9
>       tick = 1000;
10
>       tick_sec = 1;
11
>     }
12
>   }
13
> }
14
>
>
> Bei jedem Durchlauf der ISR wird "tick" zweimal dekrementiert? Wozu?

Hallo,

wäre immer noch zu viel. Kann man kürzen auf ...
1
ISR (TIMER1_COMPA_vect)
2
{
3
  static int tick {1000};
4
 
5
  tick--;         
6
  if (tick < 1) {
7
    tick = 1000;
8
    tickSec = true;
9
  }
10
}
11
12
oder
13
14
ISR (TIMER1_COMPA_vect)
15
{
16
  static unsigned int tick {0};
17
 
18
  tick++;         
19
  if (tick > 999) {
20
    tick = 0;
21
    tickSec = true;
22
  }
23
}

Wäre mir jedoch der falsche Ansatz, wenn man einen ms Zähler haben 
möchte. Die ISR soll einfach nur eine ms Variable hochzählen. Mehr muss 
und sollte sie nicht machen. Mittels millis baut man sich dann 
verschiedene unabhängige Timer. Das hat m.E. nichts in der ISR zu 
suchen.

von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> Auf volatile kann und sollte man hierfür verzichten.

Kann und sollte man nicht.

Der verlinkte Beitrag b) ist zwar nicht verkehrt, aber wie alles von 
Wilhelm ist das halt nur und ausschließlich auf aktuellstes C++ 
zutreffend.

Hier gehts um C.

Oliver

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Auf volatile kann und sollte man hierfür verzichten.
>
> b) Beitrag "Re: c volatile -> wann bracht mans wirklich?"

Da wird nur seitenweise doziert ohne zu zeigen wie es gehen könnte.

Mit avr-gcc gibe es weder _Atomic noch std::atomic. (Und auch keine 
libatomic, und wenn würde die keiner verwenden weil extrem 
resourcenfressend.)

Posix Threads gibt es auch nicht.

Und was soll "explizites Abschalten der Nebenläufigkeit" im hiesigen 
Kontext anderes sein als das, was avr/atomic.h macht?

Also mal Butter bei die Fische und nach konkrete Codebeispiele.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Nemopuk schrieb:
> Die millis Variable muss volatile sein,

Direkt über deinem Posting findest du ein funktionierendes Beispiel ohne 
volatile.
Beitrag "Re: Versuch einer Millis Funktion für AVR"

Johann L. schrieb:
> Also mal Butter bei die Fische und nach konkrete Codebeispiele.
Wenige Postings über deinem!

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Arduino F. schrieb:
> Direkt über deinem Posting findest du ein funktionierendes Beispiel ohne
> volatile.

Vor allem: Volatile würde hier auch nicht helfen.

Beitrag #7940448 wurde vom Autor gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Direkt über deinem Posting findest du [...]

> Wenige Postings über deinem!

Geht's noch etwas ungenauer?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Oliver S. schrieb:
> Veit D. schrieb:
>> Auf volatile kann und sollte man hierfür verzichten.
>
> Kann und sollte man nicht.

Volatile macht Sinn, und findet seinen Zweck wenn es sich um Hardware 
Register handelt, welche sich Außerhalb des Programms ändern können.

Bei Werten aus einer ISR  und ihrer späteren Verarbeitung ist das nicht 
der Fall.
Interrupts abschalten und Memory Barrier setzen, beides macht das Atomic 
Block Makro.
Natürlich sowohl in C als auch C++.
Zumindest für AVR.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Geht's noch etwas ungenauer?
Auf den Link klicken kannst du nicht?

von Veit D. (devil-elec)


Lesenswert?

Johann L. schrieb:
> Veit D. schrieb:
>> Auf volatile kann und sollte man hierfür verzichten.
>>
>> b) Beitrag "Re: c volatile -> wann bracht mans wirklich?"
>
> Da wird nur seitenweise doziert ohne zu zeigen wie es gehen könnte.
>
> Mit avr-gcc gibe es weder _Atomic noch std::atomic. (Und auch keine
> libatomic, und wenn würde die keiner verwenden weil extrem
> resourcenfressend.)

Womit ist sie Resourcenfressend auffällig?

> Und was soll "explizites Abschalten der Nebenläufigkeit" im hiesigen
> Kontext anderes sein als das, was avr/atomic.h macht?
>
> Also mal Butter bei die Fische und nach konkrete Codebeispiele.

Man muss ja nicht die std::atomic verwenden. Wie es ohne funktioniert 
hat Arduino F. schon gezeigt. Wenn man allerdings std::atomic verwendet, 
nimmt man einen atomic Datentyp und der ATOMIC BLOCK ist auch 
Geschichte.

von Werner P. (werner4096)


Lesenswert?

Harald K. schrieb:
> Werner P. schrieb:
>
>
1
> ISR (TIMER1_COMPA_vect)
2
> {
3
>   static int tick = 1000
4
> 
5
>   tick--;          // hier
6
>   if (tick > 0) {
7
>     tick--;        // hier nochmal
8
>     if (tick == 0) {
9
>       tick = 1000;
10
>       tick_sec = 1;
11
>     }
12
>   }
13
> }
14
>
>
> Bei jedem Durchlauf der ISR wird "tick" zweimal dekrementiert? Wozu?

ah. grober Fehler beim schnellen Tippen! tick++ vor "if" gehört 
natürlich weg.

Danke für die Info

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Man muss ja nicht die std::atomic verwenden.

Kann man ja garnicht, weil die im AVR GCC nicht implementiert sind.

Oder hast du eine eigene Toolchain?

Ist dann so, als würdest du ein Auto in die Werkstatt bringen zum 
reparieren und der Mechaniker so:

"Also mein Auto funktioniert."

Arduino F. schrieb:
> Johann L. schrieb:
>> Geht's noch etwas ungenauer?
> Auf den Link klicken kannst du nicht?

Du hattest keinen Code verlinkt.

Wenn du Code referenzierst, dann verlinke ihn einfach.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Du hattest keinen Code verlinkt.
>
> Wenn du Code referenzierst, dann verlinke ihn einfach.

Ich habe einen Link auf einen Beitrag gesetzt, welcher den Code 
beinhaltet!
Schade, dass dich das so hoffnungslos überfordert.

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Oder hast du eine eigene Toolchain?

Ja, haben wir!
AVR Gcc 12.2 mit der libstdc++

Diese libstdc++ ist (noch) nicht vollständig.
Die meisten grundlegenden Dinge sind allerdings da.

std::atomic ist da, aber funktioniert noch nicht, weil der Code für z.B. 
__atomic_fetch_add_8 und `__atomic_load_8 noch fehlt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Johann L. schrieb:
>> Oder hast du eine eigene Toolchain?
>
> Ja, haben wir!
> AVR Gcc 12.2 mit der libstdc++

Hilft dem TO bestimmt weiter.

von Sebastian W. (wangnick)


Lesenswert?

War ja klar, dass die volatile-atomic-memorybarrier-Debatte hier wieder 
fröhliche Urständ feiern würde …

LG, Sebastian

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Hilft dem TO bestimmt weiter.

Du hast danach gefragt und eine Antwort bekommen!
Also beschwere dich nicht.
Oder hast du heute deinen irrationalen Tag?

Der TO hat dagegen ein Beispiel mit ATOMIC_BLOCK bekommen.
Das kann ihm helfen.

von Veit D. (devil-elec)


Lesenswert?

Hallo Johann,

ganz ehrlich. Manchmal ist es mit dir wie im Kindergarten.
Du hattest nach einer Toolchain gefragt mit libstdc++.
Ja gibt es. https://github.com/modm-io/avr-libstdcpp
std::atomic ist wie schon geschrieben wurde noch nicht verwendbar.
Es war nur eine Nebenbemerkung von mir, bezüglich Wilhelms volatile 
Erklärung und deinem Kommentar dazu. Tut nichts zu eigentlichen Sache, 
außer volatile natürlich. Es ist allen klar, dass sie dem TO nicht 
hilft.
Sie muss ihm auch nicht helfen, weil der TO sie hier für seinen Code 
nicht benötigt. Hast du selbst schon klargestellt. ATOMICBLOCK und gut 
ist.
Um mehr geht es doch hier gar nicht.

Um des Friedens Willen extra für Johann ganz privat der Link. ;-)
Beitrag "Re: Versuch einer Millis Funktion für AVR"

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> https://github.com/modm-io/avr-libstdcpp

So wie ich die dortigen Maintainer verstehe, ist dieses Projekt auf 
modm-io designed.  Mann kann sich damit also nicht einfach eine avr-g++ 
Toolchain basteln.  Siehe auch dortige Bugreports.

Dass der Code nicht zu GCC beigetragen wurde lässt auch tief blicken 
(Wakely ist ja GCC Maintainer).

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Mann kann sich damit also nicht einfach eine avr-g++
> Toolchain basteln.

Wie schon gesagt, das meiste funktioniert.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Johann L. schrieb:
>> Mann kann sich damit also nicht einfach eine avr-g++
>> Toolchain basteln.
>
> Wie schon gesagt, das meiste funktioniert.

Es geht nicht um den Funktionsumfang, sondern das Zeugt überhaupt 
nutzbar zu machen.

von Axel S. (a-za-z0-9)


Lesenswert?

Arduino F. schrieb:
> Oliver S. schrieb:
>> Veit D. schrieb:
>>> Auf volatile kann und sollte man hierfür verzichten.
>>
>> Kann und sollte man nicht.
>
> Volatile macht Sinn, und findet seinen Zweck wenn es sich um Hardware
> Register handelt, welche sich Außerhalb des Programms ändern können.
>
> Bei Werten aus einer ISR  und ihrer späteren Verarbeitung ist das nicht
> der Fall.

Doch natürlich. Eine Variable, die in einer ISR verändert wird, ist für 
den Compiler genauso wenig verhersehbar. Weder weiß der Compiler, ob der 
Interrupt für die ISR überhaupt enabled ist, noch ob die (Hardware!) 
Bedingung für das Auslösen des Interrupt gegeben ist. Für den Compiler 
ist das eine Variable, die er nicht in einem Register cachen darf, 
sondern immer direkt lesen oder schreiben muß.

> Interrupts abschalten und Memory Barrier setzen, beides macht das Atomic
> Block Makro.
> Natürlich sowohl in C als auch C++.
> Zumindest für AVR.

Die Dokumentation auf 
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html 
gibt das nicht her. Sie sagt lediglich aus, daß der Code innerhalb des 
ATOMIC_BLOCK nicht unterbrochen werden kann. Von Memory Barrier steht da 
nix und abgesehen davon würde sie auch nicht helfen. Ja mehr noch: das 
Beispiel da geht genau auf den vorliegenden Fall ein. Und die Variable 
ist selbstverständlich volatile definiert. Man braucht beides: das 
volatile, damit der Compiler keine veraltete Kopie der Variable benutzt 
und den ATOMIC_BLOCK, damit der Compiler Code generiert, der innerhalb 
des Schreib- oder Lesezugriffs nicht unterbrochen wird.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Axel S. schrieb:
> Arduino F. schrieb:
>> Interrupts abschalten und Memory Barrier setzen, beides macht
>> das Atomic Block Makro.  Natürlich sowohl in C als auch C++.
>> Zumindest für AVR.
>
> Die Dokumentation auf
> https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
> gibt das nicht her. Sie sagt lediglich aus, daß der Code innerhalb des
> ATOMIC_BLOCK nicht unterbrochen werden kann. Von Memory Barrier steht da
> nix und abgesehen davon würde sie auch nicht helfen.

Naja eine Memory Barrier würde den kompletten Speicher invalidieren. 
Das machen zum Beispiel auch cli() und sei(), zumindest in neueren 
Versionen der AVR-LibC.  Aber gerade bei den AVR Tools sind gerne mal 
noch antike Versionen im Einsatz...

Bei ATOMIC_BLOCK fehlen teilweise Memory Barriers.  Beispiel:
1
static __ATTR_ALWAYS_INLINE__ void __iRestore(const  uint8_t *__s)
2
{
3
    SREG = *__s;
4
}
Das ist eine volatile Barrier, aber keine Memory Barrier.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Johann L. schrieb:
> Veit D. schrieb:
>> https://github.com/modm-io/avr-libstdcpp
>
> So wie ich die dortigen Maintainer verstehe, ist dieses Projekt auf
> modm-io designed.  Mann kann sich damit also nicht einfach eine avr-g++
> Toolchain basteln.  Siehe auch dortige Bugreports.

Man kann sie inkludieren und verwenden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Johann L. schrieb:
>> Veit D. schrieb:
>>> https://github.com/modm-io/avr-libstdcpp
>>
>> So wie ich die dortigen Maintainer verstehe, ist dieses Projekt auf
>> modm-io designed.  Mann kann sich damit also nicht einfach eine avr-g++
>> Toolchain basteln.  Siehe auch dortige Bugreports.
>
> Man kann sie inkludieren und verwenden.

Um das in GCC zu integrieren ist es unbrauchbar.

von Veit D. (devil-elec)


Lesenswert?

Axel S. schrieb:

> Doch natürlich.
> ...

Genauso hatte ich das bis vor einer Weile auch verteidigt. Bis mir die 
Welt hier im Forum es komplett anders erklärt hat, mit Hunderten 
Antworten und Threads. Ich kann es nicht mehr zählen. Es gab in all den 
anderen Threads zum Thema keinen Widerspruch. Und jetzt beginnt genau 
dieses Spiel von vorn. Muss man sich nicht wundern, wenn die Leute 
pauschal volatile und ATOMIC BLOCK verwenden. Einfach weil sie wissen 
das es damit funktioniert und damit sicher sind.

von Nemopuk (nemopuk)


Lesenswert?

Es gibt dazu einen Artikel von Microchip.
https://developerhelp.microchip.com/xwiki/bin/view/products/mcu-mpu/8-bit-avr/peripherals/interrupts/special-considerations/

Muss man einfach nur lesen und verstehen.

Veit D. schrieb:
> Es gab in all den anderen Threads zum Thema keinen Widerspruch.
> Und jetzt beginnt genau dieses Spiel von vorn.

Mich nervt es auch. Das ist wie die Diskussion, ob ein Glas halb voll 
oder halb leer ist. Es geht den Diskutanten nur um Klugscheißerei.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Johann L. schrieb:
> Veit D. schrieb:
>> Johann L. schrieb:
>>> Veit D. schrieb:
>>>> https://github.com/modm-io/avr-libstdcpp
>>>
>>> So wie ich die dortigen Maintainer verstehe, ist dieses Projekt auf
>>> modm-io designed.  Mann kann sich damit also nicht einfach eine avr-g++
>>> Toolchain basteln.  Siehe auch dortige Bugreports.
>>
>> Man kann sie inkludieren und verwenden.
>
> Um das in GCC zu integrieren ist es unbrauchbar.

Du sollst dir damit nicht eine Toolchain kompilieren. Du sollst sie 
inkludieren mit include <>. Siehe Abschnitt "Using the library with 
MICROCHIP's ATMEL Studio". Die Beschreibung ist gültig bis avr-gcc 14.x.

von Veit D. (devil-elec)


Lesenswert?

Nemopuk schrieb:
> Es gibt dazu einen Artikel von Microchip.
> 
https://developerhelp.microchip.com/xwiki/bin/view/products/mcu-mpu/8-bit-avr/peripherals/interrupts/special-considerations/
>
> Muss man einfach nur lesen und verstehen.
>
> Veit D. schrieb:
>> Es gab in all den anderen Threads zum Thema keinen Widerspruch.
>> Und jetzt beginnt genau dieses Spiel von vorn.
>
> Mich nervt es auch. Das ist wie die Diskussion, ob ein Glas halb voll
> oder halb leer ist. Es geht den Diskutanten nur um Klugscheißerei.

Ich lehne mich jetzt auch nicht weiter aus dem Fenster. Wie verlinkt von 
dir ist das der gemeinsame Nenner der immer und jederzeit funktioniert. 
Unabhängig von irgendwas. Alles andere ist mir jetzt egal. Die 
Zusammenhänge sind nicht einfach.

von Ben S. (bensch123)


Lesenswert?

Ich habe mir in C++ einen plattformunabhängigen Timer gebaut, welcher 
(theoretisch) in Mikrosekunden zählen kann und nach ca. 600.000 Jahren 
(64bit in Mikrosekunden) überläuft.

Also das geht alles schon.

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Ben S. schrieb:
> Ich

Schön, dass du das gemacht hast.
Bestimmt ab und zu nützlich.

Nur schade, dass außer Werbung dafür, hier nix davon zu sehen ist.

von Norbert (der_norbert)


Lesenswert?

Ben S. schrieb:
> Ich habe mir in C++ einen plattformunabhängigen Timer gebaut, welcher
> (theoretisch) in Mikrosekunden zählen kann und nach ca. 600.000 Jahren
> (64bit in Mikrosekunden) überläuft.

Die Frage ist: Was passiert danach?
Hast du das korrekte Überlaufverhalten in Echtzeit überprüft?
Nicht dass man sich da später ein Problem einfängt. 😎

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Ben S. schrieb:
> 64bit in Mikrosekunden

64 Bit in Mikrosekunden... auf einem AVR? Oder bin ich hier im falschen 
Thread?

von Axel S. (a-za-z0-9)


Lesenswert?

Veit D. schrieb:
> Axel S. schrieb:
>
>> Doch natürlich.
>> ...
>
> Genauso hatte ich das bis vor einer Weile auch verteidigt. Bis mir die
> Welt hier im Forum es komplett anders erklärt hat, mit Hunderten
> Antworten und Threads.

Das ist an mir vorbeigegangen. Ist wohl auch besser so.

Jetzt lese ich allerdings gerade, daß die AVR-libc auch ein Makro 
_MemoryBarrier() im Angebot hat 
(https://www.nongnu.org/avr-libc/user-manual/group__avr__cpufunc.html)

Und der Text dazu liest sich, als würde das den Compiler dazu zwingen, 
Variablen-Zugriffe temporär(?) so durchzuführen als wäre die Variable 
volatile deklariert:

"instructs the compiler to not cache any memory data in registers beyond 
the barrier. This can sometimes be more effective than ... volatile"

Mit dieser Dokumentation bin ich aus mehreren Gründen nicht glücklich. 
"beyond the barrier" ... und wo endet diese Barriere? Sollte das nicht 
eher lauten "to invalidate any register caches established before the 
barrier"? Und davon mal abgesehen, ich will gar nicht alle Variablen, 
die in irgendwelchen Registern gecached sind verwerfen, sondern nur die 
eine, die sich asynchron ändern kann.

Deswegen halte ich immer noch volatile für die bessere Lösung. Zumal das 
auch außerhalb eines ATOMIC_BLOCK funktioniert. Man stelle sich mal vor, 
der Nutzer hat Interrupts abgeschaltet und glaubt nun er bräuchte keinen 
ATOMIC_BLOCK mehr, um auf die Variable zuzugreifen (ist ja auch so). 
Dummerweise implementiert ATOMIC_BLOCK aber auch das volatile und das 
fehlt jetzt. Nein, das führt in Teufels Küche. Die Dinge sollten nur das 
machen, was im Namen drinsteht. Und das Makro _MemoryBarrier() sollte 
eher _DropCachedVariables() heißen. Sofern es das tut, was ich vermute.

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Axel S. schrieb:
> Dummerweise implementiert ATOMIC_BLOCK aber auch das volatile
Das tut es nicht!

von Veit D. (devil-elec)


Lesenswert?

Hallo,

es werden nicht alle Variablen die in irgendwelchen Registern stehen 
verworfen. Die Funktion dient primär ccp_write_io() und behandelt nur 
das angegebene Register + Wert. Wurde spätestens mit den neuen AVR 
Controllern notwendig. Ich habe es jedenfalls vorher nicht benötigt. 
ccp_write_io() meine ich.

> Dummerweise implementiert ATOMIC_BLOCK aber auch das volatile ...
?

Habe ich einen Test gemacht. :-)
1
#include "util/atomic.h"
2
3
unsigned long nonVolatileCounter; 
4
volatile unsigned long isVolatileCounter; 
5
6
void testeVolatilitaet()
7
{
8
  for (unsigned long i = 0; i < 1000; i++) {
9
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
10
      ++nonVolatileCounter; 
11
      ++isVolatileCounter;
12
    }  
13
  }
14
}
15
16
void setup()
17
{
18
  TCCR1B = 0;
19
  TCCR1A = 0;
20
  TCCR1B |= (1 << WGM12);        
21
  TIMSK1 |= (1 << OCIE1A);       
22
  OCR1A = 9;                  
23
  TCCR1B |= (1 << CS00);         
24
  
25
  Serial.begin(250000);
26
  Serial.println("\nStart\n");
27
28
  for (unsigned long i = 0; i < 1000; i++) {
29
    testeVolatilitaet();    
30
  }
31
32
  TCCR1B = 0;
33
  Serial.print("nonVolatile "); Serial.println(nonVolatileCounter);
34
  Serial.print(" isVolatile "); Serial.println(isVolatileCounter);
35
}
36
37
void loop() {
38
39
}
40
41
ISR (TIMER1_COMPA_vect)
42
{
43
  ++nonVolatileCounter; 
44
  ++isVolatileCounter;
45
}

Bringt wiederholbar exakt das gleiche Ergebnis.
nonVolatile 8007547
 isVolatile 8007547
Dauert in der Einstellung 40s.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Axel S. schrieb:
> Jetzt lese ich allerdings gerade, daß die AVR-libc auch ein Makro
> _MemoryBarrier() im Angebot hat
> (https://www.nongnu.org/avr-libc/user-manual/group__avr__cpufunc.html)
>
> Und der Text dazu liest sich, als würde das den Compiler dazu zwingen,
> Variablen-Zugriffe temporär(?) so durchzuführen als wäre die Variable
> volatile deklariert:
>
> "instructs the compiler to not cache any memory data in registers beyond
> the barrier. This can sometimes be more effective than ... volatile"
>
> Mit dieser Dokumentation bin ich aus mehreren Gründen nicht glücklich.
> "beyond the barrier" ... und wo endet diese Barriere?

Ich verstehe die Frage nicht.

Bei
1
int x, y;
2
3
int add_x (int i)
4
{
5
    y = x;
6
    ...Barrier
7
    return x + i;
8
}

muss x eben 2 mal gelesen werden: Einmal vor der Barriere, und einmal 
danach.  Weil die Barriere ein "Memory Clobber" macht muss der Wert von 
x neu gelesen werden.

> Sollte das nicht eher lauten
> "to invalidate any register caches established before the barrier"?

Nein.  Registerinhalte überleben ja, etwa der Wert von i unabhängig 
davon, ob i in einem Register lebt oder im Speicher (Frame).

> Und davon mal abgesehen, ich will gar nicht alle Variablen,

Dann brauchst du also echte _Atomic oder Äquivalente, oder anstelle von 
sei(), cli(), ATOMIC_BLOCK, die komplette Memory Barriers 
implementieren, eben was eigenes.  Zum Beispiel:
1
__asm ("" : "+m" (x));


Arduino F. schrieb:
> Axel S. schrieb:
>> Dummerweise implementiert ATOMIC_BLOCK aber auch das volatile
> Das tut es nicht!

Es implementiert immerhin eine volatile Barrier :-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Habe ich einen Test gemacht. :-)

Komplett belanglos.  Dass der Code macht, was du erwartest, ist 
notwendig dafür, dass der Code korrekt ist, aber nicht hinreichend.

https://de.wikipedia.org/wiki/Notwendige_und_hinreichende_Bedingung

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Es implementiert immerhin eine volatile Barrier :-)

Wenn du das volatile im Inlineassembler meinst, das hat eine andere 
Bedeutung/Zweck als das C volatile.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Johann L. schrieb:
>> Es implementiert immerhin eine volatile Barrier :-)
>
> Wenn du das volatile im Inlineassembler meinst, das hat eine andere
> Bedeutung/Zweck als das C volatile.

Hat auch keiner behauptet.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Hat auch keiner behauptet.

Hmmm....

Den Begriff "volatile Barrier" hatte ich noch nicht gehört.
Daraufhin danach gesucht...
Nicht gefunden.
Jetzt nach deine Antwort auf meine Frage, muss ich wohl davon ausgehen, 
dass du dir den gerade aus den Fingern gesaugt hast.

von Mirko (mirkomikro)


Lesenswert?

Sebastian W. schrieb:
> 1. Warum wird millis nur jede Sekunde um 1 erhöht, und nicht jede
> Millisekunde?

Haha, ja ich glaube das war das, was ich eine Blockade genannt habe. 
Wahrscheinlich war das auch mein größtes Problem. Danke für den Hinweis.

> 2. Der Zugriff auf millis in main() ist nicht atomar. Auf einem AVR sind
> byteweise Zugriffe atomar, millis besteht aber aus 4 Bytes. Eine
> mögliche Abhilfe wäre, vor der Zuweisung an currentTime die Interrupts
> zu sperren, und sie nach der Zuweisung wieder freizugeben.

Herzlichen Dank, das werde ich gleich mal probieren!


Arduino F. schrieb:
> Sicherlich gehen beide Wege.....
> Also einen aussuchen und damit glücklich werden.

Auch dir herzlichen Dank, sogar für den angepassten Code!


Ich werde mir mal einen davon aussuchen, auch wenn mir die Frage auf der 
Zunge brennt, welche optimaler bzw professioneller ist, aber ich glaube 
mit dem "funktionieren" bin ich für's erste zufrieden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Den Begriff "volatile Barrier" hatte ich noch nicht gehört.

Ein Compiler muss volatile Aktionen so ausführen wie in der Quelle, darf 
die Reihenfolge also nicht ändern.  Dabei ist es egal, ob es ein 
"normales" volatile ist oder eines aus Inline Asm.  Diesen Aspekt 
betreffend gehören sie also in die gleiche Klasse.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Mirko schrieb:
> Ich werde mir mal einen davon aussuchen, auch wenn mir die Frage auf der
> Zunge brennt, welche optimaler bzw professioneller ist

Situationsabhängig!

Wenn man direkt auf einen Tick reagieren muss, dann eben der eine Weg. 
Was allerdings dazu führt, dass die ISR mit jeder Abhängigkeit fetter 
wird.

Der Arduino Weg erlaubt es einem ohne viel Aufwand ein rudimentäres 
kooperatives Multitasking zu bauen.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> ...

Damit ist es klar, den Begriff hast du dir gerade ausgedacht.

von Mirko (mirkomikro)


Lesenswert?

Arduino F. schrieb:

[Klugscheißermodus]
Muss das in deinem Code nicht...
1
TCCR1B |= (1 << CS10);
...heißen, anstatt CS00?
[/Klugscheißermodus]

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Mirko schrieb:
> Muss das in deinem Code nicht.
Wieso?

Ich denke:
Nein, ich verwende dort den Teiler 1
Und die Magische Nummer 7999 kann man auch fein ersetzen.
OCR1A = F_CPU/1000 - 1;

Die Kommentare habe ich ohne Änderungen übernommen, die sind also eher 
falsch.

: Bearbeitet durch User
von Mirko (mirkomikro)


Lesenswert?

Arduino F. schrieb:
> Mirko schrieb:
>> Muss das in deinem Code nicht.
> Wieso?
>
> Ich denke:
> Nein, ich verwende dort den Teiler 1
> Und die Magische Nummer 7999 kann man auch fein ersetzen.
> OCR1A = F_CPU/1000 - 1;
>
> Die Kommentare habe ich ohne Änderungen übernommen, die sind also eher
> falsch.

Weil es unter dem Register

Timer/Counter1 Control Register B – TCCR1B

nur die Bits CS10, CS11 und CS12 gibt.

Bei Timer/Counter Control Register B – TCCR0B

die Bits CS00, CS01 und CS02.

Und genutzt wird TCCR1B!

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Mirko schrieb:
> nur die Bits CS10, CS11 und CS12 gibt.
Da hast du Wahr!
Und ich pures Glück, dass es dennoch funktioniert hat, da beide den Wert 
Null haben.

: Bearbeitet durch User
von Mirko (mirkomikro)


Lesenswert?

Arduino F. schrieb:
> Mirko schrieb:
>> nur die Bits CS10, CS11 und CS12 gibt.
> Da hast du Wahr!
> Und ich pures Glück, dass es dennoch funktioniert hat

Interessanterweise, hat es das bei mir auch, als ich es blind übernommen 
habe. Ist mir dann aber beim 
Schritt-für-Schritt-Nachkontrolle-zum-Verständnis ins Auge gefallen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Johann L. schrieb:
>> ...
>
> Damit ist es klar, den Begriff hast du dir gerade ausgedacht.

Was ist die korrekte Bezeichnung für das Konzept?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Mirko schrieb:
> Schritt-für-Schritt-Nachkontrolle-zum-Verständnis ins Auge gefallen.

Richtig gemacht!

von Axel S. (a-za-z0-9)


Lesenswert?

Arduino F. schrieb:
> Axel S. schrieb:
>> Dummerweise implementiert ATOMIC_BLOCK aber auch das volatile
> Das tut es nicht!

Nun, weiter oben hast du das Gegenteil behauptet:

Arduino F. schrieb:
> Direkt über deinem Posting findest du ein funktionierendes Beispiel ohne
> volatile.

Allerdings mit dem denkbar schlechtesten "Beweis" durch Zeigen eines 
Codebeispiels, das funktioniert.

Das kann entweder Zufall sein oder eben dem Vorhandensein einer - nennen 
wir es mal volatile barrier - geschuldet sein. Solange es nicht 
dokumentiert ist, daß das genauso funktioniert, verwende ich lieber ein 
explizites volatile. Auf die paar Zyklen kommt es mir dann auch nicht 
an. Lieber habe ich nachvollziehbar korrekten Code.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Axel S. schrieb:
> Nun, weiter oben hast du das Gegenteil behauptet:

Nein!

Axel S. schrieb:
> Solange es nicht
> dokumentiert ist, daß das genauso funktioniert,

Siehe hier in
https://www.nongnu.org/avr-libc/user-manual/group__avr__cpufunc.html
die Doku zu
#define _MemoryBarrier() _asm_ __volatile__("":::"memory")

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Siehe hier in
> https://www.nongnu.org/avr-libc/user-manual/group__avr__cpufunc.html
> die Doku zu
> #define _MemoryBarrier() _asm_ __volatile__("":::"memory")

Die Doku ist echt schrottig, teiweise falsch und unvollständig :-/

von Bruno V. (bruno_v)


Lesenswert?

Mirko schrieb:
> Wahrscheinlich war das auch mein größtes Problem

In der Praxis Dein einziges. Der Rest des Codes ist sehr gut:
 * Auslesen von millis einmal (statt beim Vergleich und beim "restart")
 * zuerst die Subtraktion, dann der Vergleich (der umgekehrte Weg: 
currentTime >= previousTime + 1000 funktioniert NICHT beim Überlauf)

Die fehlende Interrupt-Sperre wird Dir in der Praxis bei diesem Code nur 
selten Probleme verursachen. Nur dann, wenn der 4-Byte-Zugriff nicht 
atomar ist und das high-byte vor dem low-byte gelesen wird und dann 
vermutlich  "gutmütig".
(0muss natürlich trotzdem behoben werden! Dein Code erweitert sich und 
es ist letztendlich unvorhersehbar)

Mirko schrieb:
> welche optimaler bzw professioneller ist
Das war nur eine freundliche Floskel. Werners Ansatz solltest Du NICHT 
in Erwägung ziehen. Es ist ein Hack für Programmier-Anfänger die mit 
Deinem Ansatz noch überfordert sind. Es skaliert nicht und ist deshalb 
auch nicht weiter hier rezipiert worden.

Dein Ansatz erlaubt hunderte oder tausende Timer, lokal und unabhängig. 
Einzige Bedingung: garantierte Auswertung zwischen Ablauf und 
Maximalzeit. Beispiel: Bei 16-Bit und 1ms Takt ist die Maximalzeit 65s. 
Bei einer Timer-Zeit von 1 Minute musst Du nur sicherstellen, dass Du 
nach spätestens 5s auswertest.

: Bearbeitet durch User
von Mirko (mirkomikro)


Lesenswert?

Bruno V. schrieb:
> Mirko schrieb:
>> welche optimaler bzw professioneller ist
> Das war nur eine freundliche Floskel. Werners Ansatz solltest Du NICHT
> in Erwägung ziehen.


Arduino F. schrieb:
> Situationsabhängig!


Das war von mir etwas ungünstig formuliert. Es bezog sich auf die 
Variante von Sebastian W.

Sebastian W. (wangnick)
20.09.2025 22:09

Eine mögliche Abhilfe wäre, vor der Zuweisung an currentTime die 
Interrupts zu sperren, und sie nach der Zuweisung wieder freizugeben.

Und von Arduino F., die Atomic Makros zu nutzen.

von Mirko (mirkomikro)


Lesenswert?

Bruno V. schrieb:
> Mirko schrieb:
>> welche optimaler bzw professioneller ist
> Das war nur eine freundliche Floskel. Werners Ansatz solltest Du NICHT
> in Erwägung ziehen.


Arduino F. schrieb:
> Situationsabhängig!


Das war von mir etwas ungünstig formuliert. Es bezog sich auf die 
Variante von Sebastian W.

Sebastian W. schrieb:
> mögliche Abhilfe wäre, vor der Zuweisung an currentTime die Interrupts
> zu sperren, und sie nach der Zuweisung wieder freizugeben.


Und von Arduino F., die Atomic Makros zu nutzen.

von Christoph M. (mchris)


Lesenswert?

>Mirko schrieb:
>> Wahrscheinlich war das auch mein größtes Problem

Bruno V. (bruno_v)
22.09.2025 07:31

>In der Praxis Dein einziges. Der Rest des Codes ist sehr gut:
> * Auslesen von millis einmal (statt beim Vergleich und beim "restart")
> * zuerst die Subtraktion, dann der Vergleich (der umgekehrte Weg:
> currentTime >= previousTime + 1000 funktioniert NICHT beim Überlauf)

Korrekt ist das hier:
Beitrag "Re: rollover save timer in c"
( Aufpassen mit kleiner und kleiner-gleich )

von Ron H. (ronharry)


Lesenswert?

Mirko’s approach to implementing a Millis function on AVR is practical 
and scalable. Using atomic macros or temporarily disabling interrupts 
ensures safe access to the multi-byte millis variable. Avoiding hacks 
like Werner’s tick method is recommended. For fun, even when coding, I 
enjoy designing custom printed popcorn bags while testing 
microcontrollers.
https://www.packagingunit.com/custom-popcorn-bags/

von Daniel A. (daniel-a)


Lesenswert?

Schade, das c11 atomics für AVR noch nicht implementiert sind. 
(Folgendes ist vermutlich keine gute Idee, war nur mal zum ausprobieren, 
was passiert: https://godbolt.org/z/hh8jbfscj)

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Mirko schrieb:
> Sebastian W. schrieb:
>> mögliche Abhilfe wäre, vor der Zuweisung an currentTime die Interrupts
>> zu sperren, und sie nach der Zuweisung wieder freizugeben.
>
> Und von Arduino F., die Atomic Makros zu nutzen.

Ach so...
Das Atomic Makro, so wie ich es da verwende, macht genau das:
Interrupt für den Vorgang sperren und wieder lösen.
Zusätzlich setzt es die Memory Barriers.

Aber auch sei() und cli() alleine verwendet, setzen Memory Barriers.

Es ist also technisch egal!

Mir gefallen die Atomic Makros besser,
1. Weil man damit schön Anweisungsblöcke bauen kann, das macht es 
übersichtlicher
2. Man kann das sei() und/oder cli() nicht vergessen/vertauschen, oder 
sonstigen Mist damit versehentlich anstellen.

von Rolf (rolf22)


Lesenswert?

Johann L. schrieb:
> Dass der Code macht, was du erwartest, ist
> notwendig dafür, dass der Code korrekt ist, aber nicht hinreichend.

Das Verwechseln von "notwendig" und "hinreichend" erlebt man sehr oft 
bei Leuten, die mit "im Umkehrschluss ist also ..." argumentieren.

Allerdings stimmt oft mit der Erwartung etwas nicht, einfach deshalb, 
weil man oft aus Blindheit nicht sämtliche möglichen 
Bedingungen/Szenarien in die Erwartung einbezieht.

Im Extremfall lässt sich die Erwartung grundsätzlich nicht mit dem 
Ergebnis vergleichen, z. B. wenn ein Code den Wert von Wurzel(2) 
ziffernweise ausgeben soll, ohne jemals anzuhalten.  ;-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Daniel A. schrieb:
> Schade, das c11 atomics für AVR noch nicht implementiert sind.
> (Folgendes ist vermutlich keine gute Idee, war nur mal zum ausprobieren,
> was passiert: https://godbolt.org/z/hh8jbfscj)

Die 64-Bit und 32-Bit Versionen sind falsch.  Man kann einfach:
1
#include <stdint.h>
2
#include <util/atomic.h>
3
4
uint64_t __atomic_load_8 (const volatile void *x, int order)
5
{
6
    (void) order;
7
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
8
    {
9
        return *(const volatile uint64_t*) x;
10
    }
11
}
12
13
uint32_t __atomic_load_4 (const volatile void *x, int order)
14
{
15
    (void) order;
16
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
17
    {
18
        return *(const volatile uint32_t*) x;
19
    }
20
}

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Tatsächlich. Hab ich beim Copy & Paste total übersehen.

von Bruno V. (bruno_v)


Lesenswert?

Christoph M. schrieb:
> Korrekt ist das hier:
> Beitrag "Re: rollover save timer in c"
> ( Aufpassen mit kleiner und kleiner-gleich )

Ja, das ist identisch.
Erweitert um casts für Datentypen kleiner int (sonst geht das wegen 
"integral promotion" schief, hat für den TO aber keine Relevanz)
Und mit "auf der Stelle warten", was nie, bzw. nur für wenige µs genutzt 
werden sollte.

von Frank S. (basti2025)


Lesenswert?

Versuchs mal damit:
(Vereinfacht mit 16 Bit Variablen)


#include <avr/io.h>
#include <avr/interrupt.h>

#define CONFIG_OUTPUTS  { DDRB |= (1 << PB5); }
#define LED_GN_TOGGLE  { PORTB ^= (1 << PB5); }

volatile uint16_t millis = 0; // 16-Bit reicht für ~65 Sekunden

int main(void) {
    CONFIG_OUTPUTS;

    TCCR1B |= (1 << WGM12);
    TIMSK1 |= (1 << OCIE1A);
    OCR1A = 30; // 1 ms Intervall
    TCCR1B |= (1 << CS12);
    sei();

    const uint16_t eventInterval = 1000; // 1 Sekunde
    uint16_t previousTime = 0;

    while(1) {
        uint16_t currentTime = millis; // 16-Bit Lesen ist atomar

        if(currentTime - previousTime >= eventInterval) {
            LED_GN_TOGGLE;
            previousTime = currentTime;
        }
    }
}

ISR(TIMER1_COMPA_vect) {
    millis++;
}

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Frank S. schrieb:
> mit 16 Bit Variablen

Auch 16 Bit Variablen müssen Atomar gelesen werden.

Frank S. schrieb:
> OCR1A = 30; // 1 ms Intervall
> TCCR1B |= (1 << CS12);



OCR1A   = F_CPU/1000 -1; // 1 ms Intervall
TCCR1B |= (1 << CS10);

von Frank S. (basti2025)


Lesenswert?

Arduino F. schrieb:
> Frank S. schrieb:
>> mit 16 Bit Variablen
>
> Auch 16 Bit Variablen müssen Atomar gelesen werden.
>
Ich denke, hier geht es um einen AVR !? Beim AVR ist das Lesen von 16 
Bit Variablen stets atomar! Deshalb der vereinfachte Vorschlag von 16 
Bit Variablen wenn es nur um relativ kurze Intervalle ghet wie hier beim 
Blinken der LED (1- Minute-Intervall sollte ausreichend sein)

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Frank S. schrieb:
> Beim AVR ist das Lesen von 16
> Bit Variablen stets atomar!

Kannst du dafür einen glaubhaften Beleg bringen?

Bisher dachte ich immer, dass die kleinen AVR alle 8 Bit Prozessoren 
sind....

von Cyblord -. (cyblord)


Lesenswert?

Das Lesen von bestimmten 16 Bit Registern ist atomar. Bzw. wird ein 
Registerpaar beim lesen des 1. Byte gelockt damit sicher das zugehörige 
2. Byte gelesen werden kann.

Für normale 16 Bit Variablen gilt das natürlich nicht.

: Bearbeitet durch User
von Frank S. (basti2025)


Lesenswert?

Arduino F. schrieb:
> Frank S. schrieb:
>> Beim AVR ist das Lesen von 16
>> Bit Variablen stets atomar!
>
> Kannst du dafür einen glaubhaften Beleg bringen?
>
> Bisher dachte ich immer, dass die kleinen AVR alle 8 Bit Prozessoren
> sind....
 Bei der alten AVR Architektur hast du recht, da müssen 16Bit auch 
atomar gelesen werden, mit der neuen Architektur (ab 2020?) meines 
Wissens nicht mehr... ist mein letzter Stand

von Bruno V. (bruno_v)


Lesenswert?

Frank S. schrieb:
> Deshalb der vereinfachte Vorschlag von 16
> Bit Variablen wenn es nur um relativ kurze Intervalle ghet wie hier beim
> Blinken der LED (1- Minute-Intervall sollte ausreichend sein)

wichtig ist nur, dass int auch 16 bit ist. Sonst fehlt da ein Schritt. 
Entweder ein Cast oder die Differenz braucht ein eigenes Statement.

Also
1
    if(((uint16)(currentTime - previousTime)) >= eventInterval) {
oder
1
    uint16 diff = currentTime - previousTime;
2
    
3
         if(diff >= eventInterval){

von Nemopuk (nemopuk)


Lesenswert?

Cyblord -. schrieb:
> Das Lesen von bestimmten 16 Bit Registern ist atomar. Bzw. wird ein
> Registerpaar beim lesen des 1. Byte gelockt damit sicher das zugehörige
> 2. Byte gelesen werden kann.

Ich glaube, das gilt allerdings nur für 16 Bit Register, nicht 16 Bit 
Variablen (im RAM).

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Cyblord -. schrieb:
> Das Lesen von bestimmten 16 Bit Registern ist atomar. Bzw. wird ein
> Registerpaar beim lesen des 1. Byte gelockt damit sicher das zugehörige
> 2. Byte gelesen werden kann.

Bei Xmega werden beim Lesen / Schreiben des 1. Bytes eines 16-Bits SFRs 
wie SP die IRQs für einige Takte deaktiviert, so dass der Zugriff 
atomar ist.  Dazu muss eine bestimmte Reihenfolge eingehalten 
werden. Aus den avr-gcc Quellen:
1
         | Xmega           |  non-Xmega
2
   ------+-----------------+----------------
3
   load  | read LSB first  | read LSB first
4
   store | write LSB first | write MSB first
Diese Reihenfolge muss nur bei volatile Zugriffen eingehalten werden und 
ist der Grund, warum SP-Zugriffe im folgenden Code keine IRQ-Sperren 
brauchen:
1
void fun (int*);
2
3
void bar (void)
4
{
5
    int x[20];
6
    fun (x);
7
}
1
bar:
2
  push r28
3
  push r29
4
  in r28,__SP_L__
5
  in r29,__SP_H__
6
  sbiw r28,40
7
  out __SP_L__,r28
8
  out __SP_H__,r29
9
/* prologue: function */
10
  movw r24,r28
11
  adiw r24,1
12
  call fun
13
/* epilogue start */
14
  adiw r28,40
15
  out __SP_L__,r28
16
  out __SP_H__,r29
17
  pop r29
18
  pop r28
19
  ret


> Für normale 16 Bit Variablen gilt das natürlich nicht.

Korrekt.

von Veit D. (devil-elec)


Lesenswert?

Johann L. schrieb:
> Veit D. schrieb:
>> Habe ich einen Test gemacht. :-)
>
> Komplett belanglos.  Dass der Code macht, was du erwartest, ist
> notwendig dafür, dass der Code korrekt ist, aber nicht hinreichend.

Wie testet man sowas richtig?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Veit D. schrieb:
> Wie testet man sowas richtig?

Die Statistik gibt dir und mir recht, zumindest in meiner kleinen 
Arduino AVR Welt habe ich eine Trefferrate von 100%.
Also ein voller Erfolg beim volatile Verzicht.

Wie oben schon angemeckert wurde, ist das alles nur Zufall.
Diese Behauptung ist ohne Nachweis geblieben. (heiße Luft)
Natürlich kann ein Zufallsgenerator 10 oder 100 mal den gleichen Wert 
nacheinander liefern.
Aber sonderlich wahrscheinlich ist das nicht.

Dann wurde an anderer Stelle nach einer Doku gefragt, die die in Frage 
stehende  Funktionalität belegt. Eine solche habe ich verlinkt.
Diese wurde sofort pauschal als ungenügend/falsch abgeurteilt.
Wieder ohne Beleg. (auch wieder heiße Luft)

Veit D. schrieb:
> Wie testet man sowas richtig?
Testen heißt: Ausprobieren.
So viele verschiedene Varianten, wie dir einfallen.

Aber die Sprachrohre, hier, die wirst du nimmer überzeugen.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

solche Kurzantworten "ist falsch", "funktioniert so nicht" o,ä. stören 
mich auch. Ich hätte dann schon eine nähere Erklärung die zum Nachdenken 
anregt bzw. worüber man sich unterhalten kann.
Allgemein bin ich immer noch verdutzt darüber, dass dieser Thread der 
Erste seiner Art ist, der alles bisher im Forum gesagte in Frage stellt. 
Mich wundert das. Man hätte schon früher gegen Wilhelm u.a. 
argumentieren können. Hat man nie getan. Irgendwas stimmt nicht. Ich 
komme mir vor wie im falschen Film. Ich weiß nur nicht welche Filme 
falsch waren/sind.

Ich werde den Test noch ausbauen, ggf. über Stunden, Tage, Wochen.
Man hört sich. Ich halte mich erstmal zurück.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Veit D. schrieb:

> solche Kurzantworten "ist falsch", "funktioniert so nicht" o,ä. stören
> mich auch. Ich hätte dann schon eine nähere Erklärung die zum Nachdenken
> anregt bzw. worüber man sich unterhalten kann.

Das ist doch ganz simpel: Das mögliche Problem bei der Abwesenheit von 
volatile besteht darin, dass der Compiler Maschinencode schreibt, der 
die RAM-Inhalte in (MCU-)Registern cached und mit diesen Kopien 
arbeitet, obwohl sich die Inhalte im RAM mittlerweile geändert haben.

Das Problem ist: der Compiler cached nur unter bestimmten Bedingungen. 
Also muss die erste Maßnahme sein, ihn dazu zu bringen, etwas falsch zu 
machen, weil er es nicht besser weiß.

Also auf jeden Fall erst mal die Optimierung auf maximale 
Geschwindigkeit trimmen, also mit O2 oder O3 übersetzen.

Außerdem einen Code verwenden, der den Compiler geradezu dazu einlädt, 
seine Optimierungen anzuwenden. Also z.B. sowas in der mainloop 
aufrufen:

void test(void) {
 while (millies() == millies()) {
  ;
 }
}

Wenn er daraus in Asm eine Endlosschleife baut, dann hat das Problem 
zugeschlagen. Wenn nicht, wird irgendwo im Code von millies() dafür 
gesorgt, dass es eben nicht zuschlägt.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Ob S. schrieb:
> Das mögliche Problem bei der Abwesenheit von
> volatile besteht darin, dass der Compiler Maschinencode schreibt, der
> die RAM-Inhalte in (MCU-)Registern cached und mit diesen Kopien
> arbeitet, obwohl sich die Inhalte im RAM mittlerweile geändert haben.

Dagegen gibts die Memory Barriers.
Die erledigen das, auch wenn das hier nicht jedem schmeckt.

von Oliver S. (oliverso)


Lesenswert?

Arduino F. schrieb:
> Dagegen gibts die Memory Barriers.
> Die erledigen das, auch wenn das hier nicht jedem schmeckt.

Das stellt ja auch niemand in Frage, wenns um aktuelles C++ geht.
Das wurde ja alles auch hier lang und breit vor längerer Zeit 
diskutiert, als der C++ Standard volatile ganz rauswerfen wollte. Auf 
modernen CPU-Architekturen mit Multi core, Pipelining, Cachlines  etc, 
ist volatile da völlig fehl am Platz.

Was in Frage gestellt wird, ist, ob diese memory barriers in C auf einem 
AVR mit einer avrlibc vollumfänglich implementiert sind, und dazu auch 
noch, warum die dort nachweislich funktionierende klassische Lösung 
mit volatile nicht weiterhin funktioniert, wenn man den automic-Zugriff 
beachtet oder wie bei einem uint8_t damit kein Problem hat.

Oliver

Beitrag #7941379 wurde vom Autor gelöscht.
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ob S. schrieb:
> Wenn nicht, wird irgendwo im Code von millies() dafür
> gesorgt, dass es eben nicht zuschlägt.

Oder es ist Zufall :-)

Deadly Sin #6: To equate the unlikely with the impossible.

Mit einem Testfall kann man die Korrektheit eines Codes wiederlegen 
(wenn er schiefgeht), aber nicht beweisen.

Du könntest deine millis() Funktion also so schreiben:
1
uint32_t millis (void)
2
{
3
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
4
    {
5
       return milli;
6
    }
7
}

Falls man das allgemeiner halten will, würde man eine Funktion machen, 
die einen uint32_t liest:
1
static inline uint32_t
2
atomic_read_u32 (const uint32_t *p)
3
{
4
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
5
    {
6
       return *p;
7
    }
8
}

Damit das keine Diagnostic wirft falls man es mit einer volatlie 
Variablen verwendet, würde es also so implementieren:
1
static inline uint32_t
2
atomic_read_u32 (const volatile uint32_t *p)
3
{
4
    ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
5
    {
6
       return *p;
7
    }
8
}

und hätte doch wieder ein volatile :-)

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Was in Frage gestellt wird, ist, ob diese memory barriers in C auf einem
> AVR mit einer avrlibc vollumfänglich implementiert sind,

Ist abhängig von der Version der LibC.  Für den konkreten Code siehe 
https://avrdudes.github.io/avr-libc/ Für älteren Code musst nach 
Savannah gehen oder entspreachende GIT Tags direkt ansteuern.

> warum die dort nachweislich funktionierende klassische Lösung
> mit volatile nicht weiterhin funktioniert, wenn man den
> automic-Zugriff beachtet

Wo wurde denn gesagt dass das nicht (mehr) funktionieren soll?

Es wurde nur ein riesen Bohei darum gemacht, dass wenn man volatile 
Variablen ungeschickt anwendet, es etwas Overhead gibt.  Und dieses 
absolute Drama lässt sich mit Memory Barriers umgehen.

Dazu muss am Ende und am Anfang von ATOMIC_BLOCK eine Memory Barrier 
gesetzt werden.  Und eine volatile Barrier, weil man nicht will, dass 
ein volatile Asm in den Block oder aus dem Block verschoben werden darf.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

a) volatile sollte nie komplett abgeschafft werden. Da wurde etwas 
falsch verstanden.

b) Die avrLibC wird für C und C++ verwendet. Die avrLibCpp hat nicht 
jeder und steht nicht Standardmäßig zur Verfügung. Also reden wir nur 
von avrLibC.

c) Es geht darum, eine beliebige globale Variable muss nicht volatile 
deklariert sein, nur weil sie bspw. in einer ISR  verändert wird. Der 
Mythos das sie ohne volatile Deklaration wegoptimiert wird ist falsch.

d) Das geschriebene von hier 
Beitrag "Re: Versuch einer Millis Funktion für AVR" wurde in 
Beitrag "Re: Versuch einer Millis Funktion für AVR" umgesetzt. Wo ist 
der Widerspruch?

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Veit D. schrieb:

> c) Es geht darum, eine beliebige globale Variable muss nicht volatile
> deklariert sein, nur weil sie bspw. in einer ISR  verändert wird. Der
> Mythos das sie ohne volatile Deklaration wegoptimiert wird ist falsch.

Nein, dass kann leicht passieren. Das Problem ist, dass Compiler sehr 
lokal optimieren.

Wenn in der Hauptschleife überall nur lesend auf die Variable 
zugegriffen wird, kommt der Compiler hier zu dem Schluß, dass jeglicher 
Zugriff darauf nutzlos ist. Statt dessen wird der initiale Inhalt der 
Variablen als Konstante benutzt.

Wenn in der ISR nur schreibend auf die Variable zugegriffen wird, dann 
läuft das Spiel umgekehrt: der Compiler stellt fest, dass da völlig 
sinnlos am Inhalt der Variablen rumgemurkelt wird, da sie nie gelesen 
wird und somit ihr Inhalt völlig unwichtig sein muss. Also werden 
konsequenterweise hier diese Schreibzugriffe wegoptimiert.

Noch existiert die Variable aber in irgendeinem Objectfile. Den 
Todesstoß bekommt sie dann bei der LTO (link time optimization). Da 
stößt der Linker auf den Sachverhalt, dass da eine Variable existiert, 
auf die von nirgendwo aus zugegriffen wird. Was macht der jetzt wohl mit 
dieser Variablen, drei Mal darfst du raten...

Im konkreten Fall, einer Implementierung von Millis, wird aber in der 
ISR nicht nur geschrieben, sondern auch gelesen. Increment für mehr als 
8Bit geht nicht anders beim AVR. Sprich: Die ISR-Zugriffe bleiben 
erhalten. Dafür genügt die Tatsache, dass die Variable statisch ist, 
also zwischen Aufrufen der ISR erhalten bleiben muss. Das wiederum 
bewirkt, dass auch die LTO nicht greift. Dieser Seite der Sache 
funktioniert also auch ohne volatile.

Nützt nur nix, weil in main an keiner Stelle von dem hochlaufenden 
Zähler Notiz genommen wird...

Der Kernpunkt ist also nur, dass dem Compiler irgendwie verklickert 
werden muss, dass die Variable außerhalb seines jeweiligen Scopes eine 
Bedeutung besitzt.

Das Mittel dazu ist entweder, sie als volatile zu deklarieren oder halt 
mit memory barriers.

Theoretisch ist es möglich, dass im Vergleich die memory barriers eine 
höhere Performance des generierten Codes ermöglichen, weil an der einen 
oder anderen Stelle vielleicht doch ein Caching möglich bleibt, die 
Variable also nicht bei wirklich jedem Zugriff aus dem Speicher in die 
Arbeitsregister gelesen werden muss, sondern zumindest gelegentlich eben 
auch die Kopie ausreicht, die bereits in den Arbeitregistern ist. Das 
ist eigentlich alles.

Ich wage zu behaupten, dass der Vorteil bei der einfachen Architektur 
der AVRs minimal bis überhaupt nicht vorhanden ist.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ob S. schrieb:
> Nein, dass kann leicht passieren. Das Problem ist, dass Compiler sehr
> lokal optimieren.

Da schießt deine Vorstellund davon, was ein Compiler optimiert, weit 
über das hinaus, was ein Compiler wirklich macht bzw. überhaupt machen 
darf.

> Wenn in der Hauptschleife überall nur lesend auf die Variable
> zugegriffen wird, kommt der Compiler hier zu dem Schluß, dass jeglicher
> Zugriff darauf nutzlos ist. Statt dessen wird der initiale Inhalt der
> Variablen als Konstante benutzt.

Das würde LTO voraussetzen und dass die Variable nie geschrieben wird. 
Ist aber nicht der Fall, weil sie (zumindest) in einer ISR geschrieben 
wird.

Das Lesen der Variablen in der Haupschleife kann nur dann gegen andere 
Lesezugriffe auf diese Variable optimiert werden, wenn der Compiler den 
Wert kennt (bzw. dass er sich seit einem anderen Zugriff nicht geändert 
hat).  Diese Kenntnis wird dem Compiler aber durch die Memory Barriers 
(eingeführt per sei(), cli() oder ATOMIC_BLOCK) genommen.

Daher muss der Compiler jedes Mal lesen — zumindest wenn der gelesene 
Wert verwendet wird. Wenn der gelesene Wert nicht verwendet wird, kann 
der Compiler den Lesezugriff tatsächlich weglasssen (falls nicht 
volatile).  Ist dann aber kein Problem, ausser dass man einen nutzlosen 
ATOMIC_BLOCK hat — was aber kein wirklichen Problem darstellt.

> Wenn in der ISR nur schreibend auf die Variable zugegriffen wird, dann
> läuft das Spiel umgekehrt: der Compiler stellt fest, dass da völlig
> sinnlos am Inhalt der Variablen rumgemurkelt wird, da sie nie gelesen
> wird und somit ihr Inhalt völlig unwichtig sein muss. Also werden
> konsequenterweise hier diese Schreibzugriffe wegoptimiert.

Wenn die Variable nirgends gelesen wird, dann wäre das kein Problem. 
Die Variable wird aber gelesen — zumindest wenn main Lesezugriffe hat, 
die nicht tot oder ungenutzt sind.

> Noch existiert die Variable aber in irgendeinem Objectfile. Den
> Todesstoß bekommt sie dann bei der LTO (link time optimization). Da
> stößt der Linker auf den Sachverhalt, dass da eine Variable existiert,
> auf die von nirgendwo aus zugegriffen wird. Was macht der jetzt wohl mit
> dieser Variablen, drei Mal darfst du raten...

Komplett falsch abgebogen.

1) LTO wird nicht vom Linker durchgeführt, sondern vom Compiler.  Und 
zwar zur Linkzeit.  Daher der Name.  Der Linker macht nix anderes als 
den LTO-Compiler auszurufen.  Bei GCC also lto1.

2) Zu dem Szenario kommt es wie oben beschrieben erst garnicht.  Es sei 
denn main liest die Variable (effektiv) nicht.

> Nützt nur nix, weil in main an keiner Stelle von dem hochlaufenden
> Zähler Notiz genommen wird...

Wie gesagt, irgendwo dreht dein Brain v0.9b Compiler durch.  Nicht das, 
was ein realer C/C++ Compiler macht :-)

Beitrag #7941626 wurde vom Autor gelöscht.
von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich danke euch beiden. Dieses Wissen regt zum Nachdenken an.

von Harald K. (kirnbichler)


Lesenswert?

Volatile reicht nicht, wenn der Datentyp, um den es geht, größer als 1 
Byte ist. Nicht auf einem AVR, und um den geht es hier ja wohl doch.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Harald K. schrieb:
> Volatile /reicht nicht/

Hat auch keiner behauptet.

Die in diesem Thread duskutierten Alternativen sind:

A) volatile + atomar

B) Memory-Barrier + atomar

: Bearbeitet durch User
von Rick (rick)


Lesenswert?

Und ich habe schon gedacht, ich muß meinen ganzen alten Code 
wegschmeißen.
Da ich in der ISR fast immer nur ein Flag (Typ uint8_t) setze, bin ich 
ja mit volatile bestens bedient...

von Daniel A. (daniel-a)


Lesenswert?

Johann L. schrieb:
> Die in diesem Thread duskutierten Alternativen sind:
>
> A) volatile + atomar
>
> B) Memory-Barrier + atomar

C) Einfach nur _Atomic.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Daniel A. schrieb:
> Johann L. schrieb:
>> Die in diesem Thread diskutierten Alternativen sind:
>>
>> A) volatile + atomar
>>
>> B) Memory-Barrier + atomar
>
> C) Einfach nur _Atomic.

Ist das bei avr-gcc implementiert? Um den geht es hier nämlich.  Zum 
Beispiel wird die Signatur von __atomic_load_4 etc erkannt liefert aber 
undefined reference vom Linker.  Eben weil avr-gcc das nicht 
implementiert.

: Bearbeitet durch User
von Daniel A. (daniel-a)


Lesenswert?

Einfach mal oben nachlesen.

Edit: Oh, das warst ja sogar du, der mir darauf geantwortet hatte. Schon 
wieder vergessen?

: Bearbeitet durch User
von Bruno V. (bruno_v)


Lesenswert?

Harald K. schrieb:
> Volatile reicht nicht

Volatile ist halt das einzig verfügbare C-Sprachmittel. Ohne 
Plattform-/Compilerspezifische Methoden kann man das Auslesen mit 
volatile und ein wenig Aufwand oft C-konform gestalten.

Z.B. beim tick, der in der ISR erhöht wird: 3 Mal nacheinander auslesen 
und 2 benachbarte gleiche nehmen oder welche die genügend eng zusammen 
sind.

(Wobei es wenige Plattform/Compiler-Kombinationen geben dürfte, wo eine 
memory barrier notwendig und nicht verfügbar ist)

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Bruno V. schrieb:
> Z.B. beim tick, der in der ISR erhöht wird: 3 Mal nacheinander auslesen
> und 2 benachbarte gleiche nehmen oder welche die genügend eng zusammen
> sind.

Wow!
Sowas habe ich ja noch nie gesehen/gehört.
Das soll dein Ersatz sein?
Für was? Für eine Interrupt Sperre?

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Arduino F. schrieb:
> Wow!
> Sowas habe ich ja noch nie gesehen/gehört.
> Das soll dein Ersatz sein?
> Für was? Für eine Interrupt Sperre?

Da gab's eine Menge Systeme die zB. bei
1
SEI
2
LDA <zp>
3
LDY <zp>
4
CLI
für 10µs Interrupts still legten. Beim Zugriff auf die anderen 255 Pages 
sogar für 12µs. Das konnte/kann in vielen Fällen eine verdammt lange 
Zeit sein und man konnte dies einfach durch diese Methode verhindern.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Norbert schrieb:
> Da gab's eine Menge Systeme

Wir sind hier bei AVR!

von Norbert (der_norbert)


Lesenswert?

Arduino F. schrieb:
> Norbert schrieb:
>> Da gab's eine Menge Systeme
>
> Wir sind hier bei AVR!

Das stimmt. Beim AVR kann man natürlich die IRQs abschalten ohne dass 
sie abgeschaltet sind. Magic everywhere.

Im Übrigen sah dein Einwand doch eher generell und nicht speziell aus.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Norbert schrieb:
> Beim AVR kann man natürlich die IRQs abschalten ohne dass
> sie abgeschaltet sind.

Jetzt wirds aber besonders logisch!
Oder?

Soweit mir bekannt, ist ein Interrupt eine Unterbrechung des 
Hauptprogramms, um zwischendurch was anderes zu erledigen.
Diese Unterbrechung wird von cli() wirksam unterbunden.
Da kann die Hardware zucken wie sie will.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Norbert schrieb:
>> Beim AVR kann man natürlich die IRQs abschalten ohne dass
>> sie abgeschaltet sind.
>
> Jetzt wirds aber besonders logisch!
> Oder?

Man könnte temporär die IRQs deaktivieren, die auf der / den fraglichen 
Variablen rumnudeln, anstatt per CLI Interrupts global zu deaktivieren.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Man könnte temporär die IRQs deaktivieren, die auf der / den fraglichen
> Variablen rumnudeln, anstatt per CLI Interrupts global zu deaktivieren.

Was allerdings dazu führen würde, dass man u.U. Interrupts verliert.
Mir ist klar, dass man das tun kann, aber ob das Sinn macht?

von Norbert (der_norbert)


Lesenswert?

Arduino F. schrieb:
> Jetzt wirds aber besonders logisch!
> Oder?

Möglicherweise würdest du erkennen was gemeint war wenn du korrekt 
zitiert hättest:

> Beim AVR kann man natürlich die IRQs abschalten
> ohne dass sie abgeschaltet sind. Magic everywhere.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Johann L. schrieb:
>> Man könnte temporär die IRQs deaktivieren, die auf der / den fraglichen
>> Variablen rumnudeln, anstatt per CLI Interrupts global zu deaktivieren.
>
> Was allerdings dazu führen würde, dass man u.U. Interrupts verliert.

Das Problem hat man mit globaler Interruptsperre aber auch, nämlich wenn 
während der Interuptspette die IRQ mindestens 2× triggert. Ein einzelner 
Trigger ist kein Problem, weil das entsprechende IRQ-Flag gesetzt wird, 
das den Interrupt nach Beendung der Sperre auslöst — es sei denn es ist 
eine Level-getriggerte IRQ.  Aber dann hast du das gleiche Problem mit 
globale IRQ-Sperre.

> Mir ist klar, dass man das tun kann, aber ob das Sinn macht?

Ich hab's noch nie gebraucht; IRQ-Sperren sind ja nur kurz.  Und wenn 
sie lange sind und man zB ein printf atomar macht, dann ist das das 
Problem.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> IRQ-Sperren sind ja nur kurz.
Ja, es macht Sinn diese kurz zu halten.

Johann L. schrieb:
> zB ein printf atomar macht,
Das scheint mir nicht sonderlich klug zu sein.

Vergleichbar mit dem Arduino Serial.print() in einer ISR. Das geht 
solange gut bis die FIFO voll ist, danach blockiert es.
Ist also auch keine kluge Idee.

An dummen Ideen gibt es keinen Mangel. Ist für jeden was dabei.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> An dummen Ideen gibt es keinen Mangel. Ist für jeden was dabei.

Einzelne IRQs kurzzeitig zu deaktiviern gehört aber nicht dazu.  Das 
kann durchaus praktikabel sein.

In den meisten Fällen ist eine kurze, globale Interruptsperre gut genug, 
und man macht sich nicht die Mühe, das nur mit der jeweiligen IRQ zu 
machen.

Das macht letzteres aber nicht zu einer dummen Idee.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Johann L. schrieb:
> Das macht letzteres aber nicht zu einer dummen Idee.
Habe ich auch nicht gesagt!

Aber mit dem printf....

Es gibt ja auch noch eine dritte Möglichkeit, die am Rande dessen wohnt.
Wiedereintrittsfähige ISR.

Bzw. die Kommunikation über ein Postfach mit der Außenwelt.
Ist aufwändig, aber kommt ganz ohne Interruptsperren aus, braucht nur 
Memory Barrieres.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduino F. schrieb:
> Es gibt ja auch noch eine dritte Möglichkeit, die am Rande dessen wohnt.
> Wiedereintrittsfähige ISR.

Also dürfen ISR keinen globalen Status haben, konnen also keine globalen 
Variablen verwenden sondern müssen auf Features wie TLS (Thread Local 
Storage) zurückgreifen.  Steht auf AVR nicht zur Verfügung, und würde 
zudem ein dazu passendes Betriebssystem erfordern.

: Bearbeitet durch User
von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Johann L. schrieb:

> Arduino F. schrieb:
>> Es gibt ja auch noch eine dritte Möglichkeit, die am Rande dessen wohnt.
>> Wiedereintrittsfähige ISR.
>
> Also dürfen ISR keinen globalen Status haben, konnen also keine globalen
> Variablen verwenden sondern müssen auf Features wie TLS (Thread Local
> Storage) zurückgreifen.  Steht auf AVR nicht zur Verfügung, und würde
> zudem ein dazu passendes Betriebssystem erfordern.

Ähem, ihr redet offensichtlich aneinander vorbei. Das, was Arduino F. 
meinte, hat zunächst rein garnix mit irgendwelchen Variablen zu tun. Es 
ist vielmehr nur die Fähigkeit einer ISR, durch eine weitere Instanz 
ihrer selbst unterbrochen zu werden (und das dann sinnvoll zu handeln).

Das ist einerseits durchaus möglich, aber andererseits auch die hohe 
Schule der Programmierung. Es läuft i.d.R. darauf hinaus, die ISR in 
zwei Teile zu zerlegen. Einen kurzen Teil zu Anfang, der wirklich 
exklusiv läuft. Und einen zweiten Teil, der unterbrechbar ist, also den 
Rest seiner Laufzeit als eine Art hochpriorisierter Task eines 
kooperativen MT-OS läuft. Das geht, OHNE wirklich ein MT-OS zu haben. 
Das ist der eigentliche Witz an der Sache.

Und es ist natürlich ein zweischneidiges Schwert. Einerseits zeigt es 
auf, das kooperative RTOS eigentlich völlig überflüssig sind. Man kann 
alles, was die leisten können, mit diesem simplen Schema ebenfalls 
erreichen. Und zwar sehr viel effizienter. Es zeigt andererseits aber 
auch auf, dass es sehr guter Programmierer bedarf, sowas hinreichend 
fehlerfrei umzusetzen. Insbesondere in Bezug auf das Problem der 
endlichen Resourcen bzgl. Speicher und Rechenzeit.

von Bruno V. (bruno_v)


Lesenswert?

Ob S. schrieb:
> Ähem, ihr redet offensichtlich aneinander vorbei.
Ich denke schon, dass beide da auf gleichem Kurs sind.

> Das, was Arduino F. meinte, hat zunächst rein garnix mit
> irgendwelchen Variablen zu tun.
Es ging hier explizit darum, Interruptsperren zu vermeiden. Da es 
andererseits um das Incrementieren von millis geht, beißt sich die Katze 
in den Schwanz. Millis ist die von Johann erwähnte globale Variable.

Einzelne Aufgaben eines RTOS mittels (priorisierter) Interrupts zu 
realisieren, ist eher ein Hack (für zu kleine Plattformen) als eine 
Alternative.

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Bruno V. schrieb:
> Ich denke schon, dass beide da auf gleichem Kurs sind.

Hmmm...
Der observer hat mich schon richtig verstanden.

von Bruno V. (bruno_v)


Lesenswert?

Arduino F. schrieb:
> Hmmm...
> Der observer hat mich schon richtig verstanden.

natürlich. Aber nicht Johanns Replik.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Bruno V. schrieb:
> Aber nicht Johanns Replik.

Seinen Beitrag habe ich so verstanden, dass er Wiedereintrittsfähige 
Interrupts auf AVR für unmöglich hält.

von Bruno V. (bruno_v)


Lesenswert?

Arduino F. schrieb:
> dass er Wiedereintrittsfähige
> Interrupts auf AVR für unmöglich hält.

seine Aussage war:

Johann L. schrieb:
> Also dürfen ISR keinen globalen Status haben,
> können also keine globalen Variablen verwenden

Für "unmöglich" auf dem AVR hält er TLS, wozu ich aber nix sagen kann.

Johann L. schrieb:
> sondern müssen auf Features wie TLS (Thread Local
> Storage) zurückgreifen.  Steht auf AVR nicht zur Verfügung, und würde
> zudem ein dazu passendes Betriebssystem erfordern.

Zu wiedereinrittsfähigen Interrupts auf dem AVR finde ich keine 
expliziten Aussagen in diesem Zusammenhang. Kann ich auch nix zu sagen. 
Also nicht AVR, sonst ist mir das schon klar.

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Bruno V. schrieb:
> Zu wiedereinrittsfähigen Interrupts auf dem AVR finde ich keine
> expliziten Aussagen in diesem Zusammenhang.

Die braucht man auch nicht.
Die Bedingungen sind recht klar.
z.B. Mit sti() und cli() kann man das "globale Interrupt enable Flag" 
manipulieren.
Irgendwo muss ein Merker gehalten werden, damit in der ISR festgestellt 
werden kann ob der Interrupt Request als Wiedereintritt erfolgt und 
natürlich muss entsprechend gehandelt werden.

Die Probleme sind schon genannt worden!
Erhöhte Sorgfaltspflicht.
Kein OS welches das unterstützt.
Wenig Speicher, so dass mehrere solcher Reentranten Interrupts schwierig 
werden, u.A. wegen dem Stackbedarf.

Es ist meines Erachtens nach ein Mittel welches nur im Notfall 
eingesetzt werden sollte und keineswegs zum "heiligen Gral" erhoben 
werden darf.
Zumindest nicht auf den AVR Zwergen.

Meist besser:
Weit oben wurde die Arduino übliche Methode mit der millis() Verwendung 
als kompliziert bezeichnet. Und dennoch liefert sie eine recht einfache 
Möglichkeit quasi Nebenläufigkeiten zu bauen, ohne kaskadierende 
Stackframes. Das Prinzip weiter ausgeführt, landet man bei den 
ProtoThreads.

von Bruno V. (bruno_v)


Lesenswert?

Das wird jetzt immer mehr off topic, sowohl zum TO als auch zur 
Interruptsperre. Und im Kreis drehen wir uns bei

Arduino F. schrieb:
> Irgendwo muss ein Merker gehalten werden

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.