拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32+EC200S 4G DTU接入阿里云MQTT实战指南

简介这是一份以STM32F103单片机为核心、配合EC200S 4G模组通过MQTT协议向阿里云物联网平台上传温度数据并能接收云端JSON下发指令控制LED/继电器的嵌入式物联网项目例程。资源面向单片机开发者、物联网初学者及需要快速搭建4G数传方案的工程师基于KEIL标准库开发代码结构清晰注释详尽且针对不同STM32F103型号提供了简单的芯片型号与FLASH容量调整说明便于二次开发。 压缩包共112个文件大小约1.83MB。文件类型覆盖C源码42个、H头文件48个、Keil工程文件uvprojx/uvoptx、BMP调试与下发状态图、hex烧录文件、PDF说明以及清理编译垃圾的脚本等既包含完整可编译工程也提供运行显示效果与接线定义说明方便对照学习。目前已有159人学习下载适合用作毕业设计、竞赛或实际项目的前期验证参考。程序接线在代码中已定义传感器或硬件有差异时可根据自身情况调整是一套实用、入门的4G物联网数据交互方案。1. 4G DTU 方案的组成STM32F103 与 EC200S 如何分工STM32F103 负责采集和业务逻辑EC200S 只当一个“带 MQTT 协议的 4G 猫”。很多初学物联网的人把 EC200S 想象成 WiFi 模块拉串口就 TX/RX 乱接结果 AT 指令回显都没有。这套工程的核心思路是把 EC200S 的 MQTT 能力用 AT 指令暴露出来STM32F103 只需维护一个串口状态机按顺序发送ATQMTOPEN、ATQMTCONN、ATQMTPUB数据就上了阿里云。反过来的下行链路则是订阅/sys/{pk}/{dn}/thing/service/property/set在QMTRECV异步通知里用 cJSON 解析 JSON 负载再翻转 LED。这套方案适合手上有 STM32F103 最小系统、想快速复现 4G 数据上报场景的嵌入式开发者。建议先用 PC 串口助手把 AT 指令跑通再刷 MCU 固件能省掉一半调试时间。2. EC200S 入网与 AT 指令初始化流程2.1 电源、SIM 与引脚映射EC200S 的工作电压范围通常是 3.4V~4.3V典型值 3.8V而 STM32F103 的 VDD 是 3.3V。如果直接用 AMS1117-3.3 给 4G 模块供电电压一拉低模块就会反复重启串口打印一会儿有、一会儿没有。更稳的做法是模块电源单独从 5V 经过 DCDC 降到 3.8V输出电流至少 2A并在模块 VBAT 脚旁边放一个 470uF 电解电容和几个 100nF 陶瓷电容。STM32 与 EC200S 之间如果用的是裸模块需要注意模块 UART 电平很多第三方的 EC200S 核心板已经做了 3.3V 兼容可以直接连接。用开发板/核心板时常见接线如下信号STM32F103 引脚EC200S 引脚说明模块 TXDPA10USART1_RXUART_TX模块发给 MCU模块 RXDPA9USART1_TXUART_RXMCU 发给模块GNDGNDGND必须共地PWRKEYPC2PWRKEY拉低 600ms 开机RESETPC3RESET低电平复位SIM 卡无SIM_VDD / SIM_DATA / SIM_CLK / SIM_RST接 SIM 卡座PWRKEY 是开机使能脚不能一直拉高。常见的开机方式是初始化 GPIO 后拉低 600ms再释放之后模块会通过 UART 周期性输出开机打印RDY或CFUN: 1。有些核心板把 PWRKEY 自动上拉外部只需一个三极管或 MOS 管控制代码里也要预留相应延时。2.2 串口初始化与 AT 握手工程里 USART1 固定给 EC200S 使用波特率 1152008N1不使用硬件流控。代码使用 KEIL 标准库初始化流程如下void EC200S_UART_Init(void) { GPIO_InitTypeDef gpio; USART_InitTypeDef usart; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); gpio.GPIO_Pin GPIO_Pin_9; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); gpio.GPIO_Pin GPIO_Pin_10; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, gpio); usart.USART_BaudRate 115200; usart.USART_WordLength USART_WordLength_8b; usart.USART_StopBits USART_StopBits_1; usart.USART_Parity USART_Parity_No; usart.USART_HardwareFlowControl USART_HardwareFlowControl_None; usart.USART_Mode USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, usart); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); }这段代码把 PA9 配成推挽复用输出PA10 配成浮空输入。USART_InitStructure中的参数顺序没有严格要求但波特率、数据位、停止位必须与 EC200S 默认配置一致。如果后续要用模块的硬件流控需要把 RTS/CTS 也接上并改成USART_HardwareFlowControl_RTS_CTS不建议在 DTU 场景开局就开流控调试阶段容易丢帧。发送 AT 指令比较简单关键是每条指令必须以\r\n结尾void AT_SendString(const char *s) { while (*s) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)*s); } }上电后先等模块稳定 3 秒再发AT\r\n如果返回OK就说明串口链路已经通。若完全没有回显优先怀疑 TX/RX 接反其次检查模块有没有开机。第一次联调时可以在 PC 串口助手上直接发ATE0\r\n关闭回显减少后续协议解析的干扰。2.3 检查 SIM 卡与网络注册DTU 方案里SIM 卡未注册是 MQTT 连不上阿里的最常见原因。寄存器指令应该按固定顺序检查指令期望返回含义ATCPIN?CPIN: READYSIM 卡已识别ATCREG?CREG: 0,1或0,5GSM 网络已注册ATCGREG?CGREG: 0,1或0,5LTE 网络已注册ATCSQCSQ: 20,99信号强度在 10 以上较稳如果CPIN返回ERROR检查 SIM 卡座方向、SIM_VDD 对地电容是否过大以及卡座金属弹片是否接触良好。CSQ的第一个数值低于 10 时4G 模块虽然能注册但 MQTT 长连接稳定性会明显下降。实际项目里我会把ATCREG?和ATCGREG?轮询结果作为业务启动条件之一只有叠加到1或5才继续执行 MQTT 连接流程避免模块还在找网就发ATQMTOPEN导致超时。网络注册完成后可以用ATQLTS\r\n获取运营商网络时间。这个时间在后面生成阿里云 MQTT 连接签名时会用到工程里如果没加 RTC 电池这是最省成本的校时方式。3. 阿里云物联网平台 MQTT 参数产品创建、Topic 与签名生成3.1 产品与物模型创建步骤进入阿里云物联网平台控制台后先创建产品产品名称可以叫“STM32-EC200S-DTU”节点类型选“设备”连网方式选“蜂窝”或“WiFi”都可以。关键是物模型功能必须提前定义好因为后面发布的 JSON 字段名要和物模型一致。本案例定义两个功能属性属性标识符数据类型读写类型说明Temperaturefloat只读温度上报LEDSwitchintbool读写云端下发 LED 状态定义完成后在产品下添加调试设备保存三元组ProductKey、DeviceName、DeviceSecret。这三个值最终要写进 STM32 代码的宏定义里。注意DeviceName在同一个产品下必须唯一建议直接用设备编号比如dtu_01。物模型定义好后平台会自动创建对应的 Topic。上行属性上报和下行属性设置分别对应两条 Topic发布上行 /sys/{ProductKey}/{DeviceName}/thing/event/property/post 订阅下行 /sys/{ProductKey}/{DeviceName}/thing/service/property/set上行报文使用 JSON 格式平台要求method字段固定为thing.event.property.post下行报文的params里包含的属性名必须与物模型一致否则后台会提示“属性不存在”。3.2 EC200S 连接阿里云需要的 MQTT 参数EC200S 内置 MQTT 协议栈使用明文 1883 端口接入阿里云时需要把 TCP 连接参数与物联网设备身份参数组合成标准 MQTT CONNECT 报文。涉及三个参数clientId、username、password。开放协议接入的参数规则是clientId{deviceName}|securemode3,signmethodhmacsha1,timestamp{timestamp}|username{deviceName}{productKey}passwordBase64(HMAC-SHA1(deviceSecret, content))password的签名原文为clientId{clientId}deviceName{deviceName}productKey{productKey}timestamp{timestamp}直接在 STM32F103 里计算 HMAC-SHA1 需要移植 mbedTLS会比较占 Flash。调试阶段可以先在 PC 上用 Python 生成一份固定参数烧进代码里验证链路后续再按量产要求搬进 MCU# -*- coding: utf-8 -*- import hmac import hashlib import base64 import time productKey a1XXXXXXXX deviceName dtu_01 deviceSecret xxxxxxxxxxxxxxxx ts str(int(time.time() * 1000)) clientId {}|securemode3,signmethodhmacsha1,timestamp{}|.format(deviceName, ts) content clientId{}deviceName{}productKey{}timestamp{}.format( clientId, deviceName, productKey, ts) password base64.b64encode( hmac.new(deviceSecret.encode(utf-8), content.encode(utf-8), hashlib.sha1).digest() ).decode(utf-8) print(clientId :, clientId) print(username :, {}{}.format(deviceName, productKey)) print(password :, password)这段脚本用标准库hmac、hashlib、base64不需要额外安装包。打印结果中的clientId已经包含|分隔符写入代码时不能去掉。password末尾可能有补位这是 Base64 编码的正常现象不要当成异常字符串过滤掉。生成后先把三个参数填到工程宏里跑通一次再考虑动态计算。如果设备长时间不连接阿里云会拒绝过期时间戳。固定参数方式只适合开发验证正式产品建议用网络时间实时计算签名或者改用一机一密的设备密钥下发方案。3.3 在串口助手里验证 MQTT 连接PC 串口助手连接 EC200S依次发送ATQMTOPEN0,a1XXXXXXXX.iot-as-mqtt.cn-shanghai.aliyuncs.com,1883 ATQMTCONN0,dtu_01|securemode3,signmethodhmacsha1,timestamp...|,dtu_01a1XXXXXXXX,生成的passwordQMTOPEN的第二个参数是阿里云 MQTT 接入域名华东2 站点是{productKey}.iot-as-mqtt.cn-shanghai.aliyuncs.com端口 1883。返回OK表示 TCP 连接已建立。如果这一步超时检查域名是否写错再把卡拔出放到手机里确认 4G 数据业务正常。QMTCONN的三个字符串参数顺序不能换第一个是clientId第二个是username第三个是password。返回OK后模块会进入 MQTT 会话保持状态此时可以继续发ATQMTSUB订阅下行 Topic。4. STM32F103 端代码温度采集、MQTT 发布与 JSON 下发解析4.1 ADC 采集温度数据温度采集电路使用 10k NTC 与 10k 电阻分压NTC 一端接地采样点接 STM32F103 的 PC0对应 ADC1 通道 10。上电后先初始化 ADC1再在主循环里周期读取float Read_NTC_Temperature(void) { float v, r, kelvin; ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); uint16_t adc ADC_GetConversionValue(ADC1); v (float)adc * 3.3f / 4095.0f; r 10.0f * v / (3.3f - v); kelvin 1.0f / (1.0f / 298.15f logf(r / 10.0f) / 3950.0f); return kelvin - 273.15f; }计算思路分三步先把 ADC 原始值换算成采样点电压v再根据分压公式反推 NTC 当前阻值r最后用 B 值公式解出开尔文温度减去 273.15 得到摄氏度。3.3f需要和 ADC 参考电压一致如果板子用外部基准要改成对应的参考电压。NTC 的 B3950 只是常用值实际应该以元件手册为准否则室温下会差两三度。初始化 ADC1 时建议设置采样时间为 55.5 周期以上因为 NTC 分压电路输出阻抗较高采样时间太短会导致结果偏小ADC_InitTypeDef adc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); adc.ADC_Mode ADC_Mode_Independent; adc.ADC_ScanConvMode DISABLE; adc.ADC_ContinuousConvMode DISABLE; adc.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; adc.ADC_DataAlign ADC_DataAlign_Right; adc.ADC_NbrOfChannel 1; ADC_Init(ADC1, adc); ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE);这里用ADC_ExternalTrigConv_None表示软件触发每次读取时先调用ADC_SoftwareStartConvCmd。如果你用 DMA 连续采集需要把ADC_ContinuousConvMode设为ENABLE并在 DMA 中断里取值。4.2 通过 AT 指令发布属性到主题连接建立后温度上报只需要构造 JSON 负载并调用ATQMTPUB。负载中的params字段名必须和物模型属性标识符一致void MQTT_PublishTemp(uint8_t clientIdx, float temp) { char payload[128]; char at[192]; snprintf(payload, sizeof(payload), {\id\:\1001\,\version\:\1.0\,\method\:\thing.event.property.post\,\params\:{\Temperature\:%.1f}}, temp); snprintf(at, sizeof(at), ATQMTPUB%d,0,0,0,\/sys/%s/%s/thing/event/property/post\,%d\r\n, clientIdx, PRODUCT_KEY, DEVICE_NAME, strlen(payload)); AT_SendString(at); if (WaitPrompt(, 2000) 0) { AT_SendString(payload); AT_SendChar(0x1A); } }QMTPUB的参数含义从左到右是客户端索引、消息 ID、QoS、retain 标志、Topic、负载长度。这里 QoS 设为 0适合温度这类周期性采集数据如果下发指令需要确认建议 QoS 1。模块收到提示符后依次发送 JSON 字符串和控制字符0x1A表示数据发送结束。sprintf和snprintf在 KEIL 中默认支持浮点数格式化但需要留意微库设置。如果编译后浮点数打印成空字符串打开 Options for Target 中的 Use MicroLIB并在 C/C 页勾选--no_strict_aliasing或使用整数方式组包。4.3 订阅下行 Topic 并解析 JSON 控制 LEDEC200S 收到下行数据时会通过串口主动上报QMTRECV: 0,0,/sys/a1XXXX/dtu_01/thing/service/property/set,72 {method:thing.service.property.set,id:456,params:{LEDSwitch:1},version:1.0}因此工程初始化时需要先订阅AT_SendString(ATQMTSUB0,1,\/sys/a1XXXXXXXX/dtu_01/thing/service/property/set\,1\r\n);QMTSUB的最后一个参数是 QoS这里用 1 能减少重复下发问题。订阅返回OK后MCU 只需在串口中断里接收QMTRECV负载然后把提取出的 JSON 传给解析函数。解析用 cJSON 库代码逻辑如下void Parse_LEDCommand(const char *json) { cJSON *root cJSON_Parse(json); if (root NULL) { return; } cJSON *params cJSON_GetObjectItem(root, params); cJSON *led NULL; if (params ! NULL) { led cJSON_GetObjectItem(params, LEDSwitch); } if (cJSON_IsNumber(led) || cJSON_IsBool(led)) { if (led-valueint ! 0) { GPIO_SetBits(GPIOC, GPIO_Pin_13); } else { GPIO_ResetBits(GPIOC, GPIO_Pin_13); } } cJSON_Delete(root); }cJSON_Parse会把整个 JSON 字符串解析成树形结构解析失败时返回 NULL。cJSON_GetObjectItem按 key 取节点取params后再取LEDSwitch避免顶层字段直接取不到。valueint是 cJSON 把数字类型统一转换后的整数结果用它判断0/1即可控制 LED。注意每次解析后必须调用cJSON_Delete(root)释放内存否则长时间运行会导致堆溢出。PlatformIO 或 KEIL 中使用 cJSON只需把cJSON.c和cJSON.h加入工程并保证编译器支持 C99。解析之前最好先用strstr(rxBuf, {)定位大括号位置因为QMTRECV前缀里包含 Topic 字符串直接传给cJSON_Parse会失败。5. AT 响应状态机与 Keil 下载选项的排错清单5.1 用状态机处理异步通知EC200S 的串口不是一问一答模式模块任何时刻都可能主动上报网络状态、QMTRECV或者CMTI。如果主流程用阻塞方式等待OK收到一条下行通知就会打乱整个指令序列。我习惯把 AT 流程拆成一个简单状态机typedef enum { AT_IDLE, AT_WAIT_CONN, AT_WAIT_PUB_PROMPT, AT_WAIT_RECV } AtState; volatile AtState g_atState AT_IDLE; volatile uint8_t g_rxFlag 0;串口中断里只负责收字节、查关键字。主循环里根据当前状态决定下一步动作例如在等待QMTRECV时只要在缓冲里找到QMTRECV立刻提取 JSON 并调用Parse_LEDCommand然后回到AT_IDLE。这样温度上报和下行控制可以并发执行不会因为一条命令超时导致后续指令全部卡死。5.2 Keil 下载与硬件排错下载时最容易踩的坑是 J-Link 和 ST-Link 选错。KEIL 默认 Debug 选项里可能有上次工程留下的设置烧录前先打开 Options for Target - Debug右侧下拉框确认是J-LINK还是ST-Link Debugger。用的是 ST-Link 就选ST-Link Debugger然后点 Settings 看 IDCODE 是否识别识别不到时按下面表格检查。现象原因处理No Cortex-M SW Device FoundSWDIO/SWCLK 接反或 BOOT0 不在 0调换线序确认 BOOT0 接 GNDJ-Link 连接后立刻断开目标板供电不足给 STM32 独立 3.3V 供电串口打印乱码波特率不对或模块主频分频改变固定 115200检查 RCC 时钟树下载后程序不运行BOOT0 被拉高将 BOOT0 拨到 0复位重启模块发不出 ATEC200S 没开机或者 PWRKEY 拉低时间不够拉低 PWRKEY 600ms等待RDY下载工程里附带了一个“清除 KEIL 编译残余”的批处理脚本。它的作用是删除工程目录下的临时编译文件当出现莫名的链接错误、代码更新不生效时双击运行后再重新编译多数情况能恢复干净环境。批处理执行完后最好关闭 KEIL 重新打开工程避免文件句柄冲突。最后补充一个我常用的验证方法在串口助手里订阅ATQMTSUB后去物联网平台的在线调试页发送属性设置观察 EC200S 是否弹出QMTRECV。这一步跑通后再回抓 MCU 端打印能快速区分是平台配置问题还是 STM32 程序解析问题。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门