Detecting Taps with an Accelerometer: Disco Bike Light part 4

Tap Control of the Disco Bike Light

In last week’s blog I talked about looking into using an accelerometer output as a user interface for the Disco Bike Light design.  This week I took the next step of actually implementing a method of detecting taps with an accelerometer and using them as an on-off signal.

For a quick refresher, the Disco Bike Light is a design where I’ve decided to replace the guts of a Schwinn bike light with my own electronics. I wanted more color and more brightness.  I put some constraints on myself for the design.  One was that I couldn’t destroy the existing bike light in the process of making my new one.  For on/off/color control I was left with few options…

The Schwinn bike light uses some cool manufacturing methods to complete the circuit between the battery pack, LED, and on/off switch.  They use threaded/un-anodized areas of the flashlight case, as well as springs and contact points, to create a current path.  The on/off button seems to be a microcontroller embedded in the base of the light, and simply interrupts this path for off, or completes it for on.  This all means that there are no wires or buttons for me to connect to.

Hence my desire to use an accelerometer to detect user input like taps.  The first step in using an accelerometer as an input device was to take a look at the output signal of the accelerometer when it is tapped.   Below you can see an oscilloscope capture of three taps along with a 100mS timer tick output by a microcontroller.  The voltage output of the tap looks easily detectable.  There also appears to be a tap “duration” of about 50mS.  This is similar to a switch input.  As mechanical devices switches don’t open and close cleanly.  The bounce around between open and closed for a short period of time.  When reading a switch input you generally need to “debounce” it.

dbl_tap_scope

So it looks like the accelerometer output is appropriate for detecting taps.  Now I needed to come up with an algorithm that does the job.  I started by writing code for the microcontroller that implemented a serial interface.  This allowed me to watch the accelerometer voltages in close-to-real-time, but more importantly, to see them as the microcontroller did.  I used a PIC16F1829 from Microchip and our BM013 module as the microcontroller development board with our CS005 PICKit adapter.  I also used the BM010 USB to serial converter, the BM006 accelerometer, and the BM014 super bright LED module.  These components together allowed me to prototype all of the functionality that I’d see in my final system.

dbl_accel_test1

The software was written in Visual Studio 2010 (VB2010) using the .NET Framework 4 Client Profile.  You can download the software files from our web site.  You can see from the screen capture that the polarity of the “tap” signal depends on the direction the tap comes from, which makes sense.  However, it is on all three output channels of the accelerometer.  I modified the code in the PIC16F1829 so the controller would capture either the raw analog value or a voltage difference (voltage max minus voltage min) captured every 50mS.

Below is a screen capture of a tap as seen by the microcontroller when looking at the voltage difference.  This told me I could detect a tap on any accelerometer axis by tracking the max and min values and resetting them every 50mS.

dbl_difference_test2

The firmware function used to measure the voltages is shown below.  Again, this is code for a PIC16F1829 using the free version of XC8, Microchip’s C compiler.  The max, min, and diff values are reset every 50mS.

/******************************************************************************/
/* Measure_Analogs:
   Measures accelerometer voltages               */
/******************************************************************************/
void Measure_Analogs()
{
    char i = 0;

    ADCON0bits.CHS = 0x04;        // measures AN4 at pin RC0
    for(i=0;i<64;i++);            // wait some uS
    ADCON0bits.GO = 0x01;         // start measurement
    while(ADCON0bits.GO);

    Zmeas = ADRESH<<8;
    Zmeas = (Zmeas & 0xff00) + ADRESL;

    if (Zmeas < Zmin)             // track min max and difference voltages
       Zmin = Zmeas;              // over each 50mS period
    if (Zmeas > Zmax)
       Zmax = Zmeas;
    if ((Zmax - Zmin)> Zdiff)
        Zdiff = Zmax - Zmin;

    if (MODE.Bits.LOADDIFF == 1) // load diff measurements into comm array
    {                            // if MODE bit set, otherwise load raw ADC  measurements
        NV.Bytes.Z_InHi = Zdiff>>8;
        NV.Bytes.Z_InLo = Zdiff & 0xff;
    }
    else
    {
        NV.Bytes.Z_InLo = ADRESL;
        NV.Bytes.Z_InHi = ADRESH & 0x03;
    }

    ADCON0bits.CHS = 0x05;        // measures AN5 at pin RC1
    for(i=0;i<64;i++);            // wait some uS
    ADCON0bits.GO = 0x01;         // start measurement
    while(ADCON0bits.GO);         // everything else pretty much same as Z channel

    Ymeas = ADRESH<<8;
    Ymeas = (Ymeas & 0xff00) + ADRESL;

    if (Ymeas < Ymin)
       Ymin = Ymeas;
    if (Ymeas > Ymax)
       Ymax = Ymeas; 
    if ((Ymax - Ymin)> Ydiff)
        Ydiff = Ymax - Ymin;

    if (MODE.Bits.LOADDIFF == 1)
    {
        NV.Bytes.Y_InHi = Ydiff>>8;
        NV.Bytes.Y_InLo = Ydiff & 0xff;
    }
    else
    {
        NV.Bytes.Y_InLo = ADRESL;
        NV.Bytes.Y_InHi = ADRESH & 0x03;
    }
    ADCON0bits.CHS = 0x06;        // measures AN6 at pin RC2
    for(i=0;i<64;i++);            // wait some uS
    ADCON0bits.GO = 0x01;         // start measurement
    while(ADCON0bits.GO);         // everything else pretty much same as Z channel

    Xmeas = ADRESH<<8;
    Xmeas = (Xmeas & 0xff00) + ADRESL;

    if (Xmeas < Xmin)
       Xmin = Xmeas;
    if (Xmeas > Xmax)
       Xmax = Xmeas;
    if ((Xmax - Xmin)> Xdiff)
        Xdiff = Xmax - Xmin;

    if (MODE.Bits.LOADDIFF == 1)
    {
        NV.Bytes.X_InHi = Xdiff>>8;
        NV.Bytes.X_InLo = Xdiff & 0xff;
    }
    else
    {
        NV.Bytes.X_InLo = ADRESL;
        NV.Bytes.X_InHi = ADRESH & 0x03;
    }
}

So turning voltage measurements of the accelerometer channels into a user interface requires a little more thought.  Here are some things to consider.

1.  From the oscilloscope I know that after a tap occurs there is about 50mS where the same tap could be detected again.  I need to debounce the taps so this time period is ignored.
2.  I need to establish the number of taps needed to activate on/off and a rate at which they should occur.  I know that my bike has two wheels, so I should shoot for an odd number of taps.  After all, if I run over something that causes a tap to register it should happen in pairs (front then back tire).  3 taps is a good start.  The taps should occur fast enough that if the bike light does turn off at night I can get it back on pretty quick.  I’ll shoot for 200-400mS per tap.
3.  I need to clear out my tap counter if I don’t get to three within a certain time frame.  I’ll say that if I don’t get three taps within 1.5 seconds of the first I’ll reset the counter.
4.  I need a programmable voltage threshold that indicates a tap has occurred.  This can allow me to make the tap detection more, or less, sensitive.

Here’s a flowchart of what I came up with.

image

My C function is shown below.  Most of the variables I used are global, but I think you can get the picture.  When the design is done and working I’ll post the final software and PIC16F1829 firmware.

/******************************************************************************/
/* TapDetect:
   Detects taps on the X axis channel and resets some of the analog registers  */
/******************************************************************************/
void TapDetect()
{
    // load tap threshold detection level from comm array into variable
    int TapDiff = 0;
    TapDiff = NV.Bytes.TapDiffHi<<8;
    TapDiff = TapDiff & 0xff00 + NV.Bytes.TapDiffLo;

    if (PIR1bits.TMR1IF == 1)   // see if TMR1 overflow
    {
        TMR1 = ~50000;           // load 50mS timer value
        PIR1bits.TMR1IF = 0;    // clear overflow flag
        TapCounter++;
        if (TapCounter > 30)    // if 30 counts (1.5S reset it all)
        {
            TapNumber = 0;
            TapCounter = 0;
        }
        LED = 1;                 // set LED for scope trigger
        if (INDICATOR.Bits.TAPDETECTDELAY == 0) // check to see if we already saw this tap
        {
            if (Xdiff > TapDiff)    // check for tap
                {
                    INDICATOR.Bits.TAPDETECTDELAY = 1;  // set flag to make sure we don't look again for 50mS
                    if (TapNumber == 0)                 // if first tap reset counters
                        TapCounter = 0;
                    TapNumber++;                        // increment tap counter
                    TapTime[TapNumber] = TapCounter;    // store 50mS increments between taps
                    if (TapNumber == 3)                 // do we have 3 taps?
                    // makse sure taps were between 200mS and 400mS apart
                    {
                        if (((TapTime[2] - TapTime[1]) >= 4) 
                            && ((TapTime[2] - TapTime[1]) <= 8) 
                            && ((TapTime[3] - TapTime[2]) >= 4) 
                            && ((TapTime[3] - TapTime[2]) <= 8))
                            // if taps met requirements toggle power bit 
                              INDICATOR.Bits.POWERON = ~INDICATOR.Bits.POWERON;                          
                        TapNumber = 0;                  // reset counters
                        TapCounter = 0;
                    }
                }
        }
        else
            INDICATOR.Bits.TAPDETECTDELAY = 0;  // clear flag used to skip tests within 50mS of tap detected

        NV.Bytes.TapNum = TapNumber;  // load tap count into comm array

        // reset analog storage registers every 50mS
        Xmax = Xmeas;
        Xmin = Xmeas;
        Xdiff = 0;
        Ymax = Ymeas;
        Ymin = Ymeas;
        Ydiff = 0;
        Zmax = Ymeas;
        Zmin = Ymeas;
        Zdiff = 0;
        LED = 0;
    }
}



Trackbacks

  1. […] not sure what the Disco Bike Light project is you can look through some of my older blog posts (here’s my last post). The short story is that I wanted to design a flashy, fun, multicolor bike light using an existing […]

Speak Your Mind

*