cSerialPort Advanced
📖 Table of Contents
- Flow Control Details
- Timeout Strategy Details
- Binary Protocol Handling
- Virtual Serial Ports & Loopback Testing
- Dynamic Configuration Management
- Modbus RTU Practice
Flow Control Details
Flow control coordinates the sender and receiver rates to prevent data loss. cSerialConfig supports five flow control modes.
Flow Control Types
| Mode | Constant | Description |
|---|---|---|
| No flow control | fcNone | No flow control |
| Software flow control | fcXonXoff | Controlled via XON/XOFF characters |
| Hardware flow control RTS/CTS | fcRtsCts | Controlled via RTS/CTS signal lines |
| Hardware flow control DTR/DSR | fcDtrDsr | Controlled via DTR/DSR signal lines |
| Mixed flow control | fcRtsCtsAndXonXoff | Both hardware and software flow control |
Software Flow Control (XON/XOFF)
The sender receives XOFF(&H13) to pause sending when data volume is excessive, and XON(&H11) to resume sending.
cfg.FlowControl = fcXonXoff
' Can customize XON/XOFF characters
cfg.XonChar = &H11
cfg.XoffChar = &H13You can also manually send XON/XOFF:
sp.SendXOff ' Request peer to pause sending
sp.SendXOn ' Request peer to resume sendingHardware Flow Control (RTS/CTS)
Uses RTS/CTS signal lines for hardware handshake. Sender transmits data only when CTS is high.
cfg.FlowControl = fcRtsCts
' Setting FlowControl automatically sets RtsControl to rcHandshakeNote: After enabling hardware flow control, manual
SetRTS/SetDTRmay be overridden by flow control mechanisms.
DTR/RTS Control Modes
| Mode | Constant | DTR/RTS Behavior |
|---|---|---|
| Disable | dcDisable/rcDisable | Signal line stays low |
| Enable | dcEnable/rcEnable | Signal line stays high |
| Handshake | dcHandshake/rcHandshake | Automatically managed by flow control |
| Toggle | rcToggle | RTS only, pulls high when data present |
Timeout Strategy Details
Win32 serial port timeout is controlled by five parameters. Understanding their working mechanism is crucial.
Timeout Parameters
| Parameter | Description |
|---|---|
ReadIntervalTimeout | Maximum interval between two read characters(ms) |
ReadTotalTimeoutMultiplier | Per-byte timeout multiplier(ms/byte) |
ReadTotalTimeoutConstant | Read fixed timeout(ms) |
WriteTotalTimeoutMultiplier | Write per-byte timeout multiplier |
WriteTotalTimeoutConstant | Write fixed timeout(ms) |
Read Timeout Calculation
Total timeout = ReadTotalTimeoutMultiplier × requested data amount + ReadTotalTimeoutConstantThree Preset Modes
1. Non-Blocking Read
Read returns immediately, reads data if available, returns empty if not.
cfg.SetNonBlockingRead
' Equivalent to:
' ReadIntervalTimeout = MAXDWORD (&HFFFFFFFF)
' ReadTotalTimeoutMultiplier = 0
' ReadTotalTimeoutConstant = 02. Blocking Read
Wait until specified byte count is read, may block permanently.
cfg.SetBlockingRead
' Equivalent to:
' ReadIntervalTimeout = 0
' ReadTotalTimeoutMultiplier = 0
' ReadTotalTimeoutConstant = 0⚠️ Warning: Blocking mode will hang permanently if no data arrives. Recommend using with
ReadTotalTimeoutConstant.
3. Read With Timeout
Specify total timeout duration.
cfg.SetReadTimeout 2000 ' 2 second timeout
' Equivalent to:
' ReadIntervalTimeout = 50
' ReadTotalTimeoutMultiplier = 0
' ReadTotalTimeoutConstant = 2000Custom Timeout Example
Calculate timeout by byte count, suitable for scenarios with known data length:
' Per byte 10ms + fixed 100ms
cfg.ReadIntervalTimeout = 50
cfg.ReadTotalTimeoutMultiplier = 10
cfg.ReadTotalTimeoutConstant = 100
' Reading 100 bytes total timeout = 10×100 + 100 = 1100msBinary Protocol Handling
Send Binary Frame
' Build Modbus RTU request frame
Dim frame(7) As Byte
frame(0) = &H01 ' Slave address
frame(1) = &H03 ' Function code: read holding registers
frame(2) = &H00 ' Start address high byte
frame(3) = &H00 ' Start address low byte
frame(4) = &H00 ' Register count high byte
frame(5) = &H0A ' Register count low byte
frame(6) = &HC5 ' CRC low byte
frame(7) = &HCD ' CRC high byte
sp.WriteData frameReceive and Parse Binary Frame
Private Sub m_Port_DataReceived(ByVal BytesCount As Long)
Dim buf() As Byte
Dim n As Long
n = m_Port.ReadData(buf)
If n < 5 Then Exit Sub ' Modbus response at least 5 bytes
' Parse Modbus response
Dim slaveAddr As Byte
Dim funcCode As Byte
slaveAddr = buf(0)
funcCode = buf(1)
If funcCode And &H80 Then
' Exception response
Dim errCode As Byte
errCode = buf(2)
Debug.Print "Modbus exception: " & errCode
Else
' Normal response
Dim byteCount As Byte
byteCount = buf(2)
Debug.Print "Received " & byteCount & " bytes of data"
End If
End SubHex Display Debugging
Private Sub m_Port_DataReceived(ByVal BytesCount As Long)
Dim buf() As Byte
m_Port.ReadData buf
Dim hexStr As String
Dim i As Long
For i = 0 To UBound(buf)
hexStr = hexStr & Right$("0" & Hex$(buf(i)), 2) & " "
Next
Debug.Print "RX: " & Trim$(hexStr)
End SubVirtual Serial Ports & Loopback Testing
Virtual Serial Port Software
In development environments without physical serial ports, virtual serial port software can create port pairs:
- com0com (Free, open source)
- Virtual Serial Port Driver (Commercial software)
- VSPD (Commercial software)
After installation, create virtual serial port pairs (e.g., COM3 ↔ COM4). Data sent to COM3 will appear in COM4's receive buffer.
Loopback Test Code
Public Sub TestLoopback()
Dim spTx As New cSerialPort
Dim spRx As New cSerialPort
' Open both ports of the virtual pair
spTx.PortName = "COM3"
spRx.PortName = "COM4"
Dim cfg As New cSerialConfig
cfg.BaudRate = br9600
Set spTx.Config = cfg
Set spRx.Config = cfg.Clone()
If Not spTx.OpenPort() Then
Debug.Print "Failed to open COM3"
Exit Sub
End If
If Not spRx.OpenPort() Then
Debug.Print "Failed to open COM4"
spTx.ClosePort
Exit Sub
End If
' Clear buffers
spTx.PurgeAll
spRx.PurgeAll
' Send
Dim testStr As String
testStr = "LOOPBACK_TEST_12345"
spTx.WriteText testStr
Debug.Print "Sent: " & testStr
' Wait for data arrival
Dim t As Single
t = Timer
Do While spRx.InBufferCount < Len(testStr) And (Timer - t) < 2
DoEvents
Loop
' Receive
Dim received As String
received = spRx.ReadExisting()
Debug.Print "Received: " & received
If received = testStr Then
Debug.Print "[PASS] Loopback test passed"
Else
Debug.Print "[FAIL] Loopback test failed"
End If
spTx.ClosePort
spRx.ClosePort
End SubDynamic Configuration Management
Runtime Configuration Changes
sp.OpenPort "COM3"
' Change baud rate
sp.Config.BaudRate = br115200
sp.ApplyConfig ' Manually apply to opened port
' Read actual configuration from port
sp.RefreshConfig
Debug.Print "Actual baud rate: " & sp.Config.BaudRateConfiguration Cloning
Dim cfg1 As New cSerialConfig
cfg1.BaudRate = br115200
cfg1.Parity = ptEven
' Clone configuration (independent)
Dim cfg2 As cSerialConfig
Set cfg2 = cfg1.Clone()
cfg2.BaudRate = br9600
Debug.Print cfg1.BaudRate ' Still 115200Mode String
Dim cfg As New cSerialConfig
cfg.FromModeString "baud=115200 parity=N data=8 stop=1"
Debug.Print cfg.ToModeString() ' baud=115200 parity=N data=8 stop=1Modbus RTU Practice
Complete Modbus RTU Master Request
Public Function ModbusReadHoldingRegisters( _
ByVal sp As cSerialPort, _
ByVal SlaveAddr As Byte, _
ByVal StartAddr As Integer, _
ByVal Quantity As Integer) As Byte()
' Build request frame
Dim frame(7) As Byte
frame(0) = SlaveAddr
frame(1) = &H03 ' Function code: read holding registers
frame(2) = (StartAddr And &HFF00) \ &H100
frame(3) = StartAddr And &HFF
frame(4) = (Quantity And &HFF00) \ &H100
frame(5) = Quantity And &HFF
' Calculate CRC16
Dim crc As Long
crc = CalcCRC16(frame, 6)
frame(6) = crc And &HFF
frame(7) = (crc And &HFF00) \ &H100
' Clear buffers and send
sp.PurgeAll
sp.WriteData frame
' Wait for response (1 second timeout)
Dim t As Single
t = Timer
Do While sp.InBufferCount < 5 And (Timer - t) < 1
DoEvents
Loop
' Read response
Dim buf() As Byte
Dim n As Long
n = sp.ReadData(buf)
If n >= 5 Then
ModbusReadHoldingRegisters = buf
End If
End FunctionUsage Example
Dim sp As New cSerialPort
sp.PortName = "COM3"
sp.Config.BaudRate = br9600
sp.Config.DataBits = 8
sp.Config.Parity = ptEven ' Modbus RTU typically uses even parity
sp.Config.StopBits = sb1
sp.OpenPort
' Read holding registers from slave 1, start address 0, count 10
Dim response() As Byte
response = ModbusReadHoldingRegisters(sp, 1, 0, 10)
If UBound(response) >= 0 Then
' Parse response data
Dim byteCount As Byte
byteCount = response(2)
Debug.Print "Received " & byteCount & " bytes of data"
End If
sp.ClosePortLast Updated: 2026-07-05