下面是一个较为完整的设计方案文档和参考代码示例,基于 ESP32-S3 使用 ESP-IDF,通过 PPP(Point-to-Point Protocol)与 Cat.1 4G 模块进行数据连接。当设备无 Wi-Fi 可用时,自动切换至通过 4G 模块上网,并可将此网络通过 ESP32-S3 的 Wi-Fi AP 功能对外分享,实现类似“随身路由”的功能。
1. 系统整体架构与设计思路
1.1 硬件组成与连接方式
- 主控芯片:ESP32-S3
- 网络模块:Cat.1 4G 模块(通常通过 USB 接口与 ESP32-S3 相连,4G 模块在 USB 上提供 CDC-ACM 通道,ESP32-S3 作为 USB Host)
- 电源设计:确保 ESP32-S3 和 Cat.1 模块的电源稳定,Cat.1 模块有峰值电流需求(典型值在1~2A范围),请根据模块规格进行电源设计。
- 其他外设:
- USB Type-C 或 Micro USB 接口,用于连接 Cat.1 模块
- 指示灯(LED)用于显示当前网络状态(例如 Wi-Fi 已连接/4G 已连接/无网络)
- 按键用于强制切换网络模式或进行重置
1.2 软件架构与模块划分
系统软件框图可分为以下层次:
-
ESP-IDF 底层驱动与框架
- USB Host 驱动(CDC-ACM驱动)
- PPPoS(PPP-over-Serial)协议栈
- TCP/IP 栈(LwIP)
- Wi-Fi STA/AP 驱动
-
网络管理模块
- 网络状态机管理:
- 首先尝试使用 Wi-Fi STA 连接已知路由器
- 若 Wi-Fi 不可用(如连接失败或无配置),则启动 4G PPP 链接
- 当 PPP 建立后,ESP32-S3 获取 IP 并启用 NAT,将 PPP 接口作为 WAN,对外通过 Wi-Fi AP 分享网络
- 网络状态机管理:
-
应用逻辑层
- 提供状态信息给用户(LED、日志)
- 配置管理(如 Wi-Fi SSID/密码存储)
- Web 管理页面(可选,用于配置 Wi-Fi 和查看状态)
1.3 工作流程
-
系统上电:
- 初始化 NVS、日志和基础硬件
- 初始化 Wi-Fi 和 PPP,但默认先尝试 Wi-Fi STA 模式连接既有 AP
-
尝试 Wi-Fi 连接:
- 若 Wi-Fi STA 成功接入互联网,则通过 Wi-Fi NAT 给 Wi-Fi AP 客户端提供网络。此时不激活 PPP。
- 若 Wi-Fi STA 超时或失败,则转入 PPP 模式。
-
PPP 模式建立:
- 启动 USB Host 驱动,识别并挂载4G 模块的 CDC 接口
- 启动 PPP 协议并拨号(通过AT命令,对应CDC端口发送AT指令建立PPP Session)
- 获取 IP 后,通过 Wi-Fi AP 对外提供网络
-
故障处理与切换:
- 若 PPP 链接断开,尝试重新拨号
- 若 Wi-Fi STA 配置变更或可用,切换回 Wi-Fi 模式
1.4 相关配置与注意事项
- NAT功能: ESP-IDF 自带的 LwIP 升级版本支持 SoftAP 下 NAT 功能。需要启用
CONFIG_LWIP_HOOK_IP4_ROUTE_SRC
、CONFIG_LWIP_L3_IF
、CONFIG_NAT
等相关选项(在menuconfig
中查看并启用)。 - USB Host驱动:ESP32-S3 内部有 USB OTG 外设,可在主机模式下使用。需要参考 ESP-IDF 官方 USB Host 示例。
- PPP配置:ESP-IDF 中提供 PPPoS 示例(即通过串口实现PPP),此处将串口层替换为 USB CDC。相当于在 LwIP 上层实现 PPPoS 但使用 USB CDC 读写函数。
2. 环境准备与配置
2.1 硬件准备
- ESP32-S3 开发板,确保 USB 引脚(D+、D-)可用作为 USB Host
- Cat.1 模块(如基于 SIM7600 系列或其它 Cat.1 模块),通过 USB 线连接到 ESP32-S3
- 外部电源模块,保证4G模块电流供应充足
2.2 软件准备
- ESP-IDF(建议使用最新稳定版本,如4.4.4或5.x)
- 在
menuconfig
中进行如下配置:- USB Host:启用 USB Host stack
Component config → ESP USB Host Library → USB Host
- PPP / LwIP:
Component config → LWIP → Enable PPP support
- 启用 NAT相关选项(如需)
- Wi-Fi:
- 启用 STA 和 AP 模式
- 日志级别:选择适当的日志级别方便调试
- USB Host:启用 USB Host stack
2.3 PPP 拨号准备
需要预先了解4G模块的AT命令和拨号指令(一般为:AT+CGDCONT=1,"IP","<APN>"
,ATD*99#
或根据模块厂商要求)。
3. 核心代码示例说明
下面代码基于 ESP-IDF 示例进行适当改写与整合,仅提供关键片段。实际工程中请分文件结构组织。
3.1 包含头文件与宏定义
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_mac.h"
#include "driver/uart.h"
#include "usb/usb_host.h"
#include "usb/cdc_acm_host.h"
#include "lwip/netif.h"
#include "lwip/pppapi.h"
#include "lwip/pppos.h"
#include "lwip/sio.h"
static const char *TAG = "4G_PPP_ROUTER";
3.2 USB CDC 初始化与 PPP 接口创建
步骤:
- 初始化 USB Host
- 等待 CDC 设备连接
- 打开 CDC 设备,获取读写句柄
static cdc_acm_dev_handle_t cdc_dev = NULL;
static int cdc_read_fd = -1;
static int cdc_write_fd = -1;
static void init_usb_cdc(void)
{
// 初始化 USB 主机库
esp_err_t ret = usb_host_install(NULL);
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "Waiting for CDC device...");
// 等待设备连接(可采用事件回调机制,这里简化为轮询)
while (1) {
size_t dev_count = 0;
cdc_acm_host_device_info_t *dev_list = NULL;
cdc_acm_host_device_list(&dev_list, &dev_count);
if (dev_count > 0) {
ESP_LOGI(TAG, "CDC device connected");
// 打开第一个 CDC 设备
ret = cdc_acm_host_open(dev_list[0].dev_handle, &cdc_dev);
if (ret == ESP_OK) {
free(dev_list);
break;
}
}
if (dev_list) free(dev_list);
vTaskDelay(pdMS_TO_TICKS(1000));
}
// 配置CDC波特率、line coding 等,根据模块要求配置
cdc_acm_line_coding_t line_coding = {
.dwDTERate = 115200,
.bCharFormat = CDC_ACM_1_STOP_BITS,
.bParityType = CDC_ACM_NO_PARITY,
.bDataBits = 8,
};
ret = cdc_acm_host_line_coding_set(cdc_dev, &line_coding);
ESP_ERROR_CHECK(ret);
// 开启数据通信
ret = cdc_acm_host_set_control_line_state(cdc_dev, true, true);
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "CDC ACM device is ready");
}
3.3 PPP 层接入
PPP 需要一个 SIO (Serial I/O) 接口函数,用于读写 CDC。我们可以实现 sio_read()
和 sio_write()
来包装 USB CDC 的读写操作。
// 因为PPP需要与底层串口类似的读写接口,这里用CDC的读写函数来模拟
sio_fd_t sio_open(u8_t devnum) {
// 此处不真正使用devnum,我们只支持一个设备
return (sio_fd_t) &cdc_dev;
}
u32_t sio_write(sio_fd_t fd, u8_t *data, u32_t len) {
size_t written = 0;
esp_err_t ret = cdc_acm_host_data_tx(cdc_dev, data, len, &written, 1000);
return (ret == ESP_OK) ? (u32_t)written : 0;
}
u32_t sio_read(sio_fd_t fd, u8_t *data, u32_t len) {
size_t readed = 0;
esp_err_t ret = cdc_acm_host_data_rx(cdc_dev, data, len, &readed, 1000);
return (ret == ESP_OK) ? (u32_t)readed : 0;
}
void sio_read_abort(sio_fd_t fd) {
// 此处如需要,可调用 cdc_acm_host_data_rx_cancel(cdc_dev);
}
3.4 PPP 会话建立流程
在 PPP 启动前,需要对4G模块发送AT指令设置APN,并发起拨号(有些PPP实现可自动发起拨号,也可以在 PPP层配置chap/pap认证)。这里以手动AT为例。
static ppp_pcb *ppp = NULL;
static esp_netif_t *ppp_netif = NULL;
static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) {
struct netif *pppif = ppp_netif(pcb);
switch (err_code) {
case PPPERR_NONE: {
ESP_LOGI(TAG, "PPP connected. IP: %s", ipaddr_ntoa(&pppif->ip_addr));
break;
}
// 其他错误处理逻辑省略...
}
}
static u32_t ppp_output_cb(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) {
return sio_write((sio_fd_t)ctx, data, len);
}
static void start_ppp_session(void)
{
sio_fd_t fd = sio_open(0);
// 对4G模块下发AT指令,设置APN和拨号。例如:
// AT+CGDCONT=1,"IP","<你的APN>"
// ATD*99#
// 这些AT指令发送后将进入PPP协商阶段。
char *setup_cmds[] = {
"AT\r\n",
"AT+CGDCONT=1,\"IP\",\"your_apn\"\r\n",
"ATD*99#\r\n" // 发起PPP拨号
};
for (int i = 0; i < sizeof(setup_cmds)/sizeof(setup_cmds[0]); i++) {
sio_write(fd, (uint8_t *)setup_cmds[i], strlen(setup_cmds[i]));
vTaskDelay(pdMS_TO_TICKS(500));
}
// 创建PPP
ppp = pppapi_new();
ppp_set_default(ppp);
ppp_set_usepeerdns(ppp, 1);
ppp_set_notify_phase_callback(ppp, NULL); // 可选
pppapi_set_status_callback(ppp, ppp_status_cb);
// 将输出回调设置为ppp_output_cb
pppapi_over_serial_open(ppp, ppp_output_cb, fd);
// 启动PPP会话
pppapi_open(ppp, 0);
}
3.5 Wi-Fi AP 与 NAT 配置
假设您希望设备在无 Wi-Fi STA 成功时自动建立Wi-Fi AP并通过PPP提供网络。这里以简单的 Wi-Fi AP 配置为例。
static esp_netif_t* wifi_ap_netif = NULL;
static esp_netif_t* wifi_sta_netif = NULL;
// 初始化Wi-Fi AP
static void init_wifi_ap(void)
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_ap_netif = esp_netif_create_default_wifi_ap();
wifi_sta_netif = esp_netif_create_default_wifi_sta();
wifi_config_t wifi_config = {
.ap = {
.ssid = "ESP32_4G_Router",
.ssid_len = strlen("ESP32_4G_Router"),
.password = "12345678",
.max_connection = 5,
.authmode = WIFI_AUTH_WPA2_PSK
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "Wi-Fi AP started: SSID=ESP32_4G_Router");
}
3.6 NAT 设置
在 PPP 接口获取到 IP 后,需要将该接口作为“外网口”,将 Wi-Fi AP 的网络流量转发到 PPP。ESP-IDF 某些版本支持 esp_netif_transmit()
回调实现NAT,或使用 LwIP NAT 功能。
参考 NAT 示例(ESP-IDF中有 NAT 示例,请根据您的版本查看 examples/protocols/lwip/lwip_nat
)。您需要在 menuconfig 中启用 NAT 后,对 netif 进行注册。例如:
// 假设ppp_netif与wifi_ap_netif已经创建
// 使用LWIP自带的NAT功能
#include "lwip/lwip_napt.h"
static void enable_nat(void)
{
// 启用NAT: 假设PPP接口是ppp0,AP接口是ap0
struct netif *ap_if = esp_netif_get_netif_impl(wifi_ap_netif);
struct netif *ppp_if = ppp_netif(ppp);
// Enable NAT on PPP interface
ip_napt_enable(ip4_addr_get_u32(&ppp_if->ip_addr), 1);
// 添加从AP转发到PPP的NAT规则(如果需要具体设置,请参考 NAT 示例)
}
在PPP成功连接回调中调用 enable_nat()
。
3.7 Wi-Fi STA 回退逻辑
在上电时先尝试 STA 连接,如果STA连接成功则不启动PPP,失败则启动PPP拨号。可参考如下伪代码:
static bool wifi_connected = false;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_connected = false;
// 尝试重连若超过一定次数则放弃,进入PPP模式
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
wifi_connected = true;
// 停止PPP,如果有在运行
if (ppp) {
pppapi_close(ppp, 0);
}
}
}
static void network_manager_task(void *arg)
{
// 先尝试STA模式
// 配置STA的SSID与密码(从NVS或编译时写死)
// 启动STA连接
// 等待一段时间如10s,如果wifi_connected仍为false,则启动PPP拨号
vTaskDelay(pdMS_TO_TICKS(10000));
if (!wifi_connected) {
ESP_LOGI(TAG, "No Wi-Fi connection, start PPP...");
init_usb_cdc();
start_ppp_session();
}
vTaskDelete(NULL);
}
3.8 工程目录结构示例
project
├─ main
│ ├─ CMakeLists.txt
│ ├─ main.c
│ ├─ ppp_manager.c
│ ├─ wifi_manager.c
│ └─ usb_cdc.c
└─ CMakeLists.txt
将相关代码分模块存放,main.c
里启动 network_manager_task
。
4. 测试与调试步骤
- 硬件接线完成,上电启动
- 串口监视输出日志,查看Wi-Fi STA是否能连接本地路由器
- 若Wi-Fi不可用,等待PPP启动日志出现
- 查看 PPP 拨号日志,成功后打印分配的IP信息
- 使用手机连接到ESP32-S3的Wi-Fi AP,检查是否能上网(PING某个公网地址)
5. 参考资料
以上是一个较为完整和详细的设计与代码参考示例。实际开发中可能需要根据 Cat.1 模块的具体AT指令、实际硬件环境、ESP-IDF版本特性进行相应调整和优化。