全部例程

WIZnet W55MH32 多功能测试固件

W55MH32

更新于 2026年9月28日

本篇总结

W55MH32 作为 HTTP 服务器,通过网页配置界面实现外设控制、SD 卡读写、USB 串口通信及网络参数配置。

🔑 KEIL MDK、UART调试、硬件TOE引擎、HTTP、DHCP、SDIO

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

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

本示例演示 W55MH32 用户配置界面的实现方法,搭配 W55MH32,并结合实战例程,讲解如何以 W55MH32 作为 HTTP 服务器、PC 端作为 HTTP 客户端,通过网页配置界面完成外设功能控制、SD 卡读写及网络参数配置等操作。该例程用到的其他网络协议(例如 DHCP)以及 W55MH32 的初始化过程,请参考 Network Install 章节,这里不再赘述。

前置工具准备

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

  • WIZ UartTool V1.0:串口调试工具:点击下载
  • 浏览器:访问 W55MH32 网页配置界面(Chrome/Edge/Firefox 均可)

Web 网页简介

Web(World Wide Web,万维网)是一种基于超文本和 HTTP 的全球性、动态交互的、跨平台的分布式图形信息系统,为用户提供图形化、易于访问的信息浏览界面。W55MH32 多功能测试固件正是利用 Web 技术,在芯片内部搭建轻量级 HTTP 服务器,静态网页以直接响应返回,LED 控制、网络配置等动态操作通过 CGI 响应处理。

  • Web 页面组成:HTML(定义结构)、CSS(控制样式)、JavaScript(实现交互)、Meta 信息(提供元数据)
  • 三种交互方式:HTTP 请求、表单提交、AJAX(局部刷新无需重载整页)
  • 两种响应处理:直接响应(返回静态资源)、CGI 响应(调用外部程序生成动态内容)

W55MH32 中的 Web 实现

W55MH32 内部搭建轻量级 HTTP 服务器,静态网页以直接响应返回,LED 控制、网络配置等动态操作通过 CGI 响应处理,并借助 AJAX 与后端 CGI 接口交互实现数据实时更新和指令下发,让用户通过浏览器即可完成设备配置与功能测试。

硬件连接与拓扑

请注意:

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

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

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\HttpServer\,核心文件如下:

HttpServer/
├── Project/
│   └── Project.uvprojx       ← Keil 工程文件
└── USER/
    ├── main.c                ← 外设初始化 + HTTP Server + TIM3 中断 + 主循环
    ├── httpUtil.c/h          ← CGI 请求处理(predefined_set/get_cgi_processor)
    └── httpServer.c/h        ← HTTP Server 核心逻辑

步骤1:外设及网络配置初始化

文件:USER/main.c — 先完成 MCU 底层外设(时钟、延时、中断、串口、USB、定时器、GPIO、I2C)初始化,再初始化 WIZnet TCP/IP 协议栈,从 EEPROM 读取网络参数并配置。

wiz_NetInfo net_info;

/* hardware initialization */
rcc_clk_config();
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
console_usart_init(115200);
usb_init();

delay_ms(200);
printf("%s factory firmware\r\n", _WIZCHIP_ID_);
tim3_init();
user_gpio_init();
i2c_CfgGpio();

MYI2C_Init(&SENx, 1000, 0x38);
/* wiztoe init */
wiz_toe_init();
#if DEFAULT_MAC_EN == 1
getSHAR(default_net_info.mac);
#endif

wiz_phy_link_check();
check_eeprom_network_info(&default_net_info);
network_init(ethernet_buf, &default_net_info);

wizchip_getnetinfo(&net_info);

check_eeprom_network_info() 函数检查 EEPROM 中是否有网络地址信息,如果有则赋值给 default_net_info 结构体:

uint8_t check_eeprom_network_info(wiz_NetInfo *net_info)
{
  wiz_NetInfo eeprom_net_info = {0};

  if (ee_CheckDevice(EEPROM_DEV_ADDR) == 1)
  {
      printf("No serial EEPROM detected!\r\n");
      return 0;
  }
  ee_ReadBytes((uint8_t *)&eeprom_net_info, 0, sizeof(eeprom_net_info));
  if (eeprom_net_info.mac[0] == 0x00 && eeprom_net_info.mac[1] == 0x08 && eeprom_net_info.mac[2] == 0xdc)
  {
      memcpy(net_info, &eeprom_net_info, sizeof(wiz_NetInfo));
      return 1;
  }
  return 0;
}

步骤2:注册网页内容及 HTTP Server 初始化

文件:USER/main.c — 通过 reg_httpServer_webContent() 注册网页内容,再调用 httpServer_init() 完成 HTTP 服务器初始化。

reg_httpServer_webContent((uint8_t *)"index.html", (uint8_t *)HTML_PAGE);
httpServer_init(http_tx_ethernet_buf, http_rx_ethernet_buf, _WIZCHIP_SOCK_NUM_, socknumlist);

步骤3:注册 TIM3 中断驱动

文件:USER/main.c — TIM3 定时器中断处理函数,在 1ms 中断周期内分别实现 20ms 按键检测、100ms 温湿度采集、1000ms DHCP/HTTP 定时任务。

void TIM3_IRQHandler(void)
{
    static uint32_t tim3_1ms_count   = 0;
    static uint8_t  tim3_100ms_count = 0;
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        tim3_1ms_count++;
        tim3_100ms_count++;
        btn_timer_cnt++;
        if (btn_timer_flag && btn_timer_cnt >= 20)
        {
            btn_timer_flag = 0;
            if ((GPIO_ReadInputDataBit(GPIOG, GPIO_Pin_6) == RESET))
            {
                btn_cnt++;
            }
        }
        if (tim3_100ms_count >= 100)
        {
            tim3_100ms_count = 0;
            MYI2C_Handle(&SENx);
        }
        if (tim3_1ms_count >= 1000)
        {
            DHCP_time_handler();
            httpServer_time_handler();
            tim3_1ms_count = 0;
        }
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

步骤4:运行 HTTP 和 USB 功能及重启检测

文件:USER/main.c — 主循环持续运行 httpServer_run() 处理 HTTP 请求,检测 reboot_flag 置位时复位系统,同时运行 usb_run() 处理 USB 虚拟串口数据。

while (1)
{
    httpServer_run(SOCKET_ID);
    if (reboot_flag)
    {
        NVIC_SystemReset();
    }
    usb_run();
}

usb_run() 作为 USB 虚拟串口核心处理函数,检测 USB 连接状态变化并处理数据收发:

void usb_run(void)
{
    uint16_t len;
    if (usbstatus != bDeviceState)
    {
        usbstatus = bDeviceState;
        if (usbstatus == CONFIGURED)
        {
            usb_conn_flag = 1;
            printf("USB connected\r\n");
        }
        else
        {
            usb_conn_flag = 0;
            printf("USB disconnected\r\n");
        }
    }
    if (USB_USART_RX_STA & 0x8000)
    {
        len = USB_USART_RX_STA & 0x3FFF;
        strncpy((char *)(usb_data[data_cnt]), (char *)USB_USART_RX_BUF, len);
        usb_printf("Tx->:%s\r\n", usb_data[data_cnt]);
        USB_USART_RX_STA = 0;
        if (data_cnt < 18)
        {
            data_cnt++;
        }
        else
        {
            data_cnt = 0;
        }
    }
}

步骤5:请求内容处理(CGI 接口)

文件:USER/httpUtil.c — POST 方式由 predefined_set_cgi_processor() 处理(LED/蜂鸣器控制、网络配置、USB 发送、SD 卡写入);GET 方式由 predefined_get_cgi_processor() 处理(EEPROM 测试、网络信息、更新页面、SD 卡文件列表)。

uint8_t predefined_set_cgi_processor(uint8_t *uri_name, uint8_t *uri, uint8_t *buf, uint16_t *len)
{
    uint8_t ret = 1;

    if (strcmp((const char *)uri_name, "set_io_state.cgi") == 0)
    {
        uint8_t *param;
        uint8_t  name[10];
        uint8_t  val;
        param = get_http_param_value((char *)uri, "name");
        if (param)
        {
            strcpy((char *)name, (char *)param);
            param = get_http_param_value((char *)uri, "val");
            if (param) val = (uint8_t)ATOI(param, 10);

            if (strcmp((char *)name, "led") == 0)
            { if (val == 1) led_on(); else led_off(); }
            if (strcmp((char *)name, "beep") == 0)
            { if (val == 1) beep_on(); else beep_off(); }
        }
        *len = sprintf((char *)buf, "console.log('%s is %s');", name, val ? "on" : "off");
    }
    else if (strcmp((const char *)uri_name, "set_netinfo.cgi") == 0)
    {
        set_netinfo(uri, buf, len);
    }
    else if (strcmp((const char *)uri_name, "web_send_usb_data.cgi") == 0)
    {
        uint8_t *param;
        if (usb_conn_flag)
        {
            param = get_http_param_value((char *)uri, "data");
            if (param)
            {
                *len = sprintf((char *)buf, "$('usb-textbox').value='Tx->:'+$('usb-input').value+'\\n'+$('usb-textbox').value;");
                usb_printf("Rx->:%s\r\n", param);
            }
        }
        else
        {
            *len = sprintf((char *)buf, "alert('USB未连接');");
        }
    }
    else if (strcmp((const char *)uri_name, "put_sd.cgi") == 0)
    {
        uint8_t  filename[255];
        uint16_t filelen;
        uint8_t *param;
        param = get_http_param_value((char *)uri, "filename");
        if (param) strcpy((char *)filename, (char *)param);
        param = get_http_param_value((char *)uri, "filelen");
        if (param) filelen = ATOI(param, 10);
        param = get_http_param_value((char *)uri, "filedata");
        if (put_sd(filename, param, filelen))
            *len = sprintf((char *)buf, "alert('存储成功');readContent();");
        else
            *len = sprintf((char *)buf, "alert('存储文件失败!请检查SD卡是否正确插入!')");
    }
    else
    {
        ret = 0;
    }
    return ret;
}
uint8_t predefined_get_cgi_processor(uint8_t *uri_name, uint8_t *buf, uint16_t *len)
{
    uint8_t ret = 1;

    if (strcmp((const char *)uri_name, "check_eeprom.cgi") == 0)
    {
        if (ee_Test())
            *len = sprintf((char *)buf, "alert('eeprom测试通过!');");
        else
            *len = sprintf((char *)buf, "alert('eeprom测试失败!');");
    }
    else if (strcmp((const char *)uri_name, "get_netinfo.cgi") == 0)
    {
        make_json_netinfo(buf, len);
    }
    else if (strcmp((const char *)uri_name, "get_update_page.cgi") == 0)
    {
        make_json_update_page(buf, len);
    }
    else if (strcmp((const char *)uri_name, "get_sd.cgi") == 0)
    {
        uint8_t temp[255] = {0};
        uint8_t i;
        if (get_sd_filename() == 0)
        {
            *len = sprintf((char *)buf, "alert('SD卡未插入或识别异常!');");
        }
        else
        {
            memset(send_data, 0, sizeof(send_data));
            for (i = 0; i < file_cnt; i++)
            {
                sprintf((char *)temp, "%s\\n", file_name[i]);
                strcat((char *)send_data, (char *)temp);
            }
            *len = sprintf((char *)buf, "$('sd-textbox').value=\"%s\";", send_data);
            memset(send_data, 0, sizeof(send_data));
            memset(file_name, 0, sizeof(file_name));
            file_cnt = 0;
        }
    }
    else
    {
        ret = 0;
    }
    return ret;
}

步骤6:重启设备

文件:USER/main.c — 当网络配置被修改后再进行复位操作,避免出现客户端请求后 W55MH32 未响应就重启导致客户端请求超时。

if (reboot_flag)
{
    NVIC_SystemReset();
}

运行验证

烧录例程运行后,首先进行 PHY 链路检测,然后通过 DHCP 获取网络地址并打印网络地址信息,最后 HTTP Server 开始监听客户端请求。打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1),典型输出如下:

W55MH32 factory firmware
PHY is linked.
DHCP : IP Assignment from DHCP Server
IP  : 192.168.1.8
GW  : 192.168.1.1
SN  : 255.255.255.0
DNS : 192.168.1.1
HTTP Server started

在 PC 端浏览器中输入 W55MH32 的 IP 地址,打开网页配置界面,分别对 SD 卡读写、USB 串口功能以及网络配置参数修改进行测试。

文件列表与网络配置HTTP Server Web 界面 — SD 卡文件列表读取 + 网络参数配置 + 存储成功提示
Web 界面 USB 串口面板发送 "Hello W55MH32" + WIZ UartTool 串口接收 — USB 串口通信测试
Web 界面 — 修改 IP 地址为 192.168.1.8 并保存,网络配置成功提示

常见问题

Q: W55MH32 用户配置界面支持哪些功能

配置界面集成了温湿度实时显示、LED 与蜂鸣器控制、EEPROM 测试、SD 卡文件读写、网络参数(IP/子网掩码/网关/DNS)配置、USB 虚拟串口通信等功能,全部通过浏览器 AJAX 与后端 CGI 接口交互完成。

Q: CGI 请求中 GET 和 POST 方式有什么区别

GET 方式由 predefined_get_cgi_processor() 处理,用于获取数据(网络信息、温湿度、SD 卡文件列表、EEPROM 测试);POST 方式由 predefined_set_cgi_processor() 处理,用于下发控制指令(LED/蜂鸣器控制、网络参数保存、USB 数据发送、SD 卡文件写入)。

Q: 如何修改网络参数并使其生效

在配置界面网络配置区域选择 DHCP 或静态模式,填写 IP/子网掩码/网关/DNS 后点击"保存并配置"。前端校验 IPv4 格式后通过 set_netinfo.cgi 下发,为避免请求超时,网络配置修改后先置位 reboot_flag,待当前请求处理完成后再执行 NVIC_SystemReset() 复位使新参数生效。

Q: SD 卡文件读写有什么限制

网页端通过 FileReader 读取本地 .txt 文件,受 HTTP 请求长度限制,文件内容长度需小于 100 字符才能通过 put_sd.cgi 写入 SD 卡;读取时通过 get_sd.cgi 获取文件名列表。文件过大时会提示"文件过大!"。

总结

  • 网页配置方案:W55MH32 作为 HTTP 服务器,PC 端浏览器作为客户端,通过 HTML_PAGE 宏构建完整用户配置网页
  • 核心交互:网页通过 AJAX 与后端 CGI 接口交互,实现局部刷新而无需重载整页
  • 初始化流程:外设初始化 → 协议栈初始化 → EEPROM 读取网络参数 → 注册网页内容与 HTTP Server → TIM3 中断驱动定时任务
  • CGI 处理:predefined_set_cgi_processor() 处理 POST 控制指令,predefined_get_cgi_processor() 处理 GET 数据查询
  • 功能覆盖:温湿度显示、LED/蜂鸣器控制、EEPROM 测试、SD 卡读写、网络参数配置、USB 串口通信
  • 稳定可靠:网络配置修改后延迟复位避免请求超时,为嵌入式设备网络化管控提供简洁高效的实现思路

下载本章例程

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

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