Pale blue cloud  

   

   

Raspberry Pi motion detection with Zilog ePIR

Details

This is a quick write-up on how to use the Zilog ePIR motion detector with a Raspberry Pi. The article illustrates how to connect the ePIR to the RPi's GPIO pins and how to make its motion detection operation visible by controlling an external LED using a script.

Sources are provided for background information.

Serial port configuration

Disable serial port login and prevent boot log data from being sent to the console. This is so we can use the serial interface, ttyAMA0, to communicate with the ePIR.

The following commands are executed as root.

  1. Edit /etc/inittab and comment out the line thay says T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
  2. Edit /boot/cmdline.txt and remove the bit that says console=ttyAMA0,115200 kgdboc=ttyAMA0,115200.
  3. Reboot the Raspberry Pi with shutdown -r now.

[source]

Hook-up Zilog ePIR sensor

Connect the Zilog ePIR sensor to the Raspberry Pi according to the table below. This will configure the ePIR to run in serial interface mode. I am using Adafruit's Pi Cobbler, a convenient breakout board that sits on top of a mini bread board.

 

 Zilog pin ePIR pin
1 (GND) 6 (GND)
2 (VDD) 1 (3V3)
3 (RXD) 8 (TXD)
4 (TXD) 10 (RXD)
5 (not connected)
6 (LG) 1 (3V3)
7 (not connected)
8 (GND) 6 (GND)

Note: ePIR pin 6 (Light Gate) can be connected to 3V3 via a potentiometer to influence the ePIR's behaviour (sensitivity) with respect to ambient light conditions.

[source: Zilog ePIR product specification, Raspberry Pi GPIO pin layout]

RPi with Zilog ePIR and Pi Cobbler breakout board

Test the serial interface

  1. Use the GNU screen utility to connect to the serial port: screen /dev/ttyAMA0 9600
  2. Type the 'a' key. This is the ePIR command for 'Read Motion Status'.
  3. If the reply is 'N' then no motion was detected. If the reply is 'Y' then motion was detected. Moving something back and forth in front of the ePIR to test.
  4. Quite the screen utility with <Ctrl>-A k.

Install Perl packages

As root install the Device::SerialPort package. For some reason installing with the CPAN command line failed initially:

cpan[2]> install Device::SerialPort
Running install for module 'Device::SerialPort'
Running make for C/CO/COOK/Device-SerialPort-1.04.tar.gz
Checksum for /root/.cpan/sources/authors/id/C/CO/COOK/Device-SerialPort-1.04.tar.gz ok
Uncompressed /root/.cpan/sources/authors/id/C/CO/COOK/Device-SerialPort-1.04.tar.gz successfully
Using Tar:/bin/tar xf "Device-SerialPort-1.04.tar":
Couldn't untar Device-SerialPort-1.04.tar
Package seems to come without Makefile.PL.
  (The test -f "/root/.cpan/build/COOK-kI9Eu6/Makefile.PL" returned false.)
  Writing one on our own (setting NAME to DeviceSerialPort)
  Had problems unarchiving. Please build manually
Running make test
  Make had some problems, won't test
Running make install
  Make had some problems, won't install
Failed during this command:
 COOK/Device-SerialPort-1.04.tar.gz           : unwrapped NO -- untar failed

While doing this I noticed that the CPAN module itself was out of date so I manually installed it (download from CPAN, perl Makefile.PL; make; make test; make install), failing automatic installation.

Tried again to install Device::SerialPort using CPAN but it failed because YAML is not installed. So... installed YAML using CPAN.

I noticed that it's best to quit CPAN after each installation attempt. I've read reports that CPAN is using too much memory for the Pi and this seems to help.

After installing YAMl I was able to install Device::SerialPort using the CPAN commandline. There was some memory swapping going on so I was probably lucky that it did not bomb out.

Simple Perl script for motion detection

This is a simple script that checks every second if motion was detected. Run it from the command line, it will print a Y or an N to indicate if motion was detected or not:

#!/usr/bin/perl -w
#
# Name:         epir.pl
# Purpose:      Test script to communicate with Zilog ePIR.
#               Reads ePIR's motion status every second and
#               prints it out only when changed.
# Author:       MBUR
# Date:         16 Feb 2014
#
#-----------------------------------------------------------------------------


use strict;
use Device::SerialPort;


#
# Set the parameters for the serial communication with the
# ePIR on the Raspberry Pi.
#
my $port = Device::SerialPort->new("/dev/ttyAMA0") or die "$!";
$port->user_msg(1);
$port->baudrate(9600);
$port->parity("none");
$port->databits(8);
$port->stopbits(1);
$port->handshake("none");
$port->write_settings;


$port->lookclear;       # empty buffers

#
# Write an 'a' to the ePIR every second ('Read Motion Status')
# and print the answer but only if it differs from the previous
# status.
#
my $answer = "";
my $old_answer = "";


while (1) {
   my $count_out = $port->write('a');
   warn "write failed\n" unless ($count_out);

   until ($answer ne "") {
      $answer = $port->read(1);       # poll until data ready and read single character
      die "Aborted without match\n" unless (defined $answer);
   }

   if ($answer ne $old_answer) {        # motion status has changed
      $old_answer = $answer;
      print STDOUT "\n# $answer #\n";
   }
   $answer = "";

   sleep 1;

} # while

Initially the script prints the last motion status (usually 'Y' or 'N') and then waits till that status change, at which point it prints the new motion status.

Interfacing with other hardware

Now that I can read the status of the ePIR using a simple script I want to control other hardware as well. A simple beginning would be to control GPIO pins to switch an LED off and on with the ePIR signal.

The GPIO pins are controlled by the Broadcom BCM 2835 processor. There is a C library for Raspberry Pi. It provides access to GPIO and other IO functions on the Broadcom BCM 2835, allowing access to the 26 pin ISE plug on the RPi board so you can control and interface with various external devices.

Compile and install C library

  1. cd /tmp
  2. wgethttp://www.airspayce.com/mikem/bcm2835/bcm2835-1.36.tar.gz
  3. gunzip bcm2835-1.36.tar.gz
  4. tar xf bcm2835-1.36.tar
  5. cd bcm2835-1.36
  6. ./configure && make
  7. sudo make install

Compile and install Perl library

  1. cd /tmp
  2. wget http://search.cpan.org/CPAN/authors/id/M/MI/MIKEM/Device-BCM2835-1.9.tar.gz
  3. gunzip gunzip Device-BCM2835-1.9.tar.gz
  4. tar xf gunzip Device-BCM2835-1.9.tar
  5. cd Device-BCM2835-1.9
  6. perl Makefile.PL
  7. make && make test
  8. sudo make install

Improved Perl script to control external LED

Now I want to control an external LED with the ePIR. The LED is connected to (3.3V) GPIO pin 11 via a current-limiting resistor. Note that the maximum permitted current draw from the 3.3 V pins is 50 mA. With this particular LED/resistor combo the current is well below that limit.

The script was changed a bit to include the BCM2835 library and the detection logic was simplified. It switches the LED on for a second when motion is detected.

Note that is must be run as root because the BMC library accesses /dev/mem. This is NOT a recommended practice. Also note that tricks with suid/sgid won't help you because Linux (UNIX in general) ignores those settings on shell/Perl scripts.

#!/usr/bin/perl -w
#
# Name:         epir2.pl
# Purpose:      Test script to communicate with Zilog ePIR.
#               Reads ePIR's motion status every second and
#               switch on an LED when motion is detected.
# Author:       MBUR
# Date:         22 Feb 2014
#
#-----------------------------------------------------------------------------


use strict;
use Device::SerialPort;
use Device::BCM2835;

$ENV{'PATH'} = '';  # protection when running as root

#
# Set the parameters for the serial communication with the
# ePIR on the Raspberry Pi.
#
my $port = Device::SerialPort->new("/dev/ttyAMA0") or die "$!";
$port->user_msg(1);
$port->baudrate(9600);
$port->parity("none");
$port->databits(8);
$port->stopbits(1);
$port->handshake("none");
$port->write_settings;


$port->lookclear;       # empty buffers


#
# Setup the BCM2835
#
Device::BCM2835::init() || die "Could not init library";

# Set RPi pin 11 to be an output
Device::BCM2835::gpio_fsel(&Device::BCM2835::RPI_GPIO_P1_11, &Device::BCM2835::BCM2835_GPIO_FSEL_OUTP);

#
# Write an 'a' to the ePIR every second ('Read Motion Status') and read the returned status.
#
while (1) {
   my $answer = "";
   my $count_out = $port->write('a') or die "write failed\n";

   until ($answer ne "") {
      $answer = $port->read(1);       # poll until data ready and read single character
   }

   if ($answer eq 'Y') {        # motion status has changed
      # Switch LED on. Blocking delay.
      Device::BCM2835::gpio_write(&Device::BCM2835::RPI_GPIO_P1_11, 1);
      Device::BCM2835::delay(1000); # Milliseconds
   }
   else {   # no motion detected
      # Switch LED off

      Device::BCM2835::gpio_write(&Device::BCM2835::RPI_GPIO_P1_11, 0);
      sleep 1;   # be nice with system resources
   }


} # while

  [source]

Run script as non-root user

Running the above script as root is not a good idea from a security perspective. To be able to run it as pi we need to create a small C program which functions as a wrapper around our script and then set suid permissions on the wrapper, as follows:

  1. Create the C program, let's call it epirwrapper.c:
    int main(void)
    {
            setuid(0);
            system("./epir2.pl");
    }
  2. Compile it: gcc -o epirwrapper epirwrapper.c
  3. Set ownership and permissions:
    1. sudo chown root:pi epirwrapper
    2. sudo chmod ug+s epirwrapper
    3. sudo chmod o-rwx epirwrapper
  4. Execute it as user pi: ./epirwrapper

[discussion]

Although this does the trick I am still not happy with it: any non-root user with enough privileges could change our epir2.pl script into doing something else and then execute it with root privileges through the epirwrapper binary.

Another disadvantage is that, even though the program is started by pi, it runs as root. Which means that user pi cannot kill the program (without resorting to sudo or Control-C, that is):

pi@raspberrypi ~/bin/playground $ ps aux|grep epir
root     23054  0.2  0.1   1536   308 pts/3    S    14:35   0:00 ./epirwrapper
root     23055  0.0  0.2   1756   508 pts/3    S    14:35   0:00 sh -c ./epir2.pl
root     23056 34.5  1.7   7928  4172 pts/3    S    14:35   0:01 /usr/bin/perl -w ./epir2.pl

A better solution would be to compile the Perl script into an executable using pp. This would turn it into a standalone program that does not call the script anymore.

   
© Palebluedot