anaButtons

"anaButtons" is a method for connecting an array of buttons to a single (potentially digital) I/O pin, via resistors and a capacitor.

// resistance/capacitance values. A regular 4x4 matrix could be

// wired as below.

//

//

//

// PD6 ><----+---------+-------*-------*-------*-------* These are

// | | \ \ \ \ pushbuttons

// | / \ O \ O \ O \ O <-------

// .1uF ----- \ ,\ O ,\ O ,\ O ,\ O

// ----- 5k / \ \ \ \ \ \ \ \

// | \ * * * *

// | | | | | |

// V +-------*----|--*----|--*----|--*----|

// GND | \ | \ | \ | \ |

// / \ O | \ O | \ O | \ O |

// \ ,\ O | ,\ O | ,\ O | ,\ O |

// 5k / \ \| \ \| \ \| \ \|

// \ * * * *

// | | | | |

// +-------*----|--*----|--*----|--*----|

// | \ | \ | \ | \ |

// / \ O | \ O | \ O | \ O |

// 5k \ ,\ O | ,\ O | ,\ O | ,\ O |

// / \ \| \ \| \ \| \ \|

// \ * * * *

// | | | | |

// +-------*----|--*----|--*----|--*----|

// \ | \ | \ | \ |

// \ O | \ O | \ O | \ O |

// ,\ O | ,\ O | ,\ O | ,\ O |

// \ \| \ \| \ \| \ \|

// * * * *

// | | | |

// | | | |

// +-/\/\/-+-/\/\/-+-/\/\/-+

// 1k 1k 1k |

// /

// \ 1k

// This resistor, to ground, is necessary since the /

// pin (PD6) will be used as an output to charge the |

// capacitor to 3V3, EVEN WHEN a button is pressed. V

// GND

This is the PCB from an old Nokia phone, its matrix-keypad is on the other side. The board has been stripped of all its components and the key-matrix row/column pins have been soldered to ressitors on Kapton-tape (for insulation from the PCB). This particular phone has a 5x4 matrix, despite having 16 buttons; some matrix-ponits are not connected with buttons.

The basic idea is this: Charge a capacitor and measure how long it takes to discharge through a resistor attached to a pressed-button.

The original design used the microcontroller's Analog Comparator, as shown:

//These notes made long-after coding... so may not match exactly...//...................... .......................// Microcontroller . . .// . . Button Array... .// . . .// . . .// . . .// Charge ---. . . E.G.: .// | . . .// o . AIN0+ [AIN1-] . .// |¯-_ . (PA6) [PB3] . __|__ .// + ----| >--+----------------------+-----------+---O O----+ .// |_-¯ | . | . | | .// | . ¯¯¯¯¯ . | . \ .// _-¯| | . _____ . | . / .// _-¯ - |--' . AIN2- ^ | . | . \ .// < | . (PA5) | | . | / .// ¯-_ + |-----------. \ V . | __|__ | .// ¯-_| . | / . `---O O----+ .// . | \ . | .// Is it the drawing . | / . \ .// that's flipped? . | | . / .// Polarity.... . +----+ 1.65V Ref . \ .// (Tiny861 was . | | . / .// drawn opposite) . | \ [Bandgap . | .// . ¯¯¯¯¯ / =1.23V] . V .// . _____ \ . .// . | / .......................// . | |// . V V

Later, it was determined that a regular Digital I/O pin could be used, as the Input's threshold voltages (for switching from high to low or low to high) are relatively stable. Further, the AVR has Schmitt-Trigger inputs!

// USING A DIGITAL GPIO

//...................... .......................

// Microcontroller . . .

// . . Button Array... .

// . . .

// . . .

// . . .

// Charge ---. . . E.G.: .

// | . . .

// o . . .

// |¯-_ . (E.G. PA6) . __|__ .

// + ----| >--+----------------------+-----------+---O O----+ .

// |_-¯ | . | . | | .

// | . ¯¯¯¯¯ . | . \ .

// _-¯| | . _____ . | . / .

// Read ----< |--' . | . | . \ .

// ¯-_| . | . | / .

// . V . | __|__ | .

// . GND . `---O O----+ .

// . . | .

// . . \ .

// . . / .

// . . \ .

// . . / .

// . . | .

// . . V .

// . . .

// . .......................

The method, then, is to switch the port to an output, with its value high to charge the capacitor, then switch it to an input and sample repeatedly until the value goes low.

Math, such as T=RC can be used for initial estimates of reasonable R/C values, but ultimately the system is dependent on factors that may vary from device-to-device, supply-voltage, and even temperature. It has been found stable-enough for my own purposes 90% of the time. I have even found that the same ranges can be used to detect the same buttons/resistors, when wired to two different AVRs (one a Mega328p, the other a Tiny861) running at two different voltages (3.3V and 3.6V, respectively) and with different capacitors.

But I wouldn't use it for, e.g. a device that might be used at room-temperature one day and outside in the cold another without a lot of experimentation. And certainly not for anything "mission-critical". However, if you unexpectedly need a bunch of buttons for testing and are running out of pins, or are otherwise willing to take these apparently-minor "risks," this is for you.

(Note: This will *likely* cause "excessive" current-draw on a digital-input, as the story goes. The AVR's inputs are Schmitt-Triggered, so it should be acceptable. Again, I've used it in several projects without releasing smoke, but no promises.)

---------------------------------------

The above might-well be enough to get you going. But I've developed code for the purpose. This code can be used in both "blocking" and "non-blocking" manners via #define, so it can be used, roughly, as such (See the test-code):

//Uncomment this for BLOCKING, otherwise it's NON-BLOCKING //#define ANAB_BLOCKING TRUE #include "anaButtons.h" int main(void){ while(1){ //BLOCKING: Will wait here when a button is pressed, until it is released //NON-BLOCKING: Will return a negative value quickly after each call // until a button is released int32_t buttonTimeVal = anaButtons_getDebounced(); if (buttonTimeVal >= 0) printf("button detected: %d TCNTs", buttonTimeVal); ... other code ... } }

For a handful of buttons and relatively-long (and very-distinct!) T=RC values, non-blocking method is great, and allows for code to continue at breakneck-pace in the while-loop until a button is released.

For e.g. a matrix of buttons, and/or where resistance-values are similar, and/or where T=RC is small, it becomes necessary to use the blocking method. (This could likely be relieved, in most cases, with a larger capacitor).

Why does it wait until the button's released?

In order to get an accurate measurement, relatively unaffected by signal-bounce, varying resistance in the buttons' presses, etc. Again, we're working with analog, here, and it's susceptable to *everything* including the AC power in your house. This samples the "button time" repeatedly, and determines *the least* "button-time" during the keypress. The logic goes that e.g. a cheap button with oxidized contacts might discharge the capacitor slower when pressed lightly, than when pressed firmly. Then, there's no way its measured discharge-time could ever be *less* than a firm push; even with signal-bounce the resistance will always be equal to or greater than the resistor associated with the button. Logically, there's some lacking... AC power affects this, so as long as you press the button for longer than a few 60Hz cycles, you should be able to characterize the button's "time" pretty reliably for most "in-house" purposes, and still be able to discern between several buttons.

-------------------

The test-code for an ATmega328p exists at: anaButtons/testMega328p+NonBlocking

It should, however, be relatively easily ported to most AVR devices by changing the following lines, as appropriate, in 'makefile':

    • 'MCU=atmega328p'

    • CFLAGS += -D'ANABUTTONS_PIN=PD6'

    • CFLAGS += -D'ANABUTTONS_PORT=PORTD'

Also, copy and modify the small device-specific makefile-snippet, located in _commonCode_localized/_make/<MCU>.mk

See Also: pinout.h

Pinout information for the ATmega328p, or to modify for your device:

    • PD6 is connected to the analog-button-array

    • PB4 is the "heartbeat" pin PB4 >------------|<|-----/\/\/\------V+

    • PB3 is Tx0, to be connected to a TTL-to-RS232 level-shifter or TTL-serial -to- USB converter

    • PB5 is Rx0, ditto

Extract the zip file somewhere, then type:

    • 'make clean'

    • 'make'

    • If you have a usbTinyISP and AVR-Dude, you can then 'make fuse' and 'make run'

(If you use another programmer/utility, you can add it to the makefile snippet located in _commonCode_localized/_make/avrCommon.mk)

Run your favorite serial-terminal-emulator at 9600baud and press a button on your analog array

The terminal-emulator will show the minimum-measured "button time", which you can then use for your own purposes...

e.g. (your buttonTimes may vary!)

if((buttonTime > 50) && (buttonTime < 75)){ ...do som'n with button1... } else if(buttonTime < 100){ ...do som'n with button2... } ....

OPTIONS:

Many options can be configured via the 'makefile', e.g.:

    • which pin the array is connected-to

    • whether to use Digital I/O or the Analog-Comparator

    • whether to use Blocking or Non-Blocking function-calls

Check out the code in _commonCode_localized/anaButtons/0.50/anaButtons.h/c

Thanks go to Hackaday for inspiring me to make this available to the public!

FILES:

testMega328p+NonBlocking 0.zip