VspdCh340.Lib
1.2.2
dotnet add package VspdCh340.Lib --version 1.2.2
NuGet\Install-Package VspdCh340.Lib -Version 1.2.2
<PackageReference Include="VspdCh340.Lib" Version="1.2.2" />
<PackageVersion Include="VspdCh340.Lib" Version="1.2.2" />
<PackageReference Include="VspdCh340.Lib" />
paket add VspdCh340.Lib --version 1.2.2
#r "nuget: VspdCh340.Lib, 1.2.2"
#:package VspdCh340.Lib@1.2.2
#addin nuget:?package=VspdCh340.Lib&version=1.2.2
#tool nuget:?package=VspdCh340.Lib&version=1.2.2
VspdCh340.Lib - 虚拟串口数据转发类库
基于 com0com 驱动和 TLV 帧结构的多路串口数据转发系统。实现 CH340 USB 主串口与 12 个子虚拟串口(CNCB81-CNCB92)之间的双向数据透明传输。
功能特性
核心功能
- 多路串口管理:同时管理 12 路虚拟串口(CNCB81-CNCB92)和 1 路 CH340 USB 主串口
- 双向数据转发:支持从子串口到主串口,以及主串口到子串口的双向数据传输
- 自动驱动安装:自动检测并安装 com0com 虚拟串口驱动,无需手动配置
- 端口配置验证:安装前自动验证端口配置,避免重复安装
- CH340 自动识别:通过特征匹配自动检测 CH340 USB 转串口设备
- NuGet 包支持:封装为标准 NuGet 包,包含 MSBuild targets 自动部署驱动文件
数据链路层特性
- TLV 帧结构:采用
SOF + TYPE + ADDR + LEN + DATA + CRC16的 TLV 帧格式 - 滑动窗口重同步:帧起始符(0xAA)自动定位,支持抗粘包/分段/干扰
- CRC16-Modbus 校验:2 字节 CRC 校验确保数据完整性
- 数据合并发送:支持超时合并和长度阈值合并,减少数据包数量
性能优化
- 独立线程监听:每个串口分配独立监听线程,避免线程池资源竞争
- 非阻塞 I/O:使用
RJCP.SerialPortStream高性能串口库 - 超时控制:50ms 读取超时,支持灵活的超时策略
- 缓存队列保护:CDC 接收使用 8192 字节 FIFO 缓冲,防止数据丢失
可靠性保障
- 线程安全设计:使用
Volatile.Read/Write和ManualResetEventSlim实现线程安全 - 活动状态监测:通过 Stopwatch 监测 CDC 读写活动状态(200ms 超时判定)
- 热插拔支持:驱动安装支持热插拔,无需重启系统
- 自动重连:CDC 串口断开后自动重试扫描和重连
系统要求
- Windows 10 / Windows 11 操作系统
- .NET 10.0 或更高版本
- CH340 USB 转串口设备(硬件模式)
- 管理员权限(驱动安装必需)
安装和引用
方式 1:通过 NuGet 包管理器安装
<PackageReference Include="VspdCh340.Lib" Version="1.1.0" />
方式 2:项目引用
<ItemGroup>
<ProjectReference Include="..\VspdCh340Lib\VspdCh340Lib.csproj" />
</ItemGroup>
自动部署功能
本 NuGet 包包含 VspdCh340.Lib.targets 文件,会在构建和发布时自动将 com0com 驱动文件复制到输出目录的 com0com 子文件夹中。VspdManager.Com0ComInstall() 方法可直接使用这些文件,无需额外配置。
基础用法
using VspdCh340.Lib;
var manager = new VspdManager();
manager.Start(Com0ComMode: true);
// 向 USB 主串口写入数据 (源端口 81, 数据)
manager.WritePack(81, data);
// 从指定端口读取数据 (超时 200ms)
var received = manager.ReadPack(81, 200);
manager.Stop();
核心 API
VspdManager
| 方法 | 说明 |
|---|---|
Start(bool Com0ComMode) |
启动所有串口监听;Com0ComMode=true 时自动安装驱动并启用子串口转发 |
Stop() |
停止所有串口监听并释放资源 |
WritePack(byte port, ReadOnlySpan<byte> data) |
向 USB 主串口写入指定端口的数据(自动封装 TLV 帧) |
ReadPack(byte port, int millisecondsTimeout) |
从指定端口读取接收到的数据,支持超时等待 |
Com0ComInstall() |
自动安装 com0com 虚拟串口驱动(静态方法) |
| 属性 | 说明 |
|---|---|
IsOpen |
USB 主串口(CDC)是否已打开 |
ReadFlag |
USB 读操作活动标志(200ms 内有读操作为 true) |
WriteFlag |
USB 写操作活动标志(200ms 内有写操作为 true) |
ModbusRtu
Modbus RTU 协议封装类,支持通过 VspdManager 完成 Modbus 寄存器数据的收发。
| 方法 | 功能码 | 说明 |
|---|---|---|
ReadHoldingRegisters(byte port, byte id, ushort addr, ushort qty, int timeout) |
0x03 | 读取保持寄存器 |
ReadInputRegisters(byte port, byte id, ushort addr, ushort qty, int timeout) |
0x04 | 读取输入寄存器 |
WriteRegister(byte port, byte id, ushort addr, ushort value, int timeout) |
0x06 | 写入单个保持寄存器 |
WriteRegisters(byte port, byte id, ushort addr, ReadOnlySpan<ushort> values, int timeout) |
0x10 | 写入多个保持寄存器 |
参数说明:
port: 硬件串口号,有效范围 81-92id: Modbus 从站地址,范围 1-247(0 为广播写)addr: 寄存器起始地址qty: 读取/写入数量(读寄存器:1-125;写多寄存器:1-123)value/values: 要写入的寄存器值timeout: 等待响应的超时时间(毫秒),默认 300
MbResult 返回值:
| 属性 | 说明 |
|---|---|
Success |
通信及响应解析是否成功 |
ErrorCode |
Modbus 异常码;非异常响应为 0 |
Frame |
从站返回的完整响应帧;超时或广播写入时为空数组 |
Registers |
寄存器值数组;读寄存器时有效,写操作为空数组 |
使用示例:
using VspdCh340.Lib;
var manager = new VspdManager();
manager.Start(Com0ComMode: true);
var modbus = new ModbusRtu(manager);
// 读取保持寄存器 (端口81, 地址0, 数量10)
var result = modbus.ReadHoldingRegisters(81, 1, 0, 10);
if (result.Success)
{
foreach (var reg in result.Registers)
Console.WriteLine($"寄存器值: {reg}");
}
// 写入单个寄存器
var writeResult = modbus.WriteRegister(81, 1, 0, 0x1234);
if (writeResult.Success)
Console.WriteLine("写入成功");
manager.Stop();
帧结构说明
本类库采用带起始符、不转义、纯字节流、滑动窗口重同步的帧结构设计,适配串口通信的抗粘包/分段/干扰需求。
帧格式
┌──────┬──────┬──────┬──────┬──────────┬────────┐
│ SOF │ TYPE │ ADDR │ LEN │ DATA[] │ CRC16 │
│ 1B │ 1B │ 1B │ 1B │ 0–240B │ 2B │
└──────┴──────┴──────┴──────┴──────────┴────────┘
帧顺序:AA + TYPE + ADDR + LEN + DATA(≤240) + CRC16
字段语义
TLV 数据结构:本帧数据部分格式遵循CCITT(现 ITU-T)组织的 TLV(Type-Length-Value)编码原则。理论上ADDR应该在TYPE之前,但是为了与之前通信协议更好兼容,TYPE和ADDR保持当前位置。
| 字段 | 长度 | 说明 |
|---|---|---|
| SOF | 1B | 帧起始字节,固定为 0xAA |
| TYPE | 1B | 业务类型/命令字标识,对应 TLV 的 T(Type)部分 |
| ADDR | 1B | 从机地址,范围 81–82,0x00 用于广播,0xF0–0xFF 保留 |
| LEN | 1B | DATA 长度,范围 0–240,不包含帧头和 CRC,对应 TLV 的 L(Length)部分 |
| DATA | 0–240B | 不定长有效载荷,可裸传二进制数据,32bit 对齐(便于 32bit MCU 直接进行数据读写),对应 TLV 的 V(Value)部分 |
| CRC16 | 2B | 覆盖 SOF+TYPE+ADDR+LEN+DATA,小端序(低字节在前),CRC16-Modbus(多项式 0x8005,初始 0xFFFF) |
设计要点
本设计依赖四条防线保证可靠性:
- SOF 起始符:固定
0xAA作为帧同步标识,快速定位帧头 - LEN 硬截断:单帧数据最多 240B,不会"跑飞"
- ADDR 范围限制:仅 0x01–0xEF 有效,天然过滤非法帧
- CRC16 校验:覆盖整个帧内容,检测数据损坏
OSI 模型层次分析
本帧结构在国际标准化组织(ISO)的 OSI 七层模型中跨越多个层次,实现了从物理传输到应用协议的完整封装:
| OSI 层次 | 对应实现 | 主要职责 |
|---|---|---|
| 物理层 (L1) | RS-485/RS-232 串口 | 电信号传输、波特率配置、8N1 帧格式 |
| 数据链路层 (L2) | SOF + CRC16 + 滑动重同步 | 帧封装/解封装、错误检测、帧同步 |
| 网络层 (L3) | ADDR 从机地址判断或分发 | 节点寻址、广播支持 |
| 传输层 (L4) | 按照 TLV 的 T 进行功能分发,LEN 长度控制 | 数据分发 |
| 会话层 (L5) | 未实现 | 本协议为简单串口数据,无需会话连接/断开管理 |
| 表示层 (L6) | XOR 转码 (0x3E) | 数据载荷使用固定密钥 XOR 0x3E 进行简单加密/解密 |
| 应用层 (L7) | LEN + DATA (TLV的LV 结构) | 业务命令的数据载荷处理 |
各层详细说明
1. 物理层 (Physical Layer)
- 介质:RS-485 半双工 / RS-232 全双工 / 虚拟串口
- 波特率:可配置(典型 9600/19200/115200)
- 硬件:com0com 虚拟串口或物理串口适配器
2. 数据链路层 (Data Link Layer)
- 帧同步:
0xAA起始符快速定位帧边界 - 数据长度:通过 LEN 字段计算帧总长度
- 错误检测:CRC16-Modbus 覆盖完整帧
- 滑动重同步:逐字节试探,失败仅丢弃 1 字节
3. 网络层 (Network Layer)
- 单播:ADDR
0x01–0xEF(最多 239 个从机节点) - 广播:ADDR
0x00(所有节点接收处理) - 预留:ADDR
0xF0–0xFF(协议扩展、心跳、Bootloader)
4. 传输层 (Transport Layer)
- 数据分发:根据 TLV 的 TYPE 字段将 DATA 分发到不同的业务处理模块
- 长度限制:DATA 最大 240B,防止单帧过大
6. 表示层 (Presentation Layer)
- XOR 转码:数据载荷使用固定密钥
0x3E进行简单的 XOR 加密/解密 - 加密方向:
- 发送时:原始数据 → XOR 0x3E → 传输
- 接收时:传输数据 → XOR 0x3E → 原始数据
- 特点:相同的密钥用于加密和解密,转码可逆
7. 应用层 (Application Layer)
- 采用 TLV(Type-Length-Value)编码原则
- 根据 L = LEN、V = DATA 进行数据载荷的处理
层次交互流程
应用层数据
↓ (TLV 编码的载荷)
表示层 XOR 0x3E 加密
↓ (转码处理)
传输层分段/TYPE重组
↓ (长度控制)
网络层寻址
↓ (ADDR 路由)
数据链路层帧封装
↓ (SOF + CRC)
物理层电信号传输
↓ (RS-485 差分信号)
物理层电信号接收
↓ (位同步)
数据链路层帧解封装 + CRC 校验
↓ (滑动重同步)
网络层地址过滤
↓ (ADDR 匹配)
传输层数据合并
↓ (TYPE匹配)
表示层 XOR 0x3E 解密
↓ (转码还原)
应用层数据
接收/重同步逻辑
采用 5 态状态机实现逐字节滑动重同步:
IDLE → GOT_SOF → GOT_TYPE → GOT_ADDR → GOT_LEN → GOT_DATA ──┐
↑ │ CRC OK
└────────────────────────────────── CRC FAIL / LEN 超界 ──┘
逐字节滑动重同步要点
| 状态 | 行为 |
|---|---|
| IDLE | 收到 0xAA → 进入 GOT_SOF;其他字节丢弃 |
| GOT_SOF | 下一字节作为 TYPE,进入 GOT_TYPE |
| GOT_TYPE | 下一字节作为 ADDR,进入 GOT_ADDR |
| GOT_ADDR | 下一字节作为 ADDR,进入 GOT_LEN |
| GOT_LEN | 若 LEN > 240 → 滑动 1B,回到 IDLE 重新同步 |
| GOT_DATA | 收齐 LEN 字节后接收 2B CRC |
| CRC OK | 交付帧,所有帧数据出队 |
| CRC FAIL | 仅丢弃 1 字节(原 SOF),窗口滑动重试 |
抗干扰设计
| 干扰类型 | 处理方式 |
|---|---|
| 粘包 | SOF 定位帧头 + LEN + CRC 双重把关 |
| 分段 | 半包停在 GOT_DATA 等待后续数据,不丢失上下文 |
| 数据损坏 | CRC16 校验失败时只丢弃 1 字节,自动重新同步 |
| 串行干扰 | SOF + LEN 范围 + CRC 三重校验,干扰字节流中可自修复 |
优劣分析
| 维度 | 优点 | 缺点 |
|---|---|---|
| 性能 | 不转义,吞吐高、CPU 省、实现简单 | CRC16 计算有一定开销 |
| 数据灵活性 | DATA 可裸传二进制(结构体/图片碎片等) | 无 |
| 可靠性 | LEN 硬上限 240,单帧不会"跑飞" | LEN 域被破坏时最多吞 245B |
| 同步效率 | 有 SOF 起始符,首次同步快 | 数据中出现 0xAA 可能误触发同步 |
| 抗干扰 | 滑动 1B 重同步 + 三重校验,嘈杂环境表现优异 | 需完整接收一帧才能验证有效性 |
| 扩展性 | ADDR 预留 0xFx 空间,TYPE 可扩展多种命令 | 帧格式固定,修改需两端同步 |
使用方法
- 引用
VspdCh340.Lib类库 - 创建
VspdManager实例并调用Start(Com0ComMode: true)启动 - 如需虚拟串口模式,类库会自动检测并安装 com0com 驱动
- 通过
WritePack()/ReadPack()方法进行数据收发
虚拟串口配置
应用程序创建以下虚拟串口对,使用 PlugInMode 和 HiddenMode 优化配置:
| 端口 A | 端口 B | ADDR 值 | 配置 | 说明 |
|---|---|---|---|---|
| COM81 | CNCB81 | 0x51 (81) |
PlugInMode=yes, HiddenMode=yes |
UART 端口 1 |
| COM82 | CNCB82 | 0x52 (82) |
PlugInMode=yes, HiddenMode=yes |
UART 端口 2 |
| COM83 | CNCB83 | 0x53 (83) |
PlugInMode=yes, HiddenMode=yes |
UART 端口 3 |
| COM84 | CNCB84 | 0x54 (84) |
PlugInMode=yes, HiddenMode=yes |
UART 端口 4 |
| COM85 | CNCB85 | 0x55 (85) |
PlugInMode=yes, HiddenMode=yes |
UART 端口 5 |
| COM86 | CNCB86 | 0x56 (86) |
PlugInMode=yes, HiddenMode=yes |
UART 端口 6 |
| COM87 | CNCB87 | 0x57 (87) |
PlugInMode=yes, HiddenMode=yes |
UART 端口 7 |
| COM88 | CNCB88 | 0x58 (88) |
PlugInMode=yes, HiddenMode=yes |
MODBUS 端口 8 |
| COM89 | CNCB89 | 0x59 (89) |
PlugInMode=yes, HiddenMode=yes |
IIC 端口 9 |
| COM90 | CNCB90 | 0x5A (90) |
PlugInMode=yes, HiddenMode=yes |
SPI 端口 10 |
| COM91 | CNCB91 | 0x5B (91) |
PlugInMode=yes, HiddenMode=yes |
CAN2.0 端口 11 |
| COM92 | CNCB92 | 0x5C (92) |
PlugInMode=yes, HiddenMode=yes |
CAN2.0 端口 12 |
| COM93 | COM94 | — | — | 双向端口对(预留) |
| COM95 | COM96 | — | — | 双向端口对(预留) |
帧结构本应用中的实现说明
TYPE 字段(0xDF):标识 COM 端口转发协议,便于多协议混合场景识别。
ADDR 字段:直接映射到监听的 12 个虚拟串口(CNCB81-CNCB92),值为 81-92。
帧格式示例
当从 CNCB81(ADDR=81)收到 3 字节数据 0x11 0x22 0x33 时,转发到 USB 主串口的帧为:
AA DF 51 03 11 22 33 [CRC低字节] [CRC高字节]
↑ ↑ ↑ ↑ └─数据─┘
│ │ │ └── LEN=3
│ │ └── ADDR=81 (CNCB81)
│ └── TYPE=0xDF (COM转发协议)
└── SOF=0xAA
核心类说明
| 类名 | 职责 | 关键字段 |
|---|---|---|
| VspdManager | 串口管理和数据转发核心 | subPorts[12] (子串口数组), cdcPort (CH340 USB端口), readBufs[12] (接收缓冲区), readEvents[12] (事件通知), cdcReadSw/cdcWriteSw (活动监测) |
| ModbusRtu | Modbus RTU 协议门面类 | uart (VspdManager 实例), 支持功能码 03/04/06/10 |
| NcBus | NC 总线协议封装 | vspdManager (VspdManager 实例) |
数据流向
┌─────────────────────────────────────────────────────────────────┐
│ 子串口 → USB 主串口 (Write 路径) │
│ │
│ CNCB81-CNCB92 → 独立监听线程 → 数据合并 → TLV 帧封装 │
│ ↓ │
│ CH340 USB 主串口 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ USB 主串口 → 子串口 (Read 路径) │
│ │
│ CH340 USB 主串口 → FIFO 缓冲队列 → 帧解析 + CRC 校验 │
│ ↓ │
│ CNCB81-CNCB92 │
│ ↓ │
│ 数据载荷分发到目标端口 │
└─────────────────────────────────────────────────────────────────┘
技术栈
- 目标框架: net10.0-windows
- 语言: C# (latest)
- 虚拟串口驱动: com0com
- 串口通信库: RJCP.SerialPortStream (子串口), UartCtl (CH340 USB)
- CRC 校验: CRC16-Modbus
- 依赖项:
RJCP.SerialPortStream3.0.5UartCtl4.0.0
串口库选择说明
本项目舍弃了 .NET 自带的 System.IO.Ports.SerialPort 库,主要原因是:
- 无法打开特殊名称的串口:
System.IO.Ports对串口名称有严格限制,无法正确识别和打开CNCB81-CNCB92这类特殊命名的虚拟串口 - 兼容性问题:
System.IO.Ports在处理 com0com 创建的虚拟串口时存在兼容性问题
许可证
本项目使用 MIT 许可证。
注意:本项目包含 com0com 驱动,该驱动采用 GPL 许可证分发。详见 com0com 许可证。
免责声明
本应用程序会安装设备驱动并修改系统设置。请自行承担使用风险。运行前建议备份系统。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- RJCP.SerialPortStream (>= 3.0.5)
- UartCtl (>= 4.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.