Hi,
Ich habe mal wieder ein kleines Problemchen:
Ich möchte Veränderungen an den Eingangspins per UART verschicken. Das
ganze mache ich über einen Ringpuffer, das senden geschieht dann per
UDR_Empty Interrupt. Da sich auch mal viele Eingangspins auf einmal
ändern können und keine Informationen untern den Tisch fallen sollen,
möchte ich, wenn der Ringpuffer voll ist, einfach so lange warten, bis
dieser wieder leer ist.
Die Ringpuffer-Geschichte funktioniert auch tadellos, allerdings gibt es
Probleme, wenn der Puffer tatächlihc voll ist.
Die Funktion sieht so aus:
;//Hab schon alle Schreibweisen, mit und ohne Klammern versucht
10
}
11
}
12
/*Bis zum Endzeichen inklusive wiederholen*/
13
while(*(p_start++)!=STR_TERMINATOR);
14
15
/*String freigeben*/
16
release_last_frame(buffer);
17
}
Dabei hängt er sich auf. Als Rückgabewert der put_to_ringbuffer gibt es
nur NO_ERROR oder BUFFER_FULL.
Ich hatte dann mal zum debuggen eine uart-Sendefunktion in die leere
Anweisung geschrieben. Diese wurde zwar nie ausgeführt, aber dafür
hängte er sich auch nicht mehr auf???
Ich weiß, das ist kein lauffähiges Beispiel, mein gesamter Code ist nur
SEHR groß, daher einfach nur die Frage: kann diese leere Anweisung
Probleme hervorrufen?
Grüße
Phil
da wärs jetzt interessant, was in put_to_ringbuffer steht :) Ob der
leere Block nur aus {} oder aus {;} oder sogar nur aus ; besteht sollte
eigentlich wurst sein. Ich kann mir nicht vorstellen, dass hier das
Problem liegt.
Was mir einfällt: Was passiert, falls sich das Programm gerade in
put_to_ringbuffer befindet, wenn der Interrupt auslöst? Falls der Puffer
gerade voll ist, ist das ja quasi ständig der Fall.
Gruß, Alex
>Was mir einfällt: Was passiert, falls sich das Programm gerade in>put_to_ringbuffer befindet, wenn der Interrupt auslöst? Falls der Puffer>gerade voll ist, ist das ja quasi ständig der Fall.
Das sollte kein Problem sein. Put to Ringbuffer legt einfach einzelne
Zeichen in den Puffer. Erst wenn das Terminatorzeichen von
Put_str_to_ringbuffer gesetzt wurde, wird der Lese-Endzeiger für das
Interrupt weitergeschoben und der gesamte Frame zum Senden freigegeben.
Wenn der Puffer am Ende voll ist, sendet das Interrupt ja trotzdem vom
Anfang her die Zeichen raus und gibt den Speicher wieder frei. Einziger
Abschussfall wäre, wenn ein einzelner Frame länge wäre als der Puffer,
das kann aber nicht passieren.
Hier die Put to Ringbuffer
Und in welchem Kontext wird put_string_to_ringbuffer aufgerufen? Dir ist
schon klar, dass wenn dort Interrupts gerade abgeschaltet sind (z.B. in
einer ISR), dass dann das "Warten per leere Anweisung" bei vollem Puffer
zur Endlosschleife wird, oder?
Ok, ich versuch mal zu rekapitulieren, was der Code macht.
p_check ist ne zwischenvariable, mit der überprüft wird, ob der nächste
platz im puffer frei zum schreiben ist.
p_next_write ist der nächste platz, an dem daten in den puffer
geschrieben werden sollen.
p_end_write versteh ich nicht zu 100%. Ist das der Zeiger, dessen Daten
als nächstes vom UART übertragen werden sollen? Siehe dazu nächster
Absatz.
Du lässt jetzt p_check auf den nächsten zu beschreibenden Platz zeigen.
Wenn der noch belegt ist, wird mit BUFFER_FULL abgebrochen. Wenn nicht,
wird p_next_write auf diesen Platz gebogen und die anstehenden Daten auf
diesen Platz geschrieben.
Ich würde nochmal ganz genau schaun, was passiert, wenn eine beliebige
dieser Anweisungen von der Sende-Routine unterbrochen wird. Ich kann mir
gut vorstellen, dass es hier Probleme geben könnte. Bedenke dabei, dass
das auch mitten in einer Zeile sein kann! Ganz einfach umgehen kannst du
das, wenn du die gesamte Routine atomar machst, also während der Routine
keine Interrupts erlaubst mit cli() am Anfang und sei() am Ende. Sollte
während dessen ein Interrupt aufgetreten sein, wird der sofort nach
sei() nachgeholt.
Grundsätzlich würde ich deine Routine ein kleines bisschen umbauen.
Konkret: Ich finde es sinnvoller, wenn p_next_write nicht auf den
zuletzt beschriebenen Platz zeigt, sondern auf den nächsten zu
beschreibenden. Damit sparst du dir die ganze Geschichte mit p_check.
Also:
- ist p_next_write == p_end_write ? -> ja: dann Abbruch mit Buffer_full
-> nein: dann ist der Platz frei
- Daten an diesen Platz schreiben
- p_next_write erhöhen (mit Umbruch wie bisher für p_check)
Du schreibst, dass ein Frame erst komplett im Puffer liegen muss, bevor
er übertragen wird.
Das kann ich nirgends finden. Und dann frag ich mich noch, warum du
darauf wartest? Wenn Daten im Puffer liegen, dann würde ich die von der
Sende-Routine auch so schnell wie möglich übertragen. Ob das ein
kompletter Datensatz ist, wäre mir egal. Falls die Daten gemeinsam
ankommen müssen, würde ich evtl. ein Magic-Byte für "Start frame"
reservieren oder alternativ ein "End frame". Alles was danach kommt,
gehört dann zu diesem Frame, bis das nächste "Start frame" kommt. Evtl.
könnte man stattdessen auch eine Hardware-Handshake Leitung zum selben
Zweck missbrauchen.
Dann ist mir noch was aus deinem ersten Post aufgefallen, an dem dein
Problem liegen könnte:
Du schreibst, dass du UDR_empty zum verschicken benutzt. Was passiert,
wenn gerade keine Daten zu verschicken sind? Dann wird UDR_empty nicht
mehr aufgerufen (es wurde ja nichts verschickt) und es werden nie wieder
Daten verschickt, außer, du überträgst irgendwo nochmal was per Hand ->
deine Debug-routine? Bist du ganz sicher, dass die nicht aufgerufen
wurde?
So, ich hoffe, mein Roman hilft dir^^
Gruß, Alex
put_string_to_ringbuffer wird aus der Main heraus aufgerufen (es wird
gecheckt, ob sich Pinzustände geändert haben, wenn ja dann wird ein
String gebaut und in den Ringpuffer geschoben.) Wird der Frame
freigegeben, wird das UDRE-ISR aktiviert und es fängt an, den Puffer
rauszusenden. Dabei wird der Speicher jedes Zeichens nachdem es in den
UDR geladen wurde wieder freigegeben. Kommt es also beim Versuch, den
String zu schreiben zu einem Fehler (Buffer full), so wird die Main so
lange versuchen, dieses Zeichen zu senden, bis es geklappt hat. Das
UDRE_Intrerrupt läuft ja "nebenher" und schaufelt den Ringpuffer wieder
leer, somit kommt es natürlich an dieser Stelle zu einem Flaschenhals
wenn sehr viel gesendet werden soll, aber der UART ist ja aufgrund
seiner Baudrate sowieso der Flaschenhals, und es interessiert mich
eigentlich nicht, ob die Pins weiter ausgelesen würden, wenn ich davon
eh noch nichts erfahre, darum darf der Controller an der Stelle ruhig
etwas im Stau stehen, das ganze kommt in der Regel sowieso nur nach der
Initialisierung vor, bis alle Anfangszustände ienmal gelesen sind.
Danach ändert sich nicht mehr so viel auf einmal.
Was ich halt nicht verstehe, ist daß er sich mit der leeren Anweisung
aufhängt, mit der Debuganweisung in der Block aber nicht...
Alexander v. Grafenstein schrieb:> Ob der> leere Block nur aus {} oder aus {;} oder sogar nur aus ; besteht sollte> eigentlich wurst sein. Ich kann mir nicht vorstellen, dass hier das> Problem liegt.
Nicht 'sollte wurst sein', sondern es ist einfach definitiv egal.
Wie ist NO_ERROR und BUFFER_FULL definiert?
Sind die Pointer, die von der ISR verändert werden als volatile
deklariert?
>Grundsätzlich würde ich deine Routine ein kleines bisschen umbauen.>Konkret: Ich finde es sinnvoller, wenn p_next_write nicht auf den>zuletzt beschriebenen Platz zeigt, sondern auf den nächsten zu>beschreibenden. Damit sparst du dir die ganze Geschichte mit p_check.>Also:>- ist p_next_write == p_end_write ? -> ja: dann Abbruch mit Buffer_full> -> nein: dann ist der Platz frei>- Daten an diesen Platz schreiben>- p_next_write erhöhen (mit Umbruch wie bisher für p_check)
Das hatte ich am Anfang auch so, da ergeben sich aber einige Probleme,
da ich z.B. nicht unterscheiden kann, wenn beide Zeiger auf einander
liegen, ob ich am Anfang eines leeren oder am Ende eines vollen
Speichers stehe. Habe da in der Peter Fleury Lib ein bisschen abgeguckt
;)
>Du schreibst, dass du UDR_empty zum verschicken benutzt. Was passiert,>wenn gerade keine Daten zu verschicken sind? Dann wird UDR_empty nicht>mehr aufgerufen (es wurde ja nichts verschickt) und es werden nie wieder>Daten verschickt, außer, du überträgst irgendwo nochmal was per Hand ->>deine Debug-routine? Bist du ganz sicher, dass die nicht aufgerufen>wurde?
Das ist nicht ganz richtig: Das interrupt "poppt" nicht auf, sondern ist
immer gesetzt, wenn der leer IST (nicht leer WIRD). Aktiviere ich das
Interrupt, so wird es durch den zu diesem Zeitpunkt leeren UDR auch
gleich ausgelöst. Senden stoppen tue ich per Interrupt disable. Das
ganze ist auch nicht das Problem, die Sende-Geschichte tut anstandslos,
sendet durchaus mal ne Stunde nicht und ist dann sofort wieder da wenn
an den Pins was passiert, da sollte das Problem nicht liegen. Das
Problem trat wirklich erst auf, wenn der Puffer mal wirklich voll läuft.
>p_end_write versteh ich nicht zu 100%. Ist das der Zeiger, dessen Daten>als nächstes vom UART übertragen werden sollen? Siehe dazu nächster>Absatz.
End Write markiert das Ende des Schreibe-Bereiches (Man könnte es auch
gleichsetzen mit dem Anfang des Lesebereiches, damit noch nicht
gesendete Zeichen nicht überschrieben werden) Ich habe mich hier für
bessere Übersicht (empfand ich so) und Flexibilität dazu entschieden,
Schreibebereich und Lesebereich "separat" zu verwalten. So wandert zb
der Schreibezeiger beim reinschreiben weiter, das Leseende bleibt aber
erst einmal stehen, bis der Frame freigegeben wird.
>Du schreibst, dass ein Frame erst komplett im Puffer liegen muss, bevor>er übertragen wird.>Das kann ich nirgends finden.
Das steht in
1
release_last_frame(buffer);
>Und dann frag ich mich noch, warum du>darauf wartest? Wenn Daten im Puffer liegen, dann würde ich die von der>Sende-Routine auch so schnell wie möglich übertragen. Ob das ein>kompletter Datensatz ist, wäre mir egal.
Das gibt mir die Möglichkeit, einen Frame bei dem ein Fehler aufgetreten
ist (mach ich z.B. beim Empfangen über den UART, die Funktionen sind für
beide Richtungen da) zu verwerfen, damit kein ungültiger Quatsch in die
Auswertung kommt, indem ich dann anstatt das Leseende weiter zu ziehen
einfach den Schreibezeiger zurück ziehe.
Aber es stimmt, ich könnte beim senden, da dort sowieso keine
Fehlerbehandlung stattfindet, nach jedem Zeichen die release_last_frame
aufrufen und jedes Zeichen sofort freigeben. Das macht das ganze asber
im Zweifel lediglich minimal schneller und sollte doch eigentlich nichts
mit meinem Problem zu tun haben oder?
Zu guter Letzt
>Ich würde nochmal ganz genau schaun, was passiert, wenn eine beliebige>dieser Anweisungen von der Sende-Routine unterbrochen wird. Ich kann mir>gut vorstellen, dass es hier Probleme geben könnte. Bedenke dabei, dass>das auch mitten in einer Zeile sein kann!
Sollte eigentlich auch kein Problem sein, das interrupt liest ja nur aus
dem Puffer, und zwar so lange, bis der Lesezeiger das Leseende erreicht
hat. Bevor das Leseende überhaupt neu geschrieben wird, ist alles andere
(Zeichen schreiben...) geschehen. Das Leseende ist ein Byte, daher
eigentlich ja auch interruptfest. Entweder ist das dann bereits
aktualisiert, oder eben noch nicht. Wenni ich hier jetzt Stuss erzähle
dann gebt bitte bescheid ;)
>Wie ist NO_ERROR und BUFFER_FULL definiert?
NO_ERROR 0
BUFFER_FULL irgendne Zahl non Zero
>Sind die Pointer, die von der ISR verändert werden als volatile>deklariert?
Logisch
Phillip Hommel schrieb:>>Sind die Pointer, die von der ISR verändert werden als volatile>>deklariert?> Logisch
Sehen wollen.
Wie oft schon haben wir in solchen Fällen so was gesehen:
Jetzt häng ich grade etwas. Ich übergebe doch sowieso die Adresse,
speichere also doch alles direkt an der jeweiligen Speicheradresse ab,
wozu dann volatile?
So, jetzt schwimm ich richtig. Was muss denn da nun alles volatile
deklariert werden? Ich verändere ja sowohl die Pointer, als am Ende auch
die Zeichen auf die die Pointer zeigen...
Ok, hast ja alles, was mir eingefallen ist, stichhaltig widerlegt :-P
Stefan Ernst schrieb:> Und in welchem Kontext wird put_string_to_ringbuffer aufgerufen? Dir ist> schon klar, dass wenn dort Interrupts gerade abgeschaltet sind (z.B. in> einer ISR), dass dann das "Warten per leere Anweisung" bei vollem Puffer> zur Endlosschleife wird, oder?
Das hast du aber noch nicht ausgeschlossen :-P
Wenn noch Daten im Puffer sind, und es wird ein neuer String
reingeschrieben, der aber nicht mehr reinpasst, wird dann während dessen
der alte Frame weiter übertragen? Ich nehm aber mal an, daran hast du
auch gedacht.
wegen volatile:
Phillip Hommel schrieb:> void put_string_to_ringbuffer(volatile struct t_buffer* buffer, uint8_t*
p_start)
hier sollte doch das volatile überflüssig sein. die adresse des buffers
ändert sich ja nicht plötzlich, besser gesagt nie.
Phillip Hommel schrieb:> uint8_t buffer = get_from_ringbuffer(&tx_buffer);
bist du dir sicher mit dem "&" ? tx_buffer müsste doch schon ein pointer
sein. Obwohl, der zeigt ja aufs erste Element oder? Puh!
Bei der Definition von t_buffer würde bei mir wahrscheinlich ein typedef
davorstehen. Aber bin auch schon ganz schön am schwimmen. Soweit ists
mit meinen C-Kenntnissen nicht her und ich weiß schon, warum ich lieber
in Assembler programmier :-P
Aber ehrlich gesagt seh ich genau 1 Stelle, wo du auf
tx_buffer/rx_buffer als globale Variable zugreifst. Das ist in der ISR
> uint8_t buffer = get_from_ringbuffer(&tx_buffer);
Hier sollte der Compiler ja nichts zum weg-optimieren haben. Ansonsten
übergibst du die Puffer-Adresse ja immer als Funktionsargument und
greifst dann auf den übergebenen Puffer zu.
Tja, ein munteres herum-gestocher ist das hier :(
Vllt. kannst du dein Programm einfach mal im Simulator laufen lassen.
Musst halt nach Programmstart den Puffer per Hand befüllen und die
entsprechenden Pointer eintragen. Geht das auch in C?
>Wenn noch Daten im Puffer sind, und es wird ein neuer String>reingeschrieben, der aber nicht mehr reinpasst, wird dann während dessen>der alte Frame weiter übertragen? Ich nehm aber mal an, daran hast du>auch gedacht
Klar, das ist ja die Idee bei der Sache. Darum soll das reinlegen eines
neuen (nicht mehr reinpassenden) Strings(bzw des nächsten Zeichens) so
lange warten, bis per ISR wieder Platz gemacht wurde.
>hier sollte doch das volatile überflüssig sein. die adresse des buffers>ändert sich ja nicht plötzlich, besser gesagt nie.
Da bin ich mir eben nicht mehr so ganz sicher. Denn der Pointer zeigt ja
auf immer neue Stellen des Arrays. Und wenn die UDRE ISR ein Zeichen
gesendet hat, dann ändert sie ja den Pointer auf das nächste Zeichen.
Allerdings wird ja auch das Zeichen selbst von der UDR geändert
(zumindest in der Empfangsrichtung, dann vom RXC ISR). Und wie man das
dann deklariert bin ich mir eben unsicher.
>Die Elemente des Structs müssen auch volatile sein, zumindest gab es>hier mal einen Thread wo das ein Problem war...
Ich werde es heute wohl nicht mehr probieren können, werde aber bald
möglichste berichten.
Hab das jetzt mal so gemacht, daß ich in der Strukturdefinition alle
Pointer Volatile deklariert habe, dann sollten die schnmal nicht mehr
der Opt. zum Opfer fallen...
Hast du das Programm mal ohne Optimierung übersetzt? Damit sollten ja
Fehler, die von fehlenden volatiles herrühren, erst mal nicht
zuschlagen. Wenns also ohne Optimierung nicht läuft, ist woanders noch
was falsch.
>Hast du das Programm mal ohne Optimierung übersetzt? Damit sollten ja>Fehler, die von fehlenden volatiles herrühren, erst mal nicht>zuschlagen. Wenns also ohne Optimierung nicht läuft, ist woanders noch>was falsch.
Daran habe ich noch gar nicht gedacht, ist das soweit zu empfehlen? Hab
ich noch nie versucht, danke für den Tipp!
Es ist zu empfehlen, um zu prüfen, ob die volatiles der Fehler sind. Das
Abschalten der Optimierung ist natürlich nicht als alternative zu
korrekten volatiles gedacht ;)
SO, habs getestet und: Es funktioniert! Lag tatsächlich am vergessenen
Volatile. Was bei genauerem Hinsehen ja auch total Sinn macht: Das
Abbruchkriterium der while-Schleife kann sich ja aus Sicht des Compilers
nicht ändern (da er ja nichts von Interrupts etc weiß sondern nud
straight-forward "denkt") während diese ausgeführt wird, darum wird
diese Abfrage Laufzeitoptimiert, aus
1
while(funktion(a,b)==0)//funktion wird nach jedem Schleifendurchlauf ausgeführt
2
{
3
}
wird
1
intc=funktion(a,b)//funktion wird einmal vor der Schleife ausgeführt
2
3
while(c==0)//Leider ändert sich c nie wieder und schon hängt das ganze
4
{
5
}
Und schon knallts. Da sich aber die Funktion bei mir durch das interrupt
eben doch in der Zeit ändern kann, müssen eben die entsprechende
Variablen als volatile deklariert werden, die dem Compiler diese
Optimierung dann verbieten.
Soviel der Erklärung mal für alle, die mit dem selben Problem über
diesen Beitrag stolpern.
Nochmals vielen Dank an alle!