尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32+W5500+MQTT接入阿里云:从硬件连接到Web控制

STM32+W5500+MQTT接入阿里云:从硬件连接到Web控制 简介面向物联网单片机开发者的一套实战代码工程基于STM32F103与W5500以太网模块以SPI接口连接并通过MQTT协议接入阿里云物联网平台实现数据主动上报、平台指令下发、继电器控制、温湿度状态反馈及WEB端联动控制。代码采用KEIL开发当前适配STM32F103C8T6若使用其他F103型号修改芯片型号与FLASH容量即可移植下载时可按实际调试器选择jlink或stlink配置。压缩包共218个文件包含C源码、H头文件、KEIL工程配置文件及axf、hex、map等编译产物整体仅6.99MB方便直接查看工程结构并烧录验证。目前已有1587人学习下载适合智慧养老、智慧医疗、智慧农业等物联网场景的开发者参考。借助这套工程代码可快速梳理W5500以太网通信、STM32外设驱动、阿里云MQTT接入及双向控制的完整流程配套标准外设库文件也便于二次开发和功能扩展硬件设计、软件开发与数据联网方面还可联系作者进一步交流。1. 基于STM32W5500MQTT接入阿里云为什么这次选以太网而不是Wi-FiSTM32接W5500再走MQTT上阿里云这个组合在开发板上跑通不难但真正放到产线或实验室里连续跑有线以太网比ESP8266这类无线方案少掉很多“玄学问题”。W5500是WIZnet的硬协议栈以太网芯片MCU只通过SPI读写数据TCP连接、重传、分包都由芯片内部处理所以STM32看到的链路非常简单采集温湿度只是单总线或ADC读数控制继电器只是GPIO翻转云端交互则是固定格式的MQTT报文。文章会顺着“硬件接线 → W5500驱动 → 阿里云MQTT参数 → Topic订阅发布 → WEB控制”这条线走适合已经有STM32基础、但还没把设备真正挂到云平台的同学或者想把手里的W5500模块从“TCP透传”升级到“MQTT物联网应用”的人。2. STM32与W5500硬件连接SPI引脚、复位和中断必须接对2.1 W5500硬件接口与典型接线表W5500是一颗10/100M以太网控制器内部集成了MAC和PHY对外只暴露SPI接口。它和STM32之间是主从关系硬件连接比lwip方案简单很多但有几个关键点不能想当然SCS是SPI片选低电平有效RST最好接到STM32一个GPIO上软件可控INT是中断输出通知MCU有接收数据或socket事件接上之后可以省掉轮询。以STM32F103C8T6为例我常用SPI1和W5500模块对接接线表如下W5500引脚STM32引脚说明SCLKPA5 (SPI1_SCK)SPI时钟MISOPA6 (SPI1_MISO)W5500输出STM32输入MOSIPA7 (SPI1_MOSI)STM32输出W5500输入SCSPA4片选低有效RSTPA3复位输入INTPA2中断输出可选但建议接3.3V3.3VW5500模块供电GNDGND共地注意大部分W5500模块板载了25MHz晶振和网络变压器接线时只需要担心模块供电电流。W5500 PHY部分电流在上电瞬间能达到200mA左右开发板LDO带不动时会出现链路偶尔起来不了的现象这时候不要在3.3V上串二极管尽量用独立稳压芯片供电。2.2 CubeMX配置SPI和复位引脚的初始化CubeMX配置SPI1时参数设置为Full-Duplex Master、8bit数据、MSB先行时钟极性CPOL0、相位CPHA0即SPI Mode 0。分频系数先按16倍频算STM32F103的APB2是72MHz分频16就是4.5MHz的SPI时钟跑W5500完全够。如果后面发现SPI数据偶发错位再把分频调到32先保证功能稳定再优化速度。PA2、PA3、PA4都设为GPIO输出PA4到PA3初始化为高。PA2如果是外部中断模式拉高电平。初始化代码如下// w5500_hw.c — W5500 硬件初始化 #include main.h #include spi.h extern SPI_HandleTypeDef hspi1; // W5500 片选、复位、中断引脚定义按 CubeMX 生成的宏 #define W5500_SCS_LOW() HAL_GPIO_WritePin(SCS_GPIO_Port, SCS_Pin, GPIO_PIN_RESET) #define W5500_SCS_HIGH() HAL_GPIO_WritePin(SCS_GPIO_Port, SCS_Pin, GPIO_PIN_SET) #define W5500_RST_LOW() HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET) #define W5500_RST_HIGH() HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET) void w5500_hw_init(void) { // SPI 已在 MX_SPI1_Init() 完成这里先复位 W5500 W5500_RST_LOW(); HAL_Delay(100); // 低电平至少 100ms确保内部 PLL 重新锁定 W5500_RST_HIGH(); HAL_Delay(200); // 等待芯片启动完成 W5500_SCS_HIGH(); // 片选默认拉高避免误触发 }复位时序是很多人不走心的地方。W5500在上电后如果RST引脚一直被低电平拉住芯片会进入掉电模式SPI读寄存器全返回0xFF。核心逻辑是拉低100ms以上拉高之后再等200ms不要一拉高就急着做寄存器访问。W5500支持的SPI时钟上限在80MHz左右但STM32F103的SPI1经过APB2分频后一般不超过18MHz所以波形质量不是主要问题反而是片选和时钟之间没有延时导致的首字节丢失更常见。如果遇到第一个字节读出来不对在每次片选拉低后加一个空NOP延时。2.3 中断引脚到底要不要接很多例程不接INT直接轮询W5500中断状态寄存器这在低速温湿度上报场景下能跑但一旦要响应云端下发的继电器控制轮询周期就会变成指令延迟比如500ms才读到一次socket缓冲人点击网页后灯要等半秒才动。所以建议把INT接到STM32的EXTI线上下降沿触发。W5500在中断发生时INT引脚拉低MCU需要在中断服务函数里读中断状态寄存器并清标志。如果硬环境不允许接INT至少把主循环缩短到10ms以内并且把“有数据”判断放在前面。3. W5500驱动移植与TCP socket连接在裸机上跑通以太网3.1 W5500的SPI帧格式与寄存器读写W5500对外的SPI访问不是简单的读地址而是先发一个16位地址和一个8位控制字节再发或收数据。控制字节中bit13-12是块选择bit11-4是地址偏移bit3-2是操作模式0表示读1表示写2/3用于MAC和PHY配置。最常用的几个块是通用寄存器块、socket寄存器块和TX/RX缓冲块。实际驱动只需要把下面两个底层函数写对其余寄存器访问都是套模板// w5500_driver.c — SPI 帧读写 uint8_t w5500_read_byte(uint32_t addr) { uint8_t tx_buf[3]; uint8_t rx_buf[3]; tx_buf[0] (addr 8) 0xFF; tx_buf[1] addr 0xFF; tx_buf[2] (addr 3) 0xFF 0x00; // 控制字节读操作 W5500_SCS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 3, HAL_MAX_DELAY); // 注意 W5500 读操作是发送完控制字节后插入一段访问时间再读第二个字节 tx_buf[0] 0x00; HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf 1, 1, HAL_MAX_DELAY); W5500_SCS_HIGH(); return rx_buf[2]; }读函数里的访问段需要小心W5500要求写控制字节和读数据之间有一个内部访问时间直接用HAL_SPI_TransmitReceive连续收发是可以的但如果你把片选在控制字节后立刻拉高数据就读不回来。写操作相对简单发送地址和控制字节后直接发数据即可。标准驱动库wizchip提供了这套逻辑如果自己写重点关注地址是否对齐W5500的寄存器是16位访问对齐4字节块对齐错位会导致数据错乱。3.2 用W5500建立TCP socket并连接阿里云MQTT服务器阿里云MQTT的接入地址是TLS和TCP两套端口1883对应明文TCP。W5500没有硬件TLS所以在工程里要么用阿里云提供的“设备认证明文MQTT”要么自己移植mbedtls走加密。项目实战一般先用明文跑通后面再补TLS。连接过程就是W5500打开socket置成TCP模式然后调用connect内核协议栈自动完成三次握手。关键代码如下// tcp_client.c — 建立 TCP 连接 #define MQTT_PORT 1883 uint8_t server_ip[4]; // 阿里云 MQTT 域名解析出的 IP int8_t w5500_tcp_connect(uint8_t sn, uint8_t *ip, uint16_t port) { uint8_t status; wiz_socket(sn, Sn_MR_TCP, 0, 0); // 创建 TCP socket超时寄存器用默认值 // wiz_connect 是非阻塞的需要轮询 Sn_SR if (wiz_connect(sn, ip, port) 0) { return -1; } for (uint32_t i 0; i 3000; i) { // 最多等 3 秒 status getSn_SR(sn); if (status SOCK_ESTABLISHED) { return 0; } if (status SOCK_CLOSED || status SOCK_SYNSENT) { // 如果进入 SOCK_SYNSENT 长时间不转换说明路由不通或IP不对 return -2; } HAL_Delay(1); } close(sn); return -1; }wiz_connect的参数是目标IP和端口。W5500内部状态机会在收到SYN-ACK后进入SOCK_ESTABLISHED但网络不通时会停在SOCK_SYNSENT。需要把Sn_RTIMER和Sn_RCR设置得保守一点比如重传定时器基值为200ms最大重试次数为3次否则在阿里云内网IP错误时socket会卡很久才返回失败。常见的坑是直接拿域名去连接。W5500没有内置DNS解析器你的代码里那个server_ip必须是一个四字节IP地址。常见做法是先在电脑上ping一下阿里云接入域名得到IP后硬编码进固件区域和实例变化后再手动更新。不能把域名转成字符串传进去W5500不认识。3.3 TCP keep-alive与断线重连的取舍W5500硬件TCP/IP协议栈有超时重传但TCP本身的keep-alive默认是关闭的。阿里云物联网平台会在空闲超过一定时间后断开连接所以设备侧必须定期发MQTT心跳。实际项目里我更倾向于让MQTT层的心跳同时驱动TCP层每45秒发一次PINGREQ如果连续3次没有收到PINGRESP就主动close当前socket再重新connect。这比单纯依赖TCP超时更可控也方便在日志里打印阿里云是否还在线。重连时注意W5500的socket号和序列号复用。直接重新wiz_connect之前务必先close等待状态回到SOCK_CLOSED。如果不close就重置芯片第二次连接时抓包会看到SYN重传但服务器不回包原因是序列号没有刷新。我一般会把重连流程写成一个状态机包含“初始化 → DNS/静态IP → TCP连接 → MQTT连接 → MQTT重订阅”任何一步失败都跳回TCP连接前面而不是从头复位W5500。4. STM32通过MQTT协议接入阿里云平台三元组、签名与Topic4.1 阿里云物联网平台创建产品设备拿到三元组登录阿里云物联网平台后在公共实例下创建产品。产品名随意节点类型选“设备”连网方式选“以太网”数据格式选“ICA标准数据格式”。创建完成后进入产品详情添加物模型属性里建立Temperature、Humidity和Relay其中Relay是布尔型Temperature和Humidity是浮点型或整数型。注意标识符要和后续代码里的JSON字段严格一致我用小驼峰命名避免下划线。然后添加设备设备名用物理设备标识比如stm32_w5500_dev01。创建后拿到三把钥匙ProductKey、DeviceName、DeviceSecret。这三个值需要硬编码在固件配置区建议用宏定义方便后续按项目修改// aliyun_config.h #define ALIYUN_PRODUCT_KEY a1XXXXXX #define ALIYUN_DEVICE_NAME stm32_w5500_dev01 #define ALIYUN_DEVICE_SECRET xxxxxxxxxxxxxxxx三元组不要直接写死在业务逻辑文件里维护起来会乱。另外产品所属地域会影响接入域名阿里云物联网平台在中国区常见的是cn-shanghai、ap-southeast-1等。域名格式是${productKey}.iot-as-mqtt.${region}.aliyuncs.com在W5500上不能直接用域名所以要么用静态IP要么实现一个最简单的DNS请求。4.2 MQTT连接参数计算用户名、密码、ClientId阿里云MQTT连接的认证参数不是注册时的三元组直用而是拼接签名。连接时需要四个字段参数格式示例ClientId{DeviceName}securemode3,signmethodhmacmd5,timestamp{timestamp}Username{DeviceName}{ProductKey}stm32_w5500_dev01a1XXXXXXPasswordHMAC-MD5的hex字符串abcdef1234567890abcdef1234567890签名内容是把clientId、deviceName、productKey、timestamp按顺序做字符串拼接再以DeviceSecret为密钥做HMAC-MD5。注意这里的clientId不包含前后竖线只是{DeviceName}|securemode3,signmethodhmacmd5,timestamp...|中间的那一段。timestamp建议在设备上电时生成使用UDP连到NTP服务器获取或者直接先用一个固定时间戳。阿里云允许一定的时间偏移但要求在线校验所以生产设备必须做校时。在STM32上做HMAC-MD5常见方案是移植mbedtls。代码并不复杂// mqtt_auth.c — 计算阿里云 MQTT 密码 #include mbedtls/md.h void aliyun_auth(char *client_id, char *device_name, char *product_key, char *timestamp, char *device_secret, char *password_out) { char content[256]; unsigned char hmac_result[16]; char hex_result[33]; int i; // 顺序不能变clientId、deviceName、productKey、timestamp snprintf(content, sizeof(content), clientId%sdeviceName%sproductKey%stimestamp%s, client_id, device_name, product_key, timestamp); mbedtls_md_context_t ctx; mbedtls_md_init(ctx); mbedtls_md_setup(ctx, mbedtls_md_info_from_type(MBEDTLS_MD_MD5), 1); mbedtls_md_hmac_starts(ctx, (unsigned char *)device_secret, strlen(device_secret)); mbedtls_md_hmac_update(ctx, (unsigned char *)content, strlen(content)); mbedtls_md_hmac_finish(ctx, hmac_result); mbedtls_md_free(ctx); for (i 0; i 16; i) { sprintf(hex_result i * 2, %02x, hmac_result[i]); } strcpy(password_out, hex_result); }这里有两个易错点。第一content里的clientId后面跟着的是上表里的中间段不包含竖线分隔符。第二signmethod在ClientId里写hmacmd5如果写hmacsha1签名算法必须同步换成HMAC-SHA1否则阿里云会返回4次错误然后断开。HMAC-MD5在STM32上内存占用比SHA1低用来做设备认证足够不需要担心安全性因为DeviceSecret本身就没走明文。4.3 温湿度上报与继电器控制Topic的订阅设备连接成功后需要干两件事上报温湿度监听云端指令。阿里云基于物模型的Topic是固定前缀核心是/sys/{productKey}/{deviceName}后面接类型。上报属性用thing/event/property/post接收服务下发用thing/service/property/set。上报温湿度时JSON里必须包含method、id、params、version。params中的温度、湿度字段名要和物模型标识符完全一致。我常用的上报结构// mqtt_publish.c — 属性上报 char payload[160]; snprintf(payload, sizeof(payload), {\method\:\thing.event.property.post\, \id\:\%ld\, \params\:{\Temperature\:%d.%d,\Humidity\:%d.%d}, \version\:\1.0\}, (long)millis(), temp_int, temp_dec, humi_int, humi_dec); mqtt_publish(SYS_TOPIC_PROPERTY_POST, payload);温度用整数和小数拆开是一种避免浮点转字符串的工程做法。温湿度传感器如果是DHT11读出来的值本身就是整数如果是SHT30有小数可以放大10倍发送在阿里云物模型里把温度值定义为float类型云端再解析成小数。发布时把id设成递增序号便于排查设备上报在云端的时序。订阅服务下发的Topic必须放到连接建立后因为每次重连后订阅关系会自动丢失。我建议在MQTT连接成功后立即订阅/sys/{productKey}/{deviceName}/thing/service/property/set同时订阅一个自定义Topic用于透传控制两个都订阅避免物模型解析失败时无法接收原始指令。4.4 解析云端下发的继电器控制指令阿里云端发送属性设置时下发报文是这样的{ method: thing.service.property.set, id: 123456, params: { Relay: 1 }, version: 1.0 }STM32收到后用cJSON解析先判断method再取params.Relay。继电器控制引脚通常是低电平吸合所以代码里要先把值反转一下。执行完继电器动作后必须回复一条set_reply报文否则阿里云会认为设备执行失败并显示离线。// relay_control.c — 处理云端下发 cJSON *root cJSON_Parse(rx_buffer); cJSON *method cJSON_GetObjectItem(root, method); if (strcmp(method-valuestring, thing.service.property.set) 0) { cJSON *params cJSON_GetObjectItem(root, params); int relay_state cJSON_GetObjectItem(params, Relay)-valueint; HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, relay_state ? GPIO_PIN_RESET : GPIO_PIN_SET); char reply[128]; snprintf(reply, sizeof(reply), {\method\:\thing.service.property.set_reply\, \id\:\%s\,\code\:200,\data\:{}}, cJSON_GetObjectItem(root, id)-valuestring); mqtt_publish(SYS_TOPIC_PROPERTY_SET_REPLY, reply); } cJSON_Delete(root);这里要留意id的赋值。阿里云下发的id是字符串直接拷贝回reply里不要自作主张换成自己的编号。code必须是200否则云端物模型状态不更新。本地控制继电器时也要把状态通过属性上报同步给云端否则网页开关状态会和实际继电器的状态不一致。我一般物理按钮操作完成后立即上报一次Relay属性值。5. WEB控制继电器与在线调试以及3个容易忽略的可靠性细节5.1 用阿里云IoT Studio搭一个WEB控制页面要在网页上控制继电器最快的方式是打开阿里云物联网平台的“IoT Studio”新建一个空白Web应用添加一个开关组件绑定到设备的Relay属性。组件触发时会调用阿里云后端下发thing.service.property.set指令设备侧收到后执行继电器动作并回复reply页面开关状态就会和真实状态同步。这个方案不需要自己写后端也不需要W5500跑HTTP开发成本最低。如果希望做成独立的WEB控制界面也可以用Vue或React写前端后端通过阿里云OpenAPI的Pub接口往设备自定义Topic发指令。设备侧订阅/user/cmd但需要注意发布QoS和幂等性否则快速点击开关时会出现云端发了10条指令设备只执行了7条或执行顺序乱掉的情况。5.2 继电器指令的QoS与重复上报去重在MQTT协议里控制继电器这类命令适合用QoS 1因为不能让协议栈丢指令。W5500的TCP重传能力很好但MQTT层的QoS 1需要设备具备处理重复消息的能力。阿里云在下发时如果没收到PUBACK会重发相同id的报文。如果设备每次收到都执行一次继电器翻转就会导致反复切换。正确做法是解析出id在上一个id还没处理完时直接丢弃重复包或者记录最近处理过的id相同则忽略。属性上报则可以继续用QoS 0因为温湿度历史数据即使丢一帧也无所谓。把上报频率限制在每5秒一次既能满足Web页面刷新也不会因为发布太频繁触发阿里的限流策略。5.3 W5500的SPI速度与中断收包之间的配合最后给正在调试的人一个经验W5500的SPI时钟频率不是越高越好。在STM32F103上将SPI分频调到4即18MHz有时会读到Sn_RX_RSR为0但实际有数据的情况这是因为W5500内部DMA搬运和SPI读竞态。我一般限制在9MHz以内并启用INT中断后在中断服务程序里先读Sn_IR确认中断源再清标志。清标志要写在读数据之前否则Socket会在读操作过程中再次触发中断导致数据被覆盖。另外一个细节是W5500的发送缓冲区。默认8KB但MQTT报文如果达到1.5KB需要确认Sn_TX_FSR剩余空间足够后再写否则写爆了会覆盖已排队的其他socket数据。设备同时跑ZigBee或RS485采集时socket分配可以把发送缓冲调大接收缓冲调小。最后确认一下复位引脚的上电时序如果你的电路板上STM32先上电、W5500后上电复位脉冲太低会导致芯片锁死在掉电状态把HAL_Delay(100)改成循环等待W5500的PHY寄存器可读比傻等更稳。本文还有配套的精品资源点击获取
返回列表