Analog sensors on a Raspberry PI

picap.splWhen trying to read a sensor on a Raspberry PI, users will soon notice that unlike the Arduino, the Raspberry only has digital in and output.
That is great if you want to read a digital output sensor, but what to do when you want to read an analog sensor such as an LDR or NTC?
A method that will work is to use “capacitive sensing” by having having your variable resister (e.g. the LDR or NTC) be part of an RC network and read the time necessary for the capacitor to charge or discharge (Also see Adafruit article about this) . If you place a resistor in series with a capacitor and apply a voltage 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 :

τ = RC
where τ is time,
R is resistance (ohms)
and C is capacitance (farads)

RC time

We have to make some approximations though as the digital pin cannot measure 63% of the voltage applied, so we time how long it takes the capacitor to reach a voltage that is great enough to register as a “High” on a GPIO pin. That voltage is approximately 2 volts. 63% of 3.3V is 2.079 Volts. So the time it takes the circuit to change a GPIO input from Low to High is equal to ‘τ’.
In fact, it is not even that important if 63% is reached as long as you are only comparing values and are interested in change rather than in an exact measurement of a resistor or a capacitor.

With a 10kOhm resistor and a 1uF capacitor t is equal to 10 millisecond. A resistor of 1MOhm would give a time of 1 second.
A common NTC with a value of 10 kOhm at 25 degrees therefore would provide a high in 10msec

If you are using an LDR, 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 what the program is doing :

  • Set the GPIO pin as an output and set it LOW. This discharges the capacitor.
  • Set the GPIO pin as an input. Current through the resistor and the capacitor to ground. The voltage across the capacitor starts to rise. The time it takes is proportional to the resistance of the LDR or NTC.
  • Monitor the GPIO pin and read its value. Increment a counter while we wait.
  • When 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 value of the resistor and with an LDR thus to the amount of light falling on it and with an NTC to the ambient temperature.
  • Set the GPIO pin as an output and repeat the process.

Python Code

Here is a code that will print out the number of loops it takes for the capacitor to charge. That number is  proportional to the value of the LDR or NTC

#!/usr/local/bin/python
# Reading an analogue sensor with
# a single GPIO pin

print ("RC time")
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 (RCpin):
  measurement = 0
  # Discharge capacitor
  GPIO.setup(RCpin, GPIO.OUT)
  GPIO.output(RCpin, GPIO.LOW)
  time.sleep(0.1)

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

  return measurement

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

For those unfamiliar with pyton: open a terminal to the system you are running it from, log in as root or as user and then type:

python picap.py
or
sudo python picap.py
or
sudo ./picap.py  (if python is defined in yr environment)

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.

If you would adapt the code to for instance measue the real RC time you will find that the findings will vary a lot. This is because Python in its execution is dependent on the operating system and if that is occupied it will go slower.
In C:

#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main (void) {
  int pin,c;

  printf ("Raspberry Pi wiringPi Capacitor reading \n") ;

  if (wiringPiSetup () == -1)
    exit (1) ;

  for (pin = 0 ; pin < 8 ; ++pin) {
    pinMode (pin, OUTPUT) ;
    digitalWrite (pin, LOW) ;
  }

for (;;) {
  pinMode (1, OUTPUT);
  digitalWrite (1, LOW);
  delay(50);
  c=0;
  pinMode (1, INPUT);
  while (digitalRead(1)==LOW)
    c++;
  printf("%d\n",c);
  delay(100);
}                         

}

Averaging 200 measurements:

#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main (void) {
  int c,x,re[1000],med=0;
  int NUMREADINGS=200;
  printf ("Raspberry Pi wiringPi Capacitor reading \n") ;

  if (wiringPiSetup () == -1)
    exit (1) ;

for (;;) {

  for (x=0;x<NUMREADINGS;x++) {
      pinMode (10, OUTPUT);
      digitalWrite (10, LOW);
      delay(16);
      c=0;
      pinMode (10, INPUT);
      while (digitalRead(10)==LOW)
        c++;
      re[x]=c;
    }
    med=0;    
    for (x=0;x<NUMREADINGS;x++) 
      med+=re[x];
    printf("%d\n",med/NUMREADINGS);
}                             
}