Is there a way to read a PWM signal from the pins on Raspberry Pi?
I tried googling it, but I only found PWM output, and not input.
Is there a way to read a PWM signal from the pins on Raspberry Pi?
I tried googling it, but I only found PWM output, and not input.
The short answer: You CANNOT reliably read PWM on Raspberry Pi.
Reading PWM requires microsecond precision (unless you're reading a very-very slow PWM), and that is not available on Raspberry Pi for userland software without tinkering with kernel modules.
The easiest way to capture PWM would be to get any cheap (< $0.5) microcontroller with serial or I2C output and hook it to your Raspberry Pi and read the actual values from the microcontroller. This will work very reliably and is quite precise.
I can do fairly accurate pulse width measurement using the piGpio C library: http://abyz.me.uk/rpi/pigpio/index.html
This library enables you to install a callback function that will trigger on any edge transition on a gpio pin and gives you a microsecond level timestamp for each transition. Don't think you can count on this for microsecond accuracy - but my testing suggests that accuracy is at least +/- 10us, maybe better.
Lot better than running a busy loop polling a gpio for the level change yourself.
The long answer: You actually can! (well with a little help from our friends resistor and capacitor)
You can convert a PWM output to an analog voltage level, (DAC) and read it with ADC pin on your raspberry pi.
What you need is a 4k7 resistor and 0.1uF capacitor:
simulate this circuit – Schematic created using CircuitLab
The simple RC low-pass filter above converts the PWM signal to a voltage proportional to the duty cycle which can be read by your raspberry pi as an analog value.
This is an interesting question and one that your are correct in saying Google Search does not provide an obvious solution! (I miss the days when Google could answer anything I wanted to know for my education / assignments within seconds.)
I'm assuming you understand the principles of PWM. Therefore, I will not go into that. However, I believe you could in theory read a PWM value on a regular digital input pin with some clever coding.
I will admit I haven't tried this myself, but you should be able to measure the time that the pin is high and the time for which it is low (giving you your PWM reading) and then use whatever mathematical formula the sensor's supplier provided to convert this to the actual reading.
This method works for me on a similar problem where I needed to read the pulse length from an ultrasonic module and then convert it to distance. The problems I can envisage involve ensuring reliable readings!
If you think it will help and want to see the code I used for the ultrasonic module just say so, and I'll copy it on when I get home.
I started copying the code but for some reason the website only lets me copy it a small section at a time (and i'm too lazy to get my pi out of the garage) so here is the link to it. ignore most of the functions at the bottom as they are related to using the module as a proximity sensor. http://pibot.webnode.com/products/ultrasonic-range-sensor/
If you are happy with a slow response, you can read a fast PWM by undersampling. Simply read the GPIO in a loop and apply a low pass filter. The probability of reading a 1 each cycle is proportional to the pulse width. An easy to implement IIR low pass filter is:
double acc=0.5;
const double k=0.01;
for(;;) {
bool x=GPIO.read();
acc+=k*(x?1:0-acc);
}
As k decreases, the resolution improves but the bandwidth decreases.
Although my answer is not from the pins, you could use something based on a Sound Card Oscilloscope to read a pulsed input.
People have been using Sound Cards in desktop PCs for years to create oscilloscopes. It appears that with a modern internal sound card you can get usable results up to 10kHz. With a Raspberry Pi USB connected Sound Card, your max frequency might be lower.
Here's an example of one Sound Card Oscilloscope project for Linux: http://www.yann.com/en/diy-turn-your-gnulinux-computer-into-a-free-oscilloscope-29/09/2010.html
This python script I wrote works fine for me for reading PWM signals of a RC Receiver. High frequency PWM signals obviously won't work as it has been pointed out already.
I directly connected the ten signal out pins of the RC receiver to the Raspberry GPIO pins. The receiver is powered by the +5V and GND pins from the RPI.
I simplified the script as it does lots of other stuff, if you find any mistakes or leftovers, let me know
import RPi.GPIO as GPIO
import time
import numpy as np
inPINS = [2,3,4,14,15,18,17,27,22,23]
smoothingWindowLength=4
def getTimex():
return time.time()
GPIO.setup(inPINS, GPIO.IN)
upTimes = [[0] for i in range(len(inPINS))]
downTimes = [[0] for i in range(len(inPINS))]
deltaTimes = [[0] for i in range(len(inPINS))]
def my_callback1(channel):
i = inPINS.index(channel)
v = GPIO.input(inPINS[i])
#GPIO.output(outPINS[0], v) # mirror input state to output state directly (forward servo value only) - don't set PWM then for this pin
if (v==0):
downTimes[i].append(getTimex())
if len(downTimes[i])>smoothingWindowLength: del downTimes[i][0]
else:
upTimes[i].append(getTimex())
if len(upTimes[i])>smoothingWindowLength: del upTimes[i][0]
deltaTimes[i].append( (downTimes[i][-1]-upTimes[i][-2])/(upTimes[i][-1]-downTimes[i][-1]) )
if len(deltaTimes[i])>smoothingWindowLength: del deltaTimes[i][0]
GPIO.add_event_detect(inPINS[0], GPIO.BOTH, callback=my_callback1)
GPIO.add_event_detect(inPINS[1], GPIO.BOTH, callback=my_callback1)
try:
while True:
ovl = deltaTimes[0][-smoothingWindowLength:] # output first pin PWM
ov = sorted(ovl)[len(ovl) // 2] #ov = np.mean(ovl)
print ov
time.sleep(0.1)
except KeyboardInterrupt:
GPIO.cleanup()
It's very possible and relatively easy to read PWM inputs on Raspberry Pi using the pigpio C library. If you want good performance I recommend using C, not Python. I've provided some short sample code below. Contrary to what some people say this has excellent timing performance and quite low jitter. Readings are consistently within 5 us on my RPi 3 B and it can measure pulses as short as 5 us. Note the provided code is proof of concept only, it doesn't properly handle absence of pulses (0% / 100% duty cycle) or the 'tick' wraparound which occurs every 72 minutes. The program runs just fine in user mode but for best resistance to timing glitches run your program at negative nice level like this: sudo nice -n -20 ./program
See the pigpio docs at: http://abyz.me.uk/rpi/pigpio/pdif2.html
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include "pigpiod_if2.h"
static uint32_t rise_tick = 0; // Pulse rise time tick value
static uint32_t pulse_width = 0; // Last measured pulse width (us)
// Callback function for measuring PWM input
void pwm_cbfunc(int pi, unsigned user_gpio, unsigned level, uint32_t tick) {
if (level == 1) { // rising edge
rise_tick = tick;
}
else if (level == 0) { // falling edge
pulse_width = tick - rise_tick; // TODO: Handle 72 min wrap-around
}
}
int main(int argc, char **argv)
{
const unsigned int pwm_in = 23; // GPIO Pin # for PWM in, change as reqd
int pi = pigpio_start(0, 0);
if (pi < 0) {
fprintf(stderr, "pigpio initialization failed (%d)\n", pi);
return pi;
}
// Set up callback for PWM input
callback(pi, pwm_in, EITHER_EDGE, pwm_cbfunc);
while (true) {
printf("PWM pulse width: %u\n", pulse_width);
usleep(500000);
}
}
Easy Solution with high accuracy:
Using an Arduino as iic slave or UART device seems to work perfectly fine. The microcontoller is capable of reading the information via the pulseIn method.
Fore detailed information: https://www.youtube.com/watch?v=ncBDvcbY1l4