开源软件学习笔记 - nanoModbus

目录

[1. 修改工程](#1. 修改工程)

[2. 移植](#2. 移植)

[2.1 transport](#2.1 transport)

[2.2 arg](#2.2 arg)

[2.3 read](#2.3 read)

[2.4 write](#2.4 write)

[3. Server部分](#3. Server部分)

[3.1 查找设备](#3.1 查找设备)

[3.2 打开设备](#3.2 打开设备)

[3.3 设置串口参数](#3.3 设置串口参数)

[3.4 初始化nanoModbus](#3.4 初始化nanoModbus)

[3.5 Poll处理](#3.5 Poll处理)

[4. Client部分](#4. Client部分)

[4.1 查找设备](#4.1 查找设备)

[4.2 初始化nanoModbus](#4.2 初始化nanoModbus)

[4.3 设置Modbus从站设备的rtu地址](#4.3 设置Modbus从站设备的rtu地址)

[4.4 读写部分](#4.4 读写部分)


nanoModbus下载地址如下,基于v1.22.0github.comhttps://github.com/debevv/nanoMODBUS

硬件平台选择FT4232H Mini Module,该平台支持4个UART,将UART1和UART2分别作为Modbus主机和从机。

软件基于官方例程修改:https://ftdichip.com/wp-content/uploads/2020/07/VCP_EX.zip

1. 修改工程

在VCP_EX工程目录内新建nanoModbus_master文件夹,将VCP_EX文件夹中的VCP_EX.vcproj和VCP_EX.cpp拷贝过来,并修改文件名为nanoModbus_master.vcproj和nanoModbus_master.cpp,用文本编辑器修改nanoModbus_master.vcproj,将VCP_EX改为nanoModbus_master。

将其他文件h文件和c文件拷贝到文件夹内。

打开VCP_EX.sln,将nanoModbus_master.vcproj加入工程。将源文件加入工程,工程目录如下:

右键选中stdafx.cpp,属性界面内选择配置属性-->C/C++-->预编译头,在预编译头中选择"创建",而不是原来默认的"使用"(解决无法打开预编译头文件的问题)。

将nanoModbus的源文件(nanomodbus.c和nanomodbus.h)加入工程。和stdafx.cpp类似操作,nanomodbus.c属性在预编译头中选择"不使用预编译头"。

2. 移植

移植的核心是nmbs_platform_conf结构体的配置。

cpp 复制代码
typedef struct nmbs_platform_conf {
    nmbs_transport transport; /*!< Transport type */
    int32_t (*read)(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,
                    void* arg); /*!< Bytes read transport function pointer */
    int32_t (*write)(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms,
                     void* arg); /*!< Bytes write transport function pointer */
    uint16_t (*crc_calc)(const uint8_t* data, uint32_t length,
                         void* arg); /*!< CRC calculation function pointer. Optional */
    void* arg;                       /*!< User data, will be passed to functions above */
    uint32_t initialized; /*!< Reserved, workaround for older user code not calling nmbs_platform_conf_create() */
} nmbs_platform_conf;

transport - 传输类型,NMBS_TRANSPORT_RTU和NMBS_TRANSPORT_TCP。

Modbus RTU是一种基于串行通信的协议,通常使用RS-232或RS-485接口。其报文结构紧凑,包含从站地址、功能码、数据以及CRC校验码。RTU的通信效率较高,但对传输距离和设备数量有限制,通常适用于小型工业网络。

Modbus TCP运行在TCP/IP网络上,使用以太网进行通信。它将标准Modbus数据帧嵌入到TCP帧中,报文头部包含事务处理标识符、协议标识符、数据长度和单元标识符。由于TCP/IP的可靠性,Modbus TCP不需要额外的CRC校验。它支持更大的网络规模和更高的传输速度,适合复杂的工业自动化场景。

read - 读函数指针,用于向串口或TCP连接读数据。

write - 写函数指针,用于向串口或TCP连接写数据。

crc_calc - 校验函数指针,可选,一般不需要配置,nanoModbus会初始化为内部的CRC校验函数。

arg - 传递用户上下文信息。例如:串口句柄(如HANDLE类型)、套接字描述符(如SOCKET)、自定义配置结构体。

initialized - 解决旧代码未调用初始化函数的问题,新用户只需要调用nmbs_platform_conf_create自动初始化这个变量。

对于一般的应用,初始化transport、read、write和arg即可。当前版本的nanoModbus需要调用nmbs_platform_conf_create先初始化一下配置变量

cpp 复制代码
nmbs_platform_conf platform_conf;
nmbs_platform_conf_create(&platform_conf);
platform_conf.transport = NMBS_TRANSPORT_RTU;
platform_conf.read = ft_transport_read;
platform_conf.write = ft_transport_write;
platform_conf.arg = (void *)&nmbs_dev_ft;

2.1 transport

如果是串口,transport设置为NMBS_TRANSPORT_RTU,如果是TCP/IP,则设置为NMBS_TRANSPORT_TCP。

cpp 复制代码
platform_conf.transport = NMBS_TRANSPORT_RTU;

2.2 arg

根据自己平台定义一个用户数据结构体

cpp 复制代码
typedef struct {
	HANDLE hCommPort;
}nmbs_user_data_s;

nmbs_user_data_s nmbs_dev_ft;

这里是记录串口的句柄。

然后把这个变量赋值

cpp 复制代码
platform_conf.arg = (void *)&nmbs_dev_ft;

2.3 read

这里使用标准串口读函数,正确时返回的是读入字节数,错误则返回负数。

cpp 复制代码
int32_t ft_transport_read(uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg)
{
	nmbs_user_data_s *dev_ft = (nmbs_user_data_s*)arg;
	DWORD dwRead;
	BOOL fSuccess;

	if (byte_timeout_ms > 0)
	{
		COMMTIMEOUTS timeouts = { 0 };
		timeouts.ReadTotalTimeoutConstant = (DWORD)byte_timeout_ms;
		SetCommTimeouts(dev_ft->hCommPort, &timeouts);
	}

	fSuccess = ReadFile(dev_ft->hCommPort, buf, count, &dwRead, NULL);
	if (!fSuccess)
	{
		printf("Read Failed %d\n", GetLastError());
		return NMBS_ERROR_TRANSPORT;
	}
	return dwRead;
}

2.4 write

和读类似

cpp 复制代码
int32_t ft_transport_write(const uint8_t* buf, uint16_t count, int32_t byte_timeout_ms, void* arg)
{
	nmbs_user_data_s* dev_ft = (nmbs_user_data_s*)arg;
	BOOL fSuccess;
	DWORD dwWritten;

	if (byte_timeout_ms > 0)
	{
		COMMTIMEOUTS timeouts = { 0 };
		timeouts.WriteTotalTimeoutConstant = (DWORD)byte_timeout_ms;
		SetCommTimeouts(dev_ft->hCommPort, &timeouts);
	}
	fSuccess = WriteFile(dev_ft->hCommPort, buf, count, &dwWritten, NULL);
	if (!fSuccess)
	{
		printf("Write Failed %d\n", GetLastError());
		return NMBS_ERROR_TRANSPORT;
	}
	return dwWritten;
}

3. Server部分

3.1 查找设备

FT4232H有4个COM口,需要找到对应的COM信息,和VCP_EX例程一样,通过D2XX的API函数来实现。

通过FT_OpenEx打开FT4232H的第一个COM口。

cpp 复制代码
/***********************************************************************
//Find the com port that has been assigned to your device.
/***********************************************************************/
	FT_STATUS ftStatus;
    FT_DEVICE_LIST_INFO_NODE* devInfo; 
    DWORD numDevs; 
    // create the device information list 
    ftStatus = FT_CreateDeviceInfoList(&numDevs);
    if (ftStatus == FT_OK) { 
    	printf("Number of devices is %d\n", numDevs);
    } 
    if (numDevs > 0) { 
    	// allocate storage for list based on numDevs 
    	devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs); 
    	// get the device information list 
    	ftStatus = FT_GetDeviceInfoList(devInfo,&numDevs); 
    	if (ftStatus == FT_OK) { 
    		for (int i = 0; i < numDevs; i++) { 
    			printf("Dev %d:\n",i); 
    			printf(" Flags=0x%x\n",devInfo[i].Flags); 
    			printf(" Type=0x%x\n",devInfo[i].Type); 
    			printf(" ID=0x%x\n",devInfo[i].ID); 
    			printf(" LocId=0x%x\n",devInfo[i].LocId); 
    			printf(" SerialNumber=%s\n",devInfo[i].SerialNumber); 
    			printf(" Description=%s\n",devInfo[i].Description); 
    			printf(" ftHandle=0x%x\n",devInfo[i].ftHandle); 
    		} 
    	} 
    }

    res = FT_OpenEx("FT4232H MiniModule A", FT_OPEN_BY_DESCRIPTION, &fthandle);
	if (res != FT_OK) {
		printf("opening failed! with error %d\n", res);
		return 1;
	}
	res = FT_GetComPortNumber(fthandle, &COMPORT);
	if (res != FT_OK) {
		printf("get com port failed %d\n", res);
		return 1;
	}

	if (COMPORT == -1) {
		printf("no com port installed \n");
	}
	else {
		printf("com port number is %d\n", COMPORT);
	}
	FT_Close(fthandle);

FT_GetComPortNumber获取的是对应的COM编号。

3.2 打开设备

通过上一步获取的COM编号,打开这个串口设备,注意这里官方目前的代码有个bug,当使用COM10及更高端口时,直接使用"COM%d"格式会失败,当端口号≥10时,必须使用"\\\\.\\COM%d"格式(如"\\\\.\\COM10"),因为Windows对COM1-9有预定义别名,而COM10+需直接访问设备命名空间。

cpp 复制代码
/********************************************************/
// Open the com port assigned to your device
/********************************************************/		
	if (COMPORT <= 9)
	    sprintf(COMx, "COM%d", COMPORT);
    else
	    sprintf(COMx, "\\\\.\\COM%d", COMPORT); // 注意转义字符需写为双反斜杠
	hCommPort = CreateFile(
			COMx,
            GENERIC_READ | GENERIC_WRITE,
            0,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL
            );
	if (hCommPort == INVALID_HANDLE_VALUE) 
	{
		printf("Help - failed to open\n");
		return(1);
	}	
	printf("Open %s OK\n", COMx);
	nmbs_dev_ft.hCommPort = hCommPort;

将这个串口的句柄赋值给nanoModbus的配置参数。

注意COMx数组的大小也要改为

cpp 复制代码
char COMx[11];
memset(COMx, 0, 11);

3.3 设置串口参数

通过GetCommState获取参数,然后修改参数再通过SetCommState设置新的参数。

cpp 复制代码
/********************************************************/
// Configure the UART interface parameters
/********************************************************/
	fSuccess = GetCommState(hCommPort, &dcb);
	if (!fSuccess) {
		printf("GetCommStateFailed \n", GetLastError());
		return (2);
	}
	//set parameters.
	dcb.BaudRate = 115200;
	dcb.ByteSize = 8;
	dcb.Parity = NOPARITY;
	dcb.StopBits = ONESTOPBIT;	
	fSuccess = SetCommState(hCommPort, &dcb);
	if (!fSuccess) {
		printf("SetCommStateFailed \n", GetLastError());
		return (3);
	}
	printf("Port configured \n");

3.4 初始化nanoModbus

首先初始化一个callback变量

cpp 复制代码
nmbs_callbacks nmbs_callback;
nmbs_callbacks_create(&nmbs_callback);
nmbs_callback.arg = (void*)&nmbs_dev_ft;
nmbs_callback.read_holding_registers = ft_read_holding_registers;
nmbs_callback.write_multiple_registers = ft_write_multiple_registers;

同样,将用户数据传入这个变量

cpp 复制代码
nmbs_callback.arg = (void*)&nmbs_dev_ft;

这个变量定义了一系列的回调函数 ,这些回调函数用于定义 Modbus 从站(服务端)对各类功能码的响应逻辑,这部分是Modbus定义的功能码处理函数,用户根据实际情况选择对应的函数指针赋值。

成员类型 成员名 作用
功能码处理函数指针 read_coils 对应Modbus功能码0x01(读取线圈状态),用于从从站设备读取可读写的数字量输出(如继电器状态、LED指示灯等)。coils和discrete_inputs的区别是coils可以通过FC 05FC 15修改,而discrete_inputs不可以修改。 作用 :功能码01FC 01),用于读取从站设备中连续的线圈状态(ON/OFF)。线圈是可读写的数字量。 参数: - uint16_t address:起始地址 - uint16_t quantity:读取数量(1-2000) - nmbs_bitfield coils_out:存储结果的位数组 - uint8_t unit_id:请求的从站单元ID(RTU为设备地址,TCP为0) - void *arg:用户自定义参数 **示例:**从GPIO寄存器读取线圈状态 nmbs_error my_read_coils( uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void *arg ) { for (uint16_t i = 0; i < quantity; i++) { uint16_t addr = address + i; if (addr < MAX_COILS) { coils_out[i / 8] |= (get_gpio_output_state(addr) << (i % 8)); // 设置对应位 } else { return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; } } return NMBS_ERROR_NONE; }
read_discrete_inputs 对应Modbus功能码0x02(读取离散输入),用于读取从站设备的只读数字量输入(如传感器状态、按钮信号等)。 作用 :当主站发送FC 02请求时,从站通过该回调函数提供离散输入的实际数据。 参数: - uint16_t address:起始地址 - uint16_t quantity:读取数量(1-2000) - nmbs_bitfield inputs_out:存储结果的位数组 - uint8_t unit_id:请求的从站单元ID(RTU为设备地址,TCP为0) - void *arg:用户自定义参数 **示例:**从GPIO寄存器读取离散输入 nmbs_error my_read_discrete_inputs( uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out, uint8_t unit_id, void *arg ) { for (uint16_t i = 0; i < quantity; i++) { uint16_t addr = address + i; if (addr < MAX_INPUTS) { inputs_out[i / 8] |= (get_gpio_state(addr) << (i % 8)); // 设置对应位 } else { return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; } } return NMBS_ERROR_NONE; }
read_holding_registers 对应 Modbus 功能码 03(读取保持寄存器),用于从从站设备读取可读写的 16位寄存器数据(如配置参数、传感器测量值等) 作用 :功能码 03FC 03),用于读取从站设备中连续的保持寄存器 参数: - uint16_t quantity:读取的寄存器数量(最大125,受协议限制) - uint16_t *registers_out, 输出缓冲区(存储读取的寄存器值) - 其他:略 **示例:**从EEPROM或内存中读取寄存器数据 nmbs_error my_read_holding_registers( uint16_t address, uint16_t quantity, uint16_t *registers_out, uint8_t unit_id, void *arg ) { for (uint16_t i = 0; i < quantity; i++) { uint16_t addr = address + i; if (addr < MAX_REGISTERS) { registers_out[i] = read_eeprom_word(addr); // 从EEPROM读取16位数据 } else { return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; } } return NMBS_ERROR_NONE; }
read_input_registers 对应 Modbus 功能码 04(读取输入寄存器),用于从从站设备读取 只读的16位寄存器数据(如传感器实时测量值)。和holding_registers的区别是,holding_registers可读写,而input_registers是只读的。 作用: 功能码 04FC 04),用于读取从站设备中连续的输入寄存器(Input Registers)。 **参数:**略(类似) **示例:**从ADC读取模拟量并转换为寄存器值 nmbs_error my_read_input_registers( uint16_t address, uint16_t quantity, uint16_t *registers_out, uint8_t unit_id, void *arg ) { for (uint16_t i = 0; i < quantity; i++) { uint16_t addr = address + i; if (addr < MAX_INPUT_REGISTERS) { registers_out[i] = read_adc_value(addr); // 从ADC读取16位数据 } else { return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; } } return NMBS_ERROR_NONE; }
write_single_coil write_single_register write_multiple_coils write_multiple_registers 这4个函数对应coil或register的写。
read_file_record 用于实现 Modbus 功能码 0x14(FC 20) 的函数,其核心作用是 从从站设备的文件记录中读取数据 作用: 功能码 0x14(FC 20),用于读取从站设备中存储的文件记录(File Record) 参数: - nmbs_t *nmbs, nanoModbus 实例指针 - uint16_t file_number, 文件编号(1-65535) - uint16_t record_number, 记录编号(0000-9999) - uint16_t *registers, 输出缓冲区(存储读取的寄存器值) - uint16_t count, 需读取的寄存器数量 - 其他:略 **示例:**从Flash中读取文件记录 nmbs_error my_read_file_record( nmbs_t *nmbs, uint16_t file_number, uint16_t record_number, uint16_t *registers, uint16_t count, uint8_t unit_id, void *arg ) { uint16_t record_base_addr = calculate_flash_address(file_number, record_number); for (uint16_t i = 0; i < count; i++) { registers[i] = read_flash_word(record_base_addr + i * 2); // 假设每个寄存器占2字节 } return NMBS_ERROR_NONE; }
write_file_record 对应read_file_record理解即可。
设备识别函数 read_device_identification 用于实现 Modbus 功能码 0x2B(FC 43) 的函数,其核心作用是 读取从站设备的标识信息,包括厂商名称、产品型号、版本号等。 示例: nmbs_error my_read_device_identification( uint8_t object_id, char *buffer, ) { switch (object_id) { case 0x00: // VendorName strncpy(buffer, "ABC Corp", NMBS_DEVICE_IDENTIFICATION_STRING_LENGTH); break; case 0x01: // ProductCode strncpy(buffer, "MOD-100", NMBS_DEVICE_IDENTIFICATION_STRING_LENGTH); break; default: return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; } return NMBS_ERROR_NONE; }
read_device_identification_map 返回设备标识的映射表(位域数据)
用户数据与状态 arg 用户自定义数据指针,传递给所有回调函数(如硬件句柄、配置参数)
initialized 库内部状态标记(用于兼容旧版代码,普通用户无需关注)

这些回调函数不需要都实现,这里选2个作为示例:

cpp 复制代码
nmbs_callback.read_holding_registers = ft_read_holding_registers;
nmbs_callback.write_multiple_registers = ft_write_multiple_registers;

这2个函数实现示例如下:

cpp 复制代码
nmbs_error ft_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id, void* arg)
{
	// 从硬件或内存中读取数据
	for (uint16_t i = 0; i < quantity; i++) {
		//registers[i] = get_sensor_data(address + i); // 自定义数据获取函数
		registers_out[i] = i;
	}
	return NMBS_ERROR_NONE;
}

nmbs_error ft_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers, uint8_t unit_id, void* arg)
{
	// 将数据写入硬件或内存
	printf("write registers:\n");
	for (uint16_t i = 0; i < quantity; i++) {
		//set_device_state(address + i, registers[i]); // 自定义数据写入函数
		printf("[%d]=0x%x ", i, registers[i]);
	}
	return NMBS_ERROR_NONE;
}

由于这里是验证主机方式,所以通过nmbs_server_create创建,第二个参数是Client的地址,注意必须和Client一致。

cpp 复制代码
nmbs_t nmbs;
if (nmbs_server_create(&nmbs, 1, &platform_conf, &nmbs_callback) != NMBS_ERROR_NONE) {
	DWORD err = GetLastError();
	printf("create nmbs server fail. Error code: %d\n", err);
	return 4;
}

设置读超时

cpp 复制代码
nmbs_set_read_timeout(&nmbs, 1000);  // 设置1秒超时

3.5 Poll处理

在while(1)主循环中调用nmbs_server_poll,这个函数用于处理客户端请求并管理数据收发

cpp 复制代码
while (1) {
	nmbs_error err = nmbs_server_poll(&nmbs);
	if (err != NMBS_ERROR_NONE) {
		// 处理超时或错误
	}
    Sleep(10);
}

4. Client部分

将Server的工程复制一份作为Client的工程,将nanoModbus_server相关信息改为nanoModbus_client,再将这个工程添加到VCP_EX.sln中。

4.1 查找设备

打开设备改为设备B

cpp 复制代码
res = FT_OpenEx("FT4232H MiniModule B", FT_OPEN_BY_DESCRIPTION, &fthandle);

4.2 初始化nanoModbus

通过nmbs_client_create初始化,比server部分简单点

cpp 复制代码
nmbs_t nmbs;
if (nmbs_client_create(&nmbs, &platform_conf) != NMBS_ERROR_NONE) {
    DWORD err = GetLastError();
	printf("create nmbs client fail. Error code: %d\n", err);
	return 4;
}
printf("client create ok\n");
nmbs_set_byte_timeout(&nmbs, 100);
nmbs_set_read_timeout(&nmbs, 1000);

4.3 设置Modbus从站设备的rtu地址

通过nmbs_set_destination_rtu_address设置这个地址,要和Server端的设置一致,例如这里设置的rtu地址为1。

cpp 复制代码
nmbs_set_destination_rtu_address(&nmbs, 0x01);

4.4 读写部分

这部分是和Server端配置的回调函数匹配的,之前设置的是read_holding_registers和write_multiple_registers,所以这里是和具体的应用相关的,需要根据自己的应用修改:

cpp 复制代码
	uint16_t regs_test[32];
	while (1) {
		nmbs_error status = nmbs_read_holding_registers(&nmbs, 0, 32, regs_test);
		if (status != NMBS_ERROR_NONE) {
			printf("read registers fail\n");
			break;
		}
		else {
			printf("Client read register:\n");
			for (int i = 0; i < 32; i++)
			{
				printf("[%d]=0x%x ", i, regs_test[i]);
			}
		}
		status = nmbs_write_multiple_registers(&nmbs, 0, 32, regs_test);
		if (status != NMBS_ERROR_NONE) {
			printf("write registers fail\n");
			break;
		}
		else {
			printf("Client write register:\n");
			for (int i = 0; i < 32; i++)
			{
				printf("[%d]=0x%x ", i, regs_test[i]);
			}
		}
		Sleep(1000);
	}

5. 测试

将FT4232H模块串口1的RXD/TXD和串口2的TXD/RXD短接,分别运行server和client,先运行server,log信息如下:

bash 复制代码
*************** NanoModbus Server ***************
Number of devices is 4
Dev 0:
 Flags=0x2
 Type=0x7
 ID=0x4036011
 LocId=0x1242
 SerialNumber=FTDIB2GV2QB
 Description=FT4232H MiniModule B
 ftHandle=0x0
Dev 1:
 Flags=0x2
 Type=0x7
 ID=0x4036011
 LocId=0x1243
 SerialNumber=FTDIB2GV2QC
 Description=FT4232H MiniModule C
 ftHandle=0x0
Dev 2:
 Flags=0x2
 Type=0x7
 ID=0x4036011
 LocId=0x1244
 SerialNumber=FTDIB2GV2QD
 Description=FT4232H MiniModule D
 ftHandle=0x0
Dev 3:
 Flags=0x2
 Type=0x7
 ID=0x4036011
 LocId=0x1241
 SerialNumber=FTDIB2GV2QA
 Description=FT4232H MiniModule A
 ftHandle=0x0
com port number is 84
Open \\.\COM84 OK
Port configured
server create ok

client端的log信息如下:

bash 复制代码
*************** NanoModbus Client ***************
Number of devices is 4
Dev 0:
 Flags=0x2
 Type=0x7
 ID=0x4036011
 LocId=0x1242
 SerialNumber=FTDIB2GV2QB
 Description=FT4232H MiniModule B
 ftHandle=0x0
Dev 1:
 Flags=0x2
 Type=0x7
 ID=0x4036011
 LocId=0x1243
 SerialNumber=FTDIB2GV2QC
 Description=FT4232H MiniModule C
 ftHandle=0x0
Dev 2:
 Flags=0x2
 Type=0x7
 ID=0x4036011
 LocId=0x1244
 SerialNumber=FTDIB2GV2QD
 Description=FT4232H MiniModule D
 ftHandle=0x0
Dev 3:
 Flags=0x1
 Type=0x3
 ID=0x0
 LocId=0x0
 SerialNumber=
 Description=
 ftHandle=0x0
com port number is 85
Open \\.\COM85 OK
Port configured
client create ok

两边数据交换log:

相关推荐
吃杠碰小鸡7 分钟前
前端 IndexedDB 完全指南
学习
宵时待雨16 分钟前
C++笔记归纳14:AVL树
开发语言·数据结构·c++·笔记·算法
问道飞鱼35 分钟前
【大模型学习】LangGraph 深度解析:定义、功能、原理与实践
数据库·学习·大模型·工作流
左左右右左右摇晃39 分钟前
JDK 1.7 ConcurrentHashMap——分段锁
java·开发语言·笔记
烤麻辣烫1 小时前
I/O流 基础流
java·开发语言·学习·intellij-idea
云边散步1 小时前
godot2D游戏教程系列二(22)
笔记·学习·游戏
jincheng_1 小时前
软件设计师上午题|9模块极速背诵版
学习
Schengshuo2 小时前
Spring学习——新建module模块
java·学习·spring
chushiyunen2 小时前
langchain实现agent智能体笔记
笔记·langchain
jyan_敬言2 小时前
【算法】高精度算法(加减乘除)
c语言·开发语言·c++·笔记·算法