feat: publish FreeRTOS C FC03 card
This commit is contained in:
+275
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Simple utility for programming SPI flash via a UART shell in the bootloader.
|
||||
# The bootloader possesses a 1-page buffer which we can write to, read from and
|
||||
# checksum.
|
||||
# It also possesses commands for transfers between this buffer and the SPI flash,
|
||||
# flash erasure, and launching the 2nd stage boot code.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import serial
|
||||
import serial.tools.list_ports
|
||||
import sys
|
||||
import time
|
||||
|
||||
PAGESIZE = 2 ** 8
|
||||
SECTORSIZE = 2 ** 12
|
||||
BLOCKSIZE = 2 ** 16
|
||||
|
||||
SRAM_LOAD_ADDR = 0x2 << 28
|
||||
SRAM_EXEC_ADDR = SRAM_LOAD_ADDR + 0xc0
|
||||
|
||||
class ProtocolError(Exception):
|
||||
pass
|
||||
|
||||
class CMD:
|
||||
NOP = '\n'.encode()
|
||||
WRITE_BUF = 'w'.encode()
|
||||
READ_BUF = 'r'.encode()
|
||||
GET_CHECKSUM = 'c'.encode()
|
||||
SET_ADDR = 'a'.encode()
|
||||
WRITE_FLASH = 'W'.encode()
|
||||
READ_FLASH = 'R'.encode()
|
||||
ERASE_SECTOR = 'E'.encode()
|
||||
ERASE_BLOCK = 'B'.encode()
|
||||
BOOT_2ND = '2'.encode()
|
||||
LOAD_MEM = 'l'.encode()
|
||||
EXEC_MEM = 'x'.encode()
|
||||
ACK = ':'.encode()
|
||||
|
||||
def reset_shell(port):
|
||||
while True:
|
||||
port.write(CMD.NOP * (PAGESIZE + 1))
|
||||
port.flushOutput()
|
||||
time.sleep(0.02)
|
||||
port.flushInput()
|
||||
port.write(CMD.NOP)
|
||||
time.sleep(0.02)
|
||||
if port.readable() and port.read_all().endswith(CMD.ACK):
|
||||
break
|
||||
|
||||
def check_ack(port):
|
||||
resp = port.read()
|
||||
if len(resp) == 0 or resp != CMD.ACK:
|
||||
print("Got '{!r}'".format(resp))
|
||||
raise ProtocolError()
|
||||
|
||||
def write_buf(port, data, check=True):
|
||||
assert(len(data) == 256)
|
||||
port.write(CMD.WRITE_BUF)
|
||||
port.write(data)
|
||||
if check:
|
||||
check_ack(port)
|
||||
# TODO checksum
|
||||
|
||||
def read_buf(port, pipelined=False):
|
||||
if not pipelined:
|
||||
port.write(CMD.READ_BUF)
|
||||
resp = port.read(PAGESIZE)
|
||||
if len(resp) != PAGESIZE:
|
||||
raise ProtocolError()
|
||||
return resp
|
||||
# TODO checksum
|
||||
|
||||
def set_addr(port, addr):
|
||||
addr_b = bytes([
|
||||
(addr >> 16) & 0xff,
|
||||
(addr >> 8) & 0xff,
|
||||
(addr >> 0) & 0xff
|
||||
])
|
||||
port.write(CMD.SET_ADDR + addr_b)
|
||||
echo = port.read(3)
|
||||
if echo != addr_b:
|
||||
raise ProtocolError()
|
||||
check_ack(port)
|
||||
|
||||
def progress(header, frac, width=40):
|
||||
n = int(width * frac)
|
||||
sys.stdout.write("\r" + header + "▕" + "▒" * n + " " * (width - n) + "▏")
|
||||
|
||||
def read_flash(port, addr, size):
|
||||
set_addr(port, addr)
|
||||
data = bytes()
|
||||
addr_range = range(addr, addr + size, PAGESIZE)
|
||||
for a in addr_range:
|
||||
port.write(CMD.READ_FLASH)
|
||||
port.write(CMD.READ_BUF) # Should have space for 2 cmds in FIFO. Hoist this one up from read_buf()
|
||||
progress("Read: ", (a - addr) / size)
|
||||
check_ack(port)
|
||||
data += read_buf(port, pipelined=True)
|
||||
progress("Read: ", 1)
|
||||
if len(data) > size:
|
||||
data = data[:size] # ouch
|
||||
return data
|
||||
|
||||
def write_flash(port, addr, data):
|
||||
assert(addr % PAGESIZE == 0)
|
||||
if len(data) % PAGESIZE != 0:
|
||||
data += bytes(PAGESIZE - len(data) % PAGESIZE)
|
||||
set_addr(port, addr)
|
||||
for i in range(len(data) // PAGESIZE):
|
||||
write_buf(port, data[i * PAGESIZE : (i + 1) * PAGESIZE], check=False)
|
||||
port.write(CMD.WRITE_FLASH)
|
||||
progress("Write: ", i / (len(data) // PAGESIZE))
|
||||
check_ack(port)
|
||||
check_ack(port)
|
||||
progress("Write: ", 1)
|
||||
|
||||
def erase_flash(port, addr, size):
|
||||
end = addr + size
|
||||
if end % SECTORSIZE != 0:
|
||||
end += SECTORSIZE - end % SECTORSIZE
|
||||
start = addr - addr % SECTORSIZE
|
||||
set_addr(port, start)
|
||||
a = start
|
||||
while a < end:
|
||||
progress("Erase: ", (a - start) / (end - start))
|
||||
if end - a >= BLOCKSIZE and a % BLOCKSIZE == 0:
|
||||
port.write(CMD.ERASE_BLOCK)
|
||||
a += BLOCKSIZE
|
||||
else:
|
||||
port.write(CMD.ERASE_SECTOR)
|
||||
a += SECTORSIZE
|
||||
check_ack(port)
|
||||
progress("Erase: ", 1)
|
||||
|
||||
def run_2nd_stage(port):
|
||||
set_addr(port, 0x123456)
|
||||
port.write(CMD.BOOT_2ND)
|
||||
check_ack(port)
|
||||
|
||||
def load_mem(port, data):
|
||||
set_addr(port, 0xb00000 | SRAM_LOAD_ADDR)
|
||||
port.write(CMD.LOAD_MEM + bytes([
|
||||
(len(data) >> 16) & 0xff,
|
||||
(len(data) >> 8) & 0xff,
|
||||
(len(data) >> 0) & 0xff
|
||||
]))
|
||||
check_ack(port)
|
||||
port.write(data) # yup
|
||||
check_ack(port)
|
||||
|
||||
def exec_mem(port):
|
||||
set_addr(port, 0xb00000 | SRAM_EXEC_ADDR)
|
||||
port.write(CMD.EXEC_MEM)
|
||||
check_ack(port)
|
||||
|
||||
def any_int(x):
|
||||
return int(x, 0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("file", nargs="?", help="Filename to read from or dump to.")
|
||||
parser.add_argument("--uart", "-u", help="Path to UART device (e.g. /dev/ttyUSB0)")
|
||||
parser.add_argument("--baud", "-b", help="Baud rate for UART. Default 1 Mbaud")
|
||||
parser.add_argument("--write", "-w", action="store_true",
|
||||
help="Write to flash (default is read)")
|
||||
parser.add_argument("--verify", "-v", action="store_true",
|
||||
help="Verify contents after programming (use with --write)")
|
||||
parser.add_argument("--run", "-r", action="store_true",
|
||||
help="Run code from flash after programming")
|
||||
parser.add_argument("--execute", "-x", action="store_true",
|
||||
help="Load code directly into SRAM and execute it. Not to be combined with -w,-v,-r")
|
||||
parser.add_argument("--start", "-s", type=any_int, help="Base address to start read/write")
|
||||
parser.add_argument("--len", "-l", type=any_int, help="Number of bytes to read, or override file length for write.")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Do some simple parameter checking to make it harder to accidentally trash your device
|
||||
if args.write and args.file is None:
|
||||
sys.exit("Must specify filename for write")
|
||||
if args.start is None:
|
||||
args.start = 0
|
||||
if args.uart is None:
|
||||
args.uart = sorted(p.device for p in serial.tools.list_ports.comports())[-1]
|
||||
if args.baud is None:
|
||||
args.baud = 1000000
|
||||
if args.verify and not args.write:
|
||||
sys.exit("Verify is only valid for a write operation")
|
||||
if args.run and not args.write:
|
||||
sys.exit("Run is only valid for a write operation")
|
||||
if args.write and (args.start % PAGESIZE != 0):
|
||||
sys.exit("Writes must be aligned on a {}-byte boundary.".format(PAGESIZE))
|
||||
if args.execute and (args.write or args.verify or args.run or args.start or args.len):
|
||||
sys.exit("--execute is not compatible with flash-related arguments")
|
||||
if args.write or args.execute:
|
||||
try:
|
||||
filesize = os.stat(args.file).st_size
|
||||
if args.len is None:
|
||||
args.len = filesize
|
||||
except:
|
||||
sys.exit("Could not open file '{}'".format(args.file))
|
||||
else:
|
||||
if args.len is None:
|
||||
args.len = PAGESIZE
|
||||
|
||||
# Don't want to overwrite their image if they forget -w
|
||||
if not (args.write or args.execute) and args.file and os.path.exists(args.file):
|
||||
resp = ""
|
||||
while not resp in ["y", "n"]:
|
||||
resp = input("File '{}' exists. Overwrite? (y/n) ".format(args.file))
|
||||
resp = resp.lower().strip()
|
||||
if resp == "n":
|
||||
sys.exit(0)
|
||||
|
||||
# Summarise what we're about to do
|
||||
if args.execute:
|
||||
print("Loading {} bytes to SRAM and running".format(args.len))
|
||||
else:
|
||||
print("{} {} bytes {} {}, starting at address 0x{:06x}".format(
|
||||
"Writing" + (" and verifying" if args.verify else "") if args.write else "Reading",
|
||||
args.len,
|
||||
"from" if args.write else "to",
|
||||
"stdout" if args.file is None else "file " + args.file,
|
||||
args.start,
|
||||
", and verifying" if args.verify else ""
|
||||
))
|
||||
|
||||
# And then do it
|
||||
# need to allow for page erase, upward of 60 ms. (Uh, actually it seems to be much longer)
|
||||
port = serial.Serial(args.uart, args.baud, timeout=1)
|
||||
print("Waiting for bootloader on {}...".format(port.name))
|
||||
reset_shell(port)
|
||||
print("")
|
||||
|
||||
if args.execute:
|
||||
print("Loading...")
|
||||
load_mem(port, open(args.file, "rb").read())
|
||||
print("Load ok, running...")
|
||||
exec_mem(port)
|
||||
elif args.write:
|
||||
data = open(args.file, "rb").read()
|
||||
if len(data) > args.len:
|
||||
data = data[:args.len]
|
||||
else:
|
||||
data = data + bytes(args.len - len(data)) # zero padding
|
||||
|
||||
start = time.time()
|
||||
erase_flash(port, args.start, args.len)
|
||||
print(" Took {:.1f} s\n".format(time.time() - start))
|
||||
start = time.time()
|
||||
write_flash(port, args.start, data)
|
||||
print(" Took {:.1f} s\n".format(time.time() - start))
|
||||
if args.verify:
|
||||
start = time.time()
|
||||
readback = read_flash(port, args.start, args.len)
|
||||
print(" Took {:.1f} s".format(time.time() - start))
|
||||
if readback != data:
|
||||
sys.exit("Verification failed.") # TODO: better info
|
||||
if args.run:
|
||||
print("Launching flash second stage")
|
||||
run_2nd_stage(port)
|
||||
print("Done")
|
||||
else:
|
||||
start = time.time()
|
||||
data = read_flash(port, args.start, args.len)
|
||||
print(" Took {:.1f} s\nDone".format(time.time() - start))
|
||||
if args.file:
|
||||
open(args.file, "wb").write(data)
|
||||
else:
|
||||
for i, byte in enumerate(data):
|
||||
if i % 8 == 0:
|
||||
sys.stdout.write("{:06x}: ".format(args.start + i))
|
||||
sys.stdout.write("{:02x}".format(byte))
|
||||
sys.stdout.write("\n" if i % 8 == 7 else " ")
|
||||
sys.stdout.write("\n")
|
||||
Reference in New Issue
Block a user