日期: 2026年1月27日

  • 【ESP32】ESP32S3通过USB+WIFI透传串口GPS信息并自动切换波特率

    基于微雪ESP32-S3-Touch-LCD-1.47,基于ESP-IDF 5.5.x,该代码目的适配ATGM和UBLOX部分GPS模块,可以热切换115200、38400波特率,实测115200波特率下可以实现5Hz下的NMEA语句持续平稳透传,10Hz下不太稳定。

    #include <stdio.h>
    #include <string.h>
    #include <stdbool.h>
    
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/semphr.h"
    #include "freertos/ringbuf.h"
    
    #include "driver/uart.h"
    #include "driver/usb_serial_jtag.h"
    
    #include "esp_log.h"
    #include "esp_wifi.h"
    #include "esp_event.h"
    #include "nvs_flash.h"
    #include "lwip/sockets.h"
    #include "esp_timer.h"
    
    /* ================== 配置常量 ================== */
    #define TAG                "GPS_BRIDGE"
    
    // 物理串口
    #define GPS_UART_NUM       UART_NUM_1
    #define GPS_TX_PIN         43
    #define GPS_RX_PIN         44
    
    // 缓存大小
    #define UART_BUF_SIZE      2048
    #define RINGBUF_SIZE       (16 * 1024)
    
    // 网络配置
    #define TCP_PORT           8080
    #define WIFI_SSID          "ESP32S3_GPS"
    #define WIFI_PASS          "12345678"
    
    // GPS参数
    static const int baud_list[] = {115200, 38400};
    #define BAUD_NUM            (sizeof(baud_list) / sizeof(baud_list[0]))
    #define DETECT_TIMEOUT_MS   1500  // 每个波特率停留尝试时间
    #define LOSS_TIMEOUT_SEC    5     // 5秒无NMEA数据则判定丢失
    
    /* ================== 全局句柄 ================== */
    static RingbufHandle_t uart_rb;
    static SemaphoreHandle_t sock_mutex;
    static SemaphoreHandle_t uart_mutex;
    
    static int  client_sock = -1;
    static int  current_baud_idx = 0;
    static bool baud_locked = false;
    
    /* ================== 工具函数 ================== */
    
    // 安全地重新初始化 UART
    static void uart_reinit(int baudrate)
    {
        xSemaphoreTake(uart_mutex, portMAX_DELAY);
        
        if (uart_is_driver_installed(GPS_UART_NUM)) {
            uart_driver_delete(GPS_UART_NUM);
        }
    
        uart_config_t cfg = {
            .baud_rate = baudrate,
            .data_bits = UART_DATA_8_BITS,
            .parity    = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
            .source_clk = UART_SCLK_DEFAULT,
        };
    
        // 安装驱动:缓冲区设为 UART_BUF_SIZE 的 2 倍
        uart_driver_install(GPS_UART_NUM, UART_BUF_SIZE * 2, 0, 0, NULL, 0);
        uart_param_config(GPS_UART_NUM, &cfg);
        uart_set_pin(GPS_UART_NUM, GPS_TX_PIN, GPS_RX_PIN, -1, -1);
    
        ESP_LOGW(TAG, "UART re-init: %d baud, scanning for GPS...", baudrate);
        
        xSemaphoreGive(uart_mutex);
    }
    
    // 协议特征检测
    static bool is_valid_nmea(const uint8_t *buf, int len)
    {
        if (len < 6) return false;
        // NMEA 标准报文必须以 $ 开头
        if (buf[0] != '$') return false;
    
        // 检查常见的 NMEA 类型标识符
        if (memmem(buf, len, "GGA", 3) || 
            memmem(buf, len, "RMC", 3) || 
            memmem(buf, len, "GSA", 3) ||
            memmem(buf, len, "GSV", 3)) {
            return true;
        }
        return false;
    }
    
    /* ================== 核心任务 ================== */
    
    /**
     * 任务1: UART 接收与波特率自动匹配
     * 优先级: 高 (10)
     */
    static void gps_rx_task(void *arg)
    {
        static uint8_t rx_buf[UART_BUF_SIZE];
        int64_t last_baud_switch = 0;
        int64_t last_valid_data  = 0;
    
        uart_reinit(baud_list[current_baud_idx]);
        last_baud_switch = esp_timer_get_time();
    
        while (1) {
            // 使用锁保护 UART 读取过程
            xSemaphoreTake(uart_mutex, portMAX_DELAY);
            int len = uart_read_bytes(GPS_UART_NUM, rx_buf, sizeof(rx_buf), pdMS_TO_TICKS(20));
            xSemaphoreGive(uart_mutex);
    
            int64_t now = esp_timer_get_time();
    
            if (len > 0) {
                if (is_valid_nmea(rx_buf, len)) {
                    last_valid_data = now;
                    if (!baud_locked) {
                        baud_locked = true;
                        ESP_LOGI(TAG, ">>> GPS Locked @ %d baud <<<", baud_list[current_baud_idx]);
                    }
                }
                
                // 只有锁定了正确的波特率才转发数据,过滤扫描时的乱码
                if (baud_locked) {
                    xRingbufferSend(uart_rb, rx_buf, len, 0);
                }
            }
    
            /* 状态机切换逻辑 */
            if (!baud_locked) {
                // 未锁定时:超时轮换波特率
                if ((now - last_baud_switch) / 1000 > DETECT_TIMEOUT_MS) {
                    current_baud_idx = (current_baud_idx + 1) % BAUD_NUM;
                    uart_reinit(baud_list[current_baud_idx]);
                    last_baud_switch = now;
                }
            } else {
                // 已锁定时:长时间无有效 NMEA 信号判定为丢失
                if ((now - last_valid_data) / 1000000 > LOSS_TIMEOUT_SEC) {
                    ESP_LOGE(TAG, "GPS signal lost, restarting scan...");
                    baud_locked = false;
                    last_baud_switch = now;
                }
            }
        }
    }
    
    /**
     * 任务2: 数据多路分发 (UART -> USB & WiFi)
     * 优先级: 中 (6)
     */
    static void tx_dispatcher_task(void *arg)
    {
        size_t len;
        uint8_t *data;
    
        while (1) {
            // 从环形缓冲区提取数据,最大等待
            data = (uint8_t *)xRingbufferReceive(uart_rb, &len, portMAX_DELAY);
            if (!data) continue;
    
            // 1. 发送到物理 USB (CDC)
            if (usb_serial_jtag_is_connected()) {
                usb_serial_jtag_write_bytes(data, len, 0);
            }
    
            // 2. 发送到 TCP 客户端
            if (xSemaphoreTake(sock_mutex, 0) == pdTRUE) {
                if (client_sock != -1) {
                    // 使用非阻塞发送,防止 WiFi 拥塞卡住串口读写
                    int sent = send(client_sock, data, len, MSG_DONTWAIT);
                    if (sent < 0 && (errno != EAGAIN && errno != EWOULDBLOCK)) {
                        ESP_LOGW(TAG, "TCP send error, client might be dead");
                    }
                }
                xSemaphoreGive(sock_mutex);
            }
    
            vRingbufferReturnItem(uart_rb, data);
        }
    }
    
    /**
     * 任务3: 反向路径 USB -> UART
     */
    static void usb_rx_task(void *arg)
    {
        static uint8_t buf[512];
        while (1) {
            int len = usb_serial_jtag_read_bytes(buf, sizeof(buf), pdMS_TO_TICKS(10));
            if (len > 0 && baud_locked) {
                xSemaphoreTake(uart_mutex, portMAX_DELAY);
                uart_write_bytes(GPS_UART_NUM, (char *)buf, len);
                xSemaphoreGive(uart_mutex);
            }
        }
    }
    
    /**
     * 任务4: TCP 服务端 (WiFi -> UART)
     */
    static void tcp_server_task(void *arg)
    {
        struct sockaddr_in addr = {
            .sin_family = AF_INET,
            .sin_port = htons(TCP_PORT),
            .sin_addr.s_addr = htonl(INADDR_ANY)
        };
    
        int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
        int opt = 1;
        setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr));
        listen(listen_sock, 1);
    
        static uint8_t buf[512];
        while (1) {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sock = accept(listen_sock, (struct sockaddr *)&client, &len);
            if (sock < 0) continue;
    
            xSemaphoreTake(sock_mutex, portMAX_DELAY);
            if (client_sock != -1) close(client_sock);
            client_sock = sock;
            xSemaphoreGive(sock_mutex);
    
            ESP_LOGI(TAG, "New TCP client connected");
    
            while (1) {
                int r = recv(sock, buf, sizeof(buf), 0);
                if (r <= 0) break;
    
                if (baud_locked) {
                    xSemaphoreTake(uart_mutex, portMAX_DELAY);
                    uart_write_bytes(GPS_UART_NUM, (char *)buf, r);
                    xSemaphoreGive(uart_mutex);
                }
            }
    
            ESP_LOGI(TAG, "TCP client disconnected");
            xSemaphoreTake(sock_mutex, portMAX_DELAY);
            if (client_sock == sock) client_sock = -1;
            close(sock);
            xSemaphoreGive(sock_mutex);
        }
    }
    
    /* ================== 系统初始化 ================== */
    
    static void wifi_init_softap(void)
    {
        ESP_ERROR_CHECK(esp_netif_init());
        ESP_ERROR_CHECK(esp_event_loop_create_default());
        esp_netif_create_default_wifi_ap();
    
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
        wifi_config_t ap_cfg = {
            .ap = {
                .ssid = WIFI_SSID,
                .ssid_len = strlen(WIFI_SSID),
                .password = WIFI_PASS,
                .channel = 1,
                .max_connection = 4,
                .authmode = WIFI_AUTH_WPA2_PSK
            }
        };
    
        ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_cfg));
        ESP_ERROR_CHECK(esp_wifi_start());
    }
    
    void app_main(void)
    {
        // 1. 初始化存储
        esp_err_t ret = nvs_flash_init();
        if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
            ESP_ERROR_CHECK(nvs_flash_erase());
            ret = nvs_flash_init();
        }
        ESP_ERROR_CHECK(ret);
    
        // 2. 初始化同步组件与缓冲区
        uart_rb    = xRingbufferCreate(RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF);
        sock_mutex = xSemaphoreCreateMutex();
        uart_mutex = xSemaphoreCreateMutex();
    
        // 3. 初始化 USB 驱动
        usb_serial_jtag_driver_config_t usb_cfg = {
            .rx_buffer_size = 2048,
            .tx_buffer_size = 2048
        };
        usb_serial_jtag_driver_install(&usb_cfg);
    
        // 4. 初始化 WiFi
        wifi_init_softap();
    
        // 5. 创建任务集群
        // 核心 0 处理实时性最强的串口 IO
        xTaskCreatePinnedToCore(gps_rx_task,        "gps_rx",  4096, NULL, 10, NULL, 0);
        xTaskCreatePinnedToCore(usb_rx_task,        "usb_rx",  4096, NULL, 5,  NULL, 0);
    
        // 核心 1 处理数据分发和网络服务
        xTaskCreatePinnedToCore(tx_dispatcher_task, "tx_dis",  4096, NULL, 6,  NULL, 1);
        xTaskCreatePinnedToCore(tcp_server_task,    "tcp_srv", 4096, NULL, 4,  NULL, 1);
    
        ESP_LOGI(TAG, "Bridge System Started. Use TCP 192.168.4.1:8080 or USB JTAG Serial.");
    }