diff --git a/.github/scripts/on-release.sh b/.github/scripts/on-release.sh index 0b0c48fc82c..38f58e0b4be 100755 --- a/.github/scripts/on-release.sh +++ b/.github/scripts/on-release.sh @@ -36,6 +36,12 @@ echo "Event: $GITHUB_EVENT_NAME, Repo: $GITHUB_REPOSITORY, Path: $GITHUB_WORKSPA echo "Action: $action, Branch: $RELEASE_BRANCH, ID: $RELEASE_ID" echo "Tag: $RELEASE_TAG, Draft: $draft, Pre-Release: $RELEASE_PRE" +# Try extracting something like a JSON with a "boards" array/element and "vendor" fields +BOARDS=`echo $RELEASE_BODY | grep -Pzo '(?s){.*}' | jq -r '.boards[]? // .boards? // empty' | xargs echo -n 2>/dev/null` +VENDOR=`echo $RELEASE_BODY | grep -Pzo '(?s){.*}' | jq -r '.vendor? // empty' | xargs echo -n 2>/dev/null` +if ! [ -z "${BOARDS}" ]; then echo "Releasing board(s): $BOARDS" ; fi +if ! [ -z "${VENDOR}" ]; then echo "Setting packager: $VENDOR" ; fi + function get_file_size(){ local file="$1" if [[ "$OSTYPE" == "darwin"* ]]; then @@ -171,12 +177,26 @@ mkdir -p "$PKG_DIR/tools" # Copy all core files to the package folder echo "Copying files for packaging ..." -cp -f "$GITHUB_WORKSPACE/boards.txt" "$PKG_DIR/" +if [ -z "${BOARDS}" ]; then + # Copy all variants + cp -f "$GITHUB_WORKSPACE/boards.txt" "$PKG_DIR/" + cp -Rf "$GITHUB_WORKSPACE/variants" "$PKG_DIR/" +else + # Remove all entries not starting with any board code or "menu." from boards.txt + cat "$GITHUB_WORKSPACE/boards.txt" | grep "^menu\." > "$PKG_DIR/boards.txt" + for board in ${BOARDS} ; do + cat "$GITHUB_WORKSPACE/boards.txt" | grep "^${board}\." >> "$PKG_DIR/boards.txt" + done + # Copy only relevant variant files + mkdir "$PKG_DIR/variants/" + for variant in `cat ${PKG_DIR}/boards.txt | grep "\.variant=" | cut -d= -f2` ; do + cp -Rf "$GITHUB_WORKSPACE/variants/${variant}" "$PKG_DIR/variants/" + done +fi cp -f "$GITHUB_WORKSPACE/package.json" "$PKG_DIR/" cp -f "$GITHUB_WORKSPACE/programmers.txt" "$PKG_DIR/" cp -Rf "$GITHUB_WORKSPACE/cores" "$PKG_DIR/" cp -Rf "$GITHUB_WORKSPACE/libraries" "$PKG_DIR/" -cp -Rf "$GITHUB_WORKSPACE/variants" "$PKG_DIR/" cp -f "$GITHUB_WORKSPACE/tools/espota.exe" "$PKG_DIR/tools/" cp -f "$GITHUB_WORKSPACE/tools/espota.py" "$PKG_DIR/tools/" cp -f "$GITHUB_WORKSPACE/tools/gen_esp32part.py" "$PKG_DIR/tools/" @@ -209,6 +229,11 @@ sed 's/debug.server.openocd.scripts_dir={runtime.platform.path}\/tools\/openocd- sed 's/debug.server.openocd.scripts_dir.windows={runtime.platform.path}\\tools\\openocd-esp32\\share\\openocd\\scripts\\/debug.server.openocd.scripts_dir.windows=\{runtime.tools.openocd-esp32.path\}\\share\\openocd\\scripts\\/g' \ > "$PKG_DIR/platform.txt" +if ! [ -z ${VENDOR} ]; then + # Append vendor name to platform.txt to create a separate section + sed -i "/^name=.*/s/$/ ($VENDOR)/" "$PKG_DIR/platform.txt" +fi + # Add header with version information echo "Generating core_version.h ..." ver_define=`echo $RELEASE_TAG | tr "[:lower:].\055" "[:upper:]_"` diff --git a/boards.txt b/boards.txt index c2415668a52..c6f61851cf2 100644 --- a/boards.txt +++ b/boards.txt @@ -24112,3 +24112,54 @@ nebulas3.menu.EraseFlash.all=Enabled nebulas3.menu.EraseFlash.all.upload.erase_cmd=-e ############################################################## + +nano_nora.name=Arduino Nano ESP32 +nano_nora.vid.0=0x2341 +nano_nora.pid.0=0x0070 +nano_nora.upload_port.0.vid=0x2341 +nano_nora.upload_port.0.pid=0x0070 + +nano_nora.bootloader.tool=esptool_py +nano_nora.bootloader.tool.default=esptool_py + +nano_nora.upload.tool=dfu-util +nano_nora.upload.tool.default=dfu-util +nano_nora.upload.tool.network=esp_ota +nano_nora.upload.protocol=serial +nano_nora.upload.maximum_size=3145728 +nano_nora.upload.maximum_data_size=327680 +nano_nora.upload.use_1200bps_touch=false +nano_nora.upload.wait_for_upload_port=false + +nano_nora.serial.disableDTR=false +nano_nora.serial.disableRTS=false + +nano_nora.build.tarch=xtensa +nano_nora.build.bootloader_addr=0x0 +nano_nora.build.target=esp32s3 +nano_nora.build.mcu=esp32s3 +nano_nora.build.core=esp32 +nano_nora.build.variant=arduino_nano_nora +nano_nora.build.board=NANO_ESP32 +nano_nora.build.code_debug=0 + +nano_nora.build.usb_mode=0 +nano_nora.build.cdc_on_boot=1 +nano_nora.build.msc_on_boot=0 +nano_nora.build.dfu_on_boot=1 +nano_nora.build.f_cpu=240000000L +nano_nora.build.flash_size=16MB +nano_nora.build.flash_freq=80m +nano_nora.build.flash_mode=dio +nano_nora.build.boot=qio +nano_nora.build.boot_freq=80m +nano_nora.build.partitions=app3M_fat9M_fact512k_16MB +nano_nora.build.defines=-DBOARD_HAS_PIN_REMAP -DBOARD_HAS_PSRAM '-DUSB_MANUFACTURER="Arduino"' '-DUSB_PRODUCT="Nano ESP32"' +nano_nora.build.loop_core=-DARDUINO_RUNNING_CORE=1 +nano_nora.build.event_core=-DARDUINO_EVENT_RUNNING_CORE=1 +nano_nora.build.psram_type=opi +nano_nora.build.memory_type={build.boot}_{build.psram_type} + +nano_nora.tools.esptool_py.program.pattern_args=--chip {build.mcu} --port "{serial.port}" --before default_reset --after hard_reset write_flash -z --flash_mode {build.flash_mode} --flash_freq {build.flash_freq} --flash_size {build.flash_size} {build.bootloader_addr} "{build.path}/{build.project_name}.bootloader.bin" 0x8000 "{build.path}/{build.project_name}.partitions.bin" 0xe000 "{runtime.platform.path}/tools/partitions/boot_app0.bin" 0xf70000 "{build.variant.path}/extra/nora_recovery/nora_recovery.ino.bin" 0x10000 "{build.path}/{build.project_name}.bin" + +############################################################## diff --git a/cores/esp32/Arduino.h b/cores/esp32/Arduino.h index 2dc0d543489..d0a71911b63 100644 --- a/cores/esp32/Arduino.h +++ b/cores/esp32/Arduino.h @@ -110,13 +110,13 @@ #define analogInPinToBit(P) (P) #if SOC_GPIO_PIN_COUNT <= 32 #define digitalPinToPort(pin) (0) -#define digitalPinToBitMask(pin) (1UL << (pin)) +#define digitalPinToBitMask(pin) (1UL << digitalPinToGPIONumber(pin)) #define portOutputRegister(port) ((volatile uint32_t*)GPIO_OUT_REG) #define portInputRegister(port) ((volatile uint32_t*)GPIO_IN_REG) #define portModeRegister(port) ((volatile uint32_t*)GPIO_ENABLE_REG) #elif SOC_GPIO_PIN_COUNT <= 64 -#define digitalPinToPort(pin) (((pin)>31)?1:0) -#define digitalPinToBitMask(pin) (1UL << (((pin)>31)?((pin)-32):(pin))) +#define digitalPinToPort(pin) ((digitalPinToGPIONumber(pin)>31)?1:0) +#define digitalPinToBitMask(pin) (1UL << (digitalPinToGPIONumber(pin)&31)) #define portOutputRegister(port) ((volatile uint32_t*)((port)?GPIO_OUT1_REG:GPIO_OUT_REG)) #define portInputRegister(port) ((volatile uint32_t*)((port)?GPIO_IN1_REG:GPIO_IN_REG)) #define portModeRegister(port) ((volatile uint32_t*)((port)?GPIO_ENABLE1_REG:GPIO_ENABLE_REG)) @@ -220,5 +220,6 @@ void noTone(uint8_t _pin); #endif /* __cplusplus */ #include "pins_arduino.h" +#include "io_pin_remap.h" #endif /* _ESP32_CORE_ARDUINO_H_ */ diff --git a/cores/esp32/FunctionalInterrupt.cpp b/cores/esp32/FunctionalInterrupt.cpp index c5a8d37fed4..b278332bd94 100644 --- a/cores/esp32/FunctionalInterrupt.cpp +++ b/cores/esp32/FunctionalInterrupt.cpp @@ -28,7 +28,7 @@ void ARDUINO_ISR_ATTR interruptFunctional(void* arg) void attachInterrupt(uint8_t pin, std::function intRoutine, int mode) { // use the local interrupt routine which takes the ArgStructure as argument - __attachInterruptFunctionalArg (pin, (voidFuncPtrArg)interruptFunctional, new InterruptArgStructure{intRoutine}, mode, true); + __attachInterruptFunctionalArg (digitalPinToGPIONumber(pin), (voidFuncPtrArg)interruptFunctional, new InterruptArgStructure{intRoutine}, mode, true); } extern "C" diff --git a/cores/esp32/HardwareSerial.cpp b/cores/esp32/HardwareSerial.cpp index f7baea0d936..4655cf27a6f 100644 --- a/cores/esp32/HardwareSerial.cpp +++ b/cores/esp32/HardwareSerial.cpp @@ -4,6 +4,7 @@ #include #include "pins_arduino.h" +#include "io_pin_remap.h" #include "HardwareSerial.h" #include "soc/soc_caps.h" #include "driver/uart.h" @@ -370,6 +371,10 @@ void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, in } } + // map logical pins to GPIO numbers + rxPin = digitalPinToGPIONumber(rxPin); + txPin = digitalPinToGPIONumber(txPin); + if(_uart) { // in this case it is a begin() over a previous begin() - maybe to change baud rate // thus do not disable debug output @@ -554,6 +559,12 @@ bool HardwareSerial::setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t r return false; } + // map logical pins to GPIO numbers + rxPin = digitalPinToGPIONumber(rxPin); + txPin = digitalPinToGPIONumber(txPin); + ctsPin = digitalPinToGPIONumber(ctsPin); + rtsPin = digitalPinToGPIONumber(rtsPin); + // uartSetPins() checks if pins are valid for each function and for the SoC bool retCode = uartSetPins(_uart, rxPin, txPin, ctsPin, rtsPin); if (retCode) { diff --git a/cores/esp32/USB.cpp b/cores/esp32/USB.cpp index d82659ebbcf..cc66bb8614a 100644 --- a/cores/esp32/USB.cpp +++ b/cores/esp32/USB.cpp @@ -47,8 +47,14 @@ #define USB_WEBUSB_URL "https://espressif.github.io/arduino-esp32/webusb.html" #endif +#if CFG_TUD_DFU +__attribute__((weak, unused)) uint16_t load_dfu_ota_descriptor(uint8_t * dst, uint8_t * itf) { + return 0; +} +#endif /* CFG_TUD_DFU */ + #if CFG_TUD_DFU_RUNTIME -static uint16_t load_dfu_descriptor(uint8_t * dst, uint8_t * itf) +__attribute__((unused)) static uint16_t load_dfu_descriptor(uint8_t * dst, uint8_t * itf) { #define DFU_ATTRS (DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_CAN_UPLOAD | DFU_ATTR_MANIFESTATION_TOLERANT) @@ -185,7 +191,7 @@ bool ESPUSB::begin(){ .webusb_enabled = webusb_enabled, .webusb_url = webusb_url.c_str() }; - _started = tinyusb_init(&tinyusb_device_config) == ESP_OK; + _started = tinyusb_init(&tinyusb_device_config) == ESP_OK; } return _started; } @@ -203,7 +209,9 @@ ESPUSB::operator bool() const } bool ESPUSB::enableDFU(){ -#if CFG_TUD_DFU_RUNTIME +#if CFG_TUD_DFU + return tinyusb_enable_interface(USB_INTERFACE_DFU, TUD_DFU_DESC_LEN(1), load_dfu_ota_descriptor) == ESP_OK; +#elif CFG_TUD_DFU_RUNTIME return tinyusb_enable_interface(USB_INTERFACE_DFU, TUD_DFU_RT_DESC_LEN, load_dfu_descriptor) == ESP_OK; #endif /* CFG_TUD_DFU_RUNTIME */ return false; diff --git a/cores/esp32/io_pin_remap.h b/cores/esp32/io_pin_remap.h new file mode 100644 index 00000000000..4a23de4a829 --- /dev/null +++ b/cores/esp32/io_pin_remap.h @@ -0,0 +1,110 @@ +#ifndef __IO_PIN_REMAP_H__ +#define __IO_PIN_REMAP_H__ + +#include "Arduino.h" + +#if defined(BOARD_HAS_PIN_REMAP) && !defined(BOARD_USES_HW_GPIO_NUMBERS) + +// Pin remapping functions +int8_t digitalPinToGPIONumber(int8_t digitalPin); +int8_t digitalPinFromGPIONumber(int8_t gpioPin); + +// Apply pin remapping to API only when building libraries and user sketch +#ifndef ARDUINO_CORE_BUILD + +// Override APIs requiring pin remapping + +// cores/esp32/Arduino.h +#define pulseInLong(pin, state, timeout) pulseInLong(digitalPinToGPIONumber(pin), state, timeout) +#define pulseIn(pin, state, timeout) pulseIn(digitalPinToGPIONumber(pin), state, timeout) +#define noTone(_pin) noTone(digitalPinToGPIONumber(_pin)) +#define tone(_pin, frequency, duration) tone(digitalPinToGPIONumber(_pin), frequency, duration) + +// cores/esp32/esp32-hal.h +#define analogGetChannel(pin) analogGetChannel(digitalPinToGPIONumber(pin)) +#define analogWrite(pin, value) analogWrite(digitalPinToGPIONumber(pin), value) + +// cores/esp32/esp32-hal-adc.h +#define adcAttachPin(pin) adcAttachPin(digitalPinToGPIONumber(pin)) +#define analogRead(pin) analogRead(digitalPinToGPIONumber(pin)) +#define analogReadMilliVolts(pin) analogReadMilliVolts(digitalPinToGPIONumber(pin)) +#define analogSetPinAttenuation(pin, attenuation) analogSetPinAttenuation(digitalPinToGPIONumber(pin), attenuation) +#define analogSetVRefPin(pin) analogSetVRefPin(digitalPinToGPIONumber(pin)) + +// cores/esp32/esp32-hal-dac.h +#define dacDisable(pin) dacDisable(digitalPinToGPIONumber(pin)) +#define dacWrite(pin, value) dacWrite(digitalPinToGPIONumber(pin), value) + +// cores/esp32/esp32-hal-gpio.h +#define analogChannelToDigitalPin(channel) gpioNumberToDigitalPin(analogChannelToDigitalPin(channel)) +#define digitalPinToAnalogChannel(pin) digitalPinToAnalogChannel(digitalPinToGPIONumber(pin)) +#define digitalPinToTouchChannel(pin) digitalPinToTouchChannel(digitalPinToGPIONumber(pin)) +#define digitalRead(pin) digitalRead(digitalPinToGPIONumber(pin)) +#define attachInterruptArg(pin, fcn, arg, mode) attachInterruptArg(digitalPinToGPIONumber(pin), fcn, arg, mode) +#define attachInterrupt(pin, fcn, mode) attachInterrupt(digitalPinToGPIONumber(pin), fcn, mode) +#define detachInterrupt(pin) detachInterrupt(digitalPinToGPIONumber(pin)) +#define digitalWrite(pin, val) digitalWrite(digitalPinToGPIONumber(pin), val) +#define pinMode(pin, mode) pinMode(digitalPinToGPIONumber(pin), mode) + +// cores/esp32/esp32-hal-i2c.h +#define i2cInit(i2c_num, sda, scl, clk_speed) i2cInit(i2c_num, digitalPinToGPIONumber(sda), digitalPinToGPIONumber(scl), clk_speed) + +// cores/esp32/esp32-hal-i2c-slave.h +#define i2cSlaveInit(num, sda, scl, slaveID, frequency, rx_len, tx_len) i2cSlaveInit(num, digitalPinToGPIONumber(sda), digitalPinToGPIONumber(scl), slaveID, frequency, rx_len, tx_len) + +// cores/esp32/esp32-hal-ledc.h +#define ledcAttachPin(pin, channel) ledcAttachPin(digitalPinToGPIONumber(pin), channel) +#define ledcDetachPin(pin) ledcDetachPin(digitalPinToGPIONumber(pin)) + +// cores/esp32/esp32-hal-matrix.h +#define pinMatrixInAttach(pin, signal, inverted) pinMatrixInAttach(digitalPinToGPIONumber(pin), signal, inverted) +#define pinMatrixOutAttach(pin, function, invertOut, invertEnable) pinMatrixOutAttach(digitalPinToGPIONumber(pin), function, invertOut, invertEnable) +#define pinMatrixOutDetach(pin, invertOut, invertEnable) pinMatrixOutDetach(digitalPinToGPIONumber(pin), invertOut, invertEnable) + +// cores/esp32/esp32-hal-rgb-led.h +#define neopixelWrite(pin, red_val, green_val, blue_val) neopixelWrite(digitalPinToGPIONumber(pin), red_val, green_val, blue_val) + +// cores/esp32/esp32-hal-rmt.h +#define rmtInit(pin, tx_not_rx, memsize) rmtInit(digitalPinToGPIONumber(pin), tx_not_rx, memsize) + +// cores/esp32/esp32-hal-sigmadelta.h +#define sigmaDeltaSetup(pin, channel, freq) sigmaDeltaSetup(digitalPinToGPIONumber(pin), channel, freq) +#define sigmaDeltaDetachPin(pin) sigmaDeltaDetachPin(digitalPinToGPIONumber(pin)) + +// cores/esp32/esp32-hal-spi.h +#define spiAttachSCK(spi, sck) spiAttachSCK(spi, digitalPinToGPIONumber(sck)) +#define spiAttachMISO(spi, miso) spiAttachMISO(spi, digitalPinToGPIONumber(miso)) +#define spiAttachMOSI(spi, mosi) spiAttachMOSI(spi, digitalPinToGPIONumber(mosi)) +#define spiDetachSCK(spi, sck) spiDetachSCK(spi, digitalPinToGPIONumber(sck)) +#define spiDetachMISO(spi, miso) spiDetachMISO(spi, digitalPinToGPIONumber(miso)) +#define spiDetachMOSI(spi, mosi) spiDetachMOSI(spi, digitalPinToGPIONumber(mosi)) +#define spiAttachSS(spi, cs_num, ss) spiAttachSS(spi, cs_num, digitalPinToGPIONumber(ss)) +#define spiDetachSS(spi, ss) spiDetachSS(spi, digitalPinToGPIONumber(ss)) + +// cores/esp32/esp32-hal-touch.h +#define touchInterruptGetLastStatus(pin) touchInterruptGetLastStatus(digitalPinToGPIONumber(pin)) +#define touchRead(pin) touchRead(digitalPinToGPIONumber(pin)) +#define touchAttachInterruptArg(pin, userFunc, arg, threshold) touchAttachInterruptArg(digitalPinToGPIONumber(pin), userFunc, arg, threshold) +#define touchAttachInterrupt(pin, userFunc, threshold) touchAttachInterrupt(digitalPinToGPIONumber(pin), userFunc, threshold) +#define touchDetachInterrupt(pin) touchDetachInterrupt(digitalPinToGPIONumber(pin)) +#define touchSleepWakeUpEnable(pin, threshold) touchSleepWakeUpEnable(digitalPinToGPIONumber(pin), threshold) + +// cores/esp32/esp32-hal-uart.h +#define uartBegin(uart_nr, baudrate, config, rxPin, txPin, rx_buffer_size, tx_buffer_size, inverted, rxfifo_full_thrhd) \ + uartBegin(uart_nr, baudrate, config, digitalPinToGPIONumber(rxPin), digitalPinToGPIONumber(txPin), rx_buffer_size, tx_buffer_size, inverted, rxfifo_full_thrhd) +#define uartSetPins(uart, rxPin, txPin, ctsPin, rtsPin) \ + uartSetPins(uart, digitalPinToGPIONumber(rxPin), digitalPinToGPIONumber(txPin), digitalPinToGPIONumber(ctsPin), digitalPinToGPIONumber(rtsPin)) +#define uartDetachPins(uart, rxPin, txPin, ctsPin, rtsPin) \ + uartDetachPins(uart, digitalPinToGPIONumber(rxPin), digitalPinToGPIONumber(txPin), digitalPinToGPIONumber(ctsPin), digitalPinToGPIONumber(rtsPin)) + +#endif // ARDUINO_CORE_BUILD + +#else + +// pin remapping disabled: use stubs +#define digitalPinToGPIONumber(digitalPin) (digitalPin) +#define gpioNumberToDigitalPin(gpioNumber) (gpioNumber) + +#endif + +#endif /* __GPIO_PIN_REMAP_H__ */ diff --git a/libraries/I2S/src/I2S.cpp b/libraries/I2S/src/I2S.cpp index 78dc5c202df..68cc3b13f10 100644 --- a/libraries/I2S/src/I2S.cpp +++ b/libraries/I2S/src/I2S.cpp @@ -317,24 +317,24 @@ int I2SClass::begin(int mode, int sampleRate, int bitsPerSample, bool driveClock int I2SClass::_applyPinSetting(){ if(_driverInstalled){ esp_i2s::i2s_pin_config_t pin_config = { - .bck_io_num = _sckPin, - .ws_io_num = _fsPin, + .bck_io_num = digitalPinToGPIONumber(_sckPin), + .ws_io_num = digitalPinToGPIONumber(_fsPin), .data_out_num = I2S_PIN_NO_CHANGE, .data_in_num = I2S_PIN_NO_CHANGE }; if (_state == I2S_STATE_DUPLEX){ // duplex - pin_config.data_out_num = _outSdPin; - pin_config.data_in_num = _inSdPin; + pin_config.data_out_num = digitalPinToGPIONumber(_outSdPin); + pin_config.data_in_num = digitalPinToGPIONumber(_inSdPin); }else{ // simplex if(_state == I2S_STATE_RECEIVER){ pin_config.data_out_num = I2S_PIN_NO_CHANGE; - pin_config.data_in_num = _sdPin; + pin_config.data_in_num = digitalPinToGPIONumber(_sdPin); }else if(_state == I2S_STATE_TRANSMITTER){ - pin_config.data_out_num = _sdPin; + pin_config.data_out_num = digitalPinToGPIONumber(_sdPin); pin_config.data_in_num = I2S_PIN_NO_CHANGE; }else{ pin_config.data_out_num = I2S_PIN_NO_CHANGE; - pin_config.data_in_num = _sdPin; + pin_config.data_in_num = digitalPinToGPIONumber(_sdPin); } } if(ESP_OK != esp_i2s::i2s_set_pin((esp_i2s::i2s_port_t) _deviceIndex, &pin_config)){ diff --git a/libraries/SD_MMC/src/SD_MMC.cpp b/libraries/SD_MMC/src/SD_MMC.cpp index 83cf9aa0dd1..85f4e46681c 100644 --- a/libraries/SD_MMC/src/SD_MMC.cpp +++ b/libraries/SD_MMC/src/SD_MMC.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "pins_arduino.h" +#include "io_pin_remap.h" #include "SD_MMC.h" #ifdef SOC_SDMMC_HOST_SUPPORTED #include "vfs_api.h" @@ -54,6 +55,15 @@ bool SDMMCFS::setPins(int clk, int cmd, int d0, int d1, int d2, int d3) log_e("SD_MMC.setPins must be called before SD_MMC.begin"); return false; } + + // map logical pins to GPIO numbers + clk = digitalPinToGPIONumber(clk); + cmd = digitalPinToGPIONumber(cmd); + d0 = digitalPinToGPIONumber(d0); + d1 = digitalPinToGPIONumber(d1); + d2 = digitalPinToGPIONumber(d2); + d3 = digitalPinToGPIONumber(d3); + #ifdef SOC_SDMMC_USE_GPIO_MATRIX // SoC supports SDMMC pin configuration via GPIO matrix. Save the pins for later use in SDMMCFS::begin. _pin_clk = (int8_t) clk; diff --git a/libraries/SPI/src/SPI.cpp b/libraries/SPI/src/SPI.cpp index 23a35c0d357..3af07515c2b 100644 --- a/libraries/SPI/src/SPI.cpp +++ b/libraries/SPI/src/SPI.cpp @@ -20,6 +20,7 @@ */ #include "SPI.h" +#include "io_pin_remap.h" #include "esp32-hal-log.h" #if !CONFIG_DISABLE_HAL_LOCKS diff --git a/package/package_esp32_index.template.json b/package/package_esp32_index.template.json index f96c18a37c8..553f3f4229f 100644 --- a/package/package_esp32_index.template.json +++ b/package/package_esp32_index.template.json @@ -33,6 +33,9 @@ }, { "name": "ESP32-C3 Dev Board" + }, + { + "name": "Arduino Nano ESP32" } ], "toolsDependencies": [ @@ -85,6 +88,11 @@ "packager": "esp32", "name": "mklittlefs", "version": "3.0.0-gnu12-dc7f933" + }, + { + "packager": "arduino", + "name": "dfu-util", + "version": "0.11.0-arduino5" } ] } diff --git a/platform.txt b/platform.txt index e951a787435..b8d660962c5 100644 --- a/platform.txt +++ b/platform.txt @@ -175,8 +175,8 @@ recipe.hooks.prebuild.3.pattern.windows=cmd /c if not exist "{build.path}\partit # Check if custom bootloader exist: source > variant > build.boot recipe.hooks.prebuild.4.pattern_args=--chip {build.mcu} elf2image --flash_mode {build.flash_mode} --flash_freq {build.flash_freq} --flash_size {build.flash_size} -o -recipe.hooks.prebuild.4.pattern=bash -c "[ -f "{build.source.path}"/bootloader.bin ] && cp -f "{build.source.path}"/bootloader.bin "{build.path}"/{build.project_name}.bootloader.bin || ( [ -f "{build.variant.path}"/{build.custom_bootloader}.bin ] && cp "{build.variant.path}"/{build.custom_bootloader}.bin "{build.path}"/{build.project_name}.bootloader.bin || "{tools.esptool_py.path}"/{tools.esptool_py.cmd} {recipe.hooks.prebuild.4.pattern_args} "{build.path}"/{build.project_name}.bootloader.bin "{runtime.platform.path}"/tools/sdk/{build.mcu}/bin/bootloader_{build.boot}_{build.boot_freq}.elf )" -recipe.hooks.prebuild.4.pattern.linux=bash -c "[ -f "{build.source.path}"/bootloader.bin ] && cp -f "{build.source.path}"/bootloader.bin "{build.path}"/{build.project_name}.bootloader.bin || ( [ -f "{build.variant.path}"/{build.custom_bootloader}.bin ] && cp "{build.variant.path}"/{build.custom_bootloader}.bin "{build.path}"/{build.project_name}.bootloader.bin || python3 "{tools.esptool_py.path}"/{tools.esptool_py.cmd} {recipe.hooks.prebuild.4.pattern_args} "{build.path}"/{build.project_name}.bootloader.bin "{runtime.platform.path}"/tools/sdk/{build.mcu}/bin/bootloader_{build.boot}_{build.boot_freq}.elf )" +recipe.hooks.prebuild.4.pattern=bash -c "[ -f "{build.source.path}"/bootloader.bin ] && cp -f "{build.source.path}"/bootloader.bin "{build.path}"/{build.project_name}.bootloader.bin || ( [ -f "{build.variant.path}"/{build.custom_bootloader}.bin ] && cp "{build.variant.path}"/{build.custom_bootloader}.bin "{build.path}"/{build.project_name}.bootloader.bin || "{tools.esptool_py.path}"/{tools.esptool_py.cmd} {recipe.hooks.prebuild.4.pattern_args} "{build.path}"/{build.project_name}.bootloader.bin "{compiler.sdk.path}"/bin/bootloader_{build.boot}_{build.boot_freq}.elf )" +recipe.hooks.prebuild.4.pattern.linux=bash -c "[ -f "{build.source.path}"/bootloader.bin ] && cp -f "{build.source.path}"/bootloader.bin "{build.path}"/{build.project_name}.bootloader.bin || ( [ -f "{build.variant.path}"/{build.custom_bootloader}.bin ] && cp "{build.variant.path}"/{build.custom_bootloader}.bin "{build.path}"/{build.project_name}.bootloader.bin || python3 "{tools.esptool_py.path}"/{tools.esptool_py.cmd} {recipe.hooks.prebuild.4.pattern_args} "{build.path}"/{build.project_name}.bootloader.bin "{compiler.sdk.path}"/bin/bootloader_{build.boot}_{build.boot_freq}.elf )" recipe.hooks.prebuild.4.pattern.windows=cmd /c IF EXIST "{build.source.path}\bootloader.bin" ( COPY /y "{build.source.path}\bootloader.bin" "{build.path}\{build.project_name}.bootloader.bin" ) ELSE ( IF EXIST "{build.variant.path}\{build.custom_bootloader}.bin" ( COPY "{build.variant.path}\{build.custom_bootloader}.bin" "{build.path}\{build.project_name}.bootloader.bin" ) ELSE ( "{tools.esptool_py.path}/{tools.esptool_py.cmd}" {recipe.hooks.prebuild.4.pattern_args} "{build.path}\{build.project_name}.bootloader.bin" "{runtime.platform.path}\tools\sdk\{build.mcu}\bin\bootloader_{build.boot}_{build.boot_freq}.elf" ) ) # Check if custom build options exist in the sketch folder @@ -186,6 +186,16 @@ recipe.hooks.prebuild.6.pattern=bash -c "[ -f "{build.path}"/build_opt.h ] || to recipe.hooks.prebuild.5.pattern.windows=cmd /c if exist "{build.source.path}\build_opt.h" COPY /y "{build.source.path}\build_opt.h" "{build.path}\build_opt.h" recipe.hooks.prebuild.6.pattern.windows=cmd /c if not exist "{build.path}\build_opt.h" type nul > "{build.path}\build_opt.h" +# Set -DARDUINO_CORE_BUILD only on core file compilation +file_opts.path={build.path}/file_opts +recipe.hooks.prebuild.set_core_build_flag.pattern=bash -c ": > {file_opts.path}" +recipe.hooks.core.prebuild.set_core_build_flag.pattern=bash -c "echo '-DARDUINO_CORE_BUILD' > {file_opts.path}" +recipe.hooks.core.postbuild.set_core_build_flag.pattern=bash -c ": > {file_opts.path}" + +recipe.hooks.prebuild.set_core_build_flag.pattern.windows=cmd /c type nul > {file_opts.path} +recipe.hooks.core.prebuild.set_core_build_flag.pattern.windows=cmd /c echo "-DARDUINO_CORE_BUILD" > {file_opts.path} +recipe.hooks.core.postbuild.set_core_build_flag.pattern.windows=cmd /c type nul > {file_opts.path} + # Generate debug.cfg (must be postbuild) recipe.hooks.postbuild.1.pattern=bash -c "[ {build.copy_jtag_files} -eq 0 ] || cp -f "{debug.server.openocd.scripts_dir}"board/{build.openocdscript} "{build.source.path}"/debug.cfg" recipe.hooks.postbuild.1.pattern.windows=cmd /c IF {build.copy_jtag_files}==1 COPY /y "{debug.server.openocd.scripts_dir}board\{build.openocdscript}" "{build.source.path}\debug.cfg" @@ -199,13 +209,13 @@ recipe.hooks.postbuild.3.pattern=bash -c "[ {build.copy_jtag_files} -eq 0 ] || c recipe.hooks.postbuild.3.pattern.windows=cmd /c IF {build.copy_jtag_files}==1 COPY /y "{runtime.platform.path}\tools\ide-debug\svd\{build.mcu}.svd" "{build.source.path}\debug.svd" ## Compile c files -recipe.c.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.cpreprocessor.flags} {compiler.c.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} -DARDUINO_BOARD="{build.board}" -DARDUINO_VARIANT="{build.variant}" -DARDUINO_PARTITION_{build.partitions} {compiler.c.extra_flags} {build.extra_flags} "@{build.opt.path}" {includes} "{source_file}" -o "{object_file}" +recipe.c.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.cpreprocessor.flags} {compiler.c.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} -DARDUINO_BOARD="{build.board}" -DARDUINO_VARIANT="{build.variant}" -DARDUINO_PARTITION_{build.partitions} {compiler.c.extra_flags} {build.extra_flags} "@{build.opt.path}" "@{file_opts.path}" {includes} "{source_file}" -o "{object_file}" ## Compile c++ files -recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpreprocessor.flags} {compiler.cpp.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} -DARDUINO_BOARD="{build.board}" -DARDUINO_VARIANT="{build.variant}" -DARDUINO_PARTITION_{build.partitions} {compiler.cpp.extra_flags} {build.extra_flags} "@{build.opt.path}" {includes} "{source_file}" -o "{object_file}" +recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpreprocessor.flags} {compiler.cpp.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} -DARDUINO_BOARD="{build.board}" -DARDUINO_VARIANT="{build.variant}" -DARDUINO_PARTITION_{build.partitions} {compiler.cpp.extra_flags} {build.extra_flags} "@{build.opt.path}" "@{file_opts.path}" {includes} "{source_file}" -o "{object_file}" ## Compile S files -recipe.S.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.cpreprocessor.flags} {compiler.S.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} -DARDUINO_BOARD="{build.board}" -DARDUINO_VARIANT="{build.variant}" -DARDUINO_PARTITION_{build.partitions} {compiler.S.extra_flags} {build.extra_flags} "@{build.opt.path}" {includes} "{source_file}" -o "{object_file}" +recipe.S.o.pattern="{compiler.path}{compiler.c.cmd}" {compiler.cpreprocessor.flags} {compiler.S.flags} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} -DARDUINO_BOARD="{build.board}" -DARDUINO_VARIANT="{build.variant}" -DARDUINO_PARTITION_{build.partitions} {compiler.S.extra_flags} {build.extra_flags} "@{build.opt.path}" "@{file_opts.path}" {includes} "{source_file}" -o "{object_file}" ## Create archives recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{archive_file_path}" "{object_file}" @@ -239,6 +249,7 @@ recipe.size.regex.data=^(?:\.dram0\.data|\.dram0\.bss|\.noinit)\s+([0-9]+).* ## --------------------------------- pluggable_discovery.required.0=builtin:serial-discovery pluggable_discovery.required.1=builtin:mdns-discovery +pluggable_discovery.required.2=builtin:dfu-discovery pluggable_monitor.required.serial=builtin:serial-monitor ## ------------------ @@ -305,3 +316,11 @@ tools.esp_ota.upload.protocol=network tools.esp_ota.upload.field.password=Password tools.esp_ota.upload.field.password.secret=true tools.esp_ota.upload.pattern={cmd} -i {upload.port.address} -p {upload.port.properties.port} --auth={upload.field.password} -f "{build.path}/{build.project_name}.bin" + +## Upload Sketch Through DFU OTA +## ------------------------------------------- +tools.dfu-util.path={runtime.tools.dfu-util-0.11.0-arduino5.path} +tools.dfu-util.cmd=dfu-util +tools.dfu-util.upload.params.verbose=-d +tools.dfu-util.upload.params.quiet= +tools.dfu-util.upload.pattern="{path}/{cmd}" --device {vid.0}:{pid.0} -D "{build.path}/{build.project_name}.bin" -Q diff --git a/tools/partitions/app3M_fat9M_fact512k_16MB.csv b/tools/partitions/app3M_fat9M_fact512k_16MB.csv new file mode 100644 index 00000000000..dac4603e01a --- /dev/null +++ b/tools/partitions/app3M_fat9M_fact512k_16MB.csv @@ -0,0 +1,9 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x300000, +app1, app, ota_1, 0x310000, 0x300000, +ffat, data, fat, 0x610000, 0x960000, +factory, app, factory, 0xF70000, 0x80000, +coredump, data, coredump, 0xFF0000, 0x10000, +# to create/use ffat, see https://github.com/marcmerlin/esp32_fatfsimage diff --git a/variants/arduino_nano_nora/dfu_callbacks.cpp b/variants/arduino_nano_nora/dfu_callbacks.cpp new file mode 100644 index 00000000000..3695db80ba6 --- /dev/null +++ b/variants/arduino_nano_nora/dfu_callbacks.cpp @@ -0,0 +1,116 @@ +#include "Arduino.h" + +#include +#include + +// defines an "Update" object accessed only by this translation unit +// (also, the object requires MD5Builder internally) +namespace { +// ignore '{anonymous}::MD5Builder::...() defined but not used' warnings +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#include "../../libraries/Update/src/Updater.cpp" +#include "../../cores/esp32/MD5Builder.cpp" +#pragma GCC diagnostic pop +} + +#define ALT_COUNT 1 + +//--------------------------------------------------------------------+ +// DFU callbacks +// Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc. +//--------------------------------------------------------------------+ + +uint16_t load_dfu_ota_descriptor(uint8_t * dst, uint8_t * itf) +{ +#define DFU_ATTRS (DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_CAN_UPLOAD | DFU_ATTR_MANIFESTATION_TOLERANT) + + uint8_t str_index = tinyusb_add_string_descriptor("Arduino DFU"); + uint8_t descriptor[TUD_DFU_DESC_LEN(ALT_COUNT)] = { + // Interface number, string index, attributes, detach timeout, transfer size */ + TUD_DFU_DESCRIPTOR(*itf, ALT_COUNT, str_index, DFU_ATTRS, 100, CFG_TUD_DFU_XFER_BUFSIZE), + }; + *itf+=1; + memcpy(dst, descriptor, TUD_DFU_DESC_LEN(ALT_COUNT)); + return TUD_DFU_DESC_LEN(ALT_COUNT); +} + +// Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST) +// Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation. +// During this period, USB host won't try to communicate with us. +uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state) +{ + if ( state == DFU_DNBUSY ) + { + // longest delay for Flash writing + return 10; + } + else if (state == DFU_MANIFEST) + { + // time for esp32_ota_set_boot_partition to check final image + return 100; + } + + return 0; +} + +// Invoked when received DFU_DNLOAD (wLength>0) following by DFU_GETSTATUS (state=DFU_DNBUSY) requests +// This callback could be returned before flashing op is complete (async). +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const* data, uint16_t length) +{ + if (!Update.isRunning()) + { + // this is the first data block, start update if possible + if (!Update.begin()) + { + tud_dfu_finish_flashing(DFU_STATUS_ERR_TARGET); + return; + } + } + + // write a block of data to Flash + // XXX: Update API is needlessly non-const + size_t written = Update.write(const_cast(data), length); + tud_dfu_finish_flashing((written == length) ? DFU_STATUS_OK : DFU_STATUS_ERR_WRITE); +} + +// Invoked when download process is complete, received DFU_DNLOAD (wLength=0) following by DFU_GETSTATUS (state=Manifest) +// Application can do checksum, or actual flashing if buffered entire image previously. +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_manifest_cb(uint8_t alt) +{ + (void) alt; + bool ok = Update.end(true); + + // flashing op for manifest is complete + tud_dfu_finish_flashing(ok? DFU_STATUS_OK : DFU_STATUS_ERR_VERIFY); +} + +// Invoked when received DFU_UPLOAD request +// Application must populate data with up to length bytes and +// Return the number of written bytes +uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length) +{ + (void) alt; + (void) block_num; + (void) data; + (void) length; + + // not implemented + return 0; +} + +// Invoked when the Host has terminated a download or upload transfer +void tud_dfu_abort_cb(uint8_t alt) +{ + (void) alt; + // ignore +} + +// Invoked when a DFU_DETACH request is received +void tud_dfu_detach_cb(void) +{ + // done, reboot + esp_restart(); +} diff --git a/variants/arduino_nano_nora/double_tap.c b/variants/arduino_nano_nora/double_tap.c new file mode 100644 index 00000000000..44fe0923857 --- /dev/null +++ b/variants/arduino_nano_nora/double_tap.c @@ -0,0 +1,67 @@ +#include + +#include +#include + +#include "double_tap.h" + +#define NUM_TOKENS 3 +static const uint32_t MAGIC_TOKENS[NUM_TOKENS] = { + 0xf01681de, 0xbd729b29, 0xd359be7a, +}; + +static void *magic_area; +static uint32_t backup_area[NUM_TOKENS]; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +// Current IDF does not map external RAM to a fixed address. +// The actual VMA depends on other enabled devices, so the precise +// location must be discovered. +#include +#include +static uintptr_t get_extram_data_high(void) { + // get a pointer into SRAM area (only the address is useful) + void *psram_ptr = heap_caps_malloc(16, MALLOC_CAP_SPIRAM); + heap_caps_free(psram_ptr); + + // keep moving backwards until leaving PSRAM area + uintptr_t psram_base_addr = (uintptr_t) psram_ptr; + psram_base_addr &= ~(CONFIG_MMU_PAGE_SIZE - 1); // align to start of page + while (esp_psram_check_ptr_addr((void *) psram_base_addr)) { + psram_base_addr -= CONFIG_MMU_PAGE_SIZE; + } + + // offset is one page from start of PSRAM + return psram_base_addr + CONFIG_MMU_PAGE_SIZE + esp_psram_get_size(); +} +#else +#include +#define get_extram_data_high() ((uintptr_t) SOC_EXTRAM_DATA_HIGH) +#endif + + +void double_tap_init(void) { + // magic location block ends 0x20 bytes from end of PSRAM + magic_area = (void *) (get_extram_data_high() - 0x20 - sizeof(MAGIC_TOKENS)); +} + +void double_tap_mark() { + memcpy(backup_area, magic_area, sizeof(MAGIC_TOKENS)); + memcpy(magic_area, MAGIC_TOKENS, sizeof(MAGIC_TOKENS)); + Cache_WriteBack_Addr((uintptr_t) magic_area, sizeof(MAGIC_TOKENS)); +} + +void double_tap_invalidate() { + if (memcmp(backup_area, MAGIC_TOKENS, sizeof(MAGIC_TOKENS))) { + // different contents: restore backup + memcpy(magic_area, backup_area, sizeof(MAGIC_TOKENS)); + } else { + // clear memory + memset(magic_area, 0, sizeof(MAGIC_TOKENS)); + } + Cache_WriteBack_Addr((uintptr_t) magic_area, sizeof(MAGIC_TOKENS)); +} + +bool double_tap_check_match() { + return (memcmp(magic_area, MAGIC_TOKENS, sizeof(MAGIC_TOKENS)) == 0); +} diff --git a/variants/arduino_nano_nora/double_tap.h b/variants/arduino_nano_nora/double_tap.h new file mode 100644 index 00000000000..e797f4f64fd --- /dev/null +++ b/variants/arduino_nano_nora/double_tap.h @@ -0,0 +1,20 @@ +#ifndef __DOUBLE_TAP_H__ +#define __DOUBLE_TAP_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void double_tap_init(void); +void double_tap_mark(void); +void double_tap_invalidate(void); +bool double_tap_check_match(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __DOUBLE_TAP_H__ */ diff --git a/variants/arduino_nano_nora/extra/nora_recovery/README.md b/variants/arduino_nano_nora/extra/nora_recovery/README.md new file mode 100644 index 00000000000..786027dc6e3 --- /dev/null +++ b/variants/arduino_nano_nora/extra/nora_recovery/README.md @@ -0,0 +1,49 @@ + +# Arduino Nano Nora Recovery Sketch + +This sketch implements the DFU recovery mode logic, called by all sketches +when a double tap on the RESET button is detected. It should not be uploaded +as any other sketch; instead, this should be compiled and then flashed in +the module's `factory` partition. + +## Compilation + +The binary can be compiled with the Arduino 2.x IDE or CLI using the +`nano_nora` variant. In particular, using the CLI the resulting binary +can be exported to the `build` directory with the `-e` switch to +`arduino-cli compile`. + +## Automatic installation + +By replacing the binary in the current folder, automatic installation +can be performed by running the "Upload with Programmer" action on any +sketch in the Arduino 2.x IDE or CLI. In particular, using the CLI the +binary can be installed via the command: + +``` +arduino-cli compile -u --programmer esptool +``` + +## Manual installation + +Once compiled, the binary can also be installed on a board using `esptool.py` +with the following command: + +``` +esptool.py --chip esp32s3 --port "/dev/ttyACM0" --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 16MB 0xF70000 "nora_recovery.ino.bin" +``` + +where: +- `esptool.py` is located in your core's install path under `tools/esptool_py`; +- `/dev/ttyACM0` is the serial port exposed by the board to be used; +- `0xF70000` is the factory partition address (make sure it matches the + offset in the variant's `{build.partitions}` file); +- `nora_recovery.ino.bin` is the compiled sketch image. + +Due to a BSP issue, the first call to `esptool.py` will enter the hardware +bootloader for programming, but fail with an "Input/output error". This is +a known issue; calling the program again with the same arguments will now +work correctly. + +Once flashing is complete, a power cycle (or RESET button tap) is required +to leave the `esptool.py` flashing mode and load user sketches. diff --git a/variants/arduino_nano_nora/extra/nora_recovery/nora_recovery.ino b/variants/arduino_nano_nora/extra/nora_recovery/nora_recovery.ino new file mode 100644 index 00000000000..865fdd59020 --- /dev/null +++ b/variants/arduino_nano_nora/extra/nora_recovery/nora_recovery.ino @@ -0,0 +1,97 @@ +#define USB_TIMEOUT_MS 15000 +#define POLL_DELAY_MS 60 +#define FADESTEP 8 + +void pulse_led() { + static u32_t pulse_width = 0; + static u8_t dir = 0; + + if (dir) { + pulse_width -= FADESTEP; + if (pulse_width < FADESTEP) { + dir = 0U; + pulse_width = FADESTEP; + } + } else { + pulse_width += FADESTEP; + if (pulse_width > 255) { + dir = 1U; + pulse_width = 255; + } + } + + analogWrite(LED_GREEN, pulse_width); +} + +#include +#include +#include +#include +const esp_partition_t *find_previous_firmware() { + extern bool _recovery_active; + if (!_recovery_active) { + // user flashed this recovery sketch to an OTA partition + // stay here and wait for a proper firmware + return NULL; + } + + // booting from factory partition, look for a valid OTA image + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, NULL); + for (; it != NULL; it = esp_partition_next(it)) { + const esp_partition_t *part = esp_partition_get(it); + if (part->subtype != ESP_PARTITION_SUBTYPE_APP_FACTORY) { + esp_partition_pos_t candidate = { part->address, part->size }; + esp_image_metadata_t meta; + if (esp_image_verify(ESP_IMAGE_VERIFY_SILENT, &candidate, &meta) == ESP_OK) { + // found, use it + return part; + } + } + } + + return NULL; +} + +const esp_partition_t *user_part = NULL; + +void setup() { + user_part = find_previous_firmware(); + if (user_part) + esp_ota_set_boot_partition(user_part); + + extern bool _recovery_marker_found; + if (!_recovery_marker_found && user_part) { + // recovery marker not found, probable cold start + // try starting previous firmware immediately + esp_restart(); + } + + // recovery marker found, or nothing else to load + printf("Recovery firmware started, waiting for USB\r\n"); +} + +void loop() { + static int elapsed_ms = 0; + + pulse_led(); + delay(POLL_DELAY_MS); + if (USB) { + // wait indefinitely for DFU to complete + elapsed_ms = 0; + } else { + // wait for USB connection + elapsed_ms += POLL_DELAY_MS; + } + + if (elapsed_ms > USB_TIMEOUT_MS) { + elapsed_ms = 0; + // timed out, try loading previous firmware + if (user_part) { + // there was a valid FW image, load it + analogWrite(LED_GREEN, 255); + printf("Leaving recovery firmware\r\n"); + delay(200); + esp_restart(); // does not return + } + } +} diff --git a/variants/arduino_nano_nora/extra/nora_recovery/nora_recovery.ino.bin b/variants/arduino_nano_nora/extra/nora_recovery/nora_recovery.ino.bin new file mode 100644 index 00000000000..2a0183eb58f Binary files /dev/null and b/variants/arduino_nano_nora/extra/nora_recovery/nora_recovery.ino.bin differ diff --git a/variants/arduino_nano_nora/io_pin_remap.cpp b/variants/arduino_nano_nora/io_pin_remap.cpp new file mode 100644 index 00000000000..e53c500a328 --- /dev/null +++ b/variants/arduino_nano_nora/io_pin_remap.cpp @@ -0,0 +1,55 @@ +#ifndef BOARD_USES_HW_GPIO_NUMBERS + +#include "Arduino.h" + +static const int8_t TO_GPIO_NUMBER[NUM_DIGITAL_PINS] = { + [D0] = 44, // RX + [D1] = 43, // TX + [D2] = 5, + [D3] = 6, // CTS + [D4] = 7, // DSR + [D5] = 8, + [D6] = 9, + [D7] = 10, + [D8] = 17, + [D9] = 18, + [D10] = 21, // SS + [D11] = 38, // MOSI + [D12] = 47, // MISO + [D13] = 48, // SCK, LED_BUILTIN + [LED_RED] = 46, + [LED_GREEN] = 0, + [LED_BLUE] = 45, // RTS + [A0] = 1, // DTR + [A1] = 2, + [A2] = 3, + [A3] = 4, + [A4] = 11, // SDA + [A5] = 12, // SCL + [A6] = 13, + [A7] = 14, +}; + +int8_t digitalPinToGPIONumber(int8_t digitalPin) +{ + if ((digitalPin < 0) || (digitalPin >= NUM_DIGITAL_PINS)) + return -1; + return TO_GPIO_NUMBER[digitalPin]; +} + +int8_t gpioNumberToDigitalPin(int8_t gpioNumber) +{ + if (gpioNumber < 0) + return -1; + + // slow linear table lookup + for (int8_t digitalPin = 0; digitalPin < NUM_DIGITAL_PINS; ++digitalPin) { + if (TO_GPIO_NUMBER[digitalPin] == gpioNumber) + return digitalPin; + } + + // not found + return -1; +} + +#endif diff --git a/variants/arduino_nano_nora/pins_arduino.h b/variants/arduino_nano_nora/pins_arduino.h new file mode 100644 index 00000000000..79a94ec4ae9 --- /dev/null +++ b/variants/arduino_nano_nora/pins_arduino.h @@ -0,0 +1,80 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include "soc/soc_caps.h" + +#define USB_VID 0x2341 +#define USB_PID 0x0070 + +#define EXTERNAL_NUM_INTERRUPTS 46 +#define NUM_DIGITAL_PINS 25 +#define NUM_ANALOG_INPUTS 8 + +#define analogInputToDigitalPin(p) (p) +#define digitalPinToInterrupt(p) ((((uint8_t)digitalPinToGPIONumber(p)) < 48)? digitalPinToGPIONumber(p) : -1) +#define digitalPinHasPWM(p) (((uint8_t)digitalPinToGPIONumber(p)) < 46) + +#ifndef __cplusplus +#define constexpr const +#endif + +// primary pin names + +static constexpr uint8_t D0 = 0; // also RX +static constexpr uint8_t D1 = 1; // also TX +static constexpr uint8_t D2 = 2; +static constexpr uint8_t D3 = 3; // also CTS +static constexpr uint8_t D4 = 4; // also DSR +static constexpr uint8_t D5 = 5; +static constexpr uint8_t D6 = 6; +static constexpr uint8_t D7 = 7; +static constexpr uint8_t D8 = 8; +static constexpr uint8_t D9 = 9; +static constexpr uint8_t D10 = 10; // also SS +static constexpr uint8_t D11 = 11; // also MOSI +static constexpr uint8_t D12 = 12; // also MISO +static constexpr uint8_t D13 = 13; // also SCK, LED_BUILTIN +static constexpr uint8_t LED_RED = 14; +static constexpr uint8_t LED_GREEN = 15; +static constexpr uint8_t LED_BLUE = 16; // also RTS + +static constexpr uint8_t A0 = 17; // also DTR +static constexpr uint8_t A1 = 18; +static constexpr uint8_t A2 = 19; +static constexpr uint8_t A3 = 20; +static constexpr uint8_t A4 = 21; // also SDA +static constexpr uint8_t A5 = 22; // also SCL +static constexpr uint8_t A6 = 23; +static constexpr uint8_t A7 = 24; + +// alternate pin functions + +static constexpr uint8_t LED_BUILTIN = D13; + +static constexpr uint8_t TX = D1; +static constexpr uint8_t RX = D0; +static constexpr uint8_t RTS = LED_BLUE; +static constexpr uint8_t CTS = D3; +static constexpr uint8_t DTR = A0; +static constexpr uint8_t DSR = D4; + +static constexpr uint8_t SS = D10; +static constexpr uint8_t MOSI = D11; +static constexpr uint8_t MISO = D12; +static constexpr uint8_t SCK = D13; + +static constexpr uint8_t SDA = A4; +static constexpr uint8_t SCL = A5; + +#define PIN_I2S_SCK D7 +#define PIN_I2S_FS D8 +#define PIN_I2S_SD D9 +#define PIN_I2S_SD_OUT D9 // same as bidir +#define PIN_I2S_SD_IN D10 + +#ifndef __cplusplus +#undef constexpr +#endif + +#endif /* Pins_Arduino_h */ diff --git a/variants/arduino_nano_nora/variant.cpp b/variants/arduino_nano_nora/variant.cpp new file mode 100644 index 00000000000..a693f4e0762 --- /dev/null +++ b/variants/arduino_nano_nora/variant.cpp @@ -0,0 +1,128 @@ +// Enable pin remapping in this file, so pin constants are meaningful +#undef ARDUINO_CORE_BUILD + +#include "Arduino.h" + +#include "double_tap.h" + +#include +#include +#include + +extern "C" { + void initVariant() { + // FIXME: fix issues with GPIO matrix not being soft reset properly + for (int pin = 0; pinsubtype == ESP_PARTITION_SUBTYPE_APP_FACTORY); + + double_tap_init(); + + _recovery_marker_found = double_tap_check_match(); + if (_recovery_marker_found && !_recovery_active) { + // double tap detected in user application, reboot to factory + NANO_ESP32_enter_bootloader(); + } + + // delay with mark set then proceed + // - for normal startup, to detect first double tap + // - in recovery mode, to ignore several short presses + double_tap_mark(); + rgb_pulse_delay(); + double_tap_invalidate(); +} + +namespace { + class DoubleTap { + public: + DoubleTap() { + boot_double_tap_logic(); + } + }; + + DoubleTap dt __attribute__ ((init_priority (101))); +}