Saturday, February 3, 2018

Arduino LiPo Capacity Tester v3

I had many requests to enhance this project and this is the result:


  • New Menu system
  • Multiple cell LiPo packs up to 4S (use a larger heat sink on the FET)
  • Interval Logging from 0 to 60 secs in CSV format via the USB UART port
  • Arduino controlled current source
  • settable Battery stop voltage
  • settable Battery load current
  • settable logging interval time
  • All settings saved in EEPROM


The new schematic:

And the new sketch:
Arduino LiPo Capacity Tester v3

Sunday, January 28, 2018

Arduino LiPo Capacity Tester

After obtaining a lot of Chinese 18650 batteries labeled "5000 mAh" I decided to find out just how much capacity they DO have.
This tester features an Ardunio Nano, a Nokia 5110 display, a rotary encoder for operation, and a FET current source using a high -power mosFET (IRLZ44).



The schematic:

The current source consists of the IRLZ44 mosFET, and the 10 turn pot which can be any value between 50K and 500K. I used a 500K I had lying around. The FET acts as a variable power resistor to load the test battery.

My Nokia display was a 5 volt version... be sure to check which version you have and power it accordingly... many out there are 3v3 versions.

I tried many different encoder libraries with somewhat dismal success until I found SimonM83 excellent Instructables article. That code is awesome its very 'light' (no libraries and interrupt driven) and is used in this sketch. Thanks Instructables and SimonM83!

Using the Vin on the Nano to power insures accurate ADC measurements since the reference used is the 5 volt rail. Using the USB to power will cause inaccuracies unless you compensate in the sketch.

Download INO file.

Here is the sketch:
/*
* Battery Capacity Calculator
* Uses Nokia 5110 Display and encoder for navigation
* ©2018 Tim Stoddard
*/
/*******Interrupt-based Rotary Encoder parts*******
by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence
*/
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
Adafruit_PCD8544 display =  Adafruit_PCD8544(6, 5, 4);
#define pinA 2 // Encoder (CLK): Our first hardware interrupt pin is digital pin 2
#define pinB 3 // Encoder (DT): Our second hardware interrupt pin is digital pin 3
#define pinC 7 // Encoder switch
#define gateFET 10
#define battPin A0
#define sensePin A1

int printStart = 0;
int eValue = 0;

float mAh = 0.0;
float senseR = 1.0;  // In Ohms - Shunt resistor resistance
float voltRef = 5.00; // Reference voltage for ADCs
float current = 0.0;
float battV = 0.0;
float senseV = 0.0;
float battLow = 3;
unsigned long previousMillis = 0;
unsigned long millisPassed = 0;
//Encoder variables
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
int encoderPos = 0; //this variable stores our current value of encoder position.
int oldEncPos = 0; //stores the last encoder position value
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

void setup() {
  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  pinMode(pinC,INPUT);

  display.begin();
  display.clearDisplay();   // clears the screen and buffer
  display.display();// Update display

  pinMode(gateFET, OUTPUT);
  digitalWrite(gateFET, LOW);

  display.setContrast(50);
  display.clearDisplay();
  display.println("BattCapacity");
  display.println("v2.0");
  display.println("");
  display.print("Tim Stoddard");
//  display.drawRect(0, 0, 84, 48, BLACK);// start x,y then width,height
//  display.fillRect(0, 30, 16, 4, BLACK);
  display.display();
  while (digitalRead(pinC)){continue;}// wait for press and release of switch
  while (!digitalRead(pinC)){continue;}
}
void PinA(){
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
  if(reading == B00001100 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos --; //decrement the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

void PinB(){
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos ++; //increment the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

int getEncoder(int s,int e){
  encoderPos=s;
  display.setTextSize(2);
  display.setCursor(37,32);
  display.setTextColor(BLACK);
  display.print(encoderPos);
  display.display();
  while(digitalRead(pinC)){
    if(oldEncPos != encoderPos) {
      display.setCursor(37, 32);
      display.setTextColor(WHITE);
      display.print(oldEncPos);
      if (encoderPos > e){encoderPos=s;}
      if (encoderPos < s){encoderPos=e;}
      display.setCursor(37,32);
      display.setTextColor(BLACK);
      display.print(encoderPos);
      oldEncPos = encoderPos;
      display.display();
    }
  }
  display.setTextSize(1);
  while (!digitalRead(pinC)){continue;}
  return(encoderPos);
}
void configuration(void){
  display.clearDisplay();
  display.println("BattLow");
  display.print("Current:");
  display.println(battLow);
  display.print("Set 10s");
  display.display();
  battLow=getEncoder(0,3);
  display.clearDisplay();
  display.println("BattLow");
  display.print("Current:");
  display.println(battLow);
  display.print("Set decimal");
  display.display();
  battLow=battLow + float(getEncoder(0,99))/100;
  display.clearDisplay();
  display.println("BattLow");
  display.print("New:");
  display.println(battLow);
  display.print("Click to Exit");
  display.display();
  while (digitalRead(pinC)){continue;}// wait for press and release of switch
  while (!digitalRead(pinC)){continue;}
  }
void completed(void){
  digitalWrite(gateFET, LOW);// turn off FET
  display.clearDisplay();
  display.setCursor(12, 0);
  display.setTextColor(WHITE,BLACK);
  display.println("COMPLETED");
  display.setTextColor(BLACK);
  display.print("Voltage: ");
  display.print(battV);
  display.print("v");
  display.drawLine(0, 20, 84, 20, BLACK);
  display.setCursor(0, 24);
  display.println("Capacity:");
  display.print(mAh);
  display.println("mAh");
  display.display();
  while(digitalRead(pinC)){continue;}
}
void discharge(){
 while(digitalRead(pinC)){
    battV = analogRead(battPin) * voltRef / 1024.0;
    senseV = analogRead(sensePin) * voltRef / 1024.0;
    if(battV < battLow){completed();}
    if(battV >= battLow)
    {
      digitalWrite(gateFET, HIGH);
      millisPassed = millis() - previousMillis;
      current = (senseV / senseR)*1000;
      mAh = mAh + (current) * (millisPassed / 3600000.0);
      previousMillis = millis();

      display.clearDisplay();
      display.println("Discharging");
      display.print("V: ");
      display.println(battV);
      display.print("I: ");
      display.print(current);
      display.println("mA");
      display.println("Capacity:");
      display.print(mAh);
      display.println("mAh");
      display.display();
    }
  }
  digitalWrite(gateFET, LOW);// turn off FET
  delay(500);
}
void loop() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.setCursor(30, 0);
  display.println("MENU");
  display.drawLine(0, 8, 84, 8, BLACK);
  display.setCursor(0, 12);
  display.print("1) BattL=");
  display.println(battLow);
  display.println("2) Start");
  display.display();
  eValue = getEncoder(0,2);
//  encoderPos=0;
  switch(eValue){
    case 1:
      configuration();
      break;
    case 2:
      discharge();
      break;
    default:
      break;
  }
  eValue = 0;
  while (!digitalRead(pinC)){continue;}// wait for release of switch


Sunday, January 29, 2017

LIDAR sensor on Workdesk Assistant (Arduino Nano)

I was excited when I saw that Adafruit got the VL53LOX LIDAR range sensor back in stock and immediately ordered 5 of them. I just received them and already they are sold out again. I can see why! This is a highly accurate range sensor using a laser to measure time-of-flight.

The sensor is very easy to install and set up. I did have issues using the Adafruit libraries so I used the Pololu libraries from GitHub. This library worked perfectly. Just hook up power (5 or 3.3) to the sensor and then the I2C connections (SCL and SDA) to your Arduino. There is example software in the Pololu library, just install that library into your Arduino IDE and select from examples.

The updated Workdesk Assistant sketch is updated to include LIDAR support.

Wednesday, January 18, 2017

NEOs & PIC 12F1840 Proof of Concept

I was looking to add some NEOs to another project and wanted to see if a PIC could be programmed to drive them. Thus the birth of this POC. I took some inspiration from an Adafruit tutorial on writing your own driver for a PIC. This tutorial focused on the RGB version of the NEO (no separate white LED) The PIC used was a PIC 18F4550 clocked at 48Mhz in order to be fast enough for the timing. It's not quite fast enough to technically keep within the specs for the NEOs but it was learned in this tutorial that only the positive pulse width is relevant (within reason) and the author was able to keep that within the variance spec.
This experiment is to see if I can do the same for a much smaller PIC: 12F1840 driven at it's max clock rate of 32Mhz and using the RGBW version of the NEO: An Adafruit NEOPixel stick (8 NEOs with RGBW LEDs). I was able to do it as can be seen in the video:

The PIC is configured to use internal PLL for generating an internal 32 Mhz clock which, with a few coding optimizations is just fast enough to get within specs of the NEOs for that critical positive pulse width. In addition I coded this to use 32 bit format for the RGBW version of the NEO.

So now I can use this same technique to incorporate NEOs into a much smaller (8 pin device) PIC.
The schematic for this POC:

The entire project was coded in XC8 (free version) with one function using in-line assembler to get the speed needed to meet timing requirements for the NEOs.

The zipped MPLABX project package is here.

Sunday, January 8, 2017

Workdesk Assistant

An automated work desk light, AC control, and assistant...

This project came to life when I started wondering if I had left the work desk light on? Is the soldering iron still on? I needed some automation at my desk to reliably determine when I'm at my desk and do a few things:
  1. Switch on the strip light when I sit down.
  2. Give me the ability to turn on the bright LED desk light
  3. Ability to DIM all lighting
  4. Give me the ability to turn on the desk AC (soldering iron, etc)
  5. Ability to use a single touch switch for all activity
  6. Know when I LEAVE the desk and turn off everything.
  7. Give Basic desk temp/humidity
  8. Be expandable for future additions of sensors and probes to assist with development


With the proliferation of inexpensive Arduino clones, I decided to use a $4 Arduino Nano clone available on Amazon for $4... amazing! I used readily available parts and modules from Adafruit ST7735 Display, and the Range Sensor. The MosFETs from Mouser, the LED strip from Amazon. The touch sensor I got from a sensor kit on Amazon, but you could also just use a standard push button. The potentiometer can be pretty much any value from 1k to 100k... I used a 10k slider I had laying around. The 10w LED I got from Electronic Goldmine. Finally, the Powerswitch Tail is the safest way to switch the AC mains!
The MosFETs are SMD but can be easily soldered to a three pin header for breadboarding... these FETs have a very low (2v) gate voltage along with a very low on resistance of about 200 mOhms enabling these to switch high currents without much dissipation. 


The 10 watt LED should be attached to a heat sink... I used a CPU cooler heat sink readily available and then just attached the LED to it using heat-sink tape.
Once you assemble the circuit, load this sketch and have fun! I look to enhance this as I use it and will update this when that happens.

/*
 * Workdesk Assistant
 * Tim Stoddard
 * Released to public domain with this credit given
 * Press switch:
 *  short < 1 sec to toggle between 'panels'
 *  medium 2 sec to light 10w LED on panel 1 only
 *  long 3 sec to turn on AC on panel 1 only
 *  really long 4 sec unused
 *  
 * Note you can define different uses of the presses on each 'panel' 
 * You can code any number of 'panels' ... don't forget to code the activities for that panel
 * as needed.
 */

// Define some pins... 
#define StripLED 5
#define acPlug 4 // controlled AC
#define MainLED 3
#define psw 12
//display
#define sclk 13 // SCL in Sainsmart
#define mosi 11 // SDA
#define cs   10
#define dc   9
#define rst  8
#define bright A0
#define range A1
#define lcdBacklite 6 //LCD pwm dimmer

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#include <dht.h>
dht DHT;
#define DHT11_PIN 7

Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);// instantiate the panel

int v = 0;// variable to hold the ADC value from pot
int r = 0;// variable to hold the ADC range value
bool mLED = false;
bool sLED = false;
bool ACplug = false;
unsigned long rTimeout = 0;
unsigned long dhtTimeout = 0;
unsigned long distTimeout = 0;
unsigned long buttonTime = 0;
unsigned long currentButtonTime = 0;
unsigned long lastButtonTime = 0;
const int numReadings = 100;
const float Vi = 4.95/512;// sonar scaling vcc/512
const float adcV = 4.95/1024;// aDC scaling vcc/1024
uint16_t readings[numReadings];      // the readings from the analog input
uint16_t readIndex = 0;              // the index of the current reading
uint16_t total = 0;                  // the running total
uint16_t average = 0;                // the average
uint16_t currT = 0;
uint16_t lastT = 0;
uint16_t currH = 0;
uint16_t lastH = 0;
float currD = 0;
float lastD = 0;
bool buttonActive = false;
uint16_t buttonState = 0;// button press 1 to 4
uint16_t lastButtonState = 0;// button press 1 to 4
uint16_t displayButtonState = 0;// button press 1 to 4
uint16_t currentButtonState = 0;// button press 1 to 4
uint16_t currentPanel = 0;
uint16_t lastPanel = 1;// forces initial screen write
uint16_t selectPanel = 0;
uint16_t dispMode = 0;// display mode

unsigned long getButtonTime(void){
  if(digitalRead(psw)&& !buttonActive){
    buttonActive = true;
    buttonTime = millis(); // set start of time measurement
  }
  if(buttonActive && (!digitalRead(psw))){
    buttonActive=false;
    return millis()-buttonTime;
  }
  return 0;
}

void setup() {
  // set 3,5 pins as outputs for PWM
  pinMode(StripLED, OUTPUT);
  pinMode(MainLED, OUTPUT);
  pinMode(acPlug, OUTPUT);
  pinMode(psw,INPUT);
  // turn them all off
  digitalWrite(StripLED,LOW);
  digitalWrite(MainLED,LOW);
  digitalWrite(acPlug,LOW);
  analogReference(DEFAULT);// make sure Vref is VCC
  Serial.begin(9600);
    // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip
  int chk = DHT.read11(DHT11_PIN);
  switch(chk)// sync up/initialize the DHT11
  tft.setTextWrap(false);
  tft.fillScreen(ST7735_BLACK);
  tft.setCursor(0,0);
  tft.setTextColor(ST7735_WHITE);
  tft.setTextSize(2);
  panelMain();
//  tft.println(Vi,6);// print the sonar scaling
//  tft.println(adcV,6);// print the adc scaling
}

uint16_t getbuttonState(void){
  currentButtonTime = getButtonTime();
  if(currentButtonTime){
//    lastButtonTime = printValue(lastButtonTime,currentButtonTime,ST7735_YELLOW,10,153,1);
    if(currentButtonTime<500){
      return 1; // short press
    }else if (currentButtonTime<1500){
      return 2; // regular press
    }else if (currentButtonTime<3000){
      return 3; // long press
    }else{
      return 4; // really long press
    }
  }else{
    return 0;  
  }
}
void loop() {
  // check for a button state and act on it ... include checking for a specific panel to issolate 
  // press type to only that panel
  buttonState = getbuttonState();
  if(buttonState){
    displayButtonState = buttonState;// store the button state for display
    switch (buttonState){
      case 1: // short press
        if (++selectPanel>2){selectPanel=0;} // change to increase number of panels
        currentPanel = panel(selectPanel);
        break;
      case 2: // medium press 2 sec
        if(currentPanel==0){mLED=!mLED;} // toggle the 10w LED
        break;
      case 3: // long press 3 sec
        if(currentPanel==0){ACplug=!ACplug;} // toggle the AC line
        break;
      case 4: // really long press 4 sec
        // unused so far
        break;
    }
  }
  lastPanel = printValue(lastPanel,currentPanel,ST7735_BLUE,123,153,1);// display panel number at lower right corner of panel
  lastButtonState = printValue(lastButtonState,displayButtonState,ST7735_GREEN,0,153,1);// display last button state at lower left corner of panel
  
  activity(currentPanel);// do panel activity of currently selected panel

// read the 'dimmer' pot and generate a PWM to dim the LEDs
  v=analogRead(bright);// read the pot
  v=map(v,0,1024,0,255);// map the 10 bit value to 8 bit value for PWM   
  
  /* Get and calculate the range */
  // subtract the last reading:
  total = total - readings[readIndex];
  // read from the sensor:
  readings[readIndex] = analogRead(range);
  // add the reading to the total:
  total = total + readings[readIndex];
  // advance to the next position in the array:
  readIndex = readIndex + 1;
  // if we're at the end of the array...
  if (readIndex >= numReadings) {
    readIndex = 0;// ...wrap around to the beginning:
  }
  r = total / numReadings;// calculate the average distance
  r = (r*adcV)/Vi;// convert to inches  

  // if the range sensor indicates a close presence in inches, turn on the strip LED
  if(r<36){
    analogWrite(StripLED,v);// PWM command brightness is 0-255
    rTimeout = millis();
  }else if(millis() - rTimeout > 30000){ //30 secs timer before turning off LED
    digitalWrite(StripLED,LOW);
    mLED = false;
    ACplug = false;
  }    // Turn on or off the MainLED based on the mLED bool variable
  if(mLED){
    analogWrite(MainLED,v);// PWM command brightness is 0-255
    tft.setCursor(50,153);
    tft.setTextSize(1);
    tft.setTextColor(ST7735_WHITE);
    tft.println("LED");
  }else{
    digitalWrite(MainLED,LOW);
    tft.setCursor(50,153);
    tft.setTextSize(1);
    tft.setTextColor(tft.Color565(64, 64, 64));
    tft.println("LED");
  }
  if(ACplug){
    digitalWrite(acPlug,HIGH);
    tft.setCursor(32,153);
    tft.setTextSize(1);
    tft.setTextColor(ST7735_YELLOW);
    tft.println("AC");
  }else{
    digitalWrite(acPlug,LOW);
    tft.setCursor(32,153);
    tft.setTextSize(1);
    tft.setTextColor(tft.Color565(64, 64, 64));
    tft.println("AC");
  }
}

uint16_t panel(uint16_t i){
  if(currentPanel!=i){
    switch(i){
      case 0:
        panelMain();
        break;      
      case 1:
        panelSensors();// turn on the sensors panel
        lastD = 0;// reset last readings to force update
        lastT = 0;
        lastH = 0;
        break;
      case 2:
        panelBlank();
        break;
        
    }
    // display the last button state at bottom left of display
    tft.setCursor(0,153);
    tft.setTextSize(1);
    tft.setTextColor(ST7735_GREEN);
    tft.print(displayButtonState);
    return i;
  }
}

void activity(uint16_t a){
  switch(a){
    case 1:
      activitySensors();
    break;
  }  
}

void panelMain(){
  tft.fillScreen(ST7735_BLACK);
  tft.setCursor(0,0);
  tft.setTextColor(0x867D);
  tft.setTextSize(2);
  tft.println("Workdesk");
  tft.println("Assistant");

}

void panelBlank(void){
  tft.fillScreen(ST7735_BLACK);
  tft.setCursor(0,64);
  tft.setTextColor(ST7735_BLUE);
  tft.setTextSize(2);
  tft.println("  BLANK");

}

void panelSensors(void){
  tft.fillScreen(ST7735_BLACK);
  tft.setCursor(0,0);
  tft.setTextColor(ST7735_BLUE);
  tft.setTextSize(2);
  tft.println("Temp: ");
  tft.print("Hmty: ");
  tft.setCursor(0,64);
  tft.println("Dist:");
}

void activitySensors(void){
  //update distance
  if(millis()-distTimeout > 250){ // every 250 milliseconds
    lastD = printValue(lastD,r,ST7735_YELLOW,60,64,2);
    distTimeout=millis();
  }

  //update temp and Humity
  if(millis()-dhtTimeout > 5000){ // every 5 seconds
    printDTH();
    dhtTimeout=millis();
  }
}

uint16_t printValue(uint16_t last,uint16_t current,uint16_t color,uint16_t col,uint16_t row,uint16_t textsize){
  if(last != current){
    tft.setTextColor(ST7735_BLACK);
    tft.setTextSize(textsize);
    tft.setCursor(col,row);  
    tft.print(last);
    tft.setTextColor(color);
    tft.setCursor(col,row);  
    tft.print(current);
  }
  return current;
}

void printDTH(void){
  int chk = DHT.read11(DHT11_PIN);
  switch(chk)
  tft.setTextWrap(false);
  currT = DHT.temperature * 1.8 + 32;
  lastT = printValue(lastT,currT,ST7735_YELLOW,60,0,2);
  currH = DHT.humidity;
  lastH = printValue(lastH,currH,ST7735_YELLOW,60,16,2);
}

Sunday, December 25, 2016

PWM experiment on a 10w LED

This is an experiment to illustrate control element dissipation with the use of a PIC 12F1840 operating as a touch switch and PWM to drive a 12v 10 watt LED using a single MosFET (BSS806N) as the driver.

Wednesday, April 27, 2016

Simple Cap Sense Example for PIC 12F1840 in XC8

My wife wanted a simple touch switch battery powered sconce light... sounded like a great fit for a PIC 12F1840... small 8 pin microcontroller with a built in CSM (Cap Sense Module) also sometimes referred to as a CPS module. Attempting to research this was incredibly frustrating! There is just no SIMPLE example out there in either assembler or C! Everywhere I looked was only examples of using the mTouch library, which although a nice library is just far too much over-kill for my needs. Other research from forums suggested that the peripheral is just too complex and tweeky to be of much use. So off on my own... I dug into the manuals and put this simple example of using the CSM module on a PIC12F1840 (and probably the 12F1822 as well) written in XC8 free edition. Here it is in operation.

The PIC12F1840 CSM module is simply an R/C oscillator feeding a frequency counter! You select one of the four available pins as the capacitor input pin for the oscillator, setup timer 0 as the frequency counter time base, and timer 1 as the counter to count the R/C oscillator. The time base (Timer 0) is set to interrupt the PIC at specific intervals and it also gates the R/C pulses going to Timer 1. That interval should be long enough to capture a large count in Timer 1 from the R/C oscillator. The system clock is configured for internal 4MHz and Timer 0 is configured for a prescaling of 256. This gives a count of around 33,000 in Timer 1 (16 bit counter) or about half way through. So for every time interval of Timer 0 interrupting the PIC, Timer 1 will have a count of about 33,000 in it. Now in the interrupt routine you just read Timer 1 into a global variable, reset Timer 1 to zero, and re-enable interrupts for the next read. In the main body of code you just loop and watch the Timer 1 value stored in the global variable (touch in my case) and act on that value when it changes. Simple! If there is any change in the R/C oscillator's cap sense pin (like a finger, or other larger object), that will change the capacitance on that pin and shift the oscillator down ... Timer 1 will reflect that with a smaller count. Now just have your code watch for the change to a smaller count and you have a touch switch.

Here are the XC8 files out of the MPLABX IDE:

/***********************************/
/* Simple PIC 12F1840 Touch Switch Example */
/* main.c - Tim Stoddard, 2016  */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>        /* For uint8_t definition */
#include <stdbool.h>       /* For true/false definition */

#include "system.h"        /* System funct/params, like osc/peripheral config */
#include "user.h"          /* User funct/params, such as InitApp */

/***********************************/
/* User Global Variable Declaration */
/***********************************/

/* i.e. uint8_t <variable_name>; */

/***********************************/
/* Main Program  */
/***********************************/
void main(void)
{
    /* Configure the oscillator for the device */
    ConfigureOscillator();

    /* Initialize I/O and Peripherals for application */
    InitApp();

   /* Flash the LAMP a few times */
    for(i=0;i<5;i++){
        LAMP=1;
        __delay_ms(50);
        LAMP=0;
        __delay_ms(200);
    }
    /* grab the latest count from Timer 1 (touch), subtract a threshold amount,
        and save it (touch_cal) */
    touch_cal=touch-1000;

    /* MAIN LOOP */
    while(1)
    {
        if(touch<touch_cal){// sensor is touched
            LAMP=1;// turn it on
        }else{// otherwise ...
            LAMP=0;// turn it off
        }
    }

}


/***********************************/
/* user.c */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>         /* For uint8_t definition */
#include <stdbool.h>        /* For true/false definition */

#include "user.h"
/***********************************/
/* User Functions */
/***********************************/

void InitApp(void)
{
    /* Setup analog functionality and port direction */
       TRISA = 0b00111011; // RA2 is output
       ANSA4=1;
    /* Initialize peripherals */
       CM1CON1=0;
       CPSCON0bits.CPSRM=0;// 1=variable voltage ref, 0=fixed
       CPSCON0bits.CPSRNG=0b11;// high current
       CPSCON0bits.CPSON=1;
       CPSCON1bits.CPSCH=0b11;//CPS3 selected
       //Timer 0 is the time base for CPS
       TMR0CS=0;//Timer 0 used as time base for CPS select Fosc/4
       OPTION_REGbits.PS=0b111;
       OPTION_REGbits.PSA=0;
       // Timer 1 is the freq counter for CPS
       T1CON=0b11000101; // TMR1 capacitive sensing osc, prescaler 1/1, dedicated osc disabled,no synch,timer1 enabled
       T1GSEL=0b01;// set timer 1 gate for TMR0 overflow
    /* Enable interrupts */
       TMR0IE=1;// enable TMR0 interrupts
       PEIE=1;//enable Peripheral interrupts
       GIE=1;// enable global interrupts
}


/***********************************/
/*interrupts.c */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>         /* For uint8_t definition */
#include <stdbool.h>        /* For true/false definition */
#include "user.h"
/***********************************/
/* Interrupt Routines */
/***********************************/

#ifndef _PIC12

void interrupt isr(void)
{
 #if 1

    GIE=0;// disable all interrupts

    if(T0IF) // Timer 0 interrupt?
    {
        TMR1ON=0;// turn off timer 1
        touch=TMR1L+(unsigned int)(TMR1H << 8);// read timer 1
        TMR1H=0;// clear timer 1
        TMR1L=0;
        TMR1ON=1;// enable timer 1
        T0IF=0;
    }else{
        /* Unhandled interrupts */
    }
    GIE=1;// enable all interrupts

#endif

}
#endif


/***********************************/
/* user.h */
/***********************************/
#define _XTAL_FREQ  4000000

#define LAMP RA2

void InitApp(void);         /* I/O and Peripheral Initialization */

/* declare variables */
unsigned int touch;
unsigned int touch_cal;
unsigned int i;


/***********************************/
/* configuration_bits.c */
/***********************************/

#if defined(__XC)
    #include <xc.h>         /* XC8 General Include File */
#elif defined(HI_TECH_C)
    #include <htc.h>        /* HiTech General Include File */
#endif


// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

/***********************************/
/* system.c */
/***********************************/

#include <xc.h>         /* XC8 General Include File */
#include <stdint.h>        /* For uint8_t definition */
#include <stdbool.h>       /* For true/false definition */

#include "system.h"

void ConfigureOscillator(void)
{
    while (!HFIOFS) continue;// wait for stable osc

#if 0

    OSCCAL=_READ_OSCCAL_DATA(); /* _READ_OSCCAL_DATA macro unloads cal memory */

#endif
}

/***********************************/
/* system.h */
/***********************************/

/* TODO Define system operating frequency */

/* Microcontroller MIPs (FCY) */
#define SYS_FREQ        4000000L
#define FCY             SYS_FREQ/4

/***********************************/
/* System Function Prototypes */
/***********************************/

void ConfigureOscillator(void); /* Handles clock switching/osc initialization */