I'm making a little blink led prop controller for my kids Comic Con costume. I've got my best Dr. Strange T-shirt for the event and Stan 'The Man' Lee is there for an Autograph on one item of your own. Yes!
http://rosecitycomiccon.com/celebrity-guests/
It's going inside a arm controller band so I wanted touch control instead of buttons to make it look cool so I used the PIC18F25K22 with its CTMU module to read finger movements and presses.
The CTMU is configured to read changes in capacitance with a timed constant current source to two hand sensors. As the finger get nears the sensor (a wire) the capacitance increases causing the timed current source voltage across the capacitor to drop in a linear fashion.
One of the first design options is the value of charging current and charge times that vary with sensor types.
Slower with lower current or faster with higher currents?
Usually higher currents give better noise immunity but lower sensitivity to touches.
I then wrote the software and built a small proto board for the controller.
There's not really much to it. Socket for the pic, a reset button, connector for the sensors, a socket for the LEDs wiring header, 5vdc switching regulator for the 9vdc input and a fiber optic transmitter for rs-232 debugging data.
The C18 software does a simple timer based round-robin CTMU/ADC sample routine in the ISR to gather sensor data into an array for later processing. This routine also sends the realtime data out the SPI port and into the timer3 data buffer for possible processing.
Eight samples per channel are collected as a background process, averaged and examined for noise and stability for a zero baseline and signal trip points. If all signal criteria are go for a good sensor detection a call is made to the flasher function that's just a simple 32 bit circular shift resister with a bit pattern in this test version of software.
Rs-232 debug output via light link @38400bps. Channel, zero set-point and data raw adc trigger values.
The result is a device that senses when the finger is between two sensor wires/plates not just touching one or the other. This gives better touch control positioning.
The example software is in the zip file. updated
http://rosecitycomiccon.com/celebrity-guests/
It's going inside a arm controller band so I wanted touch control instead of buttons to make it look cool so I used the PIC18F25K22 with its CTMU module to read finger movements and presses.
The CTMU is configured to read changes in capacitance with a timed constant current source to two hand sensors. As the finger get nears the sensor (a wire) the capacitance increases causing the timed current source voltage across the capacitor to drop in a linear fashion.
One of the first design options is the value of charging current and charge times that vary with sensor types.
Slower with lower current or faster with higher currents?
Usually higher currents give better noise immunity but lower sensitivity to touches.
I then wrote the software and built a small proto board for the controller.
There's not really much to it. Socket for the pic, a reset button, connector for the sensors, a socket for the LEDs wiring header, 5vdc switching regulator for the 9vdc input and a fiber optic transmitter for rs-232 debugging data.
The C18 software does a simple timer based round-robin CTMU/ADC sample routine in the ISR to gather sensor data into an array for later processing. This routine also sends the realtime data out the SPI port and into the timer3 data buffer for possible processing.
C:
void InterruptHandlerHigh(void)
{
static union Timers timer;
static uint8_t i_adc = 0;
if (INTCONbits.TMR0IF) { // check timer0 irq
if (!CTMUCONHbits.IDISSEN) { // charge cycle timer0 int, because not shorting the CTMU voltage.
DLED1 = HIGH;
CTMUCONLbits.EDG1STAT = 0; // Stop charging touch circuit
TIME_CHARGE = FALSE; // clear charging flag
CTMU_WORKING = TRUE; // set working flag, doing touch ADC conversion
// configure ADC for next reading
ADCON0bits.CHS = ctmu_button; // Select ADC channel
ADCON0bits.ADON = 1; // Turn on ADC
ADCON0bits.GO = 1; // and begin A/D conv, will set adc int flag when done.
} else { // discharge cycle timer0 int, because CTMU voltage is shorted
DLED1 = LOW;
ADCON0bits.CHS = ctmu_button; // Select ADC channel for charging
CTMUCONHbits.IDISSEN = 0; // end drain of touch circuit
TIME_CHARGE = TRUE; // set charging flag
CTMU_WORKING = TRUE; // set working flag, doing
timer.lt = charge_time[ctmu_button]; // set timer to charge rate time
TMR0H = timer.bt[HIGH]; // Write high byte to Timer0
TMR0L = timer.bt[LOW]; // Write low byte to Timer0
CTMUCONLbits.EDG1STAT = 1; // Begin charging the touch circuit
}
// clr TMR0 int flag
INTCONbits.TMR0IF = 0; //clear interrupt flag
}
if (PIR1bits.ADIF) { // check ADC irq
PIR1bits.ADIF = 0; // clear ADC int flag
spi_stat.adc_count++;
PIR1bits.SSPIF = LOW; // clear SPI flags
PIE1bits.SSP1IE = HIGH; // enable to send second byte
SSP1BUF = ADRESH | ((ctmu_button << 4)&0xf3);
adc_buffer[ctmu_button][i_adc] = ADRES;
timer.lt = TIMERDISCHARGE; // set timer to discharge rate
if (++i_adc >= ADC_READS) {
TMR3H = ADRESH | ((ctmu_button << 4)&0xf3); // copy high byte/channel data [4..7] bits
TMR3L = ADRESL; // copy low byte and write to timer counter
i_adc = 0; // reset adc buffer position
CTMU_ADC_UPDATED = TRUE; // New data is in timer3 counter, set to FALSE in main program flow
timer.lt = TIMERPROCESS; // set timer to data processing rate
}
CTMU_WORKING = FALSE; // clear working flag, ok to read timer3 counter.
// config CTMU for next reading
CTMUCONHbits.CTMUEN = 1; // Enable the CTMU
CTMUCONLbits.EDG1STAT = 0; // Set Edge status bits to zero
CTMUCONLbits.EDG2STAT = 0;
CTMUCONHbits.IDISSEN = 1; // drain charge on the circuit
TMR0H = timer.bt[HIGH]; // Write high byte to Timer0
TMR0L = timer.bt[LOW]; // Write low byte to Timer0
}
if (PIE1bits.SSP1IE && PIR1bits.SSPIF) { // SPI port #1 receiver
PIR1bits.SSPIF = LOW;
spi_stat.int_count++;
spi_stat.data_in = SSP1BUF;
PIE1bits.SSP1IE = LOW; // disable so we don't send again
SSP1BUF = ADRESL;
}
if (PIR1bits.TMR1IF) {
PIR1bits.TMR1IF = 0; // clear TMR2 int flag
timer.lt = PDELAY;
TMR1H = timer.bt[HIGH]; // Write high byte to Timer1
TMR1L = timer.bt[LOW]; // Write low byte to Timer1
spi_stat.time_tick++;
}
}
Eight samples per channel are collected as a background process, averaged and examined for noise and stability for a zero baseline and signal trip points. If all signal criteria are go for a good sensor detection a call is made to the flasher function that's just a simple 32 bit circular shift resister with a bit pattern in this test version of software.
Rs-232 debug output via light link @38400bps. Channel, zero set-point and data raw adc trigger values.
C:
int16_t ctmu_touch(uint8_t channel, uint8_t diff_val)
{
int16_t ctmu_change;
uint8_t i;
if (CTMU_ADC_UPDATED) {
finger[channel].moving_val = finger[channel].avg_val;
finger[channel].avg_val = 0;
for (i = 0; i < 8; i++) {
finger[channel].avg_val += adc_buffer[channel][i]&0x03ff;
}
finger[channel].avg_val = finger[channel].avg_val >> (uint16_t) 3;
finger[channel].moving_avg = (finger[channel].moving_val + finger[channel].avg_val) >> (uint16_t) 1;
if (!diff_val) {
return finger[channel].avg_val;
}
ctmu_change = finger[channel].zero_ref - finger[channel].moving_avg; // read diff
return ctmu_change;
} else {
return 0;
}
}
/*
* compute the gesture zero
*/
uint16_t touch_base_calc(uint8_t channel)
{
uint8_t i;
touch_channel(channel);
CTMU_ADC_UPDATED = FALSE;
while (!CTMU_ADC_UPDATED) ClrWdt(); // wait for touch update cycle
finger[channel].avg_val = 0;
finger[channel].zero_noise = 0;
finger[channel].zero_max = adc_buffer[channel][0]&0x03ff;
finger[channel].zero_min = adc_buffer[channel][0]&0x03ff;
for (i = 0; i < 8; i++) {
finger[channel].avg_val += adc_buffer[channel][i]&0x03ff;
if (adc_buffer[channel][i]&0x03ff > finger[channel].zero_max) // look at the noise spreads
finger[channel].zero_max = adc_buffer[channel][i]&0x03ff;
if (adc_buffer[channel][i]&0x03ff < finger[channel].zero_min)
finger[channel].zero_min = adc_buffer[channel][i]&0x03ff;
}
finger[channel].avg_val = finger[channel].avg_val >> (uint16_t) 3;
finger[channel].zero_ref = finger[channel].avg_val;
if ((finger[channel].zero_max - finger[channel].zero_min) > ZERO_NOISE)
finger[channel].zero_noise = 1;
return finger[channel].zero_ref;
}
void touch_channel(uint8_t channel)
{
CTMU_ADC_UPDATED = FALSE;
while (!CTMU_ADC_UPDATED); // wait for touch update cycle
ctmu_button = channel;
CTMU_ADC_UPDATED = FALSE;
while (!CTMU_ADC_UPDATED); // wait for touch update cycle
}
int16_t ctmu_setup(uint8_t current, uint8_t channel)
{
//CTMUCONH/1 - CTMU Control registers
CTMUCONH = 0x00; //make sure CTMU is disabled
CTMUCONL = 0x90;
//CTMU continues to run when emulator is stopped,CTMU continues
//to run in idle mode,Time Generation mode disabled, Edges are blocked
//No edge sequence order, Analog current source not grounded, trigger
//output disabled, Edge2 polarity = positive level, Edge2 source =
//source 0, Edge1 polarity = positive level, Edge1 source = source 0,
//CTMUICON - CTMU Current Control Register
CTMUICON = 0x01; //.55uA, Nominal - No Adjustment default
switch (current) {
case 2:
CTMUICON = 0x02; //5.5uA, Nominal - No Adjustment
charge_time[channel] = TIMERCHARGE_BASE_X10; // faster
break;
case 11:
charge_time[channel] = TIMERCHARGE_BASE_1;
break;
case 12:
charge_time[channel] = TIMERCHARGE_BASE_2;
break;
case 13:
charge_time[channel] = TIMERCHARGE_BASE_3;
break;
case 14:
charge_time[channel] = TIMERCHARGE_BASE_4;
break;
default:
charge_time[channel] = TIMERCHARGE_BASE_3; // slower
break;
}
// timer3 register used for atomic data transfer
T3CONbits.TMR3ON = 0; // Timer is off
T3CONbits.T3RD16 = 1; // enable 16 bit reads/writes
TMR3H = 0;
TMR3L = 0;
return 0;
}
void ctmu_zero_set(void)
{
uint8_t i, max_count;
for (i = 0; i < 4; i++) {
max_count = 0;
do {
touch_base_calc(i);
if (finger[i].zero_noise)
if (++max_count > 64)
break;
} while (finger[i].zero_noise);
}
}
int16_t finger_diff(int16_t finger1, int16_t finger2)
{
return abs(finger1 - finger2);
}
/* bit rotations for 32 bit led motion control */
uint32_t rotl32(uint32_t value, unsigned int count)
{
const unsigned int mask = (CHAR_BIT * sizeof(value) - 1);
count &= mask;
return(value << count) | (value >> ((-count) & mask)); // unary minus warning cheated with -nw=2059
}
uint32_t rotr32(uint32_t value, unsigned int count)
{
const unsigned int mask = (CHAR_BIT * sizeof(value) - 1);
count &= mask;
return(value >> count) | (value << ((-count) & mask));
}
void led_motion(uint8_t mode)
{
FLED0 = mode;
}
int16_t finger_trigger(uint8_t channel_count)
{
static uint32_t roller = ROLL_PATTERN0;
/* check finger trigger conditions */
if (((finger[0].moving_diff > TRIP) && (finger[1].moving_diff > TRIP)) && (finger_diff(finger[0].moving_diff, finger[1].moving_diff) < TRIP_DIFF)) {
led_motion(roller & 0x1);
sprintf(mesg, " %u:%d:%d:%d:%d %d:%d diff %d: rotr %lu\r\n", channel_count, finger[channel_count].zero_ref, (int16_t) finger[channel_count].avg_val,
finger[channel_count].moving_avg, finger[channel_count].moving_val,
finger[0].moving_diff, finger[1].moving_diff, finger_diff(finger[0].moving_diff, finger[1].moving_diff), roller);
puts1USART(mesg);
roller = rotr32(roller, 1);
} else {
led_motion(1);
}
return 0;
}
C:
while (1) { // just loop
/* update error stats */
if (SSP2CON1bits.WCOL || SSP2CON1bits.SSPOV) { // check for overruns/collisions
SSP2CON1bits.WCOL = SSP2CON1bits.SSPOV = 0;
spi_stat.adc_error_count = spi_stat.adc_count - spi_stat.adc_error_count;
spi_stat.last_int_count = spi_stat.int_count;
}
_asm clrwdt _endasm // reset the WDT timer
/* update detector data */
CTMU_ADC_UPDATED = FALSE;
while (!CTMU_ADC_UPDATED); // wait for complete channel touch update cycle
if (ctmu_button == SCAN_MAX_CHAN) DLED0 = !DLED0;
/* clean up some detector noise */
if (finger[channel_count].avg_val > finger[channel_count].zero_ref) {
finger[channel_count].zero_ref = finger[channel_count].avg_val;
}
/* check for finger trips */
finger[0].moving_diff = ctmu_touch(0, 1);
finger[1].moving_diff = ctmu_touch(1, 1);
/* check finger trigger conditions */
finger_trigger(channel_count);
/* reset the finger zeros on a schedule */
if (++channel_count > SCAN_MAX_CHAN) {
channel_count = 0;
if (!recal_count++)
ctmu_zero_set();
}
ctmu_button = channel_count;
}
Attachments
-
11.4 KB Views: 4
Last edited:


