增强版ESP32C3无线485串口&电台实战开发

视频地址:【增强版ESP32C3无线串口&电台实战开发】

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

前言

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

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

🔧 ** https://www.trae.cn/ **:使用豆包大模型辅助开发,让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的ESPNOW无线串口&电台系统,具有以下功能:

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

2.1 系统框架图

应用层

通信层

系统层

硬件层

LED指示灯

串口通信

天线

ESP32C3芯片

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开发板(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指示灯:连接到GPIO8(ESP32C3)
  • 天线:连接到开发板的天线接口
  • 串口连接:
    • Serial:USB口,用于配置和调试
    • MySerial0:普通串口,用于数据传输
    • MySerial1:485口,用于工业设备通信

原理图
在这里插入图片描述
PCB板图
在这里插入图片描述

我用solidworks2024设计了一个外壳
在这里插入图片描述
然后发给拓竹H3D打印切片
在这里插入图片描述

PCB通过嘉立创生产后得到了屎黄色黄色板子🤣🤣🤣
在这里插入图片描述
人工手动贴片两块
在这里插入图片描述
这里我们为了提高esp32c3的天线信号增益,手动调整PCB的板载天线改为外置棒杆天线
在这里插入图片描述
修改完毕后,我们就将3D外壳、PCB、杜邦线还有天线一起组装后,大约需要1天反复调整后得到小巧的数传电台啦
在这里插入图片描述
这里我把我常用的电台都放在这里,给大家欣赏一下

在这里插入图片描述
突然发现我的小电台超好看😘😘😘

四、软件配置

首先需要VScode安装Platformio,然后Trae安装Platformio插件调用即可
! https://i-blog.csdnimg.cn/direct/6ccc36df04704e4cbb9ad4becfc23c9a.png

全程交给Trae进行编程外加写博客
! https://i-blog.csdnimg.cn/direct/55a8a6f8d5094860888ec14d42316cfd.png

4.1 PlatformIO配置(platformio.ini)

下面是esp32c3配置代码:

; 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
monitor_filters = esp32_exception_decoder
lib_deps = 
    SPIFFS
board_build.partitions = partitions.csv
build_flags = -std=gnu++17
upload_protocol = esptool
upload_resetmethod = nodemcu

配置说明

  • 支持ESP32C3开发板
  • 使用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)

#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <string.h>
#include <SPIFFS.h> // 添加SPIFFS支持
#include <HardwareSerial.h>
#define LED_PIN 8 // esp32c3 GPIO8 连接到LED,esp32c6 GPIO15
// #define LED_signal 9
#define LED_connect 10
#define C485 2
// 使用更安全的GPIO引脚(避免使用GPIO0/2等有特殊功能的引脚)
// const int RX_PIN = 1;  // 推荐使用的RX引脚
// const int TX_PIN = 0;  // 推荐使用的TX引脚
// SoftwareSerial MySerial1(RX_PIN, TX_PIN); // RX, TX
HardwareSerial MySerial0(0);
HardwareSerial MySerial1(1);
// #define LED_PIN 15 //esp32c3 GPIO8 连接到LED,esp32c6 GPIO15
#define CONFIG_FILE "/espnow_config.json" // SPIFFS配置文件路径
#define CURRENT_VERSION "1.0"             // 当前版本号

// 默认波特率
#define DEFAULT_BAUD_RATE 115200

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;
bool data = false;

// 波特率设置
int baudRate = DEFAULT_BAUD_RATE; // 当前波特率

// 连接状态更新时间
unsigned long lastConnectionUpdate = 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);
}

void print(String s)
{
    Serial.println(s);
    MySerial1.println(s);
}

// ================== SPIFFS ==================
// 保存配对MAC地址和版本信息到SPIFFS
bool saveConfig()
{
    if (!SPIFFS.begin(true))
    {
        print("❌ An error occurred while mounting SPIFFS");
        return false;
    }

    File configFile = SPIFFS.open(CONFIG_FILE, "w");
    if (!configFile)
    {
        print("❌ Failed to open config file for writing");
        return false;
    }

    // 创建JSON格式的数据 - 包含波特率配置
    String configJson = "{";
    configJson += "\"version\":\"" + String(CURRENT_VERSION) + "\",";
    configJson += "\"peer_mac\":\"" + formatMac(peerMac) + "\",";
    configJson += "\"baud_rate\":" + String(baudRate);
    configJson += "}";

    configFile.print(configJson);
    configFile.close();
    print("✅ 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))
    {
        print("❌ An error occurred while mounting SPIFFS");
        return false;
    }

    File configFile = SPIFFS.open(CONFIG_FILE, "r");
    if (!configFile)
    {
        print("❌ 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);
    int baudStart = json.indexOf("\"baud_rate\":") + 12;
    int baudEnd = json.indexOf(",", baudStart);

    if (baudStart > 12) { // 检查是否有波特率配置
        String baudStr = json.substring(baudStart, baudEnd);
        baudRate = baudStr.toInt();
        
        // 验证波特率是否为支持的值
        if (baudRate != 9600 && baudRate != 38400 && baudRate != 115200) {
            baudRate = DEFAULT_BAUD_RATE; // 使用默认值
        }
    } else {
        baudRate = DEFAULT_BAUD_RATE; // 没有保存的波特率配置时使用默认值
    }

    if (versionStart > 11 && macStart > 12) // 检查是否找到字段
    {
        String loadedVersion = json.substring(versionStart, versionEnd);
        String macStr = json.substring(macStart, macEnd);

        print("Loaded version: " + loadedVersion);
        print("Loaded MAC: " + macStr);
        print("Loaded baud rate: " + String(baudRate));

        // 尝试解析MAC地址
        return parseMacAddress(macStr, peerMac);
    }

    return false;
}

// 清除SPIFFS中的配置
void clearConfig()
{
    if (!SPIFFS.begin(true))
    {
        print("❌ An error occurred while mounting SPIFFS");
        return;
    }

    if (SPIFFS.exists(CONFIG_FILE))
    {
        SPIFFS.remove(CONFIG_FILE);
        print("✅ Config file cleared from SPIFFS");
        baudRate = DEFAULT_BAUD_RATE; // 重置为默认波特率
    }
}
// ========================================

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;
            lastConnectionUpdate = millis(); // 更新连接时间
            print("✅ Reconnected!");
            digitalWrite(LED_PIN, LOW); // 点亮
        }
        else
        {
            isConnected = false; // 修正:断开连接时更新状态
            lastConnectionUpdate = millis(); // 更新连接时间
            // print("❌ Connection lost!");
            digitalWrite(LED_PIN, HIGH); // 熄灭
        }
        vTaskDelay(pdMS_TO_TICKS(CONNECTION_TIMEOUT)); // 增加检测间隔到5秒
    }
}

void printhelp()
{
    print("\nESP32-C3 ESP-NOW Enhanced (with Debug Log)");
    print("Enter config mode with '+++', then use AT commands:");
    print("  AT+HELP         Show this help");
    print("  AT+PAIR=<MAC>   Pair with device");
    print("  AT+CLEAR        Clear paired device");
    print("  AT+MAC          Get own MAC");
    print("  AT+FRIEND       Get paired device MAC");
    print("  AT+TEST         Test communication");
    print("  AT+RESTART      Restart device");
    print("  AT+BAUD=<rate>  Set baud rate (9600/38400/115200)");
    print("  AT+GETBAUD      Get current baud rate");
    print("  AT+SYSINFO      Print system parameters");
    print("  ATO             Exit config mode");
}

// 打印系统参数
void printSystemInfo()
{
    // 更新连接状态 - 基于配对状态和最后接收数据时间
    unsigned long now = millis();

    
    print("=== ESP32c3 Radio System Parameters by @2345vor ===");
    print("Firmware Version: " + String(CURRENT_VERSION));
    print("My MAC Address: " + formatMac(selfMac));
    if (isPaired) {
        print("Peer MAC Address: " + formatMac(peerMac));
    } else {
        print("Peer MAC Address: Not Paired");
    }
    print("Current Baud Rate: " + String(baudRate));
    print("Connection Status: " + String(isConnected ? "Connected" : "Disconnected"));
    print("Pairing Status: " + String(isPaired ? "Paired" : "Not Paired"));
    print("Last Received: " + String((now - lastReceived) / 1000.0) + "s ago");
    print("Last Connection Update: " + String((now - lastConnectionUpdate) / 1000.0) + "s ago");
    print("=========================");
}

// 处理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)
            {
                print("TEST OK");
            }
            else
            {
                print("TEST FAIL");
            }
        }
        else
        {
            print("ERROR: Not paired");
        }
    }
    else if (command.startsWith("AT+PAIR="))
    {
        String macStr = command.substring(8);
        if (parseMacAddress(macStr, peerMac))
        {
            if (attemptPairWith(peerMac))
            {
                saveConfig(); // 使用新的SPIFFS保存函数
                print("SaveConfig OK");
            }
            else
            {
                print("ERROR: Pair failed");
            }
        }
        else
        {
            print("ERROR: Invalid MAC");
        }
    }
    else if (command.equalsIgnoreCase("AT+CLEAR"))
    {
        clearConfig(); // 使用新的SPIFFS清除函数
        print("ClearConfig OK");
    }
    else if (command.equalsIgnoreCase("AT+MAC"))
    {
        print(formatMac(selfMac));
    }
    else if (command.equalsIgnoreCase("AT+FRIEND"))
    {
        if (isPaired)
        {
            print(formatMac(peerMac));
        }
        else
        {
            print("ERROR: Not paired device");
        }
    }
    else if (command.equalsIgnoreCase("AT+RESTART"))
    {
        print("Restarting...");
        delay(100);
        ESP.restart();
    }
    else if (command.equalsIgnoreCase("AT+HELP"))
    {
        printhelp();
    }
    else if (command.startsWith("AT+BAUD="))
    {
        String baudStr = command.substring(8);
        int newBaud = baudStr.toInt();
        
        // 检查是否是支持的波特率
        if (newBaud == 9600 || newBaud == 38400 || newBaud == 115200) {
            baudRate = newBaud;
            
            // 重新初始化串口
            MySerial0.updateBaudRate(baudRate);
            MySerial1.updateBaudRate(baudRate);
            
            saveConfig(); // 保存新波特率到配置文件
            print("Baud rate set to: " + String(baudRate));
        } else {
            print("ERROR: Unsupported baud rate. Use 9600, 38400, or 115200");
        }
    }
    else if (command.equalsIgnoreCase("AT+GETBAUD"))
    {
        print("Current baud rate: " + String(baudRate));
    }
    else if (command.equalsIgnoreCase("AT+SYSINFO"))
    {
        printSystemInfo();
    }
    else if (command.equalsIgnoreCase("ATO"))
    {
        // 退出配置模式
        configMode = false;
        print("Exit config mode");
    }
    else
    {
        print("ERROR: Invalid 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; // 确认连接状态
        lastConnectionUpdate = millis(); // 更新连接状态时间
        isPaired = true;    // 设置配对标志
               
        
        // 特殊处理 CONN 消息,只用于连接测试,不转发
        if (len == 4 && memcmp(data, "CONN", 4) == 0) {
            // 这是一个连接测试请求,不需要转发
            return;
        }
    }

    // 直接转发数据到两个串口,保持原始数据完整性
    Serial.write(data, len);
    MySerial0.write(data, len);
    MySerial1.write(data, len);
}

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

    Serial.begin(DEFAULT_BAUD_RATE); // 使用默认波特率初始化
    MySerial0.begin(DEFAULT_BAUD_RATE, SERIAL_8N1, 20, 21);
    // And configure MySerial1 on pins RX=D9, TX=D0
    MySerial1.begin(DEFAULT_BAUD_RATE, SERIAL_8N1, 1, 0);
    
    print("Initializing SPIFFS...");

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

    printhelp();

    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)
    {
        print("❌ ESP-NOW init failed!");
        return;
    }
    esp_now_register_recv_cb(onDataRecv);

    WiFi.macAddress(selfMac); // 使用WiFi库获取MAC地址
    print(">>> MY MAC: " + formatMac(selfMac) + " <<<");

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

    print("🟢 Connection check task started");
    // digitalWrite(LED_signal, LOW);  // 初始灭灯
    digitalWrite(LED_connect, LOW); // 初始灭灯
    
    // 重新初始化串口使用加载的波特率
    if (baudRate != DEFAULT_BAUD_RATE) {
        Serial.begin(baudRate);
        MySerial0.updateBaudRate(baudRate);
        MySerial1.updateBaudRate(baudRate);
    }
    
    // 启动时打印系统参数
    printSystemInfo();
}

void loop()
{

    delayMicroseconds(10); // 使用微秒延迟,1000等同于1毫秒
    // 处理软件串口(串口0)- 仅数传
    if (MySerial0.available())
    {
        while (MySerial0.available())
        {
            char c = MySerial0.read();
            serialBuffer += c;

            if (c == '\n' || c == '\r')
            {
                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)
                {
                    MySerial0.println("❌ Not paired with any device");
                    serialBuffer = "";
                }
            }
        }
        data = true;
    }
    // 处理软件串口(串口1)- 仅数传
    if (MySerial1.available())
    {
        while (MySerial1.available())
        {
            char c = MySerial1.read();
            serialBuffer += c;

            if (c == '\n' || c == '\r')
            {
                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)
                {
                    MySerial1.println("❌ Not paired with any device");
                    serialBuffer = "";
                }
            }
        }
        data = true;
    }

    // 处理硬件串口(串口0)- 配置+数传
    if (!data && 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)
            {
                print("❌ Not paired with any device");
                serialBuffer = ""; // 清空缓冲区
            }
        }
        else
        {
            // 检查是否退出配置模式命令
            if (serialBuffer.endsWith("\n") || serialBuffer.endsWith("\r"))
            {
                // 配置模式:处理AT指令
                serialBuffer.trim();
                if (!serialBuffer.isEmpty())
                {
                    // 处理AT指令
                    handleATCommand(serialBuffer);
                    serialBuffer = ""; // 处理完后清空
                }
            }
        }
    }
    data = false;
}

5.2 串口功能分配

本项目使用了三个串口,各自功能如下:

  1. Serial:USB口,用于配置和调试

    • 连接到电脑的USB接口
    • 用于发送AT指令进行设备配置
    • 用于查看系统状态和调试信息
  2. MySerial0:普通串口,用于数据传输

    • 连接到GPIO20和GPIO21
    • 用于与普通串口设备通信
    • 支持透明数据传输
  3. MySerial1:485口,用于工业设备通信

    • 连接到GPIO1和GPIO0
    • 用于与RS-485工业设备通信
    • 通过C485引脚(GPIO2)控制485芯片的收发状态

5.3 核心功能模块

  1. 设备配对与连接管理

    • 通过AT+PAIR=指令配对目标设备
    • 自动重连机制确保连接稳定
    • 连接状态实时监测与LED指示
  2. 数据传输

    • 支持双向透明数据传输
    • 三个串口的数据都可以通过ESPNOW无线传输
    • 数据传输时LED指示灯闪烁提示
  3. 配置管理

    • 通过SPIFFS文件系统持久化存储配置
    • 支持波特率配置(9600/38400/115200)
    • 配置自动加载与验证
  4. AT指令系统

    • 丰富的AT指令集,支持设备配置和状态查询
    • 配置模式与数传模式无缝切换
    • 详细的帮助信息和错误提示

六、使用方法

6.1 设备配对

  1. 连接两个ESP32C3开发板到电脑
  2. 打开串口监视器,波特率设置为115200
  3. 在串口输入+++进入配置模式
  4. 输入AT+MAC获取本机MAC地址
  5. 在另一个设备上输入AT+PAIR=<MAC>,其中<MAC>为第一个设备的MAC地址
  6. 配对成功后,设备会自动保存配置并进入数传模式

6.2 数据传输

  • 从电脑发送数据:通过Serial(USB口)发送数据,会自动通过ESPNOW传输到配对设备
  • 从普通串口发送数据:通过MySerial0发送数据,会自动通过ESPNOW传输到配对设备
  • 从485口发送数据:通过MySerial1发送数据,会自动通过ESPNOW传输到配对设备
  • 接收数据:ESPNOW接收到的数据会同时转发到三个串口

6.3 配置命令

命令 功能 示例
AT+HELP 显示帮助信息 AT+HELP
AT+PAIR= 配对目标设备 AT+PAIR=9C:13:9E:CC:3B:88
AT+CLEAR 清除配对信息 AT+CLEAR
AT+MAC 获取本机MAC地址 AT+MAC
AT+FRIEND 获取配对设备MAC AT+FRIEND
AT+TEST 测试通信 AT+TEST
AT+RESTART 重启设备 AT+RESTART
AT+BAUD= 设置波特率9600/38400/115200 AT+BAUD=115200
AT+GETBAUD 获取当前波特率 AT+GETBAUD
AT+SYSINFO 打印系统参数 AT+SYSINFO
ATO 退出配置模式 ATO

七、测试与调试

7.1 测试步骤

  1. 准备两个ESP32C3开发板,烧录相同的代码
  2. 按照6.1节的步骤进行设备配对
  3. 验证LED指示灯状态:配对成功后LED常亮
  4. 测试数据传输:从一个设备的任意串口发送数据,在另一个设备的所有串口查看是否接收到数据
  5. 测试连接稳定性:断开一个设备的电源,重新上电后观察是否自动重连
7.1.1 USB互发测试

在这里插入图片描述
可以正常收发数据2345vor和123

7.1.2 串口互发测试

记得手动添加\r\n结束符哦👌👌👌
在这里插入图片描述

在这里插入图片描述
这里的串口0和串口1 485接口都已测试通过

7.2 常见问题与解决方案

问题 可能原因 解决方案
配对失败 MAC地址格式错误 确保MAC地址格式正确,不含空格或其他字符
连接不稳定 距离过远或干扰严重 靠近设备或减少干扰源,使用外置天线
数据传输失败 未配对或连接断开 重新配对设备,确保连接状态正常
串口无响应 波特率设置错误 使用AT+GETBAUD查看当前波特率,确保与串口监视器设置一致

八、总结

本项目成功实现了基于ESP32C3的增强版无线串口&电台系统,具有以下特点:

  1. 多串口支持:明确区分了三个串口的功能,Serial作为USB口用于配置和调试,MySerial0作为普通串口用于数据传输,MySerial1作为485口用于工业设备通信。

  2. 稳定可靠:采用ESPNOW协议实现高速无线通信,配合自动重连机制,确保连接稳定可靠。

  3. 易于配置:通过AT指令系统,用户可以方便地进行设备配对、波特率设置等操作。

  4. 灵活扩展:系统架构清晰,易于扩展其他功能,如传感器数据采集、远程控制等。

  5. AI辅助开发:使用Trae结合豆包大模型,大幅提升了开发效率,让代码编写更加智能高效。

通过本项目,我们展示了如何利用ESP32C3和ESPNOW协议构建一个功能强大的无线串口&电台系统,为物联网设备间的通信提供了一种高效、可靠的解决方案。

🔧 ** https://www.trae.cn/ **:使用豆包大模型辅助开发,让ESP32开发事半功倍!
请大家点击豆包火山注册地址,不注册是不能完成下面的实验哦: https://t.vncps.com/5LOve
谢谢啦大家的支持💖💖💖

九、扩展与展望

  1. 增加传感器支持:可以扩展系统,添加温湿度、光照等传感器,实现无线传感器网络。

  2. 实现OTA更新:通过ESPNOW实现设备固件的无线更新,方便系统维护和功能升级。

  3. 增加加密功能:对传输数据进行加密,提高系统安全性。

  4. 支持多设备网络:扩展为星型或 mesh 网络,支持更多设备的通信。

  5. 优化功耗:通过深度睡眠等技术,降低系统功耗,延长电池寿命。

通过不断完善和扩展,本系统可以应用于更多场景,如工业自动化、智能家居、环境监测等领域,为物联网应用提供更加灵活、可靠的通信解决方案。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐