跳至内容
返回

告别AT指令:ESP32通过PPPoS驱动4G模块上网

发布于:  at  08:09 上午

前言

在之前的文章中,我们都是利用ESP32自带的WiFi进行网络连接。但在户外或者没有WiFi覆盖的角落,想要让设备联网,就得请出“4G模块”了。

air780eg_modem

通常大家驱动4G模块(比如SIM800, Air724, EC20等)最原始的方法是用UART发送AT指令。 比如 AT+HTTPINITAT+HTTPPARA… 这种方式不仅繁琐,而且解析返回字符串简直是噩梦,写出来的代码全是状态机,一旦模块吐点乱码,程序直接暴毙。手动解析AT指令简直是坏文明!

为了优雅地使用4G模块,我决定使用 PPPoS(Point-to-Point Protocol over Serial)。 简单来说,就是把串口“伪装”成一个网卡。这样底层的TCP/IP协议栈(LwIP)就能直接接管网络,我们写上层代码时,完全不用关心是在用WiFi还是4G,直接调标准的Socket接口就完事了。

So Fragrant

准备工作

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模块的主串口

核心代码实现

示例项目可以通过下面命令创建

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 地址时,才算真正联网成功。

static EventGroupHandle_t event_group = NULL;
static const int CONNECT_BIT = BIT0;
static const int DISCONNECT_BIT = BIT1;

// 监听 IP 获取事件
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;

        // 打印一下获取到的IP信息,看着就舒服
        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模块)。

void app_main(void)
{
// 1. 初始化网络接口和事件循环
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();

    // 1.5 重启模块, 我的模块Reset脚接的GPIO7
    gpio_config_t io_config = {.pin bit mask = BIT64(7),
                               .mode = GPIO MODE OUTPUT};
    gpio_config(&io_config);
    gpio_set_level(70);
    TaskDelay(pdMS_TO_TICKS(500));
    gpio_set_level(71);
    // 2. 配置 PPP 网络接口
    esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("cmnet"); // APN通常是cmnet
    esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
    esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);

    // 3. 配置串口 (DTE)
    esp_modem_dte_config_t dte_config = ESP_MODEM_DTE_DEFAULT_CONFIG();
    dte_config.uart_config.tx_io_num = 21; // TX引脚
    dte_config.uart_config.rx_io_num = 20; // RX引脚
    dte_config.uart_config.flow_control = ESP_MODEM_FLOW_CONTROL_NONE; // 没接流控线就选NONE

    // 4. 创建 Modem 对象 (这里根据实际型号选择, 由于没有Air780eg的配置,所以使用的ESP_MODEM_DCE_CUSTOM)
    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);

    // 5. 检查信号质量, 如果没有信号, 很大概率会有问题, 4G模块可能没有正常工作
    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);
    }

    // 6. 关键步骤:切换到 DATA 模式!
    // 这一步之后,串口就变成了透明传输的网卡通道,不能再发AT指令了
    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;
    }

    // 7. 等待获取 IP
    ESP_LOGI(TAG, "Waiting for IP address...");
    xEventGroupWaitBits(event_group, CONNECT_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    // 到这里,你已经连上网了!
    // 可以尝试 Ping 一下
    ESP_LOGI(TAG, "Pinging example.com...");
    esp_console_run("ping example.com", NULL);

}

尤其要注意初始化之前进行模块的重启, 官方例子已经没有这部分代码了.所以我们手动补充在初始化之前

如果一切顺利就会看到下图ping成功的输出

running

踩坑小记

在调试过程中遇到过几个坑,顺便记录一下:

  1. 供电问题:4G模块瞬时电流很大,普通的3.3V LDO通常扛不住,导致模块会反复重启, 建议单独给模块供电。我用了一个DCDC提供3.9V电压给模块.
  2. 流控:如果你的线没接 RTS/CTS,一定要在配置里把 flow_control 设为 ESP_MODEM_FLOW_CONTROL_NONE,否则数据发不出去。
  3. APN:虽然现在很多卡都能自动识别APN,但最好还是显式指定一下(移动通常是 cmnet,联通 3gnet)。
  4. 4G模块初始化: RESET脚也得用ESP32 GPIO控制, 用于4G模块初始化重启

总结

使用 PPPoS 后,4G 模块的使用体验和 WiFi 几乎没有区别。 配合我在《ESP32异步网络请求》中介绍的 AsyncHTTP 或者 MQTT 库,就可以轻松地把设备部署到野外了。

那么,古尔丹,代价是什么呢?

答案是功耗爆炸. 由于4G模块一直处于数据模式, 如果需要发送一些功耗优化的AT指令, 比如飞行模式, 休眠. 那么会异常麻烦, 每次都需要切换回Command模式再发送指令. 如果和我一样是自带的GPS功能的4G模块, 那么功耗更是爆炸, 想要独立控制GPS电源必须通过AT指令, 来回切换失败风险很高.


参考资料


在以下平台分享此文章: