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


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;
    IE1 |= WDTIE;

    // configure system clock
    BCSCTL1 = CALBC1_1MHZ; // Set range

// Watchdog interval timer
interrupt(WDT_VECTOR) WDT_ISR(void)

// 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
    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;

// test if an RC discharge time looks like a trigger or not
static bool isTrigger(uint32_t sample)
            // 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;

        case TRIGGER_RUNNING:
            if (sample < min_sample)
                state = TRIGGER_RECALIBRATE;     // the environment has changed, recalibrate
                if (sample > max_sample)
                    return true;

    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


    // 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

        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




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


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 $&lt;

    rm -rf $(TARGET).elf $(OBJS) $(TARGET).map