全部例程

Search, Configuration and OTA

W5500

2025/08/27 更新

本篇文章我们将详细介绍如何在W5500芯片上面实现上位机搜索和配置功能,并通过实战例程,为大家讲解如何通过上位机搜索局域网中的W5500,并进行网络地址配置。例程中提供了一个上位机配置工具SmartConfigTool,支持搜索设备,设置网络地址参数以及固件升级等功能。

该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关W5500的初始化过程,请参考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备份区划分如下所示:

Blog Image
FLASH 分区示意图

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

Blog Image
BOOT工作流程图

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

Blog Image
APP工作流程图

实现过程

接下来,我们在W5500上实现上位机配置以及固件升级功能。

首先是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地址是否与自身一致,一致则将上位机下发的配置更新到W5500中。如果为“FIRM”指令,则判断MAC地址是否与自身一致,如果一致则回复FIRM消息给上位机,此时上位机会用TCP Client方式连接W5500,并将固件下发到W5500上,然后再将固件保存到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端和W5500处于同一网段。

烧录BOOT程序运行后,因为没有APP程序,首次运行会配置默认参数,然后进入BOOT的配置程序。

Blog Image

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

搜索示例:

Blog Image

配置示例:

Blog Image

固件升级示例:

Blog Image

总结

本文讲解了如何在 W5500 芯片上实现上位机搜索和配置功能以及固件更新功能,通过实战例程展示了使用开源上位机配置工具 ConfigTool 搜索局域网中的 W5500 并进行网络地址配置以及固件更新的过程。文章详细介绍了上位机的概念、特点、应用场景以及搜索和配置的基本工作流程,帮助读者理解其在嵌入式设备管理中的实际应用价值。

下一篇文章将聚焦在 W5500 芯片上面使用 TOE 中断功能, 解析 TOE 中断功能的核心原理及应用,同时通过实战例程讲解如何利用中断进行回环数据测试,敬请期待!

下载本章例程

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

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