Questions that are asked on Forums about serial ports are complicated because of the many approaches taken with device communication and / or not having a clear understanding of the device protocol. Taking the approach presented to serial data reception removes part of the complexity, and leaves only a discussion of the protocol.
This is some basic information that you know, or should know to use this code. It is not meant to insult your intelligence; just bear in mind that not all readers of this have the same knowledge level.
The difficulty people have using the serial port tends to be with receiving data. As I worked with different devices I kept searching for THE answer to receiving data. What is presented below is as close as I have come to THE answer. This code has been used for:
The speed of the serial port is set using the BaudRate property, and is set based on the device you are connected to. This number represents the maximum amount of data that can be received in one second.
If the BaudRate is 19200 the maximum bytes per second your application has to be able to process is 2400 bytes per second. or 1 byte every 0.000416 seconds,
If the BaudRate is 115200 the maximum bytes per second your application has to be able to process is 14400 bytes per second. or 1 byte every 0.000069 seconds,
Those are the maximum rates possible but the actual rate can be much slower depending on the device you are attached to.
In reality your application will read between 1 and n bytes depending on how the application is structured, and these bytes are further packaged into PDU’s. If your application is presenting the data to the user you have to ask yourself if the user interface can be updated at the actual PDU arrival rate, and if it can’t what do you do↑
The structure of the code is:
Guiding principles:
The base code as a forms project is included below and is also available for download.
Here are short movies showing the simulated complex PDU code running. The first movie is the general flow of the program, and the second is a more detailed look at data reception.
MovieGeneral MovieReceive
Here is what the UI looks like. Movie UI
If you do not have a loopback plug I highly recommend you get one(see references). They are inexpensive and as you will see it is sometimes useful to talk to yourself.
You can create a simple loopback for RS232 DB9 / DB25 by connecting pins two and three together; be careful not to short the pins to the case.
This is the basic flow of data through the code. Other than several threads running this code is not complicated.
Data from the serial port is first read into a Windows-created buffer. The size of that buffer is controlled by the ReadBufferSize property.
The code is notified that there are bytes to read by the firing of the DataReceived event. The DataReceived event handler sets an AutoResetEvent that causes the Receive method to execute.
The Receive method reads all available bytes. Bytes read are accumulated into a list of bytes, RcvBuffer. This method then sets another AutoResetEvent that causes the Protocol method to execute. Since RcvBuffer can be modified by more than one method access to it is controlled using thread synchronization.
The Protocol method determines if there are PDU’s, and removes the PDU bytes from RcvBuffer. The PDU’s are then processed. Based on how the code is written this is where most, if not all, application code is written.
+----------+ +----------+
+-------+ | Serial | Bytes | Windows |
|Device +------>+ Port +--------->+ Buffer |
+-------+ +----------+ +----------+
|
+-------------------+ DataReceived <---------------+
| +--------------+ +---------------+
+--->|Receive method+----> signal +--->+Protocol method|
+--------------+ +---------------+
| ^
| |
v +
bytes bytes
to from
+ ^
| +---------------+ |
+------> | RcvBuffer +------>+
| list of bytes |
+---------------+
↑ Back to top
The code presented is:
#Region directives are used to organize the code and are used to direct the conversation.
This section of code contains the serial port object and variables used throughout.
The primary buffer and its lock object are defined here. Also a ConcurrentQueue of Object is defined for pin changes and errors.
There are three threads defined with a corresponding AutoResetEvent to control event firing. In addition there is a ManualResetEvent that controls general thread execution.
Variables used for instrumentation are defined here. A variable to control instrumentation collection is defined. Their meaning can be ascertained by usage.
DataReceived – this handler first check is to see the event type is SerialData.Eof. This event type is ignored. If the event type is SerialData.Chars rcvARE, the AutoResetEvent that control the Receive method, is set.
ErrorReceived – the SerialErrorReceivedEventArgs are added to a queue that holds both errors and pin event arguments. The AutoResetEvent that control the PinsAndErrors method, is set.
PinChanged - the SerialPinChangedEventArgs are added to a queue that holds both errors and pin event arguments. The AutoResetEvent that control the PinsAndErrors method, is set.
This method is responsible for reading all available bytes from the serial port. These bytes are accumulated into RcvBuffer for processing by the Protocol method. This method is ran on a separate thread that is started before the port is opened, and is in a loop until runThreads is reset. Until rcvARE is set by the DataReceived event handler the thread is blocked by this line rcvARE.WaitOne near the end of the method.
When rcvARE is set by the DataReceived event handler the code checks to see if runThreads is set. If it is then the loop executes otherwise the thread ends.
RcvReadCT is set to zero. This tracks how many times the loop that follows it executes for one firing of the DataReceived event handler.
The inner do loop continually executes while the port is open and there are bytes to read.
The number of bytes to read is determined and a temporary array large enough to hold that number of bytes is created.
The bytes are then read. Note that the serial port read returns the number of bytes actually read which can be less than the number of bytes available. If that is the case the temporary buffer is resized.
Next the temporary buffer is added to the primary buffer, RcvBuffer, which is protected by rcvBufLock. RcvReadCT is incremented.
AutoResetEvent protoARE is set after the first read and if there are multiple reads, which causes the Protocol method to execute.
Which brings the code back to rcvARE.WaitOne, blocking until another DataReceived event happens.
Instrumentation lines were skipped in the discussion. They start with If RcvInstr Then. RcvInstr is a Boolean that controls whether or not to do instrumentation.
This method is responsible for processing serial port errors and pin changes. This method is ran on a separate thread that is started before the port is opened, and is in a loop until runThreads is reset. Until pinsErrorsARE is set the thread is blocked by this line pinsErrorsARE.WaitOne near the end of the method.
When pinsErrorsARE is set the code checks to see if runThreads is set. If it is then the loop executes otherwise the thread ends. The code in this method is a prototype that can be tailored based on the application. As written the queue is emptied and a Debug statement is used to report the error. The exception to this is SerialError.Overrun. This is a hardware error and the method discards the input buffer when this occurs.
This method is responsible for opening the port and starting the threads. The threads are started by a call to the StartSerialPortThreads method.
Then the ports settings are defined. These setting must match the device that you are connected to.
Settings of Interest
This method is responsible for starting the background threads used by the serial port.
This method resets the AutoResetEvents, then creates and starts the background threads. Before starting the threads runThreads is set to the run state.
This method is responsible for closing the port and stopping the threads.
At the end of each of the threads is Loop While runThreads.WaitOne(0). The first thing this method does is to Reset runThreads which causes the Loop While to fail, thus ending the thread. To make sure that the threads get to the Loop While their individual AutoResetEvent’s are then Set.
Once those steps are taken all of the threads are joined and the port is closed. If the application is ending this method should be called with True which causes the ResetEvent’s to be disposed.
At this point if you use the code discussed so far and connect to a device what you would see is all of the bytes received from the device in RcvBuffer. (If the device needs to be polled to get it to send add that in a button.) What is needed now is a Protocol method to see that. Here is a simple version.
01.
Private
Sub
Protocol()
02.
Do
03.
'with this you will eventually get an out of memory exception
04.
05.
Debug.WriteLine(RcvBuffer.Count.ToString(
"n0"
))
06.
07.
protoARE.WaitOne()
'wait for Receive to signal potential message
08.
Loop
While
runThreads.WaitOne(0)
'loop while running
09.
End
Give it a try with a breakpoint set inside the loop. Each time the break is encountered examine RcvBuffer. You will see that the data from the device is being accumulated into RcvBuffer and if you let the code run long enough it will run out of memory. Not very useful. Lets look at another example.
Included with the code is a class, PDUCommon. Also included is another class, LinePDU, that inherits PDUCommon. LinePDU retrieves messages from RcvBuffer that end with the ControlChars.Lf. Here is a Protocol method for receiving lines.
Dim
TotByteCT
As
Long
= 0L
linesRcvd
New
List(Of LinePDU)
PDU
LinePDU
PDU =
LinePDU(RcvBuffer, rcvBufLock)
If
PDU.isValid
Then
TotByteCT += PDU.DataLength
10.
PDU.Data = PDU.Data.TrimEnd
'get rid of trailing white space
11.
linesRcvd.Add(PDU)
12.
13.
PDU.isComplete
AndAlso
RcvBuffer.Count > 2
14.
15.
'process linesRcvd
16.
17.
18.
linesRcvd.Clear()
'replace this with your code to process the lines
19.
20.
21.
22.
23.
What you do with the lines received is up to you.
Another PDU that is simulated in the code provided looks like this (see Movie above)
There is a class, ComplexDeviceProto, that receives these PDU’s.
Included with the code are two loop back device simulators, and several Protocol methods that are surrounded with #Region’s. To use the code uncomment one of each, and comment out the others. Also included are several PDU classes, two which may be useful.
Imports
System.IO.Ports
Public
Class
Form1
#Region "Common"
#Region "Objects and Variables"
''' <summary>
''' the serial port
''' </summary>
''' <remarks></remarks>
WithEvents
mySerialPort
SerialPort
''' accumulates bytes received from mySerialPort
''' <remarks>primary buffer</remarks>
RcvBuffer
List(Of
Byte
)
'PRIMARY BUFFER! <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
rcvBufLock
Object
'lock for the above
''' controls threads looping
runThreads
Threading.ManualResetEvent(
False
'not running
''' thread that reads bytes and adds them to the rcvBuf
rcvThrd
Threading.Thread
rcvARE
Threading.AutoResetEvent(
''' responsible for determining if rcvBuf has a message
protoThrd
protoARE
''' handles PinChanged and ErrorReceived events
pinserrorsThrd
pinsErrorsARE
''' Queue of PinChanged and ErrorReceived events
PinsErrorsQ
Concurrent.ConcurrentQueue(Of
'
RcvReadCT
Integer
= 0
'used by method receive
RcvError
Boolean
=
'some receive instrumentation / metrics
RcvInstr
True
'False ' controls receive instrumentation
RcvDRECT
'used by event DataReceived
RcvDREEofs
RcvByteTot
RcvMultiRead
RcvNoData
RcvThrdTime
Stopwatch
RcvBuffAddTime
#End Region
#Region "Handlers"
mySerialPort_DataReceived(sender
, e
SerialDataReceivedEventArgs)
Handles
mySerialPort.DataReceived
e.EventType = SerialData.Eof
'it is ok to throw this away, caused by EOF (byte=26) in buffer
'the byte that is equal to 26 will be read
RcvDREEofs += 1
Else
'SerialData.Chars
rcvARE.
Set
()
'read the bytes, see method Receive
RcvDRECT += 1L
mySerialPort_ErrorReceived(sender
SerialErrorReceivedEventArgs)
mySerialPort.ErrorReceived
PinsErrorsQ.Enqueue(e)
pinsErrorsARE.
mySerialPort_PinChanged(sender
SerialPinChangedEventArgs)
mySerialPort.PinChanged
#Region "Receive / PinsAndErrors"
Receive()
'because of how the .DataReceived event handler and this method are written
'.DataReceived events can fire but there are not bytes to be read
RcvReadCT = 0
'note: BytesToRead can't be checked if the port is closed!!!
mySerialPort.IsOpen
mySerialPort.BytesToRead > 0
''read all bytes available
RcvThrdTime.Start()
'instrumentation
RcvError =
Try
numBytRd
= mySerialPort.BytesToRead
'number of bytes to read
tmpBuf(numBytRd - 1)
'create a temporary buffer to hold bytes
numBytRd = mySerialPort.Read(tmpBuf, 0, numBytRd)
'READ the bytes
numBytRd <> tmpBuf.Length
'read less than initial .BytesToRead↑
Array.Resize(tmpBuf, numBytRd)
'yes
RcvByteTot += numBytRd
'add temporary buffer to public buffer
RcvBuffAddTime.Start()
Threading.Monitor.Enter(rcvBufLock)
'<<< LOCK
RcvBuffer.AddRange(tmpBuf)
Threading.Monitor.
Exit
(rcvBufLock)
'<<< UNLOCK
RcvBuffAddTime.
Stop
RcvReadCT += 1
'increment count of reads
RcvReadCT = 1
'first read↑
protoARE.
'yes, check for possible message, see method Protocol
Catch
ex
Exception
'queue the error
PinsErrorsQ.Enqueue(ex)
RcvThrdTime.
RcvReadCT > 1
'more than one read
RcvMultiRead += 1L
'check for possible message
ElseIf
RcvNoData += 1L
rcvARE.WaitOne()
'WAIT for .DataReceived event handler to fire <<<<<<
PinsAndErrors()
LastErr
SerialErrorReceivedEventArgs
LastPin
SerialPinChangedEventArgs
LastEx
obj
PinsErrorsQ.Count > 0
serrea
spinchea
excp
PinsErrorsQ.TryDequeue(obj)
TypeOf
Is
'see https://msdn.microsoft.com/en-us/library/system.io.ports.serialerror(v=vs.110).aspx
serrea =
DirectCast
(obj, SerialErrorReceivedEventArgs)
LastErr = serrea
'todo SerialErrorReceivedEventArgs
Debug.WriteLine(DateTime.Now.ToString(
"HH:mm:ss.f"
) &
" "
& serrea.EventType.ToString)
Select
Case
serrea.EventType
SerialError.Frame
SerialError.Overrun
'hardware error
mySerialPort.DiscardInBuffer()
SerialError.RXOver
'should not happen, might need to increase ReadBufferSize
SerialError.RXParity
SerialError.TXFull
spinchea =
(obj, SerialPinChangedEventArgs)
LastPin = spinchea
& spinchea.EventType.ToString)
'todo SerialPinChangedEventArgs
excp =
(obj, Exception)
LastEx = excp
& excp.InnerException.Message)
'todo Exception
Threading.Thread.Sleep(10)
'Dequeue failed
pinsErrorsARE.WaitOne()
'wait for event handler to fire
#Region "Open / Close"
SerialPortOpen()
'start the threads before opening the port
StartSerialPortThreads()
'Modify settings as needed <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
mySerialPort.PortName =
"COM1"
mySerialPort.BaudRate = 115200
' 19200 '
mySerialPort.DataBits = 8
mySerialPort.Parity = Parity.None
mySerialPort.StopBits = StopBits.One
'other settings as needed - consider speed and max size to determine settings <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
mySerialPort.ReceivedBytesThreshold = 1
'one is the default, recommend no change unless absolutely needed
mySerialPort.ReadTimeout = 1000
'default is infinite if not set
mySerialPort.WriteTimeout = 1000
mySerialPort.ReadBufferSize = 1024 * 4
'Windows-created input buffer 4096 is default, change if needed
mySerialPort.WriteBufferSize = 1024 * 2
'Windows-created output buffer 2048 is default, change if needed
'this setting is informational only. the code only reads bytes <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
'if the device is sending strings recommend setting this to match encoding device is using <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
'if the application is writing strings this must match devices encoding <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
mySerialPort.Encoding = System.Text.Encoding.GetEncoding(28591)
'default is 7 bit ascii, this is an 8 bit flavor of asciii
mySerialPort.Open()
'some devices require the following
mySerialPort.DtrEnable =
'only after Open. may cause multiple PinChanged events
SerialPortClose(
Debug.WriteLine(ex.Message)
'Running↑
'reset AutoResetEvents
rcvARE.Reset()
pinsErrorsARE.Reset()
protoARE.Reset()
'set up the threads
'this thread reads bytes and adds them to the buffer
rcvThrd =
Threading.Thread(
AddressOf
Receive)
rcvThrd.IsBackground =
' why Threading.ThreadPriority.AboveNormal ↑??
' at low data rates this will not affect performance
' at high data rates it is imperative to Read the bytes from the
' port quickly!
rcvThrd.Priority = Threading.ThreadPriority.AboveNormal
'this thread examines the buffer to see if a
'complete message (PDU), as defined by the protocol, is available
protoThrd =
Protocol)
protoThrd.IsBackground =
'this thread handles PinChanged and ErrorReceived events
pinserrorsThrd =
PinsAndErrors)
pinserrorsThrd.IsBackground =
runThreads.
'indicate we are running before starting threads
'start threads that do the work
'do this before opening SerialPort
pinserrorsThrd.Start()
protoThrd.Start()
rcvThrd.Start()
'start last
'wait for threads to start
(pinserrorsThrd.ThreadState
And
Threading.ThreadState.Unstarted) = Threading.ThreadState.Unstarted
OrElse
(protoThrd.ThreadState
(rcvThrd.ThreadState
SerialPortClose(isAppEnd
runThreads.Reset()
'then some code to get the threads to stop
'protocol first
'wait for threads to stop
protoThrd.Join()
rcvThrd.Join()
pinserrorsThrd.Join()
'then close the port
mySerialPort.Close()
isAppEnd
'is the application ending
Threading.Thread.Sleep(100)
rcvARE.Dispose()
pinsErrorsARE.Dispose()
runThreads.Dispose()
protoARE.Dispose()
'protocol last
''' for forms only
FormIsClosingHelper()
Me
.BeginInvoke(
.Close()
'fire close again
#Region "App specific"
#Region "PDU Classes"
PDUCommon
Friend
_valid
'not valid
_complete
'True = a complete message
_data()
Function
isComplete()
Return
._complete
isValid()
._valid
''' find a byte, the needle, in the buffer, the haystack
''' <param name="needle">the byte value to find</param>
''' <param name="someData">buffer, a List(Of Byte), where to look</param>
''' <returns>index of needle in the buffer</returns>
FindByte(needle
,
ByRef
someData
),
someDataLock
idxNeedle
Threading.Monitor.Enter(someDataLock)
idxNeedle = someData.IndexOf(needle)
(someDataLock)
''' get bytes up to and including the 'needle'
''' <param name="someData">buffer, a List(Of Byte)</param>
''' <param name="someDataLock">an object used to lock the buffer</param>
''' <returns>nothing if needle not found in buffer, else byte()</returns>
GetBytes(needle
rv()
Nothing
= someData.IndexOf(needle)
idxNeedle >= 0
'has needle
idxNeedle += 1
'convert to count
rv = someData.GetRange(0, idxNeedle).ToArray()
someData.RemoveRange(0, idxNeedle)
rv
''' get bytes based on count
''' <param name="count">number of bytes to return</param>
''' <returns>nothing if count .lt. number bytes in buffer, else byte()</returns>
GetBytes(
, count
someData.Count >= count
count > 0
rv = someData.GetRange(0, count).ToArray
someData.RemoveRange(0, count)
''' gets all bytes currently in the buffer
''' <param name="someData">the buffer</param>
''' <param name="someDataLock">lock for the buffer</param>
''' <returns></returns>
GetAllBytes(
someData.Count > 0
rv = someData.GetRange(0, someData.Count).ToArray
someData.RemoveRange(0, someData.Count)
''' remove all bytes from the buffer
''' <param name="getBytes">if true _data will contain byes in buffer before clear</param>
''' <returns>count of bytes removed</returns>
FlushBuffer(
Optional
getBytes
rv = someData.Count
._data = someData.GetRange(0, someData.Count).ToArray
someData.Clear()
Inherits
Data
String
""
'if valid this has the line of data
Const
lineTerm
= Asc(ControlChars.Lf)
'linefeed
(
PDU()
.GetBytes(lineTerm, someData, someDataLock)
PDU IsNot
.Data = System.Text.Encoding.GetEncoding(28591).GetChars(PDU, 0, PDU.Length)
._complete =
._valid =
DataLength()
.Data.Length
ComplexDeviceProto
'sample
'protocol is
'0 STX = one byte = 2
'1 ID = four bytes = sequence
'5 DL = one byte = length of data (1 - 255)
'6 data - string <= 255 chars
' CRC = one byte = XOR of all data bytes
ID
.MinValue
stx
= 2
oneTime
'lock
someData(0) <> stx
someData.RemoveAt(0)
" Protocol Error"
oneTime =
someData.Count >= 6
'have DL
someData.Count >= (7 + someData(5))
'complete PDU?
= someData.GetRange(0, 7 + someData(5)).ToArray
'get it
someData.RemoveRange(0, 7 + someData(5))
'remove it
'unlock
crc
For
x
= 6
To
6 + (PDU(5) - 1)
'calc crc
crc = crc
Xor
PDU(x)
Next
crc = PDU(6 + PDU(5))
'check crc
'good
.ID = BitConverter.ToInt32(PDU, 1)
.Data = System.Text.Encoding.GetEncoding(28591).GetChars(PDU, 6, PDU(5))
" CRC Error"
Threading.Monitor.IsEntered(someDataLock)
'Me.FlushBuffer(someData, someDataLock)
#Region "Protocol Methods"
stpw
Stopwatch = Stopwatch.StartNew
UIstpw
#Region "simple"
'Private Sub Protocol()
' Do
' 'with this you will eventually get an out of memory exception
' Debug.WriteLine(RcvBuffer.Count.ToString("n0"))
' protoARE.WaitOne() 'wait for Receive to signal potential message
' Loop While runThreads.WaitOne(0) 'loop while running
'End Sub
#Region "flush"
' Dim TotByteCT As Long = 0L
' Dim upd As Stopwatch = Stopwatch.StartNew
' Dim PDU As New DeviceProtocolCommon
' Dim bf As Integer = PDU.FlushBuffer(RcvBuffer, rcvBufLock, True)
' If bf > 0 Then
' TotByteCT += bf
' If upd.ElapsedMilliseconds >= 5000 Then 'update
' upd.Restart()
' Debug.WriteLine(TotByteCT.ToString("n0"))
' End If
#Region "lines"
' Dim linesRcvd As New List(Of LinePDU)
' Dim PDU As LinePDU
' PDU = New LinePDU(RcvBuffer, rcvBufLock)
' If PDU.isValid Then
' TotByteCT += PDU.DataLength
' PDU.Data = PDU.Data.TrimEnd 'get rid of trailing white space
' linesRcvd.Add(PDU)
' Loop While PDU.isComplete AndAlso RcvBuffer.Count > 2
' 'process linesRcvd
' If UIstpw.ElapsedMilliseconds >= 100 Then 'update UI 10 times per second
' UIstpw.Restart()
' Me.Invoke(Sub()
' If linesRcvd.Count > 0 Then
' RichTextBox1.Clear()
' RichTextBox1.Lines = (From l In linesRcvd Select l.Data).ToArray
' RichTextBox1.Refresh()
' linesRcvd.Clear()
' ''Show(bits / Second())
' TextBox1.Text = (TotByteCT / stpw.Elapsed.TotalSeconds * 8).ToString("n0")
' End Sub)
#Region "complex"
addThese
List(Of ComplexDeviceProto)
TotMessCT
MaxDepth
''pass in the buffer and lock
ComplexDeviceProto(RcvBuffer, rcvBufLock)
'potential message?
'valid message?
addThese.Add(PDU)
RcvBuffer.Count >= minMessSz
'complete, see if another message present
addThese.Count > 0
'new messages
''yes
addThese.Count > MaxDepth
MaxDepth = addThese.Count
TotMessCT += addThese.Count
addThese.Clear()
UIstpw.ElapsedMilliseconds >= 250
'update UI 4 times per second
UIstpw.Restart()
Not
dispPaused
bps
CInt
((TotByteCT / stpw.Elapsed.TotalSeconds) * 8)
TextBox1.Text =
.Format(
"RMsgs: {0,-11:n0} Msgs/s: {1,-6:n0} bps: {2,-8:n0} MaxD: {3:n0}"
TotMessCT, TotMessCT / stpw.Elapsed.TotalSeconds, bps, MaxDepth)
'no data / second
TextBox3.Text =
"NoData/s: "
& (RcvNoData / stpw.Elapsed.TotalSeconds).ToString(
bps \= 1000
bps <= ProgressBar1.Maximum
ProgressBar1.Value = bps
ProgressBar1.Value = ProgressBar1.Maximum
#Region "Form Load / Shown / Close"
Form1_FormClosing(sender
FormClosingEventArgs)
.FormClosing
Static
t
Task
t = Task.Run(
e.Cancel =
t.Wait()
Form1_Load(sender
EventArgs)
.Load
Debug.WriteLine(
Debug.WriteLine(DateTime.Now.ToLongTimeString)
Form1_Shown(sender
.Shown
SetDoubleBuffering(TextBox1)
SetDoubleBuffering(TextBox2)
SimulateDevice()
'<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Comment this line if connected to device <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
ProgressBar1.Maximum = mySerialPort.BaudRate \ 1000
SetDoubleBuffering(aControl
Control)
'for some controls, like DataGridView, this can have a dramatic effect
ctrlType
Type
pi
Reflection.PropertyInfo
ctrlType = aControl.
GetType
pi = ctrlType.GetProperty(
"DoubleBuffered"
, Reflection.BindingFlags.Instance
Or
Reflection.BindingFlags.NonPublic)
pi.SetValue(aControl,
#Region "Device emulator"
Shared
prng
Random
minMessSz
slow
#Region "send lines"
'Private Sub SimulateDevice()
' '
' ' SIMULATE DEVICE - reqires Loopback
' 'protocol is
' 'send lines
' Dim lorem As String = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Four score and seven years ago..."
' Dim loremch() As Char = lorem.ToCharArray
' Dim t As Task = Task.Run(Sub()
' Dim ct As Integer = 1
' Dim dbg As Boolean = False ' True '
' Dim mess As New List(Of Byte) 'used to construct the message
' Dim uiUpd As Stopwatch = Stopwatch.StartNew
' Const cr As Byte = 13
' Const lf As Byte = 10
' Const lastCH As Byte = 126 '~
' mess.Clear()
' Dim endCHIdx As Integer = prng.Next(1, loremch.Length)
' 'some chars
' mess.AddRange(System.Text.Encoding.GetEncoding(28591).GetBytes(loremch, 0, endCHIdx))
' mess.Add(lastCH)
' mess.Add(cr)
' mess.Add(lf)
' Dim sendThis() As Byte = mess.ToArray 'convert list to array for sending
' Try
' mySerialPort.Write(sendThis, 0, sendThis.Length) '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< SEND
' Catch ex As Exception
' Stop
' End Try
' ct += 1
' If uiUpd.ElapsedMilliseconds >= 250 Then
' uiUpd.Restart()
' Me.BeginInvoke(Sub()
' TextBox2.Text = "SMsgs: " & ct.ToString("n0")
' If dbg OrElse slow Then
' Threading.Thread.Sleep(25) 'slow down for testing
' Loop While runThreads.WaitOne(0)
' SIMULATE DEVICE - reqires Loopback
lorem
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean non vehicula sem, lacinia consequat sapien. Four score and seven years ago..."
lorem = lorem & lorem
'& lorem
loremch()
Char
= lorem.ToCharArray
minMessSz = 8
Task = Task.Run(
ct
= 1
dbg
' True '
mess
'used to construct the message
uiUpd
mess.Clear()
mess.Add(stx)
'send seq
mess.AddRange(BitConverter.GetBytes(ct))
'Dim endCHIdx As Integer = prng.Next(1, 5)'four bytes. for debug
endCHIdx
= prng.
(1, 256)
mess.Add(
CByte
(endCHIdx))
'data length
'some chars
mess.AddRange(System.Text.Encoding.GetEncoding(28591).GetBytes(loremch, 0, endCHIdx))
mess.Count - 1
mess(x)
(crc
255))
sendThis()
= mess.ToArray
'convert list to array for sending
mySerialPort.Write(sendThis, 0, sendThis.Length)
'<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< SEND
ct += 1
uiUpd.ElapsedMilliseconds >= 250
uiUpd.Restart()
TextBox2.Text =
"SMsgs: "
& ct.ToString(
Threading.Thread.Sleep(25)
'slow down for testing
#Region "Other"
Button1_Click(sender
Button1.Click
'Pause button
dispPaused =
Button1.Text =
"Resume Display"
"Pause Display"
Timer1_Tick(sender
Timer1.Tick
Label1.Text = DateTime.Now.ToString(
chkSlow_CheckedChanged(sender
chkSlow.CheckedChanged
slow = chkSlow.Checked
The complete windows form project is here. SerialPortBaseProject
The project is also in the TechNet Glallery. SerialPortProject
By using the approach presented for all serial port projects the only coding needed will be for a devices specific protocol and how the data is used.