diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a9f301ea..fd1974e6a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: strategy: # devices to build for matrix: - board: [LINUX, ESP32, ESP8266_BOARD, ESP8266_4MB, MICROBIT1, MICROBIT2, ESP32C3_IDF4] + board: [LINUX, ESP32, ESP8266_BOARD, ESP8266_4MB, MICROBIT1, MICROBIT2, ESP32C3_IDF4, XIAOBLE] # try and build for all devices even if one fails fail-fast: false steps: @@ -62,6 +62,7 @@ jobs: name: ${{ matrix.board }} path: | bin/*.bin + bin/*.uf2 bin/*.hex bin/*.tgz bin/*.zip diff --git a/ChangeLog b/ChangeLog index 2234be0c9..3785e4460 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,9 @@ Bangle.js: Ensure fake LED1/LED2 remember state Ensure E.setComparator is added to the build for nRF52 Bangle.js2: Update test to use accelerometer to test vibration motor + Build: Allow creating UF2 images + XiaoBLE: Add board: Seeed XIAO BLE + nRF5x: Allow entering UF2 bootloader mode by calling E.rebootToDFU() (on boards that have such a bootloader) 2v25 : ESP32C3: Get analogRead working correctly Graphics: Adjust image alignment when rotating images to avoid cropping (fix #2535) diff --git a/Makefile b/Makefile index bd4d33497..f324607dd 100755 --- a/Makefile +++ b/Makefile @@ -891,6 +891,8 @@ clean: $(Q)rm -f $(PROJ_NAME).bin $(Q)rm -f $(PROJ_NAME).srec $(Q)rm -f $(PROJ_NAME).lst + $(Q)rm -f $(PROJ_NAME).app_hex + $(Q)rm -f $(PROJ_NAME).uf2 $(Q)rm -f $(BINDIR)/espruino_embedded.h $(Q)rm -f $(BINDIR)/espruino_embedded.c $(Q)rm -f $(BINDIR)/jstypes.h diff --git a/README_BuildProcess.md b/README_BuildProcess.md index 01091b5a8..4755b432d 100644 --- a/README_BuildProcess.md +++ b/README_BuildProcess.md @@ -148,6 +148,7 @@ This is a partial list of definitions that can be added in a `BOARD.py` file's ` * `ESPR_LCD_MANUAL_BACKLIGHT` - STM32/FSMC: Don't turn the backlight on and leave code to do this manually * `ESPR_DISABLE_KICKWATCHDOG_PIN=BTN1_PININDEX` - If this pin is 1, skip kickWatchdog calls (which would eventually force a reboot if WDT enabled) * `ESPR_TERMNINAL_NO_SCROLL` - disable scrolling in the onscreen terminal (once we get to the end, we just clear the screen and start at the top) +* `ESPR_HAS_BOOTLOADER_UF2` - nRF5x: Allow entering UF2 bootloader mode by calling E.rebootToDFU() There are some specifically that are useful for cutting a few bytes out of the build: diff --git a/boards/XIAOBLE.py b/boards/XIAOBLE.py new file mode 100644 index 000000000..aae9e0d48 --- /dev/null +++ b/boards/XIAOBLE.py @@ -0,0 +1,177 @@ +#!/bin/false +# This file is part of Espruino, a JavaScript interpreter for Microcontrollers +# +# Copyright (C) 2013 Gordon Williams +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# ---------------------------------------------------------------------------------------- +# This file contains information for a specific board - the available pins, and where LEDs, +# Buttons, and other in-built peripherals are. It is used to build documentation as well +# as various source and header files for Espruino. +# ---------------------------------------------------------------------------------------- + +# To build and flash run: +""" +source ./scripts/provision.sh XIAOBLE +make clean && BOARD=XIAOBLE RELEASE=1 make +""" +# Then connect the board to your computer and press the reset button twice. +# You should now see a USB storage device called "XIAO-SENSE" (no matter which version you have). +# Copy the "espruino_*_xiaoble.uf2" file from the "bin" folder to that drive. +# The board should automatically disconnect after copying is finished, and reboot into Espruino, +# which turns on the red led for a short time after starting up. + +# If you accidentally put some stuff in .boot0 that prevents you from interacting with Espruino: +# Pull pin D1 to 3.3 V / high (important: make sure you use 3.3 V; higher voltages may cause damage) and reset the board, +# by either pressing the reset button or cutting the power and powering it up again. +# This behaviour is archived by configuring pin D1 as BTN1. + +import pinutils +info = { + "name": "Seeed Xiao BLE", + "link": ["https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html"], + "default_console": "EV_USBSERIAL", + "variables": 14000, # How many variables are allocated for Espruino to use. RAM will be overflowed if this number is too high and code won't compile. + "binary_name": "espruino_%v_xiaoble.uf2", + "build": { + "optimizeflags": "-Os", + "libraries": [ + "BLUETOOTH", + # "GRAPHICS", + "CRYPTO","SHA256","SHA512", + "AES_CCM", + # "NFC", + # "TENSORFLOW", + "NEOPIXEL", + "JIT", + ], + "makefile": [ + "DEFINES += -DESPR_LSE_ENABLE", # Ensure low speed external osc enabled + "DEFINES += -DCONFIG_GPIO_AS_PINRESET", # Allow the reset pin to work + "DEFINES += -DNRF_USB=1 -DUSB", + "DEFINES += -DNEOPIXEL_SCK_PIN=33 -DNEOPIXEL_LRCK_PIN=34", # nRF52840 needs LRCK pin defined for neopixel + "DEFINES += -DBLUETOOTH_NAME_PREFIX='\"XIAOBLE\"'", + "DEFINES += -DSPIFLASH_READ2X", # Read SPI flash at 2x speed using MISO and MOSI for IO + "DEFINES += -DESPR_UNICODE_SUPPORT=1", + "DEFINES += -DNRF_SDH_BLE_GATT_MAX_MTU_SIZE=131", # 23+x*27 rule as per https://devzone.nordicsemi.com/f/nordic-q-a/44825/ios-mtu-size-why-only-185-bytes + # 'DEFINES += -DPIN_NAMES_DIRECT=1', # Package skips out some pins, so we can't assume each port starts from 0 + "LDFLAGS += -Xlinker --defsym=LD_APP_RAM_BASE=0x2ec0", # set RAM base to match MTU + 'DEFINES += -DESPR_BLE_PRIVATE_ADDRESS_SUPPORT', + 'DEFINES += -DESPR_HAS_BOOTLOADER_UF2', + 'NRF_SDK15=1', + ], + }, +} + +chip = { + "part": "NRF52840", + "family": "NRF52", + "package": "AQFN73", + "ram": 256, + "flash": 1024, + "speed": 64, + "usart": 1, + "spi": 1, + "i2c": 2, + "adc": 1, + "dac": 0, + "saved_code": { + "address": 0x60000000, # put this in external spiflash (see below) + "page_size": 4096, + "pages": 512, # Entire 2 MB of external flash + "flash_available": 1024 - ((38 + 7 + 12 + 1) * 4) - 780, + # Softdevice 140 uses 38 pages of flash, user data 7, bootloader 12, plus 1 page of padding (because I'm paranoid and really don't want to overwrite the bootloader). + # Each page is 4 KB. + }, +} + +devices = { + "LED1": {"pin": "D11"}, + "LED2": {"pin": "D13"}, + "LED3": {"pin": "D12"}, + "BTN1": {"pin": "D1", "pinstate" : "IN_PULLDOWN"}, + "BAT": { + "pin_charging": "D23", + "pin_voltage": "D32", + }, + "NFC": {"pin_a": "D30", "pin_b": "D31"}, + # accelerometer is only present on the "sense" variant (Seeed XIAO BLE nRF52840 Sense) + #"ACCEL" : { + # # device is actually a LSM6DS3TR-C, not sure if this would even work + # "device" : "LSM6DSL", "addr" : 0x6b, + # "pin_sda" : "D17", + # "pin_scl" : "D16" + #}, + "SPIFLASH": { + "pin_cs": "D25", + "pin_sck": "D24", + "pin_mosi": "D26", + "pin_miso": "D27", + "pin_wp": "D28", + "pin_rst": "D29", + "size": 4096 * 512, # 2 MB + "memmap_base": 0x60000000, # map into the address space (in software) + }, +} + +# left-right, or top-bottom order +board = {} + +# schematic at https://files.seeedstudio.com/wiki/XIAO-BLE/Seeed-Studio-XIAO-nRF52840-Sense-v1.1.pdf +# pinout sheet https://files.seeedstudio.com/wiki/XIAO-BLE/XIAO-nRF52840-pinout_sheet.xlsx +# see also https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino/blob/master/variants/Seeed_XIAO_nRF52840/variant.h +# and https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino/blob/master/variants/Seeed_XIAO_nRF52840/variant.cpp +def get_pins(): + pins = [ + { "name": "PD0", "sortingname": "D00", "port": "D", "num": "2", "functions": { "ADC1_IN0": 0 } }, + { "name": "PD1", "sortingname": "D01", "port": "D", "num": "3", "functions": { "ADC1_IN1": 0 } }, + { "name": "PD2", "sortingname": "D02", "port": "D", "num": "28", "functions": { "ADC1_IN4": 0 } }, + { "name": "PD3", "sortingname": "D03", "port": "D", "num": "29", "functions": { "ADC1_IN5": 0 } }, + { "name": "PD4", "sortingname": "D04", "port": "D", "num": "4", "functions": { "ADC1_IN2": 0 } }, + { "name": "PD5", "sortingname": "D05", "port": "D", "num": "5", "functions": { "ADC1_IN3": 0 } }, + { "name": "PD6", "sortingname": "D06", "port": "D", "num": "43", "functions": {} }, + { "name": "PD7", "sortingname": "D07", "port": "D", "num": "44", "functions": {} }, + { "name": "PD8", "sortingname": "D08", "port": "D", "num": "45", "functions": {} }, + { "name": "PD9", "sortingname": "D09", "port": "D", "num": "46", "functions": {} }, + { "name": "PD10", "sortingname": "D10", "port": "D", "num": "47", "functions": {} }, + { "name": "PD11", "sortingname": "D11", "port": "D", "num": "26", "functions": { "NEGATED": 0, "LED_RED": 0 } }, + { "name": "PD12", "sortingname": "D12", "port": "D", "num": "6", "functions": { "NEGATED": 0, "LED_BLUE": 0 } }, + { "name": "PD13", "sortingname": "D13", "port": "D", "num": "30", "functions": { "ADC1_IN6": 0, "NEGATED": 0, "LED_GREEN": 0 } }, + { "name": "PD14", "sortingname": "D14", "port": "D", "num": "14", "functions": { "NEGATED": 0, "VBAT_ENABLE": 0 } }, + { "name": "PD15", "sortingname": "D15", "port": "D", "num": "40", "functions": { "6D_PWR": 0 } }, + { "name": "PD16", "sortingname": "D16", "port": "D", "num": "27", "functions": { "6D_I2C_SCL": 0 } }, + { "name": "PD17", "sortingname": "D17", "port": "D", "num": "7", "functions": { "6D_I2C_SDA": 0 } }, + { "name": "PD18", "sortingname": "D18", "port": "D", "num": "11", "functions": { "6D_INT1": 0 } }, + { "name": "PD19", "sortingname": "D19", "port": "D", "num": "42", "functions": { "MIC_PWR": 0 } }, + { "name": "PD20", "sortingname": "D20", "port": "D", "num": "32", "functions": { "PDM_CLK": 0 } }, + { "name": "PD21", "sortingname": "D21", "port": "D", "num": "16", "functions": { "PDM_DATA": 0 } }, + { "name": "PD22", "sortingname": "D22", "port": "D", "num": "13", "functions": { "HICHG": 0 } }, + { "name": "PD23", "sortingname": "D23", "port": "D", "num": "17", "functions": { "NEGATED": 0, "CHG": 0 } }, + { "name": "PD24", "sortingname": "D24", "port": "D", "num": "21", "functions": { "QSPI_SCK": 0 } }, + { "name": "PD25", "sortingname": "D25", "port": "D", "num": "25", "functions": { "QSPI_CSN": 0 } }, + { "name": "PD26", "sortingname": "D26", "port": "D", "num": "20", "functions": { "QSPI_DI": 0 } }, + { "name": "PD27", "sortingname": "D27", "port": "D", "num": "24", "functions": { "QSPI_DO": 0 } }, + { "name": "PD28", "sortingname": "D28", "port": "D", "num": "22", "functions": { "QSPI_WP": 0 } }, + { "name": "PD29", "sortingname": "D29", "port": "D", "num": "23", "functions": { "QSPI_HOLD": 0 } }, + { "name": "PD30", "sortingname": "D30", "port": "D", "num": "9", "functions": { "NFC1": 0 } }, + { "name": "PD31", "sortingname": "D31", "port": "D", "num": "10", "functions": { "NFC2": 0 } }, + { "name": "PD32", "sortingname": "D32", "port": "D", "num": "31", "functions": { "ADC1_IN7": 0, "VBAT": 0 } }, + ] + # D23 CHG + # get charge state via digitalRead(D23) + # HIGH: charging, LOW: not charging + # D22 HICHG / PIN_CHARGING_CURRENT + # battery charging current see https://wiki.seeedstudio.com/XIAO_BLE/#battery-charging-current + # HIGH: 100mA, LOW: 50mA + # D14 VBAT_ENABLE + # enable battery voltage reading by digitalWrite(D14, true) + # D32 VBAT + # battery voltage; remember to enable voltage reading if you want to use this + + # everything is non-5v tolerant + for pin in pins: + pin["functions"]["3.3"] = 0 + return pins diff --git a/make/common/NRF5X.make b/make/common/NRF5X.make index b8ac0ce8f..903b4b86d 100644 --- a/make/common/NRF5X.make +++ b/make/common/NRF5X.make @@ -527,6 +527,9 @@ $(PROJ_NAME).hex: $(PROJ_NAME).app_hex python scripts/hexmerge.py $(SOFTDEVICE) $(PROJ_NAME).app_hex -o $(PROJ_NAME).hex endif # USE_BOOTLOADER +$(PROJ_NAME).uf2: $(PROJ_NAME).hex + @echo Creating UF2 + python3 scripts/uf2/uf2conv.py $(PROJ_NAME).hex -c -f $(CHIP) -o $(PROJ_NAME).uf2 $(PROJ_NAME).zip: $(PROJ_NAME).app_hex ifdef NRF5X_SDK_11 # SDK11 requires non-secure DFU that the adafruit tools support @@ -561,11 +564,10 @@ partflash: all ifdef DFU_UPDATE_BUILD_WITH_HEX proj: $(PROJ_NAME).hex $(PROJ_NAME).zip -else -ifdef DFU_UPDATE_BUILD +else ifdef DFU_UPDATE_BUILD proj: $(PROJ_NAME).zip +else ifdef CREATE_UF2 +proj: $(PROJ_NAME).uf2 else proj: $(PROJ_NAME).hex endif -endif - diff --git a/scripts/get_makefile_decls.py b/scripts/get_makefile_decls.py index 7ed2a50f0..51c51b6d5 100644 --- a/scripts/get_makefile_decls.py +++ b/scripts/get_makefile_decls.py @@ -53,6 +53,9 @@ binaryName = binaryName[:binaryName.find('.bin')] if binaryName.find('.hex')>=0: binaryName = binaryName[:binaryName.find('.hex')] +if binaryName.find('.uf2')>=0: + binaryName = binaryName[:binaryName.find('.uf2')] + print("CREATE_UF2=1") print("PROJ_NAME=$(BINDIR)/"+binaryName) if board.chip["family"]!="LINUX": diff --git a/scripts/uf2/LICENSE.txt b/scripts/uf2/LICENSE.txt new file mode 100644 index 000000000..d3e3605f8 --- /dev/null +++ b/scripts/uf2/LICENSE.txt @@ -0,0 +1,25 @@ +Microsoft UF2 + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/scripts/uf2/uf2conv.py b/scripts/uf2/uf2conv.py new file mode 100644 index 000000000..41cde18fe --- /dev/null +++ b/scripts/uf2/uf2conv.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python3 +import sys +import struct +import subprocess +import re +import os +import os.path +import argparse +import json +from time import sleep + + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto + +INFO_FILE = "/INFO_UF2.TXT" + +appstartaddr = 0x2000 +familyid = 0x0 + + +def is_uf2(buf): + w = struct.unpack(" 476: + assert False, "Invalid UF2 data size at " + ptr + newaddr = hd[3] + if (hd[2] & 0x2000) and (currfamilyid == None): + currfamilyid = hd[7] + if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid): + currfamilyid = hd[7] + curraddr = newaddr + if familyid == 0x0 or familyid == hd[7]: + appstartaddr = newaddr + padding = newaddr - curraddr + if padding < 0: + assert False, "Block out of order at " + ptr + if padding > 10*1024*1024: + assert False, "More than 10M of padding needed at " + ptr + if padding % 4 != 0: + assert False, "Non-word padding size at " + ptr + while padding > 0: + padding -= 4 + outp.append(b"\x00\x00\x00\x00") + if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]): + outp.append(block[32 : 32 + datalen]) + curraddr = newaddr + datalen + if hd[2] & 0x2000: + if hd[7] in families_found.keys(): + if families_found[hd[7]] > newaddr: + families_found[hd[7]] = newaddr + else: + families_found[hd[7]] = newaddr + if prev_flag == None: + prev_flag = hd[2] + if prev_flag != hd[2]: + all_flags_same = False + if blockno == (numblocks - 1): + print("--- UF2 File Header Info ---") + families = load_families() + for family_hex in families_found.keys(): + family_short_name = "" + for name, value in families.items(): + if value == family_hex: + family_short_name = name + print("Family ID is {:s}, hex value is 0x{:08x}".format(family_short_name,family_hex)) + print("Target Address is 0x{:08x}".format(families_found[family_hex])) + if all_flags_same: + print("All block flag values consistent, 0x{:04x}".format(hd[2])) + else: + print("Flags were not all the same") + print("----------------------------") + if len(families_found) > 1 and familyid == 0x0: + outp = [] + appstartaddr = 0x0 + return b"".join(outp) + +def convert_to_carray(file_content): + outp = "const unsigned long bindata_len = %d;\n" % len(file_content) + outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {" + for i in range(len(file_content)): + if i % 16 == 0: + outp += "\n" + outp += "0x%02x, " % file_content[i] + outp += "\n};\n" + return bytes(outp, "utf-8") + +def convert_to_uf2(file_content): + global familyid + datapadding = b"" + while len(datapadding) < 512 - 256 - 32 - 4: + datapadding += b"\x00\x00\x00\x00" + numblocks = (len(file_content) + 255) // 256 + outp = [] + for blockno in range(numblocks): + ptr = 256 * blockno + chunk = file_content[ptr:ptr + 256] + flags = 0x0 + if familyid: + flags |= 0x2000 + hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT": + drives.append(words[0]) + else: + searchpaths = ["/media"] + if sys.platform == "darwin": + searchpaths = ["/Volumes"] + elif sys.platform == "linux": + searchpaths += ["/media/" + os.environ["USER"], '/run/media/' + os.environ["USER"]] + + for rootpath in searchpaths: + if os.path.isdir(rootpath): + for d in os.listdir(rootpath): + if os.path.isdir(rootpath): + drives.append(os.path.join(rootpath, d)) + + + def has_info(d): + try: + return os.path.isfile(d + INFO_FILE) + except: + return False + + return list(filter(has_info, drives)) + + +def board_id(path): + with open(path + INFO_FILE, mode='r') as file: + file_content = file.read() + return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) + + +def list_drives(): + for d in get_drives(): + print(d, board_id(d)) + + +def write_file(name, buf): + with open(name, "wb") as f: + f.write(buf) + print("Wrote %d bytes to %s" % (len(buf), name)) + + +def load_families(): + # The expectation is that the `uf2families.json` file is in the same + # directory as this script. Make a path that works using `__file__` + # which contains the full path to this script. + filename = "uf2families.json" + pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename) + with open(pathname) as f: + raw_families = json.load(f) + + families = {} + for family in raw_families: + families[family["short_name"]] = int(family["id"], 0) + + return families + + +def main(): + global appstartaddr, familyid + def error(msg): + print(msg, file=sys.stderr) + sys.exit(1) + parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') + parser.add_argument('input', metavar='INPUT', type=str, nargs='?', + help='input file (HEX, BIN or UF2)') + parser.add_argument('-b', '--base', dest='base', type=str, + default="0x2000", + help='set base address of application for BIN format (default: 0x2000)') + parser.add_argument('-f', '--family', dest='family', type=str, + default="0x0", + help='specify familyID - number or name (default: 0x0)') + parser.add_argument('-o', '--output', metavar="FILE", dest='output', type=str, + help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') + parser.add_argument('-d', '--device', dest="device_path", + help='select a device path to flash') + parser.add_argument('-l', '--list', action='store_true', + help='list connected devices') + parser.add_argument('-c', '--convert', action='store_true', + help='do not flash, just convert') + parser.add_argument('-D', '--deploy', action='store_true', + help='just flash, do not convert') + parser.add_argument('-w', '--wait', action='store_true', + help='wait for device to flash') + parser.add_argument('-C', '--carray', action='store_true', + help='convert binary file to a C array, not UF2') + parser.add_argument('-i', '--info', action='store_true', + help='display header information from UF2, do not convert') + args = parser.parse_args() + appstartaddr = int(args.base, 0) + + families = load_families() + + if args.family.upper() in families: + familyid = families[args.family.upper()] + else: + try: + familyid = int(args.family, 0) + except ValueError: + error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) + + if args.list: + list_drives() + else: + if not args.input: + error("Need input file") + with open(args.input, mode='rb') as f: + inpbuf = f.read() + from_uf2 = is_uf2(inpbuf) + ext = "uf2" + if args.deploy: + outbuf = inpbuf + elif from_uf2 and not args.info: + outbuf = convert_from_uf2(inpbuf) + ext = "bin" + elif from_uf2 and args.info: + outbuf = "" + convert_from_uf2(inpbuf) + elif is_hex(inpbuf): + outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8")) + elif args.carray: + outbuf = convert_to_carray(inpbuf) + ext = "h" + else: + outbuf = convert_to_uf2(inpbuf) + if not args.deploy and not args.info: + print("Converted to %s, output size: %d, start address: 0x%x" % + (ext, len(outbuf), appstartaddr)) + if args.convert or ext != "uf2": + if args.output == None: + args.output = "flash." + ext + if args.output: + write_file(args.output, outbuf) + if ext == "uf2" and not args.convert and not args.info: + drives = get_drives() + if len(drives) == 0: + if args.wait: + print("Waiting for drive to deploy...") + while len(drives) == 0: + sleep(0.1) + drives = get_drives() + elif not args.output: + error("No drive to deploy.") + for d in drives: + print("Flashing %s (%s)" % (d, board_id(d))) + write_file(d + "/NEW.UF2", outbuf) + + +if __name__ == "__main__": + main() diff --git a/scripts/uf2/uf2families.json b/scripts/uf2/uf2families.json new file mode 100644 index 000000000..408caefed --- /dev/null +++ b/scripts/uf2/uf2families.json @@ -0,0 +1,247 @@ +[ + { + "id": "0x16573617", + "short_name": "ATMEGA32", + "description": "Microchip (Atmel) ATmega32" + }, + { + "id": "0x1851780a", + "short_name": "SAML21", + "description": "Microchip (Atmel) SAML21" + }, + { + "id": "0x1b57745f", + "short_name": "NRF52", + "description": "Nordic NRF52" + }, + { + "id": "0x1c5f21b0", + "short_name": "ESP32", + "description": "ESP32" + }, + { + "id": "0x1e1f432d", + "short_name": "STM32L1", + "description": "ST STM32L1xx" + }, + { + "id": "0x202e3a91", + "short_name": "STM32L0", + "description": "ST STM32L0xx" + }, + { + "id": "0x21460ff0", + "short_name": "STM32WL", + "description": "ST STM32WLxx" + }, + { + "id": "0x2abc77ec", + "short_name": "LPC55", + "description": "NXP LPC55xx" + }, + { + "id": "0x300f5633", + "short_name": "STM32G0", + "description": "ST STM32G0xx" + }, + { + "id": "0x31d228c6", + "short_name": "GD32F350", + "description": "GD32F350" + }, + { + "id": "0x04240bdf", + "short_name": "STM32L5", + "description": "ST STM32L5xx" + }, + { + "id": "0x4c71240a", + "short_name": "STM32G4", + "description": "ST STM32G4xx" + }, + { + "id": "0x4fb2d5bd", + "short_name": "MIMXRT10XX", + "description": "NXP i.MX RT10XX" + }, + { + "id": "0x53b80f00", + "short_name": "STM32F7", + "description": "ST STM32F7xx" + }, + { + "id": "0x55114460", + "short_name": "SAMD51", + "description": "Microchip (Atmel) SAMD51" + }, + { + "id": "0x57755a57", + "short_name": "STM32F4", + "description": "ST STM32F4xx" + }, + { + "id": "0x5a18069b", + "short_name": "FX2", + "description": "Cypress FX2" + }, + { + "id": "0x5d1a0a2e", + "short_name": "STM32F2", + "description": "ST STM32F2xx" + }, + { + "id": "0x5ee21072", + "short_name": "STM32F1", + "description": "ST STM32F103" + }, + { + "id": "0x621e937a", + "short_name": "NRF52833", + "description": "Nordic NRF52833" + }, + { + "id": "0x647824b6", + "short_name": "STM32F0", + "description": "ST STM32F0xx" + }, + { + "id": "0x68ed2b88", + "short_name": "SAMD21", + "description": "Microchip (Atmel) SAMD21" + }, + { + "id": "0x6b846188", + "short_name": "STM32F3", + "description": "ST STM32F3xx" + }, + { + "id": "0x6d0922fa", + "short_name": "STM32F407", + "description": "ST STM32F407" + }, + { + "id": "0x6db66082", + "short_name": "STM32H7", + "description": "ST STM32H7xx" + }, + { + "id": "0x70d16653", + "short_name": "STM32WB", + "description": "ST STM32WBxx" + }, + { + "id": "0x7eab61ed", + "short_name": "ESP8266", + "description": "ESP8266" + }, + { + "id": "0x7f83e793", + "short_name": "KL32L2", + "description": "NXP KL32L2x" + }, + { + "id": "0x8fb060fe", + "short_name": "STM32F407VG", + "description": "ST STM32F407VG" + }, + { + "id": "0xada52840", + "short_name": "NRF52840", + "description": "Nordic NRF52840" + }, + { + "id": "0xbfdd4eee", + "short_name": "ESP32S2", + "description": "ESP32-S2" + }, + { + "id": "0xc47e5767", + "short_name": "ESP32S3", + "description": "ESP32-S3" + }, + { + "id": "0xd42ba06c", + "short_name": "ESP32C3", + "description": "ESP32-C3" + }, + { + "id": "0x2b88d29c", + "short_name": "ESP32C2", + "description": "ESP32-C2" + }, + { + "id": "0x332726f6", + "short_name": "ESP32H2", + "description": "ESP32-H2" + }, + { + "id": "0x540ddf62", + "short_name": "ESP32C6", + "description": "ESP32-C6" + }, + { + "id": "0x3d308e94", + "short_name": "ESP32P4", + "description": "ESP32-P4" + }, + { + "id": "0xe48bff56", + "short_name": "RP2040", + "description": "Raspberry Pi RP2040" + }, + { + "id": "0x00ff6919", + "short_name": "STM32L4", + "description": "ST STM32L4xx" + }, + { + "id": "0x9af03e33", + "short_name": "GD32VF103", + "description": "GigaDevice GD32VF103" + }, + { + "id": "0x4f6ace52", + "short_name": "CSK4", + "description": "LISTENAI CSK300x/400x" + }, + { + "id": "0x6e7348a8", + "short_name": "CSK6", + "description": "LISTENAI CSK60xx" + }, + { + "id": "0x11de784a", + "short_name": "M0SENSE", + "description": "M0SENSE BL702" + }, + { + "id": "0x4b684d71", + "short_name": "MaixPlay-U4", + "description": "Sipeed MaixPlay-U4(BL618)" + }, + { + "id": "0x9517422f", + "short_name": "RZA1LU", + "description": "Renesas RZ/A1LU (R7S7210xx)" + }, + { + "id": "0x2dc309c5", + "short_name": "STM32F411xE", + "description": "ST STM32F411xE" + }, + { + "id": "0x06d1097b", + "short_name": "STM32F411xC", + "description": "ST STM32F411xC" + }, + { + "id": "0x72721d4e", + "short_name": "NRF52832xxAA", + "description": "Nordic NRF52832xxAA" + }, + { + "id": "0x6f752678", + "short_name": "NRF52832xxAB", + "description": "Nordic NRF52832xxAB" + } +] diff --git a/src/jshardware.h b/src/jshardware.h index 186ad9a1b..71d38b673 100644 --- a/src/jshardware.h +++ b/src/jshardware.h @@ -408,8 +408,9 @@ void jsvGetProcessorPowerUsage(JsVar *devices); /// Perform a proper hard-reboot of the device void jshReboot(); -#ifdef STM32F4 +#if defined(STM32F4) || defined(ESPR_HAS_BOOTLOADER_UF2) /// Reboot into DFU mode +/// If the device has an UF2 bootloader, the device will reappear as a USB drive. void jshRebootToDFU(); #endif diff --git a/src/jswrap_espruino.c b/src/jswrap_espruino.c index f9031a057..3b87cc54f 100644 --- a/src/jswrap_espruino.c +++ b/src/jswrap_espruino.c @@ -2356,17 +2356,21 @@ void jswrap_espruino_reboot() { /*JSON{ "type" : "staticmethod", - "ifdef" : "STM32F4", + "#if" : "defined(STM32F4) || defined(ESPR_HAS_BOOTLOADER_UF2)", "class" : "E", "name" : "rebootToDFU", "generate" : "jswrap_espruino_rebootToDFU" } -Forces a hard reboot of the microcontroller into the ST DFU mode +Forces a hard reboot of the microcontroller into DFU mode. -**Note:** The device will stay in DFU mode until it is power-cycled or reset +If this is an ST device, this will be the ST DFU mode. + +If this device has an UF2 bootloader, it will reappear as a USB drive, onto which you can copy new firmware. + +**Note:** The device will stay in DFU mode until it is power-cycled or reset. */ void jswrap_espruino_rebootToDFU() { -#ifdef STM32F4 +#if defined(STM32F4) || defined(ESPR_HAS_BOOTLOADER_UF2) // ensure `E.on('kill',...` gets called and everything is torn down correctly jsiKill(); jsvKill(); diff --git a/targets/nrf5x/jshardware.c b/targets/nrf5x/jshardware.c index 143ec5307..a95903022 100644 --- a/targets/nrf5x/jshardware.c +++ b/targets/nrf5x/jshardware.c @@ -2887,6 +2887,14 @@ void jshReboot() { NVIC_SystemReset(); } +#ifdef ESPR_HAS_BOOTLOADER_UF2 +void jshRebootToDFU() { + enum { DFU_MAGIC_UF2_RESET = 0x57 }; + nrf_power_gpregret_set(DFU_MAGIC_UF2_RESET); + NVIC_SystemReset(); +} +#endif + /* Adds the estimated power usage of the microcontroller in uA to the 'devices' object. The CPU should be called 'CPU' */ void jsvGetProcessorPowerUsage(JsVar *devices) { // draws 4mA flat out, 3uA nothing otherwise