MQTTS连接adafruit平台示例
更新于 2026年9月14日
本篇总结
本例程在 W55MH32 上通过 MQTTS(MQTT over SSL/TLS)协议接入 Adafruit IO 物联网平台,实现温湿度数据的周期发布与 LED/BEEP 主题的订阅控制。
硬件平台:本例程基于 W55MH32L-EVB 开发板运行。
采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB
本示例演示如何在 W55MH32 上通过 MQTTS(MQTT over SSL/TLS)协议连接 Adafruit IO 物联网平台,实现设备与云端的双向数据交互。设备通过 TLS 加密通道,周期性将 AHT20 传感器采集的温湿度数据发布到 Adafruit IO,同时订阅同一主题接收云端下发的 LED 与 BEEP 控制指令。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口调试工具:点击下载
- Adafruit IO 账号:注册并登录 Adafruit IO,记录用户名(Username)和 AIO Key
MQTTS 协议简介
MQTTS(MQTT Secure / MQTT over SSL/TLS)是标准 MQTT 协议的安全增强版本,通过在 TCP 连接之上引入 SSL/TLS 加密层,为物联网通信提供安全保障。它广泛应用于金融支付、智能家居、医疗健康和工业控制等对数据隐私要求极高的领域。MQTTS 协议在保留 MQTT 轻量、高效特性的同时,解决了公网传输中的数据泄露风险,特别适用于需要通过非安全网络(如公共互联网)传输敏感数据的嵌入式系统。
MQTTS 的核心安全保障包括:
- 数据加密:利用 SSL/TLS 协议对传输的所有数据包进行加密,防止数据被窃听或抓包分析
- 身份认证:支持单向认证(客户端验证服务器证书)和双向认证(MTLS)
- 数据完整性:通过 TLS 的消息认证机制,确保数据在传输过程中未被篡改
- 无缝兼容:上层应用逻辑与普通 MQTT 完全一致,仅需配置安全证书和端口即可升级
W55MH32 的 TLS 支持
W55MH32 集成了硬件加密引擎(AES、SHA、TRNG),SDK 基于硬件加密引擎移植了 mbedTLS 库,提供完整的 TLS 握手与加解密能力。通过 wiz_tls_init() 和 wiz_tls_connect() 即可建立安全连接。MQTTS 使用端口 8883(普通 MQTT 使用 1883)。
硬件连接与拓扑
请注意:
本示例需要访问互联网,请确保 W55MH32 的网络环境及配置能够正常访问互联网(路由器需连接外网)。
- Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
- 网线连接开发板 RJ45 网口 到路由器 LAN 口,路由器需连接互联网
│ W55MH32 │──网线──│ 路由器/网关 │──互联网──│ Adafruit IO │
│ (MQTTS │ │ (DHCP/DNS) │ │ io.adafruit.com │
│ Client) │ │ 192.168.1.1 │ │ :8883 (TLS) │
└───────────┘ └─────────────────┘ └──────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\MQTTS_Adafruit\,核心文件如下:
MQTTS_Adafruit/
├── Project/
│ └── Project.uvprojx ← Keil 工程文件
└── USER/
├── main.c ← 硬件初始化 + MQTT 主循环
├── do_mqtt/
│ ├── do_mqtt.c ← MQTT 主流程(init / do_mqtt / json_decode)
│ └── do_mqtt.h ← MQTT 连接参数、消息结构
├── do_dns/
│ ├── do_dns.c ← DNS 域名解析
│ └── do_dns.h
├── wiz_tls/
│ ├── wiz_tls.c ← TLS 握手与加解密封装
│ └── wiz_tls.h
├── aht20/
│ ├── aht20.c ← AHT20 温湿度传感器驱动
│ └── aht20.h
├── user_gpio/
│ ├── user_gpio.c ← LED / BEEP GPIO 初始化
│ └── user_gpio.h
├── tim/
│ ├── bsp_tim.c ← TIM3 1ms 定时器(MQTT/DHCP/DNS 时基)
│ └── bsp_tim.h
└── uart/
├── bsp_uart.c ← 调试串口 + printf 重定向
└── bsp_uart.h
本例程相比普通 MQTT 章节的核心新增:TLS 握手流程、MQTTS 收发回调替换、AHT20 传感器驱动。
步骤1:注册 MQTT 定时中断函数
文件:USER/tim/bsp_tim.c — 将 MilliTimer_Handler() 注册到 1ms 定时器中断中,为 MQTT 协议栈提供时基。同时每 1000ms 执行一次 DHCP 和 DNS 的时间处理。
void TIM3_IRQHandler(void)
{
static uint32_t tim3_1ms_count = 0;
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
tim3_1ms_count++;
MilliTimer_Handler();
if (tim3_1ms_count >= 1000)
{
DHCP_time_handler();
DNS_time_handler();
tim3_1ms_count = 0;
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}步骤2:配置 Adafruit IO 连接参数
文件:USER/do_mqtt/do_mqtt.c — 定义 MQTT 连接参数,包括服务器域名、端口、客户端 ID、用户名、密码(AIO Key)、发布/订阅主题。本例程使用 Adafruit IO 的 Groups(分组)功能,发布与订阅共用同一个主题。
mqttconn mqtt_params = {
.mqttHostUrl = "io.adafruit.com",
.server_ip = {0},
#ifdef MQTTS
.port = 8883, /* MQTTS 使用 8883 端口 */
#else
.port = 1883, /* 普通 MQTT 使用 1883 端口 */
#endif
.clientid = "Mr_huang1220_adafruit_io_connection",
.username = "Mr_huang1220",
.passwd = "aio_BiYJ23jjamijB8bx6lS2x8o5uVWi",
.pubtopic = "Mr_huang1220/groups/w55mh32-evb",
.subtopic = "Mr_huang1220/groups/w55mh32-evb",
.pubQoS = QOS0,
};如果你想自定义
- 端口说明 → MQTTS 使用 8883(SSL/TLS 加密),普通 MQTT 使用 1883。通过宏定义
MQTTS切换两种模式 - Topic 命名规则 → Adafruit IO 的 Groups 格式为
用户名/groups/组名。本例程发布与订阅使用同一个主题,通过 JSON 中的 feeds 字段区分数据来源 - AIO Key 获取 → 登录 Adafruit IO 后,点击右上角头像进入 "View AIO Key" 页面
步骤3:MQTTS 初始化与 TLS 握手
文件:USER/do_mqtt/do_mqtt.c — 函数 mqtt_init() 完成 DNS 解析、TLS 初始化、TCP 连接、TLS 握手和 MQTT 客户端初始化。在 MQTTS 模式下,收发函数被替换为 TLS 版本。
void mqtt_init(uint8_t sn, uint8_t *send_buf, uint8_t *recv_buf)
{
wiz_NetInfo get_info = {0};
wizchip_getnetinfo(&get_info);
/* DNS 解析 Adafruit IO 域名 */
if (do_dns(send_buf, (uint8_t *)mqtt_params.mqttHostUrl, mqtt_params.server_ip))
{
while (1);
}
#ifdef MQTTS
/* TLS 初始化 */
wiz_tls_socket(&tlsContext, sn, mqtt_params.local_port);
wiz_tls_init(&tlsContext, 2000, (char *)mqtt_params.server_ip, NULL, NULL, NULL);
NewNetwork(&n, sn);
n.mqttread = tls_mqtt_read; /* 收发函数替换为 TLS 版本 */
n.mqttwrite = tls_mqtt_write;
n.disconnect = tls_mqtt_disconnect;
/* TCP 连接 + TLS 握手 */
connect(tlsContext.socket_fd, (uint8_t *)mqtt_params.server_ip, mqtt_params.port);
wiz_tls_connect(&tlsContext, (char *)mqtt_params.server_ip, mqtt_params.port);
mbedtls_ssl_conf_read_timeout(tlsContext.conf, 2000);
MQTTClientInit(&c, &n, 2000, send_buf, MQTT_ETHERNET_MAX_SIZE, recv_buf, MQTT_ETHERNET_MAX_SIZE);
#else
/* 普通 MQTT 模式 */
NewNetwork(&n, sn);
ConnectNetwork(&n, mqtt_params.server_ip, mqtt_params.port);
MQTTClientInit(&c, &n, 1000, send_buf, MQTT_ETHERNET_MAX_SIZE, recv_buf, MQTT_ETHERNET_MAX_SIZE);
#endif
/* MQTT 连接参数 */
data.MQTTVersion = 4;
data.clientID.cstring = mqtt_params.clientid;
data.username.cstring = mqtt_params.username;
data.password.cstring = mqtt_params.passwd;
data.keepAliveInterval = 30;
data.cleansession = 1;
}
执行逻辑:MQTTS 模式下,n.mqttread 和 n.mqttwrite 被替换为 TLS 版本(tls_mqtt_read/write),所有 MQTT 数据都经过 TLS 加密后传输。
步骤4:do_mqtt() 状态机处理
文件:USER/do_mqtt/do_mqtt.c — 函数 do_mqtt() 采用状态机方式运行:连接服务器(CONN)→ 订阅主题(SUB)→ 发布消息(PUB_MESSAGE)→ 保活(KEEPALIVE)→ 接收处理(RECV)。
void do_mqtt(void)
{
uint8_t ret;
switch (run_status)
{
case CONN: {
ret = MQTTConnect(&c, &data);
printf("Connect to the MQTT server: %d.%d.%d.%d:%d\r\n",
mqtt_params.server_ip[0], mqtt_params.server_ip[1],
mqtt_params.server_ip[2], mqtt_params.server_ip[3],
mqtt_params.port);
printf("Connected:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS) run_status = ERR;
else run_status = SUB;
break;
}
case SUB: {
ret = MQTTSubscribe(&c, mqtt_params.subtopic, mqtt_params.subQoS, messageArrived);
printf("Subscribing to %s\r\n", mqtt_params.subtopic);
printf("Subscribed:%s\r\n\r\n", ret == SUCCESSS ? "success" : "failed");
if (ret != SUCCESSS) run_status = ERR;
else run_status = PUB_MESSAGE;
break;
}
case PUB_MESSAGE: {
if (mqtt_send_flag)
{
mqtt_send_flag = 0;
pubmessage.qos = QOS0;
/* 拼装 JSON 数据:LED / BEEP 状态 + AHT20 温湿度 */
sprintf(pubmessage.payload,
"{\"feeds\":{\"led\":%d,\"beep\":%d,\"humidity\":%.2f,\"temperature\":%.2f}}",
GPIO_ReadOutputDataBit(LED_PORT, LED_PIN),
GPIO_ReadOutputDataBit(BEEP_PORT, BEEP_PIN),
SENx.RH, SENx.T);
pubmessage.payloadlen = strlen(pubmessage.payload);
ret = MQTTPublish(&c, (char *)&(mqtt_params.pubtopic), &pubmessage);
if (ret != SUCCESSS) run_status = ERR;
else printf("publish:%s,%s\r\n\r\n", mqtt_params.pubtopic, (char *)pubmessage.payload);
}
}
/* no break — 继续执行 KEEPALIVE */
case KEEPALIVE: {
if (MQTTYield(&c, 30) != SUCCESSS) run_status = ERR;
}
/* no break — 继续执行 RECV */
case RECV: {
if (mqtt_recv_flag)
{
mqtt_recv_flag = 0;
json_decode(mqtt_recv_msg);
}
break;
}
case ERR:
printf("system ERROR!");
delay_ms(1000);
break;
default:
break;
}
}发布数据格式
设备通过 feeds 字段同时上报 LED 状态、BEEP 状态、湿度和温度。Adafruit IO 收到后会自动分发到对应 Feed 记录,例如 {"feeds":{"led":1,"beep":0,"humidity":37.12,"temperature":32.53}}。
步骤5:接收消息处理机制
文件:USER/do_mqtt/do_mqtt.c — 当设备收到 Adafruit IO 平台下发的订阅消息后,先进入 messageArrived() 回调函数保存消息,随后主状态机在 RECV 分支中调用 json_decode() 用 cJSON 解析 JSON 数据并完成 LED / BEEP 控制。
void messageArrived(MessageData *md)
{
static char topicname[64] = {0};
static char msg[512] = {0};
sprintf(topicname, "%.*s", (int)md->topicName->lenstring.len, md->topicName->lenstring.data);
sprintf(msg, "%.*s", (int)md->message->payloadlen, (char *)md->message->payload);
printf("recv:%s,%s\r\n\r\n", topicname, msg);
mqtt_recv_flag = 1;
memset(mqtt_recv_msg, 0, sizeof(mqtt_recv_msg));
memcpy(mqtt_recv_msg, msg, strlen(msg));
}
json_decode() 用 cJSON 解析 Adafruit IO 下发的 JSON 指令,从 feeds 字段中提取 led 和 beep 的值,分别控制 LED 和蜂鸣器。
void json_decode(char *msg)
{
cJSON *jsondata = NULL;
cJSON *feeds = NULL;
cJSON *led = NULL;
cJSON *beep = NULL;
jsondata = cJSON_Parse(msg);
if (jsondata == NULL) { printf("json parse fail.\r\n"); return; }
feeds = cJSON_GetObjectItem(jsondata, "feeds");
if (feeds == NULL) { printf("params parse fail.\r\n"); return; }
/* LED 控制:0 点亮,非 0 熄灭(板载 LED 低电平点亮) */
led = cJSON_GetObjectItem(feeds, "led");
if (led != NULL)
{
if (atoi(led->valuestring) == 0)
{
GPIO_ResetBits(LED_PORT, LED_PIN);
printf("LED ON\r\n");
}
else
{
GPIO_SetBits(LED_PORT, LED_PIN);
printf("LED OFF\r\n");
}
}
/* BEEP 控制:1 鸣叫,0 停止 */
beep = cJSON_GetObjectItem(feeds, "beep");
if (beep != NULL)
{
if (atoi(beep->valuestring) == 1)
{
GPIO_SetBits(BEEP_PORT, BEEP_PIN);
printf("BEEP ON\r\n");
}
else
{
GPIO_ResetBits(BEEP_PORT, BEEP_PIN);
printf("BEEP OFF\r\n");
}
}
cJSON_Delete(jsondata);
}JSON 解析说明
json_decode() 使用 cJSON 库解析 Adafruit IO 下发的 JSON,从 feeds 中提取 led / beep 字段。板载 LED 为低电平点亮,所以 led = 0 点亮、led = 1 熄灭。BEEP 相反,beep = 1 鸣叫、beep = 0 停止。
运行验证
打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1)。烧录例程运行后,程序首先进行 PHY 链路检测和硬件加密引擎初始化,然后通过 DHCP 获取网络参数,完成 DNS 解析与 TLS 握手,最后连接 Adafruit IO 平台的 MQTT 服务器。预期串口输出如下:
W55MH32 MQTTS Example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
WIZnet CARD Secure Test Demo V1.0, secure lib version is V03.01.04.00
WIZnet CARD Crypt Test V1.0 start......
[DHCP] running
[DHCP] success!
====================================================================================================
W55MH32 network configuration : DHCP
MAC : 00:08:DC:00:0E:9A
IP : 192.168.1.6
Subnet Mask : 255.255.255.0
Gateway : 192.168.1.1
DNS : 192.168.1.1
====================================================================================================
[DNS]> Translated [io.adafruit.com] to [52.7.84.197]
[SSL] Performing the SSL/TLS handshake...
[SSL] SSL/TLS handshake ok
[ Ciphersuite is TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 ]
Connect to the MQTT server: 52.7.84.197:8883
Connected:success
Subscribing to Mr_huang1220/groups/w55mh32-evb
Subscribed:success
publish:Mr_huang1220/groups/w55mh32-evb,{"feeds":{"led":1,"beep":0,"humidity":37.12,"temperature":32.53}}
连接成功后,设备会订阅 Mr_huang1220/groups/w55mh32-evb 主题,并周期性向同一主题发布 AHT20 采集的温湿度及 LED / BEEP 状态。在 Adafruit IO 平台的 Feed 页面中写入控制值,设备即可接收并执行 LED / BEEP 控制。



常见问题
Q: MQTTS 和 MQTT 有什么区别
MQTTS 是 MQTT over SSL/TLS,在 TCP 连接之上增加了一层 SSL/TLS 加密。它使用端口 8883(MQTT 默认 1883),对传输数据进行加密和身份认证。上层发布/订阅逻辑与普通 MQTT 完全一致,仅需配置证书和端口即可升级为安全连接。
Q: 如何获取 Adafruit IO 的 AIO Key
登录 Adafruit IO 平台后,点击右上角头像进入 "My Account" 或 "View AIO Key" 页面,即可看到当前账号的用户名(Username)和 AIO Key。这两个参数是设备接入平台进行 MQTT 认证的凭证,需妥善保管。
Q: 为什么收到 0 是点亮 LED
本工程中板载 LED 为低电平点亮。收到 led = 0 时调用 GPIO_ResetBits() 拉低引脚点亮 LED,收到 led = 1 时调用 GPIO_SetBits() 拉高引脚熄灭 LED。BEEP 正好相反,beep = 1 鸣叫。
Q: 发布的 Topic 命名规则是什么
Adafruit IO 支持两种 Topic 格式:单 Feed 为 用户名/feeds/Feed名称;Groups(分组)为 用户名/groups/组名。本例程使用 Groups 格式,发布与订阅共用同一主题,通过 JSON 中的 feeds 字段区分不同数据。Topic 中的用户名必须与账号一致。
Q: TLS 握手失败如何排查
首先确认 W55MH32 网络环境能够正常访问互联网。其次检查 DNS 是否成功解析 io.adafruit.com,以及 TCP 连接到 8883 端口是否建立成功。TLS 握手依赖硬件加密引擎和 TRNG,确认 main 中相关初始化已执行。若仍失败,可查看 mbedtls 返回的错误码定位具体原因。
总结
本文讲解了如何在 W55MH32 芯片上实现 MQTTS 协议,并连接 Adafruit IO 平台,通过实战例程展示了从网络配置、TLS 安全连接建立、MQTT 消息订阅、发布及接收处理的完整过程:
- MQTTS 协议:在标准 MQTT 之上引入 SSL/TLS 加密层,保障数据机密性、完整性和身份认证
- 接入流程:DHCP 获取网络参数 → DNS 解析 → TCP 连接 → TLS 握手 → MQTT 连接 → 订阅/发布
- 关键 API:
wiz_tls_init / wiz_tls_connect建立 TLS,MQTTConnect / MQTTSubscribe / MQTTPublish收发消息 - 消息收发:
messageArrived()回调接收,json_decode()用 cJSON 解析 Adafruit IO 下发的 JSON 指令控制 LED / BEEP - 数据格式:通过 feeds 字段同时上报 LED / BEEP 状态和 AHT20 采集的温湿度数据