Question with respect to the implementation of closed-loop feedback in Klipper

Hello everyone, this is my first time posting here. I apologize if this isn’t too relevant to Klipper’s overall project goals, but I’m extremely curious about your perspective on whether I should proceed.

I’m aware of the Mechaduino project and have glanced over some of the codebase and discussion posts. I am curious as to why closed-loop/servo control was not merged into the mainline Klipper. Is it a general latency issue, a conflict with the Klippers planner? I’m curious because I’m working on a project in opto-mechanics at a university that intends to create an open-source means of controlling optics equipment in a closed-loop fashion.

For context, I’ve already created a control board consisting of an RP2350, two TMC2209s and a pair of AS5147Us that actuate a kinematic mirror mount: Mirror mount - Wikipedia . It ran a simple PID loop that updated at roughly 100Hz, but earlier versions of my code were also able to run the PID loop just fine for getting satisfactory positioning accuracy at 10Hz, it was just a little slow to achieve the final position, along with a tad bit of overshotting, which is less than ideal when trying to characterize the system’s motion.

My supervisor has highlighted that for this project to be generally successful, it should be easy to replicate. So over the past couple of weeks, I’ve restarted board and firmware development to adopt a Klipper-style approach, where all boards that work with Klipper may work with this project. I know that Klipper’s motion planner is generally incompatible with closed-loop control, but given the very low polling rate relative to the fast-paced requirements of 3D printers, I’ve started work on two approaches. The first one is just a simple Python script that interfaces with Klipper to apply outer-loop corrections to a stepper motor’s position, which I think could achieve the same results as my custom board. The second one is just to complete the work Kevin O Connor started on the Mechaduino project regarding the closed-loop system, but I admittedly haven’t looked into it too deeply.

I’m curious if anyone was able to get this feedback control working in Klipper. Any feedback would be greatly appreciated since I have little experience in Klipper development.

All the best,

-Andrew

1 Like

It is a complicated topic, this is my personal opinion.

Shortly speaking, it is not like compatible or not compatible.

The high-level limitation is that the motion planner runs in the future and sends data in the future.
It is possible, though, to collect data after something happened and do a correction, simply speaking, set the actual stepper position after it has lost its position.
But it will be with about >1s round-trip latency.

So, you can correct for missing steps, but generally it is simpler to avoid that (Right size of the motor, correct current).

The second problem is how this correction is implemented in the external controller.
Assume you did, connect the servo.
For whatever reason, there is a salmon skin effect. It can be caused by:

  • PID correction of the uneven rotation of the belt sprocket
  • Uneven movement of the belt
  • Uneven timings of the steps (there is a jitter and lag, it is just small for open loop <25us).
  • Second motor on the CoreXY that applies torque

Probably, part of this problem can be solved if there is an encoder from step/dir on the host side, to the servo protocol, whatever it can be, so the servo would know what we want without step/dir translation and lag.

And the end result of this is that it adds complexity and provides pretty little practical benefits.
Skipping steps is not an issue. Precision, maybe, but generally <0.1mm dimensional error is fine.

So, for example, you can pretty easily connect your encoders to the Klipper.
You can dump data through the bulk subsystem, track motion, and whatever.

But I have a hard time imagining the practical use case.
Just for reference, I had the encoder connected, and I helped with enabling of host side closed loop.

Hope that explains something.

So, I guess, the right question is, what problem are you trying to solve with this?
The only curious setup that I can imagine is an encoder on the extruder, because it allows us to estimate pressure, and we can probably do something with this data.

(This is a video from @MRX8024, who managed that, I’ve just uploaded it).

-Timofey.

2 Likes

Another option to retain the existing motion planner could be to implement an encoder-based crash detection. It should be relatively straightforward to detect if an error is too large (layer shift), briefly pause and correct the position, then continue with the print. Note that there could be a delay of ~20s or so from the time the error is detected to when it is corrected due to the motion queue.

1 Like

Above video does basically same thing (there is just tricks, instead of pause/rehome).
JFYI: Generally, delay is 1s, was 2s before.

-Timofey

1 Like

Wow, @MRX8024’s video you shared appears very relevant to what I had in mind for adapting my project to Klipper. Would it be possible for you to share the code with me?

The project I’m working on is rather specific; it’s about actuating opto-mechanical components (specifically the KM100 and polaris mirror mounts from Thorlabs ) in an automated fashion. I.e., you can have a computer send a few G-code commands through something like Serial Python or, in Klippers ’ case, Moonraker to actuate a mirror in your experimental apparatus. The problem with an open-loop system is that it effectively reduces the resolution you can achieve, since even with microstepping, you would be lucky to achieve 9e-1 degrees of effective angular resolution in your movements with standard 1.8-degree steppers. So I had to gear the stepper motor I’m using; specifically, we used the DFRobot FIT503, but that introduces hysteresis and backlash. The AS5147Us I’ve tested can achieve angular resolution down to 4e-2 degrees and accuracy of roughly 8e-2 degrees (where, if you repeat the movements over a long period of time, you’ll end up with a drift within that range, per se).

This is the linearity data I achieved with the closed-loop system I built using the custom board I made. It essentially shows that the closed-loop attachment my group and I built is performing as expected, with little to no headroom for improvement, as we approach the mechanical stability of the mirror mount rather than our ability to move the knobs.

I know that the solution to skipped steps in the 3d printer world is often just increasing the motor current, but you run into two problems: the angular resolution problem I’ve mentioned previously (especially with geared motors), and the fact that the motors would run hot, expanding the metals that comprise my optics mounts, which would make thermal expansion an issue.

I am very willing to try and get some form of Klipper working, so this project would be easier to replicate for other groups, as it would be more standardized consumer hardware that is consistently updated. Along with Klipper’s large user base, where undergraduate research students may have built their own 3D printers with the software, making the whole endeavour more reproducible.

As you’ve indicated elsewhere, I’ve played with this a little bit over the years. I think it is certainly possible to implement a closed loop feedback system within Klipper. However, the complexity and benefits vary notably depending on the requirements - in particular how fast does the system need to respond to sensor inputs and what is the ultimate goal of the closed loop system.

If one is basing the work on the Klipper software then the desired sensor to motor latency plays an important role on where one would implement it. I’ve discussed this briefly at Fast closed-loop controllers on the MCU? - #2 by koconnor .

Ultimately, I came to the conclusion that “closed loop” control over typical 3d printer stepper motors would not be worthwhile (as mentioned briefly at Mechaduino experiment ). The issue I ran into is: the implementation would be complex and there is a high risk it could produce lower quality prints. In particular, the time it would take for a sensor to detect a positional error and move the motors to correct it would take too long to avoid a visible blemish from the initial positional error. And then one would ultimately have two surface blemishes - the blemish resulting from the original positional deviation and the subsequent blemish resulting from the motors moving back to their ideal position. I concluded this effect would likely cause “salmon skin” effects on the surfaces of prints.

That said, closed loop control might very well be useful for other motors (eg, your position control of mirrors).

Well, the rp2350, tmc2209, and as5147d are all supported in Klipper today (I don’t know how much the as5147u differs from the as5147d). If you know a little bit of python coding you could try wiring up a simple host based control loop just to see if it works. It’d likely be slow, but might get you some initial results to inform future plans.

Maybe that helps a little,
-Kevin

EDIT: Just to be clear, I concluded that a traditional PID type closed-loop control of 3d printer XYZ stepper motors would not be worthwhile. A non-traditional “closed loop” approach, such as using sensor input to tweak future movements in the hope of avoiding future positional errors, might very well be worthwhile.

1 Like

IIRC, it is: klippy-modules/position_tracker.py at main · MRX8024/klippy-modules · GitHub

Just my 2 cents.

Microstepping should be able to provide slightly more than 0.9 degrees.
You are talking about:

1.8 / 32 = 0.05625
1.8 / 64 = 0.028125

Basically, I think, the limitation here is the encoder’s effective resolution and the driver.
Saying that, in other words, it should be possible to step several times in a different direction, until your encoder position match position you wanted it to.

If there is a will to be fancy, it is possible to do either:

  • TMCs like 2240 can drive specified current into the motor by DIRECT_MODE register. So, drive coils by hand.
  • Or use external H-Bridge drivers like here: https://www.youtube.com/watch?v=MgQbPdiuUTw
    Generally, it seems to me, this is as far as you can get. (Clever tricks and 1us of resolution).

So, technically, you should be able to fine-adjust and point it as you wish.

I spent quite a time around microstepping (TMC Adaptive Microstep Table - #31 by nefelim4ag).

Hope that helps a little.
-Timofey

1 Like

Keep in mind that my “closed loop” implementation is quite messy and was written just for fun. It’s deprecated because it was written before the motion_queue module and some other changes. Furthermore, it won’t work during other motions, because I’m brazenly switching the stepper kinematics and adjusting their position, during which movements from the normal kinematics are skipped (or stepcompress will throw an error, I don’t remember anymore). The true purpose of this module was and is to stop the printer or execute a gcode command when a stepper step is missed. You can of course use this code as a reference, but the main thing is not to get confused by extra/old code that I didn’t remove.

1 Like

I think this will be the approach I end up taking; it sounds the least invasive. Yet, I wonder if it would ever be possible to modify the motion planner to inject corrective steps when the angular encoder detects a mismatch while moving. Though I haven’t looked too closely at that portion of the code, I’ve been trying to write the basic kinematics for these systems between classes.

Luckily, the AS5147U and AS5047D don’t differ much, so I was able to make minimal modifications to both the klippy/extras/angle.py and /src/sensor_angle.c to get the sensor working.

I’ve made a fork that is public, GitHub - braverman-lab/optics_klipper: Klipper is a 3d-printer firmware for those who are curious.

I was able to get this working as well, luckily, with a simple PID script outside the repo I linked. But I think I will proceed for the time being, making some klippy/extras servo class. But considering how slowly a PID system moved to the correct position in Python with the eval script I made, I think I will just try to do this all by issuing corrective steps. I just hope I don’t have to mess with the motion planner to get satisfactory results.

Thank you all for all the help and suggestions!! You guys are fantastic!!!

All the best,

  • Andrew