cSerialPort 高级进阶
📖 目录
流控制详解
流控制用于协调发送方和接收方的速率,防止数据丢失。cSerialConfig 支持五种流控制模式。
流控制类型
| 模式 | 常量 | 说明 |
|---|---|---|
| 无流控制 | fcNone | 不使用流控制 |
| 软件流控制 | fcXonXoff | 通过 XON/XOFF 字符控制 |
| 硬件流控制 RTS/CTS | fcRtsCts | 通过 RTS/CTS 信号线控制 |
| 硬件流控制 DTR/DSR | fcDtrDsr | 通过 DTR/DSR 信号线控制 |
| 混合流控制 | fcRtsCtsAndXonXoff | 同时使用硬件和软件流控制 |
软件流控制(XON/XOFF)
发送方在数据过多时收到 XOFF(&H13) 暂停发送,收到 XON(&H11) 恢复发送。
vb
cfg.FlowControl = fcXonXoff
' 可自定义 XON/XOFF 字符
cfg.XonChar = &H11
cfg.XoffChar = &H13也可手动发送 XON/XOFF:
vb
sp.SendXOff ' 请求对方暂停发送
sp.SendXOn ' 请求对方恢复发送硬件流控制(RTS/CTS)
使用 RTS/CTS 信号线进行硬件握手。发送方检测到 CTS 为高电平时才发送数据。
vb
cfg.FlowControl = fcRtsCts
' 设置 FlowControl 会自动将 RtsControl 设为 rcHandshake注意:使用硬件流控制后,手动
SetRTS/SetDTR可能被流控制机制覆盖。
DTR/RTS 控制模式
| 模式 | 常量 | DTR/RTS 行为 |
|---|---|---|
| 禁用 | dcDisable/rcDisable | 信号线保持低电平 |
| 启用 | dcEnable/rcEnable | 信号线保持高电平 |
| 握手 | dcHandshake/rcHandshake | 由流控制自动管理 |
| 切换 | rcToggle | 仅 RTS,有数据时拉高 |
超时策略详解
Win32 串口超时由五个参数控制,理解它们的工作机制至关重要。
超时参数
| 参数 | 说明 |
|---|---|
ReadIntervalTimeout | 两次读取字符间的最大间隔(ms) |
ReadTotalTimeoutMultiplier | 每字节超时系数(ms/字节) |
ReadTotalTimeoutConstant | 读取固定超时(ms) |
WriteTotalTimeoutMultiplier | 写入每字节超时系数 |
WriteTotalTimeoutConstant | 写入固定超时(ms) |
读取超时计算
总超时 = ReadTotalTimeoutMultiplier × 请求数据量 + ReadTotalTimeoutConstant三种预设模式
1. 非阻塞读取
Read 立即返回,有数据就读,没数据返回空。
vb
cfg.SetNonBlockingRead
' 等价于:
' ReadIntervalTimeout = MAXDWORD (&HFFFFFFFF)
' ReadTotalTimeoutMultiplier = 0
' ReadTotalTimeoutConstant = 02. 阻塞读取
等待直到读取到指定字节数,可能永久阻塞。
vb
cfg.SetBlockingRead
' 等价于:
' ReadIntervalTimeout = 0
' ReadTotalTimeoutMultiplier = 0
' ReadTotalTimeoutConstant = 0⚠️ 警告:阻塞模式下如果没有数据会永久挂起,建议配合
ReadTotalTimeoutConstant使用。
3. 带超时读取
指定总超时时间。
vb
cfg.SetReadTimeout 2000 ' 2秒超时
' 等价于:
' ReadIntervalTimeout = 50
' ReadTotalTimeoutMultiplier = 0
' ReadTotalTimeoutConstant = 2000自定义超时示例
按字节数计算超时,适合已知数据长量的场景:
vb
' 每字节 10ms + 固定 100ms
cfg.ReadIntervalTimeout = 50
cfg.ReadTotalTimeoutMultiplier = 10
cfg.ReadTotalTimeoutConstant = 100
' 读取100字节总超时 = 10×100 + 100 = 1100ms二进制协议处理
发送二进制帧
vb
' 构建 Modbus RTU 请求帧
Dim frame(7) As Byte
frame(0) = &H01 ' 从站地址
frame(1) = &H03 ' 功能码:读保持寄存器
frame(2) = &H00 ' 起始地址高字节
frame(3) = &H00 ' 起始地址低字节
frame(4) = &H00 ' 寄存器数量高字节
frame(5) = &H0A ' 寄存器数量低字节
frame(6) = &HC5 ' CRC 低字节
frame(7) = &HCD ' CRC 高字节
sp.WriteData frame接收并解析二进制帧
vb
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 响应至少5字节
' 解析 Modbus 响应
Dim slaveAddr As Byte
Dim funcCode As Byte
slaveAddr = buf(0)
funcCode = buf(1)
If funcCode And &H80 Then
' 异常响应
Dim errCode As Byte
errCode = buf(2)
Debug.Print "Modbus 异常: " & errCode
Else
' 正常响应
Dim byteCount As Byte
byteCount = buf(2)
Debug.Print "收到 " & byteCount & " 字节数据"
End If
End Sub十六进制显示调试
vb
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 Sub虚拟串口与回环测试
虚拟串口软件
在没有物理串口的开发环境中,可使用虚拟串口软件创建串口对:
- com0com(免费开源)
- Virtual Serial Port Driver(商业软件)
- VSPD(商业软件)
安装后创建虚拟串口对(如 COM3 ↔ COM4),向 COM3 发送的数据会出现在 COM4 的接收缓冲区。
回环测试代码
vb
Public Sub TestLoopback()
Dim spTx As New cSerialPort
Dim spRx As New cSerialPort
' 打开虚拟串口对的两个端口
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 "打开 COM3 失败"
Exit Sub
End If
If Not spRx.OpenPort() Then
Debug.Print "打开 COM4 失败"
spTx.ClosePort
Exit Sub
End If
' 清空缓冲区
spTx.PurgeAll
spRx.PurgeAll
' 发送
Dim testStr As String
testStr = "LOOPBACK_TEST_12345"
spTx.WriteText testStr
Debug.Print "发送: " & testStr
' 等待数据到达
Dim t As Single
t = Timer
Do While spRx.InBufferCount < Len(testStr) And (Timer - t) < 2
DoEvents
Loop
' 接收
Dim received As String
received = spRx.ReadExisting()
Debug.Print "接收: " & received
If received = testStr Then
Debug.Print "[PASS] 回环测试成功"
Else
Debug.Print "[FAIL] 回环测试失败"
End If
spTx.ClosePort
spRx.ClosePort
End Sub动态配置管理
运行时修改配置
vb
sp.OpenPort "COM3"
' 修改波特率
sp.Config.BaudRate = br115200
sp.ApplyConfig ' 手动应用到已打开端口
' 从端口读取实际配置
sp.RefreshConfig
Debug.Print "实际波特率: " & sp.Config.BaudRate配置克隆
vb
Dim cfg1 As New cSerialConfig
cfg1.BaudRate = br115200
cfg1.Parity = ptEven
' 克隆配置(互不影响)
Dim cfg2 As cSerialConfig
Set cfg2 = cfg1.Clone()
cfg2.BaudRate = br9600
Debug.Print cfg1.BaudRate ' 仍为 115200模式字符串
vb
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 实践
完整的 Modbus RTU 主站请求
vb
Public Function ModbusReadHoldingRegisters( _
ByVal sp As cSerialPort, _
ByVal SlaveAddr As Byte, _
ByVal StartAddr As Integer, _
ByVal Quantity As Integer) As Byte()
' 构建请求帧
Dim frame(7) As Byte
frame(0) = SlaveAddr
frame(1) = &H03 ' 功能码:读保持寄存器
frame(2) = (StartAddr And &HFF00) \ &H100
frame(3) = StartAddr And &HFF
frame(4) = (Quantity And &HFF00) \ &H100
frame(5) = Quantity And &HFF
' 计算 CRC16
Dim crc As Long
crc = CalcCRC16(frame, 6)
frame(6) = crc And &HFF
frame(7) = (crc And &HFF00) \ &H100
' 清空缓冲区并发送
sp.PurgeAll
sp.WriteData frame
' 等待响应(超时1秒)
Dim t As Single
t = Timer
Do While sp.InBufferCount < 5 And (Timer - t) < 1
DoEvents
Loop
' 读取响应
Dim buf() As Byte
Dim n As Long
n = sp.ReadData(buf)
If n >= 5 Then
ModbusReadHoldingRegisters = buf
End If
End Function使用示例
vb
Dim sp As New cSerialPort
sp.PortName = "COM3"
sp.Config.BaudRate = br9600
sp.Config.DataBits = 8
sp.Config.Parity = ptEven ' Modbus RTU 通常使用偶校验
sp.Config.StopBits = sb1
sp.OpenPort
' 读取从站1的保持寄存器,起始地址0,数量10
Dim response() As Byte
response = ModbusReadHoldingRegisters(sp, 1, 0, 10)
If UBound(response) >= 0 Then
' 解析响应数据
Dim byteCount As Byte
byteCount = response(2)
Debug.Print "收到 " & byteCount & " 字节数据"
End If
sp.ClosePort最后更新: 2026-07-05