先说结论:ESP32要上4G网络,最实用的方案就是外挂一个4G Cat.1或者Cat.4模块,通过串口做PPP拨号,把模块变成一个虚拟网卡。这样一来,ESP32上原本用WiFi跑的HTTP、MQTT、TLS这些协议栈代码基本不用改,应用层拿到的就是一个标准IP网络接口。这篇笔记是我从零折腾ESP32 + 4G模块的过程全记录,从模块选型、AT指令调试到ESP-IDF工程实现,再到故障排查,尽量把每个坑都写清楚。适合正在做远程数据采集、车载设备、野外监测这类项目的开发者参考,也适合刚入手4G模块、对PPP拨号一头雾水的朋友先建立一个完整认知。
1. 项目整体思路与方案选型
1.1 为什么ESP32需要4G模块
ESP32本身是一颗非常优秀的WiFi/蓝牙SoC,但它有个天然短板:没有蜂窝通信能力。日常开发接路由器、连手机热点都没问题,可一旦设备被放到园区角落、农业大棚、公路沿线、快递柜这些没有WiFi环境的场景,数据就回不来了。
我自己这次要做的是一个户外环境监测节点,ESP32负责采集温湿度、光照和空气质量,需要定时把数据上报到服务器。现场没有任何可用的WiFi网络,手机热点又不稳定,而且长期开着热点功耗、发热都吃不消。所以自然而然就考虑给ESP32加一个4G模块,走蜂窝网络回传数据。
当时摆在我面前的有三条路线:
- 用ESP32自带WiFi去连接一个现成的4G随身WiFi设备,ESP32这边代码几乎不用改,通过WiFi走后端网络。
- 外挂一个4G模块,通过串口和ESP32通信,走AT指令或者PPP拨号让模块提供网络能力。
- 直接换用带4G基带的SoC方案,比如部分Cat.1模组加MCU的方案,或者用瑞萨、海思等带蜂窝的芯片平台,但这就等于把主控都换掉,开发成本高。
三条路线对比下来,方案1最快但受制于随身WiFi设备的稳定性、供电方式和二次开发灵活性;方案3最彻底但工程量大、周期长;方案2是平衡点:既有独立的4G能力,又能保留ESP32原有的主控和应用代码,改动集中在网络接入这一层。最终我选了方案2,也就是外挂4G模块。
1.2 4G模块选型对比
市面上的4G模块可以简单分成两类:高速率Cat.4和低功耗Cat.1。Cat.4理论下行速率150Mbps,适合视频、图片传输;Cat.1下行速率10Mbps,上行5Mbps,虽然不高,但功耗低、成本便宜,非常适合传感器数据上报这类物联网场景。
我重点对比了这几颗模块:
| 模块型号 | 类别 | 接口 | 峰值电流 | 生态资料 | 适用场景 |
|---|---|---|---|---|---|
| 移远 EC200A | Cat.1 | UART/USB | 约0.6A | 资料全,ESP-IDF有兼容案例 | 传感器上报、低功耗物联网 |
| 移远 EC200T | Cat.1 | UART/USB | 约0.6A | 和EC200A兼容 | 同上 |
| 广和通 L610 | Cat.1 | UART/USB | 约0.5A | 有一定社区资料 | 物联网,国产化 |
| 合宙 Air724UG | Cat.1 | UART/USB | 约0.3A | 教程最多,合宙社区活跃 | 新手入门、快速原型 |
| 移远 SIM7600CE | Cat.4 | UART/USB | 约2A | 资料极全,支持PPP | 视频传输、大流量场景 |
我最后选了EC200A,原因有几个:一是Cat.1的功耗和成本比Cat.4友好很多,我这个项目对带宽要求极低;二是移远在嵌入式领域资料最全,很多坑都有现成答案;三是EC200A本身支持PPP拨号,这点是后面能走通ESP32 PPP方案的关键。
如果新手想快速跑通,我其实更推荐合宙Air724UG,网上教程多,遇到问题容易搜到。大流量、高速率场景就选SIM7600,但供电和散热就要重点设计。
1.3 “随身WiFi”方案在项目里的定位
很多人在搜索这个选题时顺带会看到“随身WiFi”相关的内容。这里先理清一个概念:市面上大家买到的随身WiFi,大多数是一块内置了4G基带加WiFi SoC的整板设备,里面跑的是精简Linux系统,4G网络通过WiFi共享给手机和电脑。
那么在ESP32项目里,“随身WiFi”可以当作一个现成的上行网关来用:ESP32通过WiFi连接随身WiFi,再走它的4G网络访问外网。这个方案的优势是开发量极小,ESP32下几乎不改代码;缺点是设备体积大、供电麻烦、价格比裸模块贵,而且大多数随身WiFi采用内置电池设计,长期通电有安全隐患。
我还看到有人对“随身WiFi去控”“刷机”感兴趣。这类玩法涉及修改设备固件、绕过厂商限制,存在合规风险,我自己在开发时一律不做。如果想安全、可控地让ESP32上4G,老老实实用正规4G模块是正路。
2. PPP拨号原理与关键技术分析
2.1 从拨号上网到4G模块里的PPP
PPP全称Point-to-Point Protocol,点对点协议。很多人听到拨号上网会想到几十年前用Modem“吱呀吱呀”连网,但PPP并没有消失,它至今仍是蜂窝数据链路的重要一环。在4G模块的场景里,链路是这样的:ESP32通过串口接到4G模块,模块通过射频连接基站,运营商的P-GW网关会把网络侧的数据转发到互联网。
PPP工作在数据链路层,负责在两点之间建立数据链路、做身份认证(PAP/CHAP)、协商IP地址和DNS,之后在PPP帧里承载IP报文。你发AT指令让模块拨号,模块就和网络侧开始PPP协商,协商成功后,模块串口上就会出现一条双向的IP数据通道。对ESP32来说,这条串口上的PPP链路经内核的pppos驱动接入lwIP协议栈,就变成了一个虚拟的eth网口,可以用标准socket收发数据。
理解成打电话比较方便:PPP是建立“电话线路”的过程,建立完成后,线路里传的是IP数据包,打电话的双方只需要往线路上说话就行。
2.2 三种走向网络的路径
用4G模块给ESP32提供网络,不同的AT指令设计对应了不同的接入路径,这里列一下我研究过的三种做法。
第一种是模块内置协议栈。直接通过AT指令操作模块内部的TCP/UDP、HTTP、MQTT功能,例如AT+HTTPCLIENT、AT+MQTTCONN。简单是真简单,不需要在ESP32上搭协议栈,发几个字符串就行。但缺点是功能受模块固件限制,很难做自定义的socket、TLS细节控制,多个并发连接时也很吃力。
第二种是串口透传模式。模块开启透明传输后,ESP32通过AT指令建立TCP或UDP连接,之后串口上的所有数据会原样转发到远端。相比第一种,至少保留了部分协议栈控制权,但连接粒度还是受模块约束,要实现HTTPS、WebSocket、多路长连接仍比较困难。
第三种就是我最终选择的PPP拨号。ESP32侧用esp_modem组件发起PPP协商,成功后把串口数据解析成IP包,进入lwIP协议栈。此时ESP32看到的是一个完整的网络接口,可以自由使用socket、用标准HTTP客户端、跑自签名证书的TLS甚至连上各种云平台SDK。
拿开发复杂度换代码灵活度,对做产品的项目来说,PPP拨号是长期最省心的一条路。
2.3 串口参数与数据流设计
PPP拨号期间,4G模块和数据流全部跑在同一路串口上。也就是说,一开始模块处于AT指令模式,你发“AT”它会回“OK”;你发“ATD*99#”发起拨号,模块进入PPP模式,串口上就开始走二进制PPP帧了。
这引出一个关键注意事项:进入PPP模式之后,绝对不能再往串口发AT指令,否则会打乱PPP帧流。如果需要切换回AT模式,一般要通过模块特有的“+++”转义序列或者DTR信号,具体看模块手册。我在调试时就用过一个笨办法:先断开PPP连接,再回到AT模式改APN,改完重新拨号。
串口波特率方面,我建议直接固定115200,这个速率是4G模块和ESP32都能稳定处理的平衡点。如果还是默认9600,链路带宽会很紧张,影响大包传输效率。接线时最好把模块的CTS/RTS流控脚也接出来,尤其在波特率较高时,能有效防止串口丢数据。
3. 硬件连接与AT指令调试实操
3.1 硬件接线与电源设计
4G模块的硬件连接第一原则是“不能侥幸”。先看接线表,这是我这次实际使用的连接关系:
| ESP32引脚 | 4G模块引脚 | 说明 |
|---|---|---|
| GPIO17(U2_TXD) | RXD | ESP32发送到模块 |
| GPIO16(U2_RXD) | TXD | 模块发送到ESP32 |
| GND | GND | 必须共地 |
| 外部5V电源 | VCC / VBAT | 模块主电源 |
| 外部5V / 3.3V | VDD_1V8等 | 模块IO参考电压(看模块手册) |
这里要特别强调电源。4G模块在注册网络、拨号、传输数据的瞬间电流非常大,Cat.1模块瞬时峰值可以到0.5A左右,Cat.4模块更夸张,可能到2A以上。如果直接从ESP32开发板的3.3V引脚取电,板载LDO会被瞬间拉垮,轻则模块重启,重则ESP32也掉电。
我这次猫了个大坑:一开始用ESP32开发板的5V引脚串了一个AMS1117降到3.3V给模块供电,结果每次一拨号模块就重启。后来换成独立的DC-DC降压模块,输出电流能力在3A以上,问题立刻消失。如果是电池供电,建议用开关稳压器设计,前端加大容量电容(至少220uF钽电容或电解电容),再在模块电源脚并联几颗100nF陶瓷电容去高频噪声。
另外很多4G模块有PowerKey脚,默认需要拉低一定时间才能开机。如果按模块手册做的话,建议用一个三极管或MOS管电路来控制PowerKey,由ESP32输出GPIO来触发开机时序。不过我这颗EC200A是带自动开机功能的版本,上电即开机,省了不少事。
3.2 上电后的AT指令巡检
焊接好之后,先在PC上用USB转串口工具单独调试模块,这时候千万不要直接接ESP32。把模块串口通过USB转TTL板连到电脑,打开串口助手,波特率设115200。每次发送指令后加回车换行。
我习惯按下面这个顺序巡检,能快速确认模块和SIM卡的状态:
AT模块如果返回OK,说明串口通信正常,也确认了波特率。
AT+CPIN?返回“+CPIN: READY”表示SIM卡已经被识别,若返回“ERROR”或“+CME ERROR: 10”说明SIM卡没插好或没放到位。
AT+CSQ查询信号强度。返回类似“+CSQ: 23,99”这样的结果,第一个数字通常大于10才比较稳定,小于5基本就是天线或者SIM卡问题。
AT+CEREG?查询4G网络注册状态,返回“+CEREG: 0,1”说明已经注册上4G网络,如果返回“0,2”是正在注册,“0,3”是拒绝接入。
AT+CGDCONT=1,"IP","cmnet"设置APN。国内移动一般是cmnet,联通是uninet,电信是ctnet。不同物联网卡可能用专门的APN,要向卡商确认。
调试的时候,串口工具要开启“加回车换行”和“显示发送”功能,方便对照命令收发。模块开机后第一次发AT可能没反应,多按两次回车唤醒串口,这很正常。
3.3 先行在Linux上验证拨号
在动手写ESP32代码之前,我强烈建议先在PC或者一块Linux板卡上,用脚本把PPP拨号完整跑一遍。这一步目的很纯粹:把SIM卡、APN、模块硬件这些底层问题统统排除掉,为后面的ESP32排障建立基线。
我在手头的一块树莓派上装了ppp软件,写了最简单的拨号脚本:
# /etc/ppp/peers/4g # 指定串口和波特率 /dev/ttyUSB0 115200 # 拨号号码,4G模块固定用 *99# connect "/usr/sbin/chat -v -f /etc/chatscripts/4g.chat" # 不要求默认路由,先手动加 noipdefault usepeerdns # 调试日志,方便看协商过程 debugchat脚本长这样:
# /etc/chatscripts/4g.chat ABORT 'BUSY' ABORT 'ERROR' ABORT 'NO CARRIER' '' AT OK ATE0 OK AT+CGDCONT=1,"IP","cmnet" OK ATD*99# CONNECT ''执行sudo pppd call 4g,如果一切正常,串口会出现“Local IP address”和“Remote IP address”,然后ip addr show ppp0就能看到一个有IP地址的ppp接口,这时再ping -I ppp0 223.5.5.5,通了就说明整条链路OK。
这套验证流程救了我很多次,后面ESP32上任何拨号问题,我都会先回到Linux环境跑一遍,判断是串口、APN还是代码的锅。
4. ESP-IDF工程实现(esp_modem组件)
4.1 创建工程与配置项
确认模块在Linux下可以拨号后,就可以回到ESP-IDF环境做嵌入式集成了。ESP-IDF从4.4版本开始把esp_modem组件纳入了官方组件仓库,5.x版本里默认支持方案更完善。我用的是IDF 5.1,网上很多老教程基于4.4,API差距不算太大。
用idf.py创建新工程后,第一步是在工程根目录的idf.py menuconfig里,进入Component config -> ESP-Modem做以下配置:
- 选择
PPP over serial方式。 - 设置串口端口,我使用UART2,对应GPIO16和GPIO17。
- 波特率设为115200。
- 填入APN
cmnet。 - 如果有SIM卡PIN码,也在这里填,没有就留空。
- 选择模块类型:EC200A可以选
Generic Module或者具体的DCE设备,不同固件版本选择略有差异。
配置完成后,工程描述文件里会自动引入esp_modem相关依赖,无需手动修改CMakeLists,这是IDF组件管理器帮我们处理的。
如果你是老版本IDF,可能需要手动添加prj_include或者从esp_modem官方示例拷贝managed_components目录,但本质逻辑是一样的。
4.2 核心代码实现
下面这段代码是我整理的简化版核心流程,主要解决三件事:初始化串口和PPP网络接口、拨号建立连接、拿网卡事件。
#include <string.h> #include "esp_log.h" #include "esp_netif.h" #include "esp_netif_ppp.h" #include "esp_modem_api.h" static const char *TAG = "ppp_4g"; static void on_ppp_changed(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_id == NETIF_PPP_ERROR_USER) { ESP_LOGE(TAG, "PPP断开,需要重新拨号"); // 这里可以加入自动重连逻辑 } else if (event_id == NETIF_PPP_CONNECTED) { ESP_LOGI(TAG, "PPP连接成功"); } else if (event_id == NETIF_PPP_LOST_IP) { ESP_LOGW(TAG, "IP地址丢失"); } } void init_4g_ppp(void) { esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP(); esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config); assert(esp_netif); esp_netif_ppp_config_t ppp_config = { .ppp_events = { .ppp_changed = on_ppp_changed, } }; esp_netif_set_driver_config(esp_netif, &ppp_config); esp_modem_dte_config_t dte_config = { .dte_buffer_size = 512, .task_stack_size = 4096, .task_priority = 5, .dte_line_buffer_size = 128, .uart_config = { .port = UART_NUM_2, .tx_io = 17, .rx_io = 16, .cts_io = UART_PIN_NO_CHANGE, .rts_io = UART_PIN_NO_CHANGE, .baud_rate = 115200, .flow_control = UART_HW_FLOWCTRL_DISABLE, .use_cts_rts = false, }, }; esp_modem_dce_config_t dce_config = { .apn_config = { .apn = "cmnet", .pdp_type = ESP_MODEM_PDP_TYPE_IP, }, }; esp_modem_dce_t *dce = esp_modem_new(ESP_MODEM_DCE_GENETIC, &dte_config, &dce_config, esp_netif); assert(dce); // 如果模块需要PIN码,调用esp_modem_set_pin(dce, "1234"); ESP_LOGI(TAG, "开始拨号"); esp_err_t err = esp_modem_set_mode(dce, ESP_MODEM_MODE_DATA); if (err != ESP_OK) { ESP_LOGE(TAG, "拨号失败"); } }代码核心就是esp_modem_new创建模块设备,esp_modem_set_mode把模块从AT模式切换到数据模式,也就是触发PPP拨号。切换成功后,esp_netif会收到NETIF_PPP_CONNECTED事件,此时网络接口已可用。lwIP会自动配置IP和DNS。
运行这个代码后,串口日志里如果看到类似“ppp: connect ok”“got IP address”这样的输出,就说明ESP32已经成功拨号上网。
4.3 网络连通性测试与MQTT验证
光看到IP不行,还得验证应用层能跑通。我在ESP32上做了一个最直接的MQTT测试,连到一个本地代理服务器,周期上报传感器数据。发送一条MQTT消息需要经历:应用层socket创建、TCP三次握手、MQTT协议栈封装、lwIP拆分IP包、pppos驱动封装成PPP帧、串口DMA发送、4G模块通过空口发给基站。
这些环节只要有任何一个不匹配,数据就会卡住。所以第一次调MQTT时,我建议先不加密,直接明文MQTT连本地服务器,日志里能明确看到TCP连接建立和数据收发。确认现象正常后再加TLS,避免一开始就混淆问题。
我还分别在局域网和4G网络下跑过同一个MQTT工程,直观感受是4G链路的延迟比WiFi高,一般会多30到60毫秒,这是蜂窝链路的正常特征。后续如果业务对时延敏感,就需要考虑优化APN到最近基站的路由,或者换低时延的专网卡。
顺带提一个功耗优化点:如果项目是电池供电,最好不要让模块一直处于数据模式长时间空跑。可以在空闲时把模块切回AT模式,用AT+CPSMS=1开启PSM低功耗模式,或者干脆用EN脚彻底关闭模块电源,定时唤醒后重新拨号。这里要结合业务上报频率来权衡连接保持时间和休眠功耗。
5. 常见问题与排查技巧
5.1 拨号失败的高频原因
我在整个调试过程中踩了不少坑,把这些高频问题整理成一张速查表,大家可以对照排查:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| AT指令无响应 | 模块未上电、电平不匹配、波特率不对 | 先用USB转串口连PC确认模块能开机,再检查接线 |
| 返回ERROR但不是OK | 模块固件版本不支持该指令 | 查模块手册确认正确指令格式 |
| AT+CPIN?报错 | SIM卡没插好、卡槽氧化、SIM卡损坏 | 擦拭SIM卡触点,换一张卡验证 |
| 注册不上网络 | SIM卡未开通数据、天线没装、信号弱 | 查CSQ值,确认SIM卡有数据套餐 |
| 拨号后LCP超时 | APN配置错误、SIM卡不可用 | 在Linux上用pppd脚本验证APN |
| PPP断开重连不稳定 | 串口干扰、电源压降、运营商策略 | 检查电源纹波,加宽况电容;复位模块重新拨号 |
| 能拨号但无法解析域名 | DNS配置没下发成功 | 手动在lwIP配置DNS服务器 |
| 大包传输丢包严重 | 串口缓冲区太小、流控未开启 | 增大dte_buffer_size,开启硬件流控 |
这里面最容易被忽略的是APN。有些物联网卡用的APN不是公共的cmnet,而是卡商自定义的,比如“cmiot”或者某个自定义字符串。APN不对,PPP能协商上甚至能拿到IP,但数据流量就是不通,排查起来异常痛苦。
还有一点:SIM卡务必要确认开通了数据业务而不是纯语音卡。我就碰到过一张能注册网络但死活拨不上的卡,最后联系运营商才确认是欠费停数据,只保留了语音。
5.2 功耗与发热优化经验
实测下来,EC200A在开机待机状态下电流大概30到50mA,拨号建立连接瞬间会升到300到400mA,数据收发时在100到200mA之间波动。如果不优化,设备7x24小时跑着,电池很快就没电了。
我这次的做法是这样的:
- 软件上把模块和ESP32的WiFi错开。ESP32的WiFi在4G模块数据收发时最好关闭,两者共用2.4GHz频段,会把射频互相干扰,导致信号质量变差、功耗变高。
- 硬件上模块电源由一颗MOS管控制,平时用GPIO拉低关闭模块,到上报时间再打开,拨号、上报、再关闭。这个周期大约每次1分钟,比常供电省很多。
- 模块支持PSM模式的话,用AT指令开启,省电效果明显。
发热方面,4G模块在工作时外壳温升是正常现象,Cat.1模块摸上去大概温温的,Cat.4模块长时间传输可能会烫手。要注意模块的布局不要把热量传到ESP32主控附近,最好留出风道,或者增加PCB散热铜箔。
5.3 天线、链路质量与稳定性的坑
很多4G模块问题最后都出在天线上。IPEX天线座子接触不良、天线线缆过长、天线贴在金属外壳上,都会导致信号强度忽高忽低。我自己吃过大亏:模块在桌面上CSQ是24,装进铝合金外壳后就变成9,怎么调都上不去。最后发现是天线贴在金属面板上,射频信号被全部屏蔽了。
天线摆放经验:
- 天线尽量远离ESP32的PCB天线或WiFi天线,至少隔5厘米以上。
- 天线不要和电源线、串口线平行走线,尽量垂直交叉。
- 如果设备外壳是金属的,必须将天线引到壳体外部,或者选用带馈线的外置天线。
- IPEX座子用热熔胶固定一下,防止震动导致松脱。
链路稳定性方面,我还加了断线自动重连。状态机很简单:监听PPP断开事件,断开后先通过AT指令把模块重置为AT模式,再重新走一遍拨号流程。如果连续拨号失败超过3次,就重启模块电源,等10秒后再次拨号。这套逻辑看着简单,实际部署后能把设备离线率从“隔几天掉一次”降到“一个月掉一次”。
最后说一个容易被忽略的事实:4G模块的PPP拨号虽然名字古老,但它在嵌入式物联网里依然是一个非常好用的联网方式。它不依赖厂商私有协议,不占用模块内部有限的socket资源,直接把标准网络栈开放给上层应用。你在ESP32上写代码,几乎可以把4G网络当作一个普通的有线网口来处理。
我自己踩过电源、天线、APN三个大坑之后,最大的感受是:这类集成项目,如果能提前把硬件基线环境验证好,软件调试会非常顺利,反过来就会陷入“到底是代码问题还是信号问题还是供电问题”的疲劳池中。另外,用4G模块一定要用正规渠道的SIM卡,实名和备案这些合规的事别想跳过,省事一时,后面出了问题更麻烦。
这篇笔记写得很细,基本把我从电路图到跑通MQTT的完整过程都掏出来了,希望能帮你少走几步弯路。