Reading Analogue Sensors With One GPIO Pin

22

Unlike some other devices the Raspberry Pi does not have any analogue inputs. All 17 of its GPIO pins are digital. They can output high and low levels or read high and low levels. This is great for sensors that provide a digital input to the Pi but not so great if you want to use a sensor that doesn’t.

For sensors that act as a variable resistor such as LDRs (Light Dependent Resistors) or thermistors (temperature sensors) there is a simple solution. It allows you to measure a number of levels using a single GPIO pin. In the case of a light sensor  this allows you to measure different light levels.

It uses a basic “RC” charging circuit (Wikipedia Article) which is often used as an introduction to electronics. In this circuit you place a Resistor in series with a Capacitor. When a voltage is applied across these components the voltage across the capacitor rises. The time it takes for the voltage to reach 63% of the maximum is equal to the resistance multiplied by the capacitance. When using a Light Dependent resistor this time will be proportional to the light level. This time is called the time constant :

t = RC

where t is time,
R is resistance (ohms)
and C is capacitance (farads)

So the trick is to time how long it takes a point in the circuit the reach a voltage that is great enough to register as a “High” on a GPIO pin. This voltage is approximatey 2 volts, which is close enough to 63% of 3.3V for my liking. So the time it takes the circuit to change a GPIO input from Low to High is equal to ‘t’.

With a 10Kohm resistor and a 1uF capacitor t is equal to 10 milliseconds. In the dark our LDR may have a resistance of 1Mohm which would give a time of 1 second. You can calculate other values using an online time constant calculator.

In order to guarantee there is always some resistance between 3.3V and the GPIO pin I inserted a 2.2Kohm resistor in series with the LDR.

Here is the circuit :

Here is the circuit implemented on a breadboard :

Theory

Here is the sequence of events :

  • Set the GPIO pin as an output and set it Low. This discharges any charge in the capacitor and ensures that both sides of the capacitor are 0V.
  • Set the GPIO pin as an input. This starts a flow of current through the resistors and through the capacitor to ground. The voltage across the capacitor starts to rise. The time it takes is proportional to the resistance of the LDR.
  • Monitor the GPIO pin and read its value. Increment a counter while we wait.
  • At some point the capacitor voltage will increase enough to be considered as a High by the GPIO pin (approx 2v). The time taken is proportional to the light level seen by the LDR.
  • Set the GPIO pin as an output and repeat the process as required.

Python Code

Here is some code that will print out the number of loops it takes for the capacitor to charge.

#!/usr/local/bin/python

# Reading an analogue sensor with
# a single GPIO pin

# Author : Matt Hawkins
# Distribution : Raspbian
# Python : 2.7
# GPIO   : RPi.GPIO v3.1.0a

import RPi.GPIO as GPIO, time

# Tell the GPIO library to use
# Broadcom GPIO references
GPIO.setmode(GPIO.BCM)

# Define function to measure charge time
def RCtime (PiPin):
  measurement = 0
  # Discharge capacitor
  GPIO.setup(PiPin, GPIO.OUT)
  GPIO.output(PiPin, GPIO.LOW)
  time.sleep(0.1)

  GPIO.setup(PiPin, GPIO.IN)
  # Count loops until voltage across
  # capacitor reads high on GPIO
  while (GPIO.input(PiPin) == GPIO.LOW):
    measurement += 1

  return measurement

# Main program loop
while True:
  print RCtime(4) # Measure timing using GPIO4

Accuracy

Given we only want to spot different light levels we don’t really need to know the resistance of the LDR or the exact time it takes to charge the capacitor. You can do the maths if you want to but I just needed to get a measurement and compare it to some known values. Seconds or Python loop counts, it doesn’t matter.

Python is an interpreted language which means the timing of loops is always going to be affected by the operating system performing other background tasks. This will affect the count loop in our example.

Practical Uses

In a more useful application you can call “RCtime” when you need it to get a count value. Your code can then perform other tasks based on the value of the count, perhaps comparing to values you have measured previously.

When I built my test circuit it was sat in front on my TV. I could see the count number change as the lighting level changed on the TV. It’s so simple you really need to just give it a try!

Acknowlegments

This article was inspired by the excellent article on Adafruit.com.

Share.

22 Comments

    • Excellent. Just imported the library into my Fritzing installation. I like the parts for the PIR and membrane keypad. Will definitely be able to make use of the PIR part in future projects. I like the Raspberry Pi part but wish the pin labels were GPIO numbers rather than the alternative function labels.

  1. I made some small changes to the code:


    from datetime import datetime


    GPIO.setup(PiPin, GPIO.IN)
    # Count loops until voltage across
    # capacitor reads high on GPIO
    startTime=dateTime.now()
    while (GPIO.input(PiPin) == GPIO.LOW):
    measurement += 1
    endTime=dateTime.now()
    diffTime=endTime-startTime
    return str(diffTime.total_seconds()/0.001)

    I figured that would give me the time in diffTime, and I know the capacitance is 1uF, so I should be returning the resistance. I put a static 22ohm resister in the circuit as a test, and I get nothing but zeros. If I crank it up to

    return str(diffTime.total_seconds()/0.000001)

    I start getting values, but in the range of 120, not 220 like I’d expect. I know timing isn’t going to be perfect, but I’d expect it to be more accurate than to be always about 100 ohms off. Am I doing something wrong?

    Thanks
    Rick

    • In your example, with a 1uF capacitor, and multiplying the time by 1000000 I would expect the result to be 22. As it is 120 I think this just shows a delay in measuring the small time interval.

      Because there is so much going on within the Linux operating system you are probably just seeing the inaccuracy of the technique. I just count loops because it gives me a number to compare without worrying about the exact meaning of the number. With the same light level I see at least a 10%-20% jumping about in the measurement. If anything loads the system at the same time this can be even worse.

      Stick in a 20K resistor and your results will probably be a bit closer to the predicted values.

  2. Pingback: Playing with GPIO and sensors on OpenWrt | #labOS

  3. Hi,

    Interesting article. I wonder if anyone used this approach to measure distance with sharp IR sensor on Pi?
    I get number of counts and from datasheet I can see that it takes ~38 msec to get one sample. So time is known, but I can’t get distance with that input.

    Any help or suggestion is appreciated.
    thanks

  4. Seems like a “dangerous” solution to me. Can the GPIO pin handle the inrush current created when discharging the capacitor which would have to rather large in order to get any precision in time measurement? I wouldn’t try this myself without some interfacing electronics between the Pi and the capacitor.

  5. Pingback: Why i chose Raspberry instead of Arduino Yun and Spark-Core in the end | Making connected stuff

  6. Thanks for your tutorial! Is a 3.3 uF capacitor ok to use for the circuit? I don’t have a 1 uF capacitor available yet. I hope it does not cause something like short circuit…

    • That would be OK. It will just change the timing calculations as the time constant will be based on 3.3uF rather than 1uF.

  7. Pingback: Experiment: use an arduino as a slave to your raspberry pi | Project Pi

  8. Great tutorial. I used this technique to monitor two different LDRs to notify me if I had accidentally left lights on in two outbuildings. Just counted number of loops; low number meant light was on, high number – lights off. If either light was on, I put power out on one of the GPIO pins to turn on an LED in the main house. Slick!

  9. Great Article!

    I have been working off of the same Adafruit article for a Light Detection system of my own.

    I have one question id like to ask. How would one go about scaling this up? How do you go about adding more light sensors in to allow for say, a 3×3 grid? And beyond?

    Apologies for the entry level questions!

    • To be honest this technique probably isn’t the way to go if you’ve got lots of sensors. You would have to poll each one and wait for the result and this would start to add a larger delay to your script. I would add a MCP3008 ADC and read the analogue outputs of the light sensor. It would be quicker and more accurate.

  10. “With a 10Kohm resistor and a 1uF capacitor t is equal to 1 millisecond. In the dark the LDR may have a resistance of 10Mohm which would give a time of 1 second.”

    Don’t you mean :
    “With a (1Kohm) resistor and a 1uF capacitor t is equal to 1 millisecond. In the dark the LDR may have a resistance of (1Mohm) which would give a time of 1 second.”
    There is like 1 zero added in your statement 😀
    Excellent tutorial Mate (Y)

    • There’s no reason it shouldn’t work on a Pi 2. It’s just measuring the time taken for a GPIO pin to go high so I would expect it to work on all models.

Leave A Reply