AVR-based Sensor KeyboardПо-русски — тут
A modern microcontroller has almost everything that's needed to implement a touch sensor matrix. There are several sensing technologies: IC manufacturers typically advise using certain tech, sometimes they offer ready to use hardware- or software-based solutions. I was curious to try to implement a sensor matrix entirely from scratch all by myself. Here's how it worked out.
Theory of Operation
I chose QMatrix technology as a basis for my experiment, mostly because I've heard about it before and because I liked their whitepaper, which I really recommend to read, here: QMatrix™ Technology White Paper. The point is to transfer the charge through the sensor capacitance to a tank capacitor gradually, until a charge sufficiently large to be measured builds up. After that, a current is applied to the tank capacitor and the time that it takes to reach zero crossing is measured. When sensor surface is being touched, the fingers steal part of the charge to be dissipated by the body and less of it gets transferred to the tank capacitor. This difference can be measured and tried to tell if a button is being touched or not.
The QMatrix Technology Whitepaper already has a perfect illustration of what's going on, but I drew my own version to aid the explanation. On my drawing: Sdrive is a switch that allows to pump the charge through the sensors; Sslope is what allows the current to the tank capacitor during the measurement phase. Stop and Sbottom are the switches that flip the capactor. Sc connects the analog comparator. Ckey is the sensor, a pattern on the PCB. Ctank is an additional capacitor, 4.7nF in my case. Rslope is fairly large, it is to limit discharge current and make the time measurable.
The process of charge transfer to the Ctank is very similar to the work of a charge pump converter (see Horowitz, Hill: The Art Of Electronics, §6.23).
Initially all keys are open, the current has no path. Close Sbottom, then Sdrive: positive edge of the pulse passes through Ckey and charges Ctank by a little bit. Now if we switch Sdrive back to GND, the charge will flow back by the same path it flew in. And that's why before the negative edge of drive current we need to open Sbottom and close Stop, effectively securing that tiny charge we've got. After this comes the negative edge of drive pulse and the process is repeated and the charge on Ctank builds up. This process is illustrated on the right part of the diagram.
The charge will keep building up and after some, e.g. 50 iterations, the voltage on the bottom plate of Ctank will reach –quant.suff V. Then it's time to close Sc to allow the comparator, start counting time and invoke input capture unit. After that, Sslope is closed and the capacitor begins to discharge, first to 0, then the voltage on the bottom plate will keep on climbing until it reaches +Vdd. We're interested in the moment when it passes zero, because it can be captured by the comparator and, subsequently by the input capture unit. By measuring time, a decision can be made whether a key is touched or not: touched keys will accumulate smaller charges and thus they will discharge faster.
Same technique easily scales up to a matrix. Drive lines will be called X, sense lines are Y.
I chose an ATmega8 @16MHz as the test platform. Initially a 5×4 matrix was planned but, the usual, overlooked this and that, it ended up a a 4×3 matrix, which I still consider a success. So what is this stuff that lets us implement this in AVR?
The most valued is the builtin analog comparator, which is absolutely necessary to detect the moment of zero crossing. No less important is the analog input multiplexor that lets us switch the comparator off while building up the charge and to switch the input between GND and hi-Z states. Timer and input capture units come in very handy too, especially now that the comparator output can be internally wired to the input capture unit. Let us sing praise to the engineers who designed AVR peripherals for all these not-so-coincidental conveniences.
There's a little problem however. All AVR inputs are protected from overvoltage by the internal Shottky diodes which have very low bandgap of 0.2V. This puts a serious constraint on the design: |quant.suff | should be not above 0.2V, or nothing will work out at all.
All that remains by this point is to design the circuit and choose the pins/ports wisely. The inputs leave no hesitation: they should be chosen from the analog inputs. Driving pins can be just any pins. It's better to avoid using input capture pin (ICP1) for any purpose at all.
The PCB is probably a requirement for a project like this. For layout guidelines refer to QMatrix whitepaper. Normally it will be 1-sided fiberglass board, the "keys" will be no less than 10mm across, the outer electrodes are the driving ones, X, and their thickness is approximately that of the fiberglass. The reverse, business end side, should be empty.
The firmware self-calibrates upon startup: this is necessary to mark the baseline of every button. Unlike regular buttons, the characteristics of every sensor area can differ significantly depending on geometry or mutual placing of other components. During the calibration, the measured value is sampled 4-16 times and averaged to minimize randomness.
An option to calculate the time of the charging burst is included, but not enabled by default. Experiments show that saturation of the tank capacitor is not as important as baseline estimation.
A touch is detected when the discharge time is 10% shorter than the calibrated baseline. Depending on the conditions, to improve reliability or to reduce interference impact this criteria can be changed to some 20-25%. Additional debouncing algorithm is also implemented for the cases when touch sensitivity is marginal.
The matrix is scanned key by key, sequentially. It is possible to charge several Y-capacitors at once but it's impossible to measure the charge on several caps at once. This is relatively slow, but the bright side of it is that such matrix can pretty reliably detect several keys touched at once, as in multitouch.
The result is a success. A reliable 4×3 sensor matrix is implemented. A larger matrix was planned but didn't work out because: a) I didn't remember that two upper analog inputs on ATmega8 can't be used as digital i/o and b) the ICP1 pin, which I planned to use to drive an extra X line, causes some sort of interference if used in a fast switching design like this.
0.2V is sufficient to reliably detect a touch not only to the back of the fiberglass, but also with added ~1mm sheet of plastic with a layer of some black film inbetween. Some materials do not work well: e.g. a plastic package from a CF card didn't work at all, probably due to antistatic treatment. A compact disc jewel case works perfectly. Dielectric layers should be pressed as tightly as possible from the empty side of the PCB.
The amount of keys can be raised to e.g. 8×8 easily, given that the used controller has enough pins. However it should be remembered that scanning time increases with the number of keys. Some optimizations can be made to improve scan time: e.g. parts of code can be rewritten in assembly and charge bursts can be reduced to shorter times. It should not be a problem to implement similar circuit with a builtin RC oscillator, too.
Sensitivity to interference varies, depending on the quality of dielectric shield and sensitivity threshold. It can be expected that a keyboard like this can function as a part of some device in an enclosure.