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?
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.
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)
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 ...
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?
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.
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.
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.
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.
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...
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.
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.
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.
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 ...
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 :)
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.
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.
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.
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.
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?
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.
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.
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)?
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.
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
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.
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.
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)?
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.
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!
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.
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.
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.
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.
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?
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 :-)
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).
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).
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.
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.
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.
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.
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
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.
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!
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.