
先说结论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虽然不高但功耗低、成本便宜非常适合传感器数据上报这类物联网场景。我重点对比了这几颗模块模块型号类别接口峰值电流生态资料适用场景移远 EC200ACat.1UART/USB约0.6A资料全ESP-IDF有兼容案例传感器上报、低功耗物联网移远 EC200TCat.1UART/USB约0.6A和EC200A兼容同上广和通 L610Cat.1UART/USB约0.5A有一定社区资料物联网国产化合宙 Air724UGCat.1UART/USB约0.3A教程最多合宙社区活跃新手入门、快速原型移远 SIM7600CECat.4UART/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模块里的PPPPPP全称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功能例如ATHTTPCLIENT、ATMQTTCONN。简单是真简单不需要在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模块引脚说明GPIO17U2_TXDRXDESP32发送到模块GPIO16U2_RXDTXD模块发送到ESP32GNDGND必须共地外部5V电源VCC / VBAT模块主电源外部5V / 3.3VVDD_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说明串口通信正常也确认了波特率。ATCPIN?返回“CPIN: READY”表示SIM卡已经被识别若返回“ERROR”或“CME ERROR: 10”说明SIM卡没插好或没放到位。ATCSQ查询信号强度。返回类似“CSQ: 23,99”这样的结果第一个数字通常大于10才比较稳定小于5基本就是天线或者SIM卡问题。ATCEREG?查询4G网络注册状态返回“CEREG: 0,1”说明已经注册上4G网络如果返回“0,2”是正在注册“0,3”是拒绝接入。ATCGDCONT1,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 ATCGDCONT1,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.4API差距不算太大。用idf.py创建新工程后第一步是在工程根目录的idf.py menuconfig里进入Component config - ESP-Modem做以下配置选择PPP over serial方式。设置串口端口我使用UART2对应GPIO16和GPIO17。波特率设为115200。填入APNcmnet。如果有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模式用ATCPSMS1开启PSM低功耗模式或者干脆用EN脚彻底关闭模块电源定时唤醒后重新拨号。这里要结合业务上报频率来权衡连接保持时间和休眠功耗。5. 常见问题与排查技巧5.1 拨号失败的高频原因我在整个调试过程中踩了不少坑把这些高频问题整理成一张速查表大家可以对照排查现象可能原因排查动作AT指令无响应模块未上电、电平不匹配、波特率不对先用USB转串口连PC确认模块能开机再检查接线返回ERROR但不是OK模块固件版本不支持该指令查模块手册确认正确指令格式ATCPIN?报错SIM卡没插好、卡槽氧化、SIM卡损坏擦拭SIM卡触点换一张卡验证注册不上网络SIM卡未开通数据、天线没装、信号弱查CSQ值确认SIM卡有数据套餐拨号后LCP超时APN配置错误、SIM卡不可用在Linux上用pppd脚本验证APNPPP断开重连不稳定串口干扰、电源压降、运营商策略检查电源纹波加宽况电容复位模块重新拨号能拨号但无法解析域名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的完整过程都掏出来了希望能帮你少走几步弯路。