While testing the analog routines in the ATtiny85 library I revisited an older project that I never got a chance to write about on the site. With my recent focus on all things related to the ATtiny I thought it would be a good topic to cover.

A while ago I bought a copy of the Arduino Basic Connections book (which I highly recommend, even for those not using the Arduino). It's full of useful circuit fragments and notes including a simple keypad that uses a single analog input. You can find the related page on the official ABC Tumblr at this link.

Essentially it uses a combination of switches and a resistor network to change the output voltage level based on which button is pressed. By sampling this voltage and comparing it to a known value you can determine which button is down. The original circuit uses four buttons, I extended it to five (UP, RIGHT, DOWN, LEFT and CENTER) which is a useful number for menu navigation or basic game control and put it together as a Clixx.IO Analog TwinTab.


You can see the schematic to the right (the full Fritzing project is available on GitHub. The schematic is a bit messy unfortunately (the new version of Fritzing modified the layout on import and I didn't have a lot of time to fix it up or redraw it). As much as I dislike Fritzing I have not found a replacement that allows you to quickly develop small schematics and board layouts - so I'm still using it and putting up with the random issues it like to throw at you from time to time.

The image to the left shows the key layout once the circuit is built up. The buttons I use are the larger type, you could use any momentary push buttons for the circuit of course, it depends on your requirements. In this case I wanted the larger buttons to make it a bit easier to use.

To test it out I simply connected the pins on the TwinTab directly to the pins on the breakout board.

The next step was to write some software to read the keys and determine which one is pressed. I'm using my ATtiny85 support library for this but it can be easily translated to other libraries and CPUs. All you need is the ability to read an analog input. The full sample code is available as a Gist, I will only be presenting portions of it in this post.

The first thing we need to do is determine what values we should expect for each key. After some long and laborious calculations (mashing the keys and printing out the ADC reading) I came up with these values:

    #define KEYVAL_UP     170
    #define KEYVAL_RIGHT  342
    #define KEYVAL_DOWN   512
    #define KEYVAL_LEFT   682
    #define KEYVAL_CENTER 852

     /* Error range      *      * A key is considered pressed if the ADC reading is withing +/- this amount      * from the 'ideal' value described above.      */
    #define KEYVAL_RANGE 16

 In my circuit I'm using Vcc as the input to the keyboard circuitry and as the ADC reference voltage, it's on an ATtiny85 so the ADC has a 10 bit resolution. These values should be the same under similar circumstances, if you are using a different reference voltage or an ADC with a different resolution you will have to sample the key readings yourself to determine the appropriate values to check for.

 The *KEYVAL_RANGE* constant allows a bit of flexibility in the reading. You are not going to get the exact same value every time you read the ADC and slight differences in the resistor values and track layout will add a difference as well.

 One important limitation to note is that you can only detect single key presses, not multiples. The reason is that the some of the values generated by multiple keys held down at once are close enough to the value generated by a single key that there is no way to tell which situation you are being presented with. For most cases this isn't a problem - if you really need reliable detection of multiple keys you will have to switch to digital input.

 The main code to determine if a key is pressed looks like this:
 /** Key codes      *      * Defined as bit masks so they can be combined easily.      */     typedef enum _KEYCODE {       KEYCODE_UP     = 0x01,       KEYCODE_RIGHT  = 0x02,       KEYCODE_DOWN   = 0x04,       KEYCODE_LEFT   = 0x08,       KEYCODE_CENTER = 0x10,       } KEYCODE;

 /** Determine which (if any) key is pressed      *      * @return a bit mask representing the key pressed or 0 if no key.      */     uint8_t keyRead() {       uint8_t result = 0xFF;       for(uint8_t count=0; count<4; count++) {         uint16_t keyval = adcRead(ADC_KEYBOARD, 1, 1);         if((keyval>=(KEYVAL_UP - KEYVAL_RANGE))&&(keyval<=(KEYVAL_UP + KEYVAL_RANGE)))           result &= KEYCODE_UP;         else if((keyval>=(KEYVAL_RIGHT - KEYVAL_RANGE))&&(keyval<=(KEYVAL_RIGHT + KEYVAL_RANGE)))           result &= KEYCODE_RIGHT;         else if((keyval>=(KEYVAL_DOWN - KEYVAL_RANGE))&&(keyval<=(KEYVAL_DOWN + KEYVAL_RANGE)))           result &= KEYCODE_DOWN;         else if((keyval>=(KEYVAL_LEFT - KEYVAL_RANGE))&&(keyval<=(KEYVAL_LEFT + KEYVAL_RANGE)))           result &= KEYCODE_LEFT;         else if((keyval>=(KEYVAL_CENTER - KEYVAL_RANGE))&&(keyval<=(KEYVAL_CENTER + KEYVAL_RANGE)))           result &= KEYCODE_CENTER;         }       // All done, a consistant keypress will have a valid bit code now       return result;       }

This function will return a value indicating which key is pressed. The key codes are defined as bit masks (bit 0 is UP, bit 1 is RIGHT, etc) so they can be combined easily. For example, you can test to see if LEFT or RIGHT are pressed with a simple boolean expression:

``` if(keyRead()&(KEYCODELEFT|KEYCODERIGHT)) { // Do something }

The purpose of the loop in the keyRead() function is to avoid detecting false positives. When the voltage changes it doesn't immediately jump to the new level, it has to rise or fall to get to the new location. If the analog sample is taken during this transition phase it will incorrectly detect the value as a completely separate key. To avoid this I do four consecutive samples and only count it as a key press if they all indicate the same key - if any of them are different the function will return 0.

Using four samples is a rather arbitrary number but gives reliable results without requiring the user to hold the key down for an excessive amount of time. If you find you are getting false key press detection's you can increase the number of times the loop iterates until they go away.

So there it is - an easy way to get simple keyboard input for a project using a minimum of pins. It's perfect for the ATtiny (or any other situation where IO pins are scarce). If you'd like to play with the code I've used simply clone my ATtiny template project, replace the main.c file with the one from this Gist and you are good to go. Have fun!