Search, Configuration and OTA
2025/08/27 更新
本篇文章我们将详细介绍如何在W55MH32芯片上面实现上位机搜索和配置功能,并通过实战例程,为大家讲解如何通过上位机搜索局域网中的W55MH32,并进行网络地址配置。例程中提供了一个上位机配置工具SmartConfigTool,支持搜索设备,设置网络地址参数以及固件升级等功能。
该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关W55MH32的初始化过程,请参考Network Install章节,这里将不再赘述。
嵌入式上位机简介
嵌入式上位机(Embedded Host)是指在嵌入式系统中,作为与嵌入式设备进行通信、控制和数据交换的上位设备。 它通常用于读取和修改下位机的配置及升级下位机的固件等功能。
特点
- 高效性:上位机配置下位机可以大大提高控制系统的效率。上位机发出控制命令, 下位机接收并解释成相应的时序信号来直接控制设备,响应速度快,可靠性高。
- 实时性:下位机可以实时地响应上位机的控制指令,并对设备进行实时控制,确保系统的稳定性和安全性。 同时,下位机也可以实时地向上位机反馈设备状态数据,使得上位机可以及时了解系统状态并进行相应的控制调整。
- 灵活性上位机和下位机可以灵活地组合和扩展,以满足不同系统的需求。上位机可以同时连接多个下位机, 对它们进行监控、控制和数据处理。同时,下位机也可以根据需要连接多个设备,实现设备的分布式控制。
- 交互友好:上位机通常具有人机交互界面,为用户提供友好的图形界面或者文本界面,方便用户进行操作、配置和监控。
应用场景
- 工业自动化:上位机发出控制指令,下位机接收并解释成相应的时序信号来直接控制设备,响应速度快,可靠性高。上位机可以监控生产过程、发出控制指令、 进行数据分析和优化等。下位机可以实时控制设备、采集设备状态数据、接收和执行控制指令等。
- 物联网:上位机可以远程监控和管理设备、进行数据分析和处理等。下位机可以接收和执行控制指令、采集和传输设备状态数据等。
- 智能家居:上位机可以发出控制指令、监控家庭网络等。下位机可以接收和执行控制指令、控制智能设备的运行和状态采集等。
- 医疗设备:上位机可以发出控制指令、远程监控和管理医疗设备等。下位机可以接收和执行控制指令、控制医疗设备的运行和状态采集等。
基本工作流程
- 搜索:上位机通过 UDP 广播发送 FIND 命令,设备作为下位机在收到后将自身配置信息发送给上位机,上位机收到后呈现获取到的设备信息。
- 配置:在已经搜索到设备的基础上,上位机向该设备发送 SETT 命令后,设备收到后根据上位机显示的网络信息对本地进行重新配置,并通过串口打印最新配置。
- 固件升级:在已经搜索到设备的基础上,上位机向该设备发送 FIRM 命令后,设备收到指令后回复确认并开启一路新的TCP链路,上位机收到回复后连接设备的TCP服务器,然后执行下发固件操作,设备接收完固件后复位进行固件拷贝更新操作。
固件升级工作原理
在固件升级过程中,我们需要在FLASH中存储多个程序,例如BOOT程序和APP程序,BOOT程序是设备启动时首先运行的程序,主要负责判断是否需要升级APP程序,以及后续的跳转操作。APP程序则是运行的主程序。
因此我们需要对FLASH进行分区,一般会分为BOOT程序区,APP程序区以及APP 备份区。
当需要升级固件时,会先将新固件保存至APP备份区域,保存完成后会复位,然后在BOOT程序中进行拷贝以及跳转操作。
本示例中的BOOT分区,APP分区,以及APP备份区划分如下所示:

BOOT程序工作流程如下所示:

APP程序工作流程如下所示:

实现过程
接下来,我们在W55MH32上实现上位机配置以及固件升级功能。
首先是BOOT固件。
步骤1:初始化校验
首先会读取FLASH中的参数,如果无参数则存储默认参数。然后判断固件长度是否大于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:搜索,配置实现和固件升级。
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的状态机,当接收到消息时,会判断指令,如果为“FIND”指令, 则读取设备的网络地址信息进行回传,如果为“SETT”指令,则判断MAC地址是否与自身一致,一致则将上位机下发的配置更新到W55MH32中。如果为“FIRM”指令,则判断MAC地址是否与自身一致,如果一致则回复FIRM消息给上位机,此时上位机会用TCP Client方式连接W55MH32,并将固件下发到W55MH32上,然后再将固件保存到APP运行区,下载完成后会跳转到APP运行区。连接、接收固件以及存储固件在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;
}
}
步骤4:APP程序实现。
接下来是APP固件,也是先初始化后,然后在主循环中调用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)
{
printf("reboot\r\n");
system_reboot();
}
}
其中,do_udp_config()函数与BOOT固件一致,但,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固件中进行固件校验以及拷贝操作即可完成升级。
运行结果
请注意:
测试实例需要PC端和W55MH32处于同一网段。
烧录BOOT程序运行后,因为没有APP程序,首次运行会配置默认参数,然后进入BOOT的配置程序。

然后打开ConfigTool上位机,在这里可以进行网络地址信息配置以及固件升级。
搜索示例:

配置示例:

固件升级示例:

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