because of the logic selecting elf or firm (bin), we need to default to no offset on normal firmware upload because we already defaulted to elf a few lines below if upload.offset_address is not user set so when no offset is needed. programming fails otherwise Unfortunately, my way of modifying the code probably sucks because I've never written anything in python but I assumed the second arg is the default n case the first is missing. Haven't had time to dig too much into it.
419 lines
14 KiB
Python
419 lines
14 KiB
Python
# Copyright 2014-present PlatformIO <contact@platformio.org>
|
|
#
|
|
# 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, isfile
|
|
from shutil import copyfile
|
|
import re
|
|
import time
|
|
|
|
from platformio.util import get_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 = get_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 <floating pointer number><conversion factor>.
|
|
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_size = flash_size - 4096 - filesystem_size_int
|
|
|
|
print("Flash size: %.2fMB" % (flash_size / 1024.0 / 1024.0))
|
|
print("Sketch size: %.2fMB" % (maximum_size / 1024.0 / 1024.0))
|
|
print("Filesystem size: %.2fMB" % (filesystem_size_int / 1024.0 / 1024.0))
|
|
|
|
flash_length = maximum_size
|
|
eeprom_start = 0x10000000 + flash_size - 4096
|
|
fs_start = 0x10000000 + flash_size - 4096 - filesystem_size_int
|
|
fs_end = 0x10000000 + flash_size - 4096
|
|
|
|
if maximum_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_size)
|
|
sys.stderr.flush()
|
|
env.Exit(-1)
|
|
|
|
env["PICO_FLASH_LENGTH"] = flash_length
|
|
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 size: %d Flash Length: %d "
|
|
"EEPROM Start: %d Filesystem start %d "
|
|
"Filesystem end %s" %
|
|
(maximum_size,flash_length, eeprom_start, fs_start, 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 CSV file
|
|
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)"
|
|
)
|
|
|
|
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
|
|
]
|
|
# use ELF file for upload, not bin (target_firm). otherwise needs
|
|
# offset 0x10000000
|
|
#upload_source = target_elf
|
|
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])
|