WCH 推出Ch9338 双机互联芯片,前面有介绍和测试过。这次介绍通过编程的方式实现双机文件互传。
在 Ch9338 的 EVT Package 中,有一份《通过CH9338 透传自定义数据说明》。本文基于该文档编写。
调用流程就是文档中描述:

我们编写一个 Windows Console 代码。
// Ch9338Test.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <Windows.h>
#include <tchar.h>
#include <Dbt.h>
#include "CH375DLL.H"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#pragma warning(disable:4996)
extern "C"
{
#include "setupapi.h"
}
#pragma comment(lib,"setupapi")
#pragma comment(lib, "WCHKMFU")
//记录设备序号,查找设备时记录
ULONG nDevIndex = 0;
//记录设备路径,查找设备时记录
CHAR szDevicePath[MAX_DEVICE_PATH_SIZE] = "";
//设备句柄
HANDLE hDev = INVALID_HANDLE_VALUE;
//设备的ID
#define szDevID_CH9338_U2 "VID_1A86&PID_8026&MI_01"
#define szDevID_CH9339_U2 "VID_1A86&PID_802A&MI_01"
#define szDevID_CH9339_U3 "VID_1A86&PID_802D&MI_01"
// 如果没有stdint.h,自己定义
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
// 包类型定义
#define TYPE_FILE_INFO 1
#define TYPE_DATA 2
#define TYPE_ACK 3
// 包结构
#pragma pack(1)
typedef struct {
uint16_t type;
uint16_t seq;
uint16_t length;
uint16_t checksum;
} PacketHeader;
#pragma pack()
#define MAX_PACKAGE 1024*16
// 最大数据包大小
#define MAX_DATA_SIZE (MAX_PACKAGE-sizeof(PacketHeader))
#define MAX_FILENAME 256
typedef struct {
uint16_t type;
uint16_t seq;
uint16_t length;
uint16_t checksum;
uint8_t data[MAX_DATA_SIZE];
} SimplePacket;
BOOL SearchDevice()
{
PCHAR lpDevName = NULL;
CHAR szDevName[MAX_DEVICE_PATH_SIZE] = "";
//查找具有指定ID的设备
for (size_t i = 0; i < 16; i++)
{
//获取设备路径
lpDevName = (PCHAR)CH375GetDeviceName(i);
if (lpDevName != NULL)
{
strcpy_s(szDevName, MAX_DEVICE_PATH_SIZE, lpDevName);
CharUpperBuffA(szDevName, strlen(szDevName));
//检查是否具有指定的ID,是则返回
if (strstr(szDevName, szDevID_CH9338_U2) != NULL ||
strstr(szDevName, szDevID_CH9339_U2) != NULL ||
strstr(szDevName, szDevID_CH9339_U3) != NULL)
{
nDevIndex = i;
strcpy_s(szDevicePath, MAX_DEVICE_PATH_SIZE, szDevName);
return TRUE;
}
}
}
return FALSE;
}
//打开设备
BOOL OpenDevice()
{
//打开指定设备
hDev = CH375OpenDevice(nDevIndex);
if (hDev != INVALID_HANDLE_VALUE)
{
return TRUE;
}
return FALSE;
}
// 显示使用方法
void ShowUsage(void) {
printf("用法:\n");
printf("发送文件: Ch9338Test.exe -s <串口> <文件路径>\n");
printf("接收文件: Ch9338Test.exe -r <串口>\n");
printf("例如:\n");
printf(" Ch9338Test.exe -s test.txt\n");
printf(" Ch9338Test.exe -r\n");
}
// 计算校验和
unsigned short CalculateChecksum(const uint8_t* data, uint16_t length) {
uint16_t sum = 0;
int i;
for (i = 0; i < length; i++) {
sum += data[i];
}
return sum;
}
// 创建数据包
void CreatePacket(SimplePacket* pkt, uint16_t type, uint16_t seq,
const uint8_t* data, uint16_t length) {
pkt->type = type;
pkt->seq = seq;
pkt->length = length;
if (data && length > 0) {
memcpy(pkt->data, data, length);
}
pkt->checksum = CalculateChecksum(pkt->data, length);
}
// 打包数据包
int PackPacket(const SimplePacket* pkt, uint8_t* buffer) {
PacketHeader header;
header.type = pkt->type;
header.seq = pkt->seq;
header.length = pkt->length;
header.checksum = pkt->checksum;
memcpy(buffer, &header, sizeof(PacketHeader));
if (pkt->length > 0) {
memcpy(buffer + sizeof(PacketHeader), pkt->data, pkt->length);
}
return sizeof(PacketHeader) + pkt->length;
}
// 解包数据包
int UnpackPacket(const unsigned char* buffer, int bufferSize, SimplePacket* pkt) {
if (bufferSize < sizeof(PacketHeader)) {
return 0; // 数据不足
}
PacketHeader header;
memcpy(&header, buffer, sizeof(PacketHeader));
if (bufferSize < sizeof(PacketHeader) + header.length) {
return 0; // 数据不完整
}
pkt->type = header.type;
pkt->seq = header.seq;
pkt->length = header.length;
pkt->checksum = header.checksum;
if (header.length > 0) {
memcpy(pkt->data, buffer + sizeof(PacketHeader), header.length);
}
// 验证校验和
unsigned short calcChecksum = CalculateChecksum(pkt->data, pkt->length);
if (calcChecksum != pkt->checksum) {
return 0; // 校验失败
}
return 1; // 成功
}
// 发送数据
int SendData(const uint8_t* data, uint32_t length) {
DWORD bytesWritten=length;
if (!CH375WriteEndP((ULONG)hDev, 1, (PVOID) data, (PULONG)&bytesWritten)) {
printf("发送数据失败\n");
return 0;
}
return (bytesWritten == length);
}
// 接收完整的数据包
int ReceivePacket(SimplePacket* pkt) {
unsigned char headerBuffer[sizeof(PacketHeader)];
DWORD bytesRead = MAX_PACKAGE;
if (!CH375SetBufUploadEx((ULONG)hDev, 1, 1, MAX_PACKAGE)) {
printf("CH375SetBufUploadEx failed\n");
return 0;
}
CH375ReadEndP((ULONG)hDev, 1, pkt, &bytesRead);
while (bytesRead == 0) {
bytesRead = MAX_PACKAGE;
if (!CH375ReadEndP((ULONG)hDev, 1, pkt, &bytesRead)) {
printf("CH375ReadEndP error\n");
return 0;
}
Sleep(1);
}
if (!CH375SetBufUploadEx((ULONG)hDev, 0, 1, MAX_PACKAGE)) {
printf("CH375SetBufUploadEx failed\n");
return 0;
}
return UnpackPacket((unsigned char *)pkt, sizeof(PacketHeader) + pkt->length, pkt);
}
// 等待确认包
int WaitForAck() {
SimplePacket ackPkt;
if (!ReceivePacket(&ackPkt)) {
return 0;
}
return ackPkt.type == TYPE_ACK;
}
// 发送文件
int SendFile(const char* filepath) {
FILE* file;
SimplePacket pkt;
unsigned char buffer[MAX_DATA_SIZE];
unsigned char packetBuffer[sizeof(PacketHeader) + MAX_DATA_SIZE];
char filename[MAX_FILENAME];
char fileInfo[MAX_FILENAME + 32];
long filesize;
unsigned short seq = 0;
size_t bytesRead;
long totalSent = 0;
int packetSize;
clock_t start = clock();
// 打开文件
file = fopen(filepath, "rb");
if (!file) {
printf("无法打开文件: %s\n", filepath);
return 0;
}
// 获取文件大小
fseek(file, 0, SEEK_END);
filesize = ftell(file);
fseek(file, 0, SEEK_SET);
// 提取文件名
const char* lastSlash = strrchr(filepath, '\\');
if (!lastSlash) lastSlash = strrchr(filepath, '/');
if (lastSlash) {
strcpy(filename, lastSlash + 1);
}
else {
strcpy(filename, filepath);
}
// 1. 发送文件信息
sprintf(fileInfo, "%s|%ld", filename, filesize);
CreatePacket(&pkt, TYPE_FILE_INFO, 0, (unsigned char*)fileInfo, strlen(fileInfo));
packetSize = PackPacket(&pkt, packetBuffer);
printf("发送文件信息: %s\n", fileInfo);
if (!SendData(packetBuffer, packetSize)) {
printf("SendData Failed\n");
fclose(file);
return 0;
}
if (!WaitForAck()) {
printf("文件信息确认失败\n");
fclose(file);
return 0;
}
// 2. 发送文件数据
while (1) {
bytesRead = fread(buffer, 1, MAX_DATA_SIZE, file);
seq++;
CreatePacket(&pkt, TYPE_DATA, seq, buffer, (unsigned short)bytesRead);
packetSize = PackPacket(&pkt, packetBuffer);
printf("发送数据包 %d, 大小: %d 字节\n", seq, (int)bytesRead);
if (!SendData(packetBuffer, packetSize)) {
fclose(file);
return 0;
}
if (!WaitForAck()) {
printf("数据包 %d 确认失败\n", seq);
fclose(file);
return 0;
}
totalSent += bytesRead;
//printf("进度: %ld/%ld\n", totalSent, filesize);
// 如果是空数据包,表示结束
if (bytesRead == 0) {
break;
}
}
fclose(file);
clock_t end = clock();
printf("文件发送完成, 耗时 %.3fms 速度:%.2fKB/S!\n",
((double)(end - start) / CLOCKS_PER_SEC) * 1000.0,
filesize/1024/ ((double)(end - start) / CLOCKS_PER_SEC));
return 1;
}
// 接收文件
int ReceiveFile() {
SimplePacket pkt;
SimplePacket ackPkt;
unsigned char packetBuffer[sizeof(PacketHeader) + MAX_DATA_SIZE];
char filename[MAX_FILENAME];
char fileInfo[MAX_FILENAME + 32];
char* separator;
long filesize;
unsigned short expectedSeq = 1;
long totalReceived = 0;
FILE* outFile;
int packetSize;
// 1. 接收文件信息
printf("等待接收文件信息...\n");
if (!ReceivePacket(&pkt)) {
printf("未收到文件信息包\n");
return 0;
}
if (pkt.type != TYPE_FILE_INFO) {
printf("收到的不是文件信息包\n");
return 0;
}
// 解析文件信息
memcpy(fileInfo, pkt.data, pkt.length);
fileInfo[pkt.length] = '\0';
separator = strchr(fileInfo, '|');
if (!separator) {
printf("文件信息格式错误\n");
return 0;
}
*separator = '\0';
strcpy(filename, fileInfo);
filesize = atol(separator + 1);
printf("准备接收文件: %s, 大小: %ld 字节\n", filename, filesize);
// 发送确认
CreatePacket(&ackPkt, TYPE_ACK, 0, NULL, 0);
packetSize = PackPacket(&ackPkt, packetBuffer);
if (!SendData(packetBuffer, packetSize)) {
return 0;
}
// 2. 接收文件数据
outFile = fopen(filename, "wb");
if (!outFile) {
printf("无法创建输出文件: %s\n", filename);
return 0;
}
while (1) {
printf("等待数据包 %d...\n", expectedSeq);
if (!ReceivePacket(&pkt)) {
printf("接收数据包失败\n");
fclose(outFile);
return 0;
}
if (pkt.type != TYPE_DATA) {
printf("收到非数据包\n");
continue;
}
if (pkt.seq == expectedSeq) {
// 发送确认
CreatePacket(&ackPkt, TYPE_ACK, pkt.seq, NULL, 0);
packetSize = PackPacket(&ackPkt, packetBuffer);
SendData(packetBuffer, packetSize);
// 检查是否为空数据包(结束标志)
if (pkt.length == 0) {
printf("收到空数据包,传输结束\n");
break;
}
// 写入数据
fwrite(pkt.data, 1, pkt.length, outFile);
totalReceived += pkt.length;
expectedSeq++;
printf("收到数据包 %d, 进度: %ld/%ld\n", pkt.seq, totalReceived, filesize);
}
else {
printf("序列号错误,期望: %d, 收到: %d\n", expectedSeq, pkt.seq);
}
}
fclose(outFile);
printf("文件接收完成: %s\n", filename);
return 1;
}
int main(int argc, char* argv[])
{
// 参数不够,提示
if (argc < 2) {
ShowUsage();
return 1;
}
//查找设备
if (SearchDevice())
{
printf("%s\r\n", szDevicePath);
}
else
{
printf("无法找到 Ch9338 设备\r\n");
return 1;
}
//打开设备
if (OpenDevice())
{
//设置独占设备,防止其他进程操作此设备
if (!CH375SetExclusive((ULONG)hDev, 1))
{
printf("CH375SetExclusive Error\r\n");
}
printf("OpenDevice Successful\r\n");
}
else
{
printf("OpenDevice failed\r\n");
return 2;
}
const char* mode = argv[1];
if (strcmp(mode, "-s") == 0 || strcmp(mode, "/s") == 0) {
// 发送模式
if (argc < 3) {
printf("发送模式需要指定文件路径\n");
ShowUsage();
return 1;
}
const char* filepath = argv[2];
printf("%s\n", filepath);
if (SendFile(filepath)) {
printf("文件发送成功!\n");
}
else {
printf("文件发送失败!\n");
return 1;
}
}
else if (strcmp(mode, "-r") == 0 || strcmp(mode, "/r") == 0) {
//接收模式
if (ReceiveFile()) {
printf("文件接收成功!\n");
}
else {
printf("文件接收失败!\n");
return 1;
}
}
else {
printf("未知模式: %s\n", mode);
ShowUsage();
return 1;
}
//关闭设备
if (hDev != INVALID_HANDLE_VALUE)
{
CloseHandle(hDev);
hDev = INVALID_HANDLE_VALUE;
printf("Close device \r\n");
}
return 0;
}
通讯协议使用之前设计的。
特别注意的是:
1.代码依赖官方提供的 WCHKMFU.lib ,我拿到的只有 32位的,因此代码需要使用 x86 编译
2.根据 Ch375DLL.h 的信息,缓冲区可以最高开到 150MB。缓冲越大,传输效率越高,速度越快。不过我的代码堆限制了局部变量的最大值,如果想改的很大需要优化一些结构。
BOOL WINAPI CH375WriteEndP( // 写出数据块
ULONG iIndex, // 指定CH375设备序号
ULONG iEndP, // 端点号,有效值为1到8。
PVOID iBuffer, // 指向一个缓冲区,放置准备写出的数据
PULONG ioLength ); // 指向长度单元,输入时为准备写出的长度,返回后为实际写出的长度
完整的代码和EXE 下载