前言
在之前的文章中,我们都是利用ESP32自带的WiFi进行网络连接。但在户外或者没有WiFi覆盖的角落,想要让设备联网,就得请出“4G模块”了。
通常大家驱动4G模块(比如SIM800, Air724, EC20等)最原始的方法是用UART发送AT指令。 比如 AT+HTTPINIT、AT+HTTPPARA… 这种方式不仅繁琐,而且解析返回字符串简直是噩梦,写出来的代码全是状态机,一旦模块吐点乱码,程序直接暴毙。手动解析AT指令简直是坏文明!
为了优雅地使用4G模块,我决定使用 PPPoS (Point-to-Point Protocol over Serial)。 简单来说,就是把串口“伪装”成一个网卡。这样底层的TCP/IP协议栈(LwIP)就能直接接管网络,我们写上层代码时,完全不用关心是在用WiFi还是4G,直接调标准的Socket接口就完事了。
准备工作 1. 硬件连接 找一个支持PPP拨号的模块(市面上绝大多数Cat.1/Cat.4模块都支持)。 我用的是合宙的Air780EG, 这个模块属于4G+GNSS二合一模块, 在制作需要定位的设备很方便. 将模块的 TX/RX 接到 ESP32C3 的串口引脚上(记得共地)
PS: 模块进入PPPOS状态以后GPS就不能走主串口输出 好消息是我们可以使用ESP32C3的另外一个串口和GPS串口对接, 这样就可以同时使用GPS和4G PPPOS拨号了 比如说我用的是GPIO8和GPSTX连接, TXD0和RXD0接4G模块的主串口
核心代码实现 示例项目可以通过下面命令创建
1 idf.py create-project-from-example "espressif/esp_modem=1.0.3:pppos_client"
接下来会基于 ESP-IDF 的 pppos_client 示例解析解析并补充说明.
整个过程其实就分三步:定义事件、初始化DCE/DTE、切换到数据模式 。
1. 事件处理 (Event Handler) PPPoS 的运行依赖于事件驱动。我们需要监听 IP 层面的事件,当拿到 IP 地址时,才算真正联网成功。
modem_event.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static EventGroupHandle_t event_group = NULL ;static const int CONNECT_BIT = BIT0;static const int DISCONNECT_BIT = BIT1;static void on_ip_event (void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_id == IP_EVENT_PPP_GOT_IP) { ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "Modem Connect to PPP Server" ); ESP_LOGI(TAG, "IP : " IPSTR, IP2STR(&event->ip_info.ip)); ESP_LOGI(TAG, "Netmask : " IPSTR, IP2STR(&event->ip_info.netmask)); ESP_LOGI(TAG, "Gateway : " IPSTR, IP2STR(&event->ip_info.gw)); xEventGroupSetBits(event_group, CONNECT_BIT); } else if (event_id == IP_EVENT_PPP_LOST_IP) { ESP_LOGW(TAG, "Modem Disconnect from PPP Server" ); xEventGroupSetBits(event_group, DISCONNECT_BIT); } }
2. 初始化与拨号 这一步是重头戏。我们需要初始化 Netif(网络接口),配置 DTE(数据终端设备,即ESP32C3端的UART),然后初始化 DCE(数据通信设备,即4G模块)。
main.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 void app_main (void ) { ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, NULL )); event_group = xEventGroupCreate(); gpio_config_t io_config = {.pin bit mask = BIT64(7 ), .mode = GPIO MODE OUTPUT}; gpio_config(&io_config); gpio_set_level(7 ,0 ); TaskDelay(pdMS_TO_TICKS(500 )); gpio_set_level(7 ,1 ); esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("cmnet" ); esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP(); esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config); esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG(); dte_config.uart_config.tx_io_num = 21 ; dte_config.uart_config.rx_io_num = 20 ; dte_config.uart_config.flow_control = ESP_MODEM_FLOW_CONTROL_NONE; ESP_LOGI(TAG, "Initializing esp_modem..." ); esp_modem_dce_t *dce = esp_modem_new_dev(ESP_MODEM_DCE_CUSTOM, &dte_config, &dce_config, esp_netif); int rssi, ber; if (esp_modem_get_signal_quality(dce, &rssi, &ber) == ESP_OK) { ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d" , rssi, ber); } ESP_LOGI(TAG, "Switching to Data Mode..." ); esp_err_t err = esp_modem_set_mode(dce, ESP_MODEM_MODE_DATA); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to enter data mode!" ); return ; } ESP_LOGI(TAG, "Waiting for IP address..." ); xEventGroupWaitBits(event_group, CONNECT_BIT, pdFALSE, pdFALSE, portMAX_DELAY); ESP_LOGI(TAG, "Pinging example.com..." ); esp_console_run("ping example.com" , NULL ); }
尤其要注意初始化之前进行模块的重启, 官方例子已经没有这部分代码了.所以我们手动补充在初始化之前
如果一切顺利就会看到下图ping成功的输出
踩坑小记 在调试过程中遇到过几个坑,顺便记录一下:
供电问题 :4G模块瞬时电流很大,普通的3.3V LDO通常扛不住,导致模块会反复重启, 建议单独给模块供电。我用了一个DCDC提供3.9V电压给模块.
流控 :如果你的线没接 RTS/CTS,一定要在配置里把 flow_control 设为 ESP_MODEM_FLOW_CONTROL_NONE,否则数据发不出去。
APN :虽然现在很多卡都能自动识别APN,但最好还是显式指定一下(移动通常是 cmnet,联通 3gnet)。
4G模块初始化 : RESET脚也得用ESP32 GPIO控制, 用于4G模块初始化重启
总结 使用 PPPoS 后,4G 模块的使用体验和 WiFi 几乎没有区别。 配合我在《ESP32异步网络请求》 中介绍的 AsyncHTTP 或者 MQTT 库,就可以轻松地把设备部署到野外了。
那么,古尔丹,代价是什么呢? 答案是功耗 爆炸. 由于4G模块一直处于数据模式, 如果需要发送一些功耗优化的AT指令, 比如飞行模式, 休眠. 那么会异常麻烦, 每次都需要切换回Command模式再发送指令. 如果和我一样是自带的GPS功能的4G模块, 那么功耗更是爆炸, 想要独立控制GPS电源必须通过AT指令, 来回切换失败风险很高.
参考资料