I’m working on integrating a reflectivity sensor (phototransistor-based) with my Manta M8P. My goal is to capture live analog voltage readings (0–3.3 V) from the sensor so I can use it to trigger a roller to spin (which I have configured as an extruder) when a reflective aluminium surface passes under it.
I’ve been trying to leverage Klipper’s analog-capable pins, similar to how thermistor pins are used. Specifically, I was hoping to read the sensor voltage using QUERY_ADC or a similar mechanism.
The problem I’m running into:
When I try QUERY_ADC or use the “temperature_sensor” interface, I don’t get the raw voltage; I only get processed values or timeouts.
Accessing raw ADC values directly doesn’t seem to be supported out-of-the-box, and the existing temperature sensor framework is oriented around thermistor readings rather than arbitrary analog voltages.
Manual tests via Moonraker show null or unexpected values, and polling the ADC from Python doesn’t return actual voltages.
What I’m trying to figure out:
What is the best way to capture raw ADC values from an analog pin on an STM32 MCU in Klipper?
Is there a recommended approach for creating a custom sensor that behaves like a thermistor but returns raw voltage values from 0–3.3 V?
Has anyone successfully repurposed an analog-capable pin for this kind of application?
I’d really appreciate any guidance on how to approach this — whether it involves firmware changes, custom modules in extras/, or another method entirely.
I think we have [adc_temperature]section which uses linear interpolation between points, so by defining points like 0/0 and 3.3/3.3 should give you the reading.
But not sure if it will help if QUERY_ADC doesn’t get you expected results.
You may also need to check for electronics issues. If you just connected one pin of phototransistor to 3.3v and the other to ADC, that probably won’t work due to ADC high input resistance.
Do you really need “raw ADC values” or do you actually need ADC values that indicate the light level shining on the phototransistor?
I use the adc_temperature statement with a couple of resistors to monitor the input voltage going into a controller board as detailed here:
This is being used as a basis for the functional test suite I’ve been working on to check the input voltage values and, more importantly, the ADC circuitry on the controller board.
Using a macro, I can test the voltage values (which are derived from the measured ADC voltages in the adc_temperature statement and respond accordingly:
It may take a bit of work to determine the conversion value but it works very well and, in my case, the “temperature” values accurately correspond to the actual voltages input into the board.
Thanks so much for sharing this example. I was trying to use this interpolation method but was getting slightly off ADC values for the sensor from the corresponding temperature recordings. I only require the ADC values to indicate the level of shining correct so I’m hoping I can either get the default adc_temperature function in Klipper working or I was even thinking to create my own adv_voltage.py function in the backend to skip the interpolation of the ADC values to temperature and just directly extract the ADC values from the phototransistor.
In my attempt at this so far, I have managed to get Klipper to recognise a custom object named adc_voltage_sensor blister_sensor, meaning the [adc_voltage my_sensor] section in my Klipper config gets parsed. However, when I got the API output for this object, I could see that Klipper is actually not publishing any value field (unlike [temperature_sensor], which publishes a "temperature" value. I got this: {“result”: {“eventtime”: …, “status”: {“adc_voltage_sensor blister_sensor”: {}}}}
I must have not correctly linked my adc_voltage.py to Klipper’s backend’s “sensor data publication” system yet. I need to find a way to expose the captured ADC values to the Klipper host API by registering an output field (like "voltage").
I could upload the modified code if you want but I was not sure if it falls beyond the scope of this forum. I may have to revert to just using the inbuilt adc_temperature method again and get it accurate this time round.
By all means, post your code here. I’m in no way an expert on it but I’m sure the people who are (and maintain the code bases) will have some useful comments.
So this is the adc_voltage_sensor.py custom code I compiled and have included in klippy/extras, which was inspired by the adc_temperature.py code. I have ensured that Klipper’s ADC sampling system would call my callback properly registering the object with query_adc. Although, I keep seeing a blank output {} in the API. The sensor appears to exist and is registered, but Moonraker has no valid status to report.
I’m also now having an issue where my Klipper board just crashes out when I try and run my polling function in my GUI. What happens is as soon as polling for this adc value starts. the monitor loses signal and the power LED next to the pi goes solid red. Although, I’m still able to carry out the lighter tasks like homing and position changes as normal. I noticed it started happening when I added query_adc in the adc_voltage.py code although the same issue keeps happening now when I revert to my old code so I don’t know if I have damaged something or if Im polling the mcu too quickly. I tried increasing the sample and report time in my adc_voltage_sensor.py code but no improvement.
I’m honestly so stuck now and may have to just go back to basics with what already exists.
This line isn’t necessary, as Klipper automatically adds the object upon initialization.
If you add this line into _adc_callback(), does anything show up in klippy.log? I would also try setting the poll interval to 0.1 or even 0.5 for testing.
logging.info(f"[adc_voltage_sensor] '{self.name}' on {self.pin} READ = {self.last_voltage:.3f}")
Thanks for the reply. I removed the G-code command that rotates the motor when sensor is triggered for now and the board stopped crashing when I ran the poll. Do you know how I can overcome this? Any tips for this is highly critical to get this working.
I kept the SAMPLE_TIME = 0.001 & REPORT_TIME = 0.300 as default since started getting strange errors like MCU ‘mcu’ shutdown: Rescheduled timer in the past in the logs and Klipper would not reset. This happened when I set reporting time equal to 0.1 and sample time to 0.5, which Klipper’s internal event scheduler appears to not be able keep up with the number or frequency of step pulses it’s being asked to generate in this instance.
Now when I added the logging line into _adc_callback() I do indeed see I am getting ADC values matching what I am measuring on my multimeter although I still get voltage of 0 outputted on my GUI. I realised I had mistakenly put the incorrect object name when call Moonraker’s /printer/objects/query endpoint, I had used voltage_sensor blister_sensor instead of adc_voltage_sensor blister_sensor.
This is the log output showing desired ADC values:
Klipper is pretty picky about asynchronous stepper motion. I suspect you’ll have to acquire the G-Code mutex, which will complicate your code quite a bit as you need to queue your motions then execute them.