Asynchronous (poll based) touch sensing

Status
Not open for further replies.

Bryan42

Active member
Recently I've had the desire to add touch sensing to a project involving a Teensy 3.2 (or 3.6) but need the touch reads to be non-blocking due to timing constraints (each call to the main loop needs to complete in roughly 100 uSecs). However, a call to touchRead() can take milliseconds, if not longer, depending on the capacitance of the sensor. Perusing the prjc.com website and all the code in the libraries, I was not able to find any mention of non-blocking/asynchronous support for touch tensing. Surely I'm not the only one that has such a need!

So I borrowed the code from touch.c (from Paul's Teensy library) and made two routines: A startTouchRead() routine to initiate touch sensing on a given pin, and a touchReady() routine that checks to see if the sensing is complete. Both routines are non-blocking, so the sensing can take as long as needed, and you can simply add a test for completion in your main loop as you see fit.

While it appears possible to have interrupt-based touch sensing according to the NXP documentation, I chose not to go that route due to possible interference with some time-critical 10 kHz ADC sampling I had in my project, and didn't want the possibility of interrupting the ADC and all the complications that brings. (Yes I know ADC's can be restarted if interrupted.) Instead, I decided to initiate touch sensing in the ISR routine of the ADC at appropriate times, and then poll for touch sensing completion in my main loop. That seems far simpler and serves my needs.

I figured others might find the routines for asynchronous touch sensing useful. So here's the contents of the modified file that I call asynctouch.c. It was tested on a Teensy 3.2, and most of the code was left intact from the original touch.c file. I show an example of its usage in the file itself. CAVEAT: This code has not been tested much at all. I just got it working this morning!

Code:
/* 
 * Asynchronous (aka polling) touch read routines, as modified by Bryan F.
 * (Bry) from the Teensy core library file touch.c. I've taken the functions
 * from that file, and modified them to be non-blocking:
 * 
 * The following non-blocking routine starts the read process for the given
 * pin. It returns the TSI channel number for the touch pin, or 255 if the
 * pin is invalid.
 *
 * uint8_t touchStartRead(uint8_t pin);
 *
 * The following non-blocking routine returns the touch value data for channel
 * ch, or -1 if the touch data is not yet ready for that channel ch.
 *
 * int touchReady(uint8_t ch);
 *
 * Example usage:
 *
 * extern "C" uint8_t touchStartRead(uint8_t pin);
 * extern "C" int touchReady(uint8_t ch);
 *
 * int touchPin = 22; // (A8 pin on Teensy 3.2)
 * int state = 0;     // 0: ready to start next read, 1: waiting to finish
 * int ch;            // TSI channel number
 *
 * void loop() 
 * {
 *    if (state == 0)  // starting state
 *    {
 *       ch = touchStartRead(touchPin);
 *       if (ch == 255) Serial.println("Invalid touch pin");
 *       else state = 1; // ready to poll
 *    }
 *    else // non-blocking polling
 *    {
 *       int v = touchReady(ch); // Be sure to use the channel num, not pin num.
 *       if (v == -1)
 *       {
 *          // Not yet ready
 *       }
 *       else
 *       {
 *          Serial.print("touch val read: "); Serial.println(v);
 *          state = 0; // ready to initiate next read
 *       }
 *
 *       delay(100); // Just for illustration purposes
 *    }
 * }
 *
 *
 *
 * Old prjc.com comments:
 *
 * Teensyduino Core Library
 * http://www.pjrc.com/teensy/
 * Copyright (c) 2017 PJRC.COM, LLC.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * 1. The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * 2. If the Software is incorporated into a build system that allows
 * selection among a list of target devices, then similar target
 * devices manufactured by PJRC.COM must be included in the list of
 * target devices and selectable in the same manner.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
 
// Bry says: The following code is pretty much left intact from touch.c,
// except modified for non-blocking reads.

#include "core_pins.h"
//#include "HardwareSerial.h"

#if defined(HAS_KINETIS_TSI) || defined(HAS_KINETIS_TSI_LITE)

#if defined(__MK20DX128__) || defined(__MK20DX256__)
// These settings give approx 0.02 pF sensitivity and 1200 pF range
// Lower current, higher number of scans, and higher prescaler
// increase sensitivity, but the trade-off is longer measurement
// time and decreased range.
#define CURRENT   2 // 0 to 15 - current to use, value is 2*(current+1)
#define NSCAN     9 // number of times to scan, 0 to 31, value is nscan+1
#define PRESCALE  2 // prescaler, 0 to 7 - value is 2^(prescaler+1)
static const uint8_t pin2tsi[] = {
//0    1    2    3    4    5    6    7    8    9
  9,  10, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
255, 255,  14,  15, 255,  12, 255, 255, 255, 255,
255, 255,  11,   5
};

#elif defined(__MK66FX1M0__)
#define NSCAN     9
#define PRESCALE  2
static const uint8_t pin2tsi[] = {
//0    1    2    3    4    5    6    7    8    9
  9,  10, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
255, 255,  14,  15, 255, 255, 255, 255, 255,  11,
 12, 255, 255, 255, 255, 255, 255, 255, 255, 255
};

#elif defined(__MKL26Z64__)
#define NSCAN     9
#define PRESCALE  2
static const uint8_t pin2tsi[] = {
//0    1    2    3    4    5    6    7    8    9
  9,  10, 255,   2,   3, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
255, 255,  14,  15, 255, 255, 255
};

#endif

// output is approx pF * 50
// time to measure 33 pF is approx 0.25 ms
// time to measure 1000 pF is approx 4.5 ms

// Bry: Used to return int touch value. Now returns channel num.
uint8_t touchStartRead(uint8_t pin) 
{
	uint32_t ch;

	if (pin >= NUM_DIGITAL_PINS) return 255;  // Bry: Indicates bad pin
	ch = pin2tsi[pin];
	if (ch == 255) return 255;  // Bry: Indicates bad pin

	*portConfigRegister(pin) = PORT_PCR_MUX(0);
	SIM_SCGC5 |= SIM_SCGC5_TSI;
#if defined(HAS_KINETIS_TSI)
	TSI0_GENCS = 0;
	TSI0_PEN = (1 << ch);
	TSI0_SCANC = TSI_SCANC_REFCHRG(3) | TSI_SCANC_EXTCHRG(CURRENT);
	TSI0_GENCS = TSI_GENCS_NSCN(NSCAN) | TSI_GENCS_PS(PRESCALE) | TSI_GENCS_TSIEN | TSI_GENCS_SWTS;
	delayMicroseconds(10);
	// Bry removed: while (TSI0_GENCS & TSI_GENCS_SCNIP) ; // wait
	// Bry removed: delayMicroseconds(1);
	// Bry removed: return *((volatile uint16_t *)(&TSI0_CNTR1) + ch);
#elif defined(HAS_KINETIS_TSI_LITE)
	TSI0_GENCS = TSI_GENCS_REFCHRG(4) | TSI_GENCS_EXTCHRG(3) | TSI_GENCS_PS(PRESCALE)
		| TSI_GENCS_NSCN(NSCAN) | TSI_GENCS_TSIEN | TSI_GENCS_EOSF;
	TSI0_DATA = TSI_DATA_TSICH(ch) | TSI_DATA_SWTS;
	delayMicroseconds(10);
	// Bry removed: while (TSI0_GENCS & TSI_GENCS_SCNIP) ; // wait
	// Bry removed: delayMicroseconds(1);
	// Bry removed: return TSI0_DATA & 0xFFFF;
#endif

    return ch; // Bry: This is the parm to pass to touchReady()
}

// Bry Added this routine

int touchReady(uint8_t ch)
{
#if defined(HAS_KINETIS_TSI)
	if (TSI0_GENCS & TSI_GENCS_SCNIP) return -1; // not ready
	delayMicroseconds(1);
	return *((volatile uint16_t *)(&TSI0_CNTR1) + ch);
#elif defined(HAS_KINETIS_TSI_LITE)
	if (TSI0_GENCS & TSI_GENCS_SCNIP) return -1; // not ready
	delayMicroseconds(1);
	return TSI0_DATA & 0xFFFF;
#endif
	
}

#else

uint8_t touchStartRead(uint8_t pin)
{
  return 255; // no Touch sensing :(
}

int touchReady(uint8_t ch)
{
	return -1; // no Touch sensing :(
}

#endif
 
Well, I've discovered one major flaw with this code: You can only use one touch sensor at a time. You can't start the reading of, say two sensors, and then wait for either to complete. Looking at the low level code, each call to touchStartRead() appears to reset the touch sensing and configures it for a given channel, so any pending reads get lost. Will see if I can tweak the low level code to start the reading on more than one channel at a time. I can see lots of problems with doing this though. What if one sensor read is pending while another is finished and then needs to be started again? Will have to read the NXP documentation very carefully to see if what I'm trying to do is even possible. I know it does talk about how it sweeps through all touch sensors as part of its major hardware loop.

Any ideas out there? Surely others have wanted/needed to do non-blocking polling calls for multiple touch sensors. Remember that I am trying to do this without using interrupts. Even with interrupts it's not clear how you can have mixed states of pending reads, finished reads, etc.
 
Figured out a way to do what I wanted. Indeed it seems the idea with the Teensy touch sensors is to enable the pins you'd like to read, all at once, using the TSIO_PEN register, (and also set up the port mux for each pin you want), start a scan which scans all enabled pins, check for that scan being ready (I use a non-blocking way), and then you can read pin data from a control register one by one. I wrote a class to do this and have some proof of concept code working. Will post this code in a message as soon as I can test it some more. Maybe others will find it useful.
 
Async Touch Manager class

Okay, so I've figured out how to do asynchronous touch sensing of multiple pins, without DMA or interrupts. I learned that the Teensy 3.1/3.2 devices have a different touch sensing architecture than the Teensy LC and 3.6 devices do. For the former, apparently the enabled pins are scanned all at once in the hardware, and then you can read each pin's data later. For the latter devices, you can only scan one pin at a time, (though the manuals seem to hint at doing all enabled pins via DMA). So I've created an AsyncTouchMgr class to handle the scanning, a different version for each kind of device (one for Teensy 3.1/3.2 and one for Teensy LC/3.6).

The basic idea with this class is you call an EnablePin function to enable each pin you'd like to have touch sensing on, and then start a scan sequence. In your main loop, you call the Ready() function to see if the scan sequence has finished, and then can read the data for each pin, and then start a new scan sequence. I've defined the different versions of AsyncTouchMgr to work the same way at the high level, though underneath they are quite different. (The one for Teensy 3.1/3.2 is much simpler.)

Here's the code for the class:


Code:
#pragma once

// /////////////////////////////////////////////////////////////////////
// Asynchronous Touch Sensor Management, by Bryan F. Lots of low level
// code borrowed from PRJC's touch.c file.

// /////////////////////////////////////////////////////////////////////
// Teensy 3.2 (and 3.0, 3.1) section. Only Teensy 3.2 has been tested.
// /////////////////////////////////////////////////////////////////////

extern "C" 
{
  #include "core_pins.h"
}

#if defined(HAS_KINETIS_TSI)

// MK20DX128: Teensy 3.0
// MK20DX256: Teensy 3.1, Teensy 3.2

#if defined(__MK20DX128__) || defined(__MK20DX256__)
// These settings give approx 0.02 pF sensitivity and 1200 pF range
// Lower current, higher number of scans, and higher prescaler
// increase sensitivity, but the trade-off is longer measurement
// time and decreased range.
#define CURRENT   2 // 0 to 15 - current to use, value is 2*(current+1)
#define NSCAN     9 // number of times to scan, 0 to 31, value is nscan+1
#define PRESCALE  2 // prescaler, 0 to 7 - value is 2^(prescaler+1)
static const uint8_t pin2tsi[] = {
//0    1    2    3    4    5    6    7    8    9
  9,  10, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
255, 255,  14,  15, 255,  12, 255, 255, 255, 255,
255, 255,  11,   5
};

#endif

// For Teensy 3.2 (and 3.1 & 3.0 I believe)
// For these boards, the touch pins are scanned all at once, 
// (for those touch pins that have been enabled). So the strategy
// is to enable each desired pin with separate calls to EnablePin(),
// and then start at scan using StartScan(), and then check for 
// scan completion using Ready(), and then each pin's data can be
// read using ReadPin().

class AsyncTouchMgr {
public:

  uint32_t enable_mask;

  AsyncTouchMgr()
  : enable_mask(0)
  {
  }
  
  uint8_t Pin2TSI(uint8_t pin)
  {
     // 255 means bad pin number
     uint8_t ch = 255;
     if (pin < NUM_DIGITAL_PINS)
     {
        ch = pin2tsi[pin];
     }
     return ch;
  } 
  
  void ClearPinEnables()
  {
    enable_mask = 0;
    TSI0_PEN = enable_mask;
  }
  
  void EnablePin(uint8_t pin)
  {
    uint8_t ch = Pin2TSI(pin);
     
    if (ch != 255)
    {
       *portConfigRegister(pin) = PORT_PCR_MUX(0);
       enable_mask |= (1 << ch);
    }
  }
  
  void StartScan()
  {
    SIM_SCGC5 |= SIM_SCGC5_TSI;
    TSI0_GENCS = 0;
    TSI0_PEN = enable_mask; // Might have multiple bits set
    TSI0_SCANC = TSI_SCANC_REFCHRG(3) | TSI_SCANC_EXTCHRG(CURRENT);
    TSI0_GENCS = TSI_GENCS_NSCN(NSCAN) | TSI_GENCS_PS(PRESCALE) | TSI_GENCS_TSIEN | TSI_GENCS_SWTS;
    delayMicroseconds(10); // This was in original touch.c code, so we left it in.
  }
  
  bool Ready()
  {
    if (TSI0_GENCS & TSI_GENCS_SCNIP) return false;
    delayMicroseconds(1); // In original touch.c code. So we left it in.
    return true;
  }
  
  void WaitForReady()
  {
    // In case you *do* want to block
    while(!Ready()) { };
  }
  
  int ReadPin(uint8_t pin)
  {
    uint8_t ch = Pin2TSI(pin);
      
    if (ch != 255)
    {
       return *((volatile uint16_t *)(&TSI0_CNTR1) + ch);
    }
    else return -1; // We use this to indicate invalid pin
  }
};

// //////////////////////////////////////////////////////
// Teensy LC and 3.6 section. Only Teensy 3.6 tested.
// //////////////////////////////////////////////////////

#elif defined(HAS_KINETIS_TSI_LITE)

// MK66FX1M0: Teensy 3.6
#if defined(__MK66FX1M0__)
#define NSCAN     9
#define PRESCALE  2
static const uint8_t pin2tsi[] = {
//0    1    2    3    4    5    6    7    8    9
  9,  10, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
255, 255,  14,  15, 255, 255, 255, 255, 255,  11,
 12, 255, 255, 255, 255, 255, 255, 255, 255, 255
};

// MKL26Z64: Teensy LC
#elif defined(__MKL26Z64__)
#define NSCAN     9
#define PRESCALE  2
static const uint8_t pin2tsi[] = {
//0    1    2    3    4    5    6    7    8    9
  9,  10, 255,   2,   3, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
255, 255,  14,  15, 255, 255, 255
};

#endif

// For Teensy 3.6 and Teensy LC. For these chips, it looks like only
// one pin can be scanned at a time. (Well, it appears DMA could be
// used to scan all automatically, but we won't do that here.)
// So we provide support to select desired pins and then scan them
// one at a time. A bit awkward, but requires no interrupts or
// exotic DMA.

class AsyncTouchMgr {
public:

  uint16_t data[16];    // one element for each channel, not all necessarily used.
  // uint8_t pins[16];     // pin map. one element for each enabled pin.
  uint8_t channels[16]; // channel map. one element for each enabled channel.
  uint8_t num_enabled;
  uint8_t curr_slot;    // Current pin/channel map slot
  uint8_t curr_ch;      // And it's corresponding channel
  
public:

  AsyncTouchMgr()
  {
    ClearPinEnables();
  }
  
  void ClearPinEnables()
  {
    for (int i = 0; i < 16; i++)
    {
        data[i] = 0;
        // pins[i] = 0;
        channels[i] = 0;
    }
      
    curr_slot = 0;
    curr_ch = 0;

    num_enabled = 0;
  }
 
  uint8_t Pin2TSI(uint8_t pin)
  {
    // 255 means bad pin number
    uint8_t ch = 255;
    if (pin < NUM_DIGITAL_PINS)
    {
        ch = pin2tsi[pin];
    }
    return ch;
  } 
 
  void EnablePin(uint8_t pin)
  {
    uint8_t ch = Pin2TSI(pin);
 
    if (ch != 255)
    {
      // pins[num_enabled] = pin;
      channels[num_enabled] = ch;
      ++num_enabled;
      
      *portConfigRegister(pin) = PORT_PCR_MUX(0);
    }
  }
  
  void StartScan()
  {
    ScanFirstSlot();
  }
  
  bool Ready()
  {
    // Only returns true if all enabled pins have been scanned
    
    if (InnerReady())
    {
       // Store the results of current pin, start the scan
       // of the next pin, if any
       StoreResults();            
       if (!ScanNextSlot())
       {
          // Finished scanning all enabled pins,
          // so we are indeed ready
          return true;
       }
       
       return false; // Not done with all.
    }
  }
  
  void WaitForReady()
  {
    // In case you *do* want to block
    while(!Ready()) { };
  }
  
  int ReadPin(uint8_t pin)
  {
    // To be called after all enabled pins read
      
    uint8_t ch = Pin2TSI(pin);
      
    if (ch != 255)
    {
       return data[ch];
    }
    else return -1; // indicates invalid pin
  }
  
 protected:  // Low level code
 
  bool ScanFirstSlot()
  {
    curr_slot = 0;
    return ScanNextSlot();
  }
  
  bool ScanNextSlot()
  {
    if (curr_slot == num_enabled)
    {
       return false;
    }
    else
    {
       // uint8_t pin = pins[curr_slot];    
       curr_ch = channels[curr_slot];
       ++curr_slot;
        
       //Serial.print("** Pin, ch: "); Serial.print(pin); Serial.print(", "); Serial.println(ch);
        
       SIM_SCGC5 |= SIM_SCGC5_TSI;
       TSI0_GENCS = TSI_GENCS_REFCHRG(4) | TSI_GENCS_EXTCHRG(3) | TSI_GENCS_PS(PRESCALE)
         | TSI_GENCS_NSCN(NSCAN) | TSI_GENCS_TSIEN | TSI_GENCS_EOSF;
       TSI0_DATA = TSI_DATA_TSICH(curr_ch) | TSI_DATA_SWTS;
       delayMicroseconds(10); // In touch.c code, so we left it in.
       return true;
    }
  }

  bool InnerReady()
  {
    // Is current channel being scanned ready
    if (TSI0_GENCS & TSI_GENCS_SCNIP) return false;
    delayMicroseconds(1); // In touch.c code, so we left it in.
    return true;
  }
 
  void StoreResults()
  {
    uint16_t v = TSI0_DATA & 0xFFFF; // Data for current channel
    data[curr_ch] = v;
  }
  
};

#else

#pragma message("AsyncTouchMgr not supported for this device."

#endif


And here's an example sketch using it. The proper version of the class is selected automatically via define statements.


Code:
#include "AsyncTouchMgr.h"

uint8_t ledPin = LED_BUILTIN;

AsyncTouchMgr TouchMgr;

void setup() 
{
  Serial.begin(38400);
  delay(500);
  
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  Serial.println("Enabling pins");
  TouchMgr.EnablePin(18);
  TouchMgr.EnablePin(19);
  TouchMgr.EnablePin(22);
  TouchMgr.EnablePin(23);

  Serial.println("Starting scans");
  TouchMgr.StartScan(); // Kick things off
}

void loop() 
{
  if (TouchMgr.Ready()) // Non blocking
  {
     int b1 = TouchMgr.ReadPin(18);
     int b2 = TouchMgr.ReadPin(19);
     int b3 = TouchMgr.ReadPin(22);
     int b4 = TouchMgr.ReadPin(23);

     Serial.print(b1); Serial.print(", ");
     Serial.print(b2); Serial.print(", ");
     Serial.print(b3); Serial.print(", ");
     Serial.println(b4);

     TouchMgr.StartScan(); // For next time 'round
  }

  delay(250); // simulating other stuff being done.
}
 
That looks interesting and promising. I looked at it briefly to get the interrupt on completion to fire and failed to see it before moving on.

The other thing I was looking at was registering a threshold for each pin and having a return of T or F for button pressed.
 
That looks interesting and promising. I looked at it briefly to get the interrupt on completion to fire and failed to see it before moving on.

The other thing I was looking at was registering a threshold for each pin and having a return of T or F for button pressed.

1) I specifically am not using interrupts in the code above. Indeed that was a key part of the design.


2) Yes, a person could easily add support for button press and release logic. I didn't do that here, since the point was to show how to handle asynchronous sensing on multiple pins w/o dma or interrupts. It's not too hard to add button press / release / calibrate code to the class. I've done that for my own projects since the code above was posted. But this sort of thing tends to be project specific in regards on how to design it / what tradeoffs to make / what features to include. There a many ways to go about it. At the very minimum, calibrate code could easily be added that loops a bunch of times and records max values on each of the pins, when no buttons are being touched.
 
Indeed - saw it was not using interrupts - just generally split the activate read across the wait, and in the case of the all read it saves multiple activate/waits. Good work.

Wasn't sure if the goal was button/bounce type replacement. Indeed when I was using touchread I did untouched reads and set a threshold for a function - seemed a common thing.
 
Indeed - saw it was not using interrupts - just generally split the activate read across the wait, and in the case of the all read it saves multiple activate/waits. Good work.

Wasn't sure if the goal was button/bounce type replacement. Indeed when I was using touchread I did untouched reads and set a threshold for a function - seemed a common thing.

The main goal was to get rid of overhead in the main loop. The touch reads can take milliseconds, which was way too much, since the main loop in my current project has a timing constraint of 100 micro-seconds. I was just surprised nobody had addressed having a non-blocking way of reading the touch pins. At least I couldn't find any such code, so I thought others might find it as a useful basis for writing their own touch code.


Maybe when I get a few micro-seconds of my own time (ha ha) I can post an example class that has more button like logic.
 
Touch is a cool thing with Teensy - there is the dual pin touch CapacitiveSensor - and the touchRead() single pin - I didn't find any samples or docs for the touchRead() version - just the source code you found.
 
The Teensy touch support is indeed cool. Having a lot of fun with it. Don't know if I'm going to use it in my current project, but it's good to investigate the possibilities. Seems like an inexpensive way to put in "buttons".
 
Indeed touchRead seemed a quick way to put a test button on with just a simple resistor so I wanted to see it work for that.

I see your other thread Why-the-delay-in-touchRead(). Hopefully that will get some answer. I was thinking I'd test your code here removing those delay()'s - perhaps doing a touchRead() immediately after the read from your code to compare - though even subsequent touchRead() calls can vary with sustained touch or repeat reads.

The way your code Activates then returns to read should replace at least the 10 us delay. I assume it is some settling time - but that isn't clear as the wait test won't go until done and after done the data should be secured.
 
Okay, then. I've refined the asynchronous touch class code, and added another class to boot that manages the "state" of the touch pads: NotBeingTouched, RisingEdge, FallingEdge, and BeingTouched.

First, here's the new touch manager class. Included in this class is a fix that gets rid of seemingly unnecessary 10 microsecond delays after starting a scan. See post in the Technical Support forum.

Code:
#pragma once

// ///////////////////////////////////////////////////////////////////////
// Asynchronous Touch Sensor Management, by Bryan Flamig. The low level
// register level code borrowed from PRJC's touch.c file.
// *** Dec 13, 2018 version. ***

// ///////////////////////////////////////////////////////////////////////
// Teensy 3.2 (and 3.0, 3.1) section. Only Teensy 3.2 has been tested.
// /////////////////////////////////////////////////////////////////////

extern "C"
{
#include "core_pins.h"
}

#if defined(HAS_KINETIS_TSI)

// MK20DX128: Teensy 3.0
// MK20DX256: Teensy 3.1, Teensy 3.2

#if defined(__MK20DX128__) || defined(__MK20DX256__)
// These settings give approx 0.02 pF sensitivity and 1200 pF range
// Lower current, higher number of scans, and higher prescaler
// increase sensitivity, but the trade-off is longer measurement
// time and decreased range.
#define CURRENT   2 // 0 to 15 - current to use, value is 2*(current+1)
#define NSCAN     9 // number of times to scan, 0 to 31, value is nscan+1
#define PRESCALE  2 // prescaler, 0 to 7 - value is 2^(prescaler+1)
static const uint8_t pin2tsi[] = {
  //0    1    2    3    4    5    6    7    8    9
  9,  10, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
  255, 255,  14,  15, 255,  12, 255, 255, 255, 255,
  255, 255,  11,   5
};

#endif

// For Teensy 3.2 (and 3.1 & 3.0 I believe)
// For these boards, the touch pins are scanned all at once,
// (for those touch pins that have been enabled). So the strategy
// is to enable each desired pin with separate calls to EnablePin(),
// and then start at scan using StartScan(), and then check for
// scan completion using Ready(), and then each pin's data can be
// read using ReadPin().

class AsyncTouchMgr {
  public:

    uint32_t enable_mask;

    // High level flags to make this class look more like the Teensy 3.6/LC class

    bool scanning;        // We set this flag during scanning so we don't try
                          // to start another one too soon.
    bool data_ready;

    AsyncTouchMgr()
    : enable_mask(0)
    , scanning(false)
    , data_ready(false)
    {
    }

    uint8_t Pin2TSI(uint8_t pin)
    {
      // 255 means bad pin number
      uint8_t ch = 255;
      if (pin < NUM_DIGITAL_PINS)
      {
        ch = pin2tsi[pin];
      }
      return ch;
    }

    void ClearPinEnables()
    {
      enable_mask = 0;
      TSI0_PEN = enable_mask;
    }

    void EnablePin(uint8_t pin)
    {
      uint8_t ch = Pin2TSI(pin);

      if (ch != 255)
      {
        // Make the connection to the touch machinery
        *portConfigRegister(pin) = PORT_PCR_MUX(0);
        enable_mask |= (1 << ch);
      }
    }

    void StartScan()
    {
      // Don't start a new one if old scan hasn't finished.
      // You'll get overrun errors if you do.
      
      if (!scanning)
      {
         data_ready = false;
         scanning = true;
         SIM_SCGC5 |= SIM_SCGC5_TSI;
         TSI0_GENCS = 0;
         TSI0_PEN = enable_mask; // Might have multiple bits set
         TSI0_SCANC = TSI_SCANC_REFCHRG(3) | TSI_SCANC_EXTCHRG(CURRENT);
         TSI0_GENCS = TSI_GENCS_NSCN(NSCAN) | TSI_GENCS_PS(PRESCALE) | TSI_GENCS_TSIEN | TSI_GENCS_SWTS;

         // The following delay was in the original touch.c code. Should we leave it in?
         // UPDATE: It looks like we need *some* kind of delay, *somewhere.* It could be a 
         // startup thing, where the readings don't work (you'll get zeros, worst case)
         // until we've fired up ADC's, timer's, etc.
         // UPDATE: OR, put a long delay (like several milliseconds, perhaps 10?) in after
         // enabling all touch pins and muxes. That seems to work, and doesn't eat into loop time.
         // UPDATE #2: Not true. You *need* some kind of delay here, right after starting scan.
         // UPDATE #3: Not true. If you use the EOSF flag to detect end of scanning, no delay needed.
         
         //delayMicroseconds(10);
         //delayMicroseconds(1);    // This "works on my machine" (ha!)
      }
    }

    bool Ready()
    {
      if (!data_ready)
      {
         #if 0
         // old way, using "scan in progress". Requires the delay at start of scan and after.
         if (TSI0_GENCS & TSI_GENCS_SCNIP) return false;
         delayMicroseconds(1); // In original touch.c code. Should we leave it in?
         #else
         // better way using "end of scan". Doesn't need the delay at start of scan or after.
         if (!(TSI0_GENCS & TSI_GENCS_EOSF)) return false;
         TSI0_GENCS |= TSI_GENCS_EOSF; // clear flag (write 1)
         #endif
        
         data_ready = true;
         scanning = false;
      }

      return data_ready;
    }

    void WaitForReady()
    {
      // In case you *do* want to block
      while (!Ready()) { };
    }

    int ReadPin(uint8_t pin)
    {
      uint8_t ch = Pin2TSI(pin);

      if (ch != 255)
      {
        return *((volatile uint16_t *)(&TSI0_CNTR1) + ch);
      }
      else return -1; // We use this to indicate invalid pin
    }
};

// //////////////////////////////////////////////////////
// Teensy LC and 3.6 section. Only Teensy 3.6 tested.
// //////////////////////////////////////////////////////

#elif defined(HAS_KINETIS_TSI_LITE)

// MK66FX1M0: Teensy 3.6
#if defined(__MK66FX1M0__)
#define NSCAN     9
#define PRESCALE  2
static const uint8_t pin2tsi[] = {
  //0    1    2    3    4    5    6    7    8    9
  9,  10, 255, 255, 255, 255, 255, 255, 255, 255,
  255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
  255, 255,  14,  15, 255, 255, 255, 255, 255,  11,
  12, 255, 255, 255, 255, 255, 255, 255, 255, 255
};

// MKL26Z64: Teensy LC
#elif defined(__MKL26Z64__)
#define NSCAN     9
#define PRESCALE  2
static const uint8_t pin2tsi[] = {
  //0    1    2    3    4    5    6    7    8    9
  9,  10, 255,   2,   3, 255, 255, 255, 255, 255,
  255, 255, 255, 255, 255,  13,   0,   6,   8,   7,
  255, 255,  14,  15, 255, 255, 255
};

#endif

// For Teensy 3.6 and Teensy LC. For these chips, it looks like only
// one pin can be scanned at a time. (Well, it appears DMA could be
// used to scan all automatically, but we won't do that here.)
// So we provide support to select desired pins and then scan them
// one at a time. A bit awkward, but requires no interrupts or
// exotic DMA.

class AsyncTouchMgr {
  public:

    uint16_t data[16];    // values read, per channel. Not all necessarily used.
    // uint8_t pins[16];    // pin map. one element for each enabled pin.
    uint8_t channels[16]; // channel map. one element for each enabled channel.
    uint8_t num_enabled;
    uint8_t curr_slot;    // Current pin/channel map slot
    uint8_t curr_ch;      // And it's corresponding channel

    bool scanning;        // We set this flag during scanning so we don't try
                          // to start another one too soon.
    bool data_ready;      // Just a high level flag that we manipulate to control when scans happen

  public:

    AsyncTouchMgr()
    {
      ClearPinEnables();
    }

    void ClearPinEnables()
    {
      for (int i = 0; i < 16; i++)
      {
        data[i] = 0;
        // pins[i] = 0;
        channels[i] = 0;
      }

      curr_slot = 0;
      curr_ch = 0;

      num_enabled = 0;
      data_ready = false;
      scanning = false;
    }
    
    uint8_t Pin2TSI(uint8_t pin)
    {
      // 255 means bad pin number
      uint8_t ch = 255;
      if (pin < NUM_DIGITAL_PINS)
      {
        ch = pin2tsi[pin];
      }
      return ch;
    }

    void EnablePin(uint8_t pin)
    {
      uint8_t ch = Pin2TSI(pin);

      if (ch != 255)
      {
         // pins[num_enabled] = pin;
         channels[num_enabled] = ch;
         ++num_enabled;

         *portConfigRegister(pin) = PORT_PCR_MUX(0);
      }
    }

    inline void StartScan()
    {
      // Don't start a new one if old scan hasn't finished.
      // You'll get overrun errors if you do.
      
      if (!scanning)
      {
        data_ready = false;
        scanning = true;
        ScanFirstSlot();
      }
    }

    bool Ready()
    {
      // Only returns true if all enabled pins have been scanned

      if (!data_ready)
      {
         if (InnerReady())
         {
            // Store the results of current pin, start the scan
            // of the next pin, if any
            StoreResults();
            if (!ScanNextSlot())
            {
              // Finished scanning all enabled pins,
              // so we are indeed ready
              data_ready = true;
              scanning = false;
            }
         }
      }

      return data_ready;
    }

    void WaitForReady()
    {
      // In case you *do* want to block
      while (!Ready()) { };
    }

    int ReadPin(uint8_t pin)
    {
      // To be called after all enabled pins scanned

      uint8_t ch = Pin2TSI(pin);

      if (ch != 255)
      {
        return data[ch];
      }
      else return -1; // indicates invalid pin
    }

  protected:  // Low level code

    inline bool ScanFirstSlot()
    {
      // It would appear this low level code only needs to be done once per
      // scan of all enabled pins. So I moved it here from ScanNextSlot().
      
      SIM_SCGC5 |= SIM_SCGC5_TSI;
      TSI0_GENCS = TSI_GENCS_REFCHRG(4) | TSI_GENCS_EXTCHRG(3) | TSI_GENCS_PS(PRESCALE)
                | TSI_GENCS_NSCN(NSCAN) | TSI_GENCS_TSIEN | TSI_GENCS_EOSF;
      
      curr_slot = 0;
      return ScanNextSlot();
    }

    bool ScanNextSlot()
    {
      if (curr_slot == num_enabled)
      {
        return false;
      }
      else
      {
        // uint8_t pin = pins[curr_slot];
        curr_ch = channels[curr_slot++];

        //Serial.print("** Pin, ch: "); Serial.print(pin); Serial.print(", "); Serial.println(ch);

        // MOVED TO ScanFirstSlot():
        //SIM_SCGC5 |= SIM_SCGC5_TSI;
        //TSI0_GENCS = TSI_GENCS_REFCHRG(4) | TSI_GENCS_EXTCHRG(3) | TSI_GENCS_PS(PRESCALE)
        //             | TSI_GENCS_NSCN(NSCAN) | TSI_GENCS_TSIEN | TSI_GENCS_EOSF;
        
        TSI0_DATA = TSI_DATA_TSICH(curr_ch) | TSI_DATA_SWTS;

        // The following delay was in the original touch.c code. Should we leave it in?
        // UPDATE: It looks like we need *some* kind of delay, *somewhere.* It could be a 
        // startup thing, where the readings don't work (you'll get zeros, worst case)
        // until we've fired up ADC's, timer's, etc.
        // UPDATE: OR, put a long delay (like several milliseconds, perhaps 10?) in after
        // enabling all touch pins and muxes. That seems to work, and doesn't eat into loop time.
        // UPDATE #2: Not true. You *need* some kind of delay here, right after starting scan.
        // UPDATE #3: Not true. If you use the EOSF flag to detect end of scanning, no delay needed.
         
        //delayMicroseconds(10);
        //delayMicroseconds(1);    // This "works on my machine" (ha!)
        
         return true;
      }
    }

    bool InnerReady()
    {
      // Is current channel being scanned ready

      #if 0
      // old way, using "scan in progress". Requires the delay at start of scan and after.
      if (TSI0_GENCS & TSI_GENCS_SCNIP) return false;
      delayMicroseconds(1); // In original touch.c code. Should we leave it in?
      #else
      // better way using "end of scan". Doesn't need the delay at start of scan or after.
      if (!(TSI0_GENCS & TSI_GENCS_EOSF)) return false;
      TSI0_GENCS |= TSI_GENCS_EOSF; // clear flag (write 1)
      #endif
      return true;
    }

    void StoreResults()
    {
      uint16_t v = TSI0_DATA & 0xFFFF; // Data for current channel
      data[curr_ch] = v;
    }
};

#else

#pragma message("AsyncTouchMgr not supported for this device."

#endif

Then, here's a new class that manages the state of the touch pads. Note that it is written independently of the touch manager class, so it could be used in more situations with other touch code.

Code:
#pragma once

// ///////////////////////////////////////////////////////////////////////////
// Touch pad sensor class that manages the state of a touch pad. 
// By Bryan Flamig. *** Dec 13, 2018 version ***
// This code was written to be independent of the AsynchTouchMgr class or
// any other low-level touch sensing code. You simply pass in the value
// read from the sensor pad (using lower level code) when updating the state.

// Note that RisingEdge and FallingEdge states are transitory. They only last
// one status update. And, they only track going above or below the apropo
// threshold(s). They do *not* track the actual sensor value rising or falling
// per se.

enum class PadState { NotBeingTouched, RisingEdge, FallingEdge, BeingTouched };

class TouchPad {
public:

    PadState state;  // current state of the touch pad
    uint8_t pin;     // what touch pin (not channel) are we using
    int val;         // last value read. Not currently used to update state.
    int qval;        // max quiescent value seen during calibration when not touching
	
	  // Dual thresholds used. Helps prevent "bouncing".
	
    int hiThold;       // Threshold for "touching". Computed from qval.
    int loThold;       // Threshold for "not touching". Computed from qval.

    float lo_tfact;  // lo threshold factor
    float hi_tfact;  // hi threshold factor
    
public:

    explicit TouchPad(uint8_t pin_, float lo_tfact_ = 0.125, float hi_tfact_ = 0.25) 
    : state(PadState::NotBeingTouched)
    , pin(pin_)
    , val(0)
    , qval(0)
    , hiThold(0)
    , loThold(0)
    , lo_tfact(lo_tfact_)
    , hi_tfact(hi_tfact_)
    {
    }

    const char *StateToStr()
    {
       switch(state)
       {
         case PadState::NotBeingTouched:
           return "Not being touched";
         case PadState::RisingEdge:
           return "Rising edge";
         case PadState::FallingEdge:
           return "Falling edge";
         case PadState::BeingTouched:
         default:
           return "Being touched";
       }
    }

    void StartCalibrate()
    {
       qval = 0;
       hiThold = 0;
       loThold = 0; 
    }

    void Calibrate(int v)
    {
       // It's assumed this function is going to be called many times
       // (100s or 1000s) while pads are not being touched. You want
       // a good, safe estimate of the maximum quiescent value.
       if (v > qval) qval = v;
    }

    void EndCalibrate()
    {
       // We use a high "touching" threshold, and a low "not touching"
       // threshold. Using two thresholds can help prevent possible
       // touch "bouncing" behavior.

       int hidelta = floor(qval * hi_tfact);
       if (hidelta == 0) hidelta = 100; // Last resort, cheapo correction. Needs work.
       hiThold = qval + hidelta;
       int lodelta = floor(qval * lo_tfact);
       if (lodelta == 0) lodelta = 50; // Last resort, cheapo correction. Needs work.
       loThold = qval + lodelta;

       state = PadState::NotBeingTouched;
    }

    inline bool AboveThreshold(int v)
    {
      return (v > hiThold);
    }

    inline bool BelowThreshold(int v)
    {
      return (v < loThold);
    }

    bool UpdateState(int v)
    {
       // Returns true if state changed. Note: The value changing
       // is not considered a "state change" by itself.

       val = v; // For posterity I guess. Not really needed.

       if (AboveThreshold(v))
       {
          switch(state)
          {
             case PadState::NotBeingTouched:
             {
                state = PadState::RisingEdge;
                return true;
             }
             break;
             case PadState::FallingEdge:
             {
                state = PadState::RisingEdge;
                return true;
             }
             break;
             case PadState::RisingEdge:
             {
                // This is how we make RisingEdge transitory
                state = PadState::BeingTouched;
                return true;
             }
             case PadState::BeingTouched:
             {
                // Stays in this state until falling edge detected.
             }
             break;
          }
       }

       // Separate lo threshold check.
       
       if (BelowThreshold(v))
       {
          switch(state)
          {
             case PadState::NotBeingTouched:
             {
                // Stays in this state until rising edge detected.
             }
             break;
             case PadState::FallingEdge:
             {
                // This is how we make FallingEdge transitory
                state = PadState::NotBeingTouched;
                return true;
             }
             break;
             case PadState::RisingEdge:
             {
                state = PadState::FallingEdge;
                return true;
             }
             case PadState::BeingTouched:
             {
                state = PadState::FallingEdge;
                return true;
             }
             break;
          }
       }

       return false; // State did not change. (Pad value might have though.)
    }   
};


Finally, here's a sketch that tests the whole thing. It works by using a 1/8 second timer to initiate touch scans. It has been tested on a Teensy 3.2 and a Teensy 3.6.

Code:
// /////////////////////////////////////////////////////////////////////////
// Asynchronous touch sensing test sketch.
// By Bryan Flamig. *** Dec 13, 2018 version ***

#include "AsyncTouchMgr.h"
#include "TouchPad.h"

AsyncTouchMgr TouchMgr;

TouchPad PadA(18, 0.20, 0.35);  // In my circuit, this pad seems to be noiser than the others
TouchPad PadB(19);
TouchPad PadC(22);
TouchPad PadD(23);

IntervalTimer timer0;
int startTimerValue0;

void TimerSetup()
{
   // We'll set up a 1/8 sec timer for the touch pad scans
   static constexpr float usecs_per_period = 1e6 / 8.0;
   startTimerValue0 = timer0.begin(timer0_callback, usecs_per_period);
   Serial.println("Timer started");
   Serial.print("period: "); Serial.println(usecs_per_period, 4);
}

volatile bool timer_clock_pulse = false;

void timer0_callback()
{
   // Called every 1/8 second
   timer_clock_pulse = true;
}

// //////////////////////////////////////////////////////////////
// Touch sensors setup
// //////////////////////////////////////////////////////////////

void TouchSetup()
{
   Serial.println("Enabling touch pins");

   TouchMgr.EnablePin(PadA.pin);
   TouchMgr.EnablePin(PadB.pin);
   TouchMgr.EnablePin(PadC.pin);
   TouchMgr.EnablePin(PadD.pin);

   // Thought a delay here might help things, but it doesn't.
   // delay(10);

   Serial.println("Calibrating touch pins");

   PadA.StartCalibrate();
   PadB.StartCalibrate();
   PadC.StartCalibrate();
   PadD.StartCalibrate();

   for (int i = 0; i < 1000; i++)
   {
      TouchMgr.StartScan();
      TouchMgr.WaitForReady();
      PadA.Calibrate(TouchMgr.ReadPin(PadA.pin));
      PadB.Calibrate(TouchMgr.ReadPin(PadB.pin));
      PadC.Calibrate(TouchMgr.ReadPin(PadC.pin));
      PadD.Calibrate(TouchMgr.ReadPin(PadD.pin));
   }

   PadA.EndCalibrate();
   PadB.EndCalibrate();
   PadC.EndCalibrate();
   PadD.EndCalibrate();

   // qval is max val seen during calibration, loThold is computed from qval and represents
   // the threshold below which we say "no touch", hiThold is computed from qval and
   // represents the threshold above which we say "touch".

   Serial.print("PadA: "); Serial.print(PadA.qval); Serial.print(", "); Serial.print(PadA.loThold); Serial.print(", "); Serial.println(PadA.hiThold);
   Serial.print("PadB: "); Serial.print(PadB.qval); Serial.print(", "); Serial.print(PadB.loThold); Serial.print(", "); Serial.println(PadB.hiThold);
   Serial.print("PadC: "); Serial.print(PadC.qval); Serial.print(", "); Serial.print(PadC.loThold); Serial.print(", "); Serial.println(PadC.hiThold);
   Serial.print("PadD: "); Serial.print(PadD.qval); Serial.print(", "); Serial.print(PadD.loThold); Serial.print(", "); Serial.println(PadD.hiThold);

   TouchMgr.data_ready = false; // So loop logic starts out in correct state, ready to scan.
}

// /////////////////////////////////////////////////////////////////////////////
// Start up code
// /////////////////////////////////////////////////////////////////////////////

void setup()
{
   Serial.begin(115200);  // baud rate is ignored with Teensy USB ACM i/o

   delay(100); // Seem to need something like this for serial stuff

   pinMode(LED_BUILTIN, OUTPUT);

   digitalWrite(LED_BUILTIN, HIGH);

   // Setup touch sensors.

   TouchSetup();

   digitalWrite(LED_BUILTIN, LOW);

   // Setup up timer

   TimerSetup();

   if (startTimerValue0 == false)
   {
       Serial.println("Timer0 setup failed");
   }

   Serial.println("Waiting for pads to be touched ...");
}

void loop()
{
   // We try to start a touch scanning every clock period, (1/8 sec),
   // if the rest of the loop code doesn't run too long.
   
   if (timer_clock_pulse)
   {
      timer_clock_pulse = false;

      digitalWrite(LED_BUILTIN, HIGH);

      // Start a new scan. Note though, that a scan won't actually
      // start if the last scan hasn't completed. If that's the case,
      // it will be at least one more clock pulse (1/8 sec) before
      // we try scanning again.

      TouchMgr.StartScan();
   }
   else
   {
     // When a scan is ready, go ahead and update the status
     // of all the enabled touch pins.

     if (TouchMgr.Ready()) // non-blocking
     {
        const char *msg;
        int v;
        bool chg;

        digitalWrite(LED_BUILTIN, LOW);

        v = TouchMgr.ReadPin(PadA.pin);
        chg = PadA.UpdateState(v);

        if (chg)
        {
           Serial.print("Pad A: "); Serial.print(v); Serial.print(", ");
           msg = PadA.StateToStr();
           Serial.println(msg);
        }

        v = TouchMgr.ReadPin(PadB.pin);
        chg = PadB.UpdateState(v);

        if (chg)
        {
           Serial.print("Pad B: "); Serial.print(v); Serial.print(", ");
           msg = PadB.StateToStr();
           Serial.println(msg);
        }

        v = TouchMgr.ReadPin(PadC.pin);
        chg = PadC.UpdateState(v);

        if (chg)
        {
          Serial.print("Pad C: "); Serial.print(v); Serial.print(", ");
          msg = PadC.StateToStr();
          Serial.println(msg);
        }

        v = TouchMgr.ReadPin(PadD.pin);
        chg = PadD.UpdateState(v);

        if (chg)
        {
           Serial.print("Pad D: "); Serial.print(v); Serial.print(", ");
           msg = PadD.StateToStr();
           Serial.println(msg);
        }

        // Signal that we've used the results. This is so we don't try doing
        // the pad updates again until a new scan has been completed.

        TouchMgr.data_ready = false;
     }
   }

   // Simulate doing 100 usec of other stuff in the loop

   delayMicroseconds(100);
}
 
analogRead() interferes with the calibration of the touch input

Thanks for putting the work in on this. I'm mainly interested in the calibration aspect. I run into a problem on my Teensy 3.6 that, as soon as I add an analogRead() to the loop, the quiescent reading of the touch input rises above the "off threshold" which renders the input unusable.
Here's my version of the test sketch...

Code:
#include "AsyncTouchMgr.h"
#include "TouchPad.h"

AsyncTouchMgr TouchMgr;
const int padPin = 18;
const int analogPin = 14;
TouchPad myPad(padPin);
IntervalTimer timer0;
int startTimerValue0;

void TimerSetup()
{
   // We'll set up a 1/8 sec timer for the touch pad scans
   static constexpr float usecs_per_period = 1e6 / 8.0;
   startTimerValue0 = timer0.begin(timer0_callback, usecs_per_period);
   Serial.println("Timer started");
   Serial.print("period: "); Serial.println(usecs_per_period, 4);
}

volatile bool timer_clock_pulse = false;

void timer0_callback()
{
   // Called every 1/8 second
   timer_clock_pulse = true;
}

void TouchSetup()
{
   Serial.println("Enabling touch pins");
   TouchMgr.EnablePin(myPad.pin);
   Serial.println("Calibrating touch pins");
   myPad.StartCalibrate();
   for (int i = 0; i < 1000; i++)
   {
      TouchMgr.StartScan();
      TouchMgr.WaitForReady();
      myPad.Calibrate(TouchMgr.ReadPin(myPad.pin));
   }

   myPad.EndCalibrate();

   // qval is max val seen during calibration, loThold is computed from qval and represents
   // the threshold below which we say "no touch", hiThold is computed from qval and
   // represents the threshold above which we say "touch".

   Serial.print("myPad: "); Serial.print(myPad.qval); Serial.print(", "); Serial.print(myPad.loThold); Serial.print(", "); Serial.println(myPad.hiThold);
   TouchMgr.data_ready = false; // So loop logic starts out in correct state, ready to scan.
}

void setup()
{
   Serial.begin(115200);  // baud rate is ignored with Teensy USB ACM i/o
   delay(100); // Seem to need something like this for serial stuff
   pinMode(LED_BUILTIN, OUTPUT);
   pinMode(12, INPUT_PULLUP);
   digitalWrite(LED_BUILTIN, HIGH);
   TouchSetup();
   digitalWrite(LED_BUILTIN, LOW);
   TimerSetup();
   if (startTimerValue0 == false)
   {
       Serial.println("Timer0 setup failed");
   }
   Serial.println("Waiting for pads to be touched ...");
}

void loop()
{
   // We try to start a touch scanning every clock period, (1/8 sec),
   // if the rest of the loop code doesn't run too long.
   
   if (timer_clock_pulse)
   {
      timer_clock_pulse = false;
      digitalWrite(LED_BUILTIN, HIGH);
      // Start a new scan. Note though, that a scan won't actually
      // start if the last scan hasn't completed. If that's the case,
      // it will be at least one more clock pulse (1/8 sec) before
      // we try scanning again.
      TouchMgr.StartScan();
   }
   else
   {
     // When a scan is ready, go ahead and update the status
     // of all the enabled touch pins.
     if (TouchMgr.Ready()) // non-blocking
     {
        const char *msg;
        int v;
        bool chg;

        digitalWrite(LED_BUILTIN, LOW);

        v = TouchMgr.ReadPin(myPad.pin);
        chg = myPad.UpdateState(v);

        if (chg)
        {
           Serial.print("myPad: "); Serial.print(v); Serial.print(", ");
           msg = myPad.StateToStr();
           Serial.println(msg);
        }
        // Signal that we've used the results. This is so we don't try doing
        // the pad updates again until a new scan has been completed.

        TouchMgr.data_ready = false;
     }
   }
   // Simulate doing 100 usec of other stuff in the loop
   // delayMicroseconds(100);

   // Reading analog changes the quiescent value of
   // the touch input and breaks it. (Teensy 3.6)
   if (digitalRead(12) == LOW){analogRead(analogPin);}

}

Incidentally (or maybe not so incidentally), the quiescent value doesn't go back down even if I set a condition to stop reading analog; It just stays broken. Lines 112 and 113 of AsyncTouchMgr.h read...

// startup thing, where the readings don't work (you'll get zeros, worst case)
// until we've fired up ADC's, timer's, etc.


So, I'm wondering if actually invoking the ADC during calibration would be helpful. The ADC smells like the problem in my case but I also wonder if the "etc." in your comment refers to anything specific that might also cause problems in the future. Thanks again for any insight.
 
Thanks for putting the work in on this. I'm mainly interested in the calibration aspect. I run into a problem on my Teensy 3.6 that, as soon as I add an analogRead() to the loop, the quiescent reading of the touch input rises above the "off threshold" which renders the input unusable.
Here's my version of the test sketch...

Code:
#include "AsyncTouchMgr.h"
#include "TouchPad.h"

AsyncTouchMgr TouchMgr;
const int padPin = 18;
const int analogPin = 14;
TouchPad myPad(padPin);
IntervalTimer timer0;
int startTimerValue0;

void TimerSetup()
{
   // We'll set up a 1/8 sec timer for the touch pad scans
   static constexpr float usecs_per_period = 1e6 / 8.0;
   startTimerValue0 = timer0.begin(timer0_callback, usecs_per_period);
   Serial.println("Timer started");
   Serial.print("period: "); Serial.println(usecs_per_period, 4);
}

volatile bool timer_clock_pulse = false;

void timer0_callback()
{
   // Called every 1/8 second
   timer_clock_pulse = true;
}

void TouchSetup()
{
   Serial.println("Enabling touch pins");
   TouchMgr.EnablePin(myPad.pin);
   Serial.println("Calibrating touch pins");
   myPad.StartCalibrate();
   for (int i = 0; i < 1000; i++)
   {
      TouchMgr.StartScan();
      TouchMgr.WaitForReady();
      myPad.Calibrate(TouchMgr.ReadPin(myPad.pin));
   }

   myPad.EndCalibrate();

   // qval is max val seen during calibration, loThold is computed from qval and represents
   // the threshold below which we say "no touch", hiThold is computed from qval and
   // represents the threshold above which we say "touch".

   Serial.print("myPad: "); Serial.print(myPad.qval); Serial.print(", "); Serial.print(myPad.loThold); Serial.print(", "); Serial.println(myPad.hiThold);
   TouchMgr.data_ready = false; // So loop logic starts out in correct state, ready to scan.
}

void setup()
{
   Serial.begin(115200);  // baud rate is ignored with Teensy USB ACM i/o
   delay(100); // Seem to need something like this for serial stuff
   pinMode(LED_BUILTIN, OUTPUT);
   pinMode(12, INPUT_PULLUP);
   digitalWrite(LED_BUILTIN, HIGH);
   TouchSetup();
   digitalWrite(LED_BUILTIN, LOW);
   TimerSetup();
   if (startTimerValue0 == false)
   {
       Serial.println("Timer0 setup failed");
   }
   Serial.println("Waiting for pads to be touched ...");
}

void loop()
{
   // We try to start a touch scanning every clock period, (1/8 sec),
   // if the rest of the loop code doesn't run too long.
   
   if (timer_clock_pulse)
   {
      timer_clock_pulse = false;
      digitalWrite(LED_BUILTIN, HIGH);
      // Start a new scan. Note though, that a scan won't actually
      // start if the last scan hasn't completed. If that's the case,
      // it will be at least one more clock pulse (1/8 sec) before
      // we try scanning again.
      TouchMgr.StartScan();
   }
   else
   {
     // When a scan is ready, go ahead and update the status
     // of all the enabled touch pins.
     if (TouchMgr.Ready()) // non-blocking
     {
        const char *msg;
        int v;
        bool chg;

        digitalWrite(LED_BUILTIN, LOW);

        v = TouchMgr.ReadPin(myPad.pin);
        chg = myPad.UpdateState(v);

        if (chg)
        {
           Serial.print("myPad: "); Serial.print(v); Serial.print(", ");
           msg = myPad.StateToStr();
           Serial.println(msg);
        }
        // Signal that we've used the results. This is so we don't try doing
        // the pad updates again until a new scan has been completed.

        TouchMgr.data_ready = false;
     }
   }
   // Simulate doing 100 usec of other stuff in the loop
   // delayMicroseconds(100);

   // Reading analog changes the quiescent value of
   // the touch input and breaks it. (Teensy 3.6)
   if (digitalRead(12) == LOW){analogRead(analogPin);}

}

Incidentally (or maybe not so incidentally), the quiescent value doesn't go back down even if I set a condition to stop reading analog; It just stays broken. Lines 112 and 113 of AsyncTouchMgr.h read...




So, I'm wondering if actually invoking the ADC during calibration would be helpful. The ADC smells like the problem in my case but I also wonder if the "etc." in your comment refers to anything specific that might also cause problems in the future. Thanks again for any insight.

joshnishikawa: It's been quite a while since I've looked at or used this code. I was going to use it for a project that got put on the back-burner.

A couple of points:

(1) Past lines 112-113 of said header file, I have some "update" comments where I was learning things, the last of which is that no delay was needed if you used a different flag,
the EOSF flag to detect end of scanning, rather than whatever flag Paul's code was using. I don't remember exactly where I learned that trick -- it was partly from reading the NXP documentation, and partly as a hint from some posts on earlier threads in this forum. It is my belief that using EOSF flag is the right way to do it, but I could be wrong.

(2) I don't recall if I've ever tried doing an analog read while performing a touch scan. I could certainly imagine that being a source of interference, but I don't know if it is, or should be. One would hope you could do both simultaneously, otherwise the timing issues get tricky if you are also trying not to block the main loop. In most of my programs that do analog reads I use the "continuous read" mode, where the ADC is read continuously, in the background, not chewing up any CPU. I use an interrupt timer that merely sets a flag to synchronize my main loop with, including when to start touch scans or detect if they are ready. There are certainly other ways to do this. It's just the way I chose because it seemed simpler than all other methods, yet still allowed the touch scans to occur without blocking the main loop. Of course, if the ADC reads really are messing up the touch scanning, then this wouldn't work. I must confess I never got far enough along in the project I was working on to test doing both things at once. It was just something I was planning on doing.

(3) One way to tell if it's my code and not analog reads would be to first time how long it takes to do an analog read, then substitute a delay for it instead of the actual read, and see what changes occur with the touch scanning. If you are still getting touch problems, then it's either my code (doubtful, but anything is possible) or a hardware issue. The touch sensing is finicky -- it depends on keeping the sensing wires short, using good grounding, having good connections, and so forth. I too have seen the touch readings go wacky for long periods of time if I'm jiggling around wires too much, or using flaky breadboard connections.

(4) If the ADC reads are really the culprit, I'm stumped on what you could do to fix that, short of not doing both touch scans and analog reads at the same time, which kind of renders my library moot.

I probably won't have time today, but I may try some experiments doing analog reading while touch scanning takes place. I'll have need for this kind of thing at some point in the future, myself.

Hope some of this helps.

Bryan
 
Thanks for the thorough response. I went ahead and added a line to StartCalibrate() in the TouchPad class.

Code:
    void StartCalibrate()
    {
       qval = 0;
       hiThold = 0;
       loThold = 0;
       analogRead(A0);
    }

This seems to solve the problem just fine! Also raising lo_tfact to 0.2 and hi_tfact to 0.3 is another workaround. However, I prefer to just run the analogRead() because the values for "Falling edge" and "Not being touched" stay consistent regardless of when analog starts being read (because the ADC is already hot).
 
Status
Not open for further replies.
Back
Top