diff --git a/.gitignore b/.gitignore index 9c89b87d..c787ba34 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,11 @@ .gitignore .idea .pio +.piohome .pioenvs .piolibdeps +.tools +.venv .vscode .vscode/extensions.json diff --git a/docs/rfp-esp32s3-wroom1-n16r8-3x106.md b/docs/rfp-esp32s3-wroom1-n16r8-3x106.md new file mode 100644 index 00000000..7327ec02 --- /dev/null +++ b/docs/rfp-esp32s3-wroom1-n16r8-3x106.md @@ -0,0 +1,64 @@ +# RFP ESP32-S3 WROOM-1 N16R8 (3 x 106) + +This repository includes a tracked PlatformIO target for the RFP ESP32-S3 WROOM-1 N16R8 nodes with three LED outputs and 106 pixels per output. + +Build target: + +- `rfp_esp32s3_wroom1_n16r8_3x106` + +Default output pins: + +- Output 1: `GPIO4` +- Output 2: `GPIO5` +- Output 3: `GPIO6` + +Pins intentionally avoided: + +- `GPIO0`, `GPIO3`, `GPIO45`, `GPIO46` for boot / strapping +- `GPIO19`, `GPIO20` for USB +- `GPIO33` to `GPIO37` because they are reserved by octal PSRAM / flash on `N16R8` +- `GPIO48` because it is used as the onboard status pixel + +Build: + +```powershell +python -m platformio run -e rfp_esp32s3_wroom1_n16r8_3x106 +``` + +Build and upload: + +```powershell +.\tools\flash_rfp_s3.ps1 -ComPort COM7 +``` + +Build only with the helper script: + +```powershell +.\tools\flash_rfp_s3.ps1 -BuildOnly +``` + +Local Wi-Fi defaults: + +- Keep SSID and password in the ignored file `wled00/my_config.h`. +- If the file does not exist yet, create it with your local values: + +```cpp +#pragma once + +#define CLIENT_SSID "your-ssid" +#define CLIENT_PASS "your-password" +``` + +Important: + +- The `3 x 106` bus layout is used as the default when the device has no saved `cfg.json`. +- If a board already has a saved WLED config, do a factory reset or erase settings once so the default bus layout is recreated. + +Onboard status pixel on `GPIO48`: + +- `green blinking`: DDP realtime active +- `green solid`: network connected +- `blue blinking`: AP / setup mode active +- `red fast blinking`: Wi-Fi configured but currently disconnected +- `amber fast blinking`: network connected, MQTT configured, but MQTT not connected +- `off`: idle / no status to show diff --git a/platformio.ini b/platformio.ini index 15485d29..c6f49061 100644 --- a/platformio.ini +++ b/platformio.ini @@ -107,6 +107,7 @@ default_envs = ;; === esp32-S3 === with 16MB flash esp32S3_16MB_PSRAM_M_HUB75 ;; for S3 with 16MB flash, HUB75 supported (MOONHUB HUB75 adapter board) esp32S3_WROOM-2_M ;; for S3 WROOM-2; HUB75 supported + ; rfp_esp32s3_wroom1_n16r8_3x106 ;; RFP ESP32-S3 WROOM-1 N16R8, 3x106 pixels on GPIO 4/5/6 ;; ;; === esp32-S2 boards === esp32s2_PSRAM_S ;; OTA-compatible with upstream @@ -2438,6 +2439,32 @@ build_flags = ${env:esp32S3_8MB_PSRAM_M_opi.build_flags} [env:esp32S3_8MB_PSRAM_M] ;; legacy alias extends = env:esp32S3_8MB_PSRAM_M_opi +[env:rfp_esp32s3_wroom1_n16r8_3x106] +;; RFP ESP32-S3 WROOM-1 N16R8, 16MB flash / 8MB OPI PSRAM, 3 outputs x 106 pixels +extends = env:esp32S3_8MB_PSRAM_M_opi +board_upload.flash_size = 16MB +board_upload.maximum_size = 16777216 +board_build.partitions = tools/WLED_ESP32_16MB.csv +build_unflags = ${env:esp32S3_8MB_PSRAM_M_opi.build_unflags} + -D WLED_RELEASE_NAME=esp32S3_8MB_PSRAM_M_opi + -D LEDPIN=21 + -D BTNPIN=0 + -D RLYPIN=1 + -D IRPIN=-1 + -D AUDIOPIN=-1 +build_flags = ${env:esp32S3_8MB_PSRAM_M_opi.build_flags} + -D WLED_RELEASE_NAME=RFP_ESP32S3_N16R8_3x106 + -D LEDPIN=4 + -D DATA_PINS=4,5,6 + -D PIXEL_COUNTS=106,106,106 + -D DEFAULT_LED_COUNT=106 + -D STATUSPIXELPIN=48 + -D STATUSPIXELCOLORORDER=COL_ORDER_GRB + -D BTNPIN=-1 + -D RLYPIN=-1 + -D IRPIN=-1 + -D AUDIOPIN=-1 + [env:esp32S3_8MB_S] ;; MM for ESP32-S3 boards - FASTPATH + optimize for speed; ; HUB75 support included (may still have pin conflicts) extends = esp32_4MB_V4_M_base diff --git a/tools/flash_rfp_s3.ps1 b/tools/flash_rfp_s3.ps1 new file mode 100644 index 00000000..112a3a79 --- /dev/null +++ b/tools/flash_rfp_s3.ps1 @@ -0,0 +1,44 @@ +param( + [string]$ComPort, + + [switch]$BuildOnly +) + +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = Split-Path -Parent $scriptDir +$envName = "rfp_esp32s3_wroom1_n16r8_3x106" +$venvPython = Join-Path $repoRoot ".venv\Scripts\python.exe" + +if (-not $BuildOnly -and [string]::IsNullOrWhiteSpace($ComPort)) { + throw "ComPort is required unless -BuildOnly is used." +} + +if (Test-Path $venvPython) { + $pythonCommand = $venvPython + $pythonArgs = @() +} elseif (Get-Command py -ErrorAction SilentlyContinue) { + $pythonCommand = "py" + $pythonArgs = @("-3") +} elseif (Get-Command python -ErrorAction SilentlyContinue) { + $pythonCommand = "python" + $pythonArgs = @() +} else { + throw "No Python runtime found. Install Python or create .venv first." +} + +$pioHome = Join-Path $repoRoot ".piohome" +$env:PLATFORMIO_CORE_DIR = $pioHome +$env:PLATFORMIO_PACKAGES_DIR = Join-Path $pioHome "packages" +$env:PLATFORMIO_PLATFORMS_DIR = Join-Path $pioHome "platforms" +$env:PLATFORMIO_CACHE_DIR = Join-Path $pioHome ".cache" +$env:PLATFORMIO_BUILD_CACHE_DIR = Join-Path $pioHome "buildcache" + +$args = @("-m", "platformio", "run", "-e", $envName) +if (-not $BuildOnly) { + $args += @("-t", "upload", "--upload-port", $ComPort) +} + +& $pythonCommand @pythonArgs @args +exit $LASTEXITCODE diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index a8e4a53c..ceef9105 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -168,6 +168,9 @@ String PinManagerClass::getPinSpecialText(int gpio) { // special purpose PIN in #endif + #if defined(STATUSPIXELPIN) + if (gpio == STATUSPIXELPIN) return(F("WLED Status Pixel")); + #endif #if defined(STATUSLED) if (gpio == STATUSLED) return(F("WLED Status LED")); #endif diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 8015d58a..e5228ec9 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -71,6 +71,73 @@ #endif // WLEDMM end +#if defined(STATUSLED) || defined(STATUSPIXELPIN) +static inline void writeStatusIndicator(uint32_t color) { + #if defined(STATUSPIXELPIN) + if (statusPixelBus != nullptr) { + if (statusPixelBus->canShow()) { + statusPixelBus->setPixelColor(0, color); + statusPixelBus->show(); + } + return; + } + #endif + + #if defined(STATUSLED) + #if STATUSLED >= 0 + #ifdef STATUSLEDINVERTED + digitalWrite(STATUSLED, color ? LOW : HIGH); + #else + digitalWrite(STATUSLED, color ? HIGH : LOW); + #endif + #else + busses.setStatusPixel(color); + #endif + #endif +} +#endif + +#if defined(STATUSPIXELPIN) +#ifndef STATUSPIXELCOLORORDER + #define STATUSPIXELCOLORORDER COL_ORDER_GRB +#endif + +static void initStatusPixelBus() { + if (statusPixelBus != nullptr) return; + if (pinManager.isPinAllocated(STATUSPIXELPIN)) { + USER_PRINTF("Skipping status pixel on GPIO %u because the pin is already in use.\n", STATUSPIXELPIN); + return; + } + + #if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2) + constexpr uint8_t maxStatusPixelBusses = 4; + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + constexpr uint8_t maxStatusPixelBusses = 2; + #else + constexpr uint8_t maxStatusPixelBusses = 8; + #endif + + uint8_t busIndex = busses.getNumBusses(); + if (busIndex >= maxStatusPixelBusses) { + USER_PRINTLN(F("Skipping status pixel because no free hardware LED channel is left.")); + return; + } + + uint8_t pins[] = {STATUSPIXELPIN}; + BusConfig statusCfg(TYPE_WS2812_RGB, pins, 0, 1, STATUSPIXELCOLORORDER, false, 0, RGBW_MODE_MANUAL_ONLY); + statusPixelBus = new BusDigital(statusCfg, busIndex, busses.getColorOrderMap()); + if (statusPixelBus == nullptr || !statusPixelBus->isOk()) { + delete statusPixelBus; + statusPixelBus = nullptr; + USER_PRINTLN(F("Failed to initialize onboard status pixel.")); + return; + } + + statusPixelBus->setBrightness(255, true); + writeStatusIndicator(0); +} +#endif + #if INCLUDE_xTaskGetHandle && defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(WLED_DEBUG_HEAP)) // WLEDMM stack debug tool - find async_tcp task, and queries it's free stack @@ -883,6 +950,9 @@ void WLED::setup() DEBUG_PRINTLN(F("Initializing strip")); beginStrip(); + #if defined(STATUSPIXELPIN) + initStatusPixelBus(); + #endif DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(getFreeHeapSize()); USER_PRINTLN(F("\nUsermods setup ...")); @@ -1568,58 +1638,79 @@ void WLED::handleConnection() } // If status LED pin is allocated for other uses, does nothing -// else blink at 1Hz when WLED_CONNECTED is false (no WiFi, ?? no Ethernet ??) -// else blink at 2Hz when MQTT is enabled but not connected -// else turn the status LED off +// green blink = DDP realtime active +// blue blink = AP active +// green solid = network connected +// amber fast blink = MQTT configured but disconnected +// red fast blink = WiFi configured but currently disconnected +// off = idle / no status to show void WLED::handleStatusLED() { - #if defined(STATUSLED) - [[maybe_unused]] uint32_t c = 0; + #if defined(STATUSLED) || defined(STATUSPIXELPIN) + uint32_t c = 0; + uint8_t nextType = 0; + uint16_t blinkIntervalMs = 0; - #if STATUSLED>=0 + #if defined(STATUSLED) && STATUSLED>=0 if (pinManager.isPinAllocated(STATUSLED)) { return; //lower priority if something else uses the same pin } #endif - if (WLED_CONNECTED) { + if (realtimeMode == REALTIME_MODE_DDP) { c = RGBW32(0,255,0,0); - ledStatusType = 2; - } else if (WLED_MQTT_CONNECTED) { - c = RGBW32(0,128,0,0); - ledStatusType = 4; + nextType = 2; + blinkIntervalMs = 250; } else if (apActive) { c = RGBW32(0,0,255,0); - ledStatusType = 1; - } - if (ledStatusType) { - if (millis() - ledStatusLastMillis >= (1000/ledStatusType)) { - ledStatusLastMillis = millis(); -#if 1 - // WLEDMM un-comment this to stop the blinking - if ((ledStatusType != 2) && (ledStatusType != 4)) - ledStatusState = !ledStatusState; - else - ledStatusState = HIGH; -#else - ledStatusState = !ledStatusState; -#endif - #if STATUSLED>=0 - digitalWrite(STATUSLED, ledStatusState); - #else - busses.setStatusPixel(ledStatusState ? c : 0); - #endif - } - } else { - #if STATUSLED>=0 - #ifdef STATUSLEDINVERTED - digitalWrite(STATUSLED, HIGH); - #else - digitalWrite(STATUSLED, LOW); - #endif - #else - busses.setStatusPixel(0); + nextType = 2; + blinkIntervalMs = 500; + } else if (WLED_CONNECTED) { + #ifndef WLED_DISABLE_MQTT + if (mqttEnabled && mqttServer[0] != 0 && !WLED_MQTT_CONNECTED) { + c = RGBW32(255,96,0,0); + nextType = 3; + blinkIntervalMs = 250; + } else #endif + { + c = RGBW32(0,255,0,0); + nextType = 1; + } + } else if (WLED_WIFI_CONFIGURED) { + c = RGBW32(255,0,0,0); + nextType = 3; + blinkIntervalMs = 250; + } + + if (nextType != ledStatusType) { + ledStatusType = nextType; + ledStatusLastMillis = millis(); + ledStatusState = (nextType == 1); + writeStatusIndicator(ledStatusState ? c : 0); + return; + } + + if (nextType == 0) { + if (ledStatusState) { + ledStatusState = false; + writeStatusIndicator(0); + } + return; + } + + if (nextType == 1) { + if (!ledStatusState) { + ledStatusState = true; + writeStatusIndicator(c); + } + return; + } + + if (millis() - ledStatusLastMillis >= blinkIntervalMs) { + ledStatusLastMillis = millis(); + ledStatusState = !ledStatusState; + writeStatusIndicator(ledStatusState ? c : 0); } #endif } diff --git a/wled00/wled.h b/wled00/wled.h index d2050e6e..d1b8023c 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -751,12 +751,15 @@ WLED_GLOBAL bool doSerializeConfig _INIT(false); // flag to initiate savi WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers WLED_GLOBAL bool doPublishMqtt _INIT(false); -// status led -#if defined(STATUSLED) +// status led / status pixel +#if defined(STATUSLED) || defined(STATUSPIXELPIN) WLED_GLOBAL unsigned long ledStatusLastMillis _INIT(0); -WLED_GLOBAL uint8_t ledStatusType _INIT(0); // current status type - corresponds to number of blinks per second +WLED_GLOBAL uint8_t ledStatusType _INIT(0); // 0=off, 1=solid, 2=slow blink, 3=fast blink WLED_GLOBAL bool ledStatusState _INIT(false); // the current LED state #endif +#if defined(STATUSPIXELPIN) +WLED_GLOBAL BusDigital* statusPixelBus _INIT(nullptr); +#endif // server library objects WLED_GLOBAL AsyncWebServer server _INIT_N(((80)));