Keypad plus Teensy++

I re-discovered a 2×2 keypad from Sparkfun (COM-09277), which I ordered several months ago, but only managed to do some very preliminary experiments. The keypad has RGB LEDs installed underneath each button (see photo on the left), which need to be multiplexed due to the shared connections. In this post, I’ll describe how I assembled the keypad into a case and configured it as a PC keyboard. I use it to convert key combinations to pre-defined text fragments (email footers, long passwords, common terminal commands, etc).

Hardware part

I had wired up the unit already, and initially connected it to a Teensy 2.0 on a breadboard (which can actually be seen in the background of the photo), on which I tried out different timer frequencies for multiplexing the LEDs. I didn’t like the fragile character of the breadboard, though. So I set out on the quest to integrate the complete circuit into a decent case. The 2×2 keypad measures roughly 50x50mm, and its mounting holes were approximately 5mm off the sides. So I set out to find a case that was slightly larger than the keypad, and at the same time had supports that fit the mounting holes of the keypad. This wasn’t a trivial task, and I eventually settled on a grey Strapubox 2412 case (black ones were not in stock, unfortunately), which was only slightly larger than the keypad, but the mounting standoffs were not really aligned with the keypad, so some little modifications were necessary. By extending the holes of the bezel and the keypad PCB, the PCB now neatly fits into the case, into which I have also cut the four holes of 16x16mm square using a dremel to cut along the markings roughly, and a scalpel to smooth the edges later on. The little pin in the center of the case also had to be taken out to allow the bezel to fit inside. The resulting upper case now perfectly fit the keypad PCB, which occupies about half of the available vertical space in the case. The remaining 8mm of height are more than sufficient to place a microcontroller board inside. For this project, I used a Teensy++ 2.0 device, which I also found hiding in my component stash. The nice side effect is that the Teensy is exactly two inches long and hence the same length as the keypad PCB. I soldered a 90 degree pin header with 2 contacts to the reset and GND pins to give it additional support against the back panel. All remaining space was filled using pieces of (non-conductive) styrofoam which came shipped with the pin headers.


Software part

I didn’t feel like re-inventing the wheel, so I based my code design on the USB Keyboard code provided on the Teensy website. In addition to the existing code, I created two C files that would read the keypad status and multiplex the LEDs to have them display the desired color.


My button code is based on a periodic timer (I used Timer 1), which I configured to fire every 32ms (more precisely, I set the prescaler to 1024, resulting in 16MHz/1024 = 15625 ticks per second. The timer is configured to fire when OCR value of 500 (i.e., 32 milliseconds) is reached and to reset OCR to zero when fired).

TCCR1B = (1<<CS12)|(1<<CS10)|(1<<WGM12);
TIMSK1 |= (1<<OCIE1A);
OCR1A = 500;

Whenever the interrupt handler is called, I store the previous button state in a variable called prevState, followed by polling the button states and storing them in the currentState variable. The debounced button state is then returned by the function getButtonStates(), which performs a logical AND operation of the current and the previous readings.

uint8_t prevState, currentState;
  prevState = currentState;
  currentState = [ button states...]

uint8_t getButtonStates(void) {
  return (prevState & currentState);

The LED driver also relies on a timer, but needs a somewhat higher frequency, though. I used Timer 0 and configured it to use a prescaler of 64 and an overflow value of 96 (in fact it took me quite a while to optimize between LED brightness and visible flicker). That’s one call every 384 microseconds. The interrupt handler code is based on a state machine, which traverses all colors of all LEDs and activates the corresponding pins if required. The desired primary colors are stored in the ledState[4][3] double array, the first index denoting the LED that is addressed, and the second index the three colors that can be set.

ISR(TIMER0_OVF_vect) {
  if (state >= 16) {
    state = 0;

  switch (state) {
  case 0:
    if (ledState[0][0]) { TURN_RED_ON; }
  case 1:
    if (ledState[0][1]) { TURN_GREEN_ON; }
  case 2:
Main program

Finally, three actions were inserted into the infinite loop of the main program. Firstly, a screensaver was activated which would indefinitely cycle each LED through the available colors if no button is pressed. Secondly, a function was written that takes any button presses and writes them to an array. Thirdly, I included code to check this array for known sequences and to emit the corresponding USB keystrokes.

#define ANIMATION_DELAY 5 /* Default animation delay in cycles */
uint8_t animation = ANIMATION_DELAY;
uint8_t color = 1;
uint8_t keyorder[8];
uint8_t repetitionLock = 0;

while (1) {
  uint8_t debouncedStates = getButtonStates();

  if (debouncedStates == 0) {
    if (animation == 0) {
      for (uint8_t i=0;i<8;i++) keyorder[i] = 0; /* Clear key buffer */
      setLed(color%4, (color%7)+1, 0); 
      if (++color==28) color = 0;
      animation = ANIMATION_DELAY;
    } else animation--;	/* Wait for next animation step */


  } else {
    if ((repetitionLock == 0) || (keyorder[0] != debouncedStates)) {
      for (uint8_t i=7;i>0;i--) keyorder[i] = keyorder[i-1];
      keyorder[0] = debouncedStates;
      repetitionLock = 10; /* 200 ms before key is accepted again */
      animation = 10 * ANIMATION_DELAY; /* wait for more input */

  [...match keyorder array against known sequences...] 
  [...emit USB keystrokes according to the detected sequence]

  if (repetitionLock > 0) repetitionLock--;


In the picture below one can see the semi-final version (it still features the connection to an external reset button for easier programming on the left) in screensaver mode, i.e., cycling the LEDs. I tend to still make minor modifications to the code, but consider the hardware part of the device pretty final.

Comments are closed.