diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yaml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yaml index 0889781..78b9eaa 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yaml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yaml @@ -72,7 +72,7 @@ body: > After a crash, include the whole panic: the `Guru Meditation Error` line, the `Backtrace:` line and the `ELF file SHA256:` line. > Each release has a `SomfyController.ino..elf.zip` that decodes the backtrace: > `xtensa-esp32s3-elf-addr2line -pfiaC -e SomfyController.ino.esp32s3.elf ` - > (`riscv32-esp-elf-addr2line` on the C3, C5 and C6). + > (`riscv32-esp-elf-addr2line` on the C3, C5, C6 and P4). render: bash validations: required: false \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 704dd7e..b80aa1f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -61,7 +61,7 @@ jobs: # elfname: the symbols of that binary, to decode a crash's backtrace # obname: onboard image (bootloader + partitions + firmware + littlefs merged) # for flashing a new chip via USB/serial - # addr_bootloader: chip-dependent (ESP32: 0x1000, S3/C3/C6: 0x0, C5: 0x2000) + # addr_bootloader: chip-dependent (ESP32: 0x1000, S3/C3/C6: 0x0, C5/P4: 0x2000) # addr_fs: must match the spiffs offset in the env's partition table # (0x310000 in both esp32_3MB.csv and esp32_huge_app.csv) include: @@ -110,6 +110,24 @@ jobs: elfname: SomfyController.ino.esp32c6.elf fsname: SomfyController.littlefs.esp32c6.bin obname: SomfyController.onboard.esp32c6.bin + - env: esp32p4 + name: ESP32-P4 + chip: ESP32-P4 + addr_bootloader: "0x2000" + addr_fs: "0x310000" + fwname: SomfyController.ino.esp32p4.bin + elfname: SomfyController.ino.esp32p4.elf + fsname: SomfyController.littlefs.esp32p4.bin + obname: SomfyController.onboard.esp32p4.bin + - env: esp32p4es + name: ESP32-P4 (before v3.0) + chip: ESP32-P4 + addr_bootloader: "0x2000" + addr_fs: "0x310000" + fwname: SomfyController.ino.esp32p4es.bin + elfname: SomfyController.ino.esp32p4es.elf + fsname: SomfyController.littlefs.esp32p4es.bin + obname: SomfyController.onboard.esp32p4es.bin steps: - name: Get Release diff --git a/data-src/index.js b/data-src/index.js index cecd83c..5efa3e6 100644 --- a/data-src/index.js +++ b/data-src/index.js @@ -1578,23 +1578,22 @@ class Wifi { { val: 4, label: 'T-Internet POE - LILYGO', clk: 3, ct: 0, addr: 0, pwr: 16, mdc: 23, mdio: 18 }, { val: 5, label: 'wESP32 v7+ - Silicognition', clk: 0, ct: 2, addr: 0, pwr: -1, mdc: 16, mdio: 17 }, { val: 6, label: 'wESP32 < v7 - Silicognition', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 16, mdio: 17 }, - { val: 1, label: 'WT32-ETH01 - Wireless Tag', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 } + { val: 1, label: 'WT32-ETH01 - Wireless Tag', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 }, + { val: 8, label: 'ESP32-P4-ETH - Waveshare', clk: 0, ct: 1, addr: 1, pwr: 51, mdc: 31, mdio: 52, p4: true } ]; ethClockModes = [{ val: 0, label: 'GPIO0 IN' }, { val: 1, label: 'GPIO0 OUT' }, { val: 2, label: 'GPIO16 OUT' }, { val: 3, label: 'GPIO17 OUT' }]; + // The P4 takes the clock in on GPIO 50 only (eth_clock_id_t in EthPhy.h). + ethClockModesP4 = [{ val: 0, label: 'GPIO50 IN' }]; // The ids the firmware stores (eth_phy_id_t in EthPhy.h). 4 is the DM9051, an SPI chip the ESP32's MAC can't drive. ethPhyTypes = [{ val: 0, label: 'LAN8720' }, { val: 1, label: 'TLK110' }, { val: 2, label: 'RTL8201' }, { val: 3, label: 'DP83848' }, { val: 5, label: 'KSZ8041' }, { val: 6, label: 'KSZ8081' }]; init() { document.getElementById("divNetworkStrength").innerHTML = this.displaySignal(-100); if (this.initialized) return; let addr = []; - this.loadETHDropdown(document.getElementById('selETHClkMode'), this.ethClockModes); this.loadETHDropdown(document.getElementById('selETHPhyType'), this.ethPhyTypes); - this.loadETHDropdown(document.getElementById('selETHBoardType'), this.ethBoardTypes); for (let i = 0; i < 32; i++) addr.push({ val: i, label: `PHY ${i}` }); this.loadETHDropdown(document.getElementById('selETHAddress'), addr); - this.loadETHPins(document.getElementById('selETHPWRPin'), 'power'); - this.loadETHPins(document.getElementById('selETHMDCPin'), 'mdc', 23); - this.loadETHPins(document.getElementById('selETHMDIOPin'), 'mdio', 18); + this.loadETHOptions(); ui.toElement(document.getElementById('divNetAdapter'), { wifi: {ssid:'', passphrase:''}, ethernet: { boardType: 1, wirelessFallback: false, dhcp: true, dns1: '', dns2: '', ip: '', gateway: '' } @@ -1602,14 +1601,27 @@ class Wifi { this.onETHBoardTypeChanged(document.getElementById('selETHBoardType')); this.initialized = true; } - loadETHPins(sel, type, selected) { + isP4() { return (document.getElementById('divContainer').getAttribute('data-chipmodel') || '').startsWith('p4'); } + clockModes() { return this.isP4() ? this.ethClockModesP4 : this.ethClockModes; } + // The board presets, clock modes and pins of the chip: the ESP32's or the + // P4's. The chip comes with the sync, after init(). + loadETHOptions() { + let p4 = this.isP4(); + this.loadETHDropdown(document.getElementById('selETHClkMode'), this.clockModes()); + this.loadETHDropdown(document.getElementById('selETHBoardType'), this.ethBoardTypes.filter(b => b.val === 0 || (b.p4 === true) === p4)); + let maxPin = p4 ? 54 : 35; + this.loadETHPins(document.getElementById('selETHPWRPin'), 'power', undefined, maxPin); + this.loadETHPins(document.getElementById('selETHMDCPin'), 'mdc', p4 ? 31 : 23, maxPin); + this.loadETHPins(document.getElementById('selETHMDIOPin'), 'mdio', p4 ? 52 : 18, maxPin); + } + loadETHPins(sel, type, selected, maxPin) { let arr = []; switch (type) { case 'power': arr.push({ val: -1, label: 'None' }); break; } - for (let i = 0; i < 36; i++) { + for (let i = 0; i <= maxPin; i++) { arr.push({ val: i, label: `GPIO ${i}` }); } this.loadETHDropdown(sel, arr, selected); @@ -1652,6 +1664,7 @@ class Wifi { else { let settings = ProtoViews.network(proto.schema, reply); console.log(settings); + this.loadETHOptions(); let hasEthernet = settings.hasEthernet; this.hasEthernet = hasEthernet; document.getElementById('divHardwired').style.display = hasEthernet ? '' : 'none'; @@ -1815,7 +1828,7 @@ class Wifi { if (obj.connType >= 2) { let boardType = this.ethBoardTypes.find(elem => obj.ethernet.boardType === elem.val); let phyType = this.ethPhyTypes.find(elem => obj.ethernet.phyType === elem.val); - let clkMode = this.ethClockModes.find(elem => obj.ethernet.CLKMode === elem.val); + let clkMode = this.clockModes().find(elem => obj.ethernet.CLKMode === elem.val); let div = document.createElement('div'); let html = `
`; html += '
BEWARE ... WARNING ... DANGER
'; @@ -2720,12 +2733,17 @@ class Somfy { { name: 's3', maxPins: 48, inputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32], outputs: [19, 20, 22, 23, 24, 25, 27, 28, 29, 30, 31, 32] }, { name: 'c3', maxPins: 21, inputs: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], outputs: [11, 12, 13, 14, 15, 16, 17, 21] }, { name: 'c5', maxPins: 28, inputs: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22], outputs: [13, 14, 15, 16, 17, 18, 19, 20, 21, 22] }, - { name: 'c6', maxPins: 23, inputs: [], outputs: [] } + { name: 'c6', maxPins: 23, inputs: [], outputs: [] }, + // Without the C6's SDIO link (14-19, 54), USB Serial/JTAG (24, 25), + // Ethernet (28-31, 34, 35, 49-52) and UART0 (37, 38). + { name: 'p4', maxPins: 54, inputs: [14, 15, 16, 17, 18, 19, 24, 25, 28, 29, 30, 31, 34, 35, 37, 38, 49, 50, 51, 52, 54], outputs: [14, 15, 16, 17, 18, 19, 24, 25, 28, 29, 30, 31, 34, 35, 37, 38, 49, 50, 51, 52, 54] } ]; loadPins(type, sel, opt) { while (sel.firstChild) sel.removeChild(sel.firstChild); let cm = document.getElementById('divContainer').getAttribute('data-chipmodel'); + // The pre-v3 P4 build has the same pins. + if (cm === 'p4es') cm = 'p4'; let pm = this.pinMaps.find(x => x.name === cm) || { name: '', maxPins: 39, inputs: [], outputs: [] }; //console.log({ cm: cm, pm: pm }); for (let i = 0; i <= pm.maxPins; i++) { diff --git a/docs/adr/0003-pioarduino-idf55.md b/docs/adr/0003-pioarduino-idf55.md index fb87a35..00f4177 100644 --- a/docs/adr/0003-pioarduino-idf55.md +++ b/docs/adr/0003-pioarduino-idf55.md @@ -15,7 +15,8 @@ We moved every firmware env from `espressif32 @ 6.13.0` (Arduino 2.0.17 on ESP-I > **Amended by [ADR 0006](0006-two-security-modes-eleven-http-routes.md) (4.0.0).** The C3 builds again. Without the old API (ports 8080 and 8081), its image is 1,498,512 bytes at level 2 and leaves 72.6 KB of the OTA slot. It runs on chip revision v0.3 or later only: the framework's C3 libraries are built with `CONFIG_ESP32C3_REV_MIN_3`. Its default radio pins moved off GPIO 12-17, which hold the SPI flash. - **The C5 is updated over USB.** New in 4.0.0. At level 2 its image left 3.4 KB of the OTA slot, under CI's 8 KB, so like the C6 it is released as a debug build with one 3 MB app slot (`esp32_huge_app.csv`), which no OTA can write. The framework's C5 libraries run on chip revision v1.0 or later only (`CONFIG_ESP32C5_REV_MIN_FULL=100`). Its WiFi has 5 GHz too, so the passive scans for the configured network (`src/ESPNetwork.cpp`, 300 ms a channel) cover about 38 channels instead of 13: about 11 s instead of 4 s, depending on the country's channels. -- **Ethernet PHY ids are ours.** Arduino 3.x renumbered `eth_phy_type_t` (`ETH_PHY_GENERIC` is 0). NVS, backups and the web UI keep the Arduino 2.x numbers as `eth_phy_id_t` (`src/EthPhy.h`), translated by name when Ethernet starts. Never cast a stored id to the framework enum. -- **Ethernet only on the original ESP32.** Arduino 3.x has the RMII MAC API only where `CONFIG_ETH_USE_ESP32_EMAC` is set. S3, C3, C5 and C6 build without Ethernet. +- **The P4 has two builds.** New in 4.0.0, for boards such as Waveshare's ESP32-P4-WIFI6-POE-ETH. The framework's P4 libraries run on chip revision v3.01 or later only (`CONFIG_ESP32P4_REV_MIN_FULL=301`), its `esp32p4_es` libraries on revisions before v2.0, so `esp32p4` (chip model `p4`) and `esp32p4es` (`p4es`) each get their own release files. Both keep the OTA slot: at level 2 their images leave about 128 KB. WiFi goes through the board's ESP32-C6 on SDIO (esp-hosted, the framework's pins 14-19 and reset 54), so the C6 has to run a matching esp-hosted slave firmware, which Waveshare's boards ship with. +- **Ethernet PHY ids are ours.** Arduino 3.x renumbered `eth_phy_type_t` (`ETH_PHY_GENERIC` is 0). NVS, backups and the web UI keep the Arduino 2.x numbers as `eth_phy_id_t` (`src/EthPhy.h`), translated by name when Ethernet starts. Never cast a stored id to the framework enum. The RMII clock mode is kept the same way, as `eth_clock_id_t` in the ESP32's numbering: the P4's `eth_clock_mode_t` is the IDF's `emac_rmii_clock_mode_t`, whose 0 is a deprecated default. On the P4 only clock in (GPIO 50) translates, since `ETH.begin()` keeps the clock on that pin. +- **Ethernet only on the original ESP32 and the P4.** Arduino 3.x has the RMII MAC API only where `CONFIG_ETH_USE_ESP32_EMAC` is set. S3, C3, C5 and C6 build without Ethernet. - **Updates roll back until proven.** We override the core's `verifyRollbackLater()` hook to return true, and `loop()` marks the new firmware valid once it has run 60 s and the network is up. A reset before then boots the previous firmware. The LittleFS image an update wrote stays, so the old firmware runs with the new web UI. - **Tooling.** pioarduino needs its own Core fork (`pip install pioarduino`, the pioarduino IDE extension in VS Code), which it installs into `~/.platformio/penv` in place of PlatformIO Core. diff --git a/platformio.ini b/platformio.ini index 1803eb4..928c322 100644 --- a/platformio.ini +++ b/platformio.ini @@ -126,6 +126,26 @@ build_flags = ${esp32.build_flags} -DCORE_DEBUG_LEVEL=3 +; ESP32-P4 boards such as Waveshare's ESP32-P4-WIFI6-POE-ETH: WiFi through an +; ESP32-C6 on SDIO (esp-hosted), Ethernet on the P4's own RMII MAC. The +; framework's P4 libraries run on chip revision v3.01 or later only, and its +; esp32p4_es libraries on revisions before v2.0, so each gets its own build. +[env:esp32p4] +extends = esp32 +board = esp32-p4_r3 +board_build.partitions = esp32_3MB.csv +build_flags = + ${dualcore.build_flags} + -DCORE_DEBUG_LEVEL=2 + +[env:esp32p4es] +extends = esp32 +board = esp32-p4 +board_build.partitions = esp32_3MB.csv +build_flags = + ${dualcore.build_flags} + -DCORE_DEBUG_LEVEL=2 + ; Host-side unit tests: pio test -e native (needs gcc/g++ on PATH). ; Only the sources the tests cover are built; test/stubs stands in for Arduino.h. [env:native] diff --git a/src/ConfigFile.cpp b/src/ConfigFile.cpp index 27926cd..8b8445c 100644 --- a/src/ConfigFile.cpp +++ b/src/ConfigFile.cpp @@ -149,7 +149,7 @@ static net_record_t netRecord() { #if HAS_ETHERNET rec.ethBoardType = settings.Ethernet.boardType; rec.ethPhyType = settings.Ethernet.phyType; - rec.ethClkMode = static_cast(settings.Ethernet.CLKMode); + rec.ethClkMode = settings.Ethernet.CLKMode; rec.ethPhyAddress = settings.Ethernet.phyAddress; rec.ethPWRPin = settings.Ethernet.PWRPin; rec.ethMDCPin = settings.Ethernet.MDCPin; @@ -196,7 +196,7 @@ static void applyNetRecord(const net_record_t &rec, const restore_options_t &opt ESP_LOGI(TAG, "Restoring Ethernet adapter settings"); settings.Ethernet.boardType = rec.ethBoardType; settings.Ethernet.phyType = rec.ethPhyType; - settings.Ethernet.CLKMode = static_cast(rec.ethClkMode); + settings.Ethernet.CLKMode = rec.ethClkMode; settings.Ethernet.phyAddress = rec.ethPhyAddress; settings.Ethernet.PWRPin = rec.ethPWRPin; settings.Ethernet.MDCPin = rec.ethMDCPin; diff --git a/src/ConfigSettings.cpp b/src/ConfigSettings.cpp index 84fe3f8..a95cd01 100644 --- a/src/ConfigSettings.cpp +++ b/src/ConfigSettings.cpp @@ -145,6 +145,17 @@ bool ConfigSettings::begin() { strcpy(this->chipModel, "c6"); break; #endif + #ifdef CONFIG_IDF_TARGET_ESP32P4 + // Chips before v3.0 run their own build (esp32p4_es libraries), so they + // get their own release files. + case esp_chip_model_t::CHIP_ESP32P4: + #if CONFIG_ESP32P4_SELECTS_REV_LESS_V3 + strcpy(this->chipModel, "p4es"); + #else + strcpy(this->chipModel, "p4"); + #endif + break; + #endif default: sprintf(this->chipModel, "UNK%d", static_cast(ci.model)); break; @@ -499,7 +510,7 @@ bool EthernetSettings::begin() { bool EthernetSettings::fromJSON(JsonObject &obj) { if(!obj["boardType"].isNull()) this->boardType = obj["boardType"]; if(!obj["phyAddress"].isNull()) this->phyAddress = obj["phyAddress"]; - if(!obj["CLKMode"].isNull()) this->CLKMode = static_cast(obj["CLKMode"]); + if(!obj["CLKMode"].isNull()) this->CLKMode = obj["CLKMode"]; if(!obj["phyType"].isNull()) this->phyType = obj["phyType"]; if(!obj["PWRPin"].isNull()) this->PWRPin = obj["PWRPin"]; if(!obj["MDCPin"].isNull()) this->MDCPin = obj["MDCPin"]; @@ -507,9 +518,7 @@ bool EthernetSettings::fromJSON(JsonObject &obj) { return true; } bool EthernetSettings::usesPin(uint8_t pin) { - if((this->CLKMode == 0 || this->CLKMode == 1) && pin == 0) return true; - else if(this->CLKMode == 2 && pin == 16) return true; - else if(this->CLKMode == 3 && pin == 17) return true; + if(ethClockPin(this->CLKMode) == pin) return true; else if(this->PWRPin == pin) return true; else if(this->MDCPin == pin) return true; else if(this->MDIOPin == pin) return true; @@ -522,7 +531,7 @@ bool EthernetSettings::save() { pref.putChar("boardType", this->boardType); pref.putChar("phyAddress", this->phyAddress); pref.putChar("phyType", this->phyType); - pref.putChar("CLKMode", static_cast(this->CLKMode)); + pref.putChar("CLKMode", this->CLKMode); pref.putChar("PWRPin", this->PWRPin); pref.putChar("MDCPin", this->MDCPin); pref.putChar("MDIOPin", this->MDIOPin); @@ -534,7 +543,7 @@ bool EthernetSettings::load() { pref.begin("ETH"); this->boardType = pref.getChar("boardType", this->boardType); this->phyType = pref.getChar("phyType", this->phyType); - this->CLKMode = static_cast(pref.getChar("CLKMode", ETH_CLOCK_GPIO0_IN)); + this->CLKMode = pref.getChar("CLKMode", this->CLKMode); this->phyAddress = pref.getChar("phyAddress", this->phyAddress); this->PWRPin = pref.getChar("PWRPin", this->PWRPin); this->MDCPin = pref.getChar("MDCPin", this->MDCPin); diff --git a/src/ConfigSettings.h b/src/ConfigSettings.h index 9c6689b..0effc5a 100644 --- a/src/ConfigSettings.h +++ b/src/ConfigSettings.h @@ -88,14 +88,26 @@ class WifiSettings: BaseSettings { class EthernetSettings: BaseSettings { public: EthernetSettings(); + uint8_t CLKMode = static_cast(eth_clock_id_t::GPIO0_IN); // An eth_clock_id_t, see EthPhy.h. +#if defined(CONFIG_IDF_TARGET_ESP32P4) + // The IP101 wiring of Espressif's P4 boards, which Waveshare's ESP32-P4-ETH + // boards copy, and the web UI's board type for it. The ESP32's defaults + // would put MDIO on an SDIO pin. + uint8_t boardType = 8; + uint8_t phyType = static_cast(eth_phy_id_t::TLK110); + int8_t phyAddress = 1; + int8_t PWRPin = 51; + int8_t MDCPin = 31; + int8_t MDIOPin = 52; +#else uint8_t boardType = 0; // These board types are enumerated in the ui and used to set the chip settings. uint8_t phyType = static_cast(eth_phy_id_t::LAN8720); // An eth_phy_id_t, see EthPhy.h. - eth_clock_mode_t CLKMode = ETH_CLOCK_GPIO0_IN; // The LAN8720 wiring Arduino 2.x defaulted to. 3.x has no defaults. int8_t phyAddress = 0; int8_t PWRPin = -1; int8_t MDCPin = 23; int8_t MDIOPin = 18; +#endif bool begin(); bool fromJSON(JsonObject &obj); diff --git a/src/DeviceOps.cpp b/src/DeviceOps.cpp index 777b0aa..d0a60b0 100644 --- a/src/DeviceOps.cpp +++ b/src/DeviceOps.cpp @@ -62,7 +62,7 @@ static void writeSettings(ProtoWriter &w, settings_section_t section) { w.add(proto_field_t::hasEthernet, true); w.add(proto_field_t::boardType, settings.Ethernet.boardType); w.add(proto_field_t::phyType, settings.Ethernet.phyType); - w.add(proto_field_t::CLKMode, static_cast(settings.Ethernet.CLKMode)); + w.add(proto_field_t::CLKMode, settings.Ethernet.CLKMode); w.add(proto_field_t::phyAddress, settings.Ethernet.phyAddress); w.add(proto_field_t::PWRPin, settings.Ethernet.PWRPin); w.add(proto_field_t::MDCPin, settings.Ethernet.MDCPin); diff --git a/src/ESPNetwork.cpp b/src/ESPNetwork.cpp index 28fce1e..43eb53f 100644 --- a/src/ESPNetwork.cpp +++ b/src/ESPNetwork.cpp @@ -356,11 +356,14 @@ bool ESPNetwork::connectWired() { ETH.setHostname("ESPSomfy-RTS"); ESP_LOGD(TAG, "Set hostname to: %s", ETH.getHostname()); eth_phy_type_t phyType; + eth_clock_mode_t clkMode; bool begun = false; if(!ethPhyType(settings.Ethernet.phyType, phyType)) ESP_LOGE(TAG, "Ethernet PHY type %u is not supported", (unsigned int)settings.Ethernet.phyType); + else if(!ethClockMode(settings.Ethernet.CLKMode, clkMode)) + ESP_LOGE(TAG, "Ethernet clock mode %u is not supported", (unsigned int)settings.Ethernet.CLKMode); else - begun = ETH.begin(phyType, settings.Ethernet.phyAddress, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.PWRPin, settings.Ethernet.CLKMode); + begun = ETH.begin(phyType, settings.Ethernet.phyAddress, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.PWRPin, clkMode); if(!begun) { ESP_LOGE(TAG, "Ethernet Begin failed"); this->ethStarted = false; diff --git a/src/EthPhy.h b/src/EthPhy.h index 004b5b4..5f2f608 100644 --- a/src/EthPhy.h +++ b/src/EthPhy.h @@ -5,8 +5,8 @@ #include #endif -// Ethernet needs the RMII MAC, which only the original ESP32 has. The C3, C5, -// S3 and C6 build without it. +// Ethernet needs the RMII MAC, which only the original ESP32 and the P4 have. +// The C3, C5, S3 and C6 build without it. #if defined(CONFIG_ETH_USE_ESP32_EMAC) && CONFIG_ETH_USE_ESP32_EMAC #define HAS_ETHERNET 1 #else @@ -26,6 +26,32 @@ enum class eth_phy_id_t : uint8_t { KSZ8081 = 6 }; +// RMII clock ids as stored in NVS and backups and sent to the web UI: the +// original ESP32's eth_clock_mode_t values. The P4's eth_clock_mode_t is the +// IDF's emac_rmii_clock_mode_t, a different enum, so an id is translated. +enum class eth_clock_id_t : uint8_t { + GPIO0_IN = 0, // The clock comes in. On the P4, on GPIO 50. + GPIO0_OUT = 1, + GPIO16_OUT = 2, + GPIO17_OUT = 3 +}; + +// The GPIO a stored clock id puts the RMII clock on, -1 for an id the chip +// can't use. The P4's ETH.begin() takes the clock in on GPIO 50 only. +inline int8_t ethClockPin(uint8_t id) { +#if defined(CONFIG_IDF_TARGET_ESP32P4) + return static_cast(id) == eth_clock_id_t::GPIO0_IN ? 50 : -1; +#else + switch(static_cast(id)) { + case eth_clock_id_t::GPIO0_IN: + case eth_clock_id_t::GPIO0_OUT: return 0; + case eth_clock_id_t::GPIO16_OUT: return 16; + case eth_clock_id_t::GPIO17_OUT: return 17; + default: return -1; + } +#endif +} + #if HAS_ETHERNET #include // Sets type to the framework's PHY for a stored id. False for an id the RMII @@ -41,5 +67,20 @@ inline bool ethPhyType(uint8_t id, eth_phy_type_t &type) { default: return false; } } +// Sets mode to the framework's clock mode for a stored id. False for an id +// the chip can't use. +inline bool ethClockMode(uint8_t id, eth_clock_mode_t &mode) { + switch(static_cast(id)) { +#if defined(CONFIG_IDF_TARGET_ESP32P4) + case eth_clock_id_t::GPIO0_IN: mode = EMAC_CLK_EXT_IN; return true; +#else + case eth_clock_id_t::GPIO0_IN: mode = ETH_CLOCK_GPIO0_IN; return true; + case eth_clock_id_t::GPIO0_OUT: mode = ETH_CLOCK_GPIO0_OUT; return true; + case eth_clock_id_t::GPIO16_OUT: mode = ETH_CLOCK_GPIO16_OUT; return true; + case eth_clock_id_t::GPIO17_OUT: mode = ETH_CLOCK_GPIO17_OUT; return true; +#endif + default: return false; + } +} #endif #endif diff --git a/src/ProtoSettings.cpp b/src/ProtoSettings.cpp index e1b6c18..09fad81 100644 --- a/src/ProtoSettings.cpp +++ b/src/ProtoSettings.cpp @@ -47,9 +47,10 @@ static const settings_field_t FIELDS[] = { {settings_part_t::ethernet, proto_field_t::phyType, nullptr, 0, 255}, {settings_part_t::ethernet, proto_field_t::CLKMode, nullptr, 0, 3}, {settings_part_t::ethernet, proto_field_t::phyAddress, nullptr, -1, 31}, - {settings_part_t::ethernet, proto_field_t::PWRPin, nullptr, -1, 48}, - {settings_part_t::ethernet, proto_field_t::MDCPin, nullptr, 0, 48}, - {settings_part_t::ethernet, proto_field_t::MDIOPin, nullptr, 0, 48}, + // Pins run to GPIO 54, the P4's last. + {settings_part_t::ethernet, proto_field_t::PWRPin, nullptr, -1, 54}, + {settings_part_t::ethernet, proto_field_t::MDCPin, nullptr, 0, 54}, + {settings_part_t::ethernet, proto_field_t::MDIOPin, nullptr, 0, 54}, {settings_part_t::ip, proto_field_t::dhcp, nullptr, 0, 0}, {settings_part_t::ip, proto_field_t::ip, nullptr, 0, 15}, {settings_part_t::ip, proto_field_t::gateway, nullptr, 0, 15}, @@ -75,12 +76,12 @@ static const settings_field_t FIELDS[] = { // RTS, RTW, RTV, GP Relay or GP Remote (radio_proto), checked on its own. {settings_part_t::radio, proto_field_t::proto, nullptr, 0, 9}, {settings_part_t::radio, proto_field_t::enabled, nullptr, 0, 0}, - {settings_part_t::radio, proto_field_t::TXPin, nullptr, 0, 48}, - {settings_part_t::radio, proto_field_t::RXPin, nullptr, 0, 48}, - {settings_part_t::radio, proto_field_t::SCKPin, nullptr, 0, 48}, - {settings_part_t::radio, proto_field_t::MOSIPin, nullptr, 0, 48}, - {settings_part_t::radio, proto_field_t::MISOPin, nullptr, 0, 48}, - {settings_part_t::radio, proto_field_t::CSNPin, nullptr, 0, 48}, + {settings_part_t::radio, proto_field_t::TXPin, nullptr, 0, 54}, + {settings_part_t::radio, proto_field_t::RXPin, nullptr, 0, 54}, + {settings_part_t::radio, proto_field_t::SCKPin, nullptr, 0, 54}, + {settings_part_t::radio, proto_field_t::MOSIPin, nullptr, 0, 54}, + {settings_part_t::radio, proto_field_t::MISOPin, nullptr, 0, 54}, + {settings_part_t::radio, proto_field_t::CSNPin, nullptr, 0, 54}, // What the CC1101 can be set to, in MHz and kHz. {settings_part_t::radio, proto_field_t::frequency, nullptr, 30000, 92800}, {settings_part_t::radio, proto_field_t::rxBandwidth, nullptr, 5803, 81250}, diff --git a/src/Transceiver.cpp b/src/Transceiver.cpp index c66280c..9b07cd0 100644 --- a/src/Transceiver.cpp +++ b/src/Transceiver.cpp @@ -506,6 +506,21 @@ void transceiver_config_t::load() { this->SCKPin = 6; this->CSNPin = 0; break; +#endif +#ifdef CONFIG_IDF_TARGET_ESP32P4 + case esp_chip_model_t::CHIP_ESP32P4: + // The SPI pins of the framework's variant for Waveshare's ESP32-P4-ETH + // (variants/waveshare_p4_poe_eth); TX and RX take 2 and 3, which that + // board leaves free. The C6's SDIO link holds 14-19 and 54, Ethernet + // 28-31, 34, 35 and 49-52, and 34-38 are strapping pins. 46 is on LDO + // channel 4, which the core turns on when a pin of it is used. + this->TXPin = 2; + this->RXPin = 3; + this->MOSIPin = 32; + this->MISOPin = 33; + this->SCKPin = 27; + this->CSNPin = 46; + break; #endif default: this->TXPin = 13; diff --git a/src/Utils.h b/src/Utils.h index ffd3483..a134453 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -85,7 +85,7 @@ const char *wifiAuthModeName(uint8_t authMode); bool otaProven(bool networkUp, unsigned long uptimeMs, unsigned long minUptimeMs); // A release asset's name: "SomfyController..esp32.bin", kind // "ino" for the firmware or "littlefs" for the web UI image, chipModel as in -// ConfigSettings ("" for the ESP32, "s3", "c3", "c5", "c6"). +// ConfigSettings ("" for the ESP32, "s3", "c3", "c5", "c6", "p4", "p4es"). void releaseAssetName(char *buf, size_t size, const char *kind, const char *chipModel); // A coredump backtrace as " 0x%08x" per frame. bt holds maxFrames entries and // depth is how many the dump claims are valid. diff --git a/test/stubs/ETH.h b/test/stubs/ETH.h index c974957..a5e50b9 100644 --- a/test/stubs/ETH.h +++ b/test/stubs/ETH.h @@ -13,4 +13,20 @@ typedef enum { ETH_PHY_KSZ8081, ETH_PHY_MAX } eth_phy_type_t; +#if defined(CONFIG_IDF_TARGET_ESP32P4) +// The P4's ETH.h takes the IDF's enum, where 0 is a deprecated default. +typedef enum { + EMAC_CLK_DEFAULT, + EMAC_CLK_EXT_IN, + EMAC_CLK_OUT +} emac_rmii_clock_mode_t; +typedef emac_rmii_clock_mode_t eth_clock_mode_t; +#else +typedef enum { + ETH_CLOCK_GPIO0_IN, + ETH_CLOCK_GPIO0_OUT, + ETH_CLOCK_GPIO16_OUT, + ETH_CLOCK_GPIO17_OUT +} eth_clock_mode_t; +#endif #endif diff --git a/test/test_ethphy/test_ethphy.cpp b/test/test_ethphy/test_ethphy.cpp index 08228d2..b86b5eb 100644 --- a/test/test_ethphy/test_ethphy.cpp +++ b/test/test_ethphy/test_ethphy.cpp @@ -52,6 +52,39 @@ static void test_rejects_unknown_ids() { TEST_ASSERT_FALSE(ethPhyType(255, type)); } +/********************************************************************* + * Clock modes + ********************************************************************/ +static void test_clock_ids_match_stored_values() { + TEST_ASSERT_EQUAL_UINT8(0, static_cast(eth_clock_id_t::GPIO0_IN)); + TEST_ASSERT_EQUAL_UINT8(1, static_cast(eth_clock_id_t::GPIO0_OUT)); + TEST_ASSERT_EQUAL_UINT8(2, static_cast(eth_clock_id_t::GPIO16_OUT)); + TEST_ASSERT_EQUAL_UINT8(3, static_cast(eth_clock_id_t::GPIO17_OUT)); +} +static void test_translates_each_clock_mode() { + eth_clock_mode_t mode; + TEST_ASSERT_TRUE(ethClockMode(0, mode)); + TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO0_IN, mode); + TEST_ASSERT_TRUE(ethClockMode(1, mode)); + TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO0_OUT, mode); + TEST_ASSERT_TRUE(ethClockMode(2, mode)); + TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO16_OUT, mode); + TEST_ASSERT_TRUE(ethClockMode(3, mode)); + TEST_ASSERT_EQUAL(ETH_CLOCK_GPIO17_OUT, mode); +} +static void test_rejects_unknown_clock_ids() { + eth_clock_mode_t mode; + TEST_ASSERT_FALSE(ethClockMode(4, mode)); + TEST_ASSERT_FALSE(ethClockMode(255, mode)); +} +static void test_clock_pins() { + TEST_ASSERT_EQUAL_INT8(0, ethClockPin(0)); + TEST_ASSERT_EQUAL_INT8(0, ethClockPin(1)); + TEST_ASSERT_EQUAL_INT8(16, ethClockPin(2)); + TEST_ASSERT_EQUAL_INT8(17, ethClockPin(3)); + TEST_ASSERT_EQUAL_INT8(-1, ethClockPin(4)); +} + int main(int, char **) { UNITY_BEGIN(); RUN_TEST(test_ids_match_stored_values); @@ -59,5 +92,9 @@ int main(int, char **) { RUN_TEST(test_lan8720_is_not_the_framework_zero); RUN_TEST(test_rejects_dm9051); RUN_TEST(test_rejects_unknown_ids); + RUN_TEST(test_clock_ids_match_stored_values); + RUN_TEST(test_translates_each_clock_mode); + RUN_TEST(test_rejects_unknown_clock_ids); + RUN_TEST(test_clock_pins); return UNITY_END(); } diff --git a/test/test_ethphy_p4/test_ethphy_p4.cpp b/test/test_ethphy_p4/test_ethphy_p4.cpp new file mode 100644 index 0000000..8d34b14 --- /dev/null +++ b/test/test_ethphy_p4/test_ethphy_p4.cpp @@ -0,0 +1,49 @@ +#include +// As on the ESP32-P4, which has the RMII MAC too but takes the IDF's clock +// enum. +#define CONFIG_ETH_USE_ESP32_EMAC 1 +#define CONFIG_IDF_TARGET_ESP32P4 1 +#include "EthPhy.h" + +void setUp() {} +void tearDown() {} + +static void test_builds_with_ethernet() { + TEST_ASSERT_EQUAL(1, HAS_ETHERNET); +} +static void test_ip101_is_the_tlk110_id() { + // Waveshare's ESP32-P4-ETH boards have an IP101, which Arduino drives as + // the TLK110; the web UI's preset stores 1. + eth_phy_type_t type = ETH_PHY_MAX; + TEST_ASSERT_TRUE(ethPhyType(1, type)); + TEST_ASSERT_EQUAL(ETH_PHY_TLK110, type); +} +static void test_clock_in_is_ext_in() { + // A cast would give the IDF's deprecated EMAC_CLK_DEFAULT. + eth_clock_mode_t mode = EMAC_CLK_DEFAULT; + TEST_ASSERT_TRUE(ethClockMode(static_cast(eth_clock_id_t::GPIO0_IN), mode)); + TEST_ASSERT_EQUAL(EMAC_CLK_EXT_IN, mode); +} +static void test_rejects_clock_out() { + // ETH.begin() keeps the clock on GPIO 50, which only takes a clock in. + eth_clock_mode_t mode; + TEST_ASSERT_FALSE(ethClockMode(1, mode)); + TEST_ASSERT_FALSE(ethClockMode(2, mode)); + TEST_ASSERT_FALSE(ethClockMode(3, mode)); + TEST_ASSERT_FALSE(ethClockMode(4, mode)); +} +static void test_clock_pin_is_50() { + TEST_ASSERT_EQUAL_INT8(50, ethClockPin(0)); + TEST_ASSERT_EQUAL_INT8(-1, ethClockPin(1)); + TEST_ASSERT_EQUAL_INT8(-1, ethClockPin(3)); +} + +int main(int, char **) { + UNITY_BEGIN(); + RUN_TEST(test_builds_with_ethernet); + RUN_TEST(test_ip101_is_the_tlk110_id); + RUN_TEST(test_clock_in_is_ext_in); + RUN_TEST(test_rejects_clock_out); + RUN_TEST(test_clock_pin_is_50); + return UNITY_END(); +} diff --git a/test/test_protosettings/test_protosettings.cpp b/test/test_protosettings/test_protosettings.cpp index f1e2ec7..5d53618 100644 --- a/test/test_protosettings/test_protosettings.cpp +++ b/test/test_protosettings/test_protosettings.cpp @@ -93,7 +93,7 @@ static void test_whole_numbers_are_checked_against_what_the_setting_holds() { TEST_ASSERT_EQUAL_STRING("connMode must be 1-3", expand(R"({"se":"network","cn":0})")); TEST_ASSERT_EQUAL_STRING("port must be 1-65535", expand(R"({"se":"mqtt","po":65536})")); TEST_ASSERT_EQUAL_STRING("txPower must be -30-12", expand(R"({"se":"radio","tp":13})")); - TEST_ASSERT_EQUAL_STRING("TXPin must be 0-48", expand(R"({"se":"radio","tx":-1})")); + TEST_ASSERT_EQUAL_STRING("TXPin must be 0-54", expand(R"({"se":"radio","tx":-1})")); TEST_ASSERT_EQUAL_STRING("securityType must be 0-2", expand(R"({"se":"security","sy":3})")); TEST_ASSERT_EQUAL_STRING("port must be 1-65535", expand(R"({"se":"mqtt","po":18446744073709551615})")); // A pin of -1 is none. diff --git a/test/test_utils/test_utils.cpp b/test/test_utils/test_utils.cpp index c95a91e..52e12a7 100644 --- a/test/test_utils/test_utils.cpp +++ b/test/test_utils/test_utils.cpp @@ -284,6 +284,10 @@ static void test_releaseAssetName_matches_the_release_workflow() { TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32c5.bin", buf); releaseAssetName(buf, sizeof(buf), "littlefs", "c6"); TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32c6.bin", buf); + releaseAssetName(buf, sizeof(buf), "ino", "p4"); + TEST_ASSERT_EQUAL_STRING("SomfyController.ino.esp32p4.bin", buf); + releaseAssetName(buf, sizeof(buf), "littlefs", "p4es"); + TEST_ASSERT_EQUAL_STRING("SomfyController.littlefs.esp32p4es.bin", buf); } /*********************************************************************