I often use ATSAM4S microprocessor and one of the neat
functions is capacitive sensing for touch buttons. There is a proprietary
technology, QTouch, that does it. But you can make a simple touch button even
easier and with half the pins. QTouch needs two pins, a capacitor and a
resistor for each channel (button). I can do it with only the resistor
(protection resistor, no need for a precise value) and one pin per channel.
The principle is very similar to QTouch sensing. First, you
attach a button through a protection resistor (3k3 works fine) to a general
purpose I/O pin. The button may be simply a piece of metal on a cable or it can
be printed on the circuit board. It only has to be covered by an insulating
layer. Solder mask, paper or a not very thick acrylic work well. This creates
one of the plates of a capacitor and the dielectric. The second part is your
finger. By approaching the button, your finger increases the capacitance and
this can be measured to detect the presence of the finger thus producing a
touch event.
Each pin has three possible states: high, low and
high-impedance (measuring). In addition, each pin can be pulled down or high by
an internal resistor, whose value is approximately 100 kOhm. We are going to
use this. First, we need to discharge the capacitor. That can be easily done by
pulling the pin low for sufficiently long time. A sufficiently long time is
arbitrary but 5 time constants is more than enough. The RC time constant is
dependent on the protection resistor and the total capacitance. A button
together with parasitic capacitance can reach few tens of picofarads. So to be
absolutely sure, lets use 100 pF as the capacitance value and 3k3 as the
protection resistor. This translates to a time constant of 0.33 microseconds.
Five time constants is 1.65 microseconds. Thus, if you rely on the built-in delay.h function delay_us(), then set two as its argument. You can see
that only two microseconds are needed to prepare the button for measurement.
Because you can do this in parallel for all channels, it is really fast.
Next thing is to measure the time needed to charge the
capacitor. We set the pin to high-impedance mode and pull it high using the
internal 100 kOhm resistor. This will form another RC series circuit, this time
with a different time constant, RC = (100k + 3k3) × (button + pin capacitance).
If the total capacitance is, say, 20 pF, then the time constant is 2.066
microseconds. But the internal resistor value is VERY approximate so for any
accurate capacitance measurement a calibration would be needed. Having said
that, we don’t need an accurate measurement at all! We only need to detect
changes in capacitance. So after pulling the pin high, we simply wait until it
reads 1. You can either continually poll the pin input register or set an
interrupt and count the time it takes the pin to set. With 2-microsecond time
constant, the pin will be high quickly so the whole measurement process is
fast. One way to measure the time is to simply set a loop and wait until the
pin sets. The iterator value corresponds to the charge-up time and is the
result of measurement. You will need to keep a baseline time value for the
untouched button. Then compare each measurement to this value to determine if
the button has been (value will be higher) or hasn’t been touched. Do the
measurement regularly to provide swift user interface experience. Protip: If you set the pin low after each measurement, you don't have to wait for discharge the next time.
Sample code (download here) uses touch.c and touch.h. Define the I/O port in touch.h, then call touch_init() once to setup the line and call touch_measure() to get a value proportional to port's capacitance. In the main program, watch for this value increasing, thus indicating a touch event.
touch.h:
/*
* touch.h
*
* Created: 26.8.2014 19:00:00
* Author: Kaktus
*/
/*
* Description:
* Simple touch sensor demonstration.
* Connect touch button to the defined port via approximately 3k3 resistor.
* No other components needed.
*/
#ifndef TOUCH_H_
#define TOUCH_H_
#define TOUCH_PIOPORT PIOA
#define TOUCH_PIOLINE 0
#define TOUCH_PMC PMC_PCER0_PID11
void touch_init(void);
uint32_t touch_measure(void);
#endif /* TOUCH_H_ */
touch.c:
/*
* touch.c
*
* Created: 26.8.2014 19:00:00
* Author: Kaktus
*/
#include <asf.h>
#include "delay.h"
#include "touch.h"
void start_discharge(void);
void touch_init()
{
PMC->PMC_WPMR = PMC_WPMR_WPKEY_PASSWD;
PMC->PMC_PCER0 |= TOUCH_PMC;
TOUCH_PIOPORT -> PIO_WPMR = (0x50494F << 8); // registry unlock
TOUCH_PIOPORT -> PIO_PER |= (1 << TOUCH_PIOLINE); // peripheral disable
TOUCH_PIOPORT -> PIO_IFDR |= (1 << TOUCH_PIOLINE); // glitch filter disable
TOUCH_PIOPORT -> PIO_PPDDR |= (1 << TOUCH_PIOLINE); // pull down disable
start_discharge();
}
uint32_t touch_measure()
{
TOUCH_PIOPORT -> PIO_ODR |= (1 << TOUCH_PIOLINE); // output disable
TOUCH_PIOPORT -> PIO_PUER |= (1 << TOUCH_PIOLINE); // pull up enable
uint32_t t = 0;
while (!((TOUCH_PIOPORT -> PIO_PDSR) & (1 << TOUCH_PIOLINE)))
{
t++;
}
start_discharge();
return t;
}
void start_discharge()
{
TOUCH_PIOPORT -> PIO_PUDR |= (1 << TOUCH_PIOLINE); // pull up disable
TOUCH_PIOPORT -> PIO_OER |= (1 << TOUCH_PIOLINE); // output enable
TOUCH_PIOPORT -> PIO_CODR |= (1 << TOUCH_PIOLINE); // output low
delay_us(2); // set > 5 RC time constants; you can comment this line if you measure at longer intervals than the set delay
}
Sample code (download here) uses touch.c and touch.h. Define the I/O port in touch.h, then call touch_init() once to setup the line and call touch_measure() to get a value proportional to port's capacitance. In the main program, watch for this value increasing, thus indicating a touch event.
touch.h:
/*
* touch.h
*
* Created: 26.8.2014 19:00:00
* Author: Kaktus
*/
/*
* Description:
* Simple touch sensor demonstration.
* Connect touch button to the defined port via approximately 3k3 resistor.
* No other components needed.
*/
#ifndef TOUCH_H_
#define TOUCH_H_
#define TOUCH_PIOPORT PIOA
#define TOUCH_PIOLINE 0
#define TOUCH_PMC PMC_PCER0_PID11
void touch_init(void);
uint32_t touch_measure(void);
#endif /* TOUCH_H_ */
touch.c:
/*
* touch.c
*
* Created: 26.8.2014 19:00:00
* Author: Kaktus
*/
#include <asf.h>
#include "delay.h"
#include "touch.h"
void start_discharge(void);
void touch_init()
{
PMC->PMC_WPMR = PMC_WPMR_WPKEY_PASSWD;
PMC->PMC_PCER0 |= TOUCH_PMC;
TOUCH_PIOPORT -> PIO_WPMR = (0x50494F << 8); // registry unlock
TOUCH_PIOPORT -> PIO_PER |= (1 << TOUCH_PIOLINE); // peripheral disable
TOUCH_PIOPORT -> PIO_IFDR |= (1 << TOUCH_PIOLINE); // glitch filter disable
TOUCH_PIOPORT -> PIO_PPDDR |= (1 << TOUCH_PIOLINE); // pull down disable
start_discharge();
}
uint32_t touch_measure()
{
TOUCH_PIOPORT -> PIO_ODR |= (1 << TOUCH_PIOLINE); // output disable
TOUCH_PIOPORT -> PIO_PUER |= (1 << TOUCH_PIOLINE); // pull up enable
uint32_t t = 0;
while (!((TOUCH_PIOPORT -> PIO_PDSR) & (1 << TOUCH_PIOLINE)))
{
t++;
}
start_discharge();
return t;
}
void start_discharge()
{
TOUCH_PIOPORT -> PIO_PUDR |= (1 << TOUCH_PIOLINE); // pull up disable
TOUCH_PIOPORT -> PIO_OER |= (1 << TOUCH_PIOLINE); // output enable
TOUCH_PIOPORT -> PIO_CODR |= (1 << TOUCH_PIOLINE); // output low
delay_us(2); // set > 5 RC time constants; you can comment this line if you measure at longer intervals than the set delay
}