
1. 项目缘起与整体设计思路1.1 为什么选择 EG800K 搭配 STM32 做物联网终端做过几个基于 STM32 的物联网项目之后你会发现一个很现实的问题WiFi 方案在实验室里跑得挺欢一到现场就各种掉线尤其是设备装在金属机柜里、地下室或者偏远厂区的时候2.4G 频段根本指望不上。这时候 4G Cat.1 模块就成了最务实的选择——速率够用下行 10Mbps、上行 5Mbps功耗和成本比 Cat.4 低一大截而且运营商网络覆盖成熟插上 SIM 卡基本就能通。EG800K 是移远推出的一款 Cat.1 模块LCCLGA 封装支持 LTE-FDD/TDD 多频段内置 TCP/IP 协议栈通过 AT 指令就能完成拨号、建连、收发数据。它和 STM32 的配合方式很直接STM32 通过 UART 发 AT 指令模块负责跟基站和云端打交道。整个链路里STM32 只管业务逻辑EG800K 只管通信分工清晰。我选这套组合的核心原因有三个。第一AT 指令是串口通信STM32 端不需要跑复杂的协议栈对 RAM 和 Flash 的压力小F103 这种入门芯片都能扛。第二EG800K 支持 MQTT 直连模块内部就能完成 MQTT 报文的封装和解析STM32 只需要发几条指令就能把数据推到云端省去了在单片机上移植 MQTT 库的麻烦。第三模块的 AT 指令集相对稳定移远在文档和固件更新上做得比较规范遇到问题有据可查。1.2 整体架构与数据流设计整个系统的数据流是这样的STM32 采集传感器数据比如温湿度、开关状态通过 UART 把 AT 指令发给 EG800K模块把数据打包成 MQTT 报文发到云端 Broker云端再把数据分发给应用端。反向的话云端下发指令模块收到后通过 UART 传给 STM32STM32 解析后执行动作。这里有个设计取舍需要提前想清楚是把 MQTT 协议栈放在 STM32 上跑还是用模块内置的 MQTT我两种都试过。放在 STM32 上跑的好处是灵活可以自己控制心跳、QoS、重传策略但代价是要移植 paho-mqtt 或者自己写一套RAM 占用至少 10KB 起步F103C8T6 这种 20KB RAM 的芯片会很紧张。用模块内置 MQTT 的好处是省资源STM32 端代码量少但灵活性差一些比如心跳间隔、遗嘱消息这些参数只能通过 AT 指令设置不能动态调整。我的建议是如果你的 STM32 是 F4 以上、RAM 有 64KB 以上可以考虑自己跑 MQTT如果是 F1 或者 L4 这种资源紧张的直接用模块内置 MQTT把精力放在业务逻辑上。本文后续的实操也是基于模块内置 MQTT 方案来展开的。1.3 硬件连接与电源设计要点硬件连接看起来简单但坑不少。EG800K 的 UART 是 1.8V 电平STM32 是 3.3V直接连会烧模块的 IO。必须加电平转换电路我用的是 TXS0108E 双向电平转换芯片或者简单点用两个 MOS 管搭也可以。串口波特率默认是 1152008 位数据位1 位停止位无校验。电源是另一个大坑。EG800K 在发射瞬间的峰值电流能到 2A平均电流在 200mA 左右。如果你用 AMS1117 这种 LDO 供电发射瞬间电压会被拉低模块直接复位或者掉网。我实测下来必须用至少 1A 以上输出能力的 DC-DC输入端加 470uF 以上的电解电容模块的 VBAT 引脚旁边再并一个 100uF 的钽电容。别省这几个电容省了之后调试能把你逼疯。还有一个容易忽略的点模块的 PWRKEY 引脚。EG800K 是低电平开机需要拉低至少 500ms 再释放。我见过有人直接用 STM32 的 GPIO 拉低结果因为 GPIO 驱动能力不够或者时序不对模块死活不开机。稳妥的做法是用一个 NPN 三极管比如 S8050来拉低 PWRKEY基极通过 1K 电阻接 STM32 的 GPIO集电极接 PWRKEY发射极接地。这样 STM32 只需要输出高电平就能控制开机。2. AT 指令核心细节与实操避坑2.1 基础 AT 指令的响应格式与超时处理EG800K 的 AT 指令响应格式跟其他模块大同小异但有几个细节容易踩坑。标准响应是\r\n开头然后是内容最后以\r\nOK\r\n或者\r\nERROR\r\n结尾。但有些指令的响应里会夹杂CSQ: 20,99这种中间信息如果你用简单的字符串匹配来找 OK很容易误判。我的做法是在 STM32 端写一个状态机来解析响应。具体来说定义一个缓冲区每次收到一个字节就存进去同时检查是否出现了\r\nOK\r\n或者\r\nERROR\r\n。如果出现了就认为这条指令执行完毕。超时时间一般设 2 秒但像ATCGATT?这种查询附着状态的指令超时要设到 5 秒以上因为模块可能正在跟基站交互响应会慢。还有一个坑是回显。默认情况下模块会回显你发的指令比如你发AT它会先返回AT再返回OK。如果你在解析的时候没处理回显可能会把回显里的AT当成响应内容。建议在初始化的时候先发ATE0关闭回显这样响应里就只有结果解析起来干净很多。// STM32 端 AT 指令发送与响应解析的简化实现 #define AT_BUF_SIZE 256 char at_buf[AT_BUF_SIZE]; uint16_t at_idx 0; void AT_SendCmd(const char *cmd) { at_idx 0; memset(at_buf, 0, AT_BUF_SIZE); HAL_UART_Transmit(huart2, (uint8_t *)cmd, strlen(cmd), 1000); HAL_UART_Transmit(huart2, (uint8_t *)\r\n, 2, 100); } uint8_t AT_WaitResp(const char *expect, uint32_t timeout_ms) { uint32_t tick HAL_GetTick(); while (HAL_GetTick() - tick timeout_ms) { if (strstr(at_buf, expect) ! NULL) { return 1; } if (strstr(at_buf, \r\nERROR\r\n) ! NULL) { return 0; } } return 0; } // UART 中断回调里把收到的字节存入 at_buf void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart huart2) { if (at_idx AT_BUF_SIZE - 1) { at_buf[at_idx] rx_byte; } HAL_UART_Receive_IT(huart2, rx_byte, 1); } }注意AT_WaitResp里的strstr是区分大小写的EG800K 的响应里 OK 是大写ERROR 也是大写但有些固件版本会返回error小写建议统一转成大写再比较。2.2 网络注册与附着状态的判断逻辑模块开机之后第一件事是等它注册到网络。很多人直接发ATCGATT?查附着状态看到返回CGATT: 1就以为可以了结果发数据的时候发现根本发不出去。这是因为CGATT只表示 PS 域附着成功但不代表 PDP 上下文激活了。正确的流程是先发ATCPIN?确认 SIM 卡就绪返回CPIN: READY才行。然后发ATCSQ查信号质量第一个参数是 RSSI范围 0-31值越大越好一般要大于 10 才比较稳。接着发ATCREG?查网络注册状态返回CREG: 0,1或者CREG: 0,5都表示已注册。最后发ATCGATT?确认附着返回CGATT: 1才算真正就绪。这里有个经验ATCSQ返回的第二个参数是误码率一般忽略就行但第一个参数如果低于 8建议先检查天线。我遇到过天线没接好CSQ 一直是 99表示无信号换了根天线立马变 20 多。uint8_t EG800K_CheckNetwork(void) { AT_SendCmd(ATCPIN?); if (!AT_WaitResp(CPIN: READY, 2000)) return 0; AT_SendCmd(ATCSQ); if (!AT_WaitResp(CSQ:, 2000)) return 0; // 解析 RSSI 值这里简化处理实际要提取数字 if (atoi(strstr(at_buf, CSQ:) 6) 8) return 0; AT_SendCmd(ATCREG?); if (!AT_WaitResp(CREG: 0,1, 5000) !AT_WaitResp(CREG: 0,5, 5000)) return 0; AT_SendCmd(ATCGATT?); if (!AT_WaitResp(CGATT: 1, 5000)) return 0; return 1; }2.3 PDP 上下文激活与 APN 配置PDP 激活是很多新手卡住的地方。EG800K 默认可能已经配置了一个 PDP 上下文但 APN 不一定对。三大运营商的 APN 分别是移动CMNET联通3GNET电信CTNET。物联网卡的话APN 通常是运营商指定的比如CMIOT或者UNIM2M.NJM2MAPN具体要问卡商。配置 PDP 的指令是ATCGDCONT1,IP,APN名称然后发ATCGACT1,1激活。激活成功会返回OK失败返回ERROR。如果失败先检查 APN 拼写再检查卡是否欠费或者被锁。我踩过的一个坑是模块之前被配置过多个 PDP 上下文ATCGACT1,1激活的是第一个但实际用的是第二个。这时候要先发ATCGDCONT?查一下所有上下文确认哪个是你要用的然后激活对应的编号。提示电信物联网卡有时候需要设置ATCGDCONT1,IP,CTNET之后还要发ATCGACT1,1才能激活。如果一直失败试试重启模块再操作。2.4 MQTT 连接参数配置与心跳设置EG800K 内置 MQTT 的指令集是ATQMTOPEN、ATQMTCONN、ATQMTPUB、ATQMTSUB这一套。先发ATQMTOPEN0,broker地址,端口打开 MQTT 客户端返回QMTOPEN: 0,0表示成功。然后发ATQMTCONN0,客户端ID,用户名,密码连接 Broker返回QMTCONN: 0,0,0表示连接成功。心跳间隔通过ATQMTCFGkeepalive,0,60设置单位是秒。我一般设 60 秒太短了费流量太长了容易被 Broker 踢掉。遗嘱消息用ATQMTCFGwill,0,1,0,0,遗嘱主题,1,遗嘱内容设置这个在设备异常掉线时很有用云端能及时知道设备离线了。这里有个细节客户端 ID 必须唯一如果你用同一个 ID 连两次第一次的连接会被踢掉。我建议用 STM32 的 UID 或者 MAC 地址拼一个唯一 ID比如STM32_加上芯片唯一 ID 的后 8 位。void EG800K_MQTTConnect(void) { AT_SendCmd(ATQMTCFG\keepalive\,0,60); AT_WaitResp(OK, 2000); AT_SendCmd(ATQMTOPEN0,\iot.example.com\,1883); AT_WaitResp(QMTOPEN: 0,0, 10000); AT_SendCmd(ATQMTCONN0,\STM32_001\,\user\,\pass\); AT_WaitResp(QMTCONN: 0,0,0, 10000); }2.5 数据发布与订阅的实操细节发布数据用ATQMTPUB0,0,0,0,主题,长度然后等模块返回提示符再把数据发过去。注意长度参数必须和实际数据长度一致否则模块会一直等数据直到超时。我见过有人长度写大了结果模块卡在那里等STM32 以为发完了两边状态不一致。订阅用ATQMTSUB0,1,主题,1最后一个参数是 QoS。订阅成功后收到消息会通过QMTRECV: 0,0,主题,长度,数据上报。STM32 端要在 UART 中断里解析这个格式把数据提取出来。注意ATQMTPUB的数据长度参数是字节数不是字符数。如果你发的是中文一个汉字占 3 个字节UTF-8长度要按字节算。3. 完整实操流程与核心环节实现3.1 硬件上电与模块开机时序上电顺序很重要。先给 STM32 上电再给 EG800K 上电或者同时上电但确保 STM32 的 GPIO 初始状态不会误触发模块。我一般是在 STM32 初始化完成后再通过 PWRKEY 控制模块开机。具体时序拉低 PWRKEY 至少 500ms然后释放等待模块返回RDY。这个RDY是模块上电就绪的标志看到它之后才能发 AT 指令。如果没看到RDY可能是电源问题或者 PWRKEY 时序不对。void EG800K_PowerOn(void) { HAL_GPIO_WritePin(PWRKEY_GPIO_Port, PWRKEY_Pin, GPIO_PIN_SET); // 三极管导通拉低 PWRKEY HAL_Delay(600); HAL_GPIO_WritePin(PWRKEY_GPIO_Port, PWRKEY_Pin, GPIO_PIN_RESET); HAL_Delay(100); // 等待 RDY AT_WaitResp(RDY, 5000); }3.2 网络初始化与 MQTT 连接全流程完整的初始化流程我整理成了一个函数按顺序执行每一步失败就重试或者报错。实测下来从开机到 MQTT 连接成功大概需要 15-25 秒取决于信号质量。uint8_t EG800K_Init(void) { EG800K_PowerOn(); AT_SendCmd(ATE0); // 关闭回显 if (!AT_WaitResp(OK, 2000)) return 1; AT_SendCmd(ATCPIN?); if (!AT_WaitResp(CPIN: READY, 2000)) return 2; // 等待网络注册最多等 30 秒 uint32_t tick HAL_GetTick(); while (HAL_GetTick() - tick 30000) { AT_SendCmd(ATCREG?); if (AT_WaitResp(CREG: 0,1, 2000) || AT_WaitResp(CREG: 0,5, 2000)) { break; } HAL_Delay(1000); } AT_SendCmd(ATCGATT?); if (!AT_WaitResp(CGATT: 1, 5000)) return 3; AT_SendCmd(ATCGDCONT1,\IP\,\CMNET\); if (!AT_WaitResp(OK, 2000)) return 4; AT_SendCmd(ATCGACT1,1); if (!AT_WaitResp(OK, 5000)) return 5; EG800K_MQTTConnect(); return 0; }3.3 数据上报与云端下发联调数据上报我一般用定时器触发比如每 30 秒发一次温湿度。上报之前先检查 MQTT 连接是否还在发ATQMTCONN?查一下如果返回QMTCONN: 0,3表示已连接如果是其他状态就重连。云端下发的话STM32 端要在 UART 中断里解析QMTRECV格式。我写了一个简单的解析函数把主题和数据提取出来然后根据主题判断是控制指令还是配置更新。void Parse_QMTRECV(char *buf) { char *p strstr(buf, QMTRECV:); if (p NULL) return; // 格式: QMTRECV: 0,0,主题,长度,数据 p strchr(p, ); if (p NULL) return; char *topic_start p 1; char *topic_end strchr(topic_start, ); if (topic_end NULL) return; uint8_t topic_len topic_end - topic_start; char topic[64] {0}; strncpy(topic, topic_start, topic_len); // 跳过长度字段找到数据 p topic_end 1; p strchr(p, ,); if (p NULL) return; p strchr(p 1, ,); if (p NULL) return; char *data p 1; // 根据 topic 处理数据 if (strstr(topic, cmd) ! NULL) { // 处理控制指令 } }3.4 低功耗与断线重连策略如果设备是电池供电低功耗就很重要。EG800K 支持 PSM 和 eDRX但配置起来比较麻烦而且不是所有运营商都支持。我一般用简单的策略数据发完之后如果 5 分钟内没有新数据就发ATQMTDISC0断开 MQTT然后发ATCFUN0进入最小功能模式把功耗降到最低。需要发数据的时候再ATCFUN1恢复。断线重连是必须做的。我的做法是维护一个状态变量每次发数据之前检查 MQTT 连接状态如果断了就重连。重连失败超过 3 次就重启模块。重启模块用ATCFUN1,1或者直接控制 PWRKEY 重新开机。提示ATCFUN1,1是重启模块会重新注册网络耗时较长。如果只是 MQTT 断开先试ATQMTCLOSE0关闭再ATQMTOPEN重开比重启模块快很多。4. 常见问题与排查技巧实录4.1 模块不开机或频繁复位这是最常见的问题90% 是电源问题。先量 VBAT 电压正常应该在 3.8V 左右。如果发射瞬间电压掉到 3.4V 以下就是供电能力不足。解决办法是换大电流 DC-DC加电容缩短电源线。还有一个可能是 PWRKEY 时序不对。用示波器看 PWRKEY 引脚拉低时间要大于 500ms释放后要等 1 秒以上再发 AT 指令。如果 PWRKEY 一直是被拉低的状态模块会一直处于关机或者复位循环。4.2 网络注册失败或信号差先查天线。EG800K 的主天线接口是 IPEX 座要接匹配的 4G 天线。天线没接或者接错CSQ 会一直是 99。如果天线没问题查 SIM 卡。用ATCPIN?看返回如果不是READY检查卡座接触、卡是否欠费、是否被锁。信号差的话可以发ATQENGservingcell查服务小区信息看 RSRP 和 SINR。RSRP 大于 -100dBm 算好小于 -110dBm 就比较差了。如果信号一直不好考虑换位置或者加信号放大器。4.3 MQTT 连接失败或频繁掉线MQTT 连接失败先看返回码。QMTCONN: 0,0,0是成功其他都是失败。常见的有QMTCONN: 0,0,1协议版本不对、QMTCONN: 0,0,2客户端 ID 被拒绝、QMTCONN: 0,0,3服务器不可用、QMTCONN: 0,0,4用户名密码错误、QMTCONN: 0,0,5未授权。频繁掉线一般是心跳设置问题。如果心跳设得太短比如 10 秒模块频繁发心跳包可能被 Broker 限流。设得太长比如 300 秒Broker 可能等不及就踢了。我实测 60 秒比较稳。4.4 数据发送失败或云端收不到先确认ATQMTPUB的返回。如果返回QMTPUB: 0,0,0表示发送成功但云端收不到可能是主题不对或者 QoS 不对。检查主题拼写确认云端订阅的主题和你发布的主题一致。如果返回QMTPUB: 0,0,1或者QMTPUB: 0,0,2表示发送失败。常见原因是数据长度不对或者模块缓冲区满了。等一会儿再发或者减小数据长度。4.5 常见问题速查表现象可能原因排查方法解决办法模块不开机电源不足、PWRKEY 时序不对量 VBAT 电压、看 PWRKEY 波形换 DC-DC、加电容、调整时序CSQ 为 99天线没接或损坏检查天线连接接好天线或换天线CGATT 一直为 0SIM 卡问题、APN 不对查 CPIN、查 CGDCONT换卡、改 APNMQTT 连接失败参数错误、网络不通看 QMTCONN 返回码核对参数、检查 PDP 激活数据发不出去长度不对、缓冲区满看 QMTPUB 返回修正长度、等待重发频繁掉线心跳设置不当查 keepalive 配置调整为 60 秒4.6 独家避坑经验分享第一个经验AT 指令之间一定要加延时。我见过有人发完ATCGDCONT立马发ATCGACT结果模块还没处理完上一条第二条就来了直接返回 ERROR。我的做法是每条指令之间至少延时 100ms关键指令延时 500ms。第二个经验UART 中断里不要做复杂解析。我一开始在中断里直接解析 MQTT 数据结果因为解析时间太长丢了后续的字节。后来改成中断里只存数据主循环里解析问题就解决了。第三个经验模块固件版本要确认。不同版本的 EG800K 固件AT 指令集可能有差异。发ATCGMR查版本号然后对照移远的文档确认指令支持情况。如果固件太老建议升级。第四个经验调试的时候用串口助手直接连模块先确认模块本身没问题再连 STM32。这样能快速定位是模块问题还是 STM32 代码问题。第五个经验MQTT 主题不要用中文虽然协议支持但有些 Broker 处理中文主题会有问题。用英文加数字比如device/001/data。4.7 调试工具与辅助手段我常用的调试工具组合是USB 转 TTL 模块CH340 或者 CP2102直接连 EG800K 的 UART用串口助手发 AT 指令。这样能绕过 STM32单独测试模块。串口助手我推荐用 SSCOM 或者 XCOM支持自动添加\r\n省得手动敲。抓包的话如果 MQTT 连的是自己搭的 Broker可以在 Broker 端开日志看模块发的报文对不对。如果连的是云平台一般平台都有设备日志能看到连接和收发记录。STM32 端调试用 SWD 接口配合 Keil 或者 STM32CubeIDE 的调试器可以单步跟踪 AT 指令的发送和响应解析过程。我一般会在关键位置打断点看at_buf里的内容是否符合预期。5. 项目扩展与个人体会这套方案跑通之后扩展方向其实挺多的。比如加个 OTA 功能通过 MQTT 下发固件包STM32 收到后写入 Flash重启后跳转到新固件。或者加个本地缓存网络断开的时候把数据存到 Flash 里恢复后补传。再或者多路传感器采集用 FreeRTOS 开多个任务一个任务专门管通信其他任务管采集和控制。我个人在实际操作中的体会是AT 指令调试最怕的就是想当然。你以为模块会返回 OK结果它返回 ERROR你以为数据发出去了结果长度不对。所以每一步都要验证发完指令一定要看响应不能发完就不管了。还有就是文档要常翻移远的 AT 指令手册写得很详细遇到问题先查手册比在网上瞎搜快得多。最后分享一个小技巧如果你觉得每次手动发 AT 指令太麻烦可以在 STM32 里写一个简单的命令行接口通过串口接收 PC 发来的指令直接转发给 EG800K。这样调试的时候可以在 PC 上用串口助手发 AT 指令STM32 只做透传省去了反复烧录的麻烦。这个透传模式在项目初期特别有用能大幅提高调试效率。