14 Commits

Author SHA1 Message Date
Kevin O'Connor
3aadda6fb3 mcu: Disable waiting in send_wait_ack() if in debugging mode
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-22 16:53:34 -04:00
Timofey Titovets
159b71e51e bus: drop obsolete i2c_write_wait_ack
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Timofey Titovets
718be7c6a3 sht3x: drop obsolete i2c_write_wait_ack
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Timofey Titovets
eb7bdf18ad bme280: drop obsolete i2c_write_wait_ack
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Timofey Titovets
fe44dd8baa bus: make i2c_write syncronous
When we introduce the host-side status check,
it will be synchronous.
There would be no sense in having an asynchronous call.
Preliminary migrate callers to synchronous call.

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 16:44:54 -04:00
Kevin O'Connor
ae010215e7 chelper: Build library first in temporary file and then rename
Try to avoid cases where an incomplete library build causes confusing
future failures.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-22 16:34:45 -04:00
Timofey Titovets
eec81683eb bus: move early i2c writes to the connect phase
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 14:57:28 -04:00
Timofey Titovets
1965298ab0 sx1509: init pwm pin on connect
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 14:57:28 -04:00
Timofey Titovets
9a1ac45d19 sx1509: migrate i2c write to connect phase
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-22 14:57:28 -04:00
minicx
b817848567 stm32: enable 64KiB bootloader for n32g45x, clarify Makefile output
- Allow selection of 64KiB bootloader offset for MACH_N32G45x in Kconfig

Signed-off-by: Lev Voronov <minicx@disroot.org>
Co-authored-by: Alexander Simonov <me@darksimpson.com>
2025-08-21 15:24:46 -04:00
minicx
3a11645afe stm32: Fix N32G45x ADC pin mapping (#7016)
Fixes PA0 (GPIO 0) incorrectly mapping to ADC1_IN0 due to
collision with placeholder zeros.

Signed-off-by: Lev Voronov <minicx@disroot.org>
Co-authored-by: Alexander Simonov <me@darksimpson.com>
2025-08-21 11:41:07 -04:00
C0co
7ed7791723 spi_flash: Update board_defs.py (#7006)
Added X-Smart3, X-Plus3 and X-Max3 mainboards

Signed-off-by: Phil Groenewold <philgroenewold@gmx.de>
2025-08-21 11:32:33 -04:00
Hendrik Poernama
3b68769ea5 tmc2240: Add OTW_OV_VTH to FieldFormatters (#6987)
This register is readable and contains the overvoltage and overtemp
threshold settings.

Signed-off-by: Hendrik Poernama <poernahi@gmail.com>
2025-08-21 09:33:31 -04:00
Kevin O'Connor
2ddfa32dd8 heaters: Reduce next_pwm_time window
Commit 0f94f6c8 decreased the MAX_HEAT_TIME from 5 seconds to 3
seconds.  However, that also decreased the amount of tolerance for
lost temperature updates from 1.25 seconds to 0.75 seconds.  With the
default temperature update every 300ms, only 2 consecutive missing
temperature updates could lead to a fault.

Tweak the internal "next_pwm_time" setting so that it is more tolerant
of two consecutive lost temperature updates.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-21 09:25:42 -04:00
12 changed files with 113 additions and 84 deletions

View File

@@ -274,6 +274,28 @@ def do_build_code(cmd):
logging.error(msg)
raise Exception(msg)
# Build the main c_helper.so c code library
def check_build_c_library():
srcdir = os.path.dirname(os.path.realpath(__file__))
srcfiles = get_abs_files(srcdir, SOURCE_FILES)
ofiles = get_abs_files(srcdir, OTHER_FILES)
destlib = get_abs_files(srcdir, [DEST_LIB])[0]
if not check_build_code(srcfiles+ofiles+[__file__], destlib):
# Code already built
return destlib
# Select command line options
if check_gcc_option(SSE_FLAGS):
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
else:
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
# Invoke compiler
logging.info("Building C code module %s", DEST_LIB)
tempdestlib = get_abs_files(srcdir, ["_temp_" + DEST_LIB])[0]
do_build_code(cmd % (tempdestlib, ' '.join(srcfiles)))
# Rename from temporary file to final file name
os.rename(tempdestlib, destlib)
return destlib
FFI_main = None
FFI_lib = None
pyhelper_logging_callback = None
@@ -286,17 +308,9 @@ def logging_callback(msg):
def get_ffi():
global FFI_main, FFI_lib, pyhelper_logging_callback
if FFI_lib is None:
srcdir = os.path.dirname(os.path.realpath(__file__))
srcfiles = get_abs_files(srcdir, SOURCE_FILES)
ofiles = get_abs_files(srcdir, OTHER_FILES)
destlib = get_abs_files(srcdir, [DEST_LIB])[0]
if check_build_code(srcfiles+ofiles+[__file__], destlib):
if check_gcc_option(SSE_FLAGS):
cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS)
else:
cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS)
logging.info("Building C code module %s", DEST_LIB)
do_build_code(cmd % (destlib, ' '.join(srcfiles)))
# Check if library needs to be built, and build if so
destlib = check_build_c_library()
# Open library
FFI_main = cffi.FFI()
for d in defs_all:
FFI_main.cdef(d)

View File

@@ -284,7 +284,7 @@ class BME280:
self.chip_type, self.i2c.i2c_address))
# Reset chip
self.write_register('RESET', [RESET_CHIP_VALUE], wait=True)
self.write_register('RESET', [RESET_CHIP_VALUE])
self.reactor.pause(self.reactor.monotonic() + .5)
# Make sure non-volatile memory has been copied to registers
@@ -394,7 +394,7 @@ class BME280:
self.write_register('CTRL_HUM', self.os_hum)
# Enter normal (periodic) mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC
self.write_register('CTRL_MEAS', meas, wait=True)
self.write_register('CTRL_MEAS', meas)
if self.chip_type == 'BME680':
self.write_register('CONFIG', self.iir_filter << 2)
@@ -528,7 +528,7 @@ class BME280:
# Enter forced mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE
self.write_register('CTRL_MEAS', meas, wait=True)
self.write_register('CTRL_MEAS', meas)
max_sample_time = self.max_sample_time
if run_gas:
max_sample_time += self.gas_heat_duration / 1000
@@ -776,15 +776,12 @@ class BME280:
params = self.i2c.i2c_read(regs, read_len)
return bytearray(params['response'])
def write_register(self, reg_name, data, wait = False):
def write_register(self, reg_name, data):
if type(data) is not list:
data = [data]
reg = self.chip_registers[reg_name]
data.insert(0, reg)
if not wait:
self.i2c.i2c_write(data)
else:
self.i2c.i2c_write_wait_ack(data)
def get_status(self, eventtime):
data = {

View File

@@ -180,6 +180,13 @@ class MCU_I2C:
self.cmd_queue = self.mcu.alloc_command_queue()
self.mcu.register_config_callback(self.build_config)
self.i2c_write_cmd = self.i2c_read_cmd = None
printer = self.mcu.get_printer()
printer.register_event_handler("klippy:connect", self._handle_connect)
# backward support i2c_write inside the init section
self._to_write = []
def _handle_connect(self):
for data in self._to_write:
self.i2c_write(data)
def get_oid(self):
return self.oid
def get_mcu(self):
@@ -207,14 +214,8 @@ class MCU_I2C:
cq=self.cmd_queue)
def i2c_write(self, data, minclock=0, reqclock=0):
if self.i2c_write_cmd is None:
# Send setup message via mcu initialization
data_msg = "".join(["%02x" % (x,) for x in data])
self.mcu.add_config_cmd("i2c_write oid=%d data=%s" % (
self.oid, data_msg), is_init=True)
self._to_write.append(data)
return
self.i2c_write_cmd.send([self.oid, data],
minclock=minclock, reqclock=reqclock)
def i2c_write_wait_ack(self, data, minclock=0, reqclock=0):
self.i2c_write_cmd.send_wait_ack([self.oid, data],
minclock=minclock, reqclock=reqclock)
def i2c_read(self, write, read_len, retry=True):

View File

@@ -75,7 +75,8 @@ class Heater:
# No significant change in value - can suppress update
return
pwm_time = read_time + self.pwm_delay
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
self.next_pwm_time = (pwm_time + MAX_HEAT_TIME
- (3. * self.pwm_delay + 0.001))
self.last_pwm_value = value
self.mcu_pwm.set_pwm(pwm_time, value)
#logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",

View File

@@ -80,10 +80,10 @@ class SHT3X:
def _init_sht3x(self):
# Device Soft Reset
self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['BREAK'])
self.i2c.i2c_write(SHT3X_CMD['OTHER']['BREAK'])
# Break takes ~ 1ms
self.reactor.pause(self.reactor.monotonic() + .0015)
self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['SOFTRESET'])
self.i2c.i2c_write(SHT3X_CMD['OTHER']['SOFTRESET'])
# Wait <=1.5ms after reset
self.reactor.pause(self.reactor.monotonic() + .0015)
@@ -97,7 +97,7 @@ class SHT3X:
logging.warning("sht3x: Reading status - checksum error!")
# Enable periodic mode
self.i2c.i2c_write_wait_ack(
self.i2c.i2c_write(
SHT3X_CMD['PERIODIC']['2HZ']['HIGH_REP']
)
# Wait <=15.5ms for first measurement

View File

@@ -29,7 +29,6 @@ class SX1509(object):
self._ppins = self._printer.lookup_object("pins")
self._ppins.register_chip("sx1509_" + self._name, self)
self._mcu = self._i2c.get_mcu()
self._mcu.register_config_callback(self._build_config)
self._oid = self._i2c.get_oid()
self._last_clock = 0
# Set up registers default values
@@ -37,22 +36,26 @@ class SX1509(object):
REG_PULLUP : 0, REG_PULLDOWN : 0,
REG_INPUT_DISABLE : 0, REG_ANALOG_DRIVER_ENABLE : 0}
self.reg_i_on_dict = {reg : 0 for reg in REG_I_ON}
def _build_config(self):
config.get_printer().register_event_handler("klippy:connect",
self.handle_connect)
def handle_connect(self):
# Reset the chip, Default RegClock/RegMisc 0x0
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
self._oid, REG_RESET, 0x12))
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
self._oid, REG_RESET, 0x34))
self._i2c.i2c_write([REG_RESET, 0x12])
self._i2c.i2c_write([REG_RESET, 0x34])
# Enable Oscillator
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
self._oid, REG_CLOCK, (1 << 6)))
self._i2c.i2c_write([REG_CLOCK, (1 << 6)])
# Setup Clock Divider
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
self._oid, REG_MISC, (1 << 4)))
self._i2c.i2c_write([REG_MISC, (1 << 4)])
# Transfer all regs with their initial cached state
for _reg, _data in self.reg_dict.items():
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%04x" % (
self._oid, _reg, _data), is_init=True)
reactor = self._mcu.get_printer().get_reactor()
for _reg in self.reg_dict:
curtime = reactor.monotonic()
printtime = self._mcu.estimated_print_time(curtime)
self.send_register(_reg, printtime)
for reg in self.reg_i_on_dict:
curtime = reactor.monotonic()
printtime = self._mcu.estimated_print_time(curtime)
self.send_register(reg, printtime)
def setup_pin(self, pin_type, pin_params):
if pin_type == 'digital_out' and pin_params['pin'][0:4] == "PIN_":
return SX1509_digital_out(self, pin_params)
@@ -159,15 +162,6 @@ class SX1509_pwm(object):
raise pins.error("SX1509_pwm must have hardware_pwm enabled")
if self._max_duration:
raise pins.error("SX1509 pins are not suitable for heaters")
# Send initial value
self._sx1509.set_register(self._i_on_reg,
~int(255 * self._start_value) & 0xFF)
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
self._sx1509.get_oid(),
self._i_on_reg,
self._sx1509.reg_i_on_dict[self._i_on_reg]
),
is_init=True)
def get_mcu(self):
return self._mcu
def setup_max_duration(self, max_duration):
@@ -181,6 +175,8 @@ class SX1509_pwm(object):
shutdown_value = 1. - shutdown_value
self._start_value = max(0., min(1., start_value))
self._shutdown_value = max(0., min(1., shutdown_value))
self._sx1509.set_register(self._i_on_reg,
~int(255 * self._start_value) & 0xFF)
def set_pwm(self, print_time, value):
self._sx1509.set_register(self._i_on_reg, ~int(255 * value)
if not self._invert

View File

@@ -262,6 +262,9 @@ FieldFormatters.update({
"adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))),
"adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)),
"adc_ain": (lambda v: "0x%04x(%.3fmV)" % (v, v * 0.3052)),
"overvoltage_vth": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)),
"overtempprewarning_vth": (lambda v:
"0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))),
})

View File

@@ -84,7 +84,7 @@ class CommandQueryWrapper:
# Wrapper around command sending
class CommandWrapper:
def __init__(self, serial, msgformat, cmd_queue=None):
def __init__(self, serial, msgformat, cmd_queue=None, debugoutput=False):
self._serial = serial
msgparser = serial.get_msgparser()
self._cmd = msgparser.lookup_command(msgformat)
@@ -92,6 +92,9 @@ class CommandWrapper:
cmd_queue = serial.get_default_command_queue()
self._cmd_queue = cmd_queue
self._msgtag = msgparser.lookup_msgid(msgformat) & 0xffffffff
if debugoutput:
# Can't use send_wait_ack when in debugging mode
self.send_wait_ack = self.send
def send(self, data=(), minclock=0, reqclock=0):
cmd = self._cmd.encode(data)
self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue)
@@ -888,7 +891,8 @@ class MCU:
def alloc_command_queue(self):
return self._serial.alloc_command_queue()
def lookup_command(self, msgformat, cq=None):
return CommandWrapper(self._serial, msgformat, cq)
return CommandWrapper(self._serial, msgformat, cq,
debugoutput=self.is_fileoutput())
def lookup_query_command(self, msgformat, respformat, oid=None,
cq=None, is_async=False):
return CommandQueryWrapper(self._serial, msgformat, respformat, oid,

View File

@@ -170,6 +170,14 @@ BOARD_DEFS = {
"firmware_path": "ZNP_ROBIN_NANO.bin",
"current_firmware_path": "ZNP_ROBIN_NANO.CUR"
},
'qidi-x6': {
'mcu': "stm32f401xc",
'spi_bus': "spi2",
'cs_pin': "PB12",
'skip_verify': False,
'firmware_path': 'X_4.bin',
'current_firmware_path': 'X_4.CUR'
},
'qidi-x7': {
'mcu': "stm32f401xc",
'spi_bus': "spi2",
@@ -228,6 +236,9 @@ BOARD_ALIASES = {
'robin_v3': BOARD_DEFS['monster8'],
'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'],
'chitu-v6': BOARD_DEFS['chitu-v6'],
'qidi-x-smart3': BOARD_DEFS['qidi-x6'],
'qidi-x-plus3': BOARD_DEFS['qidi-x6'],
'qidi-x-max3': BOARD_DEFS['qidi-x6'],
'qidi-q1-pro': BOARD_DEFS['qidi-x7'],
'qidi-plus4': BOARD_DEFS['qidi-x7']
}

View File

@@ -302,7 +302,7 @@ choice
config STM32_FLASH_START_C000
bool "48KiB bootloader" if MACH_STM32F4x5 || MACH_STM32F401
config STM32_FLASH_START_10000
bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4
bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 || MACH_N32G45x
config STM32_FLASH_START_800
bool "2KiB bootloader" if MACH_STM32F103

View File

@@ -94,7 +94,7 @@ src-$(CONFIG_HAVE_GPIO_SDIO) += stm32/sdio.c
target-y += $(OUT)klipper.bin
$(OUT)klipper.bin: $(OUT)klipper.elf
@echo " Creating hex file $@"
@echo " Creating bin file $@"
$(Q)$(OBJCOPY) -O binary $< $@
# Flash rules

View File

@@ -14,6 +14,8 @@
#include "sched.h" // sched_shutdown
#include "n32g45x_adc.h" // ADC
#define ADC_INVALID_PIN 0xFF
DECL_CONSTANT("ADC_MAX", 4095);
#define ADC_TEMPERATURE_PIN 0xfe
@@ -21,42 +23,42 @@ DECL_ENUMERATION("pin", "ADC_TEMPERATURE", ADC_TEMPERATURE_PIN);
static const uint8_t adc_pins[] = {
// ADC1
0, GPIO('A', 0), GPIO('A', 1), GPIO('A', 6),
GPIO('A', 3), GPIO('F', 4), 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
ADC_TEMPERATURE_PIN, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
ADC_INVALID_PIN, GPIO('A', 0), GPIO('A', 1), GPIO('A', 6),
GPIO('A', 3), GPIO('F', 4), ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_TEMPERATURE_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
// ADC2
0, GPIO('A', 4), GPIO('A', 5), GPIO('B', 1),
ADC_INVALID_PIN, GPIO('A', 4), GPIO('A', 5), GPIO('B', 1),
GPIO('A', 7), GPIO('C', 4), GPIO('C', 0), GPIO('C', 1),
GPIO('C', 2), GPIO('C', 3), GPIO('F', 2), GPIO('A', 2),
GPIO('C', 5), GPIO('B', 2), 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
GPIO('C', 5), GPIO('B', 2), ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
#if CONFIG_MACH_N32G455 // ADC3/4 for G455 only
// ADC3
0, GPIO('B', 11), GPIO('E', 9), GPIO('E', 13),
ADC_INVALID_PIN, GPIO('B', 11), GPIO('E', 9), GPIO('E', 13),
GPIO('E', 12), GPIO('B', 13), GPIO('E', 8), GPIO('D', 10),
GPIO('D', 11), GPIO('D', 12), GPIO('D', 13), GPIO('D', 14),
GPIO('B', 0), GPIO('E', 7), GPIO('E', 10), GPIO('E', 11),
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
// ADC4
0, GPIO('E', 14), GPIO('E', 15), GPIO('B', 12),
GPIO('B', 14), GPIO('B', 15), 0, 0,
0, 0, 0, 0,
GPIO('D', 8), GPIO('D', 9), 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
ADC_INVALID_PIN, GPIO('E', 14), GPIO('E', 15), GPIO('B', 12),
GPIO('B', 14), GPIO('B', 15), ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
GPIO('D', 8), GPIO('D', 9), ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
#endif
};