I think I've finally created a reliable non-blocking capacitive sensor library for the Atmega!
Originally I was using Paul Stoffregen's Capacitive Sensor library, which works great, except that the main program has to wait while it takes enough samples (~30) for a stable proximity reading. It's usually pretty quick, but when you're trying to poll 144 nodes at 250Kbps, this delay can add up significantly.
I had to find a way to get the capacitive sensor running "in the background". There is a dedicated cap sense chip that we could use, but I prefer the challenge of making it all work on one little chip. After several failed iterations the latest appears to be working well...so far.
How capacitive sensing works
The capacitive sensor is basically an RC circuit with the person being a variable capacitor. The circuit looks something like this:
The process to detect someone's proximity starts by setting the send pin to high and then tracks how long it takes for the receive pin to go high. The closer someone is to the foil, the longer this will take (ther person acts as a larger capacitor in the RC circuit).
Paul Stoffregen's method is to create a while loop which waits for the receive pin to register as HIGH and then waits another 10µs for everything to stabilize before taking the next reading.
The non-blocking solution!
The solution came down to juggling a few interrupts to handle this entire process without blocking the main program execution.
The program basically works by using the Atmega's input capture unit (ICU) to take a timestamp every time the receive pin goes HIGH. Then it uses a timer interrupt (triggered in ~4ms) to process the timestamp and give the pins time to discharge before starting the process all over again.
Trying to sense proximity (1+ inches / 25+ mm) with a capacitive sensor is inherently noisy since it can react to everything in its environment. I fought with this for a while -- playing with moving baselines, low pass filters, high pass filters, burning sage and chanting to the project Gods.
The solution came from implementing a simple 1-dimensional Kalman filter. The results were almost immediately a smooth and predictable line.
I also created a simple tool that will run the kamlan filter over a list of raw data, so I could play with various noise values offline.