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
> 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.
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?
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?
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.
> 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 ;)
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.
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.
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.
> 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
@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.
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.
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.
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.
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".
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:
> Ä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.
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
voidmain(void)
2
{
3
// Initialisierung serielle Schnittstelle 1:
4
S1CON=0x90;// Modus 1 (8 Datenbits + 1 Stopbit), Receive enable
5
S1RELH=0x03;// 9600 Baud
6
S1RELL=0xD9;
7
PCON=PCON|0x80;
8
EAL=1;
9
IEN2=IEN2|0x01;// Interrupts aktivieren
10
11
while(1)
12
{
13
P4=i;// HIER WIRD i AUSGEGEBEN
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.
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.
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
// atomar ändern:
2
iBufferRead=(iBufferRead+1)%iBufferMax;
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
// Daten atomar übernehmen:
2
intiWrite=iBufferWrite;// Kopie
3
4
// Kontrolle, ob Daten empfangen wurden und ausgelesen werden müssen:
5
while(iBufferRead!=iWrite)
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!
> Das ist Murks.> Du hast keine Gewähr, dass iBufferWrite + iBufferChar einen gültigen> Index in das Array ergibt.
Mein Fehler
1
iBufferWrite=(iBufferWrite+1)%iBufferMax;// nur die Interrupt-Routine darf diese Variable verändern
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.
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.
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.
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.