11

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.

9 Answers 9

7

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.

3
  • 1
    Any idea where to get such an IC?
    – Pepijn
    Commented May 27, 2015 at 16:05
  • @Pepijn what you're looking for is an analog-to-digital converter (ADC). A very common one which I use very frequently is the MCP3008 8 channel, 10-bit unit, and another that I have permanently on my unit testing Pi for my Perl software is the ADS1115 4 channel, 16-bit unit. The latter needs a bit of soldering work, the former does not. There are many code examples available for using both of these units (C, Python, Perl etc), so if you don't want/can't write your own, it should be trivial to get going.
    – stevieb
    Commented Feb 3, 2017 at 15:42
  • Any simple microcontroller will do. I used to use Microchip products for this sort of thing. The advent of the Arduino has brought the AVR chips into popularity again. Now I just use a small breakout board containing a microcontroller and whatever life-support the chip needs. My favorite is the Teensy 3.x series.
    – NomadMaker
    Commented Apr 7, 2018 at 15:08
4

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.

2
  • +1 It may be worth noting that the accuracy may vary some based on CPU load (especially the single cores), but otherwise a good answer. :)
    – Jacobm001
    Commented Feb 3, 2017 at 16:32
  • Thanks stevec, this answer is much better than saying "it can't be done". See my separate answer for more information including actual code (it's very short).
    – Georgie
    Commented May 14, 2020 at 0:47
3

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:

schematic

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.

3
  • 6
    The ADC pin? And which would that be?
    – Ghanima
    Commented Aug 9, 2016 at 10:05
  • @Ghanima Well, new Microsoft Lightning driver is in development but serve this purpose quite well ( it is documented on their pages ). For what I've found for BCM2837 it might have a hardware ADC which is on GPIO0 (sadly this one isn't linked to any header pin).. at least they linked hardware pwm channels
    – Quest
    Commented Aug 9, 2016 at 15:56
  • FYI: as I type this in 2021, the RPi headers still have no hardware analog input pin(s). Commented Aug 10, 2021 at 21:55
2

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/

2
  • That will be good to see the code, so I can get a base to start my own code, if you can paste here I'll be very gratefull ^^
    – Caio Keto
    Commented Oct 1, 2013 at 18:13
  • The problem with this suggestion is that the Linux kernel doesn't give you sufficient timing to read typical RC PWM duty cycles (which are typically 1000-2000 microseconds every 16 milliseconds) with sufficient precision. If we could install an interrupt handler for a GPIO pin change, and capture timestamp on high/low transitions, in a kernel module, then this might be a useful solution.
    – Jon Watte
    Commented Aug 14, 2017 at 18:12
0

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.

0

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

0

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()
0

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);
    }
}
-3

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

1
  • Welcome to Raspberry Pi! Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference. We're trying a new policy with regard to informationless link-only answers here. If this post is not edited to contain information that can stand as an answer, however minimal, in 48 hours it will be converted to Community Wiki to simplify having it corrected by the community.
    – Ghanima
    Commented Oct 19, 2016 at 19:31

Not the answer you're looking for? Browse other questions tagged or ask your own question.