mirror of
https://github.com/QIDITECH/klipper.git
synced 2026-01-31 07:58:42 +03:00
plus4的klipper版本
This commit is contained in:
@@ -8,6 +8,7 @@ set -eu
|
||||
MAIN_DIR=${PWD}
|
||||
BUILD_DIR=${PWD}/ci_build
|
||||
export PATH=${BUILD_DIR}/pru-gcc/bin:${PATH}
|
||||
export PATH=${BUILD_DIR}/or1k-linux-musl-cross/bin:${PATH}
|
||||
PYTHON=${BUILD_DIR}/python-env/bin/python
|
||||
PYTHON2=${BUILD_DIR}/python2-env/bin/python
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ mkdir -p ${BUILD_DIR} ${CACHE_DIR}
|
||||
######################################################################
|
||||
|
||||
echo -e "\n\n=============== Install system dependencies\n\n"
|
||||
PKGS="virtualenv python-dev libffi-dev build-essential"
|
||||
PKGS="virtualenv python2-dev libffi-dev build-essential"
|
||||
PKGS="${PKGS} gcc-avr avr-libc"
|
||||
PKGS="${PKGS} libnewlib-arm-none-eabi gcc-arm-none-eabi binutils-arm-none-eabi"
|
||||
PKGS="${PKGS} pv libmpfr-dev libgmp-dev libmpc-dev texinfo bison flex"
|
||||
@@ -35,10 +35,10 @@ if [ ! -f ${PRU_FILE} ]; then
|
||||
cd ${BUILD_DIR}
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Your Name"
|
||||
git clone https://github.com/dinuxbg/gnupru -b 2018.03-beta-rc3 --depth 1
|
||||
git clone https://github.com/dinuxbg/gnupru -b 2023.01 --depth 1
|
||||
cd gnupru
|
||||
export PREFIX=${PRU_DIR}
|
||||
./download-and-patch.sh 2>&1 | pv -nli 30 > ${BUILD_DIR}/gnupru-build.log
|
||||
./download-and-prepare.sh 2>&1 | pv -nli 30 > ${BUILD_DIR}/gnupru-build.log
|
||||
./build.sh 2>&1 | pv -nli 30 >> ${BUILD_DIR}/gnupru-build.log
|
||||
cd ${BUILD_DIR}
|
||||
tar cfz ${PRU_FILE} pru-gcc/
|
||||
@@ -47,7 +47,21 @@ else
|
||||
tar xfz ${PRU_FILE}
|
||||
fi
|
||||
|
||||
######################################################################
|
||||
# Install or1k-linux-musl toolchain
|
||||
######################################################################
|
||||
|
||||
echo -e "\n\n=============== Install or1k-linux-musl toolchain\n\n"
|
||||
TOOLCHAIN=or1k-linux-musl-cross
|
||||
TOOLCHAIN_ZIP=${TOOLCHAIN}.tgz
|
||||
GCC_VERSION=10
|
||||
TOOLCHAIN_ZIP_V=${TOOLCHAIN}-${GCC_VERSION}.tgz
|
||||
URL=https://more.musl.cc/${GCC_VERSION}/x86_64-linux-musl/
|
||||
if [ ! -f ${CACHE_DIR}/${TOOLCHAIN_ZIP_V} ]; then
|
||||
curl ${URL}/${TOOLCHAIN_ZIP} -o ${CACHE_DIR}/${TOOLCHAIN_ZIP_V}
|
||||
fi
|
||||
cd ${BUILD_DIR}
|
||||
tar xf ${CACHE_DIR}/${TOOLCHAIN_ZIP_V}
|
||||
######################################################################
|
||||
# Create python3 virtualenv environment
|
||||
######################################################################
|
||||
|
||||
203
scripts/dump_mcu.py
Normal file
203
scripts/dump_mcu.py
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env python
|
||||
# MCU flash dump assistant
|
||||
#
|
||||
# Copyright (C) 2022 Eric Callahan <arksine.code@gmail.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
KLIPPER_DIR = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), "../"))
|
||||
sys.path.append(os.path.join(KLIPPER_DIR, "klippy"))
|
||||
import reactor
|
||||
import serialhdl
|
||||
import clocksync
|
||||
|
||||
###########################################################
|
||||
#
|
||||
# Helper methods
|
||||
#
|
||||
###########################################################
|
||||
|
||||
def output_line(msg):
|
||||
sys.stdout.write("%s\n" % (msg,))
|
||||
sys.stdout.flush()
|
||||
|
||||
def output(msg):
|
||||
sys.stdout.write("%s" % (msg,))
|
||||
sys.stdout.flush()
|
||||
|
||||
DUMP_CMD="debug_read order=%d addr=%d"
|
||||
DUMP_RESP="debug_result"
|
||||
|
||||
class MCUDumpError(Exception):
|
||||
pass
|
||||
|
||||
class MCUDump:
|
||||
def __init__(self, args):
|
||||
self.reactor = reactor.Reactor()
|
||||
self.device = args.device
|
||||
self.baud = args.baud
|
||||
self.canbus_iface = args.canbus_iface
|
||||
self.canbus_nodeid = args.canbus_nodeid
|
||||
self.output_file = os.path.expanduser(args.outfile)
|
||||
try:
|
||||
self.read_start = int(args.read_start, 0)
|
||||
self.read_length = int(args.read_length, 0)
|
||||
except ValueError as e:
|
||||
raise MCUDumpError(
|
||||
"Error converting flash address: %s " % (str(e),)
|
||||
)
|
||||
if self.read_length <= 0:
|
||||
raise MCUDumpError("Read count must be greater than 0")
|
||||
self._serial = serialhdl.SerialReader(self.reactor)
|
||||
self.clocksync = clocksync.ClockSync(self.reactor)
|
||||
self.connect_completion = None
|
||||
self.connected = False
|
||||
|
||||
def connect(self):
|
||||
output("Connecting to MCU..")
|
||||
self.connect_completion = self.reactor.completion()
|
||||
self.connected = False
|
||||
self.reactor.register_callback(self._do_serial_connect)
|
||||
curtime = self.reactor.monotonic()
|
||||
while True:
|
||||
curtime = self.reactor.pause(curtime + 1.)
|
||||
output(".")
|
||||
if self.connect_completion.test():
|
||||
self.connected = self.connect_completion.wait()
|
||||
break
|
||||
self.connect_completion = None
|
||||
if not self.connected:
|
||||
output("\n")
|
||||
raise MCUDumpError("Unable to connect to MCU")
|
||||
output_line("Connected")
|
||||
msgparser = self._serial.get_msgparser()
|
||||
mcu_id = msgparser.get_constant("MCU")
|
||||
freq = msgparser.get_constant("CLOCK_FREQ")
|
||||
output_line("MCU type: %s" % (mcu_id,))
|
||||
output_line("Frequency: %s\n" % (freq,))
|
||||
|
||||
def _do_serial_connect(self, eventtime):
|
||||
endtime = eventtime + 60.
|
||||
while True:
|
||||
try:
|
||||
if self.canbus_iface is not None:
|
||||
self._serial.connect_canbus(
|
||||
self.device, self.canbus_nodeid, self.canbus_iface
|
||||
)
|
||||
elif (
|
||||
self.device.startswith("/dev/rpmsg_") or
|
||||
self.device.startswith("/tmp/")
|
||||
):
|
||||
self._serial.connect_pipe(self.device)
|
||||
else:
|
||||
self._serial.connect_uart(self.device, self.baud)
|
||||
self.clocksync.connect(self._serial)
|
||||
except Exception:
|
||||
curtime = self.reactor.monotonic()
|
||||
if curtime > endtime:
|
||||
self.connect_completion.complete(False)
|
||||
return
|
||||
output("Connection Error, retrying..")
|
||||
self._serial.disconnect()
|
||||
self.reactor.pause(curtime + 2.)
|
||||
else:
|
||||
break
|
||||
self.connect_completion.complete(True)
|
||||
|
||||
def disconnect(self):
|
||||
if not self.connected:
|
||||
return
|
||||
self._serial.disconnect()
|
||||
self.connected = False
|
||||
|
||||
def _dump_flash(self):
|
||||
addr = self.read_start
|
||||
count = self.read_length
|
||||
order = [2, 0, 1, 0][(addr | count) & 3]
|
||||
bsize = 1 << order
|
||||
# Query data from mcu
|
||||
output_line(
|
||||
"Reading %d bytes from flash, start address 0x%x\n"
|
||||
% (count, addr)
|
||||
)
|
||||
output("[")
|
||||
bytes_read = last_reported_pct = 0
|
||||
vals = []
|
||||
for i in range((count + bsize - 1) >> order):
|
||||
caddr = addr + (i << order)
|
||||
cmd = DUMP_CMD % (order, caddr)
|
||||
params = self._serial.send_with_response(cmd, DUMP_RESP)
|
||||
vals.append(params['val'])
|
||||
bytes_read += bsize
|
||||
pct = int(bytes_read / float(count) * 100 + .5)
|
||||
diff = (pct - last_reported_pct) // 2
|
||||
if diff:
|
||||
last_reported_pct = pct
|
||||
output("#" * diff)
|
||||
output_line("]\n")
|
||||
# Convert to byte format
|
||||
data = bytearray()
|
||||
for val in vals:
|
||||
for b in range(bsize):
|
||||
data.append((val >> (8 * b)) & 0xff)
|
||||
data = data[:count]
|
||||
with open(self.output_file, "wb") as f:
|
||||
f.write(data)
|
||||
output_line("Wrote %d bytes to '%s'" % (len(data), self.output_file))
|
||||
|
||||
def _run_dump_task(self, eventtime):
|
||||
self.connect()
|
||||
self._dump_flash()
|
||||
self.reactor.end()
|
||||
|
||||
def run(self):
|
||||
self.reactor.register_callback(self._run_dump_task)
|
||||
try:
|
||||
self.reactor.run()
|
||||
finally:
|
||||
self.disconnect()
|
||||
self.reactor.finalize()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="MCU Flash Dump Utility")
|
||||
|
||||
parser.add_argument(
|
||||
"-b", "--baud", metavar="<baud rate>", type=int,
|
||||
default=250000, help="Baud Rate")
|
||||
parser.add_argument(
|
||||
"-c", "--canbus_iface", metavar="<canbus iface>", default=None,
|
||||
help="Use CAN bus interface; <device> is the chip UUID")
|
||||
parser.add_argument(
|
||||
"-i", "--canbus_nodeid", metavar="<canbus nodeid>", type=int,
|
||||
default=64, help="The CAN nodeid to use (default 64)")
|
||||
parser.add_argument(
|
||||
"-s", "--read_start", metavar="<read start>", default="0x0",
|
||||
help="Flash address to start reading")
|
||||
parser.add_argument(
|
||||
"-l", "--read_length", metavar="<read length>", default="0x400",
|
||||
help="Number of bytes to read")
|
||||
parser.add_argument(
|
||||
"device", metavar="<device>", help="Device Serial Port")
|
||||
parser.add_argument(
|
||||
"outfile", metavar="<outfile>",
|
||||
help="Path to output file")
|
||||
args = parser.parse_args()
|
||||
logging.basicConfig(level=logging.CRITICAL)
|
||||
try:
|
||||
mcudump = MCUDump(args)
|
||||
mcudump.run()
|
||||
except Exception as e:
|
||||
output_line("\nMCU Dump Error: %s" % (str(e),))
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
sys.exit(-1)
|
||||
output_line("MCU Dump Complete")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
102
scripts/flash-ar100.py
Normal file
102
scripts/flash-ar100.py
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import mmap
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
FW_START = 0x00004000
|
||||
FW_BASE = 0x00040000 + FW_START
|
||||
FW_LIMIT = 0x00054000
|
||||
FW_SIZE = FW_LIMIT - FW_BASE
|
||||
|
||||
EXCEPTIONS_BASE = 0x00040000
|
||||
EXCEPTIONS_LIMIT = 0x00042000
|
||||
EXCEPTIONS_SIZE = EXCEPTIONS_LIMIT - EXCEPTIONS_BASE
|
||||
EXCEPTIONS_JUMP = FW_START # All exceptions reset program
|
||||
NR_OF_EXCEPTIONS = 14
|
||||
|
||||
R_CPU_CFG_PAGE_BASE = 0x01F01000
|
||||
R_CPU_CFG_PAGE_LIMIT = 0x01F02000
|
||||
R_CPU_CFG_SIZE = R_CPU_CFG_PAGE_LIMIT - R_CPU_CFG_PAGE_BASE
|
||||
R_CPU_CFG_OFFSET = 0xC00
|
||||
R_CPU_CLK_OFFSET = 0x400
|
||||
|
||||
parser = argparse.ArgumentParser(description='Flash and reset SRAM A2 of A64')
|
||||
parser.add_argument('filename', nargs='?', help='binary file to write')
|
||||
parser.add_argument('--reset', action='store_true', help='reset the AR100')
|
||||
parser.add_argument('--halt', action='store_true', help='Halt the AR100')
|
||||
parser.add_argument('--bl31', action='store_true', help='write bl31')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def write_exception_vectors():
|
||||
print("Writing exception vectors")
|
||||
with open("/dev/mem", "w+b") as f:
|
||||
exc = mmap.mmap(f.fileno(),
|
||||
length=EXCEPTIONS_SIZE,
|
||||
offset=EXCEPTIONS_BASE)
|
||||
for i in range(NR_OF_EXCEPTIONS):
|
||||
add = i * 0x100
|
||||
exc[add:add + 4] = ((EXCEPTIONS_JUMP - add) >> 2).to_bytes(
|
||||
4, byteorder='little')
|
||||
exc.close()
|
||||
|
||||
|
||||
def assert_deassert_reset(ass):
|
||||
with open("/dev/mem", "w+b") as f:
|
||||
r_cpucfg = mmap.mmap(f.fileno(),
|
||||
length=R_CPU_CFG_SIZE,
|
||||
offset=R_CPU_CFG_PAGE_BASE)
|
||||
if ass:
|
||||
r_cpucfg[R_CPU_CFG_OFFSET] &= ~0x01
|
||||
if r_cpucfg[R_CPU_CFG_OFFSET] & 0x01:
|
||||
print("failed to assert reset")
|
||||
else:
|
||||
r_cpucfg[R_CPU_CFG_OFFSET] |= 0x01
|
||||
if not (r_cpucfg[R_CPU_CFG_OFFSET] & 0x01):
|
||||
print("failed to deassert reset")
|
||||
r_cpucfg.close()
|
||||
|
||||
|
||||
def write_file(filename):
|
||||
with open(filename, "r+b") as fw:
|
||||
data = fw.read()
|
||||
if len(data) > FW_SIZE:
|
||||
print("File does not fit in memory")
|
||||
sys.exit(1)
|
||||
print("Writing file to SRAM A2")
|
||||
with open("/dev/mem", "w+b") as f:
|
||||
sram_a2 = mmap.mmap(f.fileno(), length=FW_SIZE, offset=FW_BASE)
|
||||
sram_a2[0:len(data)] = data
|
||||
sram_a2.close()
|
||||
|
||||
|
||||
def clear_magic_word():
|
||||
with open("/dev/mem", "w+b") as f:
|
||||
sram_a2 = mmap.mmap(f.fileno(), length=FW_SIZE, offset=FW_BASE)
|
||||
sram_a2[0] = 0x0
|
||||
sram_a2.close()
|
||||
|
||||
|
||||
if args.reset:
|
||||
print("Resetting AR100")
|
||||
assert_deassert_reset(1)
|
||||
assert_deassert_reset(0)
|
||||
sys.exit(0)
|
||||
|
||||
if args.filename:
|
||||
if args.bl31:
|
||||
print("writing bl31")
|
||||
assert_deassert_reset(1)
|
||||
write_file(args.filename)
|
||||
else:
|
||||
assert_deassert_reset(1)
|
||||
write_exception_vectors()
|
||||
write_file(args.filename)
|
||||
assert_deassert_reset(0)
|
||||
|
||||
if args.halt:
|
||||
print("Halting AR100")
|
||||
assert_deassert_reset(1)
|
||||
clear_magic_word()
|
||||
@@ -7,10 +7,17 @@ if [ "$EUID" -ne 0 ]; then
|
||||
fi
|
||||
set -e
|
||||
|
||||
# Setting build output directory
|
||||
if [ -z "${1}" ]; then
|
||||
out='out'
|
||||
else
|
||||
out=${1}
|
||||
fi
|
||||
|
||||
# Install new micro-controller code
|
||||
echo "Installing micro-controller code to /usr/local/bin/"
|
||||
rm -f /usr/local/bin/klipper_mcu
|
||||
cp out/klipper.elf /usr/local/bin/klipper_mcu
|
||||
cp ${out}/klipper.elf /usr/local/bin/klipper_mcu
|
||||
sync
|
||||
|
||||
# Restart (if system install script present)
|
||||
@@ -24,3 +31,8 @@ if [ -f /etc/init.d/klipper_mcu ]; then
|
||||
echo "Attempting host MCU restart..."
|
||||
service klipper_mcu restart
|
||||
fi
|
||||
|
||||
if [ -f /etc/systemd/system/klipper-mcu.service ]; then
|
||||
echo "Attempting host MCU restart..."
|
||||
systemctl restart klipper-mcu
|
||||
fi
|
||||
|
||||
@@ -10,6 +10,7 @@ KLIPPER_BIN_DEFAULT=$KLIPPER_BIN
|
||||
KLIPPER_DICT_DEFAULT="${SRCDIR}/out/klipper.dict"
|
||||
SPI_FLASH="${SRCDIR}/scripts/spi_flash/spi_flash.py"
|
||||
BAUD_ARG=""
|
||||
CHECK_ARG=""
|
||||
# Force script to exit if an error occurs
|
||||
set -e
|
||||
|
||||
@@ -17,7 +18,7 @@ print_help_message()
|
||||
{
|
||||
echo "SD Card upload utility for Klipper"
|
||||
echo
|
||||
echo "usage: flash_sdcard.sh [-h] [-l] [-b <baud>] [-f <firmware>] [-d <dictionary>]"
|
||||
echo "usage: flash_sdcard.sh [-h] [-l] [-c] [-b <baud>] [-f <firmware>] [-d <dictionary>]"
|
||||
echo " <device> <board>"
|
||||
echo
|
||||
echo "positional arguments:"
|
||||
@@ -27,13 +28,14 @@ print_help_message()
|
||||
echo "optional arguments:"
|
||||
echo " -h show this message"
|
||||
echo " -l list available boards"
|
||||
echo " -c run flash check/verify only (skip upload)"
|
||||
echo " -b <baud> serial baud rate (default is 250000)"
|
||||
echo " -f <firmware> path to klipper.bin"
|
||||
echo " -d <dictionary> path to klipper.dict for firmware validation"
|
||||
}
|
||||
|
||||
# Parse command line "optional args"
|
||||
while getopts "hlb:f:d:" arg; do
|
||||
while getopts "hlcb:f:d:" arg; do
|
||||
case $arg in
|
||||
h)
|
||||
print_help_message
|
||||
@@ -43,6 +45,7 @@ while getopts "hlb:f:d:" arg; do
|
||||
${KLIPPY_ENV} ${SPI_FLASH} -l
|
||||
exit 0
|
||||
;;
|
||||
c) CHECK_ARG="-c";;
|
||||
b) BAUD_ARG="-b ${OPTARG}";;
|
||||
f) KLIPPER_BIN=$OPTARG;;
|
||||
d) KLIPPER_DICT=$OPTARG;;
|
||||
@@ -82,4 +85,4 @@ fi
|
||||
|
||||
# Run Script
|
||||
echo "Flashing ${KLIPPER_BIN} to ${DEVICE}"
|
||||
${KLIPPY_ENV} ${SPI_FLASH} ${BAUD_ARG} ${KLIPPER_DICT} ${DEVICE} ${BOARD} ${KLIPPER_BIN}
|
||||
${KLIPPY_ENV} ${SPI_FLASH} ${CHECK_ARG} ${BAUD_ARG} ${KLIPPER_DICT} ${DEVICE} ${BOARD} ${KLIPPER_BIN}
|
||||
|
||||
@@ -193,6 +193,7 @@ def call_picoboot(bus, addr, binfile, sudo):
|
||||
|
||||
# Flash via Klipper modified "picoboot"
|
||||
def flash_picoboot(device, binfile, sudo):
|
||||
ttyname, serbypath = translate_serial_to_tty(device)
|
||||
buspath, devpath = translate_serial_to_usb_path(device)
|
||||
# We need one level up to get access to busnum/devnum files
|
||||
usbdir = os.path.dirname(devpath)
|
||||
@@ -202,7 +203,10 @@ def flash_picoboot(device, binfile, sudo):
|
||||
bus = f.read().strip()
|
||||
with open(usbdir + "/devnum") as f:
|
||||
addr = f.read().strip()
|
||||
call_picoboot(bus, addr, binfile, sudo)
|
||||
if detect_canboot(devpath):
|
||||
call_flashcan(serbypath, binfile)
|
||||
else:
|
||||
call_picoboot(bus, addr, binfile, sudo)
|
||||
|
||||
|
||||
######################################################################
|
||||
@@ -336,11 +340,14 @@ def flash_rp2040(options, binfile):
|
||||
sys.exit(-1)
|
||||
|
||||
MCUTYPES = {
|
||||
'sam3': flash_atsam3, 'sam4': flash_atsam4, 'samd': flash_atsamd,
|
||||
'same70': flash_atsam4, 'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1,
|
||||
'sam3': flash_atsam3, 'sam4': flash_atsam4, 'same70': flash_atsam4,
|
||||
'samd': flash_atsamd, 'same5': flash_atsamd,
|
||||
'lpc176': flash_lpc176x, 'stm32f103': flash_stm32f1,
|
||||
'stm32f4': flash_stm32f4, 'stm32f042': flash_stm32f4,
|
||||
'stm32f072': flash_stm32f4, 'stm32g0b1': flash_stm32f4,
|
||||
'stm32h7': flash_stm32f4, 'rp2040': flash_rp2040
|
||||
'stm32f070': flash_stm32f4, 'stm32f072': flash_stm32f4,
|
||||
'stm32g0b1': flash_stm32f4, 'stm32f7': flash_stm32f4,
|
||||
'stm32h7': flash_stm32f4, 'stm32l4': flash_stm32f4,
|
||||
'stm32g4': flash_stm32f4, 'rp2040': flash_rp2040,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,35 +17,50 @@ MAX_TITLE_LENGTH=65
|
||||
def parse_log(logname, opts):
|
||||
with open(logname) as f:
|
||||
for header in f:
|
||||
if not header.startswith('#'):
|
||||
if header.startswith('#'):
|
||||
continue
|
||||
if header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'):
|
||||
# Processed power spectral density file
|
||||
break
|
||||
if not header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'):
|
||||
# Raw accelerometer data
|
||||
return np.loadtxt(logname, comments='#', delimiter=',')
|
||||
# Power spectral density data or shaper calibration data
|
||||
opts.error("File %s does not contain raw accelerometer data and therefore "
|
||||
"is not supported by graph_accelerometer.py script. Please use "
|
||||
"calibrate_shaper.py script to process it instead." % (logname,))
|
||||
# Parse power spectral density data
|
||||
data = np.loadtxt(logname, skiprows=1, comments='#', delimiter=',')
|
||||
calibration_data = shaper_calibrate.CalibrationData(
|
||||
freq_bins=data[:,0], psd_sum=data[:,4],
|
||||
psd_x=data[:,1], psd_y=data[:,2], psd_z=data[:,3])
|
||||
calibration_data.set_numpy(np)
|
||||
return calibration_data
|
||||
|
||||
######################################################################
|
||||
# Raw accelerometer graphing
|
||||
######################################################################
|
||||
|
||||
def plot_accel(data, logname):
|
||||
first_time = data[0, 0]
|
||||
times = data[:,0] - first_time
|
||||
def plot_accel(datas, lognames):
|
||||
fig, axes = matplotlib.pyplot.subplots(nrows=3, sharex=True)
|
||||
axes[0].set_title("\n".join(wrap("Accelerometer data (%s)" % (logname,),
|
||||
MAX_TITLE_LENGTH)))
|
||||
axes[0].set_title("\n".join(wrap(
|
||||
"Accelerometer data (%s)" % (', '.join(lognames)), MAX_TITLE_LENGTH)))
|
||||
axis_names = ['x', 'y', 'z']
|
||||
for data, logname in zip(datas, lognames):
|
||||
if isinstance(data, shaper_calibrate.CalibrationData):
|
||||
raise error("Cannot plot raw accelerometer data using the processed"
|
||||
" resonances, raw_data input is required")
|
||||
first_time = data[0, 0]
|
||||
times = data[:,0] - first_time
|
||||
for i in range(len(axis_names)):
|
||||
avg = data[:,i+1].mean()
|
||||
adata = data[:,i+1] - data[:,i+1].mean()
|
||||
ax = axes[i]
|
||||
label = '\n'.join(wrap(logname, 60)) + ' (%+.3f mm/s^2)' % (-avg,)
|
||||
ax.plot(times, adata, alpha=0.8, label=label)
|
||||
axes[-1].set_xlabel('Time (s)')
|
||||
fontP = matplotlib.font_manager.FontProperties()
|
||||
fontP.set_size('x-small')
|
||||
for i in range(len(axis_names)):
|
||||
avg = data[:,i+1].mean()
|
||||
adata = data[:,i+1] - data[:,i+1].mean()
|
||||
ax = axes[i]
|
||||
ax.plot(times, adata, alpha=0.8)
|
||||
ax.grid(True)
|
||||
ax.set_ylabel('%s accel (%+.3f)\n(mm/s^2)' % (axis_names[i], -avg))
|
||||
axes[-1].set_xlabel('Time (%+.3f)\n(s)' % (-first_time,))
|
||||
ax.legend(loc='best', prop=fontP)
|
||||
ax.set_ylabel('%s accel' % (axis_names[i],))
|
||||
fig.tight_layout()
|
||||
return fig
|
||||
|
||||
@@ -56,10 +71,15 @@ def plot_accel(data, logname):
|
||||
|
||||
# Calculate estimated "power spectral density"
|
||||
def calc_freq_response(data, max_freq):
|
||||
if isinstance(data, shaper_calibrate.CalibrationData):
|
||||
return data
|
||||
helper = shaper_calibrate.ShaperCalibrate(printer=None)
|
||||
return helper.process_accelerometer_data(data)
|
||||
|
||||
def calc_specgram(data, axis):
|
||||
if isinstance(data, shaper_calibrate.CalibrationData):
|
||||
raise error("Cannot calculate the spectrogram using the processed"
|
||||
" resonances, raw_data input is required")
|
||||
N = data.shape[0]
|
||||
Fs = N / (data[-1,0] - data[0,0])
|
||||
# Round up to a power of 2 for faster FFT
|
||||
@@ -235,9 +255,7 @@ def main():
|
||||
|
||||
# Draw graph
|
||||
if options.raw:
|
||||
if len(args) > 1:
|
||||
opts.error("Only 1 input is supported in raw mode")
|
||||
fig = plot_accel(datas[0], args[0])
|
||||
fig = plot_accel(datas, args)
|
||||
elif options.specgram:
|
||||
if len(args) > 1:
|
||||
opts.error("Only 1 input is supported in specgram mode")
|
||||
|
||||
@@ -181,14 +181,48 @@ def plot_system(data):
|
||||
ax1.grid(True)
|
||||
return fig
|
||||
|
||||
def plot_frequency(data, mcu):
|
||||
def plot_mcu_frequencies(data):
|
||||
all_keys = {}
|
||||
for d in data:
|
||||
all_keys.update(d)
|
||||
one_mcu = mcu is not None
|
||||
graph_keys = { key: ([], []) for key in all_keys
|
||||
if (key in ("freq", "adj") or (not one_mcu and (
|
||||
key.endswith(":freq") or key.endswith(":adj")))) }
|
||||
if (key in ("freq", "adj")
|
||||
or (key.endswith(":freq") or key.endswith(":adj"))) }
|
||||
for d in data:
|
||||
st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
|
||||
for key, (times, values) in graph_keys.items():
|
||||
val = d.get(key)
|
||||
if val not in (None, '0', '1'):
|
||||
times.append(st)
|
||||
values.append(float(val))
|
||||
est_mhz = { key: round((sum(values)/len(values)) / 1000000.)
|
||||
for key, (times, values) in graph_keys.items() }
|
||||
|
||||
# Build plot
|
||||
fig, ax1 = matplotlib.pyplot.subplots()
|
||||
ax1.set_title("MCU frequencies")
|
||||
ax1.set_xlabel('Time')
|
||||
ax1.set_ylabel('Microsecond deviation')
|
||||
for key in sorted(graph_keys):
|
||||
times, values = graph_keys[key]
|
||||
mhz = est_mhz[key]
|
||||
label = "%s(%dMhz)" % (key, mhz)
|
||||
hz = mhz * 1000000.
|
||||
ax1.plot_date(times, [(v - hz)/mhz for v in values], '.', label=label)
|
||||
fontP = matplotlib.font_manager.FontProperties()
|
||||
fontP.set_size('x-small')
|
||||
ax1.legend(loc='best', prop=fontP)
|
||||
ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
|
||||
ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%d'))
|
||||
ax1.grid(True)
|
||||
return fig
|
||||
|
||||
def plot_mcu_frequency(data, mcu):
|
||||
all_keys = {}
|
||||
for d in data:
|
||||
all_keys.update(d)
|
||||
graph_keys = { key: ([], []) for key in all_keys
|
||||
if key in ("freq", "adj") }
|
||||
for d in data:
|
||||
st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
|
||||
for key, (times, values) in graph_keys.items():
|
||||
@@ -199,10 +233,7 @@ def plot_frequency(data, mcu):
|
||||
|
||||
# Build plot
|
||||
fig, ax1 = matplotlib.pyplot.subplots()
|
||||
if one_mcu:
|
||||
ax1.set_title("MCU '%s' frequency" % (mcu,))
|
||||
else:
|
||||
ax1.set_title("MCU frequency")
|
||||
ax1.set_title("MCU '%s' frequency" % (mcu,))
|
||||
ax1.set_xlabel('Time')
|
||||
ax1.set_ylabel('Frequency')
|
||||
for key in sorted(graph_keys):
|
||||
@@ -286,7 +317,10 @@ def main():
|
||||
if options.heater is not None:
|
||||
fig = plot_temperature(data, options.heater)
|
||||
elif options.frequency:
|
||||
fig = plot_frequency(data, options.mcu)
|
||||
if options.mcu is not None:
|
||||
fig = plot_mcu_frequency(data, options.mcu)
|
||||
else:
|
||||
fig = plot_mcu_frequencies(data)
|
||||
elif options.system:
|
||||
fig = plot_system(data)
|
||||
else:
|
||||
|
||||
@@ -20,7 +20,7 @@ install_packages()
|
||||
PKGLIST="${PKGLIST} avrdude gcc-avr binutils-avr avr-libc"
|
||||
# ARM chip installation and building
|
||||
PKGLIST="${PKGLIST} stm32flash libnewlib-arm-none-eabi"
|
||||
PKGLIST="${PKGLIST} gcc-arm-none-eabi binutils-arm-none-eabi libusb-1.0"
|
||||
PKGLIST="${PKGLIST} gcc-arm-none-eabi binutils-arm-none-eabi libusb-1.0 pkg-config"
|
||||
|
||||
# Update system package info
|
||||
report_status "Running apt-get update..."
|
||||
|
||||
102
scripts/install-ubuntu-22.04.sh
Normal file
102
scripts/install-ubuntu-22.04.sh
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# This script installs Klipper on an Ubuntu 22.04 ("Jammy") machine
|
||||
|
||||
PYTHONDIR="${HOME}/klippy-env"
|
||||
SYSTEMDDIR="/etc/systemd/system"
|
||||
KLIPPER_USER=$USER
|
||||
KLIPPER_GROUP=$KLIPPER_USER
|
||||
|
||||
# Step 1: Install system packages
|
||||
install_packages()
|
||||
{
|
||||
# Packages for python cffi
|
||||
PKGLIST="virtualenv python3-dev libffi-dev build-essential"
|
||||
# kconfig requirements
|
||||
PKGLIST="${PKGLIST} libncurses-dev"
|
||||
# hub-ctrl
|
||||
PKGLIST="${PKGLIST} libusb-dev"
|
||||
# AVR chip installation and building
|
||||
PKGLIST="${PKGLIST} avrdude gcc-avr binutils-avr avr-libc"
|
||||
# ARM chip installation and building
|
||||
PKGLIST="${PKGLIST} stm32flash dfu-util libnewlib-arm-none-eabi"
|
||||
PKGLIST="${PKGLIST} gcc-arm-none-eabi binutils-arm-none-eabi libusb-1.0"
|
||||
|
||||
# Update system package info
|
||||
report_status "Running apt-get update..."
|
||||
sudo apt-get update
|
||||
|
||||
# Install desired packages
|
||||
report_status "Installing packages..."
|
||||
sudo apt-get install --yes ${PKGLIST}
|
||||
}
|
||||
|
||||
# Step 2: Create python virtual environment
|
||||
create_virtualenv()
|
||||
{
|
||||
report_status "Updating python virtual environment..."
|
||||
|
||||
# Create virtualenv if it doesn't already exist
|
||||
[ ! -d ${PYTHONDIR} ] && virtualenv -p python3 ${PYTHONDIR}
|
||||
|
||||
# Install/update dependencies
|
||||
${PYTHONDIR}/bin/pip install -r ${SRCDIR}/scripts/klippy-requirements.txt
|
||||
}
|
||||
|
||||
# Step 3: Install startup script
|
||||
install_script()
|
||||
{
|
||||
# Create systemd service file
|
||||
KLIPPER_LOG=/tmp/klippy.log
|
||||
report_status "Installing system start script..."
|
||||
sudo /bin/sh -c "cat > $SYSTEMDDIR/klipper.service" << EOF
|
||||
#Systemd service file for klipper
|
||||
[Unit]
|
||||
Description=Starts klipper on startup
|
||||
After=network.target
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$KLIPPER_USER
|
||||
RemainAfterExit=yes
|
||||
ExecStart=${PYTHONDIR}/bin/python ${SRCDIR}/klippy/klippy.py ${HOME}/printer.cfg -l ${KLIPPER_LOG}
|
||||
EOF
|
||||
# Use systemctl to enable the klipper systemd service script
|
||||
sudo systemctl enable klipper.service
|
||||
}
|
||||
|
||||
# Step 4: Start host software
|
||||
start_software()
|
||||
{
|
||||
report_status "Launching Klipper host software..."
|
||||
sudo systemctl start klipper
|
||||
}
|
||||
|
||||
# Helper functions
|
||||
report_status()
|
||||
{
|
||||
echo -e "\n\n###### $1"
|
||||
}
|
||||
|
||||
verify_ready()
|
||||
{
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "This script must not run as root"
|
||||
exit -1
|
||||
fi
|
||||
}
|
||||
|
||||
# Force script to exit if an error occurs
|
||||
set -e
|
||||
|
||||
# Find SRCDIR from the pathname of this script
|
||||
SRCDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )"
|
||||
|
||||
# Run installation steps defined above
|
||||
verify_ready
|
||||
install_packages
|
||||
create_virtualenv
|
||||
install_script
|
||||
start_software
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/bin/sh
|
||||
# System startup script to start the MCU Linux firmware
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: klipper_mcu
|
||||
# Required-Start: $local_fs
|
||||
# Required-Stop:
|
||||
# Default-Start: 3 4 5
|
||||
# Default-Stop: 0 1 2 6
|
||||
# Short-Description: Klipper_MCU daemon
|
||||
# Description: Starts the MCU for Klipper.
|
||||
### END INIT INFO
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
DESC="klipper_mcu startup"
|
||||
NAME="klipper_mcu"
|
||||
KLIPPER_HOST_MCU=/usr/local/bin/klipper_mcu
|
||||
KLIPPER_HOST_ARGS="-r"
|
||||
PIDFILE=/var/run/klipper_mcu.pid
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
mcu_host_stop()
|
||||
{
|
||||
# Shutdown existing Klipper instance (if applicable). The goal is to
|
||||
# put the GPIO pins in a safe state.
|
||||
if [ -c /tmp/klipper_host_mcu ]; then
|
||||
log_daemon_msg "Attempting to shutdown host mcu..."
|
||||
set -e
|
||||
( echo "FORCE_SHUTDOWN" > /tmp/klipper_host_mcu ) 2> /dev/null || ( log_action_msg "Firmware busy! Please shutdown Klipper and then retry." && exit 1 )
|
||||
sleep 1
|
||||
( echo "FORCE_SHUTDOWN" > /tmp/klipper_host_mcu ) 2> /dev/null || ( log_action_msg "Firmware busy! Please shutdown Klipper and then retry." && exit 1 )
|
||||
sleep 1
|
||||
set +e
|
||||
fi
|
||||
|
||||
log_daemon_msg "Stopping klipper host mcu" $NAME
|
||||
killproc -p $PIDFILE $KLIPPER_HOST_MCU
|
||||
}
|
||||
|
||||
mcu_host_start()
|
||||
{
|
||||
[ -x $KLIPPER_HOST_MCU ] || return
|
||||
|
||||
if [ -c /tmp/klipper_host_mcu ]; then
|
||||
mcu_host_stop
|
||||
fi
|
||||
|
||||
log_daemon_msg "Starting klipper MCU" $NAME
|
||||
start-stop-daemon --start --quiet --exec $KLIPPER_HOST_MCU \
|
||||
--background --pidfile $PIDFILE --make-pidfile \
|
||||
-- $KLIPPER_HOST_ARGS
|
||||
log_end_msg $?
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
mcu_host_start
|
||||
;;
|
||||
stop)
|
||||
mcu_host_stop
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
reload|force-reload)
|
||||
log_daemon_msg "Reloading configuration not supported" $NAME
|
||||
log_end_msg 1
|
||||
;;
|
||||
status)
|
||||
status_of_proc -p $PIDFILE $KLIPPER_HOST_MCU $NAME && exit 0 || exit $?
|
||||
;;
|
||||
*) log_action_msg "Usage: /etc/init.d/klipper_mcu {start|stop|status|restart|reload|force-reload}"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
21
scripts/klipper-mcu.service
Normal file
21
scripts/klipper-mcu.service
Normal file
@@ -0,0 +1,21 @@
|
||||
#Systemd klipper linux mcu Service
|
||||
|
||||
[Unit]
|
||||
Description=Starts the MCU Linux firmware for klipper on startup
|
||||
Documentation=https://www.klipper3d.org/RPi_microcontroller.html
|
||||
Before=klipper.service
|
||||
ConditionFileIsExecutable=/usr/local/bin/klipper_mcu
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Environment=KLIPPER_HOST_MCU_SERIAL=/tmp/klipper_host_mcu
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/local/bin/klipper_mcu -r -I ${KLIPPER_HOST_MCU_SERIAL}
|
||||
ExecStop=sh -c 'echo "FORCE_SHUTDOWN" > ${KLIPPER_HOST_MCU_SERIAL}'
|
||||
ExecStop=sleep 1
|
||||
TimeoutStopSec=2
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
@@ -4,7 +4,7 @@
|
||||
# pip install -r klippy-requirements.txt
|
||||
cffi==1.14.6
|
||||
pyserial==3.4
|
||||
greenlet==1.1.2
|
||||
greenlet==2.0.2
|
||||
Jinja2==2.11.3
|
||||
python-can==3.3.4
|
||||
markupsafe==1.1.1
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Script to extract config and shutdown information file a klippy.log file
|
||||
#
|
||||
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import sys, re, collections, ast
|
||||
import sys, re, collections, ast, itertools
|
||||
|
||||
def format_comment(line_num, line):
|
||||
return "# %6d: %s" % (line_num, line)
|
||||
@@ -40,9 +40,10 @@ class GatherConfig:
|
||||
if comment is not None:
|
||||
self.comments.append(comment)
|
||||
def write_file(self):
|
||||
f = open(self.filename, 'wb')
|
||||
f.write('\n'.join(self.comments + self.config_lines).strip() + '\n')
|
||||
f.close()
|
||||
lines = itertools.chain(self.comments, self.config_lines)
|
||||
lines = ('%s\n' % l for l in lines)
|
||||
with open(self.filename, 'wt') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
######################################################################
|
||||
@@ -93,7 +94,7 @@ class TMCUartHelper:
|
||||
return
|
||||
# Convert data into a long integer for easy manipulation
|
||||
mval = pos = 0
|
||||
for d in bytearray(data):
|
||||
for d in data:
|
||||
mval |= d << pos
|
||||
pos += 8
|
||||
# Extract register value
|
||||
@@ -110,7 +111,7 @@ class TMCUartHelper:
|
||||
return
|
||||
# Convert data into a long integer for easy manipulation
|
||||
mval = pos = 0
|
||||
for d in bytearray(data):
|
||||
for d in data:
|
||||
mval |= d << pos
|
||||
pos += 8
|
||||
# Extract register value
|
||||
@@ -216,10 +217,10 @@ clock_r = re.compile(r"^clocksync state: mcu_freq=(?P<freq>[0-9]+) .*"
|
||||
+ r" (?P<sc>[0-9]+) (?P<f>[^ ]+)\)")
|
||||
repl_seq_r = re.compile(r": seq: 1" + shortseq_s)
|
||||
clock_s = r"(?P<clock>[0-9]+)"
|
||||
repl_clock_r = re.compile(r"clock=" + clock_s + r"(?: |$)")
|
||||
repl_clock_r = re.compile(r"clock=" + clock_s)
|
||||
repl_uart_r = re.compile(r"tmcuart_(?:response|send) oid=[0-9]+"
|
||||
+ r" (?:read|write)=(?P<msg>(?:'[^']*'"
|
||||
+ r'|"[^"]*"))(?: |$)')
|
||||
+ r" (?:read|write)=b?(?P<msg>(?:'[^']*'"
|
||||
+ r'|"[^"]*"))')
|
||||
|
||||
# MCU shutdown message parsing
|
||||
class MCUStream:
|
||||
@@ -240,12 +241,13 @@ class MCUStream:
|
||||
if seq is not None:
|
||||
line = repl_seq_r.sub(r"\g<0>(%d)" % (seq,), line)
|
||||
def clock_update(m):
|
||||
return m.group(0).rstrip() + "(%.6f) " % (
|
||||
return m.group(0).rstrip() + "(%.6f)" % (
|
||||
self.trans_clock(int(m.group('clock')), ts),)
|
||||
line = repl_clock_r.sub(clock_update, line)
|
||||
def uart_update(m):
|
||||
msg = TMCUartHelper().parse_msg(ast.literal_eval(m.group('msg')))
|
||||
return m.group(0).rstrip() + "%s " % (msg,)
|
||||
msg = ast.literal_eval('b' + m.group('msg'))
|
||||
msg = TMCUartHelper().parse_msg(msg)
|
||||
return m.group(0).rstrip() + msg
|
||||
line = repl_uart_r.sub(uart_update, line)
|
||||
if self.name != 'mcu':
|
||||
line = "mcu '%s': %s" % (self.name, line)
|
||||
@@ -382,10 +384,9 @@ class GCodeStream:
|
||||
def get_lines(self):
|
||||
# Produce output gcode stream
|
||||
if self.gcode_stream:
|
||||
data = [ast.literal_eval(gc) for gc in self.gcode_commands]
|
||||
f = open(self.gcode_filename, 'wb')
|
||||
f.write(self.gcode_state + ''.join(data))
|
||||
f.close()
|
||||
data = (ast.literal_eval(gc) for gc in self.gcode_commands)
|
||||
with open(self.gcode_filename, 'wt') as f:
|
||||
f.write(self.gcode_state + ''.join(data))
|
||||
return self.gcode_stream
|
||||
|
||||
api_cmd_r = re.compile(r"^Received " + time_s + r": \{.*\}$")
|
||||
@@ -500,8 +501,13 @@ class StatsStream:
|
||||
if info[0] is not None and info[0] >= min_stream_ts - 5.:
|
||||
del self.stats_stream[:i]
|
||||
break
|
||||
# Find the first stats timestamp
|
||||
last_ts = min_stream_ts
|
||||
for ts, line_num, line in self.stats_stream:
|
||||
if ts is not None:
|
||||
last_ts = ts
|
||||
break
|
||||
# Improve accuracy of stats timestamps
|
||||
last_ts = self.stats_stream[0][0]
|
||||
for i, (ts, line_num, line) in enumerate(self.stats_stream):
|
||||
if ts is not None:
|
||||
last_ts = self.check_stats_seq(ts, line)
|
||||
@@ -560,10 +566,10 @@ class GatherShutdown:
|
||||
# Produce output sorted by timestamp
|
||||
out = [i for s in streams for i in s]
|
||||
out.sort()
|
||||
out = [i[2] for i in out]
|
||||
f = open(self.filename, 'wb')
|
||||
f.write('\n'.join(self.comments + out))
|
||||
f.close()
|
||||
lines = itertools.chain(self.comments, (i[2] for i in out))
|
||||
lines = ('%s\n' % l for l in lines)
|
||||
with open(self.filename, 'wt') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
######################################################################
|
||||
@@ -577,29 +583,31 @@ def main():
|
||||
handler = None
|
||||
recent_lines = collections.deque([], 200)
|
||||
# Parse log file
|
||||
f = open(logname, 'rb')
|
||||
for line_num, line in enumerate(f):
|
||||
line = line.rstrip()
|
||||
line_num += 1
|
||||
recent_lines.append((line_num, line))
|
||||
if handler is not None:
|
||||
ret = handler.add_line(line_num, line)
|
||||
if ret:
|
||||
continue
|
||||
recent_lines.clear()
|
||||
handler = None
|
||||
if line.startswith('Git version'):
|
||||
last_git = format_comment(line_num, line)
|
||||
elif line.startswith('Start printer at'):
|
||||
last_start = format_comment(line_num, line)
|
||||
elif line == '===== Config file =====':
|
||||
handler = GatherConfig(configs, line_num, recent_lines, logname)
|
||||
handler.add_comment(last_git)
|
||||
handler.add_comment(last_start)
|
||||
elif 'shutdown: ' in line or line.startswith('Dumping '):
|
||||
handler = GatherShutdown(configs, line_num, recent_lines, logname)
|
||||
handler.add_comment(last_git)
|
||||
handler.add_comment(last_start)
|
||||
with open(logname, 'rt') as f:
|
||||
for line_num, line in enumerate(f):
|
||||
line = line.rstrip()
|
||||
line_num += 1
|
||||
recent_lines.append((line_num, line))
|
||||
if handler is not None:
|
||||
ret = handler.add_line(line_num, line)
|
||||
if ret:
|
||||
continue
|
||||
recent_lines.clear()
|
||||
handler = None
|
||||
if line.startswith('Git version'):
|
||||
last_git = format_comment(line_num, line)
|
||||
elif line.startswith('Start printer at'):
|
||||
last_start = format_comment(line_num, line)
|
||||
elif line == '===== Config file =====':
|
||||
handler = GatherConfig(configs, line_num,
|
||||
recent_lines, logname)
|
||||
handler.add_comment(last_git)
|
||||
handler.add_comment(last_start)
|
||||
elif 'shutdown: ' in line or line.startswith('Dumping '):
|
||||
handler = GatherShutdown(configs, line_num,
|
||||
recent_lines, logname)
|
||||
handler.add_comment(last_git)
|
||||
handler.add_comment(last_start)
|
||||
if handler is not None:
|
||||
handler.finalize()
|
||||
# Write found config files
|
||||
|
||||
@@ -23,7 +23,7 @@ def main(argv):
|
||||
help='Name of distro this package is intended for'
|
||||
)
|
||||
args = p.parse_args()
|
||||
print(util.get_git_version(from_file=False),
|
||||
print(util.get_git_version(from_file=False)["version"],
|
||||
args.distroname.replace(' ', ''), sep='-')
|
||||
|
||||
|
||||
|
||||
@@ -105,6 +105,97 @@ class GenIntegral:
|
||||
return data
|
||||
AHandlers["integral"] = GenIntegral
|
||||
|
||||
# Calculate a pointwise 2-norm of several datasets (e.g. compute velocity or
|
||||
# accel from its x, y,... components)
|
||||
class GenNorm2:
|
||||
ParametersMin = 2
|
||||
ParametersMax = 3
|
||||
DataSets = [
|
||||
('norm2(<dataset1>,<dataset2>)',
|
||||
'pointwise 2-norm of dataset1 and dataset2'),
|
||||
('norm2(<dataset1>,<dataset2>,<dataset3>)',
|
||||
'pointwise 2-norm of 3 datasets'),
|
||||
]
|
||||
def __init__(self, amanager, name_parts):
|
||||
self.amanager = amanager
|
||||
self.datasets = []
|
||||
self.datasets.append(name_parts[1])
|
||||
self.datasets.append(name_parts[2])
|
||||
if len(name_parts) == 4:
|
||||
self.datasets.append(name_parts[3])
|
||||
for dataset in self.datasets:
|
||||
amanager.setup_dataset(dataset)
|
||||
def get_label(self):
|
||||
label = self.amanager.get_label(self.datasets[0])
|
||||
units = label['units']
|
||||
datas = ['position', 'velocity', 'acceleration']
|
||||
data_name = ''
|
||||
for d in datas:
|
||||
if d in label['label']:
|
||||
data_name = d
|
||||
break
|
||||
lname = ''
|
||||
for d in self.datasets:
|
||||
l = self.amanager.get_label(d)['label']
|
||||
for r in datas:
|
||||
l = l.replace(r, '').strip()
|
||||
if lname:
|
||||
lname += '+'
|
||||
lname += l
|
||||
lname += ' ' + data_name + ' norm2'
|
||||
return {'label': lname, 'units': units}
|
||||
def generate_data(self):
|
||||
seg_time = self.amanager.get_segment_time()
|
||||
data = []
|
||||
for dataset in self.datasets:
|
||||
data.append(self.amanager.get_datasets()[dataset])
|
||||
res = [0.] * len(data[0])
|
||||
for i in range(len(data[0])):
|
||||
norm2 = 0.
|
||||
for dataset in data:
|
||||
norm2 += dataset[i] * dataset[i]
|
||||
res[i] = math.sqrt(norm2)
|
||||
return res
|
||||
AHandlers["norm2"] = GenNorm2
|
||||
|
||||
class GenSmoothed:
|
||||
ParametersMin = 1
|
||||
ParametersMax = 2
|
||||
DataSets = [
|
||||
('smooth(<dataset>)', 'Generate moving weighted average of a dataset'),
|
||||
('smooth(<dataset>,<smooth_time>)',
|
||||
'Generate moving weighted average of a dataset with a given'
|
||||
' smoothing time that defines the window size'),
|
||||
]
|
||||
def __init__(self, amanager, name_parts):
|
||||
self.amanager = amanager
|
||||
self.source = name_parts[1]
|
||||
amanager.setup_dataset(self.source)
|
||||
self.smooth_time = 0.01
|
||||
if len(name_parts) > 2:
|
||||
self.smooth_time = float(name_parts[2])
|
||||
def get_label(self):
|
||||
label = self.amanager.get_label(self.source)
|
||||
return {'label': 'Smoothed ' + label['label'], 'units': label['units']}
|
||||
def generate_data(self):
|
||||
seg_time = self.amanager.get_segment_time()
|
||||
src = self.amanager.get_datasets()[self.source]
|
||||
n = len(src)
|
||||
data = [0.] * n
|
||||
hst = 0.5 * self.smooth_time
|
||||
seg_half_len = round(hst / seg_time)
|
||||
inv_norm = 1. / sum([min(k + 1, seg_half_len + seg_half_len - k)
|
||||
for k in range(2 * seg_half_len)])
|
||||
for i in range(n):
|
||||
j = max(0, i - seg_half_len)
|
||||
je = min(n, i + seg_half_len)
|
||||
avg_val = 0.
|
||||
for k, v in enumerate(src[j:je]):
|
||||
avg_val += v * min(k + 1, seg_half_len + seg_half_len - k)
|
||||
data[i] = avg_val * inv_norm
|
||||
return data
|
||||
AHandlers["smooth"] = GenSmoothed
|
||||
|
||||
# Calculate a kinematic stepper position from the toolhead requested position
|
||||
class GenKinematicPosition:
|
||||
ParametersMin = ParametersMax = 1
|
||||
|
||||
@@ -217,6 +217,8 @@ class HandleStepQ:
|
||||
inv_freq = tdiff / cdiff
|
||||
step_dist = jmsg['step_distance']
|
||||
step_pos = jmsg['start_position']
|
||||
if not step_data[0][0]:
|
||||
step_data[0] = (0., step_pos, step_pos)
|
||||
for interval, raw_count, add in jmsg['data']:
|
||||
qs_dist = step_dist
|
||||
count = raw_count
|
||||
@@ -316,6 +318,8 @@ class HandleStepPhase:
|
||||
if cdiff:
|
||||
inv_freq = tdiff / cdiff
|
||||
step_pos = jmsg['start_mcu_position']
|
||||
if not step_data[0][0]:
|
||||
step_data[0] = (0., step_pos)
|
||||
for interval, raw_count, add in jmsg['data']:
|
||||
qs_dist = 1
|
||||
count = raw_count
|
||||
|
||||
125
scripts/parsecandump.py
Normal file
125
scripts/parsecandump.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/python3
|
||||
# Check for out of order timestamps in the output of candump
|
||||
#
|
||||
# Copyright (C) 2023 Kevin O'Connor <kevin@koconnor.net>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import sys, os, optparse
|
||||
|
||||
def import_msgproto():
|
||||
global msgproto
|
||||
# Load msgproto.py module
|
||||
kdir = os.path.join(os.path.dirname(__file__), '..', 'klippy')
|
||||
sys.path.append(kdir)
|
||||
import msgproto
|
||||
|
||||
def read_dictionary(filename):
|
||||
dfile = open(filename, 'rb')
|
||||
dictionary = dfile.read()
|
||||
dfile.close()
|
||||
return dictionary
|
||||
|
||||
def report(msg, line_info, name="", is_err=False):
|
||||
line_number, line_time = line_info
|
||||
warn = ""
|
||||
if is_err:
|
||||
warn = " WARN"
|
||||
sys.stdout.write("%04d:%010.6f:%s%s %s\n"
|
||||
% (line_number, line_time, name, warn, msg))
|
||||
|
||||
class canscan:
|
||||
def __init__(self, name, mp):
|
||||
self.name = name
|
||||
self.mp = mp
|
||||
self.data = bytearray()
|
||||
self.need_scan = False
|
||||
def handle_data(self, line_info, line, newdata):
|
||||
data = self.data
|
||||
data += bytearray(newdata)
|
||||
while 1:
|
||||
if self.need_scan:
|
||||
drop = len(data)
|
||||
syncpos = data.find(msgproto.MESSAGE_SYNC)
|
||||
if syncpos >= 0:
|
||||
drop = syncpos + 1
|
||||
self.need_scan = False
|
||||
disc = ["%02X" % (d,) for d in data[:drop]]
|
||||
report("Discarding %d (%s)" % (drop, " ".join(disc)),
|
||||
line_info, self.name, is_err=True)
|
||||
data[:drop] = []
|
||||
if not data:
|
||||
break
|
||||
l = self.mp.check_packet(data)
|
||||
if l == 0:
|
||||
break
|
||||
if l < 0:
|
||||
report("Invalid data: %s" % (line.strip(),),
|
||||
line_info, self.name, is_err=True)
|
||||
self.need_scan = True
|
||||
continue
|
||||
if l == 5:
|
||||
report("Ack %02x" % (data[1],), line_info, self.name)
|
||||
else:
|
||||
msgs = self.mp.dump(data[:l])
|
||||
report("%d: %s" % (l, ', '.join(msgs)), line_info, self.name)
|
||||
data[:l] = []
|
||||
|
||||
def read_candump(canfile, canid, dictionary):
|
||||
mp = msgproto.MessageParser()
|
||||
mp.process_identify(dictionary, decompress=False)
|
||||
rxid = "%03X" % (canid | 1,)
|
||||
txid = "%03X" % (canid & ~1,)
|
||||
handlers = {rxid: canscan("RX", mp), txid: canscan("TX", mp)}
|
||||
|
||||
last_time = -1.
|
||||
line_number = 0
|
||||
must_scan = False
|
||||
data = bytearray()
|
||||
for line in canfile:
|
||||
line_number += 1
|
||||
parts = line.split()
|
||||
if len(parts) < 7:
|
||||
if parts:
|
||||
report("Ignoring line: %s" % (line.strip(),),
|
||||
(line_number, 0.), is_err=True)
|
||||
continue
|
||||
p_ts = parts[0]
|
||||
p_canid = parts[5]
|
||||
p_len = parts[6]
|
||||
p_data = parts[7:]
|
||||
if (not p_ts.startswith('(') or not p_ts.endswith(')')
|
||||
or not p_len.startswith('[') or not p_len.endswith(']')):
|
||||
report("Ignoring line: %s" % (line.strip(),),
|
||||
(line_number, 0.), is_err=True)
|
||||
continue
|
||||
new_time = float(p_ts[1:-1])
|
||||
line_info = (line_number, new_time)
|
||||
if new_time < last_time:
|
||||
report("Backwards time %.6f vs %.6f: %s"
|
||||
% (new_time, last_time, line.strip()),
|
||||
line_info, is_err=True)
|
||||
last_time = new_time
|
||||
|
||||
hdlr = handlers.get(p_canid)
|
||||
if hdlr is not None:
|
||||
newdata = [int(i, 16) for i in p_data]
|
||||
hdlr.handle_data(line_info, line, newdata)
|
||||
|
||||
def main():
|
||||
usage = "%prog <candump.log> <canid> <mcu.dict>"
|
||||
opts = optparse.OptionParser(usage)
|
||||
options, args = opts.parse_args()
|
||||
if len(args) != 3:
|
||||
opts.error("Incorrect number of arguments")
|
||||
canfilename, canid, dictfilename = args
|
||||
canid = int(canid, 16)
|
||||
|
||||
import_msgproto()
|
||||
dictionary = read_dictionary(dictfilename)
|
||||
|
||||
canfile = open(canfilename, "r")
|
||||
read_candump(canfile, canid, dictionary)
|
||||
canfile.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -44,6 +44,27 @@ BOARD_DEFS = {
|
||||
"firmware_path": "Robin_e3.bin",
|
||||
"current_firmware_path": "Robin_e3.cur"
|
||||
},
|
||||
'btt-octopus-f407-v1': {
|
||||
'mcu': "stm32f407xx",
|
||||
'spi_bus': "swspi",
|
||||
'spi_pins': "PC8,PD2,PC12",
|
||||
'cs_pin': "PC11",
|
||||
'skip_verify': True
|
||||
},
|
||||
'btt-octopus-f429-v1': {
|
||||
'mcu': "stm32f429xx",
|
||||
'spi_bus': "swspi",
|
||||
'spi_pins': "PC8,PD2,PC12",
|
||||
'cs_pin': "PC11",
|
||||
'skip_verify': True
|
||||
},
|
||||
'btt-octopus-f446-v1': {
|
||||
'mcu': "stm32f446xx",
|
||||
'spi_bus': "swspi",
|
||||
'spi_pins': "PC8,PD2,PC12",
|
||||
'cs_pin': "PC11",
|
||||
'skip_verify': True
|
||||
},
|
||||
'btt-skr-pro': {
|
||||
'mcu': "stm32f407xx",
|
||||
'spi_bus': "swspi",
|
||||
@@ -66,6 +87,27 @@ BOARD_DEFS = {
|
||||
'spi_bus': 'spi3a',
|
||||
'cs_pin': 'PA15'
|
||||
},
|
||||
'btt-skr-3-h743': {
|
||||
'mcu': 'stm32h743xx',
|
||||
'spi_bus': 'swspi',
|
||||
'spi_pins': "PC8,PD2,PC12",
|
||||
'cs_pin': 'PC11',
|
||||
'skip_verify': True
|
||||
},
|
||||
'btt-skr-3-h723': {
|
||||
'mcu': 'stm32h723xx',
|
||||
'spi_bus': 'swspi',
|
||||
'spi_pins': "PC8,PD2,PC12",
|
||||
'cs_pin': 'PC11',
|
||||
'skip_verify': True
|
||||
},
|
||||
'creality-v4.2.2': {
|
||||
'mcu': "stm32f103xe",
|
||||
'spi_bus': "swspi",
|
||||
'spi_pins': "PC8,PD2,PC12",
|
||||
'cs_pin': "PC11",
|
||||
'skip_verify': True
|
||||
},
|
||||
'monster8': {
|
||||
'mcu': "stm32f407xx",
|
||||
'spi_bus': "spi3a",
|
||||
@@ -75,6 +117,17 @@ BOARD_DEFS = {
|
||||
'mcu': "stm32f405xx",
|
||||
'spi_bus': "spi1",
|
||||
"cs_pin": "PA4"
|
||||
},
|
||||
'fysetc-cheetah': {
|
||||
'mcu': "stm32f401xc",
|
||||
'spi_bus': "spi1",
|
||||
"cs_pin": "PA4",
|
||||
"current_firmware_path": "OLD.BIN"
|
||||
},
|
||||
'btt-skrat': {
|
||||
'mcu': "stm32g0b1xx",
|
||||
'spi_bus': "spi1",
|
||||
"cs_pin": "PB8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,16 +155,28 @@ BOARD_ALIASES = {
|
||||
'btt-skr-mini-mz': BOARD_DEFS['btt-skr-mini'],
|
||||
'btt-skr-e3-dip': BOARD_DEFS['btt-skr-mini'],
|
||||
'btt002-v1': BOARD_DEFS['btt-skr-mini'],
|
||||
'creality-v4.2.7': BOARD_DEFS['btt-skr-mini'],
|
||||
'creality-v4.2.7': BOARD_DEFS['creality-v4.2.2'],
|
||||
'btt-skr-2-f407': BOARD_DEFS['btt-octopus-f407-v1'],
|
||||
'btt-skr-2-f429': BOARD_DEFS['btt-octopus-f429-v1'],
|
||||
'btt-octopus-f407-v1.0': BOARD_DEFS['btt-octopus-f407-v1'],
|
||||
'btt-octopus-f407-v1.1': BOARD_DEFS['btt-octopus-f407-v1'],
|
||||
'btt-octopus-f429-v1.0': BOARD_DEFS['btt-octopus-f429-v1'],
|
||||
'btt-octopus-f429-v1.1': BOARD_DEFS['btt-octopus-f429-v1'],
|
||||
'btt-octopus-f446-v1.0': BOARD_DEFS['btt-octopus-f446-v1'],
|
||||
'btt-octopus-f446-v1.1': BOARD_DEFS['btt-octopus-f446-v1'],
|
||||
'btt-octopus-pro-f429-v1.0': BOARD_DEFS['btt-octopus-f429-v1'],
|
||||
'btt-octopus-pro-f446-v1.0': BOARD_DEFS['btt-octopus-f446-v1'],
|
||||
'btt-octopus-pro-h723-v1.1': BOARD_DEFS['btt-skr-3-h723'],
|
||||
'btt-skr-pro-v1.1': BOARD_DEFS['btt-skr-pro'],
|
||||
'btt-skr-pro-v1.2': BOARD_DEFS['btt-skr-pro'],
|
||||
'btt-gtr-v1': BOARD_DEFS['btt-gtr'],
|
||||
'mks-robin-e3d': BOARD_DEFS['mks-robin-e3'],
|
||||
'fysetc-cheetah-v2': BOARD_DEFS['fysetc-cheetah'],
|
||||
'fysetc-spider-v1': BOARD_DEFS['fysetc-spider'],
|
||||
'fysetc-s6-v1.2': BOARD_DEFS['fysetc-spider'],
|
||||
'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'],
|
||||
'monster8': BOARD_DEFS['monster8'],
|
||||
'robin_v3': BOARD_DEFS['monster8']
|
||||
'robin_v3': BOARD_DEFS['monster8'],
|
||||
'btt-skrat-v1.0': BOARD_DEFS['btt-skrat']
|
||||
}
|
||||
|
||||
def list_boards():
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python2
|
||||
# Module supporting uploads Klipper firmware to an SD Card via SPI
|
||||
# Module supporting uploads Klipper firmware to an SD Card via SPI and SDIO
|
||||
#
|
||||
# Copyright (C) 2021 Eric Callahan <arksine.code@gmail.com>
|
||||
# Copyright (C) 2022 H. Gregor Molter <gregor.molter@secretlab.de>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import sys
|
||||
@@ -36,7 +37,7 @@ def output(msg):
|
||||
sys.stdout.write("%s" % (msg,))
|
||||
sys.stdout.flush()
|
||||
|
||||
def calc_crc7(data):
|
||||
def calc_crc7(data, with_padding=True):
|
||||
# G(x) = x^7 + x^3 + 1
|
||||
# Shift left as we are only calculating a 7 bit CRC
|
||||
poly = 0b10001001 << 1
|
||||
@@ -47,6 +48,8 @@ def calc_crc7(data):
|
||||
crc = (crc << 1) ^ poly if crc & 0x80 else crc << 1
|
||||
# The sdcard protocol likes the crc left justfied with a
|
||||
# padded bit
|
||||
if not with_padding:
|
||||
return crc
|
||||
return crc | 1
|
||||
|
||||
def calc_crc16(data):
|
||||
@@ -89,11 +92,12 @@ def check_need_convert(board_name, config):
|
||||
|
||||
###########################################################
|
||||
#
|
||||
# SPI FLash Implementation
|
||||
# SPI / SDIO Flash Implementation
|
||||
#
|
||||
###########################################################
|
||||
|
||||
SPI_OID = 0
|
||||
SDIO_OID = 0
|
||||
SPI_MODE = 0
|
||||
SD_SPI_SPEED = 400000
|
||||
# MCU Command Constants
|
||||
@@ -114,6 +118,20 @@ SW_SPI_BUS_CMD = "spi_set_software_bus oid=%d " \
|
||||
SPI_SEND_CMD = "spi_send oid=%c data=%*s"
|
||||
SPI_XFER_CMD = "spi_transfer oid=%c data=%*s"
|
||||
SPI_XFER_RESPONSE = "spi_transfer_response oid=%c response=%*s"
|
||||
SDIO_CFG_CMD = "config_sdio oid=%d blocksize=%u"
|
||||
SDIO_BUS_CMD = "sdio_set_bus oid=%d sdio_bus=%s"
|
||||
SDIO_SEND_CMD = "sdio_send_command oid=%c cmd=%c argument=%u wait=%c"
|
||||
SDIO_SEND_CMD_RESPONSE = "sdio_send_command_response oid=%c error=%c " \
|
||||
"response=%*s"
|
||||
SDIO_READ_DATA="sdio_read_data oid=%c cmd=%c argument=%u"
|
||||
SDIO_READ_DATA_RESPONSE="sdio_read_data_response oid=%c error=%c read=%u"
|
||||
SDIO_WRITE_DATA="sdio_write_data oid=%c cmd=%c argument=%u"
|
||||
SDIO_WRITE_DATA_RESPONSE="sdio_write_data_response oid=%c error=%c write=%u"
|
||||
SDIO_READ_DATA_BUFFER="sdio_read_data_buffer oid=%c offset=%u len=%c"
|
||||
SDIO_READ_DATA_BUFFER_RESPONSE="sdio_read_data_buffer_response oid=%c data=%*s"
|
||||
SDIO_WRITE_DATA_BUFFER="sdio_write_data_buffer oid=%c offset=%u data=%*s"
|
||||
SDIO_SET_SPEED="sdio_set_speed oid=%c speed=%u"
|
||||
|
||||
FINALIZE_CFG_CMD = "finalize_config crc=%d"
|
||||
|
||||
class SPIFlashError(Exception):
|
||||
@@ -135,6 +153,40 @@ class SPIDirect:
|
||||
def spi_transfer(self, data):
|
||||
return self._spi_transfer_cmd.send([self.oid, data])
|
||||
|
||||
class SDIODirect:
|
||||
def __init__(self, ser):
|
||||
self.oid = SDIO_OID
|
||||
self._sdio_send_cmd = mcu.CommandQueryWrapper(
|
||||
ser, SDIO_SEND_CMD, SDIO_SEND_CMD_RESPONSE, self.oid)
|
||||
self._sdio_read_data = mcu.CommandQueryWrapper(
|
||||
ser, SDIO_READ_DATA, SDIO_READ_DATA_RESPONSE, self.oid)
|
||||
self._sdio_write_data = mcu.CommandQueryWrapper(
|
||||
ser, SDIO_WRITE_DATA, SDIO_WRITE_DATA_RESPONSE, self.oid)
|
||||
self._sdio_read_data_buffer = mcu.CommandQueryWrapper(
|
||||
ser, SDIO_READ_DATA_BUFFER, SDIO_READ_DATA_BUFFER_RESPONSE,
|
||||
self.oid)
|
||||
self._sdio_write_data_buffer = mcu.CommandWrapper(ser,
|
||||
SDIO_WRITE_DATA_BUFFER)
|
||||
self._sdio_set_speed = mcu.CommandWrapper(ser, SDIO_SET_SPEED)
|
||||
|
||||
def sdio_send_cmd(self, cmd, argument, wait):
|
||||
return self._sdio_send_cmd.send([self.oid, cmd, argument, wait])
|
||||
|
||||
def sdio_read_data(self, cmd, argument):
|
||||
return self._sdio_read_data.send([self.oid, cmd, argument])
|
||||
|
||||
def sdio_write_data(self, cmd, argument):
|
||||
return self._sdio_write_data.send([self.oid, cmd, argument])
|
||||
|
||||
def sdio_read_data_buffer(self, offset, length=32):
|
||||
return self._sdio_read_data_buffer.send([self.oid, offset, length])
|
||||
|
||||
def sdio_write_data_buffer(self, offset, data):
|
||||
return self._sdio_write_data_buffer.send([self.oid, offset, data])
|
||||
|
||||
def sdio_set_speed(self, speed):
|
||||
return self._sdio_set_speed.send([self.oid, speed])
|
||||
|
||||
|
||||
# FatFs Constants. Enums are implemented as lists. The item's index is its value
|
||||
DRESULT = ['RES_OK', 'RES_ERROR', 'RES_WRPRT', 'RES_NOTRDY', 'RES_PARERR']
|
||||
@@ -154,8 +206,11 @@ SECTOR_SIZE = 512
|
||||
|
||||
# FAT16/32 File System Support
|
||||
class FatFS:
|
||||
def __init__(self, ser):
|
||||
self.sdcard = SDCardSPI(ser)
|
||||
def __init__(self, ser, spi=True):
|
||||
if spi:
|
||||
self.sdcard = SDCardSPI(ser)
|
||||
else:
|
||||
self.sdcard = SDCardSDIO(ser)
|
||||
self.disk_status = STA_NO_INIT | STA_NO_DISK
|
||||
self.ffi_callbacks = []
|
||||
self.ffi_main, self.ffi_lib = fatfs_lib.get_fatfs_ffi()
|
||||
@@ -429,6 +484,10 @@ class SDCardFile:
|
||||
|
||||
SD_COMMANDS = {
|
||||
'GO_IDLE_STATE': 0,
|
||||
'ALL_SEND_CID': 2,
|
||||
'SET_REL_ADDR': 3,
|
||||
'SET_BUS_WIDTH': 6,
|
||||
'SEL_DESEL_CARD': 7,
|
||||
'SEND_IF_COND': 8,
|
||||
'SEND_CSD': 9,
|
||||
'SEND_CID': 10,
|
||||
@@ -785,6 +844,329 @@ class SDCardSPI:
|
||||
if err_msgs:
|
||||
raise OSError("\n".join(err_msgs))
|
||||
|
||||
class SDCardSDIO:
|
||||
def __init__(self, ser):
|
||||
self.sdio = SDIODirect(ser)
|
||||
self.rca = 0
|
||||
self.reactor = ser.get_reactor()
|
||||
self.enable_crc = True
|
||||
self.mutex = self.reactor.mutex()
|
||||
self.initialized = False
|
||||
self.sd_version = 0
|
||||
self.high_capacity = False
|
||||
self.write_protected = False
|
||||
self.total_sectors = 0
|
||||
self.card_info = collections.OrderedDict()
|
||||
|
||||
def init_sd(self):
|
||||
def check_for_ocr_errors(reg):
|
||||
# returns False if an error flag is set
|
||||
return ((reg[0]&0xFD) | (reg[1]&0xFF) |
|
||||
(reg[2]&0xE0) | (reg[3]&0x08)) == 0
|
||||
with self.mutex:
|
||||
if self.initialized:
|
||||
return
|
||||
# Send reset command (CMD0)
|
||||
if not self._send_command('GO_IDLE_STATE', 0):
|
||||
raise OSError(
|
||||
"flash_sdcard: failed to reset SD Card\n"
|
||||
"Note that older (Version 1.0) SD cards can not be\n"
|
||||
"hot swapped. Execute FIRMWARE_RESTART with the card\n"
|
||||
"inserted for successful initialization.")
|
||||
# Check Voltage Range (CMD8). Only Cards meeting the v2.0 spec
|
||||
# support this. V1.0 cards (and MMC) will return illegal command.
|
||||
check_pattern = 0b1010
|
||||
resp = self._send_command_with_response(
|
||||
'SEND_IF_COND', (1 << 8) | check_pattern)
|
||||
resp = resp.strip(b'\xFF')
|
||||
if len(resp) != 4:
|
||||
# CMD8 is illegal, this is a version 1.0 card
|
||||
self.sd_version = 1
|
||||
else:
|
||||
self.sd_version = 2
|
||||
if not (resp[-2] == 1 and resp[-1] == check_pattern):
|
||||
raise OSError("flash_sdcard: SD Card not running in a "
|
||||
"compatible voltage range")
|
||||
if self.sd_version == 2:
|
||||
|
||||
# Init card and come out of idle (ACMD41)
|
||||
# Version 2 Cards may init before checking the OCR
|
||||
# Allow vor LVDS card with 1.8v, too.
|
||||
resp = self._check_command(lambda x: x[0]>>7 == 1,
|
||||
'SD_SEND_OP_COND', 0xC1100000, is_app_cmd=True,
|
||||
ignoreCRC=True)
|
||||
if resp is None:
|
||||
raise OSError("flash_sdcard: SD Card did not come"
|
||||
" out of IDLE after reset")
|
||||
if len(resp) == 4:
|
||||
if self.sd_version == 1:
|
||||
# Check acceptable volatage range for V1 cards
|
||||
if resp[1] != 0xFF:
|
||||
raise OSError("flash_sdcard: card does not support"
|
||||
" 3.3v range")
|
||||
elif self.sd_version == 2:
|
||||
# Determine if this is a high capacity sdcard
|
||||
if resp[0] & 0x40:
|
||||
self.high_capacity = True
|
||||
else:
|
||||
raise OSError("flash_sdcard: Invalid OCR Response")
|
||||
if self.sd_version == 1:
|
||||
# Init card and come out of idle (ACMD41)
|
||||
# Version 1 Cards do this after checking the OCR
|
||||
if not self._check_command(0, 'SD_SEND_OP_COND', 0,
|
||||
is_app_cmd=True):
|
||||
raise OSError("flash_sdcard: SD Card did not come"
|
||||
" out of IDLE after reset")
|
||||
|
||||
# Read out CID information register
|
||||
self._process_cid_reg()
|
||||
|
||||
# Get card's relative address (RCA)
|
||||
resp = self._send_command_with_response('SET_REL_ADDR', 0)
|
||||
# Check if bits 15:13 have some error set
|
||||
if (resp[-2] & 0xe0) != 0:
|
||||
raise OSError("flash_sdcard: set card's "
|
||||
"relative address failed")
|
||||
self.rca = resp[0]<<8 | resp[1]
|
||||
|
||||
# Read out CSD information register
|
||||
self._process_csd_reg()
|
||||
|
||||
# Select the current card
|
||||
if not self._check_command(check_for_ocr_errors, 'SEL_DESEL_CARD',
|
||||
self.rca << 16, tries=1):
|
||||
raise OSError("flash_sdcard: failed to select the card")
|
||||
|
||||
# Set SDIO clk speed to approx. 1 MHz
|
||||
self.sdio.sdio_set_speed(1000000)
|
||||
|
||||
if self._check_command(check_for_ocr_errors, 'SET_BLOCKLEN',
|
||||
SECTOR_SIZE, tries=5):
|
||||
self.initialized = True
|
||||
else:
|
||||
raise OSError("flash_sdcard: failed to set block size")
|
||||
|
||||
|
||||
def deinit(self):
|
||||
with self.mutex:
|
||||
if self.initialized:
|
||||
# Reset the SD Card
|
||||
try:
|
||||
if not self._send_command('GO_IDLE_STATE', 0):
|
||||
logging.info("flash_sdcard: failed to reset SD Card")
|
||||
except Exception:
|
||||
logging.exception("Error resetting SD Card")
|
||||
self.initialized = False
|
||||
self.sd_version = 0
|
||||
self.high_capacity = False
|
||||
self.total_sectors = 0
|
||||
self.card_info.clear()
|
||||
|
||||
def _check_command(self, check_func, cmd, args, is_app_cmd=False, tries=15,
|
||||
ignoreCRC=False):
|
||||
func = self._send_app_cmd_with_response if is_app_cmd else \
|
||||
self._send_command_with_response
|
||||
while True:
|
||||
resp, rt = func(cmd, args, get_rt=True, ignoreCRC=ignoreCRC)
|
||||
#logging.info("flash_sdcard: Check cmd %s, response: %s"
|
||||
# % (cmd, repr(resp)))
|
||||
if resp and check_func(resp):
|
||||
return resp
|
||||
tries -= 1
|
||||
if tries < 1:
|
||||
return None
|
||||
self.reactor.pause(rt + .1)
|
||||
|
||||
def _send_command(self, cmd, args, wait=0):
|
||||
cmd_code = SD_COMMANDS[cmd]
|
||||
argument = 0
|
||||
if isinstance(args, int) or isinstance(args, long):
|
||||
argument = args & 0xFFFFFFFF
|
||||
elif isinstance(args, list) and len(args) == 4:
|
||||
argument = ((args[0] << 24) & 0xFF000000) | \
|
||||
((args[1] << 16) & 0x00FF0000) | \
|
||||
((args[2] << 8) & 0x0000FF00) | \
|
||||
((args[3] << 0) & 0x000000FF)
|
||||
else:
|
||||
raise OSError("flash_sdcard: Invalid SD Card Command argument")
|
||||
params = self.sdio.sdio_send_cmd(cmd_code, argument, wait)
|
||||
#logging.debug(f'_send_command({cmd=}, {args=}, {wait=}) -> '
|
||||
# 'CMD: {cmd_code} ARG: {argument} -> {params=}')
|
||||
if (wait == 0):
|
||||
# Just return the error code if no response was requested
|
||||
return params['error'] == 0
|
||||
return params
|
||||
|
||||
def _send_command_with_response(self, cmd, args, check_error=True,
|
||||
ignoreCRC=False, get_rt=False):
|
||||
# Wait for a short response
|
||||
params = self._send_command(cmd, args, wait=1)
|
||||
response = params['response']
|
||||
if check_error:
|
||||
if params['error'] != 0:
|
||||
if ignoreCRC and params['error'] != 4:
|
||||
response = []
|
||||
if get_rt:
|
||||
return bytearray(response), params['#receive_time']
|
||||
else:
|
||||
return bytearray(response)
|
||||
|
||||
def _send_app_cmd_with_response(self, cmd, args,
|
||||
ignoreCRC=False, get_rt=False):
|
||||
# CMD55 tells the SD Card that the next command is an
|
||||
# Application Specific Command.
|
||||
self._send_command_with_response('APP_CMD', self.rca << 16)
|
||||
return self._send_command_with_response(
|
||||
cmd, args, ignoreCRC=ignoreCRC, get_rt=get_rt)
|
||||
|
||||
def _process_cid_reg(self):
|
||||
params = self._send_command('ALL_SEND_CID', 0, wait=2)
|
||||
reg = bytearray(params['response'])
|
||||
if reg is None:
|
||||
raise OSError("flash_sdcard: Error reading CID register")
|
||||
|
||||
cid = collections.OrderedDict()
|
||||
cid['manufacturer_id'] = reg[0]
|
||||
cid['oem_id'] = reg[1:3].decode(encoding='ascii', errors='ignore')
|
||||
cid['product_name'] = reg[3:8].decode(
|
||||
encoding='ascii', errors='ignore')
|
||||
cid['product_revision'] = str(reg[8] >> 4 & 0xFF) + "." \
|
||||
+ str(reg[8] & 0xFF)
|
||||
cid['serial_number'] = "".join(["%02X" % (c,) for c in reg[9:13]])
|
||||
mfg_year = (((reg[13] & 0xF) << 4) | ((reg[14] >> 4) & 0xF)) + 2000
|
||||
mfg_month = reg[14] & 0xF
|
||||
cid['manufacturing_date'] = "%d/%d" % (mfg_month, mfg_year)
|
||||
crc = calc_crc7(reg[:15], with_padding=False)
|
||||
if crc != reg[15]:
|
||||
raise OSError("flash_sdcard: CID crc mismatch: 0x%02X, recd: 0x%02X"
|
||||
% (crc, reg[15]))
|
||||
self.card_info.update(cid)
|
||||
|
||||
def _process_csd_reg(self):
|
||||
params = self._send_command('SEND_CSD', self.rca << 16, wait=2)
|
||||
reg = bytearray(params['response'])
|
||||
if reg is None:
|
||||
raise OSError("flash_sdcard: Error reading CSD register")
|
||||
str_capacity = "Invalid"
|
||||
max_capacity = 0
|
||||
csd_type = (reg[0] >> 6) & 0x3
|
||||
if csd_type == 0:
|
||||
# Standard Capacity (CSD Version 1.0)
|
||||
max_block_len = 2**(reg[5] & 0xF)
|
||||
c_size = ((reg[6] & 0x3) << 10) | (reg[7] << 2) | \
|
||||
((reg[8] >> 6) & 0x3)
|
||||
c_mult = 2**((((reg[9] & 0x3) << 1) | (reg[10] >> 7)) + 2)
|
||||
max_capacity = (c_size + 1) * c_mult * max_block_len
|
||||
str_capacity = "%.1f MiB" % (max_capacity / (1024.0**2))
|
||||
elif csd_type == 1:
|
||||
# High Capacity (CSD Version 2.0)
|
||||
c_size = ((reg[7] & 0x3F) << 16) | (reg[8] << 8) | reg[9]
|
||||
max_capacity = (c_size + 1) * 512 * 1024
|
||||
str_capacity = "%.1f GiB" % (max_capacity / (1024.0**3))
|
||||
else:
|
||||
logging.info("sdcard: Unsupported csd type: %d" % (csd_type))
|
||||
self.write_protected = (reg[14] & 0x30) != 0
|
||||
crc = calc_crc7(reg[:15], with_padding=False)
|
||||
if crc != reg[15]:
|
||||
raise OSError("flash_sdcard: CSD crc mismatch: 0x%02X, recd: 0x%02X"
|
||||
% (crc, reg[15]))
|
||||
self.card_info['capacity'] = str_capacity
|
||||
self.total_sectors = max_capacity // SECTOR_SIZE
|
||||
|
||||
def print_card_info(self, print_func=logging.info):
|
||||
print_func("\nSD Card Information:")
|
||||
print_func("Version: %.1f" % (self.sd_version))
|
||||
print_func("SDHC/SDXC: %s" % (self.high_capacity))
|
||||
print_func("Write Protected: %s" % (self.write_protected))
|
||||
print_func("Sectors: %d" % (self.total_sectors,))
|
||||
for name, val in self.card_info.items():
|
||||
print_func("%s: %s" % (name, val))
|
||||
|
||||
def read_sector(self, sector):
|
||||
buf = None
|
||||
err_msg = "flash_sdcard: read error, sector %d" % (sector,)
|
||||
with self.mutex:
|
||||
if not 0 <= sector < self.total_sectors:
|
||||
err_msg += " out of range"
|
||||
elif not self.initialized:
|
||||
err_msg += ", SD Card not initialized"
|
||||
else:
|
||||
offset = sector
|
||||
if not self.high_capacity:
|
||||
offset = sector * SECTOR_SIZE
|
||||
|
||||
params = self.sdio.sdio_read_data(
|
||||
SD_COMMANDS['READ_SINGLE_BLOCK'], offset)
|
||||
if params['error'] != 0:
|
||||
raise OSError(
|
||||
'Read data failed. Error code=%d' %(params['error'],) )
|
||||
if params['read'] != SECTOR_SIZE:
|
||||
raise OSError(
|
||||
'Read data failed. Expected %d bytes but got %d.' %
|
||||
(SECTOR_SIZE, params['read']) )
|
||||
|
||||
buf = bytearray()
|
||||
offset = 0
|
||||
while SECTOR_SIZE-len(buf)>0:
|
||||
rest = min(SECTOR_SIZE-len(buf), 32)
|
||||
params = self.sdio.sdio_read_data_buffer(
|
||||
offset, length=rest)
|
||||
temp = bytearray(params['data'])
|
||||
if len(temp) == 0:
|
||||
raise OSError("Read zero bytes from buffer")
|
||||
buf += temp
|
||||
offset += len(temp)
|
||||
if buf is None:
|
||||
raise OSError(err_msg)
|
||||
return buf
|
||||
|
||||
def write_sector(self, sector, data):
|
||||
with self.mutex:
|
||||
if not 0 <= sector < self.total_sectors:
|
||||
raise OSError(
|
||||
"flash_sdcard: write error, sector number %d invalid"
|
||||
% (sector))
|
||||
if not self.initialized:
|
||||
raise OSError("flash_sdcard: write error, SD Card not"
|
||||
" initialized")
|
||||
outbuf = bytearray(data)
|
||||
if len(outbuf) > SECTOR_SIZE:
|
||||
raise OSError("sd_card: Cannot write sector larger"
|
||||
" than %d bytes"
|
||||
% (SECTOR_SIZE))
|
||||
elif len(outbuf) < SECTOR_SIZE:
|
||||
outbuf += bytearray([0] * (SECTOR_SIZE - len(outbuf)))
|
||||
offset = sector
|
||||
if not self.high_capacity:
|
||||
offset = sector * SECTOR_SIZE
|
||||
|
||||
CHUNKSIZE = 32
|
||||
for i in range(0, SECTOR_SIZE, CHUNKSIZE):
|
||||
self.sdio.sdio_write_data_buffer(i, outbuf[i:i+CHUNKSIZE])
|
||||
params = self.sdio.sdio_write_data(
|
||||
SD_COMMANDS['WRITE_BLOCK'], offset)
|
||||
if (params['error'] != 0) or (params['write'] != SECTOR_SIZE):
|
||||
raise OSError(
|
||||
"flash_sdcard: Error writing to sector %d"% (sector,))
|
||||
|
||||
status = self._send_command_with_response(
|
||||
'SEND_STATUS', self.rca << 16)
|
||||
if len(status) != 4:
|
||||
raise OSError("flash_sdcard: Failed to get status response"
|
||||
" after write: %s" % (repr(status),))
|
||||
if ((status[3]>>1) & 0x0F) != 0:
|
||||
# Bit 12:9 are not "0" (card is in idle)
|
||||
raise OSError("flash_sdcard: Write error."
|
||||
" Card is not in transfer state: 0x%02X"
|
||||
% (((status[3]>>1) & 0x0F)))
|
||||
|
||||
SDIO_WARNING = """
|
||||
This board requires a manual reboot to complete the flash process.
|
||||
If the board's bootloader uses SDIO mode for its SDCard, then a full
|
||||
power cycle is required. Please perform the power cycle now and then
|
||||
rerun this utility with the 'check' option to verify flash.
|
||||
"""
|
||||
|
||||
class MCUConnection:
|
||||
def __init__(self, k_reactor, device, baud, board_cfg):
|
||||
self.reactor = k_reactor
|
||||
@@ -889,7 +1271,7 @@ class MCUConnection:
|
||||
return True
|
||||
return False
|
||||
|
||||
def configure_mcu(self, printfunc=logging.info):
|
||||
def _configure_mcu_spibus(self, printfunc=logging.info):
|
||||
# TODO: add commands for buttons? Or perhaps an endstop? We
|
||||
# just need to be able to query the status of the detect pin
|
||||
cs_pin = self.board_config['cs_pin'].upper()
|
||||
@@ -945,6 +1327,41 @@ class MCUConnection:
|
||||
raise SPIFlashError(
|
||||
"Failed to Initialize SD Card. Is it inserted?")
|
||||
|
||||
def _configure_mcu_sdiobus(self, printfunc=logging.info):
|
||||
bus = self.board_config['sdio_bus']
|
||||
bus_enums = self.enumerations.get(
|
||||
'sdio_bus', self.enumerations.get('bus'))
|
||||
pin_enums = self.enumerations.get('pin')
|
||||
if bus not in bus_enums:
|
||||
raise SPIFlashError("Invalid SDIO Bus: %s" % (bus,))
|
||||
bus_cmd = SDIO_BUS_CMD % (SDIO_OID, bus)
|
||||
sdio_cfg_cmd = SDIO_CFG_CMD % (SDIO_OID, SECTOR_SIZE)
|
||||
cfg_cmds = [ALLOC_OIDS_CMD % (1,), sdio_cfg_cmd, bus_cmd]
|
||||
for cmd in cfg_cmds:
|
||||
self._serial.send(cmd)
|
||||
config_crc = zlib.crc32('\n'.join(cfg_cmds).encode()) & 0xffffffff
|
||||
self._serial.send(FINALIZE_CFG_CMD % (config_crc,))
|
||||
config = self.get_mcu_config()
|
||||
if not config["is_config"] or config["is_shutdown"]:
|
||||
raise MCUConfigError("Failed to configure MCU")
|
||||
printfunc("Initializing SD Card and Mounting file system...")
|
||||
self.fatfs = FatFS(self._serial,spi=False)
|
||||
self.reactor.pause(self.reactor.monotonic() + .5)
|
||||
try:
|
||||
self.fatfs.mount(printfunc)
|
||||
except OSError:
|
||||
logging.exception("SD Card Mount Failure")
|
||||
raise SPIFlashError(
|
||||
"Failed to Initialize SD Card. Is it inserted?")
|
||||
|
||||
def configure_mcu(self, printfunc=logging.info):
|
||||
if 'spi_bus' in self.board_config:
|
||||
self._configure_mcu_spibus(printfunc=printfunc)
|
||||
elif 'sdio_bus' in self.board_config:
|
||||
self._configure_mcu_sdiobus(printfunc=printfunc)
|
||||
else:
|
||||
raise SPIFlashError("Unknown bus defined in board_defs.py.")
|
||||
|
||||
def sdcard_upload(self):
|
||||
output("Uploading Klipper Firmware to SD Card...")
|
||||
input_sha = hashlib.sha1()
|
||||
@@ -989,6 +1406,9 @@ class MCUConnection:
|
||||
return sd_chksm
|
||||
|
||||
def verify_flash(self, req_chksm, old_dictionary, req_dictionary):
|
||||
if bool(self.board_config.get('skip_verify', False)):
|
||||
output_line(SDIO_WARNING)
|
||||
return
|
||||
output("Verifying Flash...")
|
||||
validation_passed = False
|
||||
msgparser = self._serial.get_msgparser()
|
||||
@@ -1063,6 +1483,7 @@ class SPIFlash:
|
||||
self.firmware_checksum = None
|
||||
self.task_complete = False
|
||||
self.need_upload = True
|
||||
self.need_verify = True
|
||||
self.old_dictionary = None
|
||||
self.new_dictionary = None
|
||||
if args['klipper_dict_path'] is not None:
|
||||
@@ -1092,7 +1513,7 @@ class SPIFlash:
|
||||
raise SPIFlashError("Unable to reconnect")
|
||||
output_line("Done")
|
||||
|
||||
def run_reset(self, eventtime):
|
||||
def run_reset_upload(self, eventtime):
|
||||
# Reset MCU to default state if necessary
|
||||
self.mcu_conn.connect()
|
||||
if self.mcu_conn.check_need_restart():
|
||||
@@ -1102,6 +1523,16 @@ class SPIFlash:
|
||||
self.need_upload = False
|
||||
self.run_sdcard_upload(eventtime)
|
||||
|
||||
def run_reset_verify(self, eventtime):
|
||||
# Reset MCU to default state if necessary
|
||||
self.mcu_conn.connect()
|
||||
if self.mcu_conn.check_need_restart():
|
||||
self.mcu_conn.reset()
|
||||
self.task_complete = True
|
||||
else:
|
||||
self.need_verify = False
|
||||
self.run_verify(eventtime)
|
||||
|
||||
def run_sdcard_upload(self, eventtime):
|
||||
# Reconnect and upload
|
||||
if not self.mcu_conn.connected:
|
||||
@@ -1121,7 +1552,8 @@ class SPIFlash:
|
||||
|
||||
def run_verify(self, eventtime):
|
||||
# Reconnect and verify
|
||||
self.mcu_conn.connect()
|
||||
if not self.mcu_conn.connected:
|
||||
self.mcu_conn.connect()
|
||||
self.mcu_conn.configure_mcu()
|
||||
self.mcu_conn.verify_flash(self.firmware_checksum, self.old_dictionary,
|
||||
self.new_dictionary)
|
||||
@@ -1148,12 +1580,18 @@ class SPIFlash:
|
||||
self.mcu_conn = k_reactor = None
|
||||
|
||||
def run(self):
|
||||
self.run_reactor_task(self.run_reset)
|
||||
self._wait_for_reconnect()
|
||||
if self.need_upload:
|
||||
self.run_reactor_task(self.run_sdcard_upload)
|
||||
if not bool(self.board_config.get('verify_only', False)):
|
||||
self.run_reactor_task(self.run_reset_upload)
|
||||
self._wait_for_reconnect()
|
||||
self.run_reactor_task(self.run_verify)
|
||||
if self.need_upload:
|
||||
self.run_reactor_task(self.run_sdcard_upload)
|
||||
self._wait_for_reconnect()
|
||||
self.run_reactor_task(self.run_verify)
|
||||
else:
|
||||
self.run_reactor_task(self.run_reset_verify)
|
||||
if self.need_verify:
|
||||
self._wait_for_reconnect()
|
||||
self.run_reactor_task(self.run_verify)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -1177,6 +1615,9 @@ def main():
|
||||
parser.add_argument(
|
||||
"-d", "--dict_path", metavar="<klipper.dict>", type=str,
|
||||
default=None, help="Klipper firmware dictionary")
|
||||
parser.add_argument(
|
||||
"-c","--check", action="store_true",
|
||||
help="Perform flash check/verify only")
|
||||
parser.add_argument(
|
||||
"device", metavar="<device>", help="Device Serial Port")
|
||||
parser.add_argument(
|
||||
@@ -1195,6 +1636,10 @@ def main():
|
||||
flash_args['baud'] = args.baud
|
||||
flash_args['klipper_bin_path'] = args.klipper_bin_path
|
||||
flash_args['klipper_dict_path'] = args.dict_path
|
||||
flash_args['verify_only'] = args.check
|
||||
if args.check:
|
||||
# override board_defs setting when doing verify-only:
|
||||
flash_args['skip_verify'] = False
|
||||
check_need_convert(args.board, flash_args)
|
||||
fatfs_lib.check_fatfs_build(output)
|
||||
try:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Encodes STM32 firmwares to be flashable from SD card by Chitu motherboards.
|
||||
# Relocate firmware to 0x08008800!
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Script to update firmware for MKS Robin bootloader
|
||||
#
|
||||
# Copyright (C) 2020 Kevin O'Connor <kevin@koconnor.net>
|
||||
|
||||
Reference in New Issue
Block a user