一、系统总架构
plaintext
PC USB 主机
│
▼
┌─────────────────────┐
│ 板1 │
│ 上行USB ◄── PC │
│ 下行USB ──► 板2 │
│ 本地USB ──► 4口USB转串口芯片 CH348
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板2 │
│ 上行USB ◄── 板1 │
│ 下行USB ──► 板3 │
│ 本地USB ──► 4口USB转串口芯片 CH348
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板3 │
│ 上行USB ◄── 板2 │
│ 下行USB ──► 板4 │
│ 本地USB ──► 4口USB转串口芯片 CH348
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板4 │
│ 上行USB ◄── 板3 │
│ 下行USB ──► 空(末端)│
│ 本地USB ──► 4口USB转串口芯片 CH348
└─────────────────────┘
说明:
二、方案一:CH348 烧录唯一序列号 SN 方案
1. 结构示意图
plaintext
PC USB 主机
│
▼
┌─────────────────────┐
│ 板1 │
│ 上行USB ◄── PC │
│ 下行USB ──► 板2 │
│ 本地USB ──► CH348(SN=BOARD01)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板2 │
│ 上行USB ◄── 板1 │
│ 下行USB ──► 板3 │
│ 本地USB ──► CH348(SN=BOARD02)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板3 │
│ 上行USB ◄── 板2 │
│ 下行USB ──► 板4 │
│ 本地USB ──► CH348(SN=BOARD03)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板4 │
│ 上行USB ◄── 板3 │
│ 下行USB ──► 空(末端)│
│ 本地USB ──► CH348(SN=BOARD04)
└─────────────────────┘
2. 逻辑关系
3. C++ 核心实现代码
cpp
运行
#include <Windows.h>
#include <SetupAPI.h>
#include <string>
#include <vector>
using namespace std;
struct ComInfo
{
wstring comName;
wstring sn;
int interfaceId;
int boardId;
int uartId;
};
vector<ComInfo> comList;
int SnToBoardId(const wstring& sn)
{
if (sn == L"BOARD01") return 1;
if (sn == L"BOARD02") return 2;
if (sn == L"BOARD03") return 3;
if (sn == L"BOARD04") return 4;
return 0;
}
void EnumAllCH348Com()
{
comList.clear();
HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT,
nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDevInfo == INVALID_HANDLE_VALUE) return;
SP_DEVICE_INTERFACE_DATA ifData = { 0 };
ifData.cbSize = sizeof(ifData);
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, nullptr,
&GUID_DEVINTERFACE_COMPORT, i, &ifData); i++)
{
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &ifData, nullptr, 0, &reqSize, nullptr);
vector<BYTE> detailBuf(reqSize);
PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)detailBuf.data();
pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
SP_DEVINFO_DATA devData = { 0 };
devData.cbSize = sizeof(devData);
if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, &ifData, pDetail, reqSize, nullptr, &devData))
continue;
wstring devicePath = pDetail->DevicePath;
int infNum = -1;
if (devicePath.find(L"&0") != wstring::npos) infNum = 0;
else if (devicePath.find(L"&1") != wstring::npos) infNum = 1;
else if (devicePath.find(L"&2") != wstring::npos) infNum = 2;
else if (devicePath.find(L"&3") != wstring::npos) infNum = 3;
if (infNum < 0) continue;
wstring sn;
size_t snPos = devicePath.find(L"SN_");
if (snPos != wstring::npos)
sn = devicePath.substr(snPos + 3, 8);
wchar_t* pCom = wcsstr(devicePath.c_str(), L"COM");
if (!pCom) continue;
ComInfo info;
info.comName = pCom;
info.sn = sn;
info.interfaceId = infNum;
info.boardId = SnToBoardId(sn);
info.uartId = infNum + 1;
comList.push_back(info);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
}
三、方案二:4bit GPIO 硬件地址方案(不烧 SN、不加 MCU)
1. 结构示意图
plaintext
PC USB 主机
│
▼
┌─────────────────────┐
│ 板1 │
│ 上行USB ◄── PC │
│ 下行USB ──► 板2 │
│ 本地USB ──► CH348(GPIO硬件地址:0001)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板2 │
│ 上行USB ◄── 板1 │
│ 下行USB ──► 板3 │
│ 本地USB ──► CH348(GPIO硬件地址:0010)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板3 │
│ 上行USB ◄── 板2 │
│ 下行USB ──► 板4 │
│ 本地USB ──► CH348(GPIO硬件地址:0011)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ 板4 │
│ 上行USB ◄── 板3 │
│ 下行USB ──► 空(末端)│
│ 本地USB ──► CH348(GPIO硬件地址:0100)
└─────────────────────┘
2. 逻辑关系
3. C++ 核心实现代码
cpp
运行
#include <Windows.h>
#include "CH348DLL.H"
#pragma comment(lib,"CH348DLL.LIB")
#include <vector>
#include <string>
using namespace std;
struct BoardPort
{
int boardAddr;
int uartId;
wstring comName;
};
vector<BoardPort> boardList;
int GpioToBoard(BYTE gpioData)
{
int addr = gpioData & 0x0F;
if (addr == 1) return 1;
if (addr == 2) return 2;
if (addr == 3) return 3;
if (addr == 4) return 4;
return 0;
}
void ScanAllCH348Board()
{
boardList.clear();
DWORD devCount = 0;
CH348GetDevices(&devCount);
if (devCount == 0) return;
for (DWORD i = 0; i < devCount; i++)
{
HANDLE hDev = CH348Open(i);
if (hDev == INVALID_HANDLE_VALUE) continue;
BYTE gpio = 0;
CH348GetGPIO(hDev, &gpio);
int board = GpioToBoard(gpio);
wchar_t comName[32];
for (int p = 0; p < 4; p++)
{
CH348GetComName(hDev, p, comName);
BoardPort bp;
bp.boardAddr = board;
bp.uartId = p + 1;
bp.comName = comName;
boardList.push_back(bp);
}
CH348Close(hDev);
}
}
四、两套方案对比总结
方案一:烧录 SN 方案
- 优点:无需改动硬件 GPIO 电路
- 缺点:必须单块单独烧录 SN,不可级联烧录
- 识别方式:USB 枚举→读 SN+Interface 号完成映射
方案二:4bit GPIO 硬件地址方案
- 优点:不烧 SN、硬件统一、支持级联识别、无需额外 MCU
- 缺点:需硬件设计 4bit GPIO 上下拉电路
- 识别方式:USB 枚举 CH348→读 GPIO 地址→匹配 4 路串口




