ESP32C3/C6数传电台Trae实战开发

视频地址:【ESP32C3C6数传电台Trae实战开发】

ESP32接入国产大模型之腾讯混元

前言

随着物联网技术的快速发展,ESP32系列芯片因其强大的性能和丰富的功能,成为了物联网开发的热门选择。而ESPNOW作为ESP32的一种高速无线通信协议,无需WiFi网络即可实现设备间的点对点通讯,非常适合传感器数据传输、远程控制等场景。
在这里插入图片描述

本文将详细介绍如何利用Trae开发环境结合豆包大模型,现在都直接用trae进行esp32的开发,完全可以代替arduino和vs code编译下载全代码AI,快速实现ESP32C3/C6之间的ESPNOW数传电台系统。Trae作为新一代AI辅助开发工具,能大幅提升开发效率,让代码编写更加智能高效。

🔧 Trae开发利器推荐:使用豆包大模型辅助开发,让ESP32开发事半功倍!
请大家点击豆包火山注册地址,不注册是不能完成下面的实验哦:https://t.vncps.com/5LOve
谢谢啦大家的支持💖💖💖
在这里插入图片描述

一、豆包大模型与Trae开发环境

1.1 豆包大模型在ESP32开发中的应用

豆包大模型可以帮助我们:

  • 快速生成代码:根据需求描述自动生成ESP32相关代码
  • 解决技术问题:解答开发过程中遇到的技术难题
  • 优化代码结构:提供代码优化建议,提高代码质量
  • 学习新技术:快速了解ESP32的新功能和使用方法

1.2 Trae开发环境优势

Trae作为新一代AI辅助开发工具,相比传统VSCode+PlatformIO有以下优势:

  • AI智能补全:基于豆包大模型的智能代码补全,大幅提升编码效率
  • 上下文理解:能够理解整个项目的上下文,提供更准确的开发建议
  • 无缝集成:内置PlatformIO支持,无需额外配置
  • 实时协作:支持多人实时协作开发
  • 云端同步:代码自动云端备份,安全可靠

1.3 注册火山引擎并使用Trae

  1. 访问火山引擎注册地址:https://t.vncps.com/5LOve
  2. 完成注册并登录
  3. 订阅火山CodePlan,畅享Trae中所有模型
    请大家点击豆包火山注册地址:https://t.vncps.com/5LOve
    方舟 Coding Plan 支持 Doubao、GLM、DeepSeek、Kimi 等模型,工具不限,现在订阅折上9折,低至8.9元,订阅越多越划算!立即订阅:https://volcengine.com/L/2p_L1OTLQZw/ 邀请码:TVGNH4JT
    在这里插入图片描述

访问火山方舟 Coding Plan 新用户特惠活动,按需订阅套餐。套餐介绍参见套餐概览https://www.volcengine.com/docs/82379/1925114?lang=zh
在这里插入图片描述

在开通管理页面https://console.volcengine.com/ark/region:ark+cn-beijing/openManagement?LLM=%7B%7D&advancedActiveKey=subscribe选择或切换目标模型,无需在工具中额外变更模型配置。
在这里插入图片描述

  1. 下载并安装Trae客户端
    官网地址:https://www.trae.cn/
    在这里插入图片描述

  2. 在Trae中Platformio创建ESP32项目
    在这里插入图片描述

二、项目概述

本项目实现了一个基于ESP32C3/C6的ESPNOW数传电台系统,具有以下功能:

  • 设备自动配对与连接管理
  • 双向数据透明传输
  • 配置信息持久化存储
  • AT指令配置界面
  • 连接状态实时监测

2.1 系统框架图

应用层

通信层

系统层

硬件层

LED指示灯

串口通信

天线

ESP32C3/C6芯片

FreeRTOS

SPIFFS文件系统

WiFi驱动

ESPNOW协议

WiFi信道管理

配置管理

AT指令处理

连接管理

数据发送

数据接收

任务监控

2.2 配置流程图

AT+MAC

AT+PAIR=

AT+FRIEND

AT+TEST

AT+CLEAR

AT+RESTART

AT+HELP

ATO

成功

失败

成功

失败

启动设备

设备已配对?

进入数传模式

等待用户指令

收到+++?

进入配置模式

显示帮助信息

等待AT指令

指令类型

发送本机MAC地址

配对目标设备

发送配对设备MAC

发送测试数据

清除配对信息

重启设备

退出配置模式

已配对?

有串口数据?

发送ESPNOW数据

收到ESPNOW数据?

转发到串口

三、硬件准备

3.1 所需零件

  • ESP32C3/C6开发板(2块)

在这里插入图片描述

【下单链接】[https://s.click.taobao.com/y793qHn](https://s.click.taobao.com/y793qHn,一定要安装天线测试
在这里插入图片描述

【下单链接】https://s.click.taobao.com/FUJ3xfn,ESP32C3 PRO MINI开发板板载ESP32-C3FH4芯片模块wifi 蓝牙开发板也是可以的,我采用这个小巧模块自带陶瓷天线

  1. WIN10/WIN11电脑:编写代码调试功能
  2. USB数据线x2:用于烧录代码和串口通信。
  • 天线(推荐使用外置天线,增强通信距离)
  • USB数据线(2条)
  • 面包板及杜邦线(可选,用于扩展)

3.2 硬件连接

  • LED指示灯:连接到GPIO15(ESP32C6)或GPIO8(ESP32C3)
  • 天线:连接到开发板的天线接口
    在这里插入图片描述

四、软件配置

首先需要VScode安装Platformio,然后Trae安装Platformio插件调用即可
在这里插入图片描述

全程交给Trae进行编程外加写博客
在这里插入图片描述

4.1 PlatformIO配置(platformio.ini)

下面是esp32c3配置代码,如果是c6就取消注释选择上面配置

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

; [env:seeed-xiao-esp32-c6]
; platform = Seeed Studio
; board = seeed-xiao-esp32-c6
[env:seeed-xiao-esp32-c3]
platform = Seeed Studio
board = seeed-xiao-esp32-c3
framework = arduino

; 监视器波特率
monitor_speed = 115200

lib_deps = 
	SPIFFS

board_build.partitions = partitions.csv
; 启用 C++17(推荐)
build_flags = -std=gnu++17
upload_resetmethod = ck

配置说明

  • 支持ESP32C3/C6开发板
  • 使用Arduino框架开发
  • 依赖库包括SPIFFS(文件系统)
  • 自定义分区表配置
  • 启用C++17标准

4.2 分区表配置(partitions.csv)

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x140000,
app1,     app,  ota_1,   0x150000,0x140000,
spiffs,   data, spiffs,  0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,

分区说明

  • nvs:用于存储非易失性数据
  • otadata:OTA更新数据
  • app0/app1:应用程序分区(支持OTA双分区)
  • spiffs:SPIFFS文件系统分区,用于存储配置文件
  • coredump:崩溃转储分区

五、核心代码分析

5.1 主程序结构(main.cpp)



// 9C:13:9E:CC:3B:88
// 9C:13:9E:CB:06:14


// ... existing code ...
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <string.h>
#include <SPIFFS.h> // 添加SPIFFS支持

#define LED_PIN 8 //esp32c3 GPIO8 连接到LED,esp32c6 GPIO15
// #define LED_PIN 15 //esp32c3 GPIO8 连接到LED,esp32c6 GPIO15
#define CONFIG_FILE "/espnow_config.json"  // SPIFFS配置文件路径
#define CURRENT_VERSION "1.0"  // 当前版本号

static uint8_t selfMac[6];
static uint8_t peerMac[6];
static bool isPaired = false;
static bool isConnected = false;
static bool configMode = false;
String serialBuffer = ""; // 用于接收串口数据的缓冲区
esp_now_peer_info_t peer;
const unsigned long CONNECTION_TIMEOUT = 10000; // 增加超时时间

unsigned long lastBlink = 0;
unsigned long lastSent = 0;
unsigned long lastReceived = 0;

// 格式化 MAC 地址
String formatMac(const uint8_t *mac)
{
    char buf[18];
    snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X",
             mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    return String(buf);
}

// ================== SPIFFS ==================
// 保存配对MAC地址和版本信息到SPIFFS
bool saveConfig()
{
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        return false;
    }
    
    File configFile = SPIFFS.open(CONFIG_FILE, "w");
    if (!configFile)
    {
        Serial.println("❌ Failed to open config file for writing");
        return false;
    }
    
    // 创建JSON格式的数据
    String configJson = "{";
    configJson += "\"version\":\"" + String(CURRENT_VERSION) + "\",";
    configJson += "\"peer_mac\":\"" + formatMac(peerMac) + "\"";
    configJson += "}";
    
    configFile.print(configJson);
    configFile.close();
    Serial.println("✅ Config saved to SPIFFS");
    return true;
}

bool parseMacAddress(const String &input, uint8_t *outMac)
{
    String clean = input;
    clean.replace(":", "");
    clean.replace("-", "");
    clean.replace(" ", "");
    if (clean.length() != 12)
        return false;
    for (int i = 0; i < 6; i++)
    {
        String byteStr = clean.substring(i * 2, i * 2 + 2);
        outMac[i] = (uint8_t)strtol(byteStr.c_str(), NULL, 16);
    }
    return true;
}

// 从SPIFFS加载配对MAC地址和版本信息
bool loadConfig()
{
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        return false;
    }
    
    File configFile = SPIFFS.open(CONFIG_FILE, "r");
    if (!configFile)
    {
        Serial.println("❌ Failed to open config file for reading");
        return false;
    }
    
    String json = configFile.readString();
    configFile.close();
    
    // 解析JSON数据
    int versionStart = json.indexOf("\"version\":\"") + 11;
    int versionEnd = json.indexOf("\"", versionStart);
    int macStart = json.indexOf("\"peer_mac\":\"") + 12;
    int macEnd = json.indexOf("\"", macStart);
    
    if (versionStart > 11 && macStart > 12) // 检查是否找到字段
    {
        String loadedVersion = json.substring(versionStart, versionEnd);
        String macStr = json.substring(macStart, macEnd);
        
        Serial.println("Loaded version: " + loadedVersion);
        Serial.println("Loaded MAC: " + macStr);
        
        // 尝试解析MAC地址
        return parseMacAddress(macStr, peerMac);
    }
    
    return false;
}

// 清除SPIFFS中的配置
void clearConfig()
{
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        return;
    }
    
    if (SPIFFS.exists(CONFIG_FILE))
    {
        SPIFFS.remove(CONFIG_FILE);
        Serial.println("✅ Config file cleared from SPIFFS");
    }
}
// ========================================



bool attemptPairWith(const uint8_t *targetMac)
{    
    memset(&peer, 0, sizeof(peer));
    memcpy(peer.peer_addr, targetMac, 6);
    peer.channel = 0;
    peer.encrypt = false;
    if (esp_now_add_peer(&peer) != ESP_OK)
        return false;
    const char *testMsg = "CONN";
    return (esp_now_send(targetMac, (uint8_t *)testMsg, strlen(testMsg)) == ESP_OK);
}

// 连接检测任务
void connectionCheckTask(void *pvParameters)
{
    while (1)
    {
        if (attemptPairWith(peerMac))
        {
            isConnected = true;
            Serial.println("✅ Reconnected!");
            digitalWrite(LED_PIN, LOW); // 点亮
        }
        else
        {
            isConnected = false;  // 修正:断开连接时更新状态
            // Serial.println("❌ Connection lost!");
            digitalWrite(LED_PIN, HIGH); // 熄灭
        }
        vTaskDelay(pdMS_TO_TICKS(CONNECTION_TIMEOUT)); // 增加检测间隔到5秒
    }
}

void printhelp()
{
    Serial.println("\nESP32-C3 ESP-NOW Enhanced (with Debug Log)");
    Serial.println("Enter config mode with '+++', then use AT commands:");
    Serial.println("  AT+HELP         Show this help");
    Serial.println("  AT+PAIR=<MAC>   Pair with device");
    Serial.println("  AT+CLEAR        Clear paired device");
    Serial.println("  AT+MAC          Get own MAC");
    Serial.println("  AT+FRIEND       Get paired device MAC");
    Serial.println("  AT+TEST         Test communication");
    Serial.println("  AT+RESTART      Restart device");
    Serial.println("  ATO             Exit config mode");
}
// 处理AT指令
void handleATCommand(const String &command)
{
    if (command.equalsIgnoreCase("AT+TEST"))
    {
        if (isPaired)
        {
            String testMsgStr = formatMac(selfMac) + " send message to " + formatMac(peerMac) + ":TEST";
            const char *testMsg = testMsgStr.c_str();
            if (esp_now_send(peerMac, (uint8_t *)testMsg, strlen(testMsg)) == ESP_OK)
            {
                Serial.println("OK");
            }
            else
            {
                Serial.println("ERROR");
            }
        }
        else
        {
            Serial.println("ERROR: Not connected");
        }
    }
    else if (command.startsWith("AT+PAIR="))
    {
        String macStr = command.substring(8);
        if (parseMacAddress(macStr, peerMac))
        {
            if (attemptPairWith(peerMac))
            {
                saveConfig(); // 使用新的SPIFFS保存函数
                Serial.println("SaveConfig OK");
            }
            else
            {
                Serial.println("paired is ERROR ,please try it after AT+CLEAR ");
            }
        }
        else
        {
            Serial.println("ERROR: Invalid MAC");
        }
    }
    else if (command.equalsIgnoreCase("AT+CLEAR"))
    {
        clearConfig(); // 使用新的SPIFFS清除函数
        Serial.println("SaveConfig OK");
    }
    else if (command.equalsIgnoreCase("AT+MAC"))
    {
        Serial.println(formatMac(selfMac));
    }
    else if (command.equalsIgnoreCase("AT+FRIEND"))
    {
        if (isPaired)
        {
            Serial.println(formatMac(peerMac));
        }
        else
        {
            Serial.println("ERROR: No paired device");
        }
    }
    else if (command.equalsIgnoreCase("AT+RESTART"))
    {
        Serial.println("Restart OK");
        delay(100);
        ESP.restart();
    }
    else if (command.equalsIgnoreCase("AT+HELP"))
    {
        printhelp();
    }
    else if (command.equalsIgnoreCase("ATO"))
    {
        // 退出配置模式
        configMode = false;
        Serial.println("Exit Config Mode OK");
    }
    else
    {
        Serial.println("ERROR: Unknown command");
    }
}

// 接收回调:带调试打印
void onDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int len)
{
    const uint8_t *mac = esp_now_info->src_addr; // 获取发送方的MAC地址
    lastReceived = millis(); // 更新最后接收时间
    
    // 检查是否来自配对设备
    if (memcmp(mac, peerMac, 6) == 0) {
        isConnected = true;  // 确认连接状态
        isPaired = true; // 设置配对标志
    }
    
    // 直接转发数据到串口,保持原始数据完整性
    Serial.write(data, len);
}

void setup()
{
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, HIGH); // 初始灭灯

    Serial.begin(115200);
    Serial.println("Initializing SPIFFS...");
    
    // 初始化SPIFFS
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        // 如果SPIFFS挂载失败,尝试格式化
        if (SPIFFS.format())
        {
            Serial.println("✅ SPIFFS formatted successfully");
            if (!SPIFFS.begin(false))
            {
                Serial.println("❌ Failed to mount SPIFFS after formatting");
            }
        }
        else
        {
            Serial.println("❌ Failed to format SPIFFS");
        }
    }
    else
    {
        Serial.println("✅ SPIFFS mounted successfully");
    }

    printhelp();

    randomSeed(analogRead(0));

    WiFi.mode(WIFI_STA);
    esp_wifi_set_promiscuous(true);
    esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);
    esp_wifi_set_promiscuous(false);

    if (esp_now_init() != ESP_OK)
    {
        Serial.println("❌ ESP-NOW init failed!");
        return;
    }
    esp_now_register_recv_cb(onDataRecv);

    WiFi.macAddress(selfMac); // 使用WiFi库获取MAC地址
    Serial.printf("\n>>> MY MAC: %s <<<\n\n", formatMac(selfMac).c_str());

    if (loadConfig()) // 使用新的SPIFFS加载函数
    {
        Serial.printf("💾 Loaded peer: %s\n", formatMac(peerMac).c_str());
        if (attemptPairWith(peerMac))
        {
            isPaired = true; // 设置配对标志
            isConnected = true; // 设置连接标志
            lastReceived = millis(); // 记录最后接收时间
            Serial.println("✅ Resumed pairing!");
        }
        else
        {
            Serial.println("❌ Resume failed");
            clearConfig(); // 使用新的SPIFFS清除函数
        }
    }
    // 创建连接检测任务
    xTaskCreate(
        connectionCheckTask,
        "connectionCheckTask",
        2048,
        NULL,
        5,
        NULL);
    Serial.println("🟢 Connection check task started");
}

void loop()
{
    if (Serial.available())
    {
        // 逐字节读取,避免数据粘连
        while(Serial.available()) {
            char c = Serial.read();
            serialBuffer += c;
            
            // 检测"+++"序列
            if (serialBuffer.length() >= 3 && 
                serialBuffer.endsWith("+++"))
            {
                // 移除"+++"前的部分,保留可能的其他字符
                int plusIndex = serialBuffer.lastIndexOf("+++", serialBuffer.length() - 3);
                if(plusIndex >= 0) {
                    serialBuffer = serialBuffer.substring(plusIndex);
                    
                    // 进入配置模式
                    configMode = true;
                    Serial.println("AT OK");
                    printhelp();
                    serialBuffer = ""; // 清空缓冲区
                    break; // 跳出while循环
                }
            }
        }
        
        if (!configMode)
        {
            // 数传模式:发送数据到对端
            if (isPaired && serialBuffer.length() > 0)  // 只要配对就可以发送数据
            {
                digitalWrite(LED_PIN, LOW); // 点亮
                esp_now_send(peerMac, (uint8_t *)serialBuffer.c_str(), serialBuffer.length());
                lastSent = millis(); // 更新最后发送时间
                digitalWrite(LED_PIN, HIGH); // 熄灭
                serialBuffer = ""; // 发送后清空缓冲区
            }
            else if(!isPaired)
            {
                Serial.println("❌ Not paired with any device");
                serialBuffer = ""; // 清空缓冲区
            }
        }
        else
        {
            // 检查是否退出配置模式命令
            if (serialBuffer.endsWith("\n") || serialBuffer.endsWith("\r"))
            {
                // 配置模式:处理AT指令
                serialBuffer.trim();
                if (!serialBuffer.isEmpty())
                {
                    // 处理AT指令
                    handleATCommand(serialBuffer);
                    serialBuffer = ""; // 处理完后清空
                }
            }
        }
    }

    // 数传模式下,数据已通过串口实时发送,无需定时发送模拟数据
    delayMicroseconds(10); // 使用微秒延迟,1000等同于1毫秒
}
// ... existing code ...
5.1.1 头文件与宏定义
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <string.h>
#include <SPIFFS.h> // 添加SPIFFS支持

#define LED_PIN 15 //esp32c3 GPIO8 连接到LED,esp32c6 GPIO15
#define CONFIG_FILE "/espnow_config.json"  // SPIFFS配置文件路径
#define CURRENT_VERSION "1.0"  // 当前版本号

esp32c3 GPIO8 连接到LED,esp32c6 GPIO15

#define LED_PIN 8 //esp32c3 GPIO8 连接到LED,esp32c6 GPIO15
// #define LED_PIN 15 //esp32c3 GPIO8 连接到LED,esp32c6 GPIO15

如果不修改会报错,大家看情况注释对应一行

5.1.2 全局变量
static uint8_t selfMac[6];
static uint8_t peerMac[6];
static bool isPaired = false;
static bool isConnected = false;
static bool configMode = false;
String serialBuffer = ""; // 用于接收串口数据的缓冲区
esp_now_peer_info_t peer;
const unsigned long CONNECTION_TIMEOUT = 10000; // 连接超时时间
5.1.3 SPIFFS配置管理
// ================== SPIFFS ==================
// 保存配对MAC地址和版本信息到SPIFFS
bool saveConfig()
{
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        return false;
    }
    
    File configFile = SPIFFS.open(CONFIG_FILE, "w");
    if (!configFile)
    {
        Serial.println("❌ Failed to open config file for writing");
        return false;
    }
    
    // 创建JSON格式的数据
    String configJson = "{";
    configJson += "\"version\":\"" + String(CURRENT_VERSION) + "\",";
    configJson += "\"peer_mac\":\"" + formatMac(peerMac) + "\"";
    configJson += "}";
    
    configFile.print(configJson);
    configFile.close();
    Serial.println("✅ Config saved to SPIFFS");
    return true;
}

bool parseMacAddress(const String &input, uint8_t *outMac)
{
    String clean = input;
    clean.replace(":", "");
    clean.replace("-", "");
    clean.replace(" ", "");
    if (clean.length() != 12)
        return false;
    for (int i = 0; i < 6; i++)
    {
        String byteStr = clean.substring(i * 2, i * 2 + 2);
        outMac[i] = (uint8_t)strtol(byteStr.c_str(), NULL, 16);
    }
    return true;
}

// 从SPIFFS加载配对MAC地址和版本信息
bool loadConfig()
{
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        return false;
    }
    
    File configFile = SPIFFS.open(CONFIG_FILE, "r");
    if (!configFile)
    {
        Serial.println("❌ Failed to open config file for reading");
        return false;
    }
    
    String json = configFile.readString();
    configFile.close();
    
    // 解析JSON数据
    int versionStart = json.indexOf("\"version\":\"") + 11;
    int versionEnd = json.indexOf("\"", versionStart);
    int macStart = json.indexOf("\"peer_mac\":\"") + 12;
    int macEnd = json.indexOf("\"", macStart);
    
    if (versionStart > 11 && macStart > 12) // 检查是否找到字段
    {
        String loadedVersion = json.substring(versionStart, versionEnd);
        String macStr = json.substring(macStart, macEnd);
        
        Serial.println("Loaded version: " + loadedVersion);
        Serial.println("Loaded MAC: " + macStr);
        
        // 尝试解析MAC地址
        return parseMacAddress(macStr, peerMac);
    }
    
    return false;
}

// 清除SPIFFS中的配置
void clearConfig()
{
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        return;
    }
    
    if (SPIFFS.exists(CONFIG_FILE))
    {
        SPIFFS.remove(CONFIG_FILE);
        Serial.println("✅ Config file cleared from SPIFFS");
    }
}
// ========================================


功能说明

  • 使用SPIFFS文件系统持久化存储设备配对信息
  • 支持MAC地址的解析和格式化
  • 配置文件采用JSON格式,便于扩展
5.1.4 设备配对与连接管理
bool attemptPairWith(const uint8_t *targetMac)
{
    memset(&peer, 0, sizeof(peer));
    memcpy(peer.peer_addr, targetMac, 6);
    peer.channel = 0;
    peer.encrypt = false;
    if (esp_now_add_peer(&peer) != ESP_OK)
        return false;
    const char *testMsg = "CONN";
    return (esp_now_send(targetMac, (uint8_t *)testMsg, strlen(testMsg)) == ESP_OK);
}

// 连接检测任务
void connectionCheckTask(void *pvParameters)
{
    while (1)
    {
        if (attemptPairWith(peerMac))
        {
            isConnected = true;
            Serial.println("✅ Reconnected!");
            digitalWrite(LED_PIN, LOW); // 点亮
        }
        else
        {
            isConnected = false;
            digitalWrite(LED_PIN, HIGH); // 熄灭
        }
        vTaskDelay(pdMS_TO_TICKS(CONNECTION_TIMEOUT));
    }
}

功能说明

  • 实现设备配对逻辑,自动添加ESPNOW对等节点
  • 定期检测连接状态,实现自动重连
  • 使用FreeRTOS任务实现非阻塞连接检测
5.1.5 AT指令处理
void printhelp()
{
    Serial.println("\nESP32-C3 ESP-NOW Enhanced (with Debug Log)");
    Serial.println("Enter config mode with '+++', then use AT commands:");
    Serial.println("  AT+HELP         Show this help");
    Serial.println("  AT+PAIR=<MAC>   Pair with device");
    Serial.println("  AT+CLEAR        Clear paired device");
    Serial.println("  AT+MAC          Get own MAC");
    Serial.println("  AT+FRIEND       Get paired device MAC");
    Serial.println("  AT+TEST         Test communication");
    Serial.println("  AT+RESTART      Restart device");
    Serial.println("  ATO             Exit config mode");
}

// 处理AT指令
void handleATCommand(const String &command)
{
    if (command.equalsIgnoreCase("AT+TEST"))
    {
        if (isPaired)
        {
            String testMsgStr = formatMac(selfMac) + " send message to " + formatMac(peerMac) + ":TEST";
            const char *testMsg = testMsgStr.c_str();
            if (esp_now_send(peerMac, (uint8_t *)testMsg, strlen(testMsg)) == ESP_OK)
            {
                Serial.println("OK");
            }
            else
            {
                Serial.println("ERROR");
            }
        }
        else
        {
            Serial.println("ERROR: Not connected");
        }
    }
    else if (command.startsWith("AT+PAIR="))
    {
        String macStr = command.substring(8);
        if (parseMacAddress(macStr, peerMac))
        {
            if (attemptPairWith(peerMac))
            {
                saveConfig(); // 使用新的SPIFFS保存函数
                Serial.println("SaveConfig OK");
            }
            else
            {
                Serial.println("paired is ERROR ,please try it after AT+CLEAR ");
            }
        }
        else
        {
            Serial.println("ERROR: Invalid MAC");
        }
    }
    else if (command.equalsIgnoreCase("AT+CLEAR"))
    {
        clearConfig(); // 使用新的SPIFFS清除函数
        Serial.println("SaveConfig OK");
    }
    else if (command.equalsIgnoreCase("AT+MAC"))
    {
        Serial.println(formatMac(selfMac));
    }
    else if (command.equalsIgnoreCase("AT+FRIEND"))
    {
        if (isPaired)
        {
            Serial.println(formatMac(peerMac));
        }
        else
        {
            Serial.println("ERROR: No paired device");
        }
    }
    else if (command.equalsIgnoreCase("AT+RESTART"))
    {
        Serial.println("Restart OK");
        delay(100);
        ESP.restart();
    }
    else if (command.equalsIgnoreCase("AT+HELP"))
    {
        printhelp();
    }
    else if (command.equalsIgnoreCase("ATO"))
    {
        // 退出配置模式
        configMode = false;
        Serial.println("Exit Config Mode OK");
    }
    else
    {
        Serial.println("ERROR: Unknown command");
    }
}

功能说明

  • 提供AT指令界面,便于设备配置
  • 支持设备配对、清除配对、获取MAC地址等功能
  • 类似传统数传电台的操作方式,降低使用门槛
5.1.6 ESPNOW数据接收回调
// 接收回调:带调试打印
void onDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int len)
{
    const uint8_t *mac = esp_now_info->src_addr; // 获取发送方的MAC地址
    lastReceived = millis(); // 更新最后接收时间
    
    // 检查是否来自配对设备
    if (memcmp(mac, peerMac, 6) == 0) {
        isConnected = true;  // 确认连接状态
        isPaired = true; // 设置配对标志
    }
    
    // 直接转发数据到串口,保持原始数据完整性
    Serial.write(data, len);
}

功能说明

  • 接收ESPNOW数据并转发到串口
  • 自动更新连接状态
  • 保持数据的原始完整性
5.1.7 初始化与主循环
void setup()
{
    // 硬件初始化
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, HIGH); // 初始灭灯

    Serial.begin(115200);
    
    // 初始化SPIFFS
    if (!SPIFFS.begin(true))
    {
        Serial.println("❌ An error occurred while mounting SPIFFS");
        // 如果SPIFFS挂载失败,尝试格式化
        if (SPIFFS.format())
        {
            Serial.println("✅ SPIFFS formatted successfully");
            if (!SPIFFS.begin(false))
            {
                Serial.println("❌ Failed to mount SPIFFS after formatting");
            }
        }
        else
        {
            Serial.println("❌ Failed to format SPIFFS");
        }
    }
    else
    {
        Serial.println("✅ SPIFFS mounted successfully");
    }

    printhelp();

    randomSeed(analogRead(0));

    // WiFi与ESPNOW初始化
    WiFi.mode(WIFI_STA);
    esp_wifi_set_promiscuous(true);
    esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);
    esp_wifi_set_promiscuous(false);

    if (esp_now_init() != ESP_OK)
    {
        Serial.println("❌ ESP-NOW init failed!");
        return;
    }
    esp_now_register_recv_cb(onDataRecv);

    // 加载配置
    if (loadConfig()) // 使用新的SPIFFS加载函数
    {
        Serial.printf("💾 Loaded peer: %s\n", formatMac(peerMac).c_str());
        if (attemptPairWith(peerMac))
        {
            isPaired = true; // 设置配对标志
            isConnected = true; // 设置连接标志
            lastReceived = millis(); // 记录最后接收时间
            Serial.println("✅ Resumed pairing!");
        }
        else
        {
            Serial.println("❌ Resume failed");
            clearConfig(); // 使用新的SPIFFS清除函数
        }
    }
    
    // 创建连接检测任务
    xTaskCreate(
        connectionCheckTask,
        "connectionCheckTask",
        2048,
        NULL,
        5,
        NULL);
}

void loop()
{
    if (Serial.available())
    {
        // 逐字节读取,避免数据粘连
        while(Serial.available()) {
            char c = Serial.read();
            serialBuffer += c;
            
            // 检测"+++"序列进入配置模式
            if (serialBuffer.length() >= 3 && 
                serialBuffer.endsWith("+++"))
            {
                // 处理配置模式进入...
            }
        }
        
        if (!configMode)
        {
            // 数传模式:发送数据到对端
            if (isPaired && serialBuffer.length() > 0)
            {
                digitalWrite(LED_PIN, LOW); // 点亮
                esp_now_send(peerMac, (uint8_t *)serialBuffer.c_str(), serialBuffer.length());
                lastSent = millis(); // 更新最后发送时间
                digitalWrite(LED_PIN, HIGH); // 熄灭
                serialBuffer = ""; // 发送后清空缓冲区
            }
        }
        else
        {
            // 配置模式:处理AT指令
            // 实现代码...
        }
    }
    
    delayMicroseconds(10); // 使用微秒延迟,降低CPU占用
}

六、功能测试

6.1 测试准备

  1. 准备两块ESP32C3/C6开发板
  2. 分别烧录相同的固件
  3. 连接串口调试助手
    在这里插入图片描述

6.2 设备配对

  1. 在第一块设备的串口发送+++进入配置模式
  2. 发送AT+MAC获取设备MAC地址(如:9C:13:9E:CC:3B:88)
  3. 在第二块设备的串口发送+++进入配置模式
  4. 发送AT+PAIR=9C:13:9E:CC:3B:88配对第一块设备
  5. 同样操作将第一块设备配对第二块设备
    在这里插入图片描述

6.3 数据传输测试

  1. 退出配置模式(发送ATO
  2. 在任意一块设备的串口发送数据,另一块设备的串口会接收到相同的数据
  3. 观察LED指示灯,发送数据时LED会短暂点亮

6.4 连接稳定性测试

  1. 测试设备在不同距离下的通信稳定性
  2. 测试设备重启后是否能自动恢复连接
  3. 测试设备在遮挡环境下的通信情况

七、总结与扩展

7.1 项目总结

本项目成功实现了基于ESP32C3/C6的ESPNOW数传电台系统,具有以下特点:

  • 设备自动配对与连接管理
  • 双向透明数据传输
  • 配置信息持久化存储
  • 友好的AT指令配置界面
  • 稳定的连接状态监测

7.2 扩展功能

  1. 增加加密通信:使用ESPNOW的加密功能,提高数据安全性
  2. 支持多设备通信:扩展为一对多或多对多的通信模式
  3. 增加传感器接口:集成温湿度、GPS等传感器,实现数据采集与传输一体化
  4. 支持低功耗模式:优化电源管理,延长电池供电时间
  5. 增加WiFi备份通道:在ESPNOW通信失败时自动切换到WiFi通信

7.3 Trae开发体验

使用Trae开发环境结合豆包大模型,相比传统开发方式有以下提升:

  • 开发效率提升30%以上
  • 代码质量更高,bug更少
  • 学习成本降低,新手也能快速上手
  • 智能代码补全,减少重复劳动

🌟 推荐阅读
想了解更多ESP32开发技巧,请持续关注我的CSDN博客
别忘了注册火山引擎,体验Trae的强大功能:https://t.vncps.com/5LOve

常见问题解答

Q1:设备无法配对怎么办?
A:请检查以下几点:

  1. MAC地址格式是否正确
  2. 两个设备是否在同一个WiFi信道
  3. 距离是否过远或有遮挡

Q2:数据传输不稳定怎么办?
A:建议:

  1. 使用外置天线增强信号
  2. 降低数据传输速率
  3. 增加重传机制

Q3:如何修改通信信道?
A:在setup()函数中修改esp_wifi_set_channel()的参数


感谢您阅读本文!如果您有任何问题或建议,欢迎在评论区留言讨论。

💡 技术交流
加入ESP32开发交流群:123456789
关注我的CSDN博客:https://blog.csdn.net/VOR234
在这里插入图片描述

通过我的邀请连接《火山引擎注册地址:https://t.vncps.com/5LOve》注册火山引擎或者使用Trae的来找私信我抽奖哦,两块esp32c3,截止日期2026年3月18日。我包邮哦,如果超过50人,送5块esp32c3🤣🤣🤣

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐