全部例程

上位机搜索、配置及OTA

W55MH32

更新于 2026年9月29日

本篇总结

在 W55MH32 上通过 UDP 广播实现局域网设备搜索、网络地址配置和固件 OTA 升级,配套 ConfigTool 上位机工具。

🔑 KEIL MDK、UART调试、硬件TOE引擎、UDP、OTA

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示如何在 W55MH32 上实现上位机搜索和配置功能,并通过实战例程,为大家讲解如何通过上位机搜索局域网中的 W55MH32,并进行网络地址配置。该例程用到的其他网络协议(例如 DHCP)以及 W55MH32 的初始化过程,请参考 Network Install 章节,这里不再赘述。

前置工具准备

开始本例程前,请确认以下工具已安装:

  • WIZ UartTool V1.0:串口调试工具:点击下载
  • ConfigTool:上位机设备搜索与配置工具:点击下载

嵌入式上位机简介

嵌入式上位机(Embedded Host)是指在嵌入式系统中,作为与嵌入式设备进行通信、控制和数据交换的上位设备,通常用于读取和修改下位机的配置以及升级下位机的固件。它具备响应快、可靠性高的高效性,可实时响应与反馈的实时性,可灵活组合扩展以连接多个下位机的灵活性,以及提供友好图形界面便于操作的交互友好性,因而广泛应用于工业自动化、物联网、智能家居、医疗设备等场景。

  • 搜索(FIND)阶段:通过 UDP 广播发送 FIND 命令发现设备并获取配置信息
  • 配置(SETT)阶段:向设备发送 SETT 命令重新配置网络信息
  • 固件升级(FIRM)阶段:发送 FIRM 命令后建立新 TCP 链接下发固件,设备接收完成后复位更新

BOOT/APP 双程序架构

FLASH 划分为 BOOT 程序区、APP 程序区和 APP 备份区,新固件先写入备份区,复位后由 BOOT 程序校验并拷贝至运行区,即使升级过程断电也不影响原有固件。

特点说明
高效性上位机发出控制命令,下位机直接控制设备,响应速度快、可靠性高
实时性下位机实时响应控制指令并控制设备,同时实时反馈设备状态数据
灵活性可灵活组合扩展,同时连接多个下位机进行监控和数据处理,实现分布式控制
交互友好提供人机交互界面,为用户提供友好的图形或文本界面,便于操作、配置和监控

硬件连接

请注意:

测试需要 PC 端和 W55MH32 处于同一网段。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口
┌─────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬──────────────────┬───────────┘
│ │
┌────┴──────┐ ┌────┴──────┐
│ W55MH32 │ │ PC │
│ BOOT+APP │ │ConfigTool │
│ IP: DHCP │ │ IP: DHCP │
└───────────┘ └───────────┘
↑ ↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:开发板与 PC 在同一局域网内,通过路由器互通

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\OTA\,包含 BOOT 和 APP 两个工程,核心文件如下:

OTA/
├── Boot/
│   ├── Project/
│   │   └── Boot.uvprojx       ← Keil BOOT 工程文件
│   └── USER/
│       ├── boot.c             ← BOOT 初始化校验 + 固件拷贝
│       └── main.c             ← UDP 配置命令处理 + 固件升级接收
└── App/
    ├── Project/
    │   └── App.uvprojx        ← Keil APP 工程文件
    └── USER/
        └── main.c             ← APP 主循环 + UDP 配置 + 固件备份

步骤1:BOOT 初始化校验

文件:Boot/USER/boot.c — 首先读取 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");
}

BOOT 启动流程

读取参数 → 判断是否有新固件需要拷贝 → app_copy 校验并拷贝 → 清除固件标志 → 跳转到 APP 运行区。如果 APP 运行区无有效固件,则留在 BOOT 程序等待上位机配置和升级。

步骤2:BOOT 主循环

文件:Boot/USER/main.c — 在主循环内调用 do_udp_config() 处理上位机命令,以及 fw_update_process() 处理固件升级。

while (1)
{
  do_udp_config(SOCK_UDP_CONFIG); /* Run and process UpperComputer command */
  fw_update_process();
  if (reboot_flag)
  {
    system_reboot();
  }
}

步骤3:搜索、配置与固件升级命令处理

文件:Boot/USER/main.c — do_udp_config() 函数运行一个 UDP 状态机,接收到消息时判断指令类型:FIND 指令回传设备网络地址信息,SETT 指令校验 MAC 地址后更新网络配置,FIRM 指令校验 MAC 后回复确认以开启固件升级 TCP 链路。

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);
                }
                else if ((RecvMsg.op[0] == 'S') && (RecvMsg.op[1] == 'E') &&
                         (RecvMsg.op[2] == 'T') && (RecvMsg.op[3] == 'T'))
                {
                    printf("Sett\r\n");
                    /* check MAC address match */
                    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]) &&
                        (RecvMsg.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]) &&
                        (RecvMsg.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;
    }
}

步骤4:BOOT 固件升级处理

文件:Boot/USER/main.c — 当收到 FIRM 指令后,上位机以 TCP Client 方式连接 W55MH32 并下发固件。fw_update_process() 函数实现 TCP 连接、接收固件并存储到 APP 运行区的完整流程。接收完成后跳转到 APP 运行区。

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;
        }
        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(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);
                    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;
    }
}

步骤5:APP 程序实现

文件:App/USER/main.c — APP 固件同样先初始化,然后在主循环中调用 do_udp_config() 和 fw_update_process() 函数。其中 do_udp_config() 函数与 BOOT 固件一致,但 fw_update_process() 函数略有不同。

while (1)
{
    do_udp_config(SOCK_UDP_CONFIG); /* Run and process UpperComputer command */
    fw_update_process();
    if (reboot_flag)
    {
        printf("reboot\r\n");
        system_reboot();
    }
}

APP 中的 fw_update_process() 函数会将收到的固件直接存储到 APP 备份区,接收完成后将固件长度及校验信息存储到 FLASH 参数区并复位,然后在 BOOT 固件中进行固件校验以及拷贝操作完成升级。

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;
        }
        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(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;
    }
}

BOOT 与 APP 的区别

BOOT 固件中的 fw_update_process() 将固件直接写入 APP 运行区(APPLICATION_START_ADDRESS),接收完成后直接跳转运行。APP 固件中的 fw_update_process() 将固件写入 APP 备份区(APPLICATION_BACK_ADDRESS),接收完成后保存校验信息并复位,由 BOOT 程序完成校验拷贝。

运行验证

测试实例需要 PC 端和 W55MH32 处于同一网段。烧录 BOOT 程序运行后,因为没有 APP 程序,首次运行会配置默认参数,然后进入 BOOT 的配置程序。打开 ConfigTool 上位机后,即可进行网络地址信息配置以及固件升级操作。

W55MH32 factory firmware
not app
Find from 192.168.1.100
Sett
socket connected
>.........
Firmware is download
reboot app
  • 搜索示例:在 ConfigTool 中点击搜索按钮,上位机通过 UDP 广播 FIND 命令,设备收到后回传网络配置信息,上位机列表中显示搜索到的设备。
  • 配置示例:选中设备后修改 IP 地址、子网掩码、网关、DNS 等参数并下发,设备收到 SETT 命令后更新本地配置并复位,重启后使用新的网络地址。
  • 固件升级示例:选中设备后选择固件文件并点击升级,设备收到 FIRM 命令后开启 TCP 服务器,上位机连接后传输固件,传输完成后设备自动复位完成升级。
WIZ UartTool 串口输出(Firmware is download)+ IAP 上位机(固件版本 1.1,升级进度 100%)— OTA 固件升级

常见问题

Q: 上位机和下位机是如何通信的

上位机通过 UDP 广播发送命令(FIND/SETT/FIRM/RSTT),下位机接收后响应。搜索和配置使用 UDP 协议(端口 1460),固件升级则在上位机收到 FIRM 确认后通过 TCP 连接下位机的 TCP 服务器(FW_UPDATE_PORT)进行固件传输。通信过程中通过 MAC 地址匹配确保配置和升级指令发送到正确的设备。

Q: 固件升级为什么需要 BOOT 和 APP 两个程序

BOOT 程序负责设备启动时的固件校验、拷贝和跳转操作,APP 程序是实际运行的主程序。固件升级时新固件先存入 APP 备份区,复位后由 BOOT 程序校验并拷贝到 APP 运行区。这种设计确保升级过程中即使断电也不会损坏当前运行的固件,实现了安全的 OTA 升级。

Q: 固件升级过程中断电了怎么办

由于采用 APP 备份区设计,新固件先写入 APP 备份区,只有写入完成并保存校验信息后才会复位。如果在固件写入备份区的过程中断电,APP 运行区中的原固件不受影响,设备重启后仍可正常运行原固件。如果备份区写入完成后断电,BOOT 程序会在下次启动时检测到备份区有新固件,自动完成校验拷贝操作。

Q: ConfigTool 搜索不到设备怎么办

请确保 PC 和 W55MH32 处于同一网段。检查设备是否已正确烧录 BOOT 程序并运行,首次烧录 BOOT 程序后会配置默认参数并进入配置程序。同时检查 PC 防火墙是否阻止了 UDP 广播数据包。如果设备 IP 未知,可通过串口查看设备启动时打印的网络配置信息确认其 IP 地址。

总结

  • 设备搜索:上位机通过 UDP 广播发送 FIND 命令,设备回传网络配置信息
  • 网络配置:SETT 命令下发 IP/子网掩码/网关/DNS 参数,MAC 地址匹配后更新并复位
  • 固件升级:FIRM 命令触发,TCP 传输固件至备份区,BOOT 程序校验拷贝至运行区
  • 双区设计:BOOT 程序区 + APP 运行区 + APP 备份区,确保升级安全可靠
  • 配套工具:ConfigTool 上位机支持搜索、配置和 OTA 升级全流程操作
  • 命令体系:FIND(搜索)、SETT(配置)、RSTT(复位)、FIRM(固件升级)

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家