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

资讯详情

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

STM32F103+EC800-4G对接阿里云IoT实现温度数据上云

STM32F103+EC800-4G对接阿里云IoT实现温度数据上云 简介本资源是一套面向嵌入式物联网开发初学者与中级工程师的实战项目例程聚焦STM32F103单片机与EC800-4G模块协同工作实现温度数据采集并上传至阿里云IoT平台有效解决边缘设备联网上云的关键链路开发难题。压缩包共227个文件含38个C源码、43个头文件.h、40个编译中间文件.d、39个.o目标文件及配套的KEIL工程文件.uvprojx/.uvoptx、Hex固件、接线说明PDF与清除编译缓存的批处理脚本等结构完整便于理解编译流程与硬件驱动逻辑整体大小为7.39MB。已有165人学习下载资源代码采用标准库编写注释详尽关键模块如TIM、ADC、I2C、RCC及4G串口通信均有清晰定义接线关系在代码中明确标注支持快速移植适配同类STM32F103芯片并提供可复用的阿里云MQTT连接与数据上报框架。1. 用STM32F103EC800-4G把本地温度数据实时上云不是接线通电就能跑通的“三步操作”很多刚做完STM32F103最小系统、能点亮LED和读DS18B20温度的工程师一上来就想把数据发到阿里云IoT平台——结果卡在AT指令超时、MQTT连接被拒绝、设备认证失败这三关里。这不是单片机编程问题而是嵌入式通信链路的完整闭环传感器采集→MCU协议封装→4G模块透传→云平台鉴权→Topic路由→消息持久化。EC800-4G虽是国产主流4G模组但其AT固件版本如V1.2.4或V1.3.1对MQTT 3.1.1的支持存在差异STM32F103资源有限64KB Flash/20KB RAMHAL库默认配置下串口DMA缓冲区常被AT响应冲垮阿里云IoT平台要求设备证书ProductKey/DeviceName/DeviceSecret必须经HMAC-SHA256签名生成ClientID与Password且每次连接Token有效期仅2小时。本文不讲“怎么点亮灯”只聚焦真实产线级落地从STM32F103串口3驱动EC800开始绕过HAL库UART接收中断丢包陷阱用环形缓冲区状态机解析AT响应手写MQTT CONNECT报文构造逻辑并验证阿里云IoT控制台中设备在线状态与历史数据曲线。适合已能独立完成STM32F103串口收发、熟悉AT指令集、但尚未打通端到云全链路的嵌入式开发者。2. STM32F103与EC800-4G硬件连接及串口驱动层关键配置EC800-4G模块与STM32F103的物理连接看似简单但引脚电平匹配、供电稳定性、信号完整性直接决定AT指令交互成功率。常见错误是直接将EC800的TX/RX接到STM32的PA9/PA10USART1却忽略EC800工作电平为3.3V TTL而部分开发板USB转串口芯片输出为5V长期运行易损坏EC800 UART接口。更隐蔽的问题是电源设计EC800峰值电流可达2APS域附着时若仅用AMS1117-3.3稳压芯片供电电压跌落会导致AT指令无响应。2.1 硬件连接规范与电源设计要点信号线STM32F103引脚EC800引脚注意事项VCC3.3V电源需≥2AVCC必须使用DC-DC降压模块如MP1584供电禁用LDO单独带载GND共地GND地线走线宽度≥2mm避免与高频信号线平行走线TXDPA15USART3_TXRXD推荐使用USART3PA15/PA14引脚复用冲突少且EC800默认波特率921600需高主频支持RXDPA14USART3_RXTXD串口线长度≤10cm加100Ω终端电阻抑制反射RESETPC13推挽输出RESET上电后需保持低电平≥100ms再拉高确保EC800完成内部初始化STATUSPB0浮空输入STATUS低电平表示EC800已注册网络可作为软件等待依据提示不要用USART1PA9/PA10驱动EC800——该串口常被调试打印占用且PA10在部分最小系统板上存在上拉电阻干扰RXD采样。PA14/PA15属于USART3在STM32F103C8T6等常用型号中引脚资源充裕且无BOOT引脚冲突风险。2.2 USART3初始化关闭HAL默认中断启用DMA双缓冲接收HAL库默认HAL_UART_Receive_IT()在高波特率下极易丢包AT响应含大量\r\n换行符中断服务函数执行时间超过字符间隔即导致Framing Error。实测921600bps下每秒约115200字节中断响应延迟超2μs即丢帧。解决方案是关闭接收中断改用DMA循环模式环形缓冲区手动解析。// stm32f103_usart3_ec800.c #include stm32f103xb.h #include ring_buffer.h // 自定义环形缓冲区头文件 #define USART3_RX_BUF_SIZE 512 uint8_t usart3_rx_dma_buf[USART3_RX_BUF_SIZE * 2]; // 双缓冲区 RingBuffer_t usart3_rx_ring; void USART3_Init(void) { RCC-APB2ENR | RCC_APB2ENR_USART3EN; // 使能USART3时钟 RCC-APB2ENR | RCC_APB2ENR_IOPCEN; // 使能PORTC时钟PA14/PA15属PORTA但RESET用PC13 // PA14/PA15复用为USART3功能 GPIOA-CRH ~(GPIO_CRH_CNF6 | GPIO_CRH_MODE6); // PA14 清除原配置 GPIOA-CRH | (GPIO_CRH_CNF6_1 | GPIO_CRH_MODE6_1); // 复用推挽输出 GPIOA-CRH ~(GPIO_CRH_CNF7 | GPIO_CRH_MODE7); // PA15 GPIOA-CRH | (GPIO_CRH_CNF7_1 | GPIO_CRH_MODE7_1); USART3-BRR 0x111; // 波特率921600 72MHz PCLK72MHz, BRRDIVMANT(0x11) DIVFRAQ(0x1) USART3-CR1 USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; // 使能USART、发送、接收 USART3-CR2 0; // 无停止位扩展 USART3-CR3 USART_CR3_DMAT | USART_CR3_DMAR; // 使能DMA发送/接收 // DMA1 Channel3 配置接收USART3_RX RCC-AHBENR | RCC_AHBENR_DMA1EN; DMA1_Channel3-CCR 0; DMA1_Channel3-CNDTR USART3_RX_BUF_SIZE; DMA1_Channel3-CPAR (uint32_t)USART3-DR; DMA1_Channel3-CMAR (uint32_t)usart3_rx_dma_buf; DMA1_Channel3-CCR DMA_CCR_EN | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_0 | DMA_CCR_MSIZE_0 | DMA_CCR_PL_0; // 启动DMA循环模式双缓冲 DMA1_Channel3-CMAR (uint32_t)usart3_rx_dma_buf; DMA1_Channel3-CNDTR USART3_RX_BUF_SIZE; DMA1_Channel3-CCR | DMA_CCR_CIRC; RingBuffer_Init(usart3_rx_ring, usart3_rx_dma_buf, USART3_RX_BUF_SIZE); }2.2.1 环形缓冲区解析AT响应的核心逻辑DMA接收的数据流是连续字节流需识别\r\n边界并提取完整AT响应。以下函数在主循环中调用// 解析DMA缓冲区提取完整AT行 void ParseEC800Response(void) { uint8_t ch; static uint8_t line_buf[128]; static uint8_t line_len 0; while (RingBuffer_Pop(usart3_rx_ring, ch)) { if (ch \r || ch \n) { if (line_len 0) { line_buf[line_len] \0; ProcessATLine(line_buf); // 关键处理函数见2.3节 line_len 0; } } else if (line_len sizeof(line_buf)-1) { line_buf[line_len] ch; } // 忽略超长行防止溢出 } } // 示例检测是否收到OK确认 void ProcessATLine(uint8_t* line) { if (strcmp((char*)line, OK) 0) { ec800_state EC800_STATE_OK; } else if (strncmp((char*)line, QMTSTAT: , 10) 0) { // MQTT状态上报解析连接状态 if (strstr((char*)line, 3)) ec800_mqtt_connected 1; } }注意strcmp在STM32F103上需包含string.h但注意其依赖__aeabi_memcmp若链接报错需在startup_stm32f103xb.s中确保__use_no_semihosting已定义。此处strcmp用于短字符串比对AT响应通常32字节性能可接受。3. EC800-4G模块AT指令序列控制与MQTT连接阿里云IoT平台EC800-4G出厂固件默认工作在TCP透传模式需通过AT指令切换至MQTT客户端模式并完成网络附着、SIM卡认证、云端连接三阶段。阿里云IoT平台要求设备使用MQTT 3.1.1协议且CONNECT报文中的ClientID、Username、Password必须按平台规则生成否则返回0x05 Connection Refused, not authorized。3.1 分阶段AT指令序列及超时重试机制EC800的AT指令执行非原子性需严格遵循时序。例如ATQMTCONN返回QMTCONN: 0,1表示连接成功但实际MQTT握手需额外200ms才能建立。以下为可靠指令序列基于EC800固件V1.2.4步骤AT指令期望响应超时说明1ATCFUN1OK500ms启用射频功能2ATCGATT1OK3000ms附着到GPRS网络需SIM卡正常3ATQIACTQIACT: ip10000ms激活PDP上下文获取IP地址4ATQMTCFGkeepalive,0,120OK500ms设置MQTT心跳120秒阿里云要求≤300s5ATQMTOPEN0,iot-as-mqtt.cn-shanghai.aliyuncs.com,1883QMTOPEN: 0,015000ms连接阿里云MQTT Broker上海节点6ATQMTCONN0,client_id,username,passwordQMTCONN: 0,310000ms发起MQTT连接3表示连接成功// 发送AT指令并等待指定响应 uint8_t SendATAndWait(const char* cmd, const char* expect, uint32_t timeout_ms) { uint32_t start HAL_GetTick(); USART3_Transmit((uint8_t*)cmd, strlen(cmd)); // 自定义发送函数 while (HAL_GetTick() - start timeout_ms) { ParseEC800Response(); // 解析DMA缓冲区 if (ec800_state EC800_STATE_OK strstr(last_at_response, expect) ! NULL) { return 1; } HAL_Delay(10); } return 0; // 超时 } // 主连接流程 uint8_t ConnectToAliyunIoT(void) { if (!SendATAndWait(ATCFUN1\r\n, OK, 500)) return 0; if (!SendATAndWait(ATCGATT1\r\n, OK, 3000)) return 0; if (!SendATAndWait(ATQIACT\r\n, QIACT:, 10000)) return 0; if (!SendATAndWait(ATQMTCFG\keepalive\,0,120\r\n, OK, 500)) return 0; if (!SendATAndWait(ATQMTOPEN0,\iot-as-mqtt.cn-shanghai.aliyuncs.com\,1883\r\n, QMTOPEN: 0,0, 15000)) return 0; // 构造MQTT连接参数见3.2节 char mqtt_conn_cmd[256]; GenerateMQTTConnectParams(mqtt_conn_cmd); if (!SendATAndWait(mqtt_conn_cmd, QMTCONN: 0,3, 10000)) return 0; return 1; }3.2 阿里云IoT平台MQTT连接参数生成规则阿里云IoT要求MQTT CONNECT报文的三个字段必须动态生成硬编码会导致认证失败ClientID:productKey:deviceName|securemode3,signmethodhmacsha256,timestampts|Username:deviceNameproductKeyPassword:hmacsha256(deviceSecret, content)其中content为clientIdclient_idusernameusernamepasswordpasswordtimestampts// 生成MQTT连接参数需提前定义PRODUCT_KEY/DEVICE_NAME/DEVICE_SECRET void GenerateMQTTConnectParams(char* cmd) { char ts_str[13]; uint32_t ts HAL_GetTick() / 1000; // 时间戳单位秒 sprintf(ts_str, %lu, ts); // 构造content字符串用于HMAC签名 char content[256]; sprintf(content, clientId%susername%s%spassword%s timestamp%s, PRODUCT_KEY, DEVICE_NAME, PRODUCT_KEY, , ts_str); // 计算HMAC-SHA256需移植mbedtls或使用轻量SHA256实现 uint8_t hmac[32]; CalcHMACSHA256((uint8_t*)DEVICE_SECRET, strlen(DEVICE_SECRET), (uint8_t*)content, strlen(content), hmac); // Base64编码阿里云要求Base64格式Password char password_b64[64]; Base64_Encode(hmac, 32, (uint8_t*)password_b64); // 组装AT指令 sprintf(cmd, ATQMTCONN0,\%s:%s|securemode3,signmethodhmacsha256,timestamp%s|\, \%s%s\,\%s\\r\n, PRODUCT_KEY, DEVICE_NAME, ts_str, DEVICE_NAME, PRODUCT_KEY, password_b64); }提示CalcHMACSHA256需移植轻量级密码库。推荐使用tiny-hmac-sha2564KB代码避免引入完整mbedtls导致Flash溢出。STM32F103的64KB Flash需为用户代码预留至少20KBHMAC计算耗时约8ms72MHz完全可接受。4. 温度数据采集与MQTT PUBLISH报文构造及阿里云IoT平台配置验证温度数据上云的最终环节是构造符合阿里云物模型Thing Model的JSON载荷并通过MQTT PUBLISH指令发送。EC800不支持自动JSON解析需在STM32F103端拼接完整报文。阿里云IoT平台要求Topic格式为/sys/{productKey}/{deviceName}/thing/event/property/post且Payload必须含id、params、method字段。4.1 DS18B20温度采集与JSON载荷生成DS18B20采用单总线协议需精确时序。HAL库无现成驱动需手写延时函数// DS18B20单总线底层操作PB1引脚 #define OW_GPIO_PORT GPIOB #define OW_GPIO_PIN GPIO_PIN_1 void OW_Reset(void) { OW_GPIO_PORT-CRH ~(GPIO_CRH_CNF1 | GPIO_CRH_MODE1); OW_GPIO_PORT-CRH | GPIO_CRH_CNF1_0 | GPIO_CRH_MODE1_0; // 推挽输出 OW_GPIO_PORT-BSRR GPIO_BSRR_BR1; // 拉低 Delay_us(480); // 480μs OW_GPIO_PORT-BSRR GPIO_BSRR_BS1; // 拉高 Delay_us(60); // 60μs // 读取应答脉冲... } float ReadDS18B20Temp(void) { uint8_t rom[8], scratchpad[9]; OW_Reset(); OW_WriteByte(0xCC); // Skip ROM OW_WriteByte(0x44); // Convert T HAL_Delay(750); // 等待转换完成 OW_Reset(); OW_WriteByte(0xCC); OW_WriteByte(0xBE); // Read Scratchpad for (int i 0; i 9; i) { scratchpad[i] OW_ReadByte(); } int16_t raw (scratchpad[1] 8) | scratchpad[0]; return (float)raw * 0.0625f; }4.1.1 构造符合阿里云物模型的JSON Payload假设物模型中定义了Temperature属性标识符为temperature数据类型为float{ id: 12345, version: 1.0, params: { temperature: 25.625 }, method: thing.event.property.post }// 动态生成JSON避免sprintf内存溢出 char json_payload[256]; void BuildTemperaturePayload(float temp) { static uint32_t msg_id 1; char id_str[12]; char temp_str[16]; sprintf(id_str, %lu, msg_id); dtostrf(temp, 1, 3, temp_str); // 保留3位小数 // 手动拼接JSON比sprintf更省内存 uint8_t pos 0; memcpy(json_payload pos, {\id\:\, 7); pos 7; memcpy(json_payload pos, id_str, strlen(id_str)); pos strlen(id_str); memcpy(json_payload pos, \,\version\:\1.0\,\params\:{\temperature\:, 38); pos 38; memcpy(json_payload pos, temp_str, strlen(temp_str)); pos strlen(temp_str); memcpy(json_payload pos, },\method\:\thing.event.property.post\}, 42); }4.2 发送MQTT PUBLISH指令及阿里云IoT平台验证方法EC800通过ATQMTPUB发送消息需指定Topic、QoS等级、消息体长度void PublishTemperature(float temp) { BuildTemperaturePayload(temp); char topic[128]; sprintf(topic, /sys/%s/%s/thing/event/property/post, PRODUCT_KEY, DEVICE_NAME); char pub_cmd[128]; sprintf(pub_cmd, ATQMTPUB0,0,0,%d,\%s\\r\n, strlen(json_payload), topic); // 发送PUBLISH指令 USART3_Transmit((uint8_t*)pub_cmd, strlen(pub_cmd)); // 等待EC800返回QMTPUB: 0,0 表示发布成功 WaitForATResponse(QMTPUB: 0,0, 5000); // 发送JSON载荷EC800会自动添加\r\n USART3_Transmit((uint8_t*)json_payload, strlen(json_payload)); }4.2.1 阿里云IoT平台实时验证四步法设备在线状态检查登录 阿里云IoT控制台 → 实例 → 设备管理 → 查看目标设备状态是否为“在线”绿色图标若为“离线”则检查EC800网络附着日志ATCGATT?返回CGATT: 1Topic订阅验证在控制台“监控运维” → “日志服务” → 开启设备日志查看/sys/{pk}/{dn}/thing/event/property/postTopic是否有PUBLISH记录数据解析验证进入“设备影子”页面查看temperature字段值是否随上报更新若为空则检查JSON格式是否合法阿里云严格校验JSON语法历史数据曲线在“数据可视化”中创建折线图绑定temperature属性观察24小时内数据点是否连续——若出现断点大概率是EC800网络瞬断导致MQTT连接丢失需在代码中加入重连机制检测QMTSTAT: 0,0后自动重连。注意阿里云IoT平台对PUBLISH频率有限制默认10条/秒/设备若温度采集周期设为1秒需在PublishTemperature()中加入HAL_Delay(100)防限频。实际产线建议设为30秒上报一次平衡功耗与数据时效性。5. STM32F103资源优化与EC800-4G异常恢复实战技巧STM32F103的RAM仅20KB而EC800-4G在MQTT长连接下需维护TCP socket、MQTT会话状态、AT指令缓冲区极易因内存碎片导致ATQMTCONN返回ERROR。同时EC800在弱信号区常发生TCP连接闪断需设计无感恢复机制而非简单重启模块。5.1 内存精简策略裁剪HAL库与静态分配缓冲区HAL库默认为每个UART分配256字节接收缓冲区USART3USART1共占用512字节。实际EC800 AT响应最长不超过128字节如QMTSTAT: 0,1可强制修改// 在stm32f103xx_hal_conf.h中注释掉HAL_UART_MODULE_ENABLED // #define HAL_UART_MODULE_ENABLED // 手动定义最小化UART结构体 typedef struct { USART_TypeDef* Instance; uint8_t* pTxBuffPtr; uint16_t TxXferSize; uint16_t TxXferCount; } UART_HandleTypeDef; // 所有缓冲区改为静态分配避免malloc static uint8_t uart3_tx_buf[64]; // 发送缓冲区足够 static uint8_t uart3_rx_buf[128]; // 接收缓冲区按最大AT响应设5.2 EC800-4G异常恢复状态机设计EC800异常包括QMTSTAT: 0,0连接断开、QMTSTAT: 0,2网络不可达、ERRORAT指令失败。状态机需区分临时故障与永久故障当前状态检测事件动作下一状态CONNECTED收到QMTSTAT: 0,0记录断开时间启动重连定时器RECONNECTINGRECONNECTING定时器超时30s执行ATQMTDISC0→ATQMTOPEN→ATQMTCONNCONNECTINGCONNECTINGQMTCONN: 0,3标记连接成功CONNECTEDCONNECTING连续3次失败触发EC800硬件复位拉低RESET引脚100msRESETTINGRESETTINGRESET引脚拉高后500ms重新执行全流程AT指令INITIALIZING// 状态机核心代码 void EC800_StateMachine(void) { switch (ec800_state) { case EC800_STATE_CONNECTED: if (ec800_mqtt_disconnected) { ec800_reconnect_timer HAL_GetTick(); ec800_state EC800_STATE_RECONNECTING; } break; case EC800_STATE_RECONNECTING: if (HAL_GetTick() - ec800_reconnect_timer 30000) { EC800_Disconnect(); EC800_OpenBroker(); ec800_state EC800_STATE_CONNECTING; } break; case EC800_STATE_CONNECTING: if (ec800_mqtt_connected) { ec800_state EC800_STATE_CONNECTED; } else if (ec800_connect_retry 3) { EC800_HardReset(); // 硬件复位 ec800_state EC800_STATE_RESETTING; } break; case EC800_STATE_RESETTING: if (HAL_GetTick() - ec800_reset_start 500) { ec800_state EC800_STATE_INITIALIZING; } break; } }5.2.1 阿里云IoT平台设备影子同步技巧设备影子Device Shadow是阿里云提供的设备状态缓存但默认不主动推送更新。若需实时同步温度值到App端可在PUBLISH后立即调用ATQMTSUB订阅/sys/{pk}/{dn}/thing/service/property/setTopic监听云端下发的控制指令如设置上报间隔。此操作需在MQTT连接成功后执行// 订阅服务Topic用于接收云端指令 void SubscribeServiceTopic(void) { char sub_cmd[128]; sprintf(sub_cmd, ATQMTSUB0,\/sys/%s/%s/thing/service/property/set\,1\r\n, PRODUCT_KEY, DEVICE_NAME); SendATAndWait(sub_cmd, QMTSUB: 0,1, 2000); }当云端下发{method:thing.service.property.set,params:{report_interval:60}}时EC800会推送QMTRECV: 0,/sys/.../property/set,...STM32F103解析后即可动态调整HAL_Delay()参数实现远程配置。本文还有配套的精品资源点击获取
返回列表