esp32s3 +arduino ide

彻底修复步骤(请严格按顺序执行)

第一步:彻底清理所有冲突库

打开文件夹 C:\Users\san\Documents\Arduino\libraries\删除以下所有文件夹(如果存在):

  • ESPAsyncWebServer
  • ESP_Async_WebServer
  • AsyncTCP

⚠️ 必须删干净再往下走,否则 IDE 缓存会继续引用旧库。

第二步:安装完全兼容的新版组合

Arduino IDE → 项目加载库从URL添加库...,依次粘贴以下两个地址并确定:

复制代码
https://github.com/mathieucarbou/AsyncTCP/archive/refs/heads/main.zip

https://github.com/mathieucarbou/ESPAsyncWebServer/archive/refs/heads/main.zip

💡 关键点mathieucarbou 维护的这两个库是配套发布 的,专门针对 ESP32 Arduino Core v3.x / IDF 5.1+ 重写了底层锁机制,不再访问 pxCurrentTCB,且 API 与原版 me-no-dev 100% 兼容,你的业务代码无需任何修改。

第三步:验证库唯一性

安装完成后,再次打开 C:\Users\san\Documents\Arduino\libraries\,确认只有以下两个文件夹:

  • AsyncTCP-main(或 AsyncTCP
  • ESPAsyncWebServer-main(或 ESPAsyncWebServer

如果还有其他同名文件夹,继续删除。

第四步:重新编译

关闭并重新打开 Arduino IDE(清除编译器缓存),然后点击编译。

方法二:如果库管理器里找不到卸载选项

说明索引已损坏,需要手动清理缓存:

彻底删除库文件夹:

打开 C:\Users\san\Documents\Arduino\libraries\,删除所有包含 Async 或 ESPAsync 的文件夹

清除 IDE 库索引缓存:

打开文件夹 C:\Users\san\AppData\Local\Arduino15\,删除文件:

library_index.json

library_index.json.gz

(删除后 IDE 会在下次打开时自动重建索引)

重启 Arduino IDE

重新通过 从URL添加库... 安装上述两个 ZIP

添加库文件

项目-导入库-添加.ZIP库,直接输入链接

复制代码
​
文件名:https://github.com/mathieucarbou/AsyncTCP/archive/refs/heads/main.zip

​文件名:https://github.com/mathieucarbou/ESPAsyncWebServer/archive/refs/heads/main.zip

完整代码

复制代码
#include <dummy.h>

/*
 * ESP32-S3 Motor Controller + WS2812B Status LED
 * 硬件: ESP32-S3, GPIO48=WS2812B, UART1(TX=4,RX=5)→电机驱动板
 * 功能: WiFi AP/STA + WebSocket 遥控 + 心跳安全停止 + RGB状态指示
 */

#include <map> 
#include <Arduino.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_NeoPixel.h>

// ==================== CONFIG ====================
const char* WIFI_SSID     = "Xiaomi_806";
const char* WIFI_PASSWORD = "123123123";
const String VALID_CMDS   = "FBLR+-S";

#define UART1_TX_PIN      4
#define UART1_RX_PIN      5
#define SERIAL_BUF_SIZE   256
#define HEARTBEAT_INTERVAL_MS 400
#define HEARTBEAT_TIMEOUT_MS  1000
#define STOP_CMD          'S'

#define RGB_LED_PIN       48
#define RGB_LED_COUNT     1

// ==================== RGB LED ====================
Adafruit_NeoPixel rgbLed(RGB_LED_COUNT, RGB_LED_PIN, NEO_GRB + NEO_KHZ800);

#define LED_RED()    do { rgbLed.setPixelColor(0, 255, 0, 0);   rgbLed.show(); } while(0)
#define LED_GREEN()  do { rgbLed.setPixelColor(0, 0, 255, 0);   rgbLed.show(); } while(0)
#define LED_BLUE()   do { rgbLed.setPixelColor(0, 0, 0, 255);   rgbLed.show(); } while(0)
#define LED_YELLOW() do { rgbLed.setPixelColor(0, 255, 255, 0); rgbLed.show(); } while(0)
#define LED_OFF()    do { rgbLed.clear(); rgbLed.show(); } while(0)

// ==================== MOTOR CTRL ====================
struct ClientState {
    unsigned long lastHeartbeat;
    bool isControlling;
};

static std::map<uint32_t, ClientState> clientStates;
static bool motorRunning = false;

void motorInit() {
    Serial1.begin(115200, SERIAL_8N1, UART1_RX_PIN, UART1_TX_PIN);
}

bool isMotorRunning() { return motorRunning; }
void setMotorRunning(bool running) { motorRunning = running; }

bool hasActiveController() {
    unsigned long now = millis();
    for (const auto& p : clientStates)
        if (p.second.isControlling && (now - p.second.lastHeartbeat <= HEARTBEAT_TIMEOUT_MS))
            return true;
    return false;
}

void clearAllControllers() {
    for (auto& p : clientStates) p.second.isControlling = false;
}

void registerClient(uint32_t id) { clientStates[id] = {millis(), false}; }

void unregisterClient(uint32_t id, bool& wasControlling) {
    auto it = clientStates.find(id);
    wasControlling = false;
    if (it != clientStates.end()) {
        wasControlling = it->second.isControlling;
        clientStates.erase(it);
    }
}

void refreshHeartbeat(uint32_t id) {
    auto it = clientStates.find(id);
    if (it != clientStates.end()) it->second.lastHeartbeat = millis();
}

void markAsController(uint32_t id) {
    auto it = clientStates.find(id);
    if (it != clientStates.end()) {
        it->second.isControlling = true;
        it->second.lastHeartbeat = millis();
    }
}

void unmarkController(uint32_t id) {
    auto it = clientStates.find(id);
    if (it != clientStates.end()) it->second.isControlling = false;
}

bool checkControllerTimeout() {
    unsigned long now = millis();
    for (const auto& p : clientStates)
        if (p.second.isControlling && (now - p.second.lastHeartbeat > HEARTBEAT_TIMEOUT_MS))
            return true;
    return false;
}

// ==================== WEBSOCKET ====================
static AsyncWebSocket ws("/ws");

void wsBroadcastText(const String& msg) { ws.textAll(msg); }

void safeStop(const char* reason) {
    if (motorRunning) {
        Serial1.write(STOP_CMD);
        motorRunning = false;
        Serial.printf("[⚠️ SAFE STOP] %s -> '%c'\n", reason, STOP_CMD);
        wsBroadcastText(String("[SAFE] ") + reason);
        LED_RED();
    }
}

static void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
                      AwsEventType type, void* arg, uint8_t* data, size_t len) {
    uint32_t id = client->id();

    if (type == WS_EVT_CONNECT) {
        Serial.printf("[WS] #%u connected\n", id);
        registerClient(id);
        client->text(("IP: " + WiFi.localIP().toString()).c_str());
        client->text("Connected to Motor Ctrl");
    }
    else if (type == WS_EVT_DISCONNECT) {
        Serial.printf("[WS] #%u disconnected\n", id);
        bool wasControlling = false;
        unregisterClient(id, wasControlling);
        if (wasControlling && !hasActiveController())
            safeStop("Controller Disconnected");
    }
    else if (type == WS_EVT_DATA) {
        String msg = String((char*)data).substring(0, len);
        msg.trim();
        if (msg == "H" || msg == "heartbeat") { refreshHeartbeat(id); return; }

        for (unsigned int i = 0; i < msg.length(); i++) {
            char c = msg.charAt(i);
            if (VALID_CMDS.indexOf(c) != -1) {
                Serial1.write(c);
                if (c == STOP_CMD) {
                    unmarkController(id);
                    if (!hasActiveController()) setMotorRunning(false);
                } else if (c == 'F' || c == 'B' || c == 'L' || c == 'R') {
                    setMotorRunning(true);
                    markAsController(id);
                }
            }
        }
    }
}

// ==================== WEB PAGE ====================
const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Motor Ctrl</title>
<style>
  body{font-family:sans-serif;text-align:center;padding:20px;background:#222;color:#fff;margin:0;
       -webkit-user-select:none;user-select:none;}
  .btn-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;max-width:300px;margin:20px auto}
  button{padding:20px;font-size:24px;border:none;border-radius:8px;cursor:pointer;background:#444;color:#fff;
         touch-action:none;}
  button.active{background:#0a0 !important;}
  #status{margin-top:10px;color:#aaa;font-size:14px}
  #uart-log{width:90%;max-width:500px;height:180px;margin:15px auto;background:#111;color:#0f0;
            font-family:'Consolas',monospace;font-size:13px;padding:10px;box-sizing:border-box;
            overflow-y:auto;border-radius:6px;text-align:left;white-space:pre-wrap;word-break:break-all;
            border:1px solid #333;}
</style></head><body>
<h2>🎮 Motor Control</h2>
<div class="btn-grid">
  <div></div><button data-cmd="F">F</button><div></div>
  <button data-cmd="L">L</button><button data-cmd="S">S</button><button data-cmd="R">R</button>
  <div></div><button data-cmd="B">B</button><div></div>
</div>
<div style="margin-top:15px">
  <button onclick="send('+')">Speed +</button>
  <button onclick="send('-')">Speed -</button>
</div>
<p id="status">Connecting...</p>
<div id="uart-log">// 等待串口数据...</div>
<script>
let ws,logBox=document.getElementById('uart-log'),isFirstMsg=true,activeCmd=null,isTouch=false,hbTimer=null;
function connect(){
  ws=new WebSocket(`ws://${location.host}/ws`);
  ws.onopen=()=>{document.getElementById('status').innerText='✅ Connected';startHB();};
  ws.onclose=()=>{document.getElementById('status').innerText='❌ Disconnected';stopHB();setTimeout(connect,2000);};
  ws.onerror=()=>ws.close();
  ws.onmessage=(e)=>{
    if(isFirstMsg){logBox.innerHTML='';isFirstMsg=false;}
    const t=new Date().toLocaleTimeString('zh-CN',{hour12:false});
    const l=document.createElement('div');l.textContent=`[${t}] ${e.data}`;
    if(e.data.startsWith('[SAFE]'))l.style.color='#f44';
    logBox.appendChild(l);logBox.scrollTop=logBox.scrollHeight;
    while(logBox.childElementCount>200)logBox.removeChild(logBox.firstChild);
  };
}
function startHB(){stopHB();hbTimer=setInterval(()=>{if(ws&&ws.readyState===1)ws.send('H');},500);}
function stopHB(){if(hbTimer){clearInterval(hbTimer);hbTimer=null;}}
function send(c){if(ws&&ws.readyState===1)ws.send(c);}
function pressBtn(b,c){if(activeCmd===c)return;activeCmd=c;b.classList.add('active');send(c);}
function releaseBtn(b){b.classList.remove('active');if(activeCmd&&activeCmd!=='S')send('S');activeCmd=null;}
document.querySelectorAll('button[data-cmd]').forEach(b=>{
  const c=b.dataset.cmd;
  b.addEventListener('touchstart',e=>{e.preventDefault();isTouch=true;pressBtn(b,c);},{passive:false});
  b.addEventListener('touchend',e=>{e.preventDefault();releaseBtn(b);});
  b.addEventListener('touchcancel',e=>{e.preventDefault();releaseBtn(b);});
  b.addEventListener('mousedown',e=>{if(!isTouch)pressBtn(b,c);});
  b.addEventListener('mouseup',()=>{if(!isTouch)releaseBtn(b);});
  b.addEventListener('mouseleave',()=>{if(!isTouch&&activeCmd)releaseBtn(b);});
});
document.addEventListener('touchend',()=>{
  if(activeCmd&&activeCmd!=='S'){const a=document.querySelector('button.active');if(a)releaseBtn(a);else{send('S');activeCmd=null;}}
});
document.addEventListener('touchstart',()=>{},{passive:true});
setTimeout(()=>{isTouch=false;},300);
connect();
</script></body></html>
)rawliteral";

// ==================== SETUP & LOOP ====================
AsyncWebServer server(80);

void setup() {
    Serial.begin(115200);

    // RGB LED 初始化
    rgbLed.begin();
    rgbLed.setBrightness(80);
    LED_BLUE();  // 🔵 启动中

    motorInit();

    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("Connecting WiFi");
    while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
    Serial.printf("\n✅ IP: %s\n", WiFi.localIP().toString().c_str());
    LED_GREEN();  // 🟢 WiFi 已连接

    ws.onEvent(onWsEvent);
    server.addHandler(&ws);
    server.on("/", HTTP_GET, [](AsyncWebServerRequest* req) {
        req->send(200, "text/html", INDEX_HTML);
    });
    server.begin();
    Serial.println("🚀 Server Started");
}

void loop() {
    ws.cleanupClients();

    // 心跳超时 → 安全停止
    if (checkControllerTimeout() && isMotorRunning()) {
        safeStop("Controller Heartbeat Timeout");
        clearAllControllers();
    }

    // LED 跟随电机状态(非阻塞)
    static bool lastMotor = false;
    bool curMotor = isMotorRunning();
    if (curMotor != lastMotor) {
        if (curMotor) LED_YELLOW();  // 🟡 电机运行
        else          LED_GREEN();   // 🟢 待机
        lastMotor = curMotor;
    }

    // 串口回传
    static char buf[SERIAL_BUF_SIZE];
    if (Serial1.available()) {
        size_t len = Serial1.readBytesUntil('\n', buf, SERIAL_BUF_SIZE - 1);
        buf[len] = '\0';
        String msg = String(buf); msg.trim();
        if (msg.length() > 0) wsBroadcastText(msg);
    }
    delay(1);
}
相关推荐
时时三省1 天前
VScode 智能插件安装
ide·vscode·编辑器
令狐前生1 天前
Intellij IDEA 2025 破解安装
java·ide·intellij-idea
知彼解己1 天前
Eclipse Temurin:企业级 Java JDK 发行版的最佳实践
java·ide·eclipse
独隅1 天前
CLion 在 Linux 上的完整安装与配置使用指南
linux·运维·服务器·c语言·c++·ide
jerryinwuhan1 天前
VScode 安装opencode
ide·vscode·编辑器
223糖1 天前
Idea,pycharm2026激活保姆级教程
java·ide·intellij-idea
独隅1 天前
CLion 接入 Codex 的完整配置使用全面指南
c++·ide·ai·c++23
sphw2 天前
nixnb: Jupyter Notebook 优雅分享
ide·人工智能·jupyter
计算机内卷的N天2 天前
CMake与Visual Studio的使用
c++·ide·visual studio