ATMEGA UART Empfang mit Verzögerung

OP #7394620
Lesenswert?

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

#7394656
Lesenswert?

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.

#7394660
Lesenswert?

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)

#7394700
Lesenswert?

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

OP #7394848
Lesenswert?

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
  unsigned char usart_rx = UDR0; // ankommendes Zeichen zur Weiterverarbeitung sichern.
4

5
  RS485_Empfang_Buffer[RS485_Empfang_Buffer_Position] = usart_rx;
6
  RS485_Empfang_Buffer_Position++;
7
  
8
  if (usart_rx == '\n')
9
  {
10
    RS485_Empfang_Buffer_Position = 0;
11
    RS485_Empfang_komplett = 1;
12
  }
13
}

[Mod: C-Tags eingefügt]

#7394851
Lesenswert?

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

OP #7394867
Lesenswert?

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
void SPI_versenden(char SPI_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.
7
}

[Mod: C-Tags eingefügt]

OP #7394875
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7394894
Lesenswert?

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.

OP #7394900
Lesenswert?

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
int UART0_Zeichen_senden(unsigned char Zeichen)
2
{
3
  while (!(UCSR0A & (1<<UDRE0)))  // warten bis Senden möglich ist
4
  {
5
  }
6
  UDR0 = Zeichen;                 // Zeichen senden.
7
  return 0;
8
}
9

10
void UART0_String_senden (char *String)
11
{
12
  while (*String)                 // Bis String '\0' erreicht.
13
  {                               
14
    uart0_putc(*String);
15
    String++;
16
    _delay_ms(1);
17
  }
18
}

[Mod: C-Tags eingefügt]

OP #7394932
Lesenswert?

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.

#7394933
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7394936
Lesenswert?

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.

OP #7394937
Lesenswert?

_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 ...

#7394940
Lesenswert?

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

#7394949
Lesenswert?

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.

#7394985
Lesenswert?

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

#7395019
Lesenswert?

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ß

#7395032
Lesenswert?

Chris B. schrieb:

1
> ISR(USART0_RX_vect)
2
> {
3
>   unsigned char usart_rx = UDR0; // ankommendes Zeichen zur
4
> Weiterverarbeitung sichern.
5
> 
6
>   RS485_Empfang_Buffer[RS485_Empfang_Buffer_Position] = usart_rx;
7
>   RS485_Empfang_Buffer_Position++;
8
> 
9
>   if (usart_rx == '\n')
10
>   {
11
>     RS485_Empfang_Buffer_Position = 0;
12
>     RS485_Empfang_komplett = 1;
13
>   }
14
> }

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.

(Firma: Privat) #7395050
Lesenswert?

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)?

#7395124
Lesenswert?

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

OP #7395359
Lesenswert?

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.

OP #7395366
Lesenswert?

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.

OP #7395371
Lesenswert?

Hmmm schrieb:

Chris B. schrieb:

1
>> ISR(USART0_RX_vect)
2
>> {
3
>>   unsigned char usart_rx = UDR0; // ankommendes Zeichen zur
4
>> Weiterverarbeitung sichern.
5
>>
6
>>   RS485_Empfang_Buffer[RS485_Empfang_Buffer_Position] = usart_rx;
7
>>   RS485_Empfang_Buffer_Position++;
8
>>
9
>>   if (usart_rx == '\n')
10
>>   {
11
>>     RS485_Empfang_Buffer_Position = 0;
12
>>     RS485_Empfang_komplett = 1;
13
>>   }
14
>> }
15
>
16
>

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.

OP #7395376
Lesenswert?

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!

Angehängte Dateien:
#7395455
Lesenswert?

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.

#7395475
Lesenswert?

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.

OP #7395499
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7395509
Lesenswert?

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
char RS485_Empfang_Buffer[255];  
2
uint8_t RS485_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?

#7395511
Lesenswert?

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.

OP #7395548
Lesenswert?

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
> char RS485_Empfang_Buffer[255];
2
> uint8_t RS485_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.

Moderator (Firma: Titel) Persönliche Seite #7395555
Lesenswert?

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?

Angehängte Dateien:
OP #7395576
Lesenswert?

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 :-)

#7396719
Lesenswert?

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
  unsigned char usart_rx = UDR0;                // ankommendes Zeichen zur Weiterverarbeitung sichern.
4

5
  RS485_Empfang_Buffer[RS485_Empfang_Buffer_Position] = usart_rx;
6
  RS485_Empfang_Buffer_Position++;

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
void RS485_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
char uart0_getnew()
2
{
3
  while (!(UCSR0A & (1<<RXC0)))
4
  ;
5
  return UDR0;
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.

1
void Encoder_Schritte_abfragen(void)
2
{
3
  Encoder_Schritte_auswerten();
4
}

Sowas ist auch albern.

Bildschirm.h

1
const unsigned char Magneto_Logo [] PROGMEM = {
2
  0x00, 0x00, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
3
  0x00, 0x00, 0x80, 0xF0, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

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.

#7396726
Lesenswert?

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.

OP #7397506
Lesenswert?

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 
2
> den Anfang setzen.
3
>       RS485_Empfang_komplett = 0;
4
>       Nachricht_vorhanden = false;
5
> 
6
>       Nachricht_Befehl[0] = '\0';
7
>       Nachricht_Daten[0] = '\0';
8
>

Ok.

Siehe auch Strukturierte Programmierung auf Mikrocontrollern

Danke für den Tipp.

defines sollte man komplett GROSS schreiben, um sie im Quelltext als

Änder ich.

1
> ISR(USART0_RX_vect)                        // Zeichen von RS485 
2
> empfangen
3
> {
4
>   unsigned char usart_rx = UDR0;                // ankommendes Zeichen 
5
> zur Weiterverarbeitung sichern.
6
> 
7
>   RS485_Empfang_Buffer[RS485_Empfang_Buffer_Position] = usart_rx;
8
>   RS485_Empfang_Buffer_Position++;
9
>

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 :-)

1
>   if (usart_rx == '\n')
2
>   {
3
>     RS485_Empfang_Buffer_Position = 0;
4
>     RS485_Empfang_komplett = 1;
5
>     RS485_auf_Senden_schalten();              // Schnittstelle gleich 
6
> auf Senden umschalten, damit
7
>                                 // während der Verarbeitung kein neues 
8
> Zeichen mehr angenommen wird.
9
>   }
10
>

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
> void RS485_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
> char uart0_getnew()
2
> {
3
>   while (!(UCSR0A & (1<<RXC0)))
4
>   ;
5
>   return UDR0;
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
> void Encoder_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

1
> const unsigned char Magneto_Logo [] PROGMEM = {
2
>   0x00, 0x00, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xC0, 0x00, 0x00, 0x00, 
3
> 0x00, 0x00, 0x00, 0x00, 0x00,
4
>   0x00, 0x00, 0x80, 0xF0, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x00, 0x00, 
5
> 0x00, 0x00, 0x00, 0x00, 0x00,
6
>

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!

#7397515
Lesenswert?

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
void uart0_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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren