MSP430 Launchpad Capacitive Sensing
Here’s a demo of capacitive proximity sensing on the TI Launchpad. I’m hoping to use this in a project I’m planning.
The circuit (below), uses a 1M resistor and (optionally) a small (<100pF) capacitor connected between ground and a port pin (P1.7). The tin-foil acts as a capacitor. When your body comes near or into contact, the capacitance increases. If your Launchpad isn’t grounded (ie. running from battery power), add a second foil to ground and move your hand between them.
I’ve found that when using a small capacitor alongside the foil I see less background noise.
By charging the capacitor then timing the discharge with a known resistance, we can determine the capacitance.
t = RC
Several cycles are shown on the trace below. To charge the capacitor, a port pin is driven high (set to an output and set high). A timer is then started and the pin is set to an input with an interupt enabled to detect a high to low edge. The capacitor discharges via the 1M resistor. Once the voltage drops below the input threshold for the pin, the timer is stopped.
Once a touch is detected, the onboard LED is lit. It’s then turned off a short time later by the tick.
Source code:
#include <io.h>
#include <signal.h>
#include <stdbool.h>
/****************************************************/
#define SWITCH_BIT BIT3 // Launchpad switch on P1.3
#define LED_BIT BIT0 // Launchpad LED on P1.0
#define CAP_BIT BIT7 // RC circuit on P1.7
/****************************************************/
// WDT Timer providing a system tick
#define TICKS_PER_SEC (1000/32)
// Time to sample background noise level
#define SAMPLING_PERIOD (TICKS_PER_SEC * 1)
/****************************************************/
typedef enum
{
TRIGGER_RECALIBRATE, // start sampling background noise level
TRIGGER_SAMPLING, // sampling background noise
TRIGGER_RUNNING, // running normally
} state_t;
static state_t state = TRIGGER_RECALIBRATE; // trigger state
static uint32_t min_sample; // low noise value
static uint32_t max_sample; // high noise value
static uint32_t sampling_timeout; // tick counter for stopping sampling (SAMPLING_PERIOD)
/****************************************************/
static volatile uint32_t sys_ticks = 0; // global tick counter
static volatile uint32_t rc_discharge_time = 0; // last discharge time
static volatile bool measuring = false; // is discharge being measured
/****************************************************/
static void cpu_init(void)
{
// configure watchdog timer as 32ms interval timer
IFG1 &=~WDTIFG;
IE1 &=~WDTIE;
WDTCTL = WDTPW + WDTHOLD;
WDTCTL = WDT_MDLY_32;
IE1 |= WDTIE;
// configure system clock
BCSCTL1 = CALBC1_1MHZ; // Set range
DCOCTL = CALDCO_1MHZ; // SMCLK = DCO = 1MHz
}
// Watchdog interval timer
interrupt(WDT_VECTOR) WDT_ISR(void)
{
sys_ticks++;
}
// Port1 ISR
interrupt(PORT1_VECTOR) P1_ISR(void)
{
uint32_t tar = TAR; // store TAR as early as possible
if((P1IFG & CAP_BIT) == CAP_BIT) // cap discharged
{
rc_discharge_time = tar - rc_discharge_time;
measuring = false;
P1IE &= ~CAP_BIT; // interrupt disable
}
else
if((P1IFG & SWITCH_BIT) == SWITCH_BIT) // switch pressed
{
state = TRIGGER_RECALIBRATE;
}
P1IFG = 0x00; // clear interrupt flags
}
// wait a short time (for cap to charge)
void wait(void)
{
volatile int i;
for(i=0;i<32;i++);
}
// test if an RC discharge time looks like a trigger or not
static bool isTrigger(uint32_t sample)
{
switch(state)
{
case TRIGGER_RECALIBRATE:
// Sample background noise for SAMPLING_PERIOD
sampling_timeout = sys_ticks + SAMPLING_PERIOD;
min_sample = 0xFFFFFFFF;
max_sample = 0x00000000;
state = TRIGGER_SAMPLING;
// DROP THROUGH
case TRIGGER_SAMPLING:
if (sample < min_sample)
min_sample = sample-1;
if (sample > max_sample)
max_sample = sample+1;
if (sys_ticks >= sampling_timeout) // sampling time over
state = TRIGGER_RUNNING;
break;
case TRIGGER_RUNNING:
if (sample < min_sample)
state = TRIGGER_RECALIBRATE; // the environment has changed, recalibrate
else
{
if (sample > max_sample)
return true;
}
break;
}
return false;
}
// Begin measuring RC discharge time
static void start_measuring(void)
{
measuring = true;
// SMCLK continuous
TACTL = TASSEL_2 | MC_2;
TACCR0 = 0x00;
// drive capacitor high, charge it up
P1DIR |= CAP_BIT;
P1OUT |= CAP_BIT;
wait(); // wait for cap to charge
// store start count
TAR = 0;
rc_discharge_time = TAR;
// interrupt when capacitor drops below pin's "high" voltage
P1IES |= CAP_BIT; // interrupt on hi to lo edge
P1IE |= CAP_BIT; // interrupt enable
// flip pin to input, capacitor will begin discharging
P1DIR &= ~CAP_BIT; // set to input
}
int main(void)
{
uint32_t led_timer = 0; // timed latch for LED
cpu_init();
// setup LED pins
P1DIR |= LED_BIT; // All LED pins as outputs
P1OUT &= ~LED_BIT; // Turn off LED
// setup switch interrupt
P1DIR &= ~SWITCH_BIT; // as input
P1IES |= 0x01; // interrupt on falling edge
P1IE |= SWITCH_BIT; // interrupt enable
// start measuring discharge time
start_measuring();
eint();
while(1)
{
if (!measuring)
{
// test discharge time to see if it looks like a trigger
if (isTrigger(rc_discharge_time))
{
P1OUT |= LED_BIT; // turn on LED
led_timer = sys_ticks; // record current time
}
start_measuring(); // measure again
}
// if 0.5s has elapsed since trigger, turn off LED
if (led_timer != 0 && sys_ticks > led_timer + (TICKS_PER_SEC/2))
{
P1OUT &= ~LED_BIT; // turn off LED
led_timer = 0; // clear timer
}
}
}
Makefile:
TARGET=captouch
CC=msp430-gcc
SIZE=msp430-size
STRIP=msp430-strip
CFLAGS=-Os -Wall -g -mmcu=msp430x2013 -ffunction-sections -fdata-sections -fno-inline-small-functions
LDFLAGS = -Wl,-Map=$(TARGET).map,--cref
LDFLAGS += -Wl,--relax
LDFLAGS += -Wl,--gc-sections
OBJS=$(TARGET).o
all: $(TARGET).elf
$(TARGET).elf: $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET).elf $(OBJS) $(LDFLAGS)
$(STRIP) $(TARGET).elf
$(SIZE) --format=sysv $(TARGET).elf
program: $(TARGET).elf
mspdebug rf2500 "prog $(TARGET).elf"
%.o: %.c
$(CC) $(CFLAGS) -c $<
clean:
rm -rf $(TARGET).elf $(OBJS) $(TARGET).map