hello, ich programmiere mir gerade eine serielle Schnittstelle. Habe einen 80C517A und verwende die S1. Den Interrupthandler habe ich schon grundsätzlich. Ich programmiere im uVision in C. Jetzt geht es darum, wie die Daten gebuffert werden sollen. Habe mir folgendes vorgestellt: Ich habe im Programm ein Array data. Werden im uC Daten empfangen, wird ein Array line angelegt. Von SBUF werden Byte für Byte eingelesen und in line abgelegt. z. B. übertragen a b c \n in line sind dann: [0]=a [1]=b [2]=c \n wird nicht abgespeichert Sobald \n gelesen wird, kommt das array line in das array data. z. B. data[0]: [0]=a [1]=b [2]=c Kommen dann wieder Daten rein, passiert das gleiche wieder z. b. übertragen x y z \n in line: [0]=x [1]=y [2]=z und in data: data[0]: [0]=a [1]=b [2]=c data[1]: [0]=x [1]=y [2]=z Das Hauptprogramm läuft in einer Endlosschleife und schaut regelmäßig nach, ob in data etwas steht. Immer den ältesten Eintrag nimmt es dann zuerst heraus und verarbeitet ihn. Wäre also ein FIFO. Funktioniert das? Macht man das so richtig?
@ Heinz B. (hez) Funktioniert das? Macht man das so richtig? Jain. Einige Detail scheinen noch nicht ganz stimmig zu sein. Ein gutes Beispiel wie man es machen könnte findest du im Artikel Interrupt. MFG Falk
Gast
#1562181
> Funktioniert das? Macht man das so richtig?
Ja, so im groben funktioniert das. Ich mache es auch immer
mit einer Fifo wenn die Daten das erlauben, man also nicht
unmittelbar auf ein Byte sofort reagieren muss.
Du benoetigst im Prinzip vier Funktionen.
fifo_init
fifo_put
fifo_get
fifo_anzahl
Klugerweise implementiert man den Fifo als Ringbuffer. Das hat
den Vorteil das nur Daten verloren gehen wenn du mal keine Zeit
hast, es aber nicht zum Supergau kommt.
Dabei gibt es etwas zu beachten. Die Funktion zum schreiben von
Daten rufst du nur im IRQ auf, und die zum lesen nur ausserhalb!
Es kann dir aber passieren das du bereits neue Daten reinschreibst
und den Zeiger inkrementierst waerend du ausserhalb gerade mittem
im Lesezugriff bist. Du musst dann beim lesen und dekrementieren
der Daten kurz den IRQ fuer die RS232 sperren.
Olaf
http://www.mikrocontroller.net/articles/Interrupt Habe mir das mal schnell angeschaut. Kenn mich eigentlich vorn und hinten nicht aus, aber ich glaube, ich konnte so ungefähr das Prinzip herauslesen. Wenn ich das richtig verstanden habe, gibt es in diesem Beispiel nur einen einzigen Buffer. Dieser Buffer kann aus einer bestimmten Anzahl von chars bestehen. Wird eine Übertragung mit \r abgeschlossen, gilt dieser eine Buffer als voll. >>Klugerweise implementiert man den Fifo als Ringbuffer So etwas finde ich besser. Allerdings habe ich null Ahnung, wie man so etwas programmieren könnte. :-/ >> Es kann dir aber passieren das du bereits neue Daten reinschreibst >> und den Zeiger inkrementierst waerend du ausserhalb gerade mittem >> im Lesezugriff bist. Verstehe das Problem nicht. Ich hätte mir so etwas vorgestellt: Ich bilde ein Array. Kommen Daten rein, lege ich Zeichen für Zeichen in das Array ab. Erst bei einem \n kommt dieses Array in den Ringbuffer. Das Hauptprogramm greift nur auf den Ringbuffer zu. Das sollte dann keine Probleme verursachen, oder???
Heinz B. schrieb: > http://www.mikrocontroller.net/articles/Interrupt > > Habe mir das mal schnell angeschaut. Kenn mich eigentlich vorn und > hinten nicht aus, aber ich glaube, ich konnte so ungefähr das Prinzip > herauslesen. > Wenn ich das richtig verstanden habe, gibt es in diesem Beispiel nur > einen einzigen Buffer. Dieser Buffer kann aus einer bestimmten Anzahl > von chars bestehen. Wird eine Übertragung mit \r abgeschlossen, gilt > dieser eine Buffer als voll. Das hast du richtig herausgelesen. Aber das im Artikel beschriebene ist auch nicht wirklich ein Ringbuffer. >>>Klugerweise implementiert man den Fifo als Ringbuffer > > So etwas finde ich besser. Allerdings habe ich null Ahnung, wie man so > etwas programmieren könnte. :-/ unsigned char Buffer[20]; unsigned char WriteIndex; void PutIntoBuffer( unsigned char Byte ) { Buffer[WriteIndex++] = Byte; if( WriteIndex == 20 ) WriteIndex = 0; } Stell dir das Buffer Array als zum geschlossenen Kreis gebogen vor. Du hast dann die Eigenschaft: Bist du am Ende angelangt, fängst du einfach wieder von vorne an. Analogie: Ein 'Zifferblatt', wo du an jeder Minutenposition einen Wert speichern kannst. Dazu einen 'Zeiger', der dir sagt, wo der nächste Wert gespeichert werden muss. Kommt ein Wert schreibst du den Wert an die Position auf die der Zeiger weist und drehst den Zeiger um 1 Position weiter. Natürlich brauchst du auch noch einen 2-ten Zeiger, der dir sagt von wo gelesen werden kann. Liest du einen Wert, so wird auch dieser Zeiger um 1 Position weitergedreht. Und jetzt kann man sich natürlich überlegen, was einem die Zeigerpositionen mitteilen in Bezug auf die Fragestellungen * Ist noch Platz im Buffer oder überschreibe ich mir noch nicht gelesene Daten * Ist überhaupt noch etwas im Buffer enthalten, was noch nicht gelesen wurde >>> Es kann dir aber passieren das du bereits neue Daten reinschreibst >>> und den Zeiger inkrementierst waerend du ausserhalb gerade mittem >>> im Lesezugriff bist. > > Verstehe das Problem nicht. Wenn du die Reihenfolge des schreibens und Zeiger weiterstellens richtig machst und das Zeiger weiterstellen atomar von statten gehen könnte, wäre es auch kein Problem. > Ich bilde ein Array. Kommen Daten rein, lege ich Zeichen für Zeichen in > das Array ab. Erst bei einem \n kommt dieses Array in den Ringbuffer. > Das Hauptprogramm greift nur auf den Ringbuffer zu. Das sollte dann > keine Probleme verursachen, oder??? Zu kompliziert und es löst das Problem des atomaren Weiterschaltens des Zeigers nicht.
>> unsigned char Buffer[20]; >> unsigned char WriteIndex; >> void PutIntoBuffer( unsigned char Byte ) >> { >> Buffer[WriteIndex++] = Byte; >> if( WriteIndex == 20 ) >> WriteIndex = 0; >> } Coole Sache. Schaut gut aus. Habe schon befürchtet, da muss man was mit Zeigern zaubern. >> Zu kompliziert Ich will jetzt nicht so klingen, dass das ein leichtes wäre ... habe ja fast 0 Ahnung vom C ... aber wie gehts denn noch einfacher?? >> und es löst das Problem des atomaren Weiterschaltens des >> Zeigers nicht. Das habe ich leider immer noch nicht verstanden. Ich sehe das Problem nicht.
@Heinz B. (hez) >Das habe ich leider immer noch nicht verstanden. Ich sehe das Problem >nicht. Dann lies mal das. http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff MFG Falk
Mir ist schon klar, was "atomar" bedeutet. Nur finde ich kein Problem darin, wenn ich - wie schon beschrieben - folgendermaßen vorgehe: Zuerst alle seriellen Daten einer Übertragung (bis zu einem \n) in einem Array line sammeln und dann beim Empfang eines \n in einem Schlag dem Array data übergeben. Wenn ich sage: data[xy}=line Ist das nicht atomar?
Gast
#1563531
Heinz B. schrieb: > Wenn ich sage: > data[xy}=line > Ist das nicht atomar? Nein. Zitat: Von einem atomaren (engl. atomic) Datenzugriff spricht man, wenn der Zugriff innerhalb einer nicht unterbrechbaren Maschinenanweisung abgearbeitet wird.
Heinz B. schrieb: > einem Schlag dem > Array data übergeben. Genau da liegt der Pferdefuss. Du kannst das eben nicht 'in einem Schlag' machen. Da sind auf Assemblerebene viele Operationen beteiligt. Und zwischen je 2 dieser Operationen kann dir ein Interrupt reinknallen. > Wenn ich sage: > data[xy}=line > Ist das nicht atomar? Wenn wir hier von strings reden: Nein das ist nicht atomar. Ausserdem ist das ja noch gar nicht die vollständige Operation data[xy] = line; xy++; Aber schreiben ist eigentlich nicht das Problem. Das geschieht ja in der ISR und kann daher (bei normalen ISR Aufbau) nicht von einem anderen Interrupt unterbrochen werden. Lesen ist blöder. Denn das passiert ausserhalb und KANN daher von einem Interrupt mitten im Vorgang unterbrochen werden.
Hört sich alles kompliziert an. Aber Interrupts vorübergehend zu deaktivieren, finde ich auch irgendwie doof. Dann muss ich damit rechnen, dass irgendwann Daten verloren gehen, weil kein Interrupt ausgelöst wurde, oder?
Gast
#1563549
Heinz B. schrieb: > Dann muss ich damit > rechnen, dass irgendwann Daten verloren gehen, weil kein Interrupt > ausgelöst wurde, oder? Wenn du nebenbei noch ne OCR auf ein Telefonbuch machst, schon. Sonst nicht.
Gast
#1563562
> Ich will jetzt nicht so klingen, dass das ein leichtes wäre ... habe ja > fast 0 Ahnung vom C ... aber wie gehts denn noch einfacher?? Wenn ich mal gnadenlos ehrlich sein soll, das hier vermittelte Konzept ist sehr einfach. Und noch besser es ist sehr gut geeignet wenn man mal ein bisschen C lernen will. Du kannst dir dann naemlich mal so einen Ringbuffer programmieren, den mit Testdatenfuellen und ausprobieren was da wann passiert. Es empfiehlt sich im uebrigen auch einen C Compiler auf dem PC zu haben. Das muss nicht mal so eine fette Kiste mit allem drum und dran sein. Selbst ein alter kostenloser TurboC reicht da aus. Dann kann man naemlich solche Funktionen erstmal auf einem PC testen bevor man sie einsetzt. > Das habe ich leider immer noch nicht verstanden. Ich sehe das Problem > nicht. Nehmen wir mal an du hast eine Variable von 16Bit breite. Wenn du jetzt einen 16Bit Microcontroller hast so kann der bei der zuweisung eines anderen Wertes das in einem einzigen Maschienenbefehl machen. Hast du jetzt aber einen 8Bit Microcontroller so macht der daraus mehrere Befehle. Und dann kann es passieren das dein Programm vom Interrupt in der Mitte dieser Befehle unterbrochen wird. Wenn der Interrupt aber auch mit derselben Variable rumspielt dann kann es passieren das die dabei einen falschen Inhalt bekommt. Nebenbei das ist auch ein gutes Beispiel warum man auf die Nase fallen kann wenn man einfach Code von anderen Leuten uebernimmt ohne den zu verstehen. .-) Aber es kommt noch schlimmer. Nimm mal diese Zeile als Beispiel: > if( WriteIndex == 20 ) WriteIndex = 0; Selbst wenn dieser Variablen vom Type char sind, so kann es ein Problem sein. Einfach weil hier zweimal auf die Variable zugegriffen wird und dazwischen auch ein IRQ liegen koennte. Das sind im uebrigen Probleme die nichts mit dem Verstaendnis von C zutun haben, sondern von der Nutzung der Sprache auf einem Microcontroller herruehren. Deshalb ist es IMHO nicht so optimal wenn man C auf einem Controller lernt. > Ich bilde ein Array. Kommen Daten rein, lege ich Zeichen für Zeichen in > das Array ab. Erst bei einem \n kommt dieses Array in den Ringbuffer. Wenn moeglich sollte man das auch vermeiden. Man haelt seinen IRQ so kurz wie moeglich weil er dein Programm unterbricht und dem Rechenzeit klaut. Und fast noch schlimmer je nach Datenlage in deinem IRQ wird der IRQ auch noch verschieden lange dauern. Das kann je nach Anwendung egal oder total schlimm sein. Mach lieber soetwas: 1. Im IRQ nur Zeichen in deinen FIFO werfen. Ohne nachdenken einfach rein damit. 2. Jetzt weisst du das ein Datensatz von dir sagen wir mal immer 10Byte gross ist. Also machst du dein Buffer mindestens 20 oder 30Byte gross. 3. In deinem Hauptprogramm schaust du nach ob bereits 10Byte da sind. Wenn nicht machst du irgendwas anderes. 4. Wenn genug Daten eingetroffen sind dann wertest du sie aus. Klugerweise machst du das in einer Statemaschine. Es ist wichtig du bei Fehlern wieder sicher aufsetzt. Viele Programmierer haben das nicht verstanden. Das sind dann die Produkte die erstmal problemlos funktionieren, wo man aber alle paar Wochen oder Monate mal den Stecker ziehen muss um sie zu resetten. Olaf
Ich habe mir jetzt angeschaut, was uVision aus der Zuweisung
data[xy] = line;
macht. Es stimmt, das sind tatsächlich mehrere Mnemonic-Befehle.
>> Wenn du nebenbei noch ne OCR auf ein Telefonbuch machst, schon.
OCR? Telefonbuch? Du schreibst mit einem Laien ;)
Gast
#1563590
OCR = Optical Character Recognition = optische Zeichenerkennung Telefonbuch = Viiieeeele Zeichen
Heinz B. schrieb: >>> Wenn du nebenbei noch ne OCR auf ein Telefonbuch machst, schon. > OCR? Telefonbuch? Du schreibst mit einem Laien ;) Was er sagen will > Aber Interrupts vorübergehend zu deaktivieren, finde ich auch > irgendwie doof. Dann muss ich damit rechnen, dass irgendwann Daten > verloren gehen, Deine Interrupts sind für ein paar µ-Sekunden gesperrt. Trifft tatsächlich in dieser Zeit ein Zeichen ein, dann muss es eben ein paar µ-Sekunden warten. Bis das nächste Zeichen von der UART kommt vergeht aber ... eine halbe Ewigkeit für den µC. Daher geht da auch nichts verloren. Über die UART kommen die Zeichen ganz einfach nicht schnell genug, als dass es etwas ausmachen würde, wenn die Interrupts ganz kurz gesperrt werden. Was anderes wäre es, wenn während der Interruptsperre etwas wirklich lang andauerndes passiert, wie eben eine OCR (optical Character Recognition, also kurz gesagt das Ratespiel welche Pixel in einem Bild einen Buchstaben ergeben und wenn ja welchen)
Achso. Ich wusste nicht, dass Interrupts auch in einer Art Warteschlange abgelegt werden. Also ich habe jetzt mal was gebastelt. Das ist mein 2tes C-Programm für einen uC. Also haut mich bitte nicht, falls hier nur Blödsinn steht (was warhrscheinlich der Fall sein wird) :) Getestet habe ich noch gar nichts. Ich weiß noch gar nicht, wie ich das wirklich testen kann. Das hier ist nur ein Ringbuffer für den Empfang von Daten.
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | |
61 | |
62 | |
63 | |
64 | |
65 | |
66 | |
67 | |
68 | |
69 | |
70 | |
71 | |
72 | |
73 | |
74 | |
75 | |
76 | |
77 | |
78 | |
79 | |
80 | |
81 | |
82 | |
83 | |
84 | |
85 | |
86 | |
87 | |
88 | |
89 | |
90 | |
91 | |
92 | |
93 | |
94 | |
95 | |
96 | |
97 | |
98 | |
99 | |
100 | |
101 | |
102 | |
103 | |
104 | |
105 | |
106 | |
107 | |
108 | |
109 | |
110 | |
111 | |
112 | |
113 | |
114 | |
115 | |
116 | |
117 | |
118 | |
119 | |
120 | |
121 | |
122 | |
123 | |
124 | |
125 | |
126 | |
127 | |
128 | |
129 | |
130 | |
131 | |
132 | |
133 | |
Was sagt ihr dazu? Müll? Gleich wieder wegwerfen?
Gast
#1563879
Ein Detail. Folgender code ist unguenstig :
void PutIntoBuffer( unsigned char Byte )
{
Buffer[WriteIndex++] = Byte;
if( WriteIndex == 20 )
WriteIndex = 0;
}
Dies weil 20 eine unguensige Zahl ist. Man verwendet besser 32.
Buffer[WriteIndex++] = Byte;
Writeindex &= 0x1F;
Macht schon alles.
Gast
#1563914
Hi Ich kenne mich mit C nicht aus, habe aber in Assembler solche Empfangsroutinen umgesetzt. Daher versuch ich's mal zu erklären, wie ich sowas mache. Ich habe z.B. 4 Bereiche, wo meine Nutzdaten drin stehen Satz_1: Byte10 Satz_2: Byte 10 Satz_3: etc.... Dann brauch ich einen Puffer, da meine Zeichen irgendwann einmal eintrudeln und ich nicht weiß, wo sie hingehören... USART_Buf: Byte 40 (hat aber nix mit den 40 Bytes der Datensätze zu tun, ist hier reinzufällig) An dieser Stelle mußt du selbst entscheiden, wie groß du diesen Puffer brauchst. Hier stehen die Daten nur temporär, bis das Hauptprogramm sie weggeschafft hat. Um nun diesen Buffer zu beschreiben und auszulesen benötige ich 2 weitere Variablen Buf_Read: Byte 1 Buf_Write: Byte 1 Diese sind meine Zeiger auf den Ringpuffer. Ist ein Zeiger so groß, wie der Puffer ( in diesem Fall 40) wird er auf 0 gesetzt und zeigt auf den Anfang. Folgendes passiert in der ISR des UART, wenn ein Zeichen eintrifft: Zuerst erhöhe ich Buf_Write, Kontrolle, ob Puffergrenze erreicht und wenn ja, dann Buf_Write auf 0 dann schreib ich den Zeiger zurück. Anschließend hole ich die Adresse der Variablen USART_Buf und addiere Buf_Write. Somit kann ich das nächste Feld im Puffer beschreiben. Damit ist die ISR fertig. Im Hauptprogramm frage ich einfach bei jedem Durchlauf ab, ob Buf_Read und Buf_Write gleich sind. Ist das der Fall, gibt's nix neues, ansonsten wird Buf_Read erhöht, geprüft auf Puffergrenze und evtl. auf 0 gesetzt, wieder abgelegt und dann zur Adresse des Puffers addiert und das Zeichen zur Weiterbehandlung gelesen. Entweder man hat irgendwann ein Steuerzeichen empfangen, was aussagt, welcher Datensatz grad eintrudelt, oder man prüft auf ein entsprechendes Steuerzeichen. Wie auch immer, der Ablauf ist nicht von der ISR abhängig und blockiert diese auch in keinster Weise. Empfängt man mit hoher Baudrate große Datenmengen, muß der Puffer entsprechend groß ausgelegt werden, damit er nicht seinen eigenen Schwanz überschreibt. Aber in der Regel sind die Controller schon sehr schnell, das ein Pufferüberlauf (oder besser gesagt, das Überschreiben der unbewerteten Daten) nicht vorkommt. Natürlich ist auch der Fall denkbar, zuerst die Addition von Pufferadresse und Zeiger und nach dem Beschreiben / Lesen die Zeiger zu erhöhen. Auch da ist Gleichheit, wenn kein neues Zeichen eingetrudelt und alles abgearbeitet ist. Ich hoffe du kannst nach meiner Beschreibung das in C umsetzen. Gruß oldmax
Heinz B. schrieb: > Ich weiß noch gar nicht, wie ich das wirklich testen kann. Das > hier ist nur ein Ringbuffer für den Empfang von Daten. Mein Tip: Ehe du jetzt auf deinem µC im Nebel rumstocherst, installier dir auf deinem PC eine Entwicklungsumgebung, mit der du am PC auch C-Programme schreiben kannst. Und dann testest du deine Ringbuffer-Funktionen am PC. Anstelle der UART, schreibt dann eben dein Hauptprogramm über einen Funktionsaufruf einen Character in den Ringbuffer. Aber: Auf dem PC hast du wesentlich bessere Möglichkeiten, dem Programm bei der Arbeit zuzusehen. Du hast einen Schirm, auf dem du mit printf zwischendurch Werte ausgeben lassen kannst. Du hast einen Debugger, der es dir ermöglicht, das Programm in Einzelschritten durchzugehen. All das ist Grund genug, einzelne Funktionalitäten erst mal auf dem PC zu entwickeln und zu testen und erst dann, wenn alles funktioniert, diese Funktionalität in das µC Programm zu übernehmen. Das spart dir eine Menge Kopfzerbrechen und auch Zeit.
Gast
#1563931
> Ein Detail. Folgender code ist unguenstig : Das denke ich zwar auch.... > void PutIntoBuffer( unsigned char Byte ) > { > Buffer[WriteIndex++] = Byte; > if( WriteIndex == 20 ) > WriteIndex = 0; > } > Dies weil 20 eine unguensige Zahl ist. Man verwendet besser 32. Aber nicht aus diesem Grunde. Natuerlich hast du recht, man kann durch solche Zahlen die sich binaer gut abbilden lassen, manches etwas schneller machen oder vereinfachen. Allerdings kommt es meist nicht so auf Geschwindigkeit an. Worauf es aber bei kleinen Controllern mehr ankommt das ist Rambedarf. Und wenn eine Aufgabe mit 20Byte buffer loesbar ist, dann sind 32 bereits eine ganz schoene Verschwendung. Besser ist folgendes: #define MAXFIFO 20 char Buffer[MAXFIFO]; if( WriteIndex >= (MAXFIFO-1) ) Zum einen kann man dann seine Fifofunktionen in eine Datei auslagern die man bei bedarf einbindet und dann einfach die groesse der Fifo dem aktuellen Problem anpassen, und zum anderen hat die Verwendung von '>=' den Vorteil das ein Programm nicht garsoviel Unsinn macht wenn da doch aus einem bestimmten Grund ein zu grosser Wert drin steht. Olaf
Gast
#1564001
Schau dir mal das Beispiel von Victoria Welch an... ist Code für einen 8x51 und sollte leicht zu übernehmen sein. http://sdccokr.dl9sec.de/resources.htm#Topic2
@Bernd #define kannte ich noch gar nicht. Habe ich gleich bei mir eingebaut! Danke für den Tip. Wann braucht man eigentlich volatile und static? Das sehe ich auch das erste mal in einem C-Programm. static kenne ich eigentlich nur im Zusammenhang mit OOP.
Heinz B. schrieb: > @Bernd > #define kannte ich noch gar nicht. Habe ich gleich bei mir eingebaut! > Danke für den Tip. > > Wann braucht man eigentlich volatile und static? Das sehe ich auch das > erste mal in einem C-Programm. static kenne ich eigentlich nur im > Zusammenhang mit OOP. http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Datenaustausch_mit_Interrupt-Routinen Bitte, bitte, bitte. Geh das avr-gcc-Tutorial durch! Da findet sich noch so manches, was du nicht kennst. Und bei deinem nächsten Ausflug in die Stadt schaust du auch noch in eine Buchhandlung rein und fragst ob sie einen "Kernighan&Ritchie Programmieren in C" haben und kaufst das Buch.
Gast
#1564302
Beitrag "static volatile und Interrupts" Oder suche in anderen Threads, ist oft diskutiert, als C Anfäner aber ok. Ich habe dir das Beispiel gegeben weil es sich bei deiner Aufgabenstellung um viele Details dreht und ein Beispielcode ist dann hilfreich... insbesondere wenn du allen daraus resultierenden Fragen nachgehst :-)
>> ist oft diskutiert
Verstehe ich. Ich kämpfe auch damit herum.
Versuch einer Definition zu volatile:
Soll man bei Variablen verwenden, deren Werte sich ohne eigenes Zutun
verändern können und man immer mit dem aktuellen Wert arbeiten möchte.
Z. B. bei einem Port. Möchte ich in meinem Script immer den aktuellen
Wert vom Eingabeport P4, muss ich den Wert von P4 einer
volatile-Variable übergeben. Dann hat diese Variable immer den
Letztstand.
Stimmt das? Habe ich das richtig verstanden?
Heinz B. schrieb: >>> ist oft diskutiert > Verstehe ich. Ich kämpfe auch damit herum. > > Versuch einer Definition zu volatile: > > Soll man bei Variablen verwenden, deren Werte sich ohne eigenes Zutun > verändern können und man immer mit dem aktuellen Wert arbeiten möchte. > Z. B. bei einem Port. Möchte ich in meinem Script immer den aktuellen > Wert vom Eingabeport P4, muss ich den Wert von P4 einer > volatile-Variable übergeben. Dann hat diese Variable immer den > Letztstand. > > Stimmt das? Habe ich das richtig verstanden? Ich denke: im Prinzip ja voltatile teilt dem Compiler mit, jegliche Optimierungen, gleich welcher Art, auf dieser Variablen zu unterlassen, weil sich ihr Wert auf Wegen ändern kann, die für den Compiler nicht einsehbar sind. Seine Annahmen, die sich aus irgendwelchen Anaylsen ergeben, über die zeitliche Entwicklung dieser Variablen, sind daher falsch und er möge doch bitte zu 100% genau das tun, was im Code steht. Und wenn im Code steht, dass von dieser Variablen gelesen werden soll und der Compiler denkt, dass genau dieser Wert eigentlich noch irgendwo in einem Prozessor-Register herumlungert, dann soll er das ignorieren und den Wert tatsächlich erneut aus dem Speicher lesen, auch wenn das vermeintlich länger dauert. Selbiges gilt sinngemäss auch fürs schreiben auf eine volatile Variable. > Möchte ich in meinem Script immer den aktuellen > Wert vom Eingabeport P4, muss ich den Wert von P4 einer > volatile-Variable übergeben Das ist Unsinn. In dem Fall ist es der Port, der volatile sein muss. Sobald du den Wert vom Port in einer Variablen hast, besteht ja sowieso keine Verbindung der Variablen mehr mit dem Port. Die Variable kann daher ihren Wert nicht mehr 'auf für den Compiler nicht mehr einsehbaren Wegen' ändern. Und damit kann der Compiler dann auf dieser Variablen weiteroptimieren, wie es ihm gerade einfällt. volatile heißt im Englischen soviel wie: flüchtig, unberechenbar Und genau das bedeutet es auch für den Compiler: Das zeitliche Verhalten einer Variablen ist für den Compiler nicht greifbar und entzieht sich seinen Berechnungen. Daher wäre es gut, wenn er keine Annahmen darüber treffen würde.
Gast
#1564326
Jein, es betrifft den Porgrammfluß. Besteht die Möglichkeit das eine Variable außerhalb des Programmfluß geändert werden kann, z.b. durch einen INTERRUPT dann sollte man sie volatile deklarieren. Der Compiler kann so etwas nicht wissen also sagt man ihm das mit "volatile". Bei einem Port ist das nicht notwendig denn er wird ja immer aktuell eingelesen und dann ausgewertet.
Gast
#1564335
Bernd schrieb: > Bei einem Port ist das nicht notwendig denn er wird ja immer aktuell > eingelesen und dann ausgewertet. Evtl. muss diese Aussage eingeschränkt werden wenn der IO-Port "memory mapped" ist.
Zum Thema volatile. Also ich habe mir jetzt so ein kleines Testscript gebastelt:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
Eigentlich hätte ich mir erwartet, dass er 1 und dann 2 ausgibt, jedoch kein 3 mehr.?.?.? Er gibt aber 3 aus ....
Gast
#1564417
Heinz B. schrieb:
> // funktioniert auch ohne volatile!!!!!!!!!!!!!!!!!!!!
NNNNNNNNNNNNNNNNEEEEEEEEEEEEEEEEEEEEIIIIIIIIIIIIIIIIIIIIIINNNNNNNNNNNNNN
NNN!!!!!!!!!!!!!!!!!
Man hat es Dir doch nun extra gesagt und ausführlich erklärt.
Das man auch mit geschlossenenen Augen lebend über die A81 laufen kann
ist wahrscheinlich nur ein Sonderfall, wenn derjenige, der empfiehlt, es
überhaupt nicht oder wenn, dann mit offenen Augen zu tun, irgendwie
lebendig aussieht.
Habe ich den Bernd falsch verstanden??? >>Jein, es betrifft den Porgrammfluß. Besteht die Möglichkeit das eine >>Variable außerhalb des Programmfluß geändert werden kann, z.b. durch >>einen INTERRUPT dann sollte man sie volatile deklarieren. >>Der Compiler kann so etwas nicht wissen also sagt man ihm das mit >>"volatile".
Gast
#1564423
Heinz B. schrieb:
> Habe ich den Bernd falsch verstanden???
Nein.
Guck mal, wo i geändert wird. Ist das innerhalb eines INterrupts?
Öhm ... ja.? Hier: i=0x03; Ist im Interrupt-Handler.
Gast
#1564427
Heinz B. schrieb:
> Ist im Interrupt-Handler.
Ergo?
Ähm ... ergo sollte das nur mit volatile funktionieren, dass er dann im Hauptprogramm die 3 ausgibt, oder?
Der springende Punkt ist, dass du dich ohne volatile dem Compiler auslieferst. Das kann funktionieren, muss es aber nicht. Mit einer anderen Compilerversion, einer anderen Einstellung des Optimizers, aufsplitten des Codes in 2 getrennte *.c Files, kann sich das alles umdrehen und den heissgeliebtes "funktioniert auch ohne volatile" dreht sich um in "Mist, ich habe einen Fehler und weiß nicht wo er steckt" Und noch was: "Kann" ist nicht dasselbe wie "Muss"
Heinz B. schrieb: > Ähm ... ergo sollte das nur mit volatile funktionieren, dass er dann im > Hauptprogramm die 3 ausgibt, oder? Das sagt kein Mensch. Umgekehrt wird ein Schuh draus. ohne volatile kann es funktionieren oder es kann auch nicht funktionieren. Je nach Wasserstand, Mondphase und wie der Optimizer aufgelegt ist. mit volatile muss es funktionieren.
>> Das kann funktionieren, muss es aber nicht.
Achso ... das funktioniert also nur zufällig gerade bei meinem Compiler
und bei dieser uC.
Aber grundsätzlich habe ich das richtig verstanden? Man sollte dann in
diesem Beispiel beim i MIT volatile arbeiten?
Heinz B. schrieb: > Aber grundsätzlich habe ich das richtig verstanden? Man sollte dann in > diesem Beispiel beim i MIT volatile arbeiten? Ja.
Gast
#1564438
Heinz B. schrieb: > Ähm ... ergo sollte das nur mit volatile funktionieren, dass er dann im > Hauptprogramm die 3 ausgibt, oder? Nicht ganz. Da muss man wohl nochmal weiter ausholen. Dein Programm ist doch wahrscheinlich nur eine Testversion. Also ein Sonderfall. Denn nach dem i einmal auf 0x03 gesetzt wurde ändert es seinen Wert nicht mehr. Ausserdem gibt es nur eine Reaktion die wahrscheinlich visuell schwer zu erkennen ist. Unter bestimmten Umständen wird es aber merkbar sein, das die Änderung von i nicht in main ankommt. Wiegesagt, einmal lebendig über die Autobahn könnte auch heissen, das sie es einmal geschafft haben, Dich zu reanimieren. Keine Garantie für das zweite Mal. Hier lernst Du aber auch was für Deine zukünftigen Projekte.
Wieso hat mich dann der Grrr so angegrrrrt? ;) EDIT @ Grrrr: Ja klar, ich experimentiere momentan nur herum.
Heinz B. schrieb: > Aber grundsätzlich habe ich das richtig verstanden? Man sollte dann in > diesem Beispiel beim i MIT volatile arbeiten? Sieh dir deine main(). Sieh dir nur die main() an
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
gibt es da irgendeine Möglichkeit, wie i jemals seinen Wert ändern
könnte? Es gibt keine Zuweisung an i, es gibt keinen Funktionsaufruf.
Also kann nach menschlichem (ähm compilerischem) Ermessen i niemals
seinen Wert ändern. Nichts und niemand würde den Compiler daran hindern
anstelle von
P4 = i;
sich den Wert für i zu bestimmen, zb von hier
int i=0x01;
und die Zuweisung durch
P4 = 0x01;
zu ersetzen. Das ist eine völlig legale Optimierung, die der Compiler
machen darf.
Gast
#1564445
Heinz B. schrieb:
> Wieso hat mich dann der Grrr so angegrrrrt? ;)
Ach das ist so meine Art.
Bin auf die "triumphierenden" Ausrufezeichen nach "funktioniert auch
ohne volatile" angesprungen. Deswegen mein Autobahnbeispiel. Einmal
geht's vielleicht, vielleicht auch nochmal. Aber ich gehe davon aus, das
Du noch mehr Programme schreiben willst.
volatile ist halt ein oft gemachter und vieldiskutierter Fehler.
Deswegen... meine Aufmerksamkeit erregende Art. Nichts für ungut.
Jetzt habe ich volatile auch noch eingebaut:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
35 | |
36 | |
37 | |
38 | |
39 | |
40 | |
41 | |
42 | |
43 | |
44 | |
45 | |
46 | |
47 | |
48 | |
49 | |
50 | |
51 | |
52 | |
53 | |
54 | |
55 | |
56 | |
57 | |
58 | |
59 | |
60 | |
61 | |
62 | |
63 | |
64 | |
65 | |
66 | |
67 | |
68 | |
69 | |
70 | |
71 | |
72 | |
73 | |
74 | |
75 | |
76 | |
77 | |
78 | |
79 | |
80 | |
81 | |
82 | |
83 | |
84 | |
85 | |
86 | |
87 | |
88 | |
89 | |
90 | |
91 | |
92 | |
93 | |
94 | |
95 | |
96 | |
97 | |
98 | |
99 | |
100 | |
101 | |
102 | |
103 | |
104 | |
105 | |
106 | |
107 | |
108 | |
109 | |
110 | |
111 | |
112 | |
113 | |
114 | |
115 | |
116 | |
117 | |
118 | |
119 | |
120 | |
121 | |
122 | |
123 | |
124 | |
125 | |
126 | |
127 | |
128 | |
129 | |
130 | |
131 | |
132 | |
133 | |
134 | |
135 | |
136 | |
137 | |
138 | |
Wie schaut das aus, liebe Profis? Ist das grundsätzlich OK? Ist alles so schön bunt :)
Heinz B. schrieb:
> Wie schaut das aus, liebe Profis? Ist das grundsätzlich OK?
1 | |
Das ist Murks. Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen Index in das Array ergibt. Grundsätzlich denke ich, dass du im Ringbuffer viel zu viel machst. Du versuchst dort schon eine Zerlegung in 'Datensätze' einzubauen. Würde ich nicht machen. Der Ringbuffer soll nur dazu dienen, die Zeichen von der UART aufzunehmen, wenn der µC gerade anderwertig beschäftigt ist. Ansonsten soll er sich aus allem anderen raushalten. Insbesondere ist es nicht Aufgabe des Ringbuffers irgendwelche Interpretiationen der Daten vorzunehmen. Das macht dann derjenige, der sich die Character aus dem Buffer holt.
1 | |
2 | |
auf einem 8-Bit System ist diese Operation höchst wahrscheinlich nicht atomar. iBufferRead wird mit dem neuen Wert beschrieben, indem HighByte und LowByte getrennt in den Speicher geschrieben werden. Kommt der Interrupt genau zwischen den beiden Operationen, dann findet die ISR einen nur halb geschriebenen neuen Wert in iBufferRead vor. Bei deinen Werten hast du insofern Glück, weil iBufferRead niemals größer als 16 werden wird und damit ist das HighByte immer 0. Auf der anderen Seite erhebt sich dann natürlich sofort die Frage: Warum ist das dann eigentlich ein int? Hier
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
hast du auch etwas grundlegend Misverstanden. Erstens ist das keine atomar durchgeführte Kopie. Zweitens: was hilft es dir, wenn iWrite einmal seinen Wert bekommt und dann nie wieder geändert wird. Noch mal der Tip: Probier und Teste die Einzelfunktionalität auf einem PC!
Karl heinz Buchegger schrieb:
>1 | |
2 | |
3 | |
> Das ist Murks. > Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen > Index in das Array ergibt. Mein Fehler
1 | |
Du wirst zwar das Array nicht überlaufen, aber du überschreibst dir unter Umständen noch nicht gelesene Daten. Daher: > Noch mal der Tip: > Probier und Teste die Einzelfunktionalität auf einem PC! Doppelt unterstreich!
>> Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen >> Index in das Array ergibt. iBufferRead und iBufferWrite habe ich (hoffentlich) so programmiert, dass sie nur Werte von 0 bis 3 annehmen können. Und bei iBufferChar mache ich noch so eine Kontrolle: if(iBufferChar < iCharMax) >> Du versuchst dort schon eine Zerlegung in 'Datensätze' einzubauen. Ich will halt je Datenübertragung nur eine gewisse Anzahl an Zeichen erlauben. In meinem Fall immer nur 4 Chars. >> Interpretiationen der Daten Da meinst du wahrscheinlich den Vergleich mit '\n' Ich dachte mir, ich mach es so ähnlich wie die Funktion PUTCHAR. Dort wird ja auch auf \n kontrolliert. Siehe http://www.humerboard.at/doku/sb8/uc3.pdf >> // atomar ändern: Fehler von mir. Der Kommentar gehört weg. Muss ja nicht atomar sein. >> Warum ist das dann eigentlich ein int Ich habe mir das Leben jetzt mal leicht gemacht und alles Zahlenmäßige ins int gegeben :) >> Probier und Teste die Einzelfunktionalität auf einem PC! Muss ich mir noch überlegen, wie ich das jetzt anstelle. >> int iWrite = iBufferWrite; // Kopie >> keine atomar durchgeführte Kopie Echt nicht???? Verdammt!! Aber wie soll ich das denn sonst lösen?
Ich habe jetzt alle "int" umgeändert auf "unsigned char". Wenn ich nur "char" angebe, ist das dann "signed char" oder "unsinged char"? Ist das in C auch so der Fall, dass "signed char" schneller ist als "unsigned char"? Sollte ich dann statt dem "int" ein "singned char" nehmen? In meinem Fall wäre es ja eigentlich egal. Zähle ja immer nur bis max. 4.
Gast
#1564636
Heinz B. schrieb: > Ich will halt je Datenübertragung nur eine gewisse Anzahl an Zeichen > erlauben. In meinem Fall immer nur 4 Chars. Das kannst Du weder "erwzingen", noch "erlauben". Ist, als wenn Du dem Wind erlaubst in Nord-West-Richtung zu wehen. Er macht's oder auch nicht. Es ist schon richtig so, wie Karl Heinz, das vorschlägt. Erstmal ist es effizienter, weil der Interrupt schneller läuft und dann sauberer, weil Zeichenempfang und Verarbeitung getrennt sind.
>> weil Zeichenempfang und Verarbeitung getrennt sind.
Wie soll ich denn beim Empfang erkennen, ob die Daten vollständig
übertragen wurden? Mit \n kann ich so eine Kontrolle machen. \n ist dann
auch das Zeichen dafür, dass ein Wort (Datensatz ... oder wie auch immer
man das nennen mag ... eben die max. 4 Zeichen) zu ende übertragen
wurde.
Gast
#1564645
Heinz B. schrieb: > Wie soll ich denn beim Empfang erkennen, ob die Daten vollständig > übertragen wurden? "beim Empfang", also im Interrupt, garnicht. Du empfängst einfach alles was reinkommt. Erst in der übergeordneten Ebene prüfst Du Protokoll und reagierst darauf.
Gast
#1564650
Heinz B. schrieb: > Mit \n kann ich so eine Kontrolle machen. \n ist dann > auch das Zeichen dafür, dass ein Wort (Datensatz ... oder wie auch immer > man das nennen mag ... eben die max. 4 Zeichen) zu ende übertragen > wurde. Der Punkt, um den es geht, ist ja nicht ob Du das Ende erkennst, sondern wo im Programm.
Also wäre die andere Variante, dass ich im Interrup-Handler für die empfangenen Daten überhaupt nichts prüfe (außer, dass natürlich mein kompletter Ring nicht überlaufen darf), dann aber das \n im Ringbuffer mit abspeichere. Dann muss ich im Hauptprogramm immer alle Zeichen bis zum \n zählen. Und solltes es zu viele sein, habe ich einen Fehler. So meinst du das, richtig?
Heinz B. schrieb: > Also wäre die andere Variante, dass ich im Interrup-Handler für die > empfangenen Daten überhaupt nichts prüfe (außer, dass natürlich mein > kompletter Ring nicht überlaufen darf), dann aber das \n im Ringbuffer > mit abspeichere. > > Dann muss ich im Hauptprogramm immer alle Zeichen bis zum \n zählen. Und > solltes es zu viele sein, habe ich einen Fehler. > > So meinst du das, richtig? Ganz genau. Und damit ist dann auch dein Fehler, den du mit den beiden Indexvariablen in der ISR hast, hinfällig :_) >>> Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen >>> Index in das Array ergibt. > iBufferRead und iBufferWrite habe ich (hoffentlich) so programmiert, > dass sie nur Werte von 0 bis 3 annehmen können. > Und bei iBufferChar mache ich noch so eine Kontrolle: > if(iBufferChar < iCharMax) Schön. Und zum abspeichern benutzt du aBuffer[iBufferWrite + iBufferChar++] = cS1BUF; d.h. die Bytes vom Paket mit iBufferWrite gleich 0 werden im Array an den Indexpositionen 0, 1, 2, 3 abgelegt. Die Bytes für iBufferWrite gleich 1 an den Arraypositionen 1, 2, 3, 4 Moment ich mach dir eine Tabelle iBufferWrite Arrayindices an denen gespeichert wird 0 0, 1, 2, 3 1 1, 2, 3, 4 2 2, 3, 4, 5 3 3, 4, 5, 6 merkst du was? Genau deshalb rachte ich dir noch einmal: Teste solche Dinge zuerst am PC, wo du dir einfach vernünftige Zwischenresultate mittels printf ausgeben lassen kannst.
>> aBuffer[iBufferWrite + iBufferChar++] = cS1BUF;
So einfach geht das leider nicht.
Angenommen mein Ringbuffer kann 16 Chars aufnehmen und 14
Ringbuffer-Elemente sind bereits belegt. Das letzte dieser 14 ist ein
\n.
Jetzt kommen wieder Daten rein.
Zuerst ein a ... passt noch rein in den Ringbuffer.
Dann ein b ... passt auch noch rein
Dann ein c ... passt nicht mehr rein, jetzt habe ich einen
Bufferoverflow
Jetzt dürfen abc aber nicht mehr im Ringbuffer aufscheinen. Jetzt habe
ich 2 Möglichkeiten:
- entweder darf ich Daten nur dann in den Ringbuffer eintragen, wenn
sie inkl. \n auch wirklich reinpassen oder
- ich lösche die eingetragenen Daten der letzten Übertragung wieder aus
dem Ringbuffer raus (also bis exklusive des \n der vorhergehenden
Übertragung)
Heinz B. schrieb: >>> aBuffer[iBufferWrite + iBufferChar++] = cS1BUF; > So einfach geht das leider nicht. Eben. > Angenommen mein Ringbuffer kann 16 Chars aufnehmen und 14 > Ringbuffer-Elemente sind bereits belegt. Das letzte dieser 14 ist ein > \n. > > Jetzt kommen wieder Daten rein. > Zuerst ein a ... passt noch rein in den Ringbuffer. > Dann ein b ... passt auch noch rein > Dann ein c ... passt nicht mehr rein, jetzt habe ich einen > Bufferoverflow Dein Buffer ist voll. Das sollte tunlichst nie passieren. Wenn die Gefahr besteht, dann musst du der Gegenstelle signalisieren: Nicht mehr weiter senden. > Jetzt dürfen abc aber nicht mehr im Ringbuffer aufscheinen. Falsche Strategie. > Jetzt habe > ich 2 Möglichkeiten: > - entweder darf ich Daten nur dann in den Ringbuffer eintragen, wenn > sie inkl. \n auch wirklich reinpassen oder > - ich lösche die eingetragenen Daten der letzten Übertragung wieder aus > dem Ringbuffer raus (also bis exklusive des \n der vorhergehenden > Übertragung) Die richtige Strategie besteht darin, den Ringbuffer gross genug zu machen, dass der verarbeitende Programmteil sich die zwischengespeicherten Zeichen rechtzeitig aus dem Buffer holt und so Platz für die nächsten Zeichen freimacht. Der Ringbuffer sollte sowieso nie mehr als ca. 80% gefüllt sein (in Ausnahmefällen). Wenn du regelmässig drüber kommst, den Buffer vergrößern. Wenn das nicht geht oder nicht praktikabel ist, dann musst du in der Datenübertragung ein Handshake einbauen und so dem Sender mitteilen: Ich kann nicht mehr, warte mal ein wenig. Alternativ kann man auch je nach Anwendung auch einfach mal einen Datensatz aus dem Buffer rausholen (also lesen bis zum \n) und die Daten ohne Verarbeitung verwerfen. Aber grundsätzlich sollte es dir nie passieren, dass der Buffer voll wird. Denn dann muss irgendetwas auf der Strecke bleiben. Oder anders ausgedrückt: Wenn der Wasserhahn deiner Badewanne 2 Liter in der Sekunde zulaufen lässt, durch den Ausguss aber nur 1 Liter pro Sekunde abfliessen kann, dann wird die Wanne irgendwann übergehen, egal wieviele Buffer du zwischen Zulauf und Ablauf dazwischenschaltest. Umgemünzt auf deinen Fall heißt das: Wenn der Sender schneller sendet als du verarbeiten kannst, rettet dich auch ein Ringbuffer nicht. Ein Ringbuffer hat einzig únd alleine die Aufgabe Prozesse zu entkoppeln. In deinem Fall macht er die Verarbeitung unabhängig vom Prozess des Empfanges. Aber ein Ringbuffer kann nicht ein grundsätzliches zeitliches Problem einer prinzipiell zu langsamen Verarbeitung lösen.
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.