W55MH32三模自由控:小程序按键网页随选
在工业自动化、智能家居等领域,为传统MCU增添以太网远程控制功能,您是否还在为复杂的多芯片方案头疼?需要MCU、以太网PHY、网络协议栈等一系列组件,不仅设计复杂、PCB面积大,更带来了高昂的BOM成本和稳定性挑战。
现在,这一切有了更优解。基于W55MH32单芯片以太网MCU,我们使用这款芯片来写入代码来实现继电器方案,以一颗芯片的力量,实现了堪比多芯片方案的强大网络功能,让产品联网化设计变得前所未有的简单。
目录
- 1. 项目核心价值与实现效果
- 2. 通信架构说明
- 3. W55MH32单芯片方案优势
- 4. 应用场景
- 5. 项目环境
- 5.1 软件准备
- 5.2 硬件准备
- 6. 阿里云平台操作
- 6.1 创建产品
- 7. 小程序修改
- 7.1 小程序源工程下载
- 8. W55MH32配置
- 8.1 完善连接参数
- 8.2 添加网页控制
- 9. 功能验证
- 总结
1. 项目核心价值与实现效果
通过本地按键、网页端、微信小程序三种形式,实现继电器的精准通断控制,满足不同场景下的操作需求。
支持三种控制模式切换:
- 本地按键:设备端即时物理操作,适合现场紧急控制或快速调试。
- 网页控制:通过浏览器访问设备 IP,可视化界面远程操作,适合多继电器集中管理场景。
- 小程序控制:依托阿里云服务器,手机端随时随地远程控制,适合移动化、无人值守场景。
基于以太网 + 阿里云架构,通信稳定且延迟低,指令执行响应迅速。
核心验证:验证了 W55MH32 在以太网继电器控制场景的高适用性,多控制模式下的兼容性、稳定性表现突出。
简单来说:用本地按键、电脑网页或手机小程序,就能灵活控制继电器的开关状态,无论是工业设备启停还是智能家居自动化都很实用。
方案图示
W55MH32单芯片集成TCP/IP协议栈、MAC+PHY,无需外置网络芯片

普通方案需要多个芯片配合才能使用,W55MH32可以直接取代这些芯片,而且普通方案使用的是TCP/IP软件协议栈,上手难度高,难调节。W55MH32使用的是硬件TCP/IP协议栈,只需要简单的配置,就可以实现数据交互。
2. 通信架构说明
整个系统的通信流程清晰易懂,支持三种控制模式,这三种模式看似复杂,其实他们的控制核心,均由一颗W55MH32独立完成,极大地简化了硬件设计,降低了整体成本:
- 微信小程序控制 (远程)
- 微信小程序发送控制指令(继电器开关)
- 指令通过MQTT协议上发到阿里云云平台
- 云平台对指令进行转发
- W55MH32解析下发的指令,然后执行响应的动作
- 设备状态通过串口助手、网页界面反馈
- 网页控制 (局域网)
- 用户在浏览器中输入设备IP地址,访问设备本地的Web服务器
- 在网页界面点击控制按钮
- 浏览器将触发一个HTTP GET请求,直接发送至设备端的W55MH32
- W55MH32解析HTTP请求中的参数,并直接执行相应的继电器动作
- 设备状态可实时更新在网页界面上,并可通过串口助手和微信小程序反馈
- 本地按键
- 用户直接按下电路板上的物理按键
- 按键动作触发W55MH32的外部中断
- W55MH32的固件程序立即检测到按键事件,无需经过任何网络协议栈
- 主控芯片直接改变继电器的控制引脚电平,执行开关动作,实现毫秒级响应
- 动作结果可通过串口助手、网页界面和微信小程序直接确认
3. 为什么选择W55MH32?单芯片方案的核心优势
- 极大简化设计,降低成本
- 传统方案:MCU + 以太网控制器/PHY + 网络协议栈(软件集成或外置芯片),设计复杂,元器件多。
- W55MH32方案:单芯片集成Cortex-M3内核、TCP/IP协议栈、MAC和PHY。直接引出RJ45即可,硬件设计复杂度与BOM成本显著下降。
- 性能稳定,响应迅速
- 基于硬件的网络处理,比软件协议栈更高效、更稳定。项目验证了在MQTT通信下的低延迟和高可靠性。
- 开发快捷,生态完善
- 提供丰富的例程(如本项目涉及的MQTT、HTTP Server等),降低开发门槛,加速产品上市周期。
4.应用场景
- 工业领域:工厂设备远程启停、状态监控。
- 智能建筑:楼宇照明、空调系统的远程集中控制。
- 农业物联网:大棚灌溉、通风设备的自动化管理。
- 智能家居:家电的远程控制和智能化管理。
5. 项目环境
5.1 软件准备
- 开发环境:Keil uVision 5
- 调试工具:WIZ UartTool
- 小程序开发:微信开发者工具
- 云平台:阿里云
5.2 硬件准备
- W55MH32L-EVB
- RJ45网线
6. 阿里云平台操作
6.1 创建产品
- 先点击产品,再点击创建产品。
- 然后我们再依次填入产品的名称,按照下图进行选择后确认。
- 点击添加设备。
- 点击添加设备,设备的名称和备注可以自己选择填写,此步骤需要重复两次,添加两个设备,一个可以作为小程序端,一个作为W55MH32端,要区别清楚,做好名字区分。
- 点击产品,在自定义Topic类下面定义两个Topic,一个用在W55MH32端,一个用在小程序端。
- 先点击云产品流转,然后点击返回旧版。
- 点击创建规则,并写上规则名称。
- 先点击编辑SQL,然后把信息如图中填写。
- 添加转发数据的操作,并设置转发数据到另一个Topic。
- 然后按照同样的方式,再转发设置一个规则这个是对微信小程序的命令进行一个转发。
- 然后按照同样的步骤进行设置编写。
设置完成后,云平台的这两个设备就可以对本地的W55MH32和小程序进行连接,然后对数据进行转发。
7. 小程序修改
7.1 小程序源工程下载
- 链接: https://pan.baidu.com/s/1zZymmcZBy-CZJF-9mNTQ6A?pwd=rbdx 提取码: rbdx
下载完成后打开文件夹点击app.wxss的图标,就会跳转到这个页面
然后在阿里云创建的设备界面把参数对应填到上方小程序控制的参数的地方:
- pubTopic应该填whatchat的主题
- subTopic订阅的为这个地方的主题,也就是W55MH32进行转发的主题
设置完这些之后,就可以编译然后看到小程序端的设备在线了。
8. W55MH32配置
8.1 完善连接参数
下载W55MH32 MQTT的例程,修改mqttconn结构体参数为你的阿里云信息:
其中MQTT连接参数按照下面的参数进行填写
其中发布主题选择下图中的主题
订阅主题选择小程序发布主题然后转发的这个主题
那这段连接的的数据我们就补充完整了,如下所示
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 = "k18maOZwQAS.W55MH32|securemode=2,signmethod=hmacsha256,timestamp=1760498715342|", /*Define the client ID*/.username = "W55MH32&k18maOZwQAS", /*Define the user name*/.passwd = "5e779efe8005537aef0c7a911c797a61beb2a6f3b61d7abf0b9d455cb0c4ea55", /*Define user passwords*/.pubtopic = "/k18maOZwQAS/W55MH32/user/W55MH32", /*Define the publication message*/.subtopic = "/sys/k18maOZwQAS/W55MH32/thing/service/property/set", /*Define subscription messages*/.pubQoS = QOS0, /*Defines the class of service for publishing messages*/
};
然后我们修改do_mqtt.c下方上报的json数据
case PUB_MESSAGE:{pubmessage.qos = QOS0;char buffer[128]; snprintf(buffer, sizeof(buffer), "{\"id\":\"123\",\"version\":\"1.0\",\"params\":{\"Relay\":%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;
把此状态机进行更改,此时编译设备之后就可以看到阿里云的W55MH32设备已经上线了,我们还需要解析转发的json数据,我们需要把do_mqtt.c中的json_decode函数进行更改
void json_decode(char *msg)
{cJSON *jsondata = NULL;cJSON *params = NULL;cJSON *relay = NULL;jsondata = cJSON_Parse(msg);if (jsondata == NULL){printf("json parse fail.\r\n");return;}params = cJSON_GetObjectItem(jsondata, "params");if (params == NULL){printf("params not found.\r\n");cJSON_Delete(jsondata);return;}relay = cJSON_GetObjectItem(params, "Relay");if (relay == NULL){printf("Relay field not found.\r\n");cJSON_Delete(jsondata);return;}if (relay->valueint == 1){printf("Relay ON\r\n");led_status = 1;set_led_status(1);}else if (relay->valueint == 0){printf("Relay OFF\r\n"); led_status = 0;set_led_status(0);}else{printf("Invalid Relay value: %d\r\n", relay->valueint);}cJSON_Delete(jsondata);
}
此时小程序控制的方式现在已经可以使用了,我们还需要添加网页控制,和本地控制。
8.2 添加网页控制
我们在程序中添加,下图这些函数,这些函数可以在http服务器例程代码中找到
我们接下来编辑网页的UI界面
uint8_t content[] =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>Relay Control Panel</title>"
"<style>"
"*{margin:0;padding:0;box-sizing:border-box}"
"body{font-family:Arial,sans-serif;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);min-height:100vh;display:flex;justify-content:center;align-items:center;padding:20px}"
".container{background:rgba(255,255,255,0.95);border-radius:20px;box-shadow:0 15px 35px rgba(0,0,0,0.2);padding:40px;max-width:500px;width:100%;text-align:center}"
"h1{color:#333;margin-bottom:30px;font-size:28px;font-weight:600}"
".relay-container{position:relative;margin:30px auto;width:200px;height:300px}"
".relay-body{position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:120px;height:80px;background:#2c3e50;border-radius:10px 10px 5px 5px;box-shadow:0 5px 15px rgba(0,0,0,0.3)}"
".relay-coil{position:absolute;top:20px;left:50%;transform:translateX(-50%);width:60px;height:40px;background:#7f8c8d;border-radius:5px}"
".relay-arm{position:absolute;top:70px;left:50%;transform:translateX(-50%) rotate(0deg);width:8px;height:120px;background:#95a5a6;border-radius:4px;transform-origin:top center;transition:transform 0.5s ease-in-out;z-index:2}"
".relay-arm.relay-on{transform:translateX(-50%) rotate(15deg)}"
".relay-contact{position:absolute;top:180px;left:50%;transform:translateX(-50%);width:80px;height:10px;background:#e74c3c;border-radius:5px;transition:background 0.5s ease-in-out}"
".relay-contact.relay-on{background:#2ecc71;box-shadow:0 0 15px #2ecc71}"
".status-indicator{display:inline-block;width:12px;height:12px;border-radius:50%;margin-right:8px;background:#e74c3c;box-shadow:0 0 5px #e74c3c;transition:all 0.5s ease-in-out}"
".status-indicator.relay-on{background:#2ecc71;box-shadow:0 0 10px #2ecc71}"
".status-text{font-size:18px;font-weight:600;margin:20px 0;color:#333}"
".btn-group{display:flex;justify-content:center;gap:15px;margin-top:20px}"
".btn{padding:12px 25px;border:none;border-radius:50px;font-size:16px;font-weight:600;cursor:pointer;transition:all 0.3s ease;box-shadow:0 5px 15px rgba(0,0,0,0.1)}"
".btn-on{background:linear-gradient(135deg,#2ecc71,#27ae60);color:white}"
".btn-off{background:linear-gradient(135deg,#e74c3c,#c0392b);color:white}"
".btn:hover{transform:translateY(-3px);box-shadow:0 8px 20px rgba(0,0,0,0.2)}"
".btn:active{transform:translateY(1px)}"
".connection-status{margin-top:25px;padding:10px;border-radius:10px;background:#f8f9fa;font-size:14px;color:#6c757d}"
".last-update{font-size:12px;color:#95a5a6;margin-top:5px}"
".pulse{animation:pulse 1.5s infinite}"
"@keyframes pulse{0%{opacity:1}50%{opacity:0.5}100%{opacity:1}}"
"</style>"
"</head>"
"<body>"
"<div class=\"container\">"
"<h1>Relay Control System</h1>"
"<div class=\"relay-container\">"
"<div class=\"relay-body\"></div>"
"<div class=\"relay-coil\"></div>"
"<div class=\"relay-arm\" id=\"relayArm\"></div>"
"<div class=\"relay-contact\" id=\"relayContact\"></div>"
"</div>"
"<div class=\"status-text\">"
"<span class=\"status-indicator\" id=\"statusIndicator\"></span>"
"<span id=\"statusText\">Relay: OFF</span>"
"</div>"
"<div class=\"btn-group\">"
"<button class=\"btn btn-on\" onclick=\"controlRelay(1)\">Turn ON</button>"
"<button class=\"btn btn-off\" onclick=\"controlRelay(0)\">Turn OFF</button>"
"</div>"
"<div class=\"connection-status\">"
"<div>Status: <span id=\"connStatus\">Connected</span></div>"
"<div class=\"last-update\">Last Update: <span id=\"lastUpdate\">--</span></div>"
"</div>"
"</div>"
"<script>"
"const relayArm=document.getElementById('relayArm');"
"const relayContact=document.getElementById('relayContact');"
"const statusIndicator=document.getElementById('statusIndicator');"
"const statusText=document.getElementById('statusText');"
"const connStatus=document.getElementById('connStatus');"
"const lastUpdate=document.getElementById('lastUpdate');"
"let connectionOk=true;"
"function controlRelay(state){"
"fetch('/control?action='+state).then(r=>{"
"if(r.ok){connectionOk=true;updateConnectionStatus();setTimeout(fetchStatus,500)}"
"else{handleConnectionError()}}).catch(handleConnectionError)}"
"function fetchStatus(){"
"fetch('/status?t='+Date.now()).then(r=>{if(!r.ok)throw new Error('Bad status');return r.text()})"
".then(status=>{connectionOk=true;updateConnectionStatus();updateRelayUI(status.trim())})"
".catch(handleConnectionError)}"
"function updateRelayUI(status){"
"let isOn=status.includes('11');"
"if(isOn){"
"relayArm.classList.add('relay-on');"
"relayContact.classList.add('relay-on');"
"statusIndicator.classList.add('relay-on');"
"statusText.textContent='Relay: ON';"
"}else{"
"relayArm.classList.remove('relay-on');"
"relayContact.classList.remove('relay-on');"
"statusIndicator.classList.remove('relay-on');"
"statusText.textContent='Relay: OFF';"
"}"
"lastUpdate.textContent=new Date().toLocaleTimeString();"
"}"
"function handleConnectionError(){"
"connectionOk=false;updateConnectionStatus();statusIndicator.classList.add('pulse')}"
"function updateConnectionStatus(){"
"if(connectionOk){"
"connStatus.textContent='Connected';connStatus.style.color='#27ae60';statusIndicator.classList.remove('pulse')"
"}else{"
"connStatus.textContent='Disconnected';connStatus.style.color='#e74c3c'"
"}}"
"setInterval(fetchStatus,1500);"
"window.addEventListener('load',function(){lastUpdate.textContent=new Date().toLocaleTimeString()});"
"fetchStatus();"
"</script>"
"</body>"
"</html>";
这是我们的网页设计界面
web_server.c除了网页,还有初始化服务器,打开服务器以及接口函数和执行函数,也要在这个函数中进行实现。
static uint8_t parse_url_action(uint8_t *url)
{// 在URL中查找"action="字符串,定位动作参数位置uint8_t *pAction = (uint8_t *)strstr((char *)url, "action=");if (pAction == NULL) // 未找到"action=",解析失败{return 0;}else // 找到"action=",返回其后的动作值("action="长度为7,故偏移7字节){return *(pAction + 7);}
}
static void do_led_action(uint8_t action)
{if (action == '1') // 动作值为'1',开启继电器{ led_status = 1; // 更新继电器状态标志为开启(1)// 更新状态缓冲区(具体含义可根据实际需求定义,此处示例为三位状态值)status_content[0] = '1';status_content[1] = '1';status_content[2] = '0'; printf("Relay ON"); // 打印继电器开启日志// 注释:实际应用中可在此处添加继电器硬件控制代码,如:// HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET);}else if (action == '0') // 动作值为'0',关闭继电器{led_status = 0; // 更新继电器状态标志为关闭(0)// 更新状态缓冲区status_content[0] = '1';status_content[1] = '0';status_content[2] = '0';printf("Relay OFF"); // 打印继电器关闭日志}
}
void WebServer_Init(void)
{// 初始化HTTP服务器,指定收发缓冲区、Socket数量和Socket列表httpServer_init(txBuff, rxBuff, socketCount, socketList);// 根据继电器初始状态初始化状态缓冲区if(led_status) { // 若继电器初始为开启状态status_content[0] = '1';status_content[1] = '1';} else { // 若继电器初始为关闭状态status_content[0] = '1';status_content[1] = '0'; }status_content[2] = '0'; // 状态缓冲区第三位固定值(示例)// 注册静态网页内容到服务器reg_httpServer_webContent(contentName, content); // 注册"control"资源,用于反馈控制操作结果reg_httpServer_webContent("control", (uint8_t *)"OK"); // 注册"status"资源,用于返回当前继电器状态reg_httpServer_webContent("status", status_content);
}
void WebServer_Start(void)
{// 遍历所有Socket,处理每个Socket上的HTTP请求for (uint8_t i = 0; i < sizeof(socketList); i++){httpServer_run(i);}
}
void handler_user_function(uint8_t *url)
{ if (strstr((char *)url, "control") != NULL) // 检测到控制请求(URL含"control"){// 解析URL中的action参数,获取继电器控制动作uint8_t action = parse_url_action(url); // 执行继电器控制动作do_led_action(action);// 更新服务器中的"status"资源,同步当前继电器状态reg_httpServer_webContent("status", status_content); // 更新"control"资源,反馈控制成功reg_httpServer_webContent("control", (uint8_t *)"OK");}else if (strstr((char *)url, "status") != NULL) // 检测到状态查询请求(URL含"status"){// 更新服务器中的"status"资源,返回当前继电器状态reg_httpServer_webContent("status", status_content);}else{// 其他URL请求,此处暂不处理}
}
uint8_t* get_led_status(void)
{return status_content;
}
void set_led_status(uint8_t status)
{led_status = status; // 更新继电器状态标志if(status) { // 若目标状态为开启status_content[0] = '1';status_content[1] = '1';status_content[2] = '0';} else { // 若目标状态为关闭status_content[0] = '1';status_content[1] = '0';status_content[2] = '0';}// 更新服务器中的"status"资源,确保Web客户端获取最新状态reg_httpServer_webContent("status", status_content);
}
此时代码部分已经全部修改完成,我们接下来进行测试功能的情况
9. 功能验证
先使用微信小程序调节看是否正常

开关正常,
再来使用网页控制是否正常

开关正常,
最后我们使用本地按键控制看看是否正常

开关正常,由此可以看出三种开关都可以正常控制以及响应状态的变化。
总结
如果您正在寻找一种能够简化设计、降低成本、提升稳定性的以太网互联方案,W55MH32无疑是您的理想选择。它不仅仅是一颗芯片,更是您产品迈向智能化、网络化的强大引擎。
立即访问W55MH32,获取W55MH32详细数据手册、开发板信息及完整项目源码,开启您的单芯片以太网开发之旅!
本项目成功设计并实现了一套集本地、网页和远程控制于一体的智能化继电器控制系统。系统以W55MH32以太网单片机为核心,结合阿里云物联网平台与微信小程序,构建了一个稳定、高效、多接入方式的物联网应用原型,充分验证了W55MH32在工业物联网控制场景下的强大适用性。
