全部例程

MQTTS连接adafruit平台示例

W55MH32

更新于 2026年9月14日

本篇总结

本例程在 W55MH32 上通过 MQTTS(MQTT over SSL/TLS)协议接入 Adafruit IO 物联网平台,实现温湿度数据的周期发布与 LED/BEEP 主题的订阅控制。

🔑 KEIL MDK、UART调试、硬件TOE引擎、MQTTS、TLS、AHT20、DHCP

硬件平台:本例程基于 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 的网络环境及配置能够正常访问互联网(路由器需连接外网)。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口,路由器需连接互联网
┌───────────┐ ┌─────────────────┐ ┌──────────────────┐
│ W55MH32 │──网线──│ 路由器/网关 │──互联网──│ Adafruit IO │
│ (MQTTS │ │ (DHCP/DNS) │ │ io.adafruit.com │
│ Client) │ │ 192.168.1.1 │ │ :8883 (TLS) │
└───────────┘ └─────────────────┘ └──────────────────┘
↑
DEBUG USB ──→ WIZ UartTool
网络拓扑:W55MH32 通过路由器接入互联网,经 TLS 加密连接 Adafruit IO 平台(端口 8883)

工程结构

工程位于 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 控制。

WIZ UartTool 串口输出 — MQTTS 连接全流程(链路检测 → DHCP → DNS → TLS 握手 → MQTT 连接 → 订阅 → 发布)
WIZ UartTool 串口输出 — 接收 Adafruit IO 平台 LED 控制消息
Adafruit IO 平台 — Feed 数据记录表(时间戳 + LED 开关值 0/1)


常见问题

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 采集的温湿度数据

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Administrator

Wiznet 雇员