The Touch of a Button
I've noticed a lot of development boards now have touch sensors on board. The STM32L053 discovery board has one as well as the Freescale FRDM-KL25Z board I've talked about before. The typical way to use these touch sensors is for controlling something "knob-like" (perhaps a volume control). However, I often find these sensors useful for emulating buttons. Especially since many of these boards don't have many (if any) mechanical switches.
The concept is simple. You divide the touch sensor into multiple zones and then detect touch events and categorize them. Of course, I wasn't satisfied with the simple approach, so I added a bit of code to sense swipes much as you would find on a touchscreen phone.
I broke out the KL25Z board again to develop a simple library. The touch sensor operates by sensing your finger's capacitance, but thanks to the mbed libraries, you don't really need to understand that. You simply instantiate a TSIAnalogSlider
object. Then you can call the readPercentage
method of the object.
The mbed libraries seem to enjoy using floating point numbers, and readPercentage
returns a number between 0.0 (no touch) to 1.0 (a touch at the right hand side). It seems like integers would be more efficient, but the ARM processor on the board handles floating point with no real problem, so it doesn't matter — at least not for this simple example.
The code implements a very simple state machine. There are three global variables shared between the main API call (getbtn
) and a function scheduled using an mbed Timeout (checkbtn
). The variables are:
- candidate — A potential button number (0-3)
- realbtn — An actual button press (0-3 or 256/512 for swipes)
- hold — Set when the state machine is waiting for a button press or swipe to complete
Note two of these are volatile because they are accessed from both the main line code and the Timeout
code. You can think of a Timeout
as a thread that gets executed sometime in the future. Unlike a Ticker
object, the Timeout
occurs exactly once per call to attach
. That is, a call to attach
causes the attached function to execute after the specified interval, but it won’t execute again unless there is another call to attach
later.
When the program detects a finger press, it schedules the timeout function. The timeout function examines the state of the sensor. There are three possibilities:
- There is no touch (button pressed quickly)
- There is a touch in the same zone as before (button pressed)
- There is a touch in a different zone (swipe)
In the case of a swipe, the program can tell the difference between a right and a left swipe by determining if the new zone is a higher or lower number than the original zone.
You can find the code below. There are several enhancements you could try to add. For example, regular button presses could repeat at some programmable rate. You could also implement the idea of a "long press" if you hold a button down for a certain length of time.
I doubt this is what the designer had in mind for the touch sensor, but it works well enough. A good example of adapting your design to the tools at hand to avoid having to customize or alter the existing hardware.
Listing:
#include "mbed.h" #include "tsi_sensor.h" // Simple button/swipe library for FRDM-KL25Z // Williams -- DDJ /* This defines will be replaced by PinNames soon */ #if defined (TARGET_KL25Z) || defined (TARGET_KL46Z) #define ELEC0 9 #define ELEC1 10 #elif defined (TARGET_KL05Z) #define ELEC0 9 #define ELEC1 8 #else #error TARGET NOT DEFINED #endif // Define the "zones" considered to be buttons // 4 is about the limit and 3 might be even better // You could also equal space them automatically pretty easily // (e.g., specify 25% and fill in BTNMAX programmatically float BTNMAX[]= {0.25, 0.50, 0.75, 1.00 }; #define SWIPETIME .350 // seconds to wait before sensing a button push or swipe #define SWIPE_R 256 // virtual button code for right swipe #define SWIPE_L 512 // virtual button code for left swipe Serial pc(USBTX, USBRX); // tx, rx for debugging TSIAnalogSlider tsi(ELEC0, ELEC1, 40); // The Analog slider Timeout scan; int candidate=-1; // possible button push (-1 is none) volatile int realbtn=-1; // actual button push (-1 is none) volatile int hold=0; // waiting for button release when 1 // internal function to get raw button state int getrawbtn() { float v=tsi.readPercentage(); // read slider if (v==0.0) return -1; // no button at all for (int i=0;i<sizeof(BTNMAX)/sizeof(BTNMAX[0]);i++) // classify by zone if (v<BTNMAX[i]) return i; return -1; // what? } // This is called by the timeout to // either see there is no swipe // see that there is a swipe // or see that there is a button release void checkbtn(void) { int newbtn=getrawbtn(); if (hold!=0 && newbtn==-1) // wait for key release { hold=0; // released so return; // don't reschedule me } // reschedule us for next swipetime scan.attach(checkbtn,SWIPETIME); if (hold) return; // still waiting for release hold=1; // we will be waiting for release from now on if (newbtn==-1||newbtn==candidate) // if no touch or button is the same, the candidate is the button { realbtn=candidate; return; } // Otherwise we are swiping either left or right if (candidate<newbtn) realbtn=SWIPE_L; else realbtn=SWIPE_R; return; } // This is the main API // Call it to get a button code (0-3 or 256 or 512) // You can block or not (default is to block) int getbtn(bool kwait=true) { while (hold) // if holding, either wait or return { if (!kwait) return -1; } realbtn=-1; // mark that we don't know do { candidate=getrawbtn(); // get a candidate (or return if not waiting) } while (candidate==-1 || !kwait); if (candidate==-1) return -1; scan.attach(checkbtn,SWIPETIME); // schedule the checkbtn routine while (realbtn==-1); // wait for realbtn to get set return realbtn; // return it } // Simple test program int main(void) { while (true) { pc.printf("Button %d\r\n",getbtn()); } }