Generic analog endstops

Hi everyone!

I’ve been working on this for my own usage in my spare time, curious if there’s any interest for this feature in the community.

The idea is to provide a configuration to define a “virtual” endstop by a state of an ADC pin (crossing some threshold value). Here’s an example of what I’m using right now:

[adc_endstop probe]
pin: PC2
threshold: 0.14

pin: adc_endstop:probe

My use case is as piezo element in the extruder assembly, connected directly to the board running Klipper. The general idea is similar to and , but with no additional hardware so all processing done on the Klipper mcu. Seems to be working great for me so far, getting about 30 micron precision with a very janky setup (piezo just tucked in under the extruder) and unfinished code


Sounds like a brilliant idea. If we can detect when the extruder has touched something it can be used for bed mesh / screw adjust etc. It should be a lot cheaper than all the various probes even it is not quite as accurate. Should be good for just hobbyists like me.

Just musing here, if the system reacts quick enough, it may even help prevent crashes into the model lifting it from the bed, although it may be difficult to differentiate between drag on the extruder during normal use and the extra force of hitting the model. One sensor may be sufficient if it triggers on any unexpected force while not extruding but also triggers if force is unexpectedly high during extrusion.


Ping @garethky and @koconnor

This is something that had crossed my mind in recent monnths as well. I was working with piezo probes at the time and ended up implementing an rp2040 as a separate mcu that triggered a pin.

Having adc_endstop would make it easy to allow this board to beceme part of the can network of the printer…

I did find that overclocking the rp2040 I used gave better measurements. I used the Tap-xxx mass dampened piezo mounted to the side of the bed, so needing extra sensitivity wouldn’t be as much of an issue for probes mounted on the hotend I imagine…


A separate rp2040 was exactly what I went with at first as well!

The overclocking part raises an interesting concern. If the freq of the mcu seems to be a bottleneck even for a dedicated mcu, it makes me wonder how much of a performance overhead doing it on the main mcu of the printer might be. Seems ok in my specific case, but might be a problem on slower chips or more involved setups

1 Like

Can you explain what you mean by “better measurements” when you overclock (and, how much are you overclocking)? ADCs can be fickle beasts when you play around with clocking.

Looking at the datasheet, the RP2040’s ADC is of the Successive Approximation Register type which has somewhat more leeway in terms of clocking than other methods (ie Sigmoid-Delta) BUT it is clocked using the 48MHz USB clock which means that while you can speed up the processor core, I don’t think you can do a lot with the ADC itself (unless you’re okay with losing USB operation).

As @nosock pointed out, if you have to devote so much of the system’s resources to this single function then you are limited in what you can do with MCU in the printer itself.

Doing a bit of looking around on the subject of RP2040 overclocking, I did find this:


For what it is worth, I’m not sure a “generic analog endstop” is possible in the “generic” sense. Most commonly available ADC sensors do not have a sufficiently high sampling rate to be used effectively as an endstop. So, it may be possible to use one in a specific context, but it seems unlikely one could use an arbitrary ADC in a generic sense.

The loadcell code (as cited above) goes through a specific series of movements and performs an analysis on the data, in part, to compensate for the ADC frequencies. Instead of making a “generic analog endstops” it may be interesting to see if your setup could be extended to use that loadcell code. Said another way, maybe we can support more generic loadcell/piezo code than generic analog endstop code.

Just some high-level thoughts.


1 Like

+1 Maybe there are some ways that the techniques that the load cell code is using can be applied to other probing methods.

There was a discussion around the challenges of using the Pi Pico starting here. The 12 bit sensor is a real limitation. It’s more like a 9 bit sensor once you consider noise.

Fair enough! In my understanding however, endstop accuracy would be a function of both the sampling rate and the homing speed. From a quick search, it seems like most MCUs supported by Klipper have sampling speeds in the tens of kSps at least (AVR has one of the lowest rates at 15kSps). Even with 10x oversampling that still leaves over 1k values per second. Homing at 10mm/s, we get a (very theoretical!) precision of 10 micron, which seems at least passable. From there, it’s just a balancing game of precision vs homing speed, which I’d argue comes down to a specific setup.

That would be another way to go around this for sure! From a brief look at the loadcell implementation, it looks like piezo vs loadcell are pretty different conceptually, so I’d say it might be easier to implement a similar approach for piezos as a separate thing altogether.

However, a big focus for me was to try and do this with no additional hardware needed, and in the most generic way possible. A theoretical advantage to this would be potential support of all kinds of sensing methods - strain gauges, capacitive/inductive sensors, HAL sensors etc.

Yeah, that makes sense. To continue the trend of using AVR as a common lowest denominator, those only have a 10bit ADC, making them even less precise. It still seems ok for my specific situation, but can absolutely be not enough for other cases. Piezos output a relatively high-voltage signal on deformation, so even mounted on the side of the extruder and essentially measuring vibration on touch I’m getting a spike of about 0.01 of the scaled ADC value, which should be reasonably detectable with as few as 8 bit (?)

With all that said, I’m quite new to all of this and coming from a very unrelated background. Please feel free to challenge any of my assumptions here!

I didn’t overclock it a ton. I think I went to 150 or 175 MHz.

I took a lot of inspiration from RepRap Forum Post and Tap-XXX Video. I also looked at the tap-attiny. Both of those projects set a sensitivity threshold above which the mcu triggers.

What I found by overclocking was that I was able to raise that sensitivity threshold while still getting triggered, but weeding out a little more noise (a belt that creaks in my case).

Both of the other projects used much smaller mcus but I had the 2040 laying around so I gave it a spin. It works pretty well. I have the probe gcode set to drop the acceleration way down which was essential. I also found I got great measurements when the probe is on the bed, and less so when it was placed in other areas… I think that observation was what led me to thinking about the possibility of adc_endstop… That if I could compile klipper as usual and have it on the rp2040 running my probe, I would be able to communicate with it via can.

Tuning the probe was challenging, and involved reflashing quite a bit. It made me contemplate the possibility that the trigger threshold could be set in the front end (like setting a temperature in Fluidd)… While I was tuning the threshold, I was logging it via serial (I implemented the probe via an Arduino sketch), but I also found that removing my logging code drastically improved the probe.

I’m not sure of the deep internals of the rp2040 adc, but it seems like the peak of the piezo trigger is so brief, that by operating at a higher clock frequency, it has a higher probability of making a measurement closer to the peak, meaning a higher trigger threshold can be used.

What do you mean by “reflashing”? Are you writing your own code, modifying the Klipper firmware or changing the menuconfig? You’re not clear on this. How are you maintaining the 48MHz clock for the ADC?

Along with that, what do you mean that you were able to “raise that sensitivity threshold” by overclocking? Are you apparently getting more accuracy (ie seeing less noise on the least significant bits) or increasing the samples per second your taking so you have a clearer image of what’s happening.

If you have an oscilloscope, I’d be interested in seeing the waveform of the output of your piezo sensor.

I find this a very interesting thread although I have a different take on some of the points brought up in the discussion.

The ADCs on most MCUs are a very poor match for direct input from a piezo element without some preconditioning. This is not because of insensitivity of piezo elements, but because a slow contact will fail to give a sufficient signal - e.g., plastic or other detritis on the nozzle or bed.

Neither the sampling frequency nor the resolution of the ADC have to be particularly high. I use a 10-bit ADC on a PIC microcontroller sampled at 4000 samples per second to get repeatability and sensitivity of the order of 3 microns and 1 gram.

Some background to the above:-

Using an analog input does have another use though. The rate of rise of a signal can show how good the nozzle contact was and thus if there is anything between the nozzle and the bed.


I don’t have an oscilloscope. I wrote the sensor as an Arduino sketch that runs on the RP2040. It’s very heavily influenced by this tap-attiny sketch.

#include <Adafruit_NeoPixel.h>
#include <Arduino.h>

int Power = 11;
int Neo_PIN = 12;

#define NUMPIXELS 1
#define THRESHOLD 70
#define DELAY 100

Adafruit_NeoPixel pixels(NUMPIXELS, Neo_PIN, NEO_GRB + NEO_KHZ800);

const int piezoPin = A0;
const int outPin = D10;

void setup()
  // Setup input
  pinMode(piezoPin, INPUT);

  // Configure outpin and bring low (inactive/normally closed).
  pinMode(outPin, OUTPUT);

  // Configure neopixel
  pinMode(Power, OUTPUT);
  digitalWrite(Power, HIGH);

void loop()
  if (analogRead(piezoPin) > THRESHOLD)
    // reconfigure outpin to float (high-Z/active)
    pinMode(outPin, INPUT);

    // flash pixel orange
    pixels.setPixelColor(0, pixels.Color(150, 15, 0));;

    // Delay to allow to settle

    // Configure outpin and bring low (inactive)
    // The default state is low, so it's not necessary to set it explicitly.
    pinMode(outPin, OUTPUT);

    // Turn off LED

I was curious about the analogRead() function, and I referenced this article, which seemed to indicate that it was mostly unaffected by compiler optimization level.

By ‘raising the sensitivity threshold’, I mean the #define THRESHOLD 70. It’s been a couple of months since I was working with it, but I think I went from conservatively low 50 something to 70, maybe 40 something even.

@leadinglights I read a lot of your work from the past several years as I explored piezo probing. Big thanks for all the thought provoking reflection and commentary you’ve made on the topic through the years!

Instead of using an ADC for this, could I suggest that you look at using a comparator that can have the test value being an ADC output, like the one on the STM32 (and, really all ARM based MCUs):

If you’re just waiting for a specific voltage level, this approach is a lot less software intensive and can provide much faster yes/no confirmation rather than continually polling an ADC.

Thank you so much for your input @leadinglights ! Big fan of your work on the topic! Didn’t realize how much of what I had been referencing comes back to you :grin:I was also not aware of the full-on paper, great read! A lot of important consideration I hadn’t thought about at all!

I’m really intrigued by the detritis situation. I’ve been operating on the assumption that any amount of debris on the nozzle and/or the bed flat out invalidates the probing result. That might be a function of me homing with cold nozzle and bed to avoid damaging the bed surface.

@mykepredko I wasn’t aware that these were available on-chip! Would be extremely interesting to look into that! Will try to play around over the weekend :grin:

Sorry, they’re not on the RP2040 but they are on the STM32s. Just FYI.

The image above is from the STM32G datasheet.

Thanks for making sure, I didn’t expect to find them on an RP2040! (although them being missing there is a bit a of a bummer).

Just to clarify, my main printer runs on an SKR Mini E3 V2.0 which uses a (knockoff) stm32, paired with a Rapberry Pi Zero as a host machine. I was only using an rp2040 (in the form of a Seeed Studio XIAO) as an external MCU for the first proof-of-concept stages, trying to figure out where I could stick the piezo for it to actually work :grin:

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.