
Ich verwende einen ATMEL Embedded Controller (at91sam9x25e) und dessen seriellen Port zum Senden/Empfangen über RS485 Modbus RTU. Die normale Kommunikation funktioniert einwandfrei, aber manchmal – beim Schreiben – werden einige Pakete mit einer Verzögerung von etwa 3,5 ms zwischen zwei Zeichen innerhalb einer Nachricht aufgeteilt und daher als neue Nachricht erkannt.
Einstellungen:
- Mono 3.2.8
- Modbus RTU 19,2 kBaud
- RS485
- Auto-RTS
Ich versende das komplette Paket auf einmal:
try
{
Thread.Sleep(_timePreSend);
_serialPort.Write(buffer, offset, count);
} catch(Exception err)
{
log.Error(err.Message);
} finally
{
Thread.Sleep(2);
}
Ich habe die 2 ms zuvor verwendet, als ich RTS manuell aktiviert/deaktiviert habe, sodass die letzten Bits aus dem Puffer (UART oder FIFO) übertragen wurden.
Erfassung vom Oszilloskop:
120318000100020032000403200000001C0005000000000000000003D <- Lücke -> B6
Ausgabe von stty:
stty -F /dev/ttyS4 -a
speed 19200 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts
ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff
-iuclc -ixany -imaxbel -iutf8
-opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke
Was könnte der Grund für diese Lücke sein? Was kann ich tun, um sie zu vermeiden?
Antwort1
Es ist schwer zu sagen, was genau die Ursache dafür ist. Leider bieten serielle Linux-Ports keine Zeitgarantien und normale UART-Protokolle haben normalerweise keine so engen Zeitanforderungen. Wenn Ihr Protokoll diese Art von Zeitgarantie erfordert, müssen Sie sich mit Ihren Kerneltreibern befassen und vielleicht Ihren eigenen Treiber schreiben, der die atomare Zustellung aller Bytes ohne Lücken garantiert. Dabei verwenden Sie wahrscheinlich Interrupts, um sicherzustellen, dass der UART-Puffer immer rechtzeitig aufgefüllt wird. Sie können diese Art von Garantie nicht aus dem Benutzerbereich ohne die Hilfe des Kernels geben.
Sie können jedoch versuchen, die Ursache für Ihre Aufteilung herauszufinden und diese möglicherweise zu umgehen. Außerdem können Sie empirisch feststellen, ob die Lösung in der Praxis zuverlässig genug ist, um für Ihre Zwecke zu funktionieren. Tritt die Verzögerung immer vor dem letzten Byte auf? Wenn ja, klingt es so, als würde etwas im Stapel aus irgendeinem Grund ein Zeichen puffern, möglicherweise um den Durchsatz zu optimieren, indem mehrere Bytes gleichzeitig an die Hardware übermittelt werden und das einzelne Byte erst nach einer kurzen Zeitüberschreitung übermittelt wird. Sie sollten zunächst sicherstellen, dass sich Ihre Userspace-App ordnungsgemäß verhält: Überprüfen Sie die Systemaufrufe, indem Sie sie ausführen, strace
und stellen Sie sicher, dass das Paket in einem einzigen Systemaufruf in den Kernel geschrieben wird write
. Wenn dies der Fall ist, ist es an der Zeit, die Kernelquelle für Ihren UART-Treiber zu lesen und zu prüfen, ob eine merkwürdige Pufferung vorliegt.
Eine andere Möglichkeit ist, dass der Kerneltreiber die Daten einfach in einem Kernelthread (oder direkt in Ihrem Prozesskontext) verarbeitet und die Lücken, die Sie sehen, ein anderer Prozess sind, der geplant wird. Wenn das der Fall ist, besteht die einzige Möglichkeit, dies zu umgehen, darin, den Kerneltreiber zu ändern oder einen eigenen zu schreiben, sodass er die Übertragung streng in Echtzeit verarbeitet, ohne dass andere Prozesse Vorrang haben (z. B. indem alles im Interruptkontext ausgeführt wird).
Antwort2
Ich habe endlich herausgefunden, was das Problem für dieses Verhalten war. Ich habe in meiner Kommunikationsschleife einige Warteschleifen verwendet, was zu einer hohen CPU-Last führte. Ich habe dann gelesen, dass die Verwendung von SerialPort-Befehlen wieBytesToRead und Readsind überhaupt nicht ideal: Wenn Sie .NET System.IO.Port.Serialport verwenden müssen
Daher habe ich versucht, asynchrone Funktionen von ByteStream zu verwendenReadAsync und WriteAsyncwie in meiner neuen Frage beschriebenRichtige Verwendung von Timeouts mit serieller Schnittstelle und ReadAsyncund ich habe eine viel geringere CPU-Auslastung und überhaupt keine Lücken mehr.