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.
In order to detect a meaningful change in capacitance, it’s necessary to filter the values. The background noise is sampled for 1s on startup (pressing the Launchpad button also recalibrates). For a discharge time to indicate a touch, it must exceed the maximum found in the calibration period.
Once a touch is detected, the onboard LED is lit. It’s then turned off a short time later by the tick.
Download source and Makefile.
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