PowerShell Serial

Auch wenn die wenigsten Computer heute noch eine parallele oder serielle Schnittstelle haben, so sind zumindest die seriellen Ports im IoT immer noch Standard. Über entsprechende USB-Adapter werden Arduino, ESP826 und Co programmiert und Ausgaben der kleinen Computer zu Diagnosezwecken ausgegeben. Aber auch per PowerShell lassen sich diese Schnittstellen sehr einfach verwenden

Serielle Ports finden

Zuerst sollten wie mal schauen, welche COM-Ports es gibt. Das erledigt ein Einzeiler in PowerShelll:

PS C:\>[System.IO.Ports.SerialPort]::GetPortNames()
COM4

Mein PC halt also zumindest einen COM4-Port.

SerialPort instanzieren und parametrisieren

Über das gleiche Objekt kann ich einen Port instanzieren und die Parameter setzen.

$serial = New-Object System.IO.Ports.SerialPort
$serial.PortName = "COM4"
$serial.BaudRate = "9600"
$serial.Parity = "None"
$serial.DataBits = 8
$serial.StopBits = 1
$serial.ReadTimeout = 1000  # 1 sek

Wer schon mit seriellen Schnittstellen gearbeitet hat, sollte die Parameter zur Baudrate, Start/Stop-Bits etc. kennen. Es gibt auch eine Kurzform dafür.

$serial= new-Object System.IO.Ports.SerialPort COM4,9600,None,8,1

Das Objekt hat natürlich noch weitere Parameter:

PS C:\> $serial

BaseStream             :
BaudRate               : 9600
BreakState             :
BytesToWrite           :
BytesToRead            :
CDHolding              :
CtsHolding             :
DataBits               : 8
DiscardNull            : False
DsrHolding             :
DtrEnable              : False
Encoding               : System.Text.ASCIIEncoding+ASCIIEncodingSealed
Handshake              : None
IsOpen                 : False
NewLine                :

Parity                 : None
ParityReplace          : 63
PortName               : COM4
ReadBufferSize         : 4096
ReadTimeout            : -1
ReceivedBytesThreshold : 1
RtsEnable              : False
StopBits               : One
WriteBufferSize        : 2048
WriteTimeout           : -1
Site                   :
Container              :

Und auch Event und Methoden sind vorhanden.

PS C:\> $serial | gm

   TypeName: System.IO.Ports.SerialPort
Name                      MemberType Definition
----                      ---------- ----------
DataReceived              Event      System.IO.Ports.SerialDataReceivedEventHandler DataReceived(System.Object, System…
Disposed                  Event      System.EventHandler Disposed(System.Object, System.EventArgs)
ErrorReceived             Event      System.IO.Ports.SerialErrorReceivedEventHandler ErrorReceived(System.Object, Syst…
PinChanged                Event      System.IO.Ports.SerialPinChangedEventHandler PinChanged(System.Object, System.IO.…
Close                     Method     void Close()
DiscardInBuffer           Method     void DiscardInBuffer()
DiscardOutBuffer          Method     void DiscardOutBuffer()
Dispose                   Method     void Dispose(), void IDisposable.Dispose()
Equals                    Method     bool Equals(System.Object obj)
GetHashCode               Method     int GetHashCode()
GetLifetimeService        Method     System.Object GetLifetimeService()
GetType                   Method     type GetType()
InitializeLifetimeService Method     System.Object InitializeLifetimeService()
Open                      Method     void Open()
Read                      Method     int Read(byte[] buffer, int offset, int count), int Read(char[] buffer, int offse…
ReadByte                  Method     int ReadByte()
ReadChar                  Method     int ReadChar()
ReadExisting              Method     string ReadExisting()
ReadLine                  Method     string ReadLine()
ReadTo                    Method     string ReadTo(string value)
ToString                  Method     string ToString()
Write                     Method     void Write(string text), void Write(char[] buffer, int offset, int count), void W…
WriteLine                 Method     void WriteLine(string text)

Lesen und Schreiben

Schon bei den Methoden haben Sie sicher gesehen, dass es Write/Writeline und Read/Readline gibt. Zuerst müssen wie den Port aber öffnen und am Ende sollten wir ihn auch wieder schließen.

$serial.Open()
$serial.Readtimeout = 1000   # 1 Sek. default ist unlimited
$serial.WriteLine("123456789abcdef123456789abcdef123456789abcdef123456789abcdef")
$serial.ReadLine()
123456789abcdef123456789abcdef123456789abcdef123456789abcdef

Bei Open habe ich bislang zwei Fehler provozieren können. Der letzte Fehler passiert beim Read

Fehler Ursache
Exception calling "Open" with "0" argument(s): "Access to the port 'COM4' is denied."

Der Port ist vermutlich durch eine andere Anwendung schon geöffnet und damit blockiert

Exception calling "Open" with "0" argument(s): "The port 'COM2' does not exist."

Prüfen Sie noch mal, ob es den angegeben Port in ihrem System gibt.

Exception calling "ReadLine" with "0" argument(s): "The operation has timed out."

Sie haben mit einer der "Read"-Methode auf ein Zeichen gewartet und es wurde während des ReadTimeout" kein Zeichen empfangen..

Wenn ich Bytes lese und keine Daten kommen, dann terminiert das Skript nach dem eingestellten "ReadTimeout" in Millisekunden mit einem Fehler

Über Puffer muss ich mir nur bedingt Gedanken machen, denn die meisten seriellen UARTs haben ein paar Bytes Buffer, um kurze Verzögerungen beim Einlesen zu erlauben. Aber Windows selbst hat natürlich auch einen eigenen FIFO-Buffer, der bei einfachen Operationen das Programmieren einfacher macht.

PS C:\> $serial | fl *buffer*

ReadBufferSize  : 4096
WriteBufferSize : 2048

Dennoch sollte das Skript nicht allzu lange irgendwo warten, denn irgendwann sind auch die 4096 Bytes aufgebraucht und die früher empfangenen Informationen werden überschrieben. Beim Schreiben können Sie natürlich auch "zu schnell" schreiben. Zwei Eigenschaften des Serial-Objects helfen ihnen dabei, die wartenden Bytes zu zählen.

PS C:\> $serial| fl bytes*

BytesToWrite : 0
BytesToRead  : 3135

Insofern könnten Sie den Fehler beim "Read" auch einfach vermeiden, wenn Sie das Feld "BytesToRead" abfragen. Das hilft aber nicht bei "ReadLine", welches auf ein Stringendezeichen (CHR(10) wartet. Wenn ein String im Buffer ist aber das Endezeichen fehlt, dann bricht "ReadLine" ab aber lässt die Zeichen im Buffer.

Steuerleitungen

Mit der Übertragung von Informationen über TxD und RxD ist es alleine ja nicht getan. Die serielle Schnittstelle kennt auch mit RTS/CTS einen Handshake und auch DSR/DTR/RI sind beim Modembetrieb interessante Aus- und Eingänge, die auch zweckentfremdet werden können. Alle Leitungen sind auch im Serial-Objekt enthalten. Der Breakstate ist aber keine eigene Leitung, sondern zeigt an, wenn RxD permanent als "0" steht und damit eine Unterbrechung signalisiert wird.

BreakState             : False
CDHolding              : False  (lesen)
CtsHolding             : False  (Lesen)
DsrHolding             : False  (Lesen)
DtrEnable              : False  (Lesen und Setzen)
Handshake              : None
RtsEnable              : False  (Lesen und Setzen)

Die Leitungen können also recht einfach gesetzt werden

$serial.DtrEnable = "true"

Leider haben aber die meisten USB-Seriell -Adapter im IoT-Bereich die Steuerleitungen nicht nach außen geführt und daher nicht nutzbar. Die RTS/CTS-Steuerung wird auch durch das Property "Handshake" bestimmt, welcher normalerweise auf "None" steht und einen von vier Werten annehmen kann

Bezeichnung Wert Bedeutung

None

0

Kein Handshake. Die Leitungen sind ungenutzt

XOnXOff

1

Über TxD/RxD wird ein XON/XOFF genutzt, um die Übertragung zu steuern

RequestToSend

2

Die Steuerleitungen RTS/CTS werden genutzt

RequestToSendXOnXOff

3

XON/XOFF und RTS/CTS werden genutzt.

Beispiel mit D0

Auf der Seite SML Smartmeter Code habe ich mit einem Infrarot-Transistor die D0-Schnittstelle eines smarten Stromzählers ausgelesen. Da habe ich auch erst einmal mit einem einfachen USB/Seriell-Adapter und dem Notebook geprüft, was ankommt und wie leserlich das ist.

Für erste Gehversuche ist auch ein solcher USB/Seriell-Adapter nützlich, bei dem Sie einfach RxD und TxD verbunden haben.

So können Sie erste Informationen seriell senden und gleich wieder empfangen.

Interessant ist so ein Adapter natürlich auch mit einem NodeMCU, WeMos D1 oder anderen ESP32 oder ESP8266-Board, welches über die serielle Schnittstelle nicht nur per Strom versorgt wird, sondern z.B. auch WLAN-Scanning und Bluetooth-Scanning betreiben kann. So können Sie auf einem leistungsfähigen Notebook dann diese Tochtersysteme für spezielle Aufgaben einsetzen und die Ergebnisse direkt weiter verarbeiten.

Weitere Links