Forum: Mikrocontroller und Digitale Elektronik UART Ring Buffer volatile?


von KingJulian (Gast)


Angehängte Dateien:

Lesenswert?

Ich hab mir das angehängte STM32 Beispiel zu UART Interrupt und Buffer 
heruntergeladen und hätte da eine Frage dazu...

Warum muss
1
static struct buf_st rbuf = { 0, 0, };
und
1
static struct buf_st tbuf = { 0, 0, };

nicht als volatile deklariert werden?

von Little B. (lil-b)


Lesenswert?

Weil sie sich nicht in Hardware ändern.

Dieses Beispiel wird über Interrupts getriggert. Somit hat die Software 
die volle Kontrolle über die Buffer. Bei einem DMA-Beispiel sieht das 
sicher anders aus.

Oder verbreite ich hier gefährliches Halbwissen?

von Jonas B. (jibi)


Lesenswert?

Weil man sicher stellen kann das die Zugriffe auf die Buffer atomar 
sind.

Gruß Jonas

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Little B. schrieb:
> Oder verbreite ich hier gefährliches Halbwissen?

Ja. Was in einer Interruptroutine geändert wird, läuft außerhalb der 
Kontrolle des Compilers und muss daher als volatile deklariert 
werden, wenn darauf auch von außerhalb der Interruptroutine zugegriffen 
werden soll.

von King J. (rappel_p)


Lesenswert?

Rufus Τ. F. schrieb:
> Little B. schrieb:
>> Oder verbreite ich hier gefährliches Halbwissen?
>
> Ja. Was in einer Interruptroutine geändert wird, läuft außerhalb der
> Kontrolle des Compilers und muss daher als volatile deklariert
> werden, wenn darauf auch von außerhalb der Interruptroutine zugegriffen
> werden soll.

Das wäre grundsätzlich auch mein Verständnis. Die Frage ist höchstens, 
ob es eine Rolle spielt, dass der Inhalt des buffers und der Input Index 
nur von der Interrupt Routine selbst geändert wird und beim lesen nur 
der output index geändert wird.

von Stefan F. (Gast)


Lesenswert?

Wenn schon, muss man nur die Zeiger, die Anfang und Ende des Ringpuffers 
markieren als volatile deklarieren.

Bei 8bit Controller verwende ich jedoch kleine Arrays mit maximal 255 
Bytes als Puffer und dann keine Zeiger auf Anfang und Ende, sondern 
indexe. Die beiden Indexe sind dann kleine 8bit Variablen. Und weil sie 
8bit sind, entfällt die Synchronisation beim Zugriff.

Beim Interrupt wird nur der Ende-Index verändert.
Beim lesen aus dem Empfangspuffer wird nur der Anfang-Index verändert.
So brauche ich nichtmal diese beiden Zugriffe zu synchronisieren. Sie 
dürfen ruhig "gleichzeitig" stattfinden.

von W.S. (Gast)


Lesenswert?

Little B. schrieb:
> Oder verbreite ich hier gefährliches Halbwissen?

Ja.

Also, wenn nur eine einzige Instanz auf eine Variable zugreift und auch 
keinerlei Hardware hineinspukt, dann ist volatile überflüssig. Bei 
Ringpuffern zum Beispiel gibt es genau 3 Dinge: den eigentlichen Puffer, 
den Schreibzeiger und den Lesezeiger. Den Puffer braucht man nie als 
volatile zu kennzeichnen. Bei den Schreib- und Lesezeigern hingegen ist 
das etwas kritischer. Wenn man sein Inteface so schreibt, daß es 
nichtblockierend ist, dann braucht man auch kein volatile. Wenn hingegen 
jemand blockierend schreibt, dann sehr wohl, weil er ja mit seinem 
Trampeln auf der Stelle seinen Dunstkreis nicht verläßt. Ich geb dir mal 
ein Beispiel:
1
/* ob empfangene Zeichen vorhanden sind */
2
bool V24RxAvail1 (void) { if (U1Buf.InRP != U1Buf.InWP) return 1;  return 0; }
3
4
/* empfangene Zeichen abholen */
5
char V24GetChar1 (void)
6
{ int i;
7
  char c;
8
  i = U1Buf.InRP;  if (i == U1Buf.InWP) return 0;
9
  c = U1Buf.InBuf[i];  U1Buf.InRP = (i+1) & (IBLEN-1);
10
  return c;
11
}
Hier siehst du, daß es bei nicht blockierender Schreibweise völlig egal 
ist, ob da was volatile ist oder nicht, denn V24RxAvail1 wird immer 
wieder aufgerufen und wartet niemals, und auch V24GetChar1 tut das 
nicht. Da beide Funktionen immer wieder beendet werden und nie warten, 
kann eine unbemerkte Änderung in U1Buf.InWP auch nicht passieren.

Klaro?

btw: der Beispielcode ist fragwürdig. Ich hätte da ein noch viel 
schlimmeres Wort für solch kruden Mist. Sowas schreiben nur Leute, die 
selber nie und nimmer jemals eine tatsächliche Anwendung geschrieben 
haben, die auf sowas aufbaut.

W.S.

von King J. (rappel_p)


Lesenswert?

Wäre in dem Fall sowas eine vetretbare Implementierung?
1
struct DataBuffer{
2
  char data[RXBUFFERSIZE];
3
  uint8_t rbHead;
4
  uint8_t rbTail;
5
};
6
7
/*
8
* Set the read and write position of the databuffer to 0
9
*/
10
void initDataBuffer(struct DataBuffer* pDataBuffer){
11
  pDataBuffer->rbHead = 0;
12
  pDataBuffer->rbTail = 0;
13
}
14
15
/*
16
* Interrupt Handler for Bluetooth USART communication
17
*/
18
void USART2_IRQHandler(void){
19
  struct DataBuffer *p;
20
  USARTInterruptTriggered = 1;
21
    
22
  if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == SET){
23
    p = &UARTrxBuffer;
24
    
25
    //Check for rbHead wrap around
26
    p->rbHead = (p->rbHead > RXBUFFERSIZE-1) ? 0 : p->rbHead++;
27
    //Check for buffer overflow
28
    if(p->rbHead == p->rbTail){
29
      //do something
30
      }
31
      
32
    p->data[p->rbHead] = USART_ReceiveData(USART2);
33
  }
34
35
}
36
37
/*
38
* Returns the char stored at the tail of the buffer
39
*/
40
char readUSART2Buffer(void){
41
  struct DataBuffer *p;
42
  if(p->rbTail == p->rbHead){
43
    return 0;
44
  } else {
45
    p->rbTail = (p->rbTail > RXBUFFERSIZE-1) ? 0 : p->rbTail;
46
    return p->data[p->rbTail++];
47
  }
48
}

und zum auslesen mache ich
1
if(getInterruptFlag()){
2
  clearInterruptFlag();
3
  buffer = readUSART2Buffer();
4
  while(buffer != 0){
5
    printf("%c", buffer);
6
    buffer = readUSART2Buffer();
7
  }
8
}

von Stefan F. (Gast)


Lesenswert?

Ich hab's nicht getestet, aber es seiht gut aus.

Nur diese ternären Ausdrücke (p->rbTail = (p->rbTail > RXBUFFERSIZE-1) ? 
0 : p->rbTail;) mag ich nicht. Ich finde, sie sind schwer lesbar. Aber 
fachlich natürlich in Ordnung.

readUSART2Buffer() liefert eine 0 zurück, wenn der Puffer leer ist aber 
auch wenn im Puffer eine 0 liegt. Dir fehlt eine Möglichkeit, diese 
beiden Fälle zu unterscheiden.

Vorschläge:

1) Ändere den Rückgabetyp von readUSART2Buffer() auf int. Dann kannst du 
im Fehlerfall -1 zurück geben.

2) Füge eine Funktion hinzu, mit der du abfragen kannst, ob der Puffer 
leer ist.

3) Wenn du schon dabei bist, kann es auch bei einigen Anwendungen sehr 
praktisch sein, zu wissen, ob im Puffer ein Zeilenumbruch liegt.

Und dann befasse dich mal mit der Konsole (stdin und stdout). Ich nehme 
an, dass die C Library für STM32 das ebenso kann, wie die für AVR. Wenn 
du diese beiden Kanäle mit dem seriellen Port verbindest, kannst du 
sämtliche Funktionen der stdio.h und printf() benutzen.

von King J. (rappel_p)


Lesenswert?

Dieser Buffer wird von einem Bluetooth modul mit commands gefüttert, 
eine 0 sollte deshalb eigentlich nicht im Buffer stehen - sonst ist dein 
Einwand natürlich absolut gerechtfertigt.

von DerFreud (Gast)


Lesenswert?

W.S. schrieb:
> Sowas schreiben nur Leute, die
> selber nie und nimmer jemals eine tatsächliche Anwendung geschrieben
> haben, die auf sowas aufbaut.

mit solchen Sätze ist das µC.net in Verruf gekommen.

W.S: Warum greifst du Leute, die vielleicht nicht so viel wissen wie du 
so gemein an? Würdest du das mit deinen Freunden oder Frau und deinen 
Kindern auch so machen?
Wie würdest du auf so einen Satz antworten?

von Marco H. (damarco)


Lesenswert?

Ist wird wohl sich nicht übersetzen lassen ;)
1
struct DataBuffer{
2
  char data[RXBUFFERSIZE];
3
  uint8_t rbHead;
4
  uint8_t rbTail;
5
};


Da in der Struktur keine Zeiger sind.

Außerdem
1
int *p=0  ist nicht gleich int *P=NULL und if(!0); nicht if(!NULL);
1
NULL
 ist wirklich nichts :)

Solche Dinge löst man mit einer "linked List" . Die Liste zeigt immer 
auf die nächste Struktur , wie ein Domino rennt das ganze durch bis das 
Ende erreicht ist. Das Ende ist markiert durch einen Null Pointer.  Wenn 
das Ende zum Anfang zeigt dreht sich das ganze im Kreis.

Hier die nötige Struktur die auf sich selber zeigt.
1
typedef struct node {
2
void *data;
3
struct node *next;
4
}Node_t;

Nun die eigentliche Liste
1
typedef struct linkedList {
2
3
Node_t *head;
4
Node_t *tail;
5
Node_t *current;
6
7
}LinkedList_t;

Nun müssen wir sie Initialisieren
1
void initList (LinkedList_t *list) {
2
3
list->head = NULL;
4
list->tail = NULL;
5
list->current = NULL;
6
7
}


Nun benötigt man weitere Funktionen wie z.Bsp addHead(LinkedList_t 
*list, void *data); addTail(LinkedList_t *list, void *data); 
delete(LinkedList_t *list, void *data);

in diesen Funktionen werden die Pointer nur umcopiert.


Das Auslesen geht ungefähr so
1
Node_t *current = list->head;
2
3
while(current !=NULL) {
4
5
readData(current->Data);
6
current = current->next;
7
8
}

Einmal angestoßen rennt das ganze solange bis NULL also "tail" erreicht 
ist. Oder es hört die auf wenn es kein Ende gibt ;)

Im Netz gibt es Beispiele dieser Art...

Wer jetzt meint das wäre ja Verschwendung, der Data Pointer kann auch 
auf ein Array etc. zeigen. Wenn die Länge des Telegramms bekannt ist 
könnte jedes Commando in solch eine Struktur. Durch die Liste werden sie 
dann abgearbeitet.

: Bearbeitet durch User
von Bernd K. (prof7bit)


Lesenswert?

King J. schrieb:
> /*
> * Returns the char stored at the tail of the buffer
> */
> char readUSART2Buffer(void){
>   struct DataBuffer *p;
>   if(p->rbTail == p->rbHead){
>     return 0;
>   } else {
>     p->rbTail = (p->rbTail > RXBUFFERSIZE-1) ? 0 : p->rbTail;
>     return p->data[p->rbTail++];
>   }
> }

Der Zeiger ist nicht initialisiert. Wo liegt das DataBuffer struct 
überhaupt, den Teil hast Du vergessen. Und außerdem: Ich sehe kein 
einziges volatile. Das ganze struct MUSS volatile sein, denn sowohl die 
Indices als auch der Buffer selbst werden sowohl vom Interrupt als auch 
von main() verwendet.

von Bernd K. (prof7bit)


Lesenswert?

Marco H. schrieb:
> Node_t *current = list->head;

Thema verfehlt. Bitte nochmal von vorne lesen um was es hier überhaupt 
geht.

von Bernd K. (prof7bit)


Lesenswert?

In diesem Zusammenhang:

Ich habe beobachtet daß beim gcc auch das Einziehen einer 
Speicherbarriere vor dem Lesen einer nicht volatile Variablen beim gcc 
scheinbar ein Lesen der Variable erzwingt (welches ohne die Barriere 
herausoptimiert worden wäre).
1
asm volatile("" ::: "memory");

ABER ist das tatsächlich ein garantierter Weg das zu erreichen oder 
ist es nur Zufall daß der gcc sich so verhält? Oder anders gefragt: 
Fällt eine (jede!) nicht volatile globale variable tatsächlich offiziell 
unter die Kategorie "memory"? Steht das irgendwo geschrieben? Wenn dem 
nämlich so wäre könnte man damit einiges vereinfachen, einen ganzen 
Stall von volatile einsparen (so ziemlich alle) und temporären lokalen 
Variablen um alle unnötigen volatile Zugriffe zu verhindern bräuchte man 
auch nicht mehr. Dann würde es reichen an beiden Enden jeweils eine 
Barriere zu errichten und fertig.

von Marco H. (damarco)


Lesenswert?

Um einen Ringbuffer. Ich wollte nur zeigen das man die Aufgabe auch 
anders lösen kann.

von King J. (rappel_p)


Lesenswert?

Also, wenn ich das struct als volatile deklariere dann läuft das 
Ganze...

von Rolf Magnus (Gast)


Lesenswert?

Jonas B. schrieb:
> Weil man sicher stellen kann das die Zugriffe auf die Buffer atomar
> sind.

volatile stellt nur sicher, dass alle Zugriffe auch tatsächlich 
ausgeführt werden, nicht, dass dies atomar geschieht. volatile hat mit 
Atomar...izität nichts zu tun.

Stefan U. schrieb:
> Bei 8bit Controller verwende ich jedoch kleine Arrays mit maximal 255
> Bytes als Puffer und dann keine Zeiger auf Anfang und Ende, sondern
> indexe. Die beiden Indexe sind dann kleine 8bit Variablen. Und weil sie
> 8bit sind, entfällt die Synchronisation beim Zugriff.

Gleiches Thema wie oben: volatile ist kein Synchronisationsmechanismus.

> Beim Interrupt wird nur der Ende-Index verändert.
> Beim lesen aus dem Empfangspuffer wird nur der Anfang-Index verändert.
> So brauche ich nichtmal diese beiden Zugriffe zu synchronisieren. Sie
> dürfen ruhig "gleichzeitig" stattfinden.

Wie stellst du denn sicher, dass dein Ringpuffer nicht "unterläuft", 
bzw. erkennst, ob er überläuft? Ich würde erwarten, dass irgendwo auch 
ein Vergleich beider Indizes stattfindet.

W.S. schrieb:
> Also, wenn nur eine einzige Instanz auf eine Variable zugreift und auch
> keinerlei Hardware hineinspukt, dann ist volatile überflüssig. Bei
> Ringpuffern zum Beispiel gibt es genau 3 Dinge: den eigentlichen Puffer,
> den Schreibzeiger und den Lesezeiger. Den Puffer braucht man nie als
> volatile zu kennzeichnen.

Warum nicht? Deine eigene vorherige Definition widerspricht dem. Es wird 
von der ISR aus und vom Hauptprogramm aus drauf zugegriffen, also muss 
es volatile sein. Es geht soweit ich weiß i.d.R. trotzdem, da Compiler 
solche Arrayzugriffe normalerweise nicht wegoptimieren, aber im Prinzip 
ist volatile dort ebenfalls erforderlich.

> Wenn man sein Inteface so schreibt, daß es nichtblockierend ist, dann
> braucht man auch kein volatile. Wenn hingegen jemand blockierend
> schreibt, dann sehr wohl, weil er ja mit seinem Trampeln auf der Stelle
> seinen Dunstkreis nicht verläßt.

Das ist aber Frickelei, weil du da Annahmen darüber machst, wie der 
Compiler optimiert.

> Hier siehst du, daß es bei nicht blockierender Schreibweise völlig egal
> ist, ob da was volatile ist oder nicht, denn V24RxAvail1 wird immer
> wieder aufgerufen und wartet niemals, und auch V24GetChar1 tut das
> nicht.

Und? Warum sollte die Notwendigkeit für volatile davon abhängen, ob man 
wartet oder nicht? Wenn man einen Wert lesen will, der außerhalb des 
normalen Programmflusses verändert werden kann, braucht man volatile.

> Da beide Funktionen immer wieder beendet werden und nie warten,
> kann eine unbemerkte Änderung in U1Buf.InWP auch nicht passieren.

Warum sollte es nicht? Es änddert eigentlich nicht wirklich was.

von S. R. (svenska)


Lesenswert?

Faustregeln:

(1)
Alles, was in der ISR und im Hauptprogramm benutzt wird, muss volatile 
sein. Das betrifft den Puffer, den Read-Index und den Write-Index, die 
brauchen das alle.

(2)
Alles, was atomar sein muss, aber nicht zwingend durch die Hardware 
atomar ist, muss in eine critical section. Das betrifft auf 8 
Bit-Architekturen Read- und Write-Pointer.

(3)
1
asm volatile ("" : : : "memory");
 sagt dem Compiler, dass der Speicher gerade unvorhersehbar geändert 
wurde und alle in Registern gecachten Werte aus dem Speicher jetzt 
ungültig sind. Das sollte also für so einen Anwendungsfall auf AVR 
funktionieren. Auf anderen Architekturen kann es sein, dass du noch eine 
Memory Barrier brauchst.

von Marco (Gast)


Lesenswert?

S. R. schrieb:
> (1)
> Alles, was in der ISR und im Hauptprogramm benutzt wird, muss volatile
> sein. Das betrifft den Puffer, den Read-Index und den Write-Index, die
> brauchen das alle.

Du meinst alles was in der ISR beschrieben wird. Wenn in der ISR nur 
gelesen wird sollte kein volatile benötigt werden.

von Stefan K. (stefan64)


Lesenswert?

W.S. schrieb:
1
ein Beispiel:/* ob empfangene Zeichen vorhanden sind */
2
bool V24RxAvail1 (void) { if (U1Buf.InRP != U1Buf.InWP) return 1; 
3
return 0; }
> Hier siehst du, daß es bei nicht blockierender Schreibweise völlig egal
> ist, ob da was volatile ist oder nicht, denn V24RxAvail1 wird immer
> wieder aufgerufen und wartet niemals, und auch V24GetChar1 tut das
> nicht. Da beide Funktionen immer wieder beendet werden und nie warten,
> kann eine unbemerkte Änderung in U1Buf.InWP auch nicht passieren.

Das stimmt, wenn V24RxAvail1 als Funktion aufgerufen wird. Aber nicht 
mehr, wenn V24RxAvail1 geinlined wird:
1
while (!V24RxAvail1());
Auf der anderen Seite bringt das Weglassen von volatile keinerlei 
Vorteile, solange die Variable nur einmal innerhalb des Kontext 
verwendet wird.

Viele Grüße, Stefan

von Marco (Gast)


Lesenswert?

Marco schrieb:
> S. R. schrieb:
>> (1)
>> Alles, was in der ISR und im Hauptprogramm benutzt wird, muss volatile
>> sein. Das betrifft den Puffer, den Read-Index und den Write-Index, die
>> brauchen das alle.
>
> Du meinst alles was in der ISR beschrieben wird. Wenn in der ISR nur
> gelesen wird sollte kein volatile benötigt werden.

Dem füge ich noch hinzu ich hatte erst kürzlich ein Problem wo ich 2 
Volatile variablen hintereinander beschrieben hatte - dies hatte zu 
einem hardfault geführt. Nachdem ich einen rausgenommen hatte hat es 
problemlos funktioniert.

Also mit volatile sollte man grundsätzlich schon so sparsam wie möglich 
umgehen, einfach volatile vorsichtshalber stehen lassen ist keine gute 
Idee.

von Rolf Magnus (Gast)


Lesenswert?

Marco schrieb:
> S. R. schrieb:
>> (1)
>> Alles, was in der ISR und im Hauptprogramm benutzt wird, muss volatile
>> sein. Das betrifft den Puffer, den Read-Index und den Write-Index, die
>> brauchen das alle.
>
> Du meinst alles was in der ISR beschrieben wird. Wenn in der ISR nur
> gelesen wird sollte kein volatile benötigt werden.

Nein, wer liest und wer schreibt, spielt keine Rolle.

Stefan K. schrieb:
> Auf der anderen Seite bringt das Weglassen von volatile keinerlei
> Vorteile, solange die Variable nur einmal innerhalb des Kontext
> verwendet wird.

Und wenn sie mehrmals verwendet wird, ist es besser, den Wert in einer 
lokalen Variable zwischenzuspeichern und die stattdessen zu nehmen, so 
dass die Austausch-Variable wieder nur einmal verwendet wird. Damit ist 
auch ein atomarer Zugriff leichter, weil man nicht kreuz und quer durch 
den ganzen Code darauf zugreift und dann aufpassen muss, nicht in einen 
inkonsitenten Zustand zu kommen, weil die eine Hälfte noch mit einem 
alten Wert rechnet und die andere schon mit einem inzwischen 
eingetroffenen neuen Wert.
Das ist natürlich schwieriger, wenn man, wie von W.S. vorgeschlagen, die 
Zugriffe auf separate Funktionen verteilt, geht aber zur Not per 
Parameter-Übergabe auch.

von Stefan K. (stefan64)


Lesenswert?

Marco schrieb:
> Du meinst alles was in der ISR beschrieben wird. Wenn in der ISR nur
> gelesen wird sollte kein volatile benötigt werden.

Dann kann es passieren, dass die isr einen Wert benutzt, der im 
Hauptprogramm längst verändert, aber noch nicht wieder in die Variable 
zurück geschrieben wurde.

Bei einem Input-Buffer kann dann die isr einen Buffer-Overflow 
feststellen,
obwohl das Hauptprogramm schon längst Zeichen abgeholt hat.

Bei einem Output-Buffer kann es passieren, dass die Output-isr enabled 
wird und dann als erstes feststellt, dass sie nichts zu senden hat, da 
das Hauptprogramm seinen Index noch nicht zurück in die Variable 
geschrieben hat.

Ich gebe zu, dass diese Szenarien etwas konstruiert werden müssen. Auf 
der anderen Seite gibt es in diesem Zusammenhang kaum Szenarien, wo der 
Compiler ohne volatile besser optimieren kann.

Gruß, Stefan

von Marco (Gast)


Lesenswert?

A variable should be declared volatile whenever its value could change 
unexpectedly. In practice, only three types of variables could change:

1. Memory-mapped peripheral registers

2. Global variables modified by an interrupt service routine

3. Global variables accessed by multiple tasks within a multi-threaded 
application

We'll talk about each of these cases in the sections that follow.

http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

---

Sprich bei einem normalen Programm mit Interrupt - und wenn im Interrupt 
nur gelesen wird - dann wird meiner Ansicht nach kein volatile benötigt.

von S. R. (svenska)


Lesenswert?

Marco schrieb:
> Du meinst alles was in der ISR beschrieben wird. Wenn in der ISR nur
> gelesen wird sollte kein volatile benötigt werden.

Aus Sicht des Hauptprogramms ist die ISR ein externer Zugriff, aus Sicht 
der ISR ist das Hauptprogramm ein externer Zugriff. Das ist ein 
symmetrisches Problem.

Das wird speziell dann interessant, wenn Hauptprogramm und ISR 
gleichzeitig laufen können (Multicore-CPUs, DMA o.ä.)

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

DerFreud schrieb:
> W.S: Warum greifst du Leute, die vielleicht nicht so viel wissen wie du
> so gemein an? Würdest du das mit deinen Freunden oder Frau und deinen
> Kindern auch so machen?
> Wie würdest du auf so einen Satz antworten?

Wenn ich jemanden angreifen würde, dann sähe das ganz anders aus. Ich 
kann sowas, kannste glauben.

Du bist mit deinen Ausführungen deutlich zu zimperlich. Wir schreiben 
hier nicht über Händchenhalten oder Eia-Popeia, sondern über Technik.

W.S.

von W.S. (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Warum nicht? Deine eigene vorherige Definition widerspricht dem. Es wird
> von der ISR aus und vom Hauptprogramm aus drauf zugegriffen, also muss
> es volatile sein.

Du irrst.

Also noch einmal das Ganze:
1. es gibt einen Puffer, wo die Bytes (oder sonstwas) hineinkommen. 
Dieser Puffer braucht niemals als volatile gekennzeichnet zu werden, wie 
ich bereits schrieb.

2. es gibt zwei Indizes, die in den Puffer hineinzeigen. Einer der 
Indizes wird von der ISR verwaltet und der andere von der Leseroutine. 
Den jeweils eigenen Index verändert die Instanz, den anderen liest sie 
nur einmalig und verändert ihn nicht.

Verstehe mal das Prinzip: Da schreibt man einen HW-Treiber, der sowohl 
die ISR, als auch die Initialisierung als auch die im Headerfile 
ausgewiesenen Schnittstellen-Funktionen enthält. Alle diese Funktionen 
werden von ganz woanders aufgerufen und warten nicht auf irgendwelche 
Änderungen der von ihnen nicht verwalteten Indizes. Sie können sich 
auch in ihrem Kontext niemals etwas unter der Hand merken (was ja genau 
das ist, was man per volatile zu unterbinden trachtet).

Logischerweise gibt es nur einen einmaligen Gebrauch, zu diesem Gebrauch 
muß dieser Index einmalig geladen werden und das war's.

Ebenso logischerweise ist ein Inline für Funktionen, die im Headerfile 
als extern deklariert sind, nicht möglich.

Ist das jetzt etwas klarer?

W.S.

von W.S. (Gast)


Lesenswert?

S. R. schrieb:
> Faustregeln:
>
> (1)
> Alles, was in der ISR und im Hauptprogramm benutzt wird, muss volatile
> sein. Das betrifft den Puffer, den Read-Index und den Write-Index, die
> brauchen das alle.

Auch du, Brutus?

Nein, so etwas zu sagen, ist schlichtweg falsch. Allerdings stört es 
niemanden, wenn man vor all so etwas 'volatile' davorschreibt.

Der Knackpunkt ist ja, daß volatile einzig nur dazu benutzt wird, um den 
Compiler davon abzuhalten, einen echten Zugriff auf das 'ding' 
wegzuoptimieren. Sowas trifft man stets, wenn irgendwo blockierend 
geschrieben wurde:
 while (wichtigesFlag) warte();

Wenn man hingegen im gerade lokalen Kontext NICHT auf irgendwas wartet, 
sondern das wichtige Flag oder so nur ein einziges mal abfragt, dann 
kann ein Wegoptimieren nicht stattfinden. Folglich ist es schnurz, ob 
dort volatile steht oder nicht.

W.S.

von S. R. (svenska)


Lesenswert?

W.S. schrieb:
> S. R. schrieb:
>> Faustregeln:
>>
>> (1)
>> Alles, was in der ISR und im Hauptprogramm benutzt wird, muss volatile
>> sein. Das betrifft den Puffer, den Read-Index und den Write-Index, die
>> brauchen das alle.
>
> Auch du, Brutus?
>
> Nein, so etwas zu sagen, ist schlichtweg falsch. Allerdings stört es
> niemanden, wenn man vor all so etwas 'volatile' davorschreibt.

Darum schrieb ich "Faustregeln" oben drüber. Hältst du dich dran, 
bekommst du keine falschen Ergebnisse, hältst du dich nicht dran, 
solltest du es für jeden Einzelfall verdammt gut begründen können.

> Der Knackpunkt ist ja, daß volatile einzig nur dazu benutzt wird, um den
> Compiler davon abzuhalten, einen echten Zugriff auf das 'ding'
> wegzuoptimieren.

Richtig.

> Sowas trifft man stets, wenn irgendwo blockierend
> geschrieben wurde:

Aber nicht ausschließlich!

> Wenn man hingegen im gerade lokalen Kontext NICHT auf irgendwas wartet,
> sondern das wichtige Flag oder so nur ein einziges mal abfragt, dann
> kann ein Wegoptimieren nicht stattfinden.

Heutige Compiler können das nicht wegoptimieren, richtig. Aber kein 
Compiler garantiert dir das.

Wenn du den Optimizer deines Compilers kennst und dich auf dessen 
Implementation verlässt, oder du durch die Struktur deines Codes den 
Optimizer hinreichend behinderst, kannst du dir das volatile auch 
sparen.

Dafür riskierst du, dass dein Code irgendwann mal kaputtgeht. Sei es 
durch bessere Compiler (in den letzten 20 Jahren ist da viel passiert - 
in den nächsten 20 Jahren wird da viel passieren) oder durch eine 
veränderte Umgebung (Multicore).

von Rolf Magnus (Gast)


Lesenswert?

S. R. schrieb:
> Marco schrieb:
>> Du meinst alles was in der ISR beschrieben wird. Wenn in der ISR nur
>> gelesen wird sollte kein volatile benötigt werden.
>
> Aus Sicht des Hauptprogramms ist die ISR ein externer Zugriff, aus Sicht
> der ISR ist das Hauptprogramm ein externer Zugriff. Das ist ein
> symmetrisches Problem.

Selbst aus Sicht des Hauptprogramms alleine egibt sich das Problem 
schon. Ist ja nicht anders, als bei I/O-Registern. Denn der Compiler 
könnte ja sowohl Lese-, als auch Schreibzugriffe wegoptimieren.


W.S. schrieb:
> Also noch einmal das Ganze:
> 1. es gibt einen Puffer, wo die Bytes (oder sonstwas) hineinkommen.
> Dieser Puffer braucht niemals als volatile gekennzeichnet zu werden, wie
> ich bereits schrieb.

Das hast du bereits geschrieben. Und ich habe geschrieben, dass das 
nicht stimmt.

> Verstehe mal das Prinzip: Da schreibt man einen HW-Treiber, der sowohl
> die ISR, als auch die Initialisierung als auch die im Headerfile
> ausgewiesenen Schnittstellen-Funktionen enthält. Alle diese Funktionen
> werden von ganz woanders aufgerufen und warten nicht auf irgendwelche
> Änderungen der von ihnen nicht verwalteten Indizes. Sie können sich
> auch in ihrem Kontext niemals etwas unter der Hand merken (was ja genau
> das ist, was man per volatile zu unterbinden trachtet).

Das heißt, du trennst es in eigene Funktionen, die du alle in separate 
C-Files schreibst, nur damit der Compiler kein Inlinig betreiben kann 
und du dir so das volatile sparen kannst? Und dann kommt gcc -flto und 
betreibt Inlining über die Grenzen der einzelnen C-Files hinweg.
Wie ich schon sagte: Gefrickel, weil Annahmen über das 
Optimierungsverhalten des Compilers getroffen werden. So entsteht dann 
der Code, der mit neueren Compiler nicht mehr tut. Es gibt da immer drei 
Möglichkeiten: Man schreibt korrekten Code, man schreibt unbewusst 
falschen code - oder man schreibt bewußt falschen Code, weil er ja 
trotzdem funktioniert (zumindest im Moment).
Die Regel ist doch ganz einfach: Variablen, bei denen es essenziell ist, 
dass alle Zugriffe darauf auch statt finden, brauchen volatile. Und das 
schreibt man natürlich auch dann hin, wenn man einen Spezialfall 
gefunden hat, in dem mit dem aktuellen Compiler der Zugriff auch ohne 
volatile stattfindet.

> Ebenso logischerweise ist ein Inline für Funktionen, die im Headerfile
> als extern deklariert sind, nicht möglich.

Das ist falsch. Siehe -flto.

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.