Liebe Kollegen,
vielleicht hat jemand eine Erklärung für folgendes Phänomen:
ATMEGA1284. Die USART0 läuft mit 115KBaud. Der PC sendet einen String,
der Controller sucht die Daten zusammen und antwortet. Fehlerfrei
getestet 1000x hintereinander ohne Pause (auf das write folgt gleich ein
read).
Nun beschäftige ich den Controller noch mit Analogwerte abfragen, SPI
Display, Drehencoder (Timer), Tasten, LEDs.
Nun gibt es plötzlich sporadische Empfangsfehler am Beginn einer
eintreffenden Nachricht. Nur wenn ich beim PC zwischen empfangen und
erneut an MC senden 1ms warte, bin ich fehlerfrei (auf 10.000x
getestet). Die 1ms sind empirisch ermittelt, in 0,1ms Schritten
aufwärts.
Was ich nicht verstehe: Der Interrupt ISR(USART0_RX_vect) wird
aufgerufen, wenn ein Zeichen empfangen wird. Was verlangsamt den
Interrupt so, dass er nicht mehr zuverlässig die Zeichen abholt. Wo ist
der Unterschied, ob der Controller in der while(1) Schleife hängt, oder
sonstiges ausführt?
Liebe Grüße in die Runde
Chris B. schrieb:> Die 1ms sind empirisch ermittelt, in 0,1ms Schritten aufwärts.
Hast du das Timing per LA auf der seriellen Schnittstelle des uC
überprüft?
> Wo ist der Unterschied, ob der Controller in der while(1) Schleife> hängt, oder sonstiges ausführt?
Ich tippe auf ein Problem in Zeile 42.
Chris B. schrieb:> Liebe Grüße in die Runde
Ich fasse zusammen: dein Problem liegt in "Sonstiges" oder
in Zeile 42.
Chris B. schrieb:> Was verlangsamt den> Interrupt so, dass er nicht mehr zuverlässig die Zeichen abholt.
Ein Interrupt wird nicht verlangsamt. Entweder kommt ein Interrupt
durch oder nicht. Wenn er "nicht durchkommt" dann ist ein AVR wohl
gerade mit einem (anderen?) Interrupt beschäftigt, und dessen
Abarbeitung dauert zu lange dass er weitere Interrupt-Ereignisse
rechtzeitig abarbeiten kann. Dann können Daten verloren gehen,
z.B. durch UART Receive Register Overflow.
Alles Vermutungen über Probleme die in deinem geheimen Programm
versteckt sind.
Chris B. schrieb:> Was ich nicht verstehe: Der Interrupt ISR(USART0_RX_vect) wird> aufgerufen, wenn ein Zeichen empfangen wird. Was verlangsamt den> Interrupt so, dass er nicht mehr zuverlässig die Zeichen abholt. Wo ist> der Unterschied, ob der Controller in der while(1) Schleife hängt, oder> sonstiges ausführt?
Schläft der Mikrocontroller vielleicht? zum Aufwecken mal den
Pinchange-Interrupt für den RX-Pin aktivieren und schauen ob das hilft
(man muss in der ISR dafür ja nix tun)
M. K. schrieb:> Schläft der Mikrocontroller vielleicht?
Das geht schief.
Ein Quarz braucht mehrere ms zum anschwingen, daher auch die 16K CK zum
Aufwachen.
Chris B. schrieb:> Der Interrupt ISR(USART0_RX_vect) wird> aufgerufen, wenn ein Zeichen empfangen wird.
Nur kann er max 3 Zeichen puffern. Richte doch einen FIFO ein für eine
komplette Nachricht. Ich nehme typisch je 128 Byte für RX und TX.
Peter D. schrieb:>> Schläft der Mikrocontroller vielleicht?>> Das geht schief.> Ein Quarz braucht mehrere ms zum anschwingen, daher auch die 16K CK zum> Aufwachen.
Jain. Es gibt zumindest beim AVR den Sleep Mode Stand By, wo der
Hauptoszillator weiter läuft und damit sofort verfügbar ist.
Chris B. schrieb:> SPI Display,
Ist es ein grafisches Display, bei dem die Pixel einzeln angesteuert
werden können? Benutzt du zudem größere Zeichen? Denn dabei sind selbst
die einzelnen SPI-Übertragungen recht voluminös und können mehrere
Millisekunden dauern. Und ich meine mich zu erinnern, dass zumindest die
SPI-Bibliothek des Arduino während des ganzen Schreibvorgangs die
Interrupts sperrt ...
LG, Sebastian
M. K. schrieb:> Schläft der Mikrocontroller vielleicht? zum Aufwecken mal den
Danke für den Ansatz, aber der Controller schläft nicht. Das Phänomen
taucht auch erst bei Auslastung auf.
Peter D. schrieb:> Chris B. schrieb:>> Der Interrupt ISR(USART0_RX_vect) wird>> aufgerufen, wenn ein Zeichen empfangen wird.>> Nur kann er max 3 Zeichen puffern. Richte doch einen FIFO ein für eine> komplette Nachricht. Ich nehme typisch je 128 Byte für RX und TX.
Ich schreib jedes Zeichen sofort weg, dass in UDR steht, nachdem der
Interrupt ausgelöst wurde.
1
ISR(USART0_RX_vect)
2
{
3
unsignedcharusart_rx=UDR0;// ankommendes Zeichen zur Weiterverarbeitung sichern.
Chris B. schrieb:> Nun gibt es plötzlich sporadische Empfangsfehler am Beginn einer> eintreffenden Nachricht. Nur wenn ich beim PC zwischen empfangen und> erneut an MC senden 1ms warte, bin ich fehlerfrei
Das heisst, der Empfang der Nachricht funktioniert mit Sendepause auch
bei voller Auslastung, ohne die Pause wird sozusagen der Anfang
verpasst?
Oliver
Sebastian W. schrieb:> Und ich meine mich zu erinnern, dass zumindest die> SPI-Bibliothek des Arduino während des ganzen Schreibvorgangs die> Interrupts sperrt ...
Habs nachgeschlagen, stimmt so nicht, sorry. Die Arduino SPI-Bibliothek
verhindert die fehlerhafte Verschränkung von SPI-Kommunikation mit
unterschiedlicher Peripherie, aber SPI und UART sollten sich gegenseitig
nicht die Interrupts sperren.
LG, Sebastian
Sebastian W. schrieb:> Ist es ein grafisches Display, bei dem die Pixel einzeln angesteuert> werden können? Benutzt du zudem größere Zeichen? Denn dabei sind selbst> die einzelnen SPI-Übertragungen recht voluminös und können mehrere> Millisekunden dauern. Und ich meine mich zu erinnern, dass zumindest die> SPI-Bibliothek des Arduino während des ganzen Schreibvorgangs die> Interrupts sperrt ...
Servus Sebastian,
ja, ein EADOGL, das hatte ich auch im Verdacht. Aber da wird kein
Interrupt gesperrt. Die Übertragung hängt nur in der while Schleife. Das
sollte den UART Interrupt nicht hindern, oder?
1
voidSPI_versenden(charSPI_Daten)
2
{
3
PORTB&=~(1<<Display_CS);// Display ansprechen.
4
SPDR=SPI_Daten;// Starte Übertragung.
5
while(!(SPSR&(1<<SPIF)));// Warte bis Übertragung fertig ist.
6
PORTB|=(1<<Display_CS);// Display wieder vom Bus nehmen.
Oliver S. schrieb:> Chris B. schrieb:>> Nun gibt es plötzlich sporadische Empfangsfehler am Beginn einer>> eintreffenden Nachricht. Nur wenn ich beim PC zwischen empfangen und>> erneut an MC senden 1ms warte, bin ich fehlerfrei>> Das heisst, der Empfang der Nachricht funktioniert mit Sendepause auch> bei voller Auslastung, ohne die Pause wird sozusagen der Anfang> verpasst?
Servus Oliver,
yepp - genau so isses! Nachdem ich vom MC die Daten empfangen habe, muss
ich die ms warten, bis ich ihm was neues schicken kann, sonst
verkrüppelt er die ersten Zeichen.
Ist der MC nicht ausgelastet (läuft leer in der main), brauch ich diese
Pause nicht.
Chris B. schrieb:> Der Interrupt ISR(USART0_RX_vect) wird aufgerufen, wenn ein Zeichen> empfangen wird.
... und Interrupts gnerell erlaubt sind und sonst kein Interrupt in
Bearbeitung ist.
Du kannst wie erwähnt übrigens einfach das UART Overflow-Flag abfragen,
das anzeigt, ob der Puffer überschrieben wurde.
Chris B. schrieb:> Das Phänomen taucht auch erst bei Auslastung auf.
Kannst du das Problem auch mit einem "Dreizeiler" nachvollziehen?
Oder andersrum: deaktiviere alle Komponenten nacheinander, bis der
Fehler weg ist. Dann nimm wieder alle Komponenten bis auf die zuletzt
deaktivierte in Betrieb. Wenn der Fahler dann weg ist, dann kannst du
dort die Ursache suchen.
S. L. schrieb:> Wie sieht die UART-Senderoutine aus?
Ich stelle mir einen String zusammen, den ich dann so abschicke. Beim
Senden hat er aber keine Probleme.
1
intUART0_Zeichen_senden(unsignedcharZeichen)
2
{
3
while(!(UCSR0A&(1<<UDRE0)))// warten bis Senden möglich ist
Chris B. schrieb:> _delay_ms(1);
Das wäre für mich ein Programmfehler, denn das ist mindestens aktive
Rechenzeitverschwendung.
Und mich würde nicht wenig wundern, wenn das die Ursache für das hiesige
Problem ist. Besonders, wenn diese Sendereoutine dann z.B. vom
Empfangsinterrupt aus aufgerufen wird...
Lothar M. schrieb:>> Der Interrupt ISR(USART0_RX_vect) wird aufgerufen, wenn ein Zeichen>> empfangen wird.> ... und Interrupts gnerell erlaubt sind und sonst kein Interrupt in> Bearbeitung ist.
Das war mein erster Gedanke :-) Es gibt eine Dreh-Encoder Routine die
den Interrupt kurzzeitig sperrt und wieder freigibt cli() sei(), die
hatte ich aber schon ausgeklammert, ohne Effekt.
> Du kannst wie erwähnt übrigens einfach das UART Overflow-Flag abfragen,> das anzeigt, ob der Puffer überschrieben wurde.
Du meinst das DORn Data OverRun Bit im UCSRnA Register? Das hatte ich
bis jetzt nicht auf dem Schirm. Probier ich aus. Danke.
>> Das Phänomen taucht auch erst bei Auslastung auf.> Kannst du das Problem auch mit einem "Dreizeiler" nachvollziehen?
Nein, weil ...
> Oder andersrum: deaktiviere alle Komponenten nacheinander, bis der> Fehler weg ist. Dann nimm wieder alle Komponenten bis auf die zuletzt> deaktivierte in Betrieb. Wenn der Fahler dann weg ist, dann kannst du> dort die Ursache suchen.
... ich natürlich genau so auf die Fehlersuche gegangen bin. Ich hab
mich jetzt wirklich viele Tage schon damit beschäftigt und einige Fallen
aufgebaut, aber es stellt sich nicht so trivial dar - sonst hätt ich
nicht das Forum bemüht ;-)
Hab zuerst alles bis auf die Schnittstelle deaktiviert. Läuft.
Dann den Drehencoder zugeschaltet, weil ich den wegen dem Interrupt
sperren im Verdacht hatte. Läuft (1000 Durchläufe). Wieder deaktiviert.
Display aktiviert. Läuft. Wieder deaktiviert. (Hatte ich im Verdacht
wegen dem SchadowRAM und viel Datenverkehr auf dem SPI Bus).
ADC für 2 Joysticks aktiviert. Läuft. Wieder deaktiviert. (Hatte ich
wegen 3 fachen Oversampling aller 8 Kanäle in Verdacht).
Timer für LED blinken und Tastenabfragen aktiviert. Läuft. Wieder
deaktiviert.
Jetzt kommt's. Nehm ich nun mehrere Komponenten, auch in verschiedenen
Kombinationen dazu, verschluckt sich der UART Empfang - aber nur
manchmal. Manchmal nach 2, manchmal nach 30, manchmal nach 70, manchmal
nach 200 Protokollen. Füge ich dann 1ms warten am PC ein, nachdem ich
vom MC empfangen habe und wieder was schicke, läuft es 10.000x
fehlerfrei durch.
Bin in einer gedanklichen Sackgasse.
Chris B. schrieb:> Jetzt kommt's. Nehm ich nun mehrere Komponenten, auch in verschiedenen> Kombinationen dazu, verschluckt sich der UART Empfang - aber nur> manchmal. Manchmal nach 2, manchmal nach 30, manchmal nach 70, manchmal> nach 200 Protokollen. Füge ich dann 1ms warten am PC ein, nachdem ich> vom MC empfangen habe und wieder was schicke, läuft es 10.000x> fehlerfrei durch.>> Bin in einer gedanklichen Sackgasse.
Deine Empfangsroutine sowie das Konzept sind nicht robust, sondern
funktionieren nur, wenn alles PERFEKT läuft. Zeig uns deinen kompletten
Quelltext als ZIP-Archiv im Anhang.
Chris B. schrieb:> Nehm ich nun mehrere Komponenten, auch in verschiedenen Kombinationen dazu
Manchmal macht die Menge das Gift... ;-)
Setz doch mal einen Toggle auf 1 Pin am anfang der Mainloop und lösche
ihn am Ende und schau mit dem Oszi, ob du da dann Ausreißer in der
Zykluszeit siehst. Und ob die irgendwie mit dem Fehler korrelieren.
>> _delay_ms(1);> Das wäre für mich ein Programmfehler, denn das ist mindestens aktive> Rechenzeitverschwendung.>> Und mich würde nicht wenig wundern, wenn das die Ursache für das hiesige> Problem ist. Besonders, wenn diese Sendereoutine dann z.B. vom> Empfangsinterrupt aus aufgerufen wird...
Nehm ich sofort raus. So kommt's bei 2500 Programmzeilen. Ich les da
selber irgendwann drüber, bzw. such an falscher Stelle. Ich arbeite da
schon so viele Wochen dran. Danke für den Hinweis. Melde mich gleich
wieder nach der Änderung ...
Chris B. schrieb:> Bin in einer gedanklichen Sackgasse.
Hast du einen Logikanalysator? Dann in jeder Interruptroutine und bei
jedem Interrupt-Sperrungsbereich am Anfang und am Ende mit einem
Ausgangspin wackeln. Und dann auf das Auftauchen des Phänomens warten.
Bei mir war es oft so, dass ich beim Einbau des Pingewackels dann schon
verstanden habe was das Problem war :)
LG, Sebastian
Wastl schrieb:> Ein Interrupt wird nicht verlangsamt. Entweder kommt ein Interrupt> durch oder nicht.
Ein Interrupt wird genauso schnell abgearbeitet, wie der Rest des Codes
- da hast du Recht.
Es kann aber durchaus sein, dass der µC gerade mit einem anderer
Interrupt höherer Priorität beschäftigt ist und bei einem neu
auflaufenden Interrupt der Sprung in die ISR erst mit einer Verzögerung
erfolgt - je nach dem, wie das Programm designt ist.
Peter D. schrieb:> M. K. schrieb:>> Schläft der Mikrocontroller vielleicht?>> Das geht schief.> Ein Quarz braucht mehrere ms zum anschwingen, daher auch die 16K CK zum> Aufwachen.
Das kommt auf den Sleep-Mode an, es gibt Sleep-Modes, da läuft der Oszi
weiter, z.B. Extended Standby.
Aber da der Controller ja nicht schläft ist das Thema ja eh gegessen.
S. L. schrieb:> Ich möchte nochmals auf die Prioritäten-Reihenfolge hinweisen.Rainer W. schrieb:> Es kann aber durchaus sein, dass der µC gerade mit einem anderer> Interrupt höherer Priorität beschäftigt ist und bei einem neu> auflaufenden Interrupt der Sprung in die ISR erst mit einer Verzögerung> erfolgt
Es geht hier um einen Atmega1284. Der hat keine echten
Interrupt-Prioritäten. Es kann also durchaus auch sein, dass ein
Interrupt "höherer" Priorität erst verzögert aufgerufen wird, weil
gerade noch ein interrupt "niedrigerer" Priorität abgearbeitet wird. Nur
wenn die Entscheidung zur Abarbeitung zwischen mehreren Interrupts
getroffen werden muss, werden diejenigen höherer Priorität bevorzugt.
S.L., Rainer, ich denke euch ist das bekannt. Ich wollte das nur für den
TO klarstellen.
LG, Sebastian
> ... diejenigen höherer Priorität bevorzugt.
Eben.
Es ist folglich höchst einfach (auch aus Versehen), eine SPI-ISR zu
schreiben, welche einen Datenblock ausgibt, und damit z.B. den
USART-Interrupt blockiert, solange diese Ausgabe läuft.
Ich würde SPI auch nicht als Interrupt schreiben, sondern das Ready-Bit
pollen. Bei F_CPU /2 oder /4 ist der Overhead zu groß, für die ganze
Springerei und Registerrettung.
Servus,
mir ist noch nicht ganz klar,
A) welche Interrupts wirklich genutzt werden (ist wirklich nur der
UART-RX-Interrupt aktiv?)
B) ob es (unbewusst) Code gibt, welcher Interrupts kurz stoppt und
wieder aktiviert
C) Ob es nicht vielleicht eine "Zugriffskollision" auf
RS485_Empfang_Buffer, RS485_Empfang_Buffer_Position oder
RS485_Empfang_komplett gibt
zu A)
bitte nochmal klar schreiben
zu B)
Es gibt durchaus Makros, die mal kurz die Interrupts abschalten.
Vielleicht mal im List-File nach sei() und cli() suchen
zu C) Wenn diese Variablen auch außerhalb des Interrupts verwendet
werden, muß man tierisch aufpassen, daß diese "in sich konsistent" sind.
Die einfache Variante ist, die Interrupts beim Zugriff zu sperren, ist
aber gar nicht schön. Bessere Möglichkeiten sind ohne Code Kenntnis
leider nicht pauschal nennbar. Welche Datengöße hat denn z.B.
RS485_Empfang_Buffer_Position?
Gruß
Damit baust Du Dir eine schöne Race Condition. Wenn nach dem LF weitere
Zeichen kommen, bevor der gefüllte Buffer verarbeitet wurde, wird der
Anfang des Buffers mit den neu empfangenen Zeichen überschrieben.
Wenn Du damit leben kannst, dass das neu eingegangene Zeichen dann
einfach verworfen wird, reicht sowas hinter der ersten Zeile der
Funktion:
1
if(RS485_Empfang_komplett)
2
return;
RS485_Empfang_komplett muss natürlich volatile sein und darf in der Main
Loop erst dann auf 0 gesetzt werden, wenn die Verarbeitung beendet
wurde.
Hmmm schrieb:> Damit baust Du Dir eine schöne Race Condition. Wenn nach dem LF weitere> Zeichen kommen, bevor der gefüllte Buffer verarbeitet wurde, wird der> Anfang des Buffers mit den neu empfangenen Zeichen überschrieben.Chris B. schrieb:> yepp - genau so isses! Nachdem ich vom MC die Daten empfangen habe, muss> ich die ms warten, bis ich ihm was neues schicken kann, sonst> verkrüppelt er die ersten Zeichen.
Hier muss man noch einmal nachfragen, kommt etwas verkrüppelt heraus,
oder werden die ersten x Zeichen verschluckt?
Z.B., du schickst den String ABCDEFGHIJ. Kommt es an als #*CDEFGHIJ
(nach wie vor 10 Zeichen) , oder nur CDEFGHIJ (8 Zeichen, weil die
ersten 2 verschluckt)?
Hmmm schrieb:> Damit baust Du Dir eine schöne Race Condition.
Mit einem FIFO kann man das Ganze entschärfen. Das Main holt sich die
Zeichen aus dem FIFO, und parst sie in aller Ruhe, wenn es Zeit hat. Der
FIFO liest derweil schon die nächsten Zeichen ein.
Peter D. schrieb:> Mit einem FIFO kann man das Ganze entschärfen. Das Main holt sich die> Zeichen aus dem FIFO, und parst sie in aller Ruhe, wenn es Zeit hat. Der> FIFO liest derweil schon die nächsten Zeichen ein.
In der Tat. Peter Fleury hat da was Fertiges, was tausendfach genutzt
wird.
Die Forensoftware meint, der Link dorthin enthielte eine SPAM-Adresse.
Den _ entfernen, dann sollte es gehen
http://www.peterfleury.epi_zy.com/avr-software.html?i=1
Lothar M. schrieb:> Chris B. schrieb:>> _delay_ms(1);> Das wäre für mich ein Programmfehler, denn das ist mindestens aktive> Rechenzeitverschwendung.>> Und mich würde nicht wenig wundern, wenn das die Ursache für das hiesige> Problem ist. Besonders, wenn diese Sendereoutine dann z.B. vom> Empfangsinterrupt aus aufgerufen wird...
Dann hab ich wohl mehrere Probleme, denn wenn ich die 1ms rausnehme,
wird gar nichts gesendet. Das ist mit SingleStep schlecht zu debuggen,
da ich nicht sehen kann, warum es ohne Verzögerung nicht geht.
Robert G. schrieb:
Servus Robert,
> mir ist noch nicht ganz klar,> A) welche Interrupts wirklich genutzt werden (ist wirklich nur der> UART-RX-Interrupt aktiv?)
Es gibt noch den ISR(TIMER0_COMPA_vect) und beim Abfragen vom
Drehencoder werden für vier Befehle die Interrupts deaktiviert. Aber
diese Routinen, auch der Timer Interrupt verursachen nicht das Problem.
Hatte ich schon ausgeklammert.
> B) ob es (unbewusst) Code gibt, welcher Interrupts kurz stoppt und> wieder aktiviert
Ich kenn nur diesen bewußten :-)
> C) Ob es nicht vielleicht eine "Zugriffskollision" auf> RS485_Empfang_Buffer, RS485_Empfang_Buffer_Position oder> RS485_Empfang_komplett gibt
Wird alles sequenziell abgearbeitet. Dann würde es ja gar nicht richtig
funktionieren, bzw. auch zwischendurch einmal Fehler auftauchen. Geht
aber mit leerer Main Schleife fehlerfrei.
> zu B)> Es gibt durchaus Makros, die mal kurz die Interrupts abschalten.> Vielleicht mal im List-File nach sei() und cli() suchen
Kommt im Code nur einmal vor.
> leider nicht pauschal nennbar. Welche Datengöße hat denn z.B.> RS485_Empfang_Buffer_Position?
uint8_t und der Buffer 256 char. Aber wie gesagt, das klappt fehlerfrei.
Ozvald K. schrieb:> Hier muss man noch einmal nachfragen, kommt etwas verkrüppelt heraus,> oder werden die ersten x Zeichen verschluckt?>> Z.B., du schickst den String ABCDEFGHIJ. Kommt es an als #*CDEFGHIJ> (nach wie vor 10 Zeichen) , oder nur CDEFGHIJ (8 Zeichen, weil die> ersten 2 verschluckt)?
Verkrüppelt.
>> Damit baust Du Dir eine schöne Race Condition. Wenn nach dem LF weitere> Zeichen kommen, bevor der gefüllte Buffer verarbeitet wurde, wird der> Anfang des Buffers mit den neu empfangenen Zeichen überschrieben.
Nicht hier gepostet: Wenn der Empfang fertig ist '\n' erhalten, schalte
ich den RS485 Baustein schon auf Senden. Dann erst verarbeite ich den
Buffer. Somit kann hinterher nichts mehr reinkommen.
> Wenn Du damit leben kannst, dass das neu eingegangene Zeichen dann> einfach verworfen wird, reicht sowas hinter der ersten Zeile der
Ich arbeite mit nur einem Master am PC. Er fragt Daten an, bekommt etwas
geschickt und fragt erst dann wieder, wenn er eine Antwort erhalten hat
(oder eben Timeout). Somit kommt es nicht vor, dass noch etwas
nachkommen kann.
S. L. schrieb:> Welche Interrupts sind eigentlich freigegeben?> Ich möchte nochmals auf die Prioritäten-Reihenfolge hinweisen.
Timer0. Hatte ich zum Testen aber deaktivert und er hat das Problem
nicht verursacht.
Falk B. schrieb:
Hallo Falk,
> Deine Empfangsroutine sowie das Konzept sind nicht robust, sondern> funktionieren nur, wenn alles PERFEKT läuft. Zeig uns deinen kompletten> Quelltext als ZIP-Archiv im Anhang.
Aha. Da kann ich nur lernen. Code ist anbei.
Vielen Dank an dieser Stelle für die vielen Meldungen und Ratschläge bis
jetzt. Das hilft mir sehr!
Chris B. schrieb:> Wenn der Empfang fertig ist '\n' erhalten, schalte> ich den RS485 Baustein schon auf Senden.
Warum sagst Du nicht gleich, daß es um RS485 geht?
Da hätte man sich das ganze bisherige Gesülze sparen können.
RS485 braucht Zeit zum Umschalten der Richtung.
Auf der AVR-Seite kann man die Umschaltung mit dem TXCn: USART Transmit
Complete Bit machen. Das sollte Dein Problem lösen.
Ein paar Posts habe ich uebersprungen.
Es sieht nicht nach einem Protokoll aus. Ein Protokoll wird waehrend des
Empfangs schon im Interrupt dekodiert. Der Interrupt gibt dann nach
vollstaendig decodierter Nachricht ein Flag an das Main, welches die
Verarbeitung uebernimmt.
Dann sollte ein Protokoll eine Antwort zurueck senden, sodass der Sender
wiss, dass etwas schief ging.
zB Der Controller sendet die Antwort auf eine verstandene und
bearbeitete Nachricht. Falls der Controller nichts verstand sendet er
nichts. Der Sender erkennt an einem Timeout, dass der letzte Befehl
nicht verarbeitet wurde. Und sendet ihn nochmals. Das Bedeutet dann
nebenbei, jeder Befehl wird beantwortet, auch wenn der Sender nichts
benoetigt.
Peter D. schrieb:> Warum sagst Du nicht gleich, daß es um RS485 geht?> Da hätte man sich das ganze bisherige Gesülze sparen können.>> RS485 braucht Zeit zum Umschalten der Richtung.> Auf der AVR-Seite kann man die Umschaltung mit dem TXCn: USART Transmit> Complete Bit machen. Das sollte Dein Problem lösen.
Das tut mir jetzt sehr leid, dass meine Fehlersuche zum Gesülze wurde.
Würde ich wissen, wo der Fehler liegt, hätt ich keinen mehr. Ich hab die
4ms Umschaltzeit vom Datenblatt des Bausteins in der Umschaltroutine von
lesen zu schreiben eingebaut. Nachdem ich ohne Probleme Daten empfangen
und senden konnte, war der Punkt für mich keine Fehlerquelle.
Ich werde mich gleich zum Thema TXCn einlesen, hab ich noch nie benutzt.
Vielen Dank für Hilfe.
Chris B. schrieb:> Ich hab die 4ms Umschaltzeit vom Datenblatt des Bausteins in der> Umschaltroutine von lesen zu schreiben eingebaut.
Wieder mal als delay() aka. "Programmfehler"...
Der Kommentar da drin gibt mir sehr zu denken:
1
// Schnittstelle gleich auf Senden umschalten, damit
2
// während der Verarbeitung kein neues Zeichen mehr angenommen wird.
Bist du sicher, dass das auch genau so funktioniert, wie du es dir
wünschst? Denn damit schaltest du zwar die Richtung des Bustreibers um,
aber nicht den RX-Teils des µC inaktiv.
1
charRS485_Empfang_Buffer[255];
2
uint8_tRS485_Empfang_Buffer_Position=0;
3
:
4
:
5
RS485_Empfang_Buffer_Position++;
Hochzählen ohne Kontrolle, ob der Buffer inzwischen mal übergelaufen
ist, das führt hier zum Bufferoverflow. Denn das höchste Element im
RS485_Empfang_Buffer ist RS485_Empfang_Buffer[254], die
RS485_Empfang_Buffer_Position kann aber 255 werden, vor sie auf 0
überläuft. Die Buffergröße müsste 256 sein, damit auch mit einem uint8_t
kein Speicherüberlauf stattfinden kann.
> Ich hab die 4ms Umschaltzeit vom Datenblatt des Bausteins in der> Umschaltroutine von lesen zu schreiben eingebaut.
Und andersrum braucht der geheime "Baustein" keine Zeit?
Das TxComplete .. ja ... dieser Interrupt kommt, wenn das letzte
Datenbit rausgeschoben wurde. Kann man verwenden und dabei das Stoppbit
abschneiden, welches auch noch aus dem Uart rausgeschoben wird.
Fuer etwas Sonderaufwand kann man fuer das Stoppbit auch einen Timer
laufen lassen, oder es einfach sein lassen. Was geschieht wenn das
Stoppbit abgeschnitten wird ? Falls da ein uebereifriges anderes UART
schon mit dem Startbit beginnt ?
Hint ..
welches sind die Ruhepegel des Busses ?
Was geschieht mit den empfangenden Uarts, welche noch am Reinsampeln
sind ?
Bessere Uarts kennen Framing Errors, welche auf ein fehlendes Start-,
Stopp-, Parity Bit hinweisen.
Purzel H. meinte:
> Das TxComplete .. ja ... dieser Interrupt kommt, wenn das> letzte Datenbit rausgeschoben wurde. Kann man verwenden> und dabei das Stoppbit abschneiden
Nein - 'complete' heißt genau das, und beinhaltet das(die) Stoppbit(s).
Lothar M. schrieb:
Hallo Lothar,
vielen Dank für Deine Geduld!
>> Ich hab die 4ms Umschaltzeit vom Datenblatt des Bausteins in der>> Umschaltroutine von lesen zu schreiben eingebaut.> Wieder mal als delay() aka. "Programmfehler"...
Sollte ich die 4ms über einen Timer realisieren? Mir kam das an dieser
Stelle nicht zeitkritisch vor.
> Der Kommentar da drin gibt mir sehr zu denken:>
1
> // Schnittstelle gleich auf Senden umschalten, damit //
2
> während der Verarbeitung kein neues Zeichen mehr angenommen wird.
3
>
> Bist du sicher, dass das auch genau so funktioniert, wie du es dir> wünschst? Denn damit schaltest du zwar die Richtung des Bustreibers um,> aber nicht den RX-Teils des µC inaktiv.
Aber der RX-Teil vom µC kann ja nichts mehr empfangen, wenn er
physikalisch vom MAX485 getrennt wurde.
>
1
>charRS485_Empfang_Buffer[255];
2
>uint8_tRS485_Empfang_Buffer_Position=0;
3
>:
4
>:
5
>RS485_Empfang_Buffer_Position++;
6
>
> Hochzählen ohne Kontrolle, ob der Buffer inzwischen mal übergelaufen> ist, das führt hier zum Bufferoverflow. Denn das höchste Element im> RS485_Empfang_Buffer ist RS485_Empfang_Buffer[254], die> RS485_Empfang_Buffer_Position kann aber 255 werden, vor sie auf 0> überläuft. Die Buffergröße müsste 256 sein, damit auch mit einem uint8_t> kein Speicherüberlauf stattfinden kann.
Nachdem die gesendeten Befehle so um die 30 Bytes liegen und ich den
Buffer nach dem Auswerten immer auf den Anfang setze hab ich keine
Überprüfung eingebaut. Du hast natürlich recht, wenn einmal das
Endzeichen nicht kommt, kann es zu einem Überlauf kommen. Werde das
ändern und abfangen.
>> Ich hab die 4ms Umschaltzeit vom Datenblatt des Bausteins in der>> Umschaltroutine von lesen zu schreiben eingebaut.> Und andersrum braucht der geheime "Baustein" keine Zeit?
Beim geheimen MAX485 Baustein hab ich im Datenbaltt beim Punkt "Receiver
Input to Output" in den Switching Characteristics eine Zeit gefunden,
aber keine Zeit für die Umschaltung Output to Input. Also bin ich davon
ausgegangen, dass der nur für diese eine Umschaltung mehr Zeit benötigt.
Wobei: Ich hab grad nochmal nachgesehen. Da fand ich jetzt 200ns
maximal. Ich weiß nicht mehr, wie ich auf die ms gekommen bin. Hab das
delay nun entfernt und es läuft trotzdem. Sorry, ich weiß nicht mehr,
warum ich das damals eingefügt hatte. Ich würde jetzt einen unschuldigen
Schulterzuck Smiley verwenden. Wird wohl so sein, dass ich das TXC nicht
verwendet hatte und mir so unwissend beholfen habe.
Chris B. schrieb:> Mir kam das an dieser Stelle nicht zeitkritisch vor.
Es vergeudet anderweitig verwendbare Rechenzeit.
> Sollte ich die 4ms über einen Timer realisieren?
Nimm einen Timertick (den hat man ja sowieso) und realisiere
Verzögerungen über Zeitdifferenzen. Stichwort: millis() beim Arduino.
Chris B. schrieb:> Aber der RX-Teil vom µC kann ja nichts mehr empfangen, wenn er> physikalisch vom MAX485 getrennt wurde.
Steht das so in der Beschreibung deines Transceivers? Welchen Pegel
nimmt die RX-Leitung des µC im inaktiven Zustand ein? Wird der
Empfängerausgang evtl. hochohmig und der µC kann sich dann selber
irgendwelche EMV-Bits aus der Luft herzaubern?
Lothar M. schrieb:>> Aber der RX-Teil vom µC kann ja nichts mehr empfangen, wenn er>> physikalisch vom MAX485 getrennt wurde.> Steht das so in der Beschreibung deines Transceivers? Welchen Pegel> nimmt die RX-Leitung des µC im inaktiven Zustand ein? Wird der> Empfängerausgang evtl. hochohmig und der µC kann sich dann selber> irgendwelche EMV-Bits aus der Luft herzaubern?
Ok, ok - ich hab jetzt das Empfangen während der Auswertung über RXCIE
abgeschaltet :-)
Chris B. schrieb:> Ok, ok - ich hab jetzt das Empfangen während der Auswertung über RXCIE> abgeschaltet :-)
Kann man machen, ist aber eher eine Holzhammermethode.
Falk B. schrieb:>> Ok, ok - ich hab jetzt das Empfangen während der Auswertung über RXCIE>> abgeschaltet :-)> Kann man machen, ist aber eher eine Holzhammermethode.
Ich lern doch so gerne - wie sieht denn die technisch eleganteste Lösung
dazu aus?
Chris B. schrieb:> Ich lern doch so gerne - wie sieht denn die technisch eleganteste Lösung> dazu aus?
Sorge auch im inaktiven Zustand für einen definerten Pegel am RX-Eingang
(z.B. Pullup) und dann gestalte die Pufferverwaltung so robust, dass sie
auch mit ein paar korrupten Bytes nicht ins Schleudern kommt (z.B.
Checksum).
Lothar M. schrieb:> Sorge auch im inaktiven Zustand für einen definerten Pegel am RX-Eingang> (z.B. Pullup) und dann gestalte die Pufferverwaltung so robust, dass sie> auch mit ein paar korrupten Bytes nicht ins Schleudern kommt (z.B.> Checksum).
Verstehe.
Hier mal meine halbe Tube Senf zum Thema.
Naja, der erste Ansatz ist OK. main.c ist halbwegs aufgeräumt, die
einzelnen Inits per Funktion aufgerufen. Allerdings gehört so ein
krümelkram wie die Logikblöcke von
1
if(LED_blinken==1)....
2
if(RS485_Empfang_komplett==1)....
so nicht in main. Pack sie in eine Funktion. Das macht die Hauptschleife
deutlich kürzer und übersichtlicher, das will und braucht man.
Auch sowas hier gehört in eine Funktion.
1
RS485_Empfang_Buffer[0]='\0';// Den Empfangsbuffer wieder an den Anfang setzen.
2
RS485_Empfang_komplett=0;
3
Nachricht_vorhanden=false;
4
5
Nachricht_Befehl[0]='\0';
6
Nachricht_Daten[0]='\0';
Siehe auch Strukturierte Programmierung auf Mikrocontrollern
defines sollte man komplett GROSS schreiben, um sie im Quelltext als
solche leicht zu erkennen. Variablen klein, bestenfalls mit
Großbuchstaben am Anfang bzw. SilbenAnfang.
Jetzt aber zum Wesentlichen.
1
ISR(USART0_RX_vect)// Zeichen von RS485 empfangen
2
{
3
unsignedcharusart_rx=UDR0;// ankommendes Zeichen zur Weiterverarbeitung sichern.
Hier fehlt eine Begrenzung! Klassischer Pufferüberlauf!
Und bitte nicht einen Roman als Variablenname! rxCnt tut's auch!
1
if(usart_rx=='\n')
2
{
3
RS485_Empfang_Buffer_Position=0;
4
RS485_Empfang_komplett=1;
5
RS485_auf_Senden_schalten();// Schnittstelle gleich auf Senden umschalten, damit
6
// während der Verarbeitung kein neues Zeichen mehr angenommen wird.
7
}
Nö, so macht man das nicht. Man kann eine Variable setzen, daß eine
Nachricht vorliegt und bearbeitet werden muss. So lange die nicht wieder
vom Hauptprogramm gelöscht wurde, werden alle ankommenden zeichen vom
UART verworfen. Der RXC Interrupt läuft aber weiter! Und wenn wieder
Nachrichten empfangen werden können, muss erstmal nach einem hoffentlich
vorhandenen Startzeichen der Nachricht gesucht werden. Bis dahin werden
auch alle Zeichen verworfen.
Die Vorauswertung der Zeichen in der ISR auf Nachrichtenende etc. ist
OK. Die eigentliche Auswertung erfolgt aber im Hauptprogramm.
1
voidRS485_auf_Empfang_schalten(void)
2
{
3
RS485_lesen;
4
}
Was soll der Unsinn? Da kann man sich die Funktion auch sparen und das
MACRO gleich direkt hinschreiben.
1
String++;
2
_delay_ms(1);
Das hatten wir ja schon mehrfach. Das ist Unsinn. Kam es dir nicht
komisch vor, daß du mit 115200 Baud übertragen willst (~11000 Zeichen/s,
sprich 11 Zeichen/ms) und du hier 1ms warten willst?
1
charuart0_getnew()
2
{
3
while(!(UCSR0A&(1<<RXC0)))
4
;
5
returnUDR0;
6
}
Was soll das Semikolon auf der neuen Zeile? Das ist maximal verwirrend.
Üblich sind
1
while(!(UCSR0A&(1<<RXC0)));
2
while(!(UCSR0A&(1<<RXC0))){}
3
while(!(UCSR0A&(1<<RXC0))){
4
}
1
// Das LF am Ende der empfangenen Nachricht abschneiden
2
// und durch '\0' ersetzen.
3
4
strtok(RS485_Empfang_Buffer,"\n");
Das kann man so machen, kann man aber auch in der ISR schon tun, wenn da
\n erkannt wird. Ist einfacher und sicherer.
Durch deine ganze Stringauswertung fizze ich mich jetzt nicht durch.
Sieht aber eher zu kompliziert aus.
Sowas gehört in eine .c Datei!
Das Grundproblem ist dein noch nicht robustes Konzept. Deine RXC-ISR
darf nie ins Schwitzen kommen. Entweder Daten im Puffer speichern oder
wegwerfen. Fertig. Es darf NIE einen Pufferüberlauf geben! Immer den
Index prüfen! Den Puffer kannst du im Hauptprogramm in "aller Ruhe"
bearbeiten. Wenn das zu langsam ist, kann ein FIFO in Software
helfen. Da du im Halbduplex arbeitest, kann man ohne ISR senden, das
lohnt vermutlich nicht.
Falk B. schrieb:> Es darf NIE einen Pufferüberlauf geben!
Der Receive-Buffer [255] ist um ein Byte zu klein als das
was der Buffer-Index gross werden könnte.
Wastl schrieb:>> Es darf NIE einen Pufferüberlauf geben!>> Der Receive-Buffer [255] ist um ein Byte zu klein als das> was der Buffer-Index gross werden könnte.
Solche Tricks mit 8 Bit Index und Überlauf ist egal sollte man sich
gleich ABGEWÖHNEN! Denn selbst wenn der Puffer 256 Bytes oder 1000
hätte, würde der im Kreis vollgeschrieben! Das ist Unsinn und keiner
weiß mehr, wo der Anfang ist! Das Schreiben des Puffers beginnt mit dem
Erkennen des Startzeichend der Nachricht und endet mit dem Endzeichen
oder Pufferende.
Falk B. schrieb:> Hier mal meine halbe Tube Senf zum Thema.
WOW - vielen Dank, dass Du Dir dafür die Zeit genommen hast. Ich bin
kein Profi, sondern Autodidakt. Umso wichtiger für mich, aus solchen
Analysen zu lernen, um besser zu werden.
> Naja, der erste Ansatz ist OK. main.c ist halbwegs aufgeräumt, die> einzelnen Inits per Funktion aufgerufen. Allerdings gehört so ein> krümelkram wie die Logikblöcke von>>
1
>if(LED_blinken==1)....
2
>if(RS485_Empfang_komplett==1)....
3
>
Das war mehr zu Testzwecken. Räum ich auf.
> Auch sowas hier gehört in eine Funktion.>>
1
>RS485_Empfang_Buffer[0]='\0';// Den Empfangsbuffer wieder an
>> Hier fehlt eine Begrenzung! Klassischer Pufferüberlauf!> Und bitte nicht einen Roman als Variablenname! rxCnt tut's auch!
Hatten wir schon besprochen, werde das überarbeiten.
Mit kürzeren Variablennamen hadere ich - das kann ich nicht versprechen
:-)
>
>> Nö, so macht man das nicht. Man kann eine Variable setzen, daß eine> Nachricht vorliegt und bearbeitet werden muss. So lange die nicht wieder> vom Hauptprogramm gelöscht wurde, werden alle ankommenden zeichen vom> UART verworfen. Der RXC Interrupt läuft aber weiter! Und wenn wieder> Nachrichten empfangen werden können, muss erstmal nach einem hoffentlich> vorhandenen Startzeichen der Nachricht gesucht werden. Bis dahin werden> auch alle Zeichen verworfen.
Ah, verstehe. Da muss ich nochmal ran. Mach ich.
>
1
>voidRS485_auf_Empfang_schalten(void)
2
>{
3
>RS485_lesen;
4
>}
5
>
>> Was soll der Unsinn? Da kann man sich die Funktion auch sparen und das> MACRO gleich direkt hinschreiben.
Haha - ja, das ist zum Unsinn geworden. Das ist nach einiger Optimierung
in der Funktion übrig geblieben. Hast natürlich recht - kommt weg.
>
1
>String++;
2
>_delay_ms(1);
3
>
>> Das hatten wir ja schon mehrfach. Das ist Unsinn. Kam es dir nicht> komisch vor, daß du mit 115200 Baud übertragen willst (~11000 Zeichen/s,> sprich 11 Zeichen/ms) und du hier 1ms warten willst?
Ja, hatten wir. Ist schon erledigt und funktioniert jetzt hervorragend!
>
1
>charuart0_getnew()
2
>{
3
>while(!(UCSR0A&(1<<RXC0)))
4
>;
5
>returnUDR0;
6
>}
7
>
>> Was soll das Semikolon auf der neuen Zeile? Das ist maximal verwirrend.
Nur ein dummer Tippfehler, das sollte natürlich so nicht sein. Schon
ausgebessert.
>
1
>// Das LF am Ende der empfangenen Nachricht abschneiden
2
>// und durch '\0' ersetzen.
3
>
4
>strtok(RS485_Empfang_Buffer,"\n");
5
>
>> Das kann man so machen, kann man aber auch in der ISR schon tun, wenn da> \n erkannt wird. Ist einfacher und sicherer.
Verstehe. Ich wollte die ISR so kurz wie möglich halten und habe das
(für mich) als logisch empfunden, es bei der Analyse der empfangenen
Nachricht zu erledigen. Änder ich.
> Durch deine ganze Stringauswertung fizze ich mich jetzt nicht durch.> Sieht aber eher zu kompliziert aus.>>
1
>voidEncoder_Schritte_abfragen(void)
2
>{
3
>Encoder_Schritte_auswerten();
4
>}
5
>
Das kann ich verstehen. Das eine ist das hardwaremäßige Abfragen, dass
andere die Auswertung des Wertes, was damit geschehen soll. Ist mir
völlig klar, dass sich meine Gedankengänge dazu nicht jedem gleich
erschließen. Geht ganz bestimmt auch besser.
> Sowas ist auch albern.>> Bildschirm.h>
>> Sowas gehört in eine .c Datei!
Ah, ok. Hab ich mir irgendwo abgekupfert. Änder ich.
> Das Grundproblem ist dein noch nicht robustes Konzept. Deine RXC-ISR> darf nie ins Schwitzen kommen. Entweder Daten im Puffer speichern oder> wegwerfen. Fertig. Es darf NIE einen Pufferüberlauf geben! Immer den> Index prüfen! Den Puffer kannst du im Hauptprogramm in "aller Ruhe"> bearbeiten. Wenn das zu langsam ist, kann ein FIFO in Software> helfen. Da du im Halbduplex arbeitest, kann man ohne ISR senden, das> lohnt vermutlich nicht.
Gehe die Änderungen alle an und sag nochmal vielen Dank für Deine Zeit!
Chris B. schrieb:>> Da du im Halbduplex arbeitest, kann man ohne ISR senden, das>> lohnt vermutlich nicht.
Noch ein Tip. Bei solchen Kommunikationen, erst recht auf einem Bus mit
mehreren Teilnehmern, ist "Klappe halten" der Normalzustand. Sprich, die
Busteilnehmer empfangen meistens. Das Senden von Antworten ist kurz und
konzentriert.
Tranceiver auf Senden umschalten
Alle Daten schnellstmöglich senden
Auf das Ende des Übertragung des letzten Zeichens warten
Tranceiver auf Empfangen umschalten
An anderen Stellen, erst recht im RXC-Interrupt wird an der
Datenrichtung des Tranceivers NICHT rumgefummelt!
Etwa so.
1
voiduart0_puts(char*String){
2
3
RS485_TX_EN;
4
while(*String){
5
uart0_putc(*String);// String bis zum '\0' rausschreiben.
6
String++;
7
}
8
UCSR0A=1<<TCX0;// TXC0 Bit löschen
9
while(!(UCSR0A&(1<<TXC0)));// warte auf das Ende des letzten Bytes
10
RS485_RX_EN;
11
}
Meistens schließt man DE und !RE vom Tranceiver zuammen an ein IO vom
Controller, um die Datenrichtung zu bestimmen. Dann braucht man einen
Pull-Up Widerstand am RX vom uC, damit der auf HIGH bleibt, wenn der
Empfänger inaktiv ist, denn dann geht der meistens auf Tristate
(hochohmig). Damit hört man auch nicht seine eigenen Sendedaten, was
meist OK ist. Bei CAN ist das anders, aber das ist ein anderes Thema.
Ich würde bei Halb-Duplex die Daten auch rücklesen und auf Kollision
prüfen. Das sollte leicht schon im Interrupthandler gehen.
/RE auf GND und mittels DE entscheidet man, ob in den Empfangs-FIFO oder
vergleich mit Sende-FIFO.