From b336a21fe7361c4a0ff177a99e046472dfe0e96b Mon Sep 17 00:00:00 2001 From: Rufo Sanchez Date: Sun, 12 Sep 2021 16:59:15 -0500 Subject: [PATCH] button: Support half-stepping encoders Adds support for half-stepping encoders (encoders that only emit two steps per detent, instead of four). Incorporates the feedback from @susisstrolch's PR: https://github.com/KevinOConnor/klipper/pull/4202 , which was itself built upon a previous PR from @nickbrennan01: https://github.com/KevinOConnor/klipper/pull/730 Uses the table from the Rotary Arduino library linked in buttons.py: https://github.com/brianlow/Rotary/blob/6b784cca67c5f1ce5e11d757a540fc4c0311efca/Rotary.cpp#L21-L40 Signed-off-by: Rufo Sanchez --- docs/Config_Reference.md | 5 ++ klippy/extras/buttons.py | 121 +++++++++++++++++++++-------- klippy/extras/display/menu_keys.py | 5 +- 3 files changed, 96 insertions(+), 35 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 1ae5a5c6..b8dd356e 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -3240,6 +3240,11 @@ lcd_type: #encoder_pins: # The pins connected to encoder. 2 pins must be provided when using # encoder. This parameter must be provided when using menu. +#encoder_steps_per_detent: +# How many steps the encoder emits per detent ("click"). If the +# encoder takes two detents to move between entries or moves two +# entries from one detent, try changing this. Allowed values are 2 +# (half-stepping) or 4 (full-stepping). The default is 4. #click_pin: # The pin connected to 'enter' button or encoder 'click'. This # parameter must be provided when using menu. The presence of an diff --git a/klippy/extras/buttons.py b/klippy/extras/buttons.py index 4a23b60d..1a6147d1 100644 --- a/klippy/extras/buttons.py +++ b/klippy/extras/buttons.py @@ -158,47 +158,93 @@ class MCU_ADC_buttons: # Rotary encoder handler https://github.com/brianlow/Rotary # Copyright 2011 Ben Buxton (bb@cactii.net). # Licenced under the GNU GPL Version 3. -R_START = 0x0 -R_CW_FINAL = 0x1 -R_CW_BEGIN = 0x2 -R_CW_NEXT = 0x3 -R_CCW_BEGIN = 0x4 -R_CCW_FINAL = 0x5 -R_CCW_NEXT = 0x6 -R_DIR_CW = 0x10 -R_DIR_CCW = 0x20 -R_DIR_MSK = 0x30 -# Use the full-step state table (emits a code at 00 only) -ENCODER_STATES = ( - # R_START - (R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START), - # R_CW_FINAL - (R_CW_NEXT, R_START, R_CW_FINAL, R_START | R_DIR_CW), - # R_CW_BEGIN - (R_CW_NEXT, R_CW_BEGIN, R_START, R_START), - # R_CW_NEXT - (R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START), - # R_CCW_BEGIN - (R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START), - # R_CCW_FINAL - (R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | R_DIR_CCW), - # R_CCW_NEXT - (R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START) -) +class BaseRotaryEncoder: + R_START = 0x0 + R_DIR_CW = 0x10 + R_DIR_CCW = 0x20 + R_DIR_MSK = 0x30 -class RotaryEncoder: def __init__(self, cw_callback, ccw_callback): self.cw_callback = cw_callback self.ccw_callback = ccw_callback - self.encoder_state = R_START + self.encoder_state = self.R_START def encoder_callback(self, eventtime, state): - es = ENCODER_STATES[self.encoder_state & 0xf][state & 0x3] + es = self.ENCODER_STATES[self.encoder_state & 0xf][state & 0x3] self.encoder_state = es - if es & R_DIR_MSK == R_DIR_CW: + if es & self.R_DIR_MSK == self.R_DIR_CW: self.cw_callback(eventtime) - elif es & R_DIR_MSK == R_DIR_CCW: + elif es & self.R_DIR_MSK == self.R_DIR_CCW: self.ccw_callback(eventtime) +class FullStepRotaryEncoder(BaseRotaryEncoder): + R_CW_FINAL = 0x1 + R_CW_BEGIN = 0x2 + R_CW_NEXT = 0x3 + R_CCW_BEGIN = 0x4 + R_CCW_FINAL = 0x5 + R_CCW_NEXT = 0x6 + + # Use the full-step state table (emits a code at 00 only) + ENCODER_STATES = ( + # R_START + (BaseRotaryEncoder.R_START, R_CW_BEGIN, R_CCW_BEGIN, + BaseRotaryEncoder.R_START), + + # R_CW_FINAL + (R_CW_NEXT, BaseRotaryEncoder.R_START, R_CW_FINAL, + BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CW), + + # R_CW_BEGIN + (R_CW_NEXT, R_CW_BEGIN, BaseRotaryEncoder.R_START, + BaseRotaryEncoder.R_START), + + # R_CW_NEXT + (R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, BaseRotaryEncoder.R_START), + + # R_CCW_BEGIN + (R_CCW_NEXT, BaseRotaryEncoder.R_START, R_CCW_BEGIN, + BaseRotaryEncoder.R_START), + + # R_CCW_FINAL + (R_CCW_NEXT, R_CCW_FINAL, BaseRotaryEncoder.R_START, + BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW), + + # R_CCW_NEXT + (R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, BaseRotaryEncoder.R_START) + ) + +class HalfStepRotaryEncoder(BaseRotaryEncoder): + # Use the half-step state table (emits a code at 00 and 11) + R_CCW_BEGIN = 0x1 + R_CW_BEGIN = 0x2 + R_START_M = 0x3 + R_CW_BEGIN_M = 0x4 + R_CCW_BEGIN_M = 0x5 + + ENCODER_STATES = ( + # R_START (00) + (R_START_M, R_CW_BEGIN, R_CCW_BEGIN, BaseRotaryEncoder.R_START), + + # R_CCW_BEGIN + (R_START_M | BaseRotaryEncoder.R_DIR_CCW, BaseRotaryEncoder.R_START, + R_CCW_BEGIN, BaseRotaryEncoder.R_START), + + # R_CW_BEGIN + (R_START_M | BaseRotaryEncoder.R_DIR_CW, R_CW_BEGIN, + BaseRotaryEncoder.R_START, BaseRotaryEncoder.R_START), + + # R_START_M (11) + (R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, BaseRotaryEncoder.R_START), + + # R_CW_BEGIN_M + (R_START_M, R_START_M, R_CW_BEGIN_M, + BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CW), + + # R_CCW_BEGIN_M + (R_START_M, R_CCW_BEGIN_M, R_START_M, + BaseRotaryEncoder.R_START | BaseRotaryEncoder.R_DIR_CCW), + ) + ###################################################################### # Button registration code @@ -240,8 +286,15 @@ class PrinterButtons: self.mcu_buttons[mcu_name] = mcu_buttons = MCU_buttons( self.printer, mcu) mcu_buttons.setup_buttons(pin_params_list, callback) - def register_rotary_encoder(self, pin1, pin2, cw_callback, ccw_callback): - re = RotaryEncoder(cw_callback, ccw_callback) + def register_rotary_encoder(self, pin1, pin2, cw_callback, ccw_callback, + steps_per_detent): + if steps_per_detent == 2: + re = HalfStepRotaryEncoder(cw_callback, ccw_callback) + elif steps_per_detent == 4: + re = FullStepRotaryEncoder(cw_callback, ccw_callback) + else: + raise self.printer.config_error( + "%d steps per detent not supported" % steps_per_detent) self.register_buttons([pin1, pin2], re.encoder_callback) def register_button_push(self, pin, callback): def helper(eventtime, state, callback=callback): diff --git a/klippy/extras/display/menu_keys.py b/klippy/extras/display/menu_keys.py index 29e58fe3..91a96e19 100644 --- a/klippy/extras/display/menu_keys.py +++ b/klippy/extras/display/menu_keys.py @@ -17,6 +17,8 @@ class MenuKeys: buttons = self.printer.load_object(config, "buttons") # Register rotary encoder encoder_pins = config.get('encoder_pins', None) + encoder_steps_per_detent = config.getchoice('encoder_steps_per_detent', + {2: 2, 4: 4}, 4) if encoder_pins is not None: try: pin1, pin2 = encoder_pins.split(',') @@ -24,7 +26,8 @@ class MenuKeys: raise config.error("Unable to parse encoder_pins") buttons.register_rotary_encoder(pin1.strip(), pin2.strip(), self.encoder_cw_callback, - self.encoder_ccw_callback) + self.encoder_ccw_callback, + encoder_steps_per_detent) self.encoder_fast_rate = config.getfloat('encoder_fast_rate', .030, above=0.) self.last_encoder_cw_eventtime = 0