hi
WIZnet W55MH32 多功能测试固件
更新于 2026年1月8日
本篇文章将详细介绍W55MH32 芯片用户配置界面的实现方法,搭配W55MH32LEVB,并结合实战例程,讲解如何以W55MH32 作为 HTTP 服务器、PC 端作为 HTTP 客户端,通过网页配置界面完成外设功能控制、SD 卡读写及网络参数配置等操作。
该例程用到的其他网络协议,例如DHCP, 请参考相关章节。有关 W55MH32 的初始化过程,请参考Network install,这里将不再赘述。
Web网页简介
Web(World Wide Web,万维网)是一种基于超文本和HTTP的全球性、动态交互的、跨平台的分布式图形信息系统。它建立在Internet之上,为用户提供了一个图形化、易于访问的界面,用于查找和浏览信息。
Web页面的基本构成
1.HTML(超文本标记语言)
作用:定义网页的结构和内容。
内容:
- 结构标签:如 <html>、<head>、<body>。
- 内容标签:如 <h1>、<p>、<img>、<a>。
- 表单标签:如 <form>、<input>、<button>。
2.CSS(层叠样式表)
作用:控制网页的样式和布局。
内容:
- 字体设置:如 font-family、font-size。
- 颜色设置:如 color、background-color。
- 布局设计:如 margin、padding、display、flex。
- 响应式设计:如媒体查询(@media)。
3.JavaScript(脚本语言)
作用:增加网页的交互性和动态功能。
应用:
- 表单验证。
- 动画效果。
- 与服务器交互(如通过 AJAX 请求)。
- 处理用户事件(如点击、悬停)。
4.Meta 信息
作用:提供页面的元数据,通常包含在 <head> 中。
内容:
- 网页标题:<title>。
- 字符集:<meta charset="UTF-8">。
- SEO 信息:如<meta name="description" content="描述">。
- 设备适配:如 <meta name="viewport" content="width=device-width, initial-scale=1.0>"。
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Page</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }
button { padding: 10px 20px; cursor: pointer; }
</style>
</head>
<body>
<h1>Hello, Web!</h1>
<p>Click the button for a surprise.</p>
<button onclick="alert('You clicked the button!')">Click Me</button>
</body>
</html>
Web页面交互
1.HTTP 请求页面
描述:客户端通过 HTTP 协议向服务器发送请求,服务器处理后返回响应。
特点:
- 最基础的交互方式。
- 包括常见的 HTTP 方法:GET、POST、PUT、DELETE 等。
示例:
- GET 请求:浏览器访问网页,获取静态资源(HTML、CSS、JavaScript 等)。
- POST 请求:提交表单数据。
2.表单提交
描述:通过 HTML 表单向服务器提交数据。
特点:
- 表单数据会被编码后随请求发送。
- 可使用 GET 或 POST 方法。
示例:
<form action="/submit" method="post">
<input type="text" name="username" placeholder="Enter your name">
<button type="submit">Submit</button>
</form>3.AJAX(Asynchronous JavaScript and XML)
描述:使用 JavaScript 在后台与服务器通信,更新部分页面内容而无需刷新整个页面。
特点:
- 提高用户体验,减少页面加载时间。
- 现代开发中多用 JSON 代替 XML。
示例:
fetch('/api/data', {
method: 'GET'
})
.then(response => response.json())
.then(data => console.log(data));Web服务器响应处理
1.直接响应
定义: 服务器直接处理请求,返回静态资源或简单的动态内容,而不调用外部脚本或程序。
特点
- 高效:直接处理请求,无需额外调用外部程序,适合静态内容。
适用场景:
- 静态资源(HTML、CSS、JavaScript、图像等)的分发。
- 轻量级动态内容生成。
工作流程
- 客户端发送 HTTP 请求。
- 服务器解析请求 URL,查找相应的资源(如文件路径)。
- 直接读取资源内容并返回给客户端,附加适当的 HTTP 响应头。
2.CGI 响应
定义: 服务器通过 CGI(Common Gateway Interface)调用外部程序或脚本,处理客户端请求并生成动态响应内容。
特点
- 灵活性: 可以动态生成内容,支持复杂逻辑。
适用场景:
- 动态内容生成(如用户登录、数据查询)。
- 与数据库交互或其他后台服务的复杂逻辑处理。
工作流程
- 客户端发送 HTTP 请求。
- 服务器解析请求并将请求数据(如 URL 参数或表单数据)传递给 CGI 程序。
- CGI 程序处理请求,生成响应内容并返回给服务器。
- CGI 程序生成的内容包装为 HTTP 响应发送给客户端。
实现过程
接下来,我们看看如何通过W55MH32用户配置页面实现操作和配置W55MH32。
首先需要编写网页内容,这里我们写了W55MH32用户配置的显示界面以及异常处理的提示内容,如下所示:
#ifndef _WEBPAGE_H_
#define _WEBPAGE_H_
/*************************************************************************************
* HTML Pages (web pages)
*************************************************************************************/
#define HTML_PAGE \
"<!DOCTYPE html>\n" \
"<html lang="en">\n" \
"<head>\n" \
" <meta charset="UTF-8">\n" \
" <meta name="viewport" content="width=device-width, initial-scale=1.0">\n" \
" <title>W55MH32 用户配置界面</title>\n" \
" <link rel="icon" href="data:,">\n" \
" <!-- CSS -->\n" \
" <style>\n" \
" body {\n" \
" font-family: Arial, sans-serif;\n" \
" text-align: center;\n" \
" margin: 20px;\n" \
" }\n" \
" .container {\n" \
" width: 80%;\n" \
" margin: auto;\n" \
" border: 1px solid #ccc;\n" \
" padding: 20px;\n" \
" box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);\n" \
" }\n" \
" .section {\n" \
" margin: 20px 0;\n" \
" padding: 10px;\n" \
" border: 1px solid #ccc;\n" \
" height: auto;\n" \
" display: flex;\n" \
" flex-direction: column;\n" \
" justify-content: flex-start;\n" \
" align-items: center;\n" \
" }\n" \
" button {\n" \
" background-color: #4CAF50;\n" \
" color: white;\n" \
" border: none;\n" \
" padding: 8px 16px;\n" \
" font-size: 16px;\n" \
" cursor: pointer;\n" \
" border-radius: 5px;\n" \
" margin: 5px;\n" \
" }\n" \
" button:hover {\n" \
" background-color: #45a049;\n" \
" }\n" \
" .textbox {\n" \
" width: 90%;\n" \
" height: 300px;\n" \
" margin: 10px 0;\n" \
" display: block;\n" \
" border: 1px solid #ccc;\n" \
" padding: 10px;\n" \
" resize: none;\n" \
" box-sizing: border-box;\n" \
" overflow-y: auto;\n" \
" }\n" \
" .input {\n" \
" width: 90%;\n" \
" padding: 10px;\n" \
" margin: 10px 0;\n" \
" border: 1px solid #ccc;\n" \
" box-sizing: border-box;\n" \
" }\n" \
" .row {\n" \
" display: flex;\n" \
" justify-content: space-between;\n" \
" align-items: flex-start;\n" \
" gap: 10px;\n" \
" }\n" \
" .column {\n" \
" width: 30%;\n" \
" height: 450px;\n" \
" }\n" \
" .info-section {\n" \
" display: flex;\n" \
" justify-content: space-between;\n" \
" align-items: center;\n" \
" margin-bottom: 10px;\n" \
" }\n" \
" .info {\n" \
" font-size: 16px;\n" \
" margin: 0 5px;\n" \
" }\n" \
" .button-container {\n" \
" display: flex;\n" \
" justify-content: space-evenly;\n" \
" align-items: center;\n" \
" flex-wrap: wrap;\n" \
" gap: 10px;\n" \
" }\n" \
" </style>\n" \
"</head>\n" \
"<!-- body -->\n" \
"<body onload='page_load()'>\n" \
" <div class="container">\n" \
" <h1>W55MH32 用户配置界面</h1>\n" \
" <p id="current-time"></p>\n" \
" <div class="section">\n" \
" <div class="info-section">\n" \
" <p class="info">当前温度:<label id="temperature">NULL</label>°C</p>\n" \
" <p class="info">当前湿度:<label id="humidity">NULL</label>%RH</p>\n" \
" <p class="info">按键次数统计:<label id="button_cnt">0</label>次</p>\n" \
" </div>\n" \
" <div class="button-container">\n" \
" <button val='1' name='led' onclick="set_io_state(this)">LED开</button>\n" \
" <button val='0' name='led' onclick="set_io_state(this)">LED关</button>\n" \
" <button val='1' name='beep' onclick="set_io_state(this)">蜂鸣器开</button>\n" \
" <button val='0' name='beep' onclick="set_io_state(this)">蜂鸣器关</button>\n" \
" <button onclick="check_eeprom()">测试eeprom</button>\n" \
" </div>\n" \
" </div>\n" \
" <div class="row">\n" \
" <div class="column section">\n" \
" <p>SD卡</p>\n" \
" <textarea class="textbox" readonly id="sd-textbox" placeholder="SD卡内容显示"></textarea>\n" \
" <button onclick="readContent()">读取内容</button>\n" \
" <button onclick="writeContent()">写入内容</button>\n" \
" <input type="file" id="fileInput" onchange="choose_file()" accept=".txt" style="display: none;" />\n" \
" </div>\n" \
" <div class="column section">\n" \
" <p>网络配置</p>\n" \
" <select class="input" name="network-mode" id="network-mode">" \
" <option value="2" selected>DHCP</option>" \
" <option value="1">静态配置</option>" \
" </select>" \
" <input class="input" type="text" id="mac-address" disabled>\n" \
" <input class="input" type="text" id="ip-address" placeholder="IP地址">\n" \
" <input class="input" type="text" id="subnet-mask" placeholder="子网掩码">\n" \
" <input class="input" type="text" id="default-gateway" placeholder="默认网关">\n" \
" <input class="input" type="text" id="dns" placeholder="DNS">\n" \
" <button onclick="saveConfiguration()">保存并配置</button>\n" \
" </div>\n" \
" <div class="column section">\n" \
" <p>USB串口</p>\n" \
" <textarea class="textbox" readonly id="usb-textbox" placeholder="USB串口显示"></textarea>\n" \
" <input class="input" type="text" id="usb-input" placeholder="输入消息">\n" \
" <button onclick="sendMessage()">发送消息</button>\n" \
" </div>\n" \
" </div>\n" \
" </div>\n" \
" <script>\n" \
" <!-- AJAX -->\n" \
" function AJAX(a,e){\n" \
" var c=d();\n" \
" c.onreadystatechange=b;\n" \
" function d(){\n" \
" if(window.XMLHttpRequest){\n" \
" return new XMLHttpRequest()\n" \
" }\n" \
" else{\n" \
" if(window.ActiveXObject){\n" \
" return new ActiveXObject("Microsoft.XMLHTTP")\n" \
" }\t\n" \
" }\n" \
" }\n" \
" function b(){\n" \
" if(c.readyState==4){\n" \
" if(c.status==200){\n" \
" if(e){\n" \
" e(c.responseText)\n" \
" }\n" \
" }\n" \
" }\n" \
" }\n" \
" this.doGet=function(){\n" \
" c.open("GET",a,true);\n" \
" c.send(null)\n" \
" };\n" \
" this.doPost=function(f){\n" \
" c.open("POST",a,true);\n" \
" c.setRequestHeader("Content-Type","application/x-www-form-urlencoded");\n" \
" c.setRequestHeader("ISAJAX","yes");\n" \
" c.send(f)\n" \
" }\n" \
" }\n" \
" function $(a){\n" \
" return document.getElementById(a)\n" \
" }\n" \
" function $$(a){\n" \
" return document.getElementsByName(a)\n" \
" }\n" \
" function $$_ie(a,c){if(!a){\n" \
" a="*"\n" \
" }\n" \
" var b=document.getElementsByTagName(a);\n" \
" var e=[];\n" \
" for(var d=0;d<b.length;d++){\n" \
" att=b[d].getAttribute("name");\n" \
" if(att==c){\n" \
" e.push(b[d])\n" \
" }\n" \
" }\n" \
" return e\n" \
" }\n" \
" <!-- led_beep_js -->\n" \
" function set_io_state(o){\n" \
" var name=o.attributes['name'].value;\n" \
" var val=o.attributes['val'].value;\n" \
" dout=new AJAX('set_io_state.cgi', function(t){try{eval(t);}catch(e){alert(e);}});\n" \
" dout.doPost('name='+name+'&val='+val);\n" \
" }\n" \
" <!-- eeprom -->\n"\
" function check_eeprom(){\n"\
" ajax = new AJAX('check_eeprom.cgi', function (t) { try { eval(t); } catch (e) { alert(e); } });\n" \
" ajax.doGet();\n" \
" }\n"\
" <!-- netinfo_js -->\n" \
" function get_netinfo(){\n" \
" var oUpdate;\n" \
" setTimeout(function(){\n" \
" oUpdate=new AJAX('get_netinfo.cgi',function(t){\n" \
" try{eval(t);}catch(e){alert(e);}\n" \
" });\n" \
" oUpdate.doGet();},500);\n" \
" }\n" \
" function set_netinfo(){\n" \
" const mode = document.getElementById('network-mode').value;\n" \
" const mac = document.getElementById('mac-address').value;\n" \
" const ip = document.getElementById('ip-address').value;\n" \
" const subnet = document.getElementById('subnet-mask').value;\n" \
" const gateway = document.getElementById('default-gateway').value;\n" \
" const dns = document.getElementById('dns').value;\n" \
" update_netinfo=new AJAX('set_netinfo.cgi',function(t){try{eval(t);}catch(e){alert(e);}});\n" \
" update_netinfo.doPost('mode='+mode+'&ip='+ip+'&sn='+subnet+'&gw='+gateway+'&dns='+dns);\n" \
" }\n" \
" function get_netinfo_callback(o){\n" \
" $('network-mode').value=o.mode;\n" \
" $('mac-address').value=o.mac;\n" \
" $('ip-address').value=o.ip;\n" \
" $('default-gateway').value=o.gw;\n" \
" $('subnet-mask').value=o.sn;\n" \
" $('dns').value=o.dns;\n" \
" }\n" \
" <!-- update_page_js -->\n" \
" function update_page(){\n" \
" setInterval(function () {\n" \
" update_page = new AJAX('get_update_page.cgi', function (t) { try { eval(t); } catch (e) { alert(e); } });\n" \
" update_page.doGet();\n" \
" }, 1000);\n" \
" }\n" \
" function update_page_callback(o){\n" \
" $('temperature').innerHTML=o.temperature;\n" \
" $('humidity').innerHTML=o.humidity;\n" \
" $('button_cnt').innerHTML=o.button_cnt;\n" \
" if(o.usbdata){\n" \
" $('usb-textbox').value=o.usbdata+$('usb-textbox').value;\n" \
" }\n" \
" if(o.usb_conn_flag==0){\n"\
" document.getElementById('usb-input').disabled=true\n"\
" $('usb-input').placeholder="USB未连接"\n"\
" }\n"\
" else{\n"\
" document.getElementById('usb-input').disabled=false\n"\
" $('usb-input').placeholder="输入消息"\n"\
" }\n"\
" }\n" \
" <!-- web_usb_js -->\n" \
" function web_send_usb_data(){\n" \
" dout=new AJAX('web_send_usb_data.cgi', function(t){try{eval(t);}catch(e){alert(e);}});\n" \
" dout.doPost('data='+$('usb-input').value);\n" \
" }\n" \
" <!-- sd_js -->\n" \
" function get_sd_file_name(){\n" \
" $('sd-textbox').value='';\n" \
" dout=new AJAX('get_sd.cgi', function(t){try{eval(t);}catch(e){alert(e);}});\n" \
" dout.doGet();\n" \
" }\n" \
" function choose_file(){ \n" \
" file = $('fileInput').files[0];\n" \
" dout=new AJAX('put_sd.cgi', function(t){try{eval(t);}catch(e){alert(e);}});\n" \
" if(file){\n" \
" const reader = new FileReader();\n" \
" reader.onload = function(e){\n" \
" let content = e.target.result;\n" \
" if(content.length<100){\n" \
" dout.doPost("filename="+file.name+"&filelen="+content.length+"&filedata="+content);\n" \
" }\n" \
" else{\n" \
" alert('文件过大!');\n" \
" }\n" \
" console.log("filename="+file.name+"&filelen='"+content.length+"'&filedata="+content);\n" \
" console.log('filedata:'+content);\n" \
" console.log('filename:'+file.name);\n" \
" console.log('filelen:'+content.length);\n" \
" };\n" \
" reader.readAsText(file);\n" \
" }\n" \
" else{\n" \
" alert('请选择文件');\n" \
" }\n" \
" }\n" \
" <!-- update_time_js -->\n" \
" function updateTime() {\n" \
" const now = new Date();\n" \
" const formattedTime = now.toLocaleString();\n" \
" document.getElementById('current-time').innerText = `当前时间: ${formattedTime}`;\n" \
" }\n" \
" setInterval(updateTime, 1000);\n" \
" updateTime();\n" \
" function writeContent() {\n" \
" document.getElementById('fileInput').value="";\n" \
" document.getElementById('fileInput').click();\n" \
" }\n" \
" function readContent() {\n" \
" get_sd_file_name();\n" \
" }\n" \
" function isValidIPv4(ip) {\n" \
" const regex = /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$/;\n" \
" return regex.test(ip);\n" \
" }\n" \
" function saveConfiguration() {\n" \
" const ip = document.getElementById('ip-address').value;\n" \
" const subnet = document.getElementById('subnet-mask').value;\n" \
" const gateway = document.getElementById('default-gateway').value;\n" \
" const dns = document.getElementById('dns').value;\n" \
" if (!isValidIPv4(ip)) {\n" \
" alert('IP地址格式不正确!');\n" \
" return;\n" \
" }\n" \
" if (!isValidIPv4(subnet)) {\n" \
" alert('子网掩码格式不正确!');\n" \
" return;\n" \
" }\n" \
" if (!isValidIPv4(gateway)) {\n" \
" alert('默认网关格式不正确!');\n" \
" return;\n" \
" }\n" \
" if (!isValidIPv4(dns)) {\n" \
" alert('DNS地址格式不正确!');\n" \
" return;\n" \
" }\n" \
" set_netinfo();\n" \
" }\n" \
" function sendMessage() {\n" \
" const input = document.getElementById('usb-input');\n" \
" if (input.value.trim() !== "") {\n" \
" web_send_usb_data();\n" \
" } else {\n" \
" alert('输入框不能为空!');\n" \
" }\n" \
" }\n" \
" function page_load() {\n" \
" get_netinfo();\n" \
" update_page();\n" \
" readContent();\n" \
" }\n" \
" </script>\n" \
"</body>\n" \
"</html>"
#endif通过宏定义HTML_PAGE构建了一个完整的 W55MH32 设备用户配置网页,该网页集成了温湿度显示、LED / 蜂鸣器控制、EEPROM 测试、SD 卡读写、网络参数配置、USB 串口通信等功能,并通过 AJAX 与后端 CGI 接口交互实现数据的实时更新和指令下发。
步骤1:外设及网络配置初始化
先完成 MCU 各类底层外设(时钟、延时、中断、串口、USB、定时器、GPIO、I2C)的初始化,再初始化 WIZnet 芯片的 TCP/IP 协议栈;随后按需加载默认 MAC 地址,检查以太网物理链路状态,从 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);
//ee_Erase();
/* 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)
{
/* No EEPROM detected */
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初始化
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); // Initializing the HTTP server步骤3:注册TIM3 中断驱动
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);
}
}该函数作为 TIM3 定时器的中断处理函数,在 1ms 中断周期内累计计时,分别实现 20ms 按键状态检测、100ms 温湿度传感器 I2C 数据处理、1000ms DHCP 和 HTTP 服务器的定时任务处理,并在每次中断结束后清除中断标志位。
步骤4:运行HTTP和USB功能以及重启检测
while (1)
{
httpServer_run(SOCKET_ID);
if (reboot_flag)
{
NVIC_SystemReset();
}
usb_run();
}
httpServer_run()函数的逻辑跟TCP Server基本一致,也是运行了一个状态机,根据SOCKET不同状态,执行相应的HTTP Server部分的处理,内容如下
void httpServer_run(uint8_t seqnum)
{
uint8_t s; // socket number
uint16_t len;
uint32_t gettime = 0;
#ifdef _HTTPSERVER_DEBUG_
uint8_t destip[4] = {
0,
};
uint16_t destport = 0;
#endif
http_request = (st_http_request *)pHTTP_RX; // Structure of HTTP Request
parsed_http_request = (st_http_request *)pHTTP_TX;
// Get the H/W socket number
s = getHTTPSocketNum(seqnum);
/* HTTP Service Start */
switch (getSn_SR(s))
{
case SOCK_ESTABLISHED:
// Interrupt clear
if (getSn_IR(s) & Sn_IR_CON)
{
setSn_IR(s, Sn_IR_CON);
}
// HTTP Process states
switch (HTTPSock_Status[seqnum].sock_status)
{
case STATE_HTTP_IDLE:
if ((len = getSn_RX_RSR(s)) > 0)
{
if (len > DATA_BUF_SIZE) len = DATA_BUF_SIZE;
len = recv(s, (uint8_t *)http_request, len);
*(((uint8_t *)http_request) + len) = '\0';
parse_http_request(parsed_http_request, (uint8_t *)http_request);
#ifdef _HTTPSERVER_DEBUG_
getSn_DIPR(s, destip);
destport = getSn_DPORT(s);
printf("\r\n");
printf("> HTTPSocket[%d] : HTTP Request received ", s);
printf("from %d.%d.%d.%d : %d\r\n", destip[0], destip[1], destip[2], destip[3], destport);
#endif
#ifdef _HTTPSERVER_DEBUG_
printf("> HTTPSocket[%d] : [State] STATE_HTTP_REQ_DONE\r\n", s);
#endif
// HTTP 'response' handler; includes send_http_response_header / body function
http_process_handler(s, parsed_http_request);
gettime = get_httpServer_timecount();
// Check the TX socket buffer for End of HTTP response sends
while (getSn_TX_FSR(s) != (getSn_TxMAX(s)))
{
if ((get_httpServer_timecount() - gettime) > 3)
{
#ifdef _HTTPSERVER_DEBUG_
printf("> HTTPSocket[%d] : [State] STATE_HTTP_REQ_DONE: TX Buffer clear timeout\r\n", s);
#endif
break;
}
}
if (HTTPSock_Status[seqnum].file_len > 0)
HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_INPROC;
else
HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_DONE; // Send the 'HTTP response' end
}
break;
case STATE_HTTP_RES_INPROC:
/* Repeat: Send the remain parts of HTTP responses */
#ifdef _HTTPSERVER_DEBUG_
printf("> HTTPSocket[%d] : [State] STATE_HTTP_RES_INPROC\r\n", s);
#endif
// Repeatedly send remaining data to client
send_http_response_body(s, 0, http_response, 0, 0);
if (HTTPSock_Status[seqnum].file_len == 0) HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_DONE;
break;
case STATE_HTTP_RES_DONE:
#ifdef _HTTPSERVER_DEBUG_
printf("> HTTPSocket[%d] : [State] STATE_HTTP_RES_DONE\r\n", s);
#endif
// Socket file info structure re-initialize
HTTPSock_Status[seqnum].file_len = 0;
HTTPSock_Status[seqnum].file_offset = 0;
HTTPSock_Status[seqnum].file_start = 0;
HTTPSock_Status[seqnum].sock_status = STATE_HTTP_IDLE;
//#ifdef _USE_SDCARD_
// f_close(&fs);
//#endif
#ifdef _USE_WATCHDOG_
HTTPServer_WDT_Reset();
#endif
http_disconnect(s);
break;
default:
break;
}
break;
case SOCK_CLOSE_WAIT:
#ifdef _HTTPSERVER_DEBUG_
printf("> HTTPSocket[%d] : ClOSE_WAIT\r\n", s); // if a peer requests to close the current connection
#endif
disconnect(s);
break;
case SOCK_CLOSED:
if (reboot_flag)
{
NVIC_SystemReset();
}
#ifdef _HTTPSERVER_DEBUG_
printf("> HTTPSocket[%d] : CLOSED\r\n", s);
#endif
if (socket(s, Sn_MR_TCP, HTTP_SERVER_PORT, 0x00) == s) /* Reinitialize the socket */
{
#ifdef _HTTPSERVER_DEBUG_
printf("> HTTPSocket[%d] : OPEN\r\n", s);
#endif
}
break;
case SOCK_INIT:
listen(s);
break;
case SOCK_LISTEN:
break;
default:
break;
} // end of switch
#ifdef _USE_WATCHDOG_
HTTPServer_WDT_Reset();
#endif
}usb_run()作为 USB 虚拟串口的核心处理函数,先检测 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);
//sprintf((char*)(usb_data[data_cnt]),"%s",(char*)USB_USART_RX_BUF);
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:请求内容处理
POST方式的CGI请求处理,在httpUtil.c文件的predefined_set_cgi_processor()函数中处理。
GET方式的CGI请求处理,在httpUtil.c文件的predefined_get_cgi_processor()函数中处理。
predefined_set_cgi_processor()函数作为 CGI 请求处理器,根据传入的 URI 名称匹配 todo.cgi、set_io_state.cgi 等不同 CGI 指令,执行 LED / 蜂鸣器控制、USB 数据发送、SD 卡文件写入等对应操作,并将执行结果以 JS 语句形式写入缓冲区、设置返回数据长度,最终返回 URI 名称的匹配状态。
uint8_t predefined_set_cgi_processor(uint8_t *uri_name, uint8_t *uri, uint8_t *buf, uint16_t *len)
{
uint8_t ret = 1; // ret = '1' means 'uri_name' matched
if (strcmp((const char *)uri_name, "todo.cgi") == 0)
{
// to do
; // val = todo(uri);
//*len = sprintf((char *)buf, "%d", val);
}
else 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) // GPIO;
{
strcpy((char *)name, (char *)param);
param = get_http_param_value((char *)uri, "val");
if (param) // State; high(on)/low(off)
{
val = (uint8_t)ATOI(param, 10);
}
if (strcmp((char *)name, (char *)"led") == 0)
{
if (val == 1)
{
led_on();
}
else
{
led_off();
}
}
if (strcmp((char *)name, (char *)"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) // GPIO;
{
strcpy((char *)filename, (char *)param);
}
param = get_http_param_value((char *)uri, "filelen");
if (param) // GPIO;
{
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;
}
predefined_get_cgi_processor()函数作为 CGI 读请求处理器,依据传入的 URI 名称匹配 todo.cgi、check_eeprom.cgi 等不同 CGI 指令,执行 eeprom 测试、获取网络信息、生成更新页面、读取 SD 卡文件名列表等操作,将结果以 JS 语句或 JSON 形式写入缓冲区并设置返回数据长度,最终返回 URI 名称的匹配状态。
uint8_t predefined_get_cgi_processor(uint8_t *uri_name, uint8_t *buf, uint16_t *len)
{
uint8_t ret = 1; // ret = 1 means 'uri_name' matched
if (strcmp((const char *)uri_name, "todo.cgi") == 0)
{
// to do
; // make_json_todo(buf, len);
}
else 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));
// printf("file_cnt:%d\r\n", file_cnt);
for (i = 0; i < file_cnt; i++)
{
sprintf((char *)temp, "%s\\n", file_name[i]);
strcat((char *)send_data, (char *)temp);
}
// printf("send_data:%s\r\n", send_data);
*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:重启设备
当网络配置被修改后再进行复位操作, 避免出现客户端请求后,W55MH32未响应就重启导致客户端请求超时的情况。
if (reboot_flag)
{
NVIC_SystemReset();
}
运行结果
烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息, 最后HTTP Server程序开始监听客户端的请求并处理响应,如下图所示:

W55MH32 用户配置界面设置如下

接下来,我们会分别对 SD 卡读写、USB 串口功能以及网络配置参数修改这几个功能点进行测试。
我们先测试SD卡的读写, HTTP 服务器接收到get_sd.cgi请求后,成功识别 SD 卡并读取到 4 个文件的文件名列表返回给客户端。

在W55MH32 用户配置界面操作如下:

接下来,我们再对 USB 串口的发送功能进行测试。

最后,我们在用户配置界面完成W55MH32网络参数的修改测试。


总结
本文介绍了 W55MH32 芯片网页用户配置界面的实现方案,其实现流程涵盖外设及网络配置初始化、网页内容与 HTTP Server 注册、中断驱动配置、功能运行及请求处理等关键步骤,成功在网页端实现温湿度显示、LED / 蜂鸣器控制、SD 卡读写、USB 串口通信、网络参数配置等核心功能,实测验证该网页配置界面操作便捷、稳定可靠,为嵌入式设备的网络化管控提供了简洁高效的实现思路。