Forum: Mikrocontroller und Digitale Elektronik ATMEGA UART Empfang mit Verzögerung


von Chris B. (chrisberger)


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

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Chris B. schrieb:
> oder sonstiges ausführt?
Ich sehe kein "sonstiges".

von Rainer W. (rawi)


Lesenswert?

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.

von Wastl (hartundweichware)


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.

von M. K. (sylaina)


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)

von Peter D. (peda)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

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.

von Sebastian W. (wangnick)


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

von Chris B. (chrisberger)


Lesenswert?

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.

von Chris B. (chrisberger)


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]

: Bearbeitet durch Moderator
von Oliver S. (oliverso)


Lesenswert?

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

von Sebastian W. (wangnick)


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

von Chris B. (chrisberger)


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]

: Bearbeitet durch Moderator
von Chris B. (chrisberger)


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.

von S. L. (sldt)


Lesenswert?

Wie sieht die UART-Senderoutine aus?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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.

von Chris B. (chrisberger)


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]

: Bearbeitet durch Moderator
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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

: Bearbeitet durch Moderator
von S. L. (sldt)


Lesenswert?

Also dieses _delay_ms(1) gefällt mir auch nicht.
 Zur 'Auslastung': daran denken, dass der SPI-Interrupt höhere Priorität 
hat als die USARTs.

von Falk B. (falk)


Lesenswert?

Lothar M. schrieb:
>> _delay_ms(1);
> Das wäre für mich ein Programmfehler, denn das ist mindestens aktive
> Rechenzeitverschwendung.

AUTSCH!

von Chris B. (chrisberger)


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.

von Falk B. (falk)


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.

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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.

von Chris B. (chrisberger)


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

von Sebastian W. (wangnick)


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

von S. L. (sldt)


Angehängte Dateien:

Lesenswert?

Welche Interrupts sind eigentlich freigegeben?
  Ich möchte nochmals auf die Prioritäten-Reihenfolge hinweisen.

von Rainer W. (rawi)


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.

von M. K. (sylaina)


Lesenswert?

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.

von Sebastian W. (wangnick)


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

von S. L. (sldt)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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.

von Robert G. (robert_g311)


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ß

von Hmmm (hmmm)


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.

von Ozvald K. (Firma: Privat) (ozvaldk)


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

von Peter D. (peda)


Lesenswert?

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.

von Falk B. (falk)


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

von Chris B. (chrisberger)


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.

von Chris B. (chrisberger)


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.

von Chris B. (chrisberger)


Lesenswert?

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.

von Chris B. (chrisberger)


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

von Chris B. (chrisberger)


Lesenswert?

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.

von Chris B. (chrisberger)


Angehängte Dateien:

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!

von Peter D. (peda)


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.

von Purzel H. (hacky)


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.

von Chris B. (chrisberger)


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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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?

: Bearbeitet durch Moderator
von Purzel H. (hacky)


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.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Sinnvoll für eine saubere Umschaltung ist eine aktive Terminierung:
https://www.felser.ch/profibus-manual/der_busabschluss_fuer_rs485.html

von S. L. (sldt)


Lesenswert?

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

von Chris B. (chrisberger)


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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Angehängte Dateien:

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?

: Bearbeitet durch Moderator
von Chris B. (chrisberger)


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

von Falk B. (falk)


Lesenswert?

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.

von Chris B. (chrisberger)


Lesenswert?

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?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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

von Chris B. (chrisberger)


Lesenswert?

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.

von Falk B. (falk)


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.

von Wastl (hartundweichware)


Lesenswert?

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.

von Falk B. (falk)


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.

von Sebastian W. (wangnick)


Lesenswert?

Falk B. schrieb:
> Was soll das Semikolon auf der neuen Zeile? Das ist maximal verwirrend.
> Üblich sind
>
1
> while (!(UCSR0A & (1<<RXC0)));
2
>

Ich schreibe immer
1
while (!(UCSR0A & (1<<RXC0))) continue;

LG, Sebastian

von Chris B. (chrisberger)


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!

von Falk B. (falk)


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.

: Bearbeitet durch User
von Chris B. (chrisberger)


Lesenswert?

Werd ich so umbauen - Danke.

von Peter D. (peda)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.