# Copyright 2014-present PlatformIO # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys from platform import system from os import makedirs from os.path import isdir, join import re import time from platformio.public import list_serial_ports from SCons.Script import (ARGUMENTS, COMMAND_LINE_TARGETS, AlwaysBuild, Builder, Default, DefaultEnvironment) def BeforeUpload(target, source, env): # pylint: disable=W0613,W0621 upload_options = {} if "BOARD" in env: upload_options = env.BoardConfig().get("upload", {}) env.AutodetectUploadPort() before_ports = list_serial_ports() if upload_options.get("use_1200bps_touch", False): env.TouchSerialPort("$UPLOAD_PORT", 1200) if upload_options.get("wait_for_upload_port", False): env.Replace(UPLOAD_PORT=env.WaitForNewSerialPort(before_ports)) def generate_uf2(target, source, env): elf_file = target[0].get_path() env.Execute( " ".join( [ "elf2uf2", '"%s"' % elf_file, '"%s"' % elf_file.replace(".elf", ".uf2"), ] ) ) env = DefaultEnvironment() platform = env.PioPlatform() board = env.BoardConfig() env.Replace( AR="arm-none-eabi-ar", AS="arm-none-eabi-as", CC="arm-none-eabi-gcc", CXX="arm-none-eabi-g++", GDB="arm-none-eabi-gdb", OBJCOPY="arm-none-eabi-objcopy", RANLIB="arm-none-eabi-ranlib", SIZETOOL="arm-none-eabi-size", ARFLAGS=["rc"], MKFSTOOL="mklittlefs", PICO_FS_IMAGE_NAME=env.get("PICO_FS_IMAGE_NAME", "littlefs"), SIZEPROGREGEXP=r"^(?:\.text|\.data|\.rodata|\.text.align|\.ARM.exidx)\s+(\d+).*", SIZEDATAREGEXP=r"^(?:\.data|\.bss|\.noinit)\s+(\d+).*", SIZECHECKCMD="$SIZETOOL -A -d $SOURCES", SIZEPRINTCMD='$SIZETOOL -B -d $SOURCES', PROGSUFFIX=".elf" ) # Allow user to override via pre:script if env.get("PROGNAME", "program") == "program": env.Replace(PROGNAME="firmware") env.Append( BUILDERS=dict( ElfToBin=Builder( action=env.VerboseAction(" ".join([ "$OBJCOPY", "-O", "binary", "$SOURCES", "$TARGET" ]), "Building $TARGET"), suffix=".bin" ), ElfToHex=Builder( action=env.VerboseAction(" ".join([ "$OBJCOPY", "-O", "ihex", "-R", ".eeprom", "$SOURCES", "$TARGET" ]), "Building $TARGET"), suffix=".hex" ) ) ) if not env.get("PIOFRAMEWORK"): env.SConscript("frameworks/_bare.py") def convert_size_expression_to_int(expression): conversion_factors = { "M": 1024*1024, "MB": 1024*1024, "K": 1024, "KB": 1024, "B": 1, "": 1 # giving no conversion factor is factor 1. } # match . extract_regex = r'^((?:[0-9]*[.])?[0-9]+)([mkbMKB]*)$' res = re.findall(extract_regex, expression) # unparsable expression? Warning. if len(res) == 0: sys.stderr.write( "Error: Could not parse filesystem size expression '%s'." " Will treat as size = 0.\n" % str(expression)) return 0 # access first result number, factor = res[0] number = float(number) number *= conversion_factors[factor.upper()] return int(number) def fetch_fs_size(env): # follow generation formulas from makeboards.py for Earle Philhower core # given the total flash size, a user can specify # the amount for the filesystem (0MB, 2MB, 4MB, 8MB, 16MB) # via board_build.filesystem_size, # and we will calculate the flash size and eeprom size from that. flash_size = board.get("upload.maximum_size") filesystem_size = board.get("build.filesystem_size", "0MB") filesystem_size_int = convert_size_expression_to_int(filesystem_size) maximum_sketch_size = flash_size - 4096 - filesystem_size_int print("Flash size: %.2fMB" % (flash_size / 1024.0 / 1024.0)) print("Sketch size: %.2fMB" % (maximum_sketch_size / 1024.0 / 1024.0)) print("Filesystem size: %.2fMB" % (filesystem_size_int / 1024.0 / 1024.0)) eeprom_start = 0x10000000 + flash_size - 4096 fs_start = 0x10000000 + flash_size - 4096 - filesystem_size_int fs_end = 0x10000000 + flash_size - 4096 if maximum_sketch_size <= 0: sys.stderr.write( "Error: Filesystem too large for given flash. " "Can at max be flash size - 4096 bytes. " "Available sketch size with current " "config would be %d bytes.\n" % maximum_sketch_size) sys.stderr.flush() env.Exit(1) env["PICO_FLASH_LENGTH"] = maximum_sketch_size env["PICO_EEPROM_START"] = eeprom_start env["FS_START"] = fs_start env["FS_END"] = fs_end # LittleFS configuration paramters taken from # https://github.com/earlephilhower/arduino-pico-littlefs-plugin/blob/master/src/PicoLittleFS.java env["FS_PAGE"] = 256 env["FS_BLOCK"] = 4096 print("Maximium Sketch size: %d " "EEPROM start: %s Filesystem start: %s " "Filesystem end: %s" % (maximum_sketch_size, hex(eeprom_start), hex(fs_start), hex(fs_end))) def __fetch_fs_size(target, source, env): fetch_fs_size(env) return (target, source) env.Append( BUILDERS=dict( DataToBin=Builder( action=env.VerboseAction(" ".join([ '"$MKFSTOOL"', "-c", "$SOURCES", "-p", "$FS_PAGE", "-b", "$FS_BLOCK", "-s", "${FS_END - FS_START}", "$TARGET" ]), "Building file system image from '$SOURCES' directory to $TARGET"), emitter=__fetch_fs_size, source_factory=env.Dir, suffix=".bin" ) ) ) # store function to get infno about filesystems for builder scripts. env["fetch_fs_size"] = fetch_fs_size # # Target: Build executable and linkable firmware # target_elf = None if "nobuild" in COMMAND_LINE_TARGETS: target_elf = join("$BUILD_DIR", "${PROGNAME}.elf") target_firm = join("$BUILD_DIR", "${PROGNAME}.bin") else: target_elf = env.BuildProgram() if set(["buildfs", "uploadfs"]) & set(COMMAND_LINE_TARGETS): target_firm = env.DataToBin( join("$BUILD_DIR", "${PICO_FS_IMAGE_NAME}"), "$PROJECTDATA_DIR") AlwaysBuild(target_firm) else: target_firm = env.ElfToBin(join("$BUILD_DIR", "${PROGNAME}"), target_elf) env.Depends(target_firm, "checkprogsize") env.AddPlatformTarget("buildfs", target_firm, target_firm, "Build Filesystem Image") AlwaysBuild(env.Alias("nobuild", target_firm)) target_buildprog = env.Alias("buildprog", target_firm, target_firm) env.AddPostAction( target_elf, env.VerboseAction(generate_uf2, "Generating UF2 image") ) def _update_max_upload_size(env): fetch_fs_size(env) env.BoardConfig().update("upload.maximum_size", env["PICO_FLASH_LENGTH"]) # update max upload size based on set sketch size (or raw maximum size) if env.get("PIOMAINPROG"): env.AddPreAction( "checkprogsize", env.VerboseAction( lambda source, target, env: _update_max_upload_size(env), "Retrieving maximum program size $SOURCE")) # remove after PIO Core 3.6 release elif set(["checkprogsize", "upload"]) & set(COMMAND_LINE_TARGETS): _update_max_upload_size(env) # # Target: Print binary size # target_size = env.Alias( "size", target_elf, env.VerboseAction("$SIZEPRINTCMD", "Calculating size $SOURCE")) AlwaysBuild(target_size) def DelayBeforeUpload(target, source, env): # pylint: disable=W0613,W0621 time.sleep(0.5) def RebootPico(target, source, env): time.sleep(0.5) env.Execute( '"%s" reboot' % join(platform.get_package_dir("tool-rp2040tools") or "", "picotool") ) # # Target: Upload by default .bin file # debug_tools = env.BoardConfig().get("debug.tools", {}) upload_protocol = env.subst("$UPLOAD_PROTOCOL") or "picotool" upload_actions = [] upload_source = target_firm def UploadUF2ToDisk(target, source, env): assert "UPLOAD_PORT" in env progname = env.subst("$PROGNAME") ext = "uf2" fpath = join(env.subst("$BUILD_DIR"), "%s.%s" % (progname, ext)) if not isfile(fpath): print( "Firmware file %s not found.\n" % fpath ) return copyfile(fpath, join(env.subst("$UPLOAD_PORT"), "%s.%s" % (progname, ext))) print( "Firmware has been successfully uploaded.\n" "(Some boards may require manual hard reset)" ) env.UploadUF2ToDisk = UploadUF2ToDisk if upload_protocol == "mbed": upload_actions = [ env.VerboseAction(env.AutodetectUploadPort, "Looking for upload disk..."), env.VerboseAction(env.UploadUF2ToDisk, "Uploading $SOURCE") ] elif upload_protocol == "picotool": env.Replace( UPLOADER=join(platform.get_package_dir("tool-rp2040tools") or "", "rp2040load"), UPLOADERFLAGS=["-v", "-D"], UPLOADCMD='"$UPLOADER" $UPLOADERFLAGS $SOURCES' ) if "uploadfs" in COMMAND_LINE_TARGETS: env.Replace( UPLOADER=join(platform.get_package_dir("tool-rp2040tools") or "", "picotool"), UPLOADERFLAGS=[ "load", "--verify" ], UPLOADCMD="$UPLOADER $UPLOADERFLAGS $SOURCES --offset ${hex(FS_START)}", ) upload_actions = [ env.VerboseAction(BeforeUpload, "Looking for upload port..."), env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE"), ] # picotool seems to need just a tiny bit of delay, but rp2040 load not.. if "uploadfs" in COMMAND_LINE_TARGETS: upload_actions.insert(1, env.VerboseAction(DelayBeforeUpload, "Delaying a tiny bit...")) # reboot after filesystem upload upload_actions.append(env.VerboseAction(RebootPico, "Rebooting device...")) upload_source = target_elf elif upload_protocol.startswith("jlink"): def _jlink_cmd_script(env, source): build_dir = env.subst("$BUILD_DIR") if not isdir(build_dir): makedirs(build_dir) script_path = join(build_dir, "upload.jlink") commands = [ "h", "loadbin %s, %s" % (source, board.get( "upload.offset_address", "0x0")), "r", "q" ] with open(script_path, "w") as fp: fp.write("\n".join(commands)) return script_path env.Replace( __jlink_cmd_script=_jlink_cmd_script, UPLOADER="JLink.exe" if system() == "Windows" else "JLinkExe", UPLOADERFLAGS=[ "-device", board.get("debug", {}).get("jlink_device"), "-speed", env.GetProjectOption("debug_speed", "4000"), "-if", ("jtag" if upload_protocol == "jlink-jtag" else "swd"), "-autoconnect", "1", "-NoGui", "1" ], UPLOADCMD='$UPLOADER $UPLOADERFLAGS -CommanderScript "${__jlink_cmd_script(__env__, SOURCE)}"' ) upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")] upload_source = env.ElfToHex(join("$BUILD_DIR", "${PROGNAME}"), target_elf) elif upload_protocol in debug_tools: openocd_args = [ "-d%d" % (2 if int(ARGUMENTS.get("PIOVERBOSE", 0)) else 1) ] openocd_args.extend( debug_tools.get(upload_protocol).get("server").get("arguments", [])) if env.GetProjectOption("debug_speed"): openocd_args.extend( ["-c", "adapter speed %s" % env.GetProjectOption("debug_speed")] ) if "uploadfs" in COMMAND_LINE_TARGETS: # filesystem upload. use FS_START. openocd_args.extend([ "-c", "program {$SOURCE} ${hex(FS_START)} verify reset; shutdown;" ]) else: # normal firmware upload. flash starts at 0x10000000 openocd_args.extend([ "-c", "program {$SOURCE} %s verify reset; shutdown;" % # board.get("upload.offset_address", "0x10000000") board.get("upload.offset_address", "") #because of the logic selecting elf or firm (bin), we need to default to no offset because we already defaulted to elf a few lines below when no offset is needed # programming fails otherwise ]) openocd_args = [ f.replace("$PACKAGE_DIR", platform.get_package_dir( "tool-openocd-raspberrypi") or "") for f in openocd_args ] env.Replace( UPLOADER="openocd", UPLOADERFLAGS=openocd_args, UPLOADCMD="$UPLOADER $UPLOADERFLAGS") if not board.get("upload").get("offset_address"): upload_source = target_elf upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")] # custom upload tool elif upload_protocol == "custom": upload_actions = [env.VerboseAction("$UPLOADCMD", "Uploading $SOURCE")] if not upload_actions: sys.stderr.write("Warning! Unknown upload protocol %s\n" % upload_protocol) AlwaysBuild(env.Alias("upload", upload_source, upload_actions)) env.AddPlatformTarget("uploadfs", target_firm, upload_actions, "Upload Filesystem Image") # # Default targets # Default([target_buildprog, target_size])