Coding Sensors on the rPi3

Raspberry Pi 3 (rPI3) is becoming ubiquitous in the development of embedded-based applications. With a 64 bit 1.2GHz processor and 1GB secondary cache, rPI3 is capable of high volume native software development, also.

In this article, you will learn about various ways in which different kind of sensors can communicate with a Raspberry PI3. Ports in peripherals can be configured for up to six alternative functions (Protocols), excluding using it for GPIO I/O. For analog sensors, an external Analog Digital Converter (ADC) can convert sensor analog signals to a digital signal and communicate with the rPI3 with I2C or SPI protocols. In this article, PCF8591, an I2C based 4-channel, 8-bit ADC is used to communicate with the rPI3. Digital sensors use GPIO input and output for communication. Sensor data is drawn in real time fashion through Qt-based C++ graphs. Graph utilities also can be developed, along with sensor communication.

In this article, you’ll learn the basic coding of using some of the most common sensors on a Raspberry Pi3. In this case, it is an arm BCM2837 64-bit–based 1.2 GHZ Raspberry Pi 3. rPI3 supports interfacing with digital devices only; it does not have any built-in Analog to Digital Converter (ADC). This article primarily covers digital devices interfacing in rPI3 through GPIO i/o protocol. Sensors covered in this article are the following:

  • LED
  • Temperature and Humidity Sensor, DHT11
  • Motion sensor (PIR sensor)
  • Moisture sensor (LM393 based)
  • Light Sensor (LM393 based)

The moisture sensor and light sensor have both digital and analog outputs. In brief, we are also covering interfacing these analog devices through the I2C protocol with rPI3. The words “external device” and “sensor” are used interchangeably in the article.

Aspects of Digital and Analog Communications

How do you make various peripheral devices, including sensors, communicate with the rPI3? How do you have a sensor’s data display in real time? An rPI3 only understands digital {0,1} signals; however, many sensors and other devices can be analog or digital in nature. Devices can be of parallel port (I/O through multiple ports) or serial port (multiplexing data in one or two ports). All of these are protocols that an rPI3 supports, so it can have various modes of communication possible with sensors or external devices.

User Application vs Kernel Device Driver

The rPI3 is a digital board that does not have a built-in Analog Digital Converter (ADC). An rPI3 has a limited set of peripheral ports (pins) that are used to interface with sensors.

There are two ways an rPI3 communicates with external devices: through parallel ports and through serial ports. Parallel port devices communicate through the GPIO I/O protocol whereas serial port devices communicate through SPI, I2c, or custom protocol (DHT11). rPI3 peripherals are memory mapped. Computations to memory-mapped bytes are available through dedicated device drivers (i2c-dev or i2c-bcm2708). To make interface computations more transparent to the user, mapping device memory to virtual address space is done in the user space side (through the mmap API). For analog devices such as a light sensor or moisture sensor, PC8591 I2C-based IC is used. The user space library bcm2835-1.52 is used for I2c communication.

About Raspberry Pi 3

If you are unfamiliar with the hardware and system details of a Raspberry Pi3, you should refer to the article, “Raspberry Pi 3 Hardware and System Software Reference,” here on Codeguru.com. It will give you some of the hardware details of the device. Otherwise, we’ll jump into working with the sensors.

Implementation of Raspberry Pi3 Sensors

Design

To map the memory mapped address to process address space and then start tweaking the bits for various devices, a bridge-mediator hybrid design is proposed as follows:

A bridge-mediator hybrid design
Figure 1: A bridge-mediator hybrid design

In this illustration, mainwindow and rPI3 makes a bridge whereas mainwindow behaves as mediator between the rPI3 derivative and chart. rPI3 is abstract class and further sub classed to abstract classes such asas SPI, I2C, and GP IO. SPI and I2C are sub classed to concrete classes based on ICs or sensors whereas GPIO is sub classed to sensors.

Memory Mapping and Mapping to Process Address Space

As discussed above, rPI3 memory maps 0x7E00_0000 at 0X3F00_0000 in physical memory. Class rPI3 opens (for the root user only) physical memory device file /dev/mem and then maps 0x7E20_0000 to a virtual address space in the volatile address field addr, which is declared static. For light and moisture analog sensors, we have derived from rPI3 and used I2C library from bcm2835-1.52 for I2c communication. Meanwhile, this article is primarily about GPIO interfacing. To show the output on a graph, Qt5.3 (the default on Raspibian) is used. Install Qt5.x as ‘sudo apt-get install qt5-default’.

The Code

The following listings illustrate each class.

Code Listing 1: Class rPI3

Listing 1's flow chart
Figure 2: Listing 1’s flow chart

struct rpi3{
   rpi3();
   virtual ~rpi3();
   c_t _ct;
   int _min1,_max1,_step1,_min2,_max2,_step2;
   /* mapped address in virtual space*/
   static volatile unsigned int *addr;
   virtual int read(void*)=0;
   /*process the read, data will be stored in _ct.y1, _ct.y2*/
   virtual int write(void*)=0;
   /*void* deals with various write requirements in different
    * protocols*/
   virtual int process(void*)=0; /*process the request*/
};

In this listing, ‘rPI3’ declares min and max values for y1 and y2 axis displayed in the chart, step declares resolution in the scale. ‘rPI3’ maps memory mapped address 0x3F20_0000 processes the address space by opening the /dev/mem file and then a mapping file descriptor with the mmap API in shared mode. This class provides three pure virtual functions. read() and write() are implemented as per protocol or sensor, process() typically returns 1, giving a hint for drawing the chart whereas 0 hints for no draw.

Code Listing 2: Class gpio

#define INP_GPIO(g)  *(addr + ((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g)  *(addr + ((g)/10)) |= (1<<(((g)%10)*3))
#define GPIO_SET(g)  *(addr + 7) |= (1<<(g))
#define GPIO_CLR(g)  *(addr + 10) |= (1<<(g))
#define GPIO_READ(g) *(addr + 13) &= (1<<(g))

struct gpio:rpi3{
   gpio(int);
   int pin;
   virtual ~gpio();
   virtual int read(void*);
   virtual int write(void*);
};

int gpio::write(void* c){
   INP_GPIO(pin);
   OUT_GPIO(pin);
   if(*(int*)c){
      GPIO_CLR(pin);
      return 0;
   }else{
      GPIO_SET(pin);
      return 1;
   }
}

INP_GPIO sets selection register GPFSEL 000 after shifting 111 in register (GPIO/10), ((g)%10)*3 times and then inverting to 000, whereas OUT_GPIO sets the selection register 001.

GPIO_SET and GPIO_CLR set the GPSET and GPCLR registers at offset 7 and 10 words from 0x3F20 0000, respectively.

Before calling OUT_GPIO, it is mandatory to call INP_GPIO as it set 000 to 3 bits, then OUT make OR with 001.

pin:int is GPIO pin number class gpio implements read(void*) and write(void*)

read(void*) calls GPIO_READ on the particular GPIO, and accepts void* as an argument to be used in generic fashion, specific to derived classes.

write(void*) calls GPIO_SET and GPIO_CLR on the particular GPIO.

Digital Sensor Classes

Code Listing 3: Class led

struct led:gpio{
   led(int);
   int process(void*);
};
int led::process(void*){
   static timeval st={0,0};
   static int tc=0;
   timeval tv;
   gettimeofday(&tv,0);
   if((tv.tv_sec-st.tv_sec-((tv.tv_usec-st.tv_usec)>0?0:1))>=1){
      st=tv;
      _ct.y1=write((tc=!tc,&tc));
      return 1;
   }
   return 0;
}

Circuit Diagram
Figure 3: Circuit Diagram

The ‘led’ class implements the process(void*):int function and returns 1 when the last time GPIO pin was tweaked is more than 1 sec ago. Otherwise, it returns 0.

Code Listing 4: Class dht11

struct dht11:gpio {
   dht11(int pin);
   ~dht11();
   unsigned wait_until_low();
   unsigned wait_until_high();
   int process(void*);
};

DHT11 is a humidity and temperature sensor. It works on its own protocol.

Considering rPI3 as the master and DHT11 as the slave, it works in the following ways:

  1. Master makes the wire low for 18ms then brings it up, giving the slave a hint to start.
  2. After a pause of 20 to 40 us, the slave brings the line low for 18us, then high for 18 us.
  3. Slave sends 40bits(5 bytes) of data where 0 signifies 50us low followed by 26-28us high and 1 signifies 50us low and 70us high.
  4. The first byte is humidity whereas the third byte is temperature. The sum of the first four bytes are a checksum that is the 5th byte.
  5. Transmission ends with keeping the wire high for more than 50us.
int dht11::process(void* waitp){
   ......
   if((cs & 0xFF)==d[4]){
      if(_ct.y1!=(int)d[2] || _ct.y2!=(int)d[0]){
         _ct.y1=(int)d[2];
         _ct.y2=(int)d[0];
      }else
         throw std::out_of_range("same as before");
   }else
      throw std::out_of_range("data error");
   }
   catch(std::out_of_range& exp){
      trace(exp.what());
      *(int*)wait=5000;/*millisecond*/
      return 0;
   }
   /*Set pin to input mode */
   *(int*)wait=2000;/*millisecond*/
   return 1;
}

In this listing, process(void*):int has been implemented to return 1 when there is a change in either humidity or temperature. Failure causes a 5 sec wait, whereas success makes a 2 sec wait before making the next attempt.

Circuit Diagram
Figure 4: Circuit Diagram

Code Listing 5: Class pir

struct pir:gpio{
   pir(int);
   int process(void*);
};

int pir::process(void* waitp){
   static int intruder=!(GPIO_READ(pin));
   int temp=0;
   if(intruder!=(temp=GPIO_READ(pin))){
      intruder=temp;
      if(intruder){
         _ct.y1=1;
         *(int*)waitp=1000;
      }else{
         _ct.y1=0;
         *(int*)waitp=100;
         return 1;
      }
   }
   return 0;
}

The motion sensor, also known as a Passive InfraRed(PIR) sensor, is based on Infrared technology. It triggers the bcm547 transistor when something is found in its range. Finally, 5V output is generated for a certain duration, typically 15 seconds to 1 minute.

process(void*):int is overridden to wait for 100ms if an intrusion is not detected. Otherwise, it keeps checking every second until the voltage drops.

Circuit Diagram
Figure 5: Circuit Diagram

Code Listing 6: Class ms

struct ms:gpio{
   ms(int);
   int process(void*);
};

int ms::process(void* waitp){
   static int dry=!(GPIO_READ(pin));
   int temp=0;
   if(dry!=(temp=GPIO_READ(pin))){
      dry=temp;
   if(dry)
      _ct.y1=0;
   else
      _ct.y1=1;
      *(int*)waitp=100;
      return 1;
   }
   return 0;
}

ms (moisture sensor) is an lm393 comparator-based sensor that provides both digital and analog output. Digital output is 0 (high moisture) and 1 (no moisture). The threshold can be adjusted in a potentiometer provided on the chip. Analog provides analog output handled through the ‘ams’ class. Class ‘ms’ is responsible for digital data processing and it overrides process(void*):int. It pings the sensor every 100ms.

Circuit Diagram
Figure 6: Circuit Diagram

Code Listing 7: Class ls

struct ls:gpio{
   ls(int);
   int process(void*);
};

int ls::process(void*){
   static int dark=!(GPIO_READ(pin));
   int temp=0;
   if(dark!=(temp=GPIO_READ(pin))){
      dark=temp;
      if(dark)
         _ct.y1=0;
   else
      _ct.y1=1;
      return 1;
   }
   return 0;
}

‘ls'(light sensor) is a lm393 comparator-based sensor that provides both digital and analog output. In digital mode, 0 is bright whereas 1 is dark. The threshold can be adjusted in a fixed potentiometer. Analog output provides output ranging from 0.0 (bright) to 3.3v (dark), managed through the ‘als’ class.

process():int pings every 10ms (default) on success, or every 100ms on failure (default).

Circuit Diagram
Figure 7: Circuit Diagram

Analog Sensor Classes

Code Listing 8: Class ams

struct ams:i2c{
   ams();
   virtual ~ams();
   int process(void*);
};

int ams::process(void* waitp){
   // Trace("ams::process")
   static char result=0;
   har buf=0;
   int i=0;
   for(i=2;i<4;i++){
      INP_GPIO(i);
      SET_GPIO_ALT(i,0);
   }

   read(&buf);
   if(((result-buf)>0?result-buf:buf-result)>5){
      _ct.y1=(double)(buf*3.3)/255;
      result=buf;
      *(int*)waitp=1000;
      return 1;
   }
   return 0;
}

‘ams’ is an analog moisture sensor class and it derives from I2C. It tweaks the Control, Status, Data Length, Slave Address, FIFO, and DIV registers. An ADC (Analog-Digital Converter) PCF8591 is used to convert analog signals to digital signals. It communicates with rPI3 through the I2C protocol. For more information, refer to the “Raspberry Pi 3 Hardware and System Software Reference.”

process():int processes the signal every 1s if differential output is more than 5. Otherwise, it pings for differential output every 100ms (default).

Circuit Diagram
Figure 8: Circuit Diagram

Code Listing 9: Class als

als();
virtual ~als();
int process(void*);
};

int als::process(void*){
   static char result=0;
   char buf=0;
   int i=0;
   for(i=2;i<4;i++){
      INP_GPIO(i);
      SET_GPIO_ALT(i,0);
   }
   read(&buf);
   if(((result-buf)>0?result-buf:buf-result)>2){
      _ct.y1=(double)(buf*3.3)/255;
      result=buf;
      return 1;
   }
   return 0;
}

‘als’ is analog light sensor class derives from I2C. Analog output provides output ranging from 0.0(Bright) to 3.3v (Dark). PCF8591 ADC is used to map analog to 8bit output and then output is serially transferred to rPI3 through I2C protocol on BSC1 ports(GPIO2 SDA,GPIO3 SDL). It communicates with rPI3 through I2C protocol. Refer to the “Raspberry Pi 3 Hardware and System Software Reference.”

Circuit Diagram
Figure 9: Circuit Diagram

Displaying Results with Qt

To show the output of using the sensors, we used Qt 2D Dsipaly for RPI2. If you are unfamiliar with Qt, then check out the article, “Using Qt 2D Display for rPI3” here on Codeguru.com

Experimental Output

The following shows you the experimental environment used to work with each of the sensors mentioned above. You’ll need to have your rPI3 set up. Qt is used to show graphed results. Refer to the “Raspberry Pi 3 Hardware and System Software Reference” and “Using the Qt 2D Display on a Raspberry Pi3.”

Graph

LED:

Sensor activated
Figure 10: Sensor activated

LED sets 1 and clears it every 1 second.

Chart of LED activity
Figure 11: Chart of LED activity

DHT11

Pin connector
Figure 12: Pin connector

Keeping the dht11 sensor on the bcm2837 processor increases the temperature while it decreases the humidity.

Chart of temperature and humidity changes
Figure 13: Chart of temperature and humidity changes

7.4 PIR:

Breadboard and perimeter detector
Figure 14: Breadboard and perimeter detector

It takes around 16 seconds to go low once an intrusion is detected.

Intrusion detection cycling
Figure 15: Intrusion detection cycling

Moisture Sensor (Digital)

Sensor placed in water
Figure 16: Sensor placed in water

For moisture detection, the sensor is kept in water.

Chart of moisture detection
Figure 17: Chart of moisture detection

Light Sensor (Digital)

Exposing the sensor to light
Figure 18: Exposing the sensor to light

Keep the sensor in the dark and then illuminate it through torch light.

Chart of light/dark cycling
Figure 19: Chart of light/dark cycling

Moisture Sensor (Analog)

Sensor placed in water
Figure 20: Sensor placed in water

In high moisture (water), the sensor goes to 1.5 V.

Chart of moisture detection
Figure 21: Chart of moisture detection

Light Sensor (Analaog)

Sensor being exposed to light
Figure 22: Sensor being exposed to light

It touches almost zero when illuminated with torch light near the top of the sensor.

Chart of light exposure
Figure 23: Chart of light exposure

Downloadable File

Please feel free to download and use the accompanying .tar file. It contains program files to make your journey easier.

Summary

This article introduced various sensor peripherals communicating with the Raspberry PI3 though parallel port and serial port protocols. The article also included various tools and utilities available to develop an application. Whereas LED, DHT11, and Motion sensors are digital sensors, Light and Moisture sensors are both digital and analog. A digital sensor communicates though GPIO i/o whereas an analog sensor signal first gets converted (through ADC PCF8591) and then communicates to the rPI3 through the I2C protocol. The article showed run time data on graphs where differential data are captured and shown on a time stamp basis in a min:sec:millisecond scale. The Qt-based graph is developed in the article itself and it shows data in dual x-y axis (left and right).

References

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read