mcu: Convert ADC callback to pass a list of samples

Support batching multiple samples together and pass those samples to
the registered adc callback.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2026-02-14 12:38:14 -05:00
parent 5a2fd1009d
commit 8ed426b54c
8 changed files with 73 additions and 50 deletions

View file

@ -19,15 +19,16 @@ class MCU_scaled_adc:
self._callback = None
self.setup_adc_sample = self._mcu_adc.setup_adc_sample
self.get_mcu = self._mcu_adc.get_mcu
def _handle_callback(self, read_time, read_value):
def _handle_callback(self, samples):
max_adc = self._main.last_vref[1]
min_adc = self._main.last_vssa[1]
scaled_val = (read_value - min_adc) / (max_adc - min_adc)
self._last_state = (read_time, scaled_val)
self._callback(read_time, scaled_val)
def setup_adc_callback(self, report_time, callback):
adjsamples = [(t, (read_value - min_adc) / (max_adc - min_adc))
for t, read_value in samples]
self._last_state = adjsamples[-1]
self._callback(adjsamples)
def setup_adc_callback(self, callback):
self._callback = callback
self._mcu_adc.setup_adc_callback(report_time, self._handle_callback)
self._mcu_adc.setup_adc_callback(self._handle_callback)
def get_last_value(self):
return self._last_state
@ -52,8 +53,8 @@ class PrinterADCScaled:
pin_name = config.get(name + '_pin')
ppins = self.printer.lookup_object('pins')
mcu_adc = ppins.setup_pin('adc', pin_name)
mcu_adc.setup_adc_callback(REPORT_TIME, callback)
mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT)
mcu_adc.setup_adc_callback(callback)
mcu_adc.setup_adc_sample(REPORT_TIME, SAMPLE_TIME, SAMPLE_COUNT)
query_adc = config.get_printer().load_object(config, 'query_adc')
query_adc.register_adc(self.name + ":" + name, mcu_adc)
return mcu_adc
@ -68,9 +69,11 @@ class PrinterADCScaled:
adj_time = min(time_diff * self.inv_smooth_time, 1.)
smoothed_value = last_value + value_diff * adj_time
return (read_time, smoothed_value)
def vref_callback(self, read_time, read_value):
def vref_callback(self, samples):
read_time, read_value = samples[-1]
self.last_vref = self.calc_smooth(read_time, read_value, self.last_vref)
def vssa_callback(self, read_time, read_value):
def vssa_callback(self, samples):
read_time, read_value = samples[-1]
self.last_vssa = self.calc_smooth(read_time, read_value, self.last_vssa)
def load_config_prefix(config):

View file

@ -21,20 +21,21 @@ class PrinterADCtoTemperature:
self.adc_convert = adc_convert
ppins = config.get_printer().lookup_object('pins')
self.mcu_adc = ppins.setup_pin('adc', config.get('sensor_pin'))
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
self.mcu_adc.setup_adc_callback(self.adc_callback)
self.diag_helper = HelperTemperatureDiagnostics(
config, self.mcu_adc, adc_convert.calc_temp)
def setup_callback(self, temperature_callback):
self.temperature_callback = temperature_callback
def get_report_time_delta(self):
return REPORT_TIME
def adc_callback(self, read_time, read_value):
def adc_callback(self, samples):
read_time, read_value = samples[-1]
temp = self.adc_convert.calc_temp(read_value)
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
def setup_minmax(self, min_temp, max_temp):
arange = [self.adc_convert.calc_adc(t) for t in [min_temp, max_temp]]
min_adc, max_adc = sorted(arange)
self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT,
self.mcu_adc.setup_adc_sample(REPORT_TIME, SAMPLE_TIME, SAMPLE_COUNT,
minval=min_adc, maxval=max_adc,
range_check_count=RANGE_CHECK_COUNT)
self.diag_helper.setup_diag_minmax(min_temp, max_temp, min_adc, max_adc)

View file

@ -364,7 +364,7 @@ class ADS1X1X_pin:
systime = self._reactor.monotonic()
measured_time = self.chip.mcu.estimated_print_time(systime)
self._last_state = (measured_time, target_value)
self.callback(measured_time, target_value)
self.callback([(measured_time, target_value)])
else:
self.invalid_count = self.invalid_count + 1
self.check_invalid()
@ -379,16 +379,17 @@ class ADS1X1X_pin:
def get_mcu(self):
return self.mcu
def setup_adc_callback(self, report_time, callback):
self.report_time = report_time
def setup_adc_callback(self, callback):
self.callback = callback
self.chip.handle_report_time_update()
def setup_adc_sample(self, sample_time, sample_count,
minval=0., maxval=1., range_check_count=0):
def setup_adc_sample(self, report_time, sample_time=0., sample_count=1,
batch_num=1, minval=0., maxval=1.,
range_check_count=0):
self.report_time = report_time
self.minval = minval
self.maxval = maxval
self.range_check_count = range_check_count
self.chip.handle_report_time_update()
def get_last_value(self):
return self._last_state

View file

@ -104,8 +104,9 @@ class MCU_ADC_buttons:
self.max_value = 0.
ppins = printer.lookup_object('pins')
self.mcu_adc = ppins.setup_pin('adc', self.pin)
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
self.mcu_adc.setup_adc_sample(ADC_REPORT_TIME,
ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(self.adc_callback)
query_adc = printer.lookup_object('query_adc')
query_adc.register_adc('adc_button:' + pin.strip(), self.mcu_adc)
@ -114,7 +115,8 @@ class MCU_ADC_buttons:
self.max_value = max(self.max_value, max_value)
self.buttons.append((min_value, max_value, callback))
def adc_callback(self, read_time, read_value):
def adc_callback(self, samples):
read_time, read_value = samples[-1]
adc = max(.00001, min(.99999, read_value))
value = self.pullup * adc / (1.0 - adc)

View file

@ -49,11 +49,13 @@ class HallFilamentWidthSensor:
# Start adc
self.ppins = self.printer.lookup_object('pins')
self.mcu_adc = self.ppins.setup_pin('adc', self.pin1)
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
self.mcu_adc.setup_adc_sample(ADC_REPORT_TIME,
ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(self.adc_callback)
self.mcu_adc2 = self.ppins.setup_pin('adc', self.pin2)
self.mcu_adc2.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc2.setup_adc_callback(ADC_REPORT_TIME, self.adc2_callback)
self.mcu_adc2.setup_adc_sample(ADC_REPORT_TIME,
ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc2.setup_adc_callback(self.adc2_callback)
# extrude factor updating
self.extrude_factor_update_timer = self.reactor.register_timer(
self.extrude_factor_update_event)
@ -83,12 +85,14 @@ class HallFilamentWidthSensor:
self.reactor.update_timer(self.extrude_factor_update_timer,
self.reactor.NOW)
def adc_callback(self, read_time, read_value):
def adc_callback(self, samples):
# read sensor value
read_time, read_value = samples[-1]
self.lastFilamentWidthReading = round(read_value * 10000)
def adc2_callback(self, read_time, read_value):
def adc2_callback(self, samples):
# read sensor value
read_time, read_value = samples[-1]
self.lastFilamentWidthReading2 = round(read_value * 10000)
# calculate diameter
diameter_new = round((self.dia2 - self.dia1)/

View file

@ -31,12 +31,13 @@ class PrinterTemperatureMCU:
ppins = config.get_printer().lookup_object('pins')
self.mcu_adc = ppins.setup_pin('adc',
'%s:ADC_TEMPERATURE' % (mcu_name,))
self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
self.mcu_adc.setup_adc_callback(self.adc_callback)
self.diag_helper = adc_temperature.HelperTemperatureDiagnostics(
config, self.mcu_adc, self.calc_temp)
# Register callbacks
if self.printer.get_start_args().get('debugoutput') is not None:
self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT)
self.mcu_adc.setup_adc_sample(REPORT_TIME,
SAMPLE_TIME, SAMPLE_COUNT)
return
self.printer.register_event_handler("klippy:mcu_identify",
self.handle_mcu_identify)
@ -49,7 +50,8 @@ class PrinterTemperatureMCU:
self.min_temp = min_temp
self.max_temp = max_temp
# Internal code
def adc_callback(self, read_time, read_value):
def adc_callback(self, samples):
read_time, read_value = samples[-1]
temp = self.base_temperature + read_value * self.slope
self.temperature_callback(read_time + SAMPLE_COUNT * SAMPLE_TIME, temp)
def calc_temp(self, adc):
@ -95,7 +97,7 @@ class PrinterTemperatureMCU:
# Setup min/max checks
arange = [self.calc_adc(t) for t in [self.min_temp, self.max_temp]]
min_adc, max_adc = sorted(arange)
self.mcu_adc.setup_adc_sample(SAMPLE_TIME, SAMPLE_COUNT,
self.mcu_adc.setup_adc_sample(REPORT_TIME, SAMPLE_TIME, SAMPLE_COUNT,
minval=min_adc, maxval=max_adc,
range_check_count=RANGE_CHECK_COUNT)
self.diag_helper.setup_diag_minmax(self.min_temp, self.max_temp,

View file

@ -33,8 +33,9 @@ class FilamentWidthSensor:
# Start adc
self.ppins = self.printer.lookup_object('pins')
self.mcu_adc = self.ppins.setup_pin('adc', self.pin)
self.mcu_adc.setup_adc_sample(ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(ADC_REPORT_TIME, self.adc_callback)
self.mcu_adc.setup_adc_sample(ADC_REPORT_TIME,
ADC_SAMPLE_TIME, ADC_SAMPLE_COUNT)
self.mcu_adc.setup_adc_callback(self.adc_callback)
# extrude factor updating
self.extrude_factor_update_timer = self.reactor.register_timer(
self.extrude_factor_update_event)
@ -57,8 +58,9 @@ class FilamentWidthSensor:
self.reactor.update_timer(self.extrude_factor_update_timer,
self.reactor.NOW)
def adc_callback(self, read_time, read_value):
def adc_callback(self, samples):
# read sensor value
read_time, read_value = samples[-1]
self.lastFilamentWidthReading = round(read_value * 5, 2)
def update_filament_array(self, last_epos):

View file

@ -535,7 +535,7 @@ class MCU_adc:
self._pin = pin_params['pin']
self._min_sample = self._max_sample = 0.
self._sample_time = self._report_time = 0.
self._sample_count = self._range_check_count = 0
self._sample_count = self._batch_num = self._range_check_count = 0
self._report_clock = 0
self._last_state = (0., 0.)
self._oid = self._callback = None
@ -544,15 +544,17 @@ class MCU_adc:
self._unpack_from = struct.Struct('<H').unpack_from
def get_mcu(self):
return self._mcu
def setup_adc_sample(self, sample_time, sample_count,
minval=0., maxval=1., range_check_count=0):
def setup_adc_sample(self, report_time, sample_time=0., sample_count=1,
batch_num=1, minval=0., maxval=1.,
range_check_count=0):
self._report_time = report_time
self._sample_time = sample_time
self._sample_count = sample_count
self._batch_num = max(1, min(48 // 2, batch_num))
self._min_sample = minval
self._max_sample = maxval
self._range_check_count = range_check_count
def setup_adc_callback(self, report_time, callback):
self._report_time = report_time
def setup_adc_callback(self, callback):
self._callback = callback
def get_last_value(self):
return self._last_state
@ -560,12 +562,15 @@ class MCU_adc:
if not self._sample_count:
return
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (
self._oid, self._pin))
self._mcu.add_config_cmd("config_analog_in oid=%d pin=%s"
% (self._oid, self._pin))
clock = self._mcu.get_query_slot(self._oid)
sample_ticks = self._mcu.seconds_to_clock(self._sample_time)
mcu_adc_max = self._mcu.get_constant_float("ADC_MAX")
max_adc = self._sample_count * mcu_adc_max
if max_adc >= (1<<16):
raise self._mcu.get_printer().config_error(
"ADC sample_count=%d too large for MCU" % (self._sample_count,))
self._inv_max_adc = 1.0 / max_adc
self._report_clock = self._mcu.seconds_to_clock(self._report_time)
min_sample = max(0, min(0xffff, int(self._min_sample * max_adc)))
@ -575,7 +580,8 @@ class MCU_adc:
oldcmd = (
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u min_value=%hu max_value=%hu range_check_count=%c")
if self._mcu.try_lookup_command(oldcmd) is not None:
if (self._batch_num == 1
and self._mcu.try_lookup_command(oldcmd) is not None):
self._mcu.add_config_cmd(
"query_analog_in oid=%d clock=%d sample_ticks=%d"
" sample_count=%d rest_ticks=%d"
@ -587,12 +593,13 @@ class MCU_adc:
"analog_in_state", self._oid)
return
BYTES_PER_SAMPLE = 2
bytes_per_report = self._batch_num * BYTES_PER_SAMPLE
self._mcu.add_config_cmd(
"query_analog_in oid=%d clock=%d sample_ticks=%d sample_count=%d"
" rest_ticks=%d bytes_per_report=%d"
" min_value=%d max_value=%d range_check_count=%d" % (
self._oid, clock, sample_ticks, self._sample_count,
self._report_clock, BYTES_PER_SAMPLE, min_sample, max_sample,
self._report_clock, bytes_per_report, min_sample, max_sample,
self._range_check_count), is_init=True)
self._mcu.register_response(self._handle_analog_in_state,
"analog_in_state", self._oid)
@ -603,16 +610,17 @@ class MCU_adc:
last_read_time = self._mcu.clock_to_print_time(last_read_clock)
self._last_state = (last_read_time, last_value)
if self._callback is not None:
self._callback(last_read_time, last_value)
self._callback([(last_read_time, last_value)])
def _handle_analog_in_state(self, params):
values = self._unpack_from(params['values'])
last_value = values[0] * self._inv_max_adc
next_clock = self._mcu.clock32_to_clock64(params['next_clock'])
last_read_clock = next_clock - self._report_clock
last_read_time = self._mcu.clock_to_print_time(last_read_clock)
self._last_state = (last_read_time, last_value)
ctpt = self._mcu.clock_to_print_time
num = len(values)
samples = [(ctpt(next_clock - (num - i)*self._report_clock),
values[i] * self._inv_max_adc) for i in range(num)]
self._last_state = samples[-1]
if self._callback is not None:
self._callback(last_read_time, last_value)
self._callback(samples)
######################################################################