Proposal: Button Debounce

Basic Information:

Printer Model: Peopoly Magneto X
MCU / Printerboard: Magneto X Toolhead v1.1
klippy.log N/A

Rationale

Button debounce is a common problem, and “add a pull up resistor” is not always practical or the appropriate fix. For example, some mechanical switches have bouncy by their very nature. Plus extremely high accelerations could cause issues. Adding an entire debounce circuit or using clunky macros seem like hacks for what could be handled easily in software.

Prior Work

ADC buttons already have a debounce time. This could be expanded to cover all buttons.

5 posts were split to a new topic: Debouncing endstops

This references buttons, IDK if OP meant homing? Do they mean gcode buttons? @EmperorArthur can you explain what buttons you are talking about?

The homing debounce check for switch pins is in the endstop c code so that single “triggers” of a switch are ignored. When the switch triggers the first time the trigger time is captured. Then the switch is polled subsequent times, the default is 4 times. If it doesn’t stay triggered the for all 4 checks the trigger resets. If it stays triggered the time of the first trigger is reported.

2 Likes

You’re correct that I do not mean homing. I have no idea where that came from.

I was not aware that endstops already perform debounce checks. It’s relevant to this discussion in it shows other places where that type of code is used.

I was specifically talking about klippy/extras/buttons.py. Which is used by other code.

The primary interest I have is the filament_switch_sensor. The Magneto X comes with this sketchy macro to make up for either a hardware / sensor tolerance issue or the stupid accelerations it is capable of triggering a false detection.

Looking through the code, filament_switch_sensor registers a button with a callback under the hood. That button code just seems like the logical place for debounce code to live, but I’m not that familiar with Klipper internals.

@mykepredko

Excluding everything that’s been said about how homing already, here’s a few links. Including multiple people using macros, and one manufacturer explicitly noting they implemented debounce on a separate microcontroller.

Edit:
I am aware that pull up resistors do not help with debounce, however that was the recommended solution in one of the discourse links I’ve provided. Which is why I noted it does not solve the problem.

If I’m not mistaken, the buttons already do a similar debounce magic, but someone with more code understanding should verify.

Yes, there is some debounce magic in buttons.c. But I think this only requires that the button is pressed for 2 reads. And reads happen pretty frequently, every 2 milliseonds

That’s a very short amount of time on a human scale (the blink of an eye is ~150ms). So its possible that manipulating the filament causes multiple button presses.

But filament_switch_sensor has a second much more aggressive debounce. The option event_delay defaults to 3 seconds and it ignores duplicate events during that time. So the macro cant be triggered more than once every 3 seconds.

@EmperorArthur can you describe the problem? What is happening with the filament_switch_sensor that’s frustrating you?

I think I see the confusion. You’re thinking about debounce for multiple events. Which can be an issue. However, I’m focusing on false triggers, like you mentioned with the endstops and what buttons.c is doing. event_delay doesn’t help with that situation at all.

I wasn’t aware of the C debounce code, since I found the other debounce code in Python. Not a big deal, but worth noting the two different implementations of the same functionality. The original proposal was to generalize the Python code. Especially since that sort of false trigger detection could potentially be used in other areas as well.


Concrete Problem Example

I’ve personally left that original macro code in place, but it seems like a hack for a hardware design problem. While the specifics below are unique to the Magneto X, I’m sure there are plenty of other run-out sensor designs which have similar problems.





The Magneto X has a ball bearing that is pressed just under half-way into the filament path by a no-name microswitch. It’s a clever design, but I can think of multiple ways for it to go wrong. Especially under high acceleration.

There’s also some circuitry to drive the two color LED based on the sensor and the buttons. Which I suspect may also lead to false triggering.

@EmperorArthur

Sorry about being flippant earlier about the filament sensors - as you’re discovering, most of them out there are, well, crap.

In my experience, I have found that a filament sensor must have a filament motion sensor to be truly useful. Just having a mechanical sensor (such as the ball you mentioned) is not going to give you an acceptable level of coverage.

What I would recommend is using a filament sensor with an optical or Hall effect rotary encoder that monitors the motion of the filament passing through it. If the filament stops, then an error is thrown. Without monitoring the movement of the filament, it’s not 100% that a broken filament will be detected (anywhere you put the sensor, there are cases where it won’t detect a problem).

Optical rotary encoders were the method used in early mice to monitor the movement of the ball at the bottom of the device.

Personally, I use the BTT Smart Filament Sensor:

I don’t know what you call “high acceleration” but I doubt that the speed of the filament is such that a filament sensor with an optical rotary encoder will have a problem.

No worries. I will be adding more sensors when I build the 3MS MMU. However, the false trigger issue seems to be the nature of the switch based sensor. It’s common enough that I think the development effort is worth it. Especially since doing so in the “button” code would deal with multiple scenarios.

For the Magneto X, I will absolutely agree there are better designs of sensor. However, replacing that one with something else in the same location pretty much requires replacing the entire toolhead, since it’s integrated into the extruder feed mechanism.

The acceleration I’m referring to is the toolhead itself. The advertised speeds are 800mm/s and 22,000mm/s^2. The ball bearing should have more than enough inertia to overcome the switch spring when performing Y travel moves if filament is not present.


Development

I could do the work myself, but am not familiar with the code base and don’t want to work on something which would be rejected as unwanted.

I could take the ADC debounce code in Python and use that as a base. Alternatively, I could modify the C code. However, I don’t think I fully understand that. At first I thought pressed and last_pressed were bit fields. Now it seems like they’re closer to a hash of all current pin states…

Well a good step 1 would be to hack together something that solves your problem. Then we would at least know if debouncing is a solution, and if so, just how much is required for it to be effective.

I could see a macro that only alerts that filament has run out if the switch stabilises in the triggered state for 3 seconds.

A macro solution is easier to hack together than modding the c code. If that’s a fix then we could try to replicate it in the Python or C layers.

I just saw the response from @garethky and rather than look at the software, my suggestion would be to use a filament sensor that isn’t mounted on the toolhead.

Peopoly probably placed the filament sensor there because if there is a break in the filament or if it runs out, you get an indication with the least possible waste of filament. Personally, I wouldn’t place it there because it adds weight and complexity to the toolhead and, as you’re noting, the high accelerations can cause problems with its operation.

Again, a filament sensor with a rotary optical encoder mounted on the printer’s frame will give you the same advantages without worrying about its operation at high accelerations.

As I have a MagentoX as well: My interpretation is that the filament sensor is causing spurious triggers even if filament is present. Thus, this would be attributed to the mechanical design of the sensor rather than its electrical characteristics like bouncing.

1 Like

That’s easy. Peopoly already did the work. Though, I just noticed they didn’t update their GitHub with those macros! That’s my bad for not actually linking to what I was talking about.

I will post their 3 second verification macro either after work or on my lunch break.

@Sineos

Is the housing for your sensor body metal? Peopoly doesn’t advertise it, but there are three different variations of the printer. The wiki doesn’t have the V1.1 boards, or the enclosure mount changes for the V2 design.

@mykepredko
On the other hand, putting it somewhere else ads significant complexity to the manufacturing process. Plus, having it somewhere else means having to unhook the bowden tube from the extruder and pull the ~1m of filament out before loading more in. Which is a pain with how their enclosure is set up.

I will agree they could have gone with an optical switch or an encoder instead of the approach they took.

As for the cons you mentioned:

Complexity of the toolhead was a lost cause the moment they decided to use a force sensor built into the extruder as the Z endstop. Also, the toolhead board serves double duty as a connector board for motor power.

The extra few grams are also not that big of a deal, as the linear motors on this thing are powerful. Like, it has a separate 48V 600A power supply just to run the two motors powerful. Plus, it uses linear encoders with a fine control loop, done on the motor driver boards, to ensure accurate positioning.

The Magneto X is a beast of a printer. It’s just more like a beta product, with some fit and finish issues to work out.

2 Likes

Here is a swing at doing this in klippy: Comparing Klipper3d:master...garethky:pr-debounce-filament-switch · Klipper3d/klipper · GitHub

If you are interested, please test it out. I haven’t, I don’t have a filament switch. You’ll need to add a debounce option to the filament switch config:

debounce: 3.0
event_delay: 0.0

You’ll also want to disable the default 3s event delay as there will already be a 3 second delay before an action is taken.

This should make it such that, if you inserted the filament and then removed it, all in under 3 seconds, the printer would ignore that action.

If this solves the problem and its bug free I can send it out as a PR.

I think it would be more appropriate to implement the debounce config/logic in the SwitchSensor class. It probably isn’t necessary for the other sensors that use the RunoutHelper, and you really only want to call note_filament_present() after the input has been processed (including debounced if desired).

1 Like

It looks like its in use by the hall filament sensor and the motion sensor. Would support in those places actually be a bad thing? It is off by default so it shouldn’t impact existing setups. And this is debouncing on a timescale (seconds) that’s not going to be covered by hardware/firmware. This is a behavior changing feature, why wouldn’t you want the ability to suppress transient events across all sensor types?

I would submit that while the default state would be fine, any configuration of debounce with those sensors could lead to invalid results. They don’t use the RunoutSensor the same way the switch sensor does, generally calls to note_filament_present() are the result of some calculation made using results reported by the sensor, so the calls themselves cannot be transient. They could be the result of incorrect internal state, however that filtering would need to be done at the module level.

Ok, I broke it out into its own class so its easy to re-use and test in other places. The patch only applies it to the switch filament sensor.

My argument for including this in more places would be that this could be beneficial to any ‘button’ regardless of the underlying sensor tech. E.g.:

  • You inserts some filament but then remember that you forgot to trim the end so you pull it back out. Debounce stops the printer from going into its loading filament routine.
  • You have a physical button to run M112 / E-Stop the machine. You put a 150ms debounce on it to prevent accidental taps on the button from doing anything bad. You have to hold it down intentionally for it to activate.

This kind of usability stuff is widespread but goes unnoticed. Its usually the difference between something that feels like a quality product and something that feels ‘jank’.


separately, something I noticed… It looks like buttons notify on startup, which generates an event if the state of the button is True. I don’t understand how this isn’t an issue now? Like if you start the machine, and there is filament already in the extruder, then wouldn’t it trigger filament loading? You’d have to track filament loading state in a save variable and then ignore the first call to the macro or something?

Should the debounce code filter this out?

e.g. this config:

[filament_switch_sensor filament_sw]
switch_pin: ESTOP6
runout_gcode:
    {action_respond_info("Runout Detected")}
insert_gcode:
    {action_respond_info("Filament Insertion Detected")}
event_delay: 0.0
debounce_delay: 3.0

Will print this on restart:

// Filament Insertion Detected

And if I invert the pin:

switch_pin: !ESTOP6

It prints nothing

1 Like

The first example will not work with all sensor implementations. Take the filament motion sensor for example. It will call note_filament_present(True) when any encoder event is received. However, it can only detect a runout when the extruder is has moved a specified distance with no encoder events. So merely inserting and removing filament would not stop an insert event with a “debounce” configured.

The second example however would, debounce easily applies to a button that returns a discrete value. I agree that it provides a good reason to break out the debounce logic.

It should be taken care of. The RunoutHelper.min_event_systime attribute is initialized to NEVER, so the initial event should be ignored. The ready event handler in the RunoutHelper resets min_event_systime to 2 seconds after the ready event was received.

1 Like

its not the case with my change. In _handle_ready the time gets updated. Then DebounceButton delays the event by 3 seconds and triggers it after _handle_ready.

  • I can just initialize the internal state when the first event shows up and not treat that as a state transition (i kinda prefer this)
  • I could report the time when the button was triggered, rather than the current time.