一直以黎我覺得用電腦供電比 12V PWM 風扇調速最實用,寫 Arduino 嘅首選係有 12V DC 插頭用 esp8266 嘅 Wemos D1 R2 ,令外我新買左块都好普及用 esp32 嘅 UNO D1 R32 。咁叫 esp32 應該係第一代嘅双核 240MHz mcu 比 8266 嘅單核 160MHz 快,ram 都多好多。esp32 嘅新一代係 esp32s3 速度一樣,淘宝有块 ESP32S3 UNO;令外我有試過同 8266 速度一樣嘅 esp32c3 。

雖然都係 UNO size,但因為 Arduino UNO R3 嘅 shield 係用5V,所以 GPIO 用 3.3V 嘅 8266 大部份都唔啱用,但 shield 嘅概念好值得參考,可以用 6*4 PCB 板同 11cm 加长排母自制 shield,一般排母太幼有接觸不良問題。令外,有天線頭嘅 Wemos D1 mini pro 都好好用,可以配 mini D1 洞洞板,但我只係預插單層方便測試,所以用圆孔排母。如果想自制嘅 mini D1 同 6*4 PCB shield 兼容,可以銲一塊 6*4 PCB 底板接線 map 番咁多條 pin 到 mini D1 嘅插座。

D0 – D8 號唔代表 GPIO 嘅真正数字,而且Wemos D1 R2 同 UNO D1 R32 就算 pin 嘅位置一樣,但 GPIO 嘅数字會唔同,所以想寫到兼容,首先要幫啲 pin 改茗 map 番啲 GPIO,同時方便分辨 8266 D0 – D8 號 pin 有啲唔同特質 。除左 D0 無 PWM 功能只限 boolean 嘅特質之外,其他都有 PWM 無乜大分別,所以我用 D0 黎造 BLDC 控制 CW 定 CCW 所以叫 “PIN_DIR”,或者用黎造電源開關嘅 relay;令外 D8 係獨特一定要 “低電平” 先 boot 倒机,因為 “高電平” 嘅話會入左特別嘅 SDIO mode,唔會 run Ardinuo 啲 code,需然可以用黎造 PWM,但輸出唔可以駁電平由 3.3V 上拉到 5V 裝置,又唔可以 INPUT_PULLUP 造 PWM 風扇嘅 hall sensor 測速或者一般黚制 button,但可以用黎駁有訊號先輸入高電平嘅 “光耦測速器” ,於是我叫佢造 “PIN_COUNTER”。
#if defined(ESP32) #include <ESPmDNS.h> #include <WiFi.h> #include <AsyncTCP.h> #include <HTTPClient.h> #include <Update.h> #include <BlynkSimpleEsp32.h> #include <ESP32Servo.h> // (ESP32c3 not support) #else #include <ESP8266mDNS.h> #include <ESP8266WiFi.h> #include <ESPAsyncTCP.h> #include <ESP8266HTTPClient.h> #include <Updater.h> //update littleFS #include <BlynkSimpleEsp8266.h> #include <Servo.h> #endif #include <AceRoutine.h> // multi task manager using namespace ace_routine; #include <Adafruit_AS7341.h> // 11 channel colorimeter #include <Adafruit_INA219.h> //current sensor #include <Adafruit_MLX90614.h> //IR temp sensor #include <Adafruit_SHT31.h> // temp & humidity sensor #include <Adafruit_TCS34725.h> // rgb colorimeter #include <Adafruit_VL53L0X.h> // ToF distance sensor #include <ArduinoJson.h> #include <ArduinoOTA.h> #include <DFRobot_QMC5883.h> // compass sensor #include <ESPAsyncWebServer.h> #include <LittleFS.h> #include <MAX30105.h> // heart rate sensor #include <heartRate.h> // for MAX30105 #include <MPU9250_WE.h> // accelerometer sensor #include <NeoPixelBus.h> // neopixel ws2812 #include <OLEDDisplayUi.h> #include <SH1106Wire.h> // OLED display (ESP32c3 not support) #include <SSD1306Wire.h> // OLED display (ESP32c3 not support) #include <SparkFunSX1509.h> // I2C GPIO expander #include <TimeLib.h> // time for ntp #include <WiFiUdp.h> //******************************************************************************************* // global & class objects //******************************************************************************************* const char SSID[] = "XXXXXX"; const char PASSWORD[] = "*******"; bool enable_blynk = 0; bool is_wsconnect = 0; struct tm time_tm; time_t time_ntp = 0; String string_json = ""; StaticJsonDocument<512> json; StaticJsonDocument<128> jsonRX; HTTPClient http; AsyncWebServer server(80); AsyncWebSocketMessageHandler wsHandler; AsyncWebSocket ws("/ws", wsHandler.eventHandler()); WiFiClient client; WiFiUDP udp; //******************************************************************************************* // html //******************************************************************************************* const char INDEX_HTML[] = R"rawliteral( <!DOCTYPE HTML><html> <head> <title>%echo-dns%.local - ESP Async Web Server</title> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"> <link rel="icon" href="data:,"> <link rel='stylesheet' href='http://external-web-server.com/espwebserver.css' type='text/css'> <script src='https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js'></script> <script src='http://external-web-server.com/espwebserver.js'></script> </head> <body> </body> </html> )rawliteral"; String processor(const String& placeholder) { if (placeholder == "echo-dns") { return DNS; } return String(); } //******************************************************************************************* // hardware //******************************************************************************************* // GPIO pins #if defined(ESP32) // WEMOS D1 R32 const uint8_t PIN_RX = 3; // ws2812 LED const uint8_t PIN_TX = 1; const uint8_t PIN_ANALOG = 2; // A0 const uint8_t PIN_DIR = 26; // D0, no PWM, pwm motor direction const uint8_t PIN_SCL = 25; // D1 const uint8_t PIN_SDA = 17; // D2 const uint8_t PIN_IN0 = 16; // D3, button PULLUP const uint8_t PIN_SERVO = 27; // D4 const uint8_t PIN_PWM = 14; // D5 const uint8_t PIN_IN1 = 34; // D6 const uint8_t PIN_IN2 = 35; // D7 const uint8_t PIN_COUNTER = 4; // D8 const uint8_t PIN_R32_LED = 2; #else // WEMOS D1 R2 & D1 mini 8266 const uint8_t PIN_RX = 3; // ws2812 LED DMA+I2C method const uint8_t PIN_TX = 1; const uint8_t PIN_ANALOG = 17; // A0 const uint8_t PIN_DIR = 16; // D0, no PWM, motor direction, power on/off const uint8_t PIN_SCL = 5; // D1 const uint8_t PIN_SDA = 4; // D2 const uint8_t PIN_IN0 = 0; // D3, button PULLUP const uint8_t PIN_SERVO = 2; // D4, LED_BUILTIN const uint8_t PIN_PWM = 14; // D5 const uint8_t PIN_IN1 = 12; // D6 const uint8_t PIN_IN2 = 13; // D7 const uint8_t PIN_COUNTER = 15; // D8, low to boot, don't PULLUP #endif // I2C Address bool array_i2c[127]; const uint8_t ADD_COMPASS = 30; const uint8_t ADD_LIGHT = 35; //BH1750 const uint8_t ADD_LCD = 39; const uint8_t ADD_TOF = 41; //TIME OF FIGHT const uint8_t ADD_TCS = 41; // RGB colorimeter const uint8_t ADD_COLOR = 57; //AS7341 colorimeter const uint8_t ADD_OLED = 60; const uint8_t ADD_SX = 62; //SX1509 PORT EXPANSION const uint8_t ADD_INA = 64; // VOLTAGE & CURRENT const uint8_t ADD_SHT = 68; //HUMIDITY & TEMP const uint8_t ADD_TEMP = 72; //TMP102 const uint8_t ADD_EEPROM = 80; //24LCXX const uint8_t ADD_HEART = 87; //MAX3010X const uint8_t ADD_MLX = 90; //IR TEMP const uint8_t ADD_MPU = 104; //ACELEROMETER const uint8_t ADD_PRESSURE = 118; //BMP280 const uint8_t ADD_GAS = 119; //BME680 //******************************************************************************************* // multi task loop //******************************************************************************************* void fn_loop_1ms(); void fn_loop_40ms(); void fn_loop_1000ms(); void fn_loop_8640ms(); void loop() { fn_loop_1ms(); CoroutineScheduler::loop(); } COROUTINE(loop_40ms) { COROUTINE_LOOP() { serializeJson(json, string_json); #if defined(ESP8266) MDNS.update(); #endif ws.cleanupClients(2); // no more than 2 clients ws.textAll(string_json); fn_loop_40ms(); COROUTINE_DELAY(40); } } COROUTINE(loop_1000ms) { COROUTINE_LOOP() { json["heap"] = ESP.getFreeHeap(); if (WiFi.status() == 3) json["now"] = now(); ArduinoOTA.handle(); fn_loop_1000ms(); COROUTINE_DELAY(1000); } } COROUTINE(loop_8640ms) { COROUTINE_LOOP() { fn_wifi_begin(); fn_loop_8640ms(); COROUTINE_DELAY(8640); } } ......
我主要寫一個 .h 比晒唔同板 include,為左寫到兼容 8266 同 esp32,好多只係有小小唔同有両個版本嘅 library,但有啲只得 8266 版例如 multitask management 就要轉用 AceRoutine,重點在於 web server 我改左用 ESPAsyncWebServer 而改左唔小曲。
void fn_wemos_blynk_begin(const char DNS[], const char blynk_token[]) { // hardware json["dns"] = String(DNS); Serial.begin(115200); Serial.println(""); #if defined(ESP32) LittleFS.begin(true); // format if mount failed #else analogWriteFreq(38000); // IRremote analogWriteRange(255); LittleFS.begin(); #endif CoroutineScheduler::setup(); // network WiFi.hostname(DNS); WiFi.mode(WIFI_STA); server.onNotFound(fn_handle_cors_404); server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/html", INDEX_HTML, processor); }); server.on("/json", HTTP_GET, fn_handle_cors_json); server.on("/update", HTTP_POST, [](AsyncWebServerRequest* request) {}, [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { if (!index) { Serial.printf("Starting update: %s\n", filename.c_str()); ws.textAll("Starting update: " + String(filename)); #if defined(ESP32) Update.begin(); #else Update.begin(0x200000, U_FS);// offset for Flash size: 4MB (FS:2MB OTA:~1019KB) #endif } Update.write(data, len); if (final) { if (Update.end(true)) { Serial.println("Update LittleFS complete!"); ws.textAll("Update LittleFS complete!"); } else { Serial.println("Update LittleFS failed!"); ws.textAll("Update LittleFS failed!"); } } }); // websocket ws.onEvent([](AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) { (void)len; if (type == WS_EVT_CONNECT) { is_wsconnect = true; ws.textAll("new client connected"); client->setCloseClientOnQueueFull(false); client->ping(); } else if (type == WS_EVT_DISCONNECT) { is_wsconnect = false; ws.textAll("client disconnected"); } else if (type == WS_EVT_ERROR) { is_wsconnect = false; } else if (type == WS_EVT_DATA) { AwsFrameInfo* info = (AwsFrameInfo*)arg; String msg = ""; if (info->final && info->index == 0 && info->len == len) { if (info->opcode == WS_TEXT) { DeserializationError error = deserializeJson(jsonRX, (char*)data); if (jsonRX["WSRX"].as<String>() == "RESTART") { ESP.restart(); } } } } }); server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest* request, ArMiddlewareNext next) { if (ws.count() > 1) { // if we have 2 clients or more, prevent the next one to connect request->send(503, "text/plain", "Server is busy"); } else { // process next middleware and at the end the handler next(); } }); server.begin(); MDNS.begin(DNS); MDNS.addService("http", "tcp", 80); fn_ota_begin(DNS, 8266); udp.begin(80); if (enable_blynk) Blynk.begin(blynk_token, SSID, PASSWORD); fn_i2c_begin(PIN_SDA, PIN_SCL); fn_ntp_sync(); } //******************************************************************************************* // functions //******************************************************************************************* void fn_handle_cors_json(AsyncWebServerRequest* request) { AsyncWebServerResponse* response; response = request->beginResponse(200, "application/json; charset=utf-8", string_json); response->addHeader("Access-Control-Allow-Origin", "*"); response->addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); response->addHeader("Access-Control-Allow-Headers", "Content-Type"); response->addHeader("X-Content-Type-Options", "nosniff"); response->addHeader("Content-Length", String(string_json.length())); response->addHeader("Cache-Control", "no-cache"); response->addHeader("Connection", "keep-alive"); request->send(response); }
相比起 ESP8266 WebServer,ESPAsyncWebServer 用返 http port 80 就造倒 websocket 功能,而且效果明顯更穏定同順暢,但 poll rate 最快去到 25Hz 去唔到 50Hz。同一段曲轉用 esp32 嘅話令我大感意外,唔知點解竟然明顯比 8266 更窒,就算問 copilot 都無法解答攪左好耐都解決唔倒嘅問題。喺 8266 閑倒但 esp32 閑唔倒嘅 library 對我黎講只有 IRremote,相反因為我唔用 bluetooth 同 cam,所以揾唔倒有乜功能轉用 esp32 之後可以受惠。再加上用 Arduino IDE complie esp32 嘅曲明顯比 8266 慢好多;所以雖然好似好落後,但對我黎講 8266 比 esp32 更好用,但寫就盡量寫到両者兼容方便比較。
Reference: