Search, Configuration and OTA
在本文中,我们将详细介绍如何在 W55MH32芯片之上实现上位机的搜索和配置功能,并说明如何通过上位机在局域网中搜索 W55MH32,并通过实际程序来配置网络地址。示例程序提供了一个开源的主机计算机配置工具 SmartConfigTool,支持搜索设备、设置网络地址参数以及进行固件升级等功能。
对于本文中使用的其他网络协议,例如 DHCP,请参考相关章节。关于 W55MH32 的初始化过程,请参考“网络安装”章节,此处不再赘述。
上位机简介
嵌入式上位机(Embedded Host)是指在嵌入式系统中,作为与嵌入式设备进行通信、控制和数据交换的上位设备。 它通常拥有更强的计算能力和存储资源,用于控制和监控下位嵌入式设备(如传感器、执行器、嵌入式控制器等)的运行。
特点
应用场景
W55MH32使用NetBIOS 协议可以进行以下几种应用:
搜索和配置的基本工作流程
搜索:上位机通过 UDP 广播发送 FIND 命令,设备作为下位机在收到后将自身配置信息发送给上位机,上位机收到后呈现获取到的设备信息;
配置:在已经搜索到设备的基础上,上位机向该设备发送 SETT 命令后,设备收到后根据上位机显示的网络信息对本地进行重新配置,并在串口显示。

实现过程
第1步:初始化检查
首先,它会读取闪存中的参数,如果没有参数,那么它就会存储默认参数,并开始判断固件长度是否大于 0。如果大于 0,这意味着固件需要更新,然后会调用 app_copy() 函数将 APP 备份区中的固件复制到 APP 运行区。最后,它会确定 APP 运行区中是否有任何固件。如果有,那么它将直接跳转到 APP 固件。
system_config_param_get().
system_first_run_check().
if (system_cfg.fw_len> 0 && system_cfg.fw_len ! = 0xFFFFFFFF)
{
if (app_copy(system_cfg.fw_len, system_cfg.fw_checksum)< 0)
{
printf("Copy fw error\r\n").
}
else
{
system_cfg.fw_len= 0;
system_cfg.fw_checksum= 0.
system_config_param_save().
reboot_app().
}
}
if (app_is_inside())
{
printf("reboot app\r\n").
reboot_app().
}
else
{
printf("not app\r\n").
}
第2步:在主循环中调用 do_udp_config() 和 fw_update_process() 这两个函数。
参考代码:
while (1)
{
do_udp_config(SOCK_UDP_CONFIG); // Run and precess UpperComputer command.
fw_update_process().
if (reboot_flag)
{
system_reboot().
}
}
第 3 步:搜索、配置实施及固件升级
do_udp_config() 函数如下所示:
参考代码
void do_udp_config(uint8_t sn)
{
uint16_t len = 0;
uint8_t rip[4];
uint16_t rport;
uint16_t local_port = 1460;
memset(RecvMsg.op, 0, sizeof(RecvMsg)); // clear RecvMsg
switch (getSn_SR(sn))
{
case SOCK_UDP:
if ((len = getSn_RX_RSR(sn)) > 0)
{
len = recvfrom(sn, (uint8_t *)&RecvMsg, len, rip, &rport);
if (len > sizeof(CONFIG_MSG))
break;
{
// FIND: searching, SETT: setting,
if ((RecvMsg.op[0] == 'F') && (RecvMsg.op[1] == 'I') && (RecvMsg.op[2] == 'N') && (RecvMsg.op[3] == 'D'))
{
printf("Find from %d.%d.%d.%d\r\n", rip[0], rip[1], rip[2], rip[3]);
memcpy(public_buff, &system_cfg, sizeof(CONFIG_MSG));
memcpy(public_buff, "FIND", 4);
sendto(sn, public_buff, sizeof(CONFIG_MSG), rip, rport); // return network info to uppercomputer.
}
else if ((RecvMsg.op[0] == 'S') && (RecvMsg.op[1] == 'E') && (RecvMsg.op[2] == 'T') && (RecvMsg.op[3] == 'T'))
{
printf("Sett\r\n");
if ((RecvMsg.mac[0] == system_cfg.mac[0]) && (RecvMsg.mac[1] == system_cfg.mac[1]) && (RecvMsg.mac[2] == system_cfg.mac[2]) && (RecvMsg.mac[3] == system_cfg.mac[3]) && (system_cfg.mac[4] == system_cfg.mac[4]) && (RecvMsg.mac[5] == system_cfg.mac[5]))
{
memcpy(system_cfg.lip, RecvMsg.lip, 4);
memcpy(system_cfg.sub, RecvMsg.sub, 4);
memcpy(system_cfg.gw, RecvMsg.gw, 4);
memcpy(system_cfg.dns, RecvMsg.dns, 4);
system_config_param_save();
reboot_flag = 1;
sendto(sn, (uint8_t *)&RecvMsg, sizeof(CONFIG_MSG), rip, rport);
}
}
else if ((RecvMsg.op[0] == 'R') && (RecvMsg.op[1] == 'S') && (RecvMsg.op[2] == 'T') && (RecvMsg.op[3] == 'T'))
{
printf("Reset\r\n");
reboot_flag = 1;
}
else if ((RecvMsg.op[0] == 'F') && (RecvMsg.op[1] == 'I') && (RecvMsg.op[2] == 'R') && (RecvMsg.op[3] == 'M'))
{
printf("Fire\r\n");
if ((RecvMsg.mac[0] == system_cfg.mac[0]) && (RecvMsg.mac[1] == system_cfg.mac[1]) && (RecvMsg.mac[2] == system_cfg.mac[2]) && (RecvMsg.mac[3] == system_cfg.mac[3]) && (system_cfg.mac[4] == system_cfg.mac[4]) && (RecvMsg.mac[5] == system_cfg.mac[5]))
{
system_cfg.state = FW_UPDATE_CONFIG;
sendto(sn, (uint8_t *)&RecvMsg, sizeof(CONFIG_MSG), rip, rport);
}
}
}
}
break;
case SOCK_CLOSED:
socket(sn, Sn_MR_UDP, local_port, 0x00);
break;
}
}
首先,它会运行一个 UDP 状态机,当接收到消息时,会判断该指令。 如果是“查找”指令,它会读取设备的网络地址信息并将其返回。 如果是“设置”指令,它会判断 MAC 地址是否与自身一致,如果一致,就会将主机计算机发出的配置更新到 W55MH32中。
如果是“固件”指令,它会判断 MAC 地址是否与自身相同,如果相同,就会向主机计算机回复“固件”消息,然后主机计算机将通过 TCP 客户端连接到 W55MH32,并将固件发送给 W55MH32,随后将固件保存到应用程序运行区域。
下载完成后,它会跳转到应用程序运行区域。 连接、接收固件以及存储固件的操作是在“fw_update_process()”函数中实现的,具体如下:
void fw_update_process(void)
{
static uint32_t file_size = 0;
static uint32_t file_recv_length = 0;
static uint8_t is_erased_flag = 0;
static uint32_t flash_offset = 0;
uint32_t len = 0;
switch (getSn_SR(SOCK_FW_UPDATE))
{
case SOCK_ESTABLISHED:
if (getSn_IR(SOCK_FW_UPDATE) & Sn_IR_CON)
{
setSn_IR(SOCK_FW_UPDATE, Sn_IR_CON);
printf("socket connected\r\n");
is_erased_flag = 0;
flash_offset = APPLICATION_START_ADDRESS;
file_size = 0;
file_recv_length = 0;
}
// some timeout function should be added here
len = getSn_RX_RSR(SOCK_FW_UPDATE);
if (len > 0)
{
if ((len == 4) && (is_erased_flag == 0))
{
recv(SOCK_FW_UPDATE, (uint8_t *)&file_size, 4);
flash_erase(APPLICATION_START_ADDRESS, APPLICATION_AREA_SIZE);
is_erased_flag = 1;
// send the len to PC program to tell him flash erased over
send(SOCK_FW_UPDATE, (uint8_t *)&file_size, 4);
printf(">\r\n");
}
else
{
recv(SOCK_FW_UPDATE, public_buff, len);
printf(".");
flash_write(flash_offset, public_buff, len);
flash_offset += len;
file_recv_length += len;
send(SOCK_FW_UPDATE, (uint8_t *)&len, (uint16_t)4);
if (file_size == file_recv_length)
{
disconnect(SOCK_FW_UPDATE);
// save fw checksum and module state
system_cfg.state = FW_APP_NORMAL;
system_cfg.fw_checksum = 0;
system_cfg.fw_len = 0;
system_config_param_save();
printf("\r\nFirmware is download\r\n");
reboot_app();
}
}
}
break;
case SOCK_CLOSE_WAIT:
disconnect(SOCK_FW_UPDATE);
break;
case SOCK_CLOSED:
close(SOCK_FW_UPDATE);
socket(SOCK_FW_UPDATE, Sn_MR_TCP, FW_UPDATE_PORT, Sn_MR_ND);
break;
case SOCK_INIT:
listen(SOCK_FW_UPDATE);
break;
}
}
接下来要处理的是应用程序固件,这需要在主循环中调用一个程序来完成 UDP 配置以及固件更新的工作。
while (1)
{
do_udp_config(SOCK_UDP_CONFIG); // Run and precess UpperComputer command.
fw_update_process();
if (reboot_flag)
{
system_reboot();
}
}
其中,do_udp_config() 函数与启动固件保持一致,但 fw_update_process() 函数则略有不同,具体如下:
void fw_update_process(void)
{
static uint32_t file_size= 0;
static uint32_t file_recv_length= 0;
static uint8_t is_erased_flag= 0;
static uint32_t flash_offset= 0;
uint32_t len= 0;
if (system_cfg.state ! = FW_UPDATE_CONFIG)
{
return;
}
switch (getSn_SR(SOCK_FW_UPDATE))
{
case SOCK_ESTABLISHED.
if (getSn_IR(SOCK_FW_UPDATE) & Sn_IR_CON)
{
setSn_IR(SOCK_FW_UPDATE, Sn_IR_CON).
is_erased_flag= 0.
flash_offset= APPLICATION_BACK_ADDRESS.
file_size= 0.
file_recv_length= 0;
}
// some timeout function should be added here
len= getSn_RX_RSR(SOCK_FW_UPDATE).
if (len> 0)
{
if ((len== 4) && (is_erased_flag== 0))
{
recv(SOCK_FW_UPDATE, (uint8_t *)&file_size, 4).
flash_erase(APPLICATION_BACK_ADDRESS, APPLICATION_AREA_SIZE).
is_erased_flag= 1.
// send the len to PC program to tell him flash erased over
send(SOCK_FW_UPDATE, (uint8_t *)&file_size, 4).
printf("file_size %d\r\n", file_size).
printf(">").
}
else
{
len= recv(SOCK_FW_UPDATE, public_buff, len);
printf(".") ;
flash_write(flash_offset, public_buff, len);
flash_offset+= len.
file_recv_length+= len;
send(SOCK_FW_UPDATE, (uint8_t *)&len, (uint16_t)4).
if (file_size== file_recv_length)
{
printf("\r\n").
disconnect(SOCK_FW_UPDATE).
system_cfg.fw_checksum= update_firmware_calc_checksum(file_size);
system_cfg.fw_len= file_size.
system_config_param_write(&system_cfg).
reboot_flag= 1; // reboot
}
}
}
break;
case SOCK_CLOSE_WAIT.
// disconnect(SOCK_FW_UPDATE);
break;
case SOCK_CLOSED.
close(SOCK_FW_UPDATE).
socket(SOCK_FW_UPDATE, Sn_MR_TCP, FW_UPDATE_PORT, Sn_MR_ND).
break;
case SOCK_INIT.
listen(SOCK_FW_UPDATE).
printf("fw update socket init\r\n");
break;
}
}
在执行固件升级操作时,接收到的固件将直接存储到 APP 的备份区域中,接收完成后,固件的长度以及校验和信息将被存储到 FLASH 参数区域并进行重置。然后,可以通过检查并复制 BOOT 固件中的固件来完成升级操作。

地址划分:
BOOT:0x08000000->0x08007FFF(64KB)
APP:0x08010000->0x08041FFF(200KB)
APPBAK:0x08042000->0x08073FFF(200KB)
未定义:0x08074000->0x080FEFFF(556KB)
参数区:0x080FF000->0x08100000(4KB)
运行结果
请注意:
测试实例需要PC端和W55MH32处于同一网段。
在运行烧录 BOOT 程序之后,由于没有 APP 程序,首次运行将会配置默认参数,然后进入 BOOT 的配置程序。

然后打开“配置工具”上行界面,在这里您可以设置网络地址信息以及进行固件升级操作。
搜索示例:

配置示例:

固件更新示例:

总结
本文介绍了如何在 W55MH32 芯片上实现主机计算机搜索与配置功能以及固件更新功能,并演示了使用开源主机计算机配置工具 ConfigTool 在局域网中搜索 W55MH32 设备、配置网络地址以及通过实际例程更新固件的过程。文章详细介绍了主机计算机搜索与配置的概念、特点、应用场景以及基本工作流程,以帮助读者了解其在嵌入式设备管理中的实际应用价值。
接下来的文章将重点介绍在 W55MH32 芯片上使用 TOE 中断的操作,分析 TOE 中断的核心原理和应用,并通过实际示例解释如何使用中断进行回环数据测试,敬请期待!