目录
[1 前言](#1 前言)
[2 项目环境](#2 项目环境)
[2.1 硬件准备](#2.1 硬件准备)
[2.2 软件准备](#2.2 软件准备)
[2.3 方案图示](#2.3 方案图示)
[3 例程修改](#3 例程修改)
[4 功能验证](#4 功能验证)
[5. 总结](#5. 总结)
1 前言
HTTP(超文本传输协议,HyperText Transfer Protocol)是一种用于分布式、协作式、超媒体信息系统的应用层协议, 基于 TCP/IP 通信协议来传递数据,是万维网(WWW)的数据通信的基础。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法,通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。 以上是HTTP协议的简介,如想深入了解该协议,请参考mozilla网站上的介绍: HTTP 概述 - HTTP | MDN
MQTT是一种轻量级通信协议,基于TCP/IP,采用发布-订阅模式,广泛应用于物联网领域。
MQTT的工作原理围绕着三个核心部分:发布者(Publishers)、代理(Broker,也称服务器)和订阅者(Subscribers)。发布者负责发送消息到特定的主题(Topic),代理则接收这些消息并将其转发给所有订阅了该主题的订阅者。这种模式允许设备间异步通信,且设备不需要直接了解彼此的存在,从而降低了系统的复杂性。
W55MH32 是 WIZnet 新推出的高性能以太网单片机。它采用高性能 Arm® Cortex-M3 内核,主频最高达 216MHz,内置 1024KB FLASH、96KB SRAM 。尤为突出的是,其搭载 WIZnet TCP/IP offload 引擎(TOE),集成全硬件 TCP/IP 协议栈、MAC 及 PHY ,还配备 32KB 独立以太网收发缓存,供 8 个硬件 socket 使用,是真正的All-in-One解决方案。
2 项目环境
2.1 硬件准备
- W55MH32L-EVB
- 一根网线
- USB Type-C
2.2 软件准备
- 例程链接:w5500.com/w55mh32.html
- 开发环境:keil uvision 5
- 飞思创串口助手
- 浏览器
- 阿里云
2.3 方案图示

3 例程修改
1.我们把HTTP_Server的例程和MQTT&Aliyun的例程下载下来,我们对先对HTTP_Server的例程进行修改,我们创建一个web_server.c和web_server.h,并在web_server.c添加头文件以及全局变量和初始化
#include "user_main.h"`
`#include "web_server.h"`
`#include <stdio.h>`
`#include <string.h>`
`// 全局变量`
`uint8_t txBuff[2048]` `=` `{0};` `// 发送缓冲区`
`uint8_t rxBuff[2048]` `=` `{0};` `// 接收缓冲区`
`uint8_t socketCount =` `8;` `// 支持的Socket数量`
`uint8_t socketList[]` `=` `{0,1};` `// Socket列表`
`// LED状态管理`
`uint8_t led_status =` `0;` `// 0:关灯 1:开灯`
`uint8_t status_content[2]` `=` `"0";` `// 状态页内容
2.创建HTML用户界面,主要包含开/关控制按钮(JavaScript事件),实时调试信息面板,状态轮询机制(每2秒更新),时间戳日志功能
cpp
uint8_t *contentName = "index.html";
uint8_t content[] =
"<!doctype html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"GBK\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
" <title>LED Control</title>\n"
" <style>\n"
" #light {\n"
" width: 100px; height: 100px; border-radius: 50%;\n"
" margin: 20px auto; border: 2px solid #333;\n"
" transition: background-color 0.5s;\n"
" }\n"
" .light-on { background-color: yellow; box-shadow: 0 0 20px yellow; }\n"
" .light-off { background-color: #ccc; }\n"
" .btn { padding: 10px 20px; margin: 5px; }\n"
" #debug { margin-top: 20px; padding: 10px; border: 1px solid #ccc; font-family: monospace; max-height: 200px; overflow-y: auto; }\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <h1>LED Control Panel</h1>\n"
" <button class=\"btn\" onclick=\"controlLED(1)\">开灯</button>\n"
" <button class=\"btn\" onclick=\"controlLED(0)\">关灯</button>\n"
" <div id=\"debug\">Debug information will appear here...</div>\n"
" <script>\n"
" const light = document.getElementById('light');\n"
" const debugDiv = document.getElementById('debug');\n"
" \n"
" // 调试函数 - 在页面上显示调试信息\n"
" function log(message) {\n"
" console.log(message);\n"
" debugDiv.innerHTML += '<p>' + new Date().toLocaleTimeString() + ': ' + message + '</p>';\n"
" debugDiv.scrollTop = debugDiv.scrollHeight; // 自动滚动到底部\n"
" }\n"
" \n"
" // 初始加载时获取状态\n"
" fetchStatus();\n"
" \n"
" function controlLED(state) {\n"
" log('Sending control: ' + state);\n"
" fetch(`/control?action=${state ? '1' : '0'}&t=${Date.now()}`)\n"
" .then(response => {\n"
" log(`Control response: ${response.status}`);\n"
" if (response.ok) {\n"
" log('Control command successful');\n"
" fetchStatus();\n"
" } else {\n"
" log('Control command failed');\n"
" }\n"
" })\n"
" .catch(error => {\n"
" log('Control error: ' + error);\n"
" });\n"
" }\n"
" \n"
" function fetchStatus() {\n"
" const url = '/status?t=' + Date.now();\n"
" log('Fetching status: ' + url);\n"
" \n"
" fetch(url)\n"
" .then(response => {\n"
" log(`Status response: ${response.status}`);\n"
" if (!response.ok) {\n"
" throw new Error('Bad status: ' + response.status);\n"
" }\n"
" return response.text();\n"
" })\n"
" .then(status => {\n"
" log('Received status: ' + status);\n"
" updateLight(status.trim()); // 确保去除空白字符\n"
" })\n"
" .catch(error => {\n"
" log('Status error: ' + error);\n"
" });\n"
" }\n"
" \n"
" function updateLight(status) {\n"
" if (status === '1') {\n"
" light.className = 'light-on';\n"
" log('Light ON - UI updated');\n"
" } else if (status === '0') {\n"
" light.className = 'light-off';\n"
" log('Light OFF - UI updated');\n"
" } else {\n"
" log('Invalid status: ' + status);\n"
" }\n"
" }\n"
" \n"
" // 每2秒轮询一次状态\n"
" setInterval(fetchStatus, 2000);\n"
" \n"
" // 初始日志\n"
" log('Control panel initialized');\n"
" log('Waiting for status updates...');\n"
" </script>\n"
"</body>\n"
"</html>";
3.添加URL解析与控制逻辑
static` `uint8_t` `parse_url_action(uint8_t` `*url)` `{`
`// 从URL中提取action参数`
`uint8_t` `*pAction =` `(uint8_t` `*)strstr((char` `*)url,` `"action=");`
`return` `*(pAction +` `7);` `// 返回action值`
`}`
`// 根据action执行LED操作`
`static` `void` `do_led_action(uint8_t action)`
`{`
`if` `(action ==` `'1')` `// 开灯`
`{`
`printf("[LED] Turning ON\n");`
` led_status =` `1;`
` status_content[0]` `=` `'1';`
`}`
`else` `if` `(action ==` `'0')` `// 关灯`
`{`
`printf("[LED] Turning OFF\n");`
` led_status =` `0;`
` status_content[0]` `=` `'0';`
`}`
`else`
`{`
`printf("[LED] Unknown action: %c\n", action);`
`}`
`// 打印当前状态`
`printf("[STATUS] Current LED status: %d\n", led_status);`
`}
4.添加初始化WEB服务器函数
// 初始化Web服务器`
`void` `WebServer_Init(void)`
`{`
`// 初始化http服务器`
`httpServer_init(txBuff, rxBuff, socketCount, socketList);`
`// 注册html页面`
`reg_httpServer_webContent(contentName, content);`
`// 注册状态页面 - 关键修复:确保状态页正确注册`
`reg_httpServer_webContent("status", status_content);`
`// 注册控制端点`
`reg_httpServer_webContent("control",` `(uint8_t` `*)"OK");`
`printf("[WEB] Server initialized\n");`
`printf("[STATUS] Initial LED status: %d\n", led_status);`
`}`
`
- 添加web服务器
// 启动Web服务器`
`void` `WebServer_Start(void)`
`{`
`for` `(uint8_t i =` `0; i <` `sizeof(socketList); i++)`
`{`
`httpServer_run(i);`
`}`
`}
- 添加处理核心函数
void` `handler_user_function(uint8_t` `*url)`
`{`
`printf("[HTTP] Request received: %s\n", url);`
`// 检查是否为控制请求`
`if` `(strstr((char` `*)url,` `"control")` `!=` `NULL)`
`{`
`// 1. 从URL里提取出action的值`
`uint8_t action =` `parse_url_action(url);`
`// 2. 根据action的值,执行相应的LED操作`
`do_led_action(action);`
`// 调试输出`
`printf("[CONTROL] Processed action: %c\n", action);`
`}`
`// 检查是否为状态请求`
`else` `if` `(strstr((char` `*)url,` `"status")` `!=` `NULL)`
`{`
`// 确保返回最新的状态值`
`printf("[STATUS] Request received. Returning: %s\n", status_content);`
`// 更新状态页内容 - 确保返回最新值`
`reg_httpServer_webContent("status", status_content);`
`}`
`}`
`// 状态获取函数 `
`uint8_t*` `get_led_status(void)`
`{`
`return status_content;`
`}`
`// 状态更新通知 `
`void` `update_led_status(void)`
`{`
`// 当LED状态改变时调用此函数`
`reg_httpServer_webContent("status", status_content);`
`printf("[STATUS] Updated status page to: %s\n", status_content);`
`}`
`
7.添加web_server.函数
#ifndef __WEB_SERVER_H`
`#define __WEB_SERVER_H`
`#include "httpServer.h"`
`#include <string.h>`
`// 初始化Web服务器`
`void WebServer_Init(void);`
`// 启动Web服务器`
`void WebServer_Start(void);`
`extern uint8_t led_status; // 0:关灯 1:开灯`
`extern uint8_t status_content[2]; // 状态页内容`
`#endif`
`
- 接下来我们把MQTT&Aliyun中的函数添加到HTTP_Server中去,如图所示,把所需要的文件添加进来。

- 在do_mqtt.c函数中把clientid、passwd以及username修改为自己的参数,把post主题和set主题中的产品名称以及,设备名称修改为自己的。
mqttconn mqtt_params = {`
` .mqttHostUrl = "iot-06z00h54zbdynx7.mqtt.iothub.aliyuncs.com",`
` .server_ip = {`
` 0,`
` }, /*Define the Connection Server IP*/`
` .port = 1883, /*Define the connection service port number*/`
` .clientid = "k18mmode=2,signmethod=hmacsha256,timestamp=1751966302834|", /*Define the client ID*/`
` .username = "zhao&k18maZe3w1u", /*Define the user name*/`
` .passwd = "1f0069321cb554e432ad893177919fc65579c227a65a9a420b303b7bf8301778", /*Define user passwords*/`
` .pubtopic = "/sys/k18maZe3w1u/zhao/thing/event/property/post", /*Define the publication message*/`
` .subtopic = "/sys/k18maZe3w1u/zhao/thing/service/property/set", /*Define subscription messages*/`
` .pubQoS = QOS0, /*Defines the class of service for publishing messages*/`
`};
- 修改函数使数据定时上传到云平台,在PUB_MESSAGE和RECV中进行更改。
#define PUBLISH_COUNTER_MAX 500 // 10秒间隔(假设主循环周期为10ms)`
`static uint32_t publish_counter = 0;`
` case PUB_MESSAGE:`
` {`
` pubmessage.qos = QOS0;`
` char buffer[128]; // 确保缓冲区足够大`
` snprintf(buffer, sizeof(buffer), `
` "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{\"LEDSwitch\":%d},\"method\":\"thing.event.property.post\"}", `
` led_status);`
` pubmessage.payload = buffer;`
` pubmessage.payloadlen = strlen(pubmessage.payload);`
` ret = MQTTPublish(&c, (char *)&(mqtt_params.pubtopic), &pubmessage); /* Publish message */`
` if (ret != SUCCESSS) {`
` run_status = ERR;`
` } else {`
` printf("publish:%s,%s\r\n\r\n", mqtt_params.pubtopic, (char *)pubmessage.payload);`
` publish_counter = 0; // 重置计数器`
` run_status = RECV; // 转到接收/心跳状态`
` }`
` break;`
` }`
` case RECV: // 合并心跳和接收处理`
` {`
` // 保持MQTT连接活跃`
` if (MQTTYield(&c, 30) != SUCCESSS) {`
` run_status = ERR;`
` break;`
` }`
` // 处理接收到的消息`
` if (mqtt_recv_flag) {`
` mqtt_recv_flag = 0;`
` json_decode(mqtt_recv_msg);`
` }`
` // 计数器方案:检查是否到达发布间隔`
` publish_counter++;`
` if(publish_counter >= PUBLISH_COUNTER_MAX) {`
` run_status = PUB_MESSAGE; // 重新进入发布状态`
` }`
` break;`
` }
- 接收函数,打印出LED的状态的时候,要改变led_status的数值,在第17行和第22行进行添加,此目的是为了让云平台数据和网页数据进行统一。
void json_decode(char *msg)`
`{`
` cJSON *jsondata = NULL;`
` cJSON *params = NULL;`
` cJSON *LED = NULL;`
` jsondata = cJSON_Parse(msg);`
` if (jsondata == NULL)`
` {`
` printf("json parse fail.\r\n");`
` return;`
` }`
` params = cJSON_GetObjectItem(jsondata, "params");`
` LED = cJSON_GetObjectItem(params, "LEDSwitch");`
` if (LED->valueint == 1)`
` {`
`printf("LED ON\r\n");`
`led_status=1; status_content[0] = '1';`
` }`
` else`
` {`
` printf("LED OFF\r\n");led_status=0; status_content[0] = '0';`
` }`
` cJSON_Delete(jsondata);`
`}
- 在主函数为MQTT定义分配一路socket,然后添加一段PG6外部中断的代码,PG6作为本地控制继电器的开关。
#define SOCKET_ID4 4`
`void` `EXTI_Configuration(void)`
`{`
` EXTI_InitTypeDef EXTI_InitStructure;`
` GPIO_InitTypeDef GPIO_InitStructure;`
` NVIC_InitTypeDef NVIC_InitStructure;`
`// 1. 使能GPIOG时钟`
`RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);`
`// 2. 配置PG6为浮空输入`
` GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;`
` GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;`
` GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;`
`GPIO_Init(GPIOG,` `&GPIO_InitStructure);`
`// 3. 使能AFIO时钟`
`RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);`
`// 4. 映射PG6到EXTI6`
`GPIO_EXTILineConfig(GPIO_PortSourceGPIOG, GPIO_PinSource6);`
`// 5. 配置EXTI6中断线`
` EXTI_InitStructure.EXTI_Line = EXTI_Line6;`
` EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;`
` EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;` `// 上升沿触发`
` EXTI_InitStructure.EXTI_LineCmd = ENABLE;`
`EXTI_Init(&EXTI_InitStructure);`
`// 6. 配置NVIC`
` NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;` `// EXTI5~9共享中断通道`
` NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =` `0;`
` NVIC_InitStructure.NVIC_IRQChannelSubPriority =` `0;`
` NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;`
`NVIC_Init(&NVIC_InitStructure);`
`}`
`// EXTI9_5中断服务函数`
`void` `EXTI9_5_IRQHandler(void)`
`{`
`// 检查EXTI6中断标志`
`if` `(EXTI_GetITStatus(EXTI_Line6)` `!= RESET)`
`{`
`// 清除中断标志`
`EXTI_ClearITPendingBit(EXTI_Line6);`
`// 执行中断处理`
` led_status =` `!led_status;`
` status_content[0]` `=` `(status_content[0]` `==` `'0')` `?` `'1'` `:` `'0';`
`if(led_status==0)`
`{`
`printf("LED_OFF");`
`}`
`else`
`{` `printf("LED_ON");}`
`}`
`}`
`}
- 把do_mqtt()函数添加到循环中去,然后在main.c中添加#include "do_mqtt.h"头文件.
4 功能验证
1.硬件连接完毕,烧录程序上电打印如下信息:在浏览器输入192.168.2.22来进入html页面

2.我们同时打开串口助手,阿里云物联网平台以及HTML网页控制来进行观察,我们先使用网页控制查看现象,先进行打开

3.关闭LED

- 我们再来使用阿里云进行控制,首先设置开启LED。

- 关闭LED

- 本地按键控制,查看阿里云物联网平台状态以及html网页变化。我们先测试打开

- 我们再进行本地按键关闭测试

由上述可知,功能完好,可由阿里云,html网页和按键控制继电器开关,三种模式的开与管互通,这三种模式下都可以查看继电器的状态
5. 总结
本项目通过 W55MH32成功实现了 Web 网页以及云平台对继电器的远程控制,验证了基于以太网的嵌入式 Web 服务器方案和云平台控制的可行性。感谢大家的耐心阅读!如果您在阅读过程中有任何疑问,或者希望进一步了解这款产品及其应用,欢迎随时通过私信或评论区留言。我们会尽快回复您的消息,为您提供更详细的解答和帮助!