使用 Ch9338 文件传输

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 下载

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注