Detecting User Input with Capacitive Touch and Passive Infrared (PIR) - Part 3

by <a href="http://community.silabs.com/t5/Welcome-and-Announcements/Community-Ranking-System-and-Recognition-Program/m-p/140490#U140490"><font color="#000000"><font size="2">Ninja</font></font> </a> lynchtron on ‎04-10-2017 11:21 PM

15_title.png

 

 

In part 2, we configured the host computer with Python and pyqtgraph.  You should now be able to do some great things with those tools.  We are now ready to build a Python script that fetches data from the serial port and displays the data to a graphing window.  You can see this full file Scrolling.py in Github.

 

The first thing that we do in the script is import the libraries that we need to do most of the work for us:

import pyqtgraph as pg 
from pyqtgraph.Qt import QtCore, QtGui 
import serial 

Next, the script opens the serial port.  Note that on a Windows computer, the serial port that pySerial expects is the COM port minus one.  In my case, it uses COM3, so I pass in “3 -  1” to pySerial.  Edit this first line as necessary to match the port on your computer.

# Set up serial port attached to Wonder Gecko 
# Your computer may instantiate port numbers differently. 
port = 3  - 1   # -1 is for Windows 
baudrate = 115200 
serial_port = serial.Serial(port, baudrate=baudrate, timeout=0) # 0 for timeoutNonblocking 
serial_port.flushInput() 

Then, we will set some configuration options and create a pyqtgraph plot, curve, and curve pen.

# Switch to using white background and black foreground 
pg.setConfigOption('background', 'w') 
pg.setConfigOption('foreground', 'k') 

# Create a pyqtgraph window object 
win = pg.GraphicsWindow() 
win.setWindowTitle('Capcitive sense plot') 

# Create a plot inside the window 
plot = win.addPlot() 

# Create a list of 300 zeros (initially) to hold the streaming data 
data = [0 for x in range(300)] 

# Create a "pen" that is width of 1 and blue color 
curve_pen = pg.mkPen(width=1, color='b') 

# Creata "curve" that uses the curve_pen, with data points (symbols) that are filled and outlined with blue 
curve = plot.plot(data, pen=curve_pen, symbolBrush='b', symbolPen='b', symbolSize=5) 

# Createa place to store the incoming serial port characters 
value_string = "" 

We now need an “update” function.  This is the function that will be called from the Qt library to update the active window.  This function will read data from the serial port and then shift the data structure to the left in order to scroll the graph on the screen.

 

# Define a function that will be called by pyqtgraph periodically to update the screen 
def update(): 
    # Tell Python that these variables are to be tied to the outside global scope 
    global data, curve, value_string 
     
    # Only do something when the serial port has data 
    while serial_port.inWaiting(): 
         
        # Read a single character 
        sample = serial_port.read(1) 
         
        # Add this sample to the value_string if it is not whitespace 
        if sample.strip(): 
            value_string += sample 
        else: 
            # Process the data when we find a space in the read data, if the string is not empty 
            if len(value_string) > 0: 
                 
                # Convert the ASCII base 16 string to an integer 
                value = int(value_string, 16) 
                 
                # Shift the whole datalist by one sample to the left 
                data[:-1] = data[1:] 
                 
                # Add the new element to the end of the list 
                data[-1] = value 
                 
                # Set the curve with the data 
                curve.setData(data) 
                 
                # Clear the string for the next value 
                value_string = "" 
    return 

 

 

Next, we set up a timer that pyqtgraph uses to update the graph window.  We pass in a reference to the update() function that we just defined by specifying “update” without the parenthesis , and pyqtgraph will call on this update function whenever the timer expires, which is set here with a 50ms timeout.

 

# Start Qt timer and pass the update function to it to set up periodic updates 
timer = pg.QtCore.QTimer() 
timer.timeout.connect(update) 
timer.start(50)

 

 

So far, we have objects ready to handle graphing, but we still don’t have any active windows.  The following lines will configure the pyqtgraph’s Qt system, which handles windowing.  Qt is a software library available for many languages that helps developers create graphical user interfaces.

 

## Start Qt event loop unless running in interactive mode or using pyside. 
if __name__ == '__main__': 
    import sys 
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): 
        QtGui.QApplication.instance().exec_()

 

 

And that’s it.  A file of under 100 lines is all that is required to fetch data from a serial port and create a live streaming graph in Windows, Mac, and Linux.  Note that most of this code was pulled from the pyqtgraph demo programs.  All I had to do was modify the formatting and add the pySerial stuff to the top of the file and in the update() function. 

 

When you run this file on an OS command prompt by changing directory to the directory that contains the file (i.e. cd c:\users\david\documents” or equivalent), and then typing “python Scrolling.py”, you will see a graphing window that you can use to fine tune the capacitive sensor.  The graph starts out at 0, then quickly jumps up to the ambient count detected by the ACMP in the specified period on TIMER1.  Each touch event causes the count to drop, depending on how close your finger is to the center of the touch pad.

capacitive_sense_graph_noise.png

 

You can “zoom in” on the noise band by simply not touching the touch pad for 300 samples, at which time, pyqtgraph will automatically rescale the graph on the noise band as shown in the figure below.  This shows that the noise band is only two counts wide.

capacitive_sense_ambient.png

 

Now that you have a tool to create a graph of the ongoing live count, you can use this to fine tune the capacitive sense circuit.  You can adjust the frequency of the oscillator, the frequency of TIMER0 and TIMER1, or change the size and structure of the touch pad, and then use the graph to ensure that your touch event is still 5x the noise band.  You can use copper tape to adjust the size of your capacitive sense pads during the prototyping stage.

 

Passive Infrared (PIR) Sensor Board Summary

The PIR sensor board that is obtained from Adafruit contains a raw pyroelectric sensor, some discrete electronics, a controller chip to make sense of the pyroelectric sensor analog output, and a Fresnel lens, which creates an omnidirectional detection field for the sensor.  It also contains some trimming potentiometers to allow you to adjust the delay time, sensitivity of the sensor, and the width of the pulse on its output pin when it detects movement.  There is a great tutorial over at Adafruit to learn more about the sensor and the board: https://learn.adafruit.com/pir-passive-infrared-proximity-motion-sensor/.

 

pir_sensor.png

We will be using the PIR sensor board to simply produce a signal whenever someone approaches our circuit, or doorbell in our example, so that we wake up the EFM32, illuminate an LED, and start sampling the capacitive sense button.  It makes no sense to keep the EFM32 awake scanning for capacitive touch inputs and an LED illuminated continuously if there is no one standing in front of our sensor.

 

Attach the PIR Sensor and Illuminate an LED

The PIR sensor board has just three pins and is powered by 3 to 5V, so the connection to the Starter Kit is simple.  Attach the sensor to the Starter Kit per the following table.  Note that the Adafruit board contains a 3.3V regulator, so even though we are supplying 5V to the board, it is only producing 3.3V on the OUT pin, and it is therefore safe to use on the Starter Kit.

 

Starter Kit           PIR Sensor Board
5V                      +5V
PC9                    OUT
GND                   GND

 

Note that PC9 is only available on the row of pins called J100 on the Starter Kit.  This pin is the only pinreadily available on the Starter Kit capable of bringing the MCU out of EM4 deep sleep state.

 

wonder_gecko_em4_pin.png

wonder_gecko_em4_pin._photo.png

The jumper on the corner of the PIR board is used to set the “retriggering” option.  In our case, it should be set closest to the board edge, which means that the OUT pin will remain high for as long as there is motion detected in the sensor range. 

 

Configure PC9 as an input and enable the PE2 as an output, which is connected to one of the LEDs on the Starter Kit, using the following code inside the setup_capsense() function:

 

      // Set the LED as push/pull
      GPIO_PinModeSet(gpioPortE, 2, gpioModePushPull, 0);
 
      // Install the PIR sensor on PC9
      GPIO_PinModeSet(gpioPortC, 9, gpioModeInput, 0);

Add the following code to the top of your while loop to illuminate the LED whenever the PIR sensor’s OUT pin is high. 

 

            if (GPIO_PinInGet(gpioPortC, 9))
            {
                  GPIO_PinOutSet(gpioPortE, 2);
            }
            else
            {
                  GPIO_PinOutClear(gpioPortE, 2);
            }

Note that the sensor holds the OUT pin high for a few seconds even on the lowest DELAY setting as set by the potentiometer.  This is perfect for our use, since we would want to illuminate the LED for our doorbell for a few seconds after movement has ceased.  You should now see the PIR sensor illuminate the LED0 on the Starter Kit whenever the PIR detects motion.  Note that there is a few seconds of holdoff after the LED has turned off before the PIR sensor board will arm itself again.

 

Use the PIR Sensor to Wake the EFM32 from EM4 Sleep
The PC9 pin that we used for the PIR board OUT pin was chosen because it is one of the possible choices as shown in the Wonder Gecko Reference Manual as being able to wake up the MCU from EM4.  Once configured for EM4 wake up, these pins will remain active as inputs even though all other GPIO’s are disabled.

 

gpio_pins.png

 

To configure PC9 as an EM4 wake up pin, use the following setup_em4() function:

 

#define EM4_WAKEUP_ENABLE_PC9     0x04
void enter_em4(void)
{
      EMU_EM4Init_TypeDef em4_init = EMU_EM4INIT_DEFAULT;
      EMU_EM4Init(&em4_init);
 
      // Set the wake up enable on PC9 and the polarity of high from the PIR OUT
      GPIO->EM4WUEN = EM4_WAKEUP_ENABLE_PC9;
      GPIO->EM4WUPOL = 1;
 
      // Set EM4WUCLR = 1 in GPIO CMD, to clear all previous events
      GPIO->CMD = 1;
 
      EMU_EnterEM4();
 
      // Now, the system can only be awakened by PC9 or reset, and it acts as
      //   if it is coming out of reset.  Code beyond this point will
      //   never execute.
}

Now, augmenting our main while loop is simple.  After we clear the GPIO pin PE2 to LED0, we call the enter_em4() function.  Keep in mind that once this function is called, any data in RAM is lost.  The board can only exit EM4 energy mode by the PC9 pin going high, through the reset signal, or through a power cycle.  In all of those cases, the board will be starting up from the Chip_Init() statement.  The following is the final main() function:

 

int main(void)
{
      /* Chip errata */
      CHIP_Init();
 
      setup_utilities();
 
      setup_capsense();
 
      while (1)
      {
            // Check the PIR OUT signal
            if (GPIO_PinInGet(gpioPortC, 9))
            {
                  // PIR is high, so set the LED0 to ON
                  GPIO_PinOutSet(gpioPortE, 2);
            }
            else
            {
                  // PIR is low, so turn off LED0 and enter EM4 engergy mode
                  GPIO_PinOutClear(gpioPortE, 2);
                  enter_em4();
            }
 
            // Clear the count and start the timers
            measurement_complete = false;
            TIMER0->CNT = 0;
            TIMER1->CNT = 0;
            TIMER0->CMD = TIMER_CMD_START;
            TIMER1->CMD = TIMER_CMD_START;
 
            // Now, wait for TIMER0 interrupt to set the complete flag
            while(!measurement_complete)
            {
                  EMU_EnterEM1();
            }
 
            // Now observe the count, send it out the USART
            print_count();
 
            // Delay to not overwhelm the serial port
            delay(100);
      }
}

When you run this code in Simplicity Studio, you will see that when the LED0 is not lit, the debugger loses connection with the MCU.  This is expected, because the debug interface shuts down in EM4 energy mode.  When you wave your hand over the PIR sensor, you should see the LED0 illuminate again.  If you watch the Python graphing window (or a terminal output) connected to the USART, you will see that no capacitance counts are streamed while the LED0 is off.  Once you wake up the MCU with the PIR OUT pin, the count of capacitance values resumes on the USART.

 

When working with EM4 mode, you can sometimes get “stuck” in EM4 if the MCU is booted and then goes to EM4 state too quckly.  You will have to go through the trouble of erasing flash and recovering the part outside of the normal programming process.  To keep this from happening, you should create a “GPIO trap” which looks for a pushbutton pin to be pressed at startup and if so, it does not enter EM4.  Then, by holding the pushbutton while you attempt to reprogram the part, it will prevent it from reentering EM4 too quickly.

 

This concludes the chapter on capacitive and PIR sensing.  You should be gaining the skills necessary to integrate several of EFM32’s peripherals in a single project to make something awesome.