//
// Title        : Keyboard decoder module with debounce
// Author       : Lars Pontoppidan Larsen
// Date         : February 13, 2006
// Version      : 1.00
// Target MCU   : Atmel AVR Series
//
// DESCRIPTION:
// This module implements decoding a 2x3 keyboard matrix. Uses IO ports as 
// output and input with internal pullup.
//
// USAGE:
// kbd_init() should be called at init and kbd_handler() should be called
// frequently. Each of the 8 buttons represent a bit in kbd_status. 
// Whenever keys have settled to a new status, it will be copied to kbd_status
//
// RESOURCES:
// 4 bytes of sram.
//
// DISCLAIMER:
// The author is in no way responsible for any problems or damage caused by
// using this code. Use at your own risk.
//
// LICENSE:
// This code is distributed under the GNU Public License
// which can be found at http://www.gnu.org/licenses/gpl.txt
//

#include <avr/io.h>
#include "kbd.h"

/******************************************************************************
*                                                                             *
*                           HARDWARE ABSTRACTION                              *
*                                                                             *
*  In this section it is defined what hardware resources the module uses.     *
*                                                                             *
******************************************************************************/


#define KBD_PIN  PINC
#define KBD_DDR  DDRC
#define KBD_PORT PORTC

#define KBD_X1   (1<<0)
#define KBD_X2   (1<<1)
#define KBD_Y1   (1<<2)
#define KBD_Y2   (1<<3)
#define KBD_Y3   (1<<4)



/******************************************************************************
*                                                                             *
*                                  GLOBALS                                    *
*                                                                             *
******************************************************************************/


unsigned char kbd_status; // Current status of keyboard. EXTERN
unsigned char x_now;      // The X signal held low, either KBD_X1 or KBD_X2.
unsigned char new_kbd;    // Proposed new status of keyboard
unsigned char debounce;   // Debounce status

// Waits KBD_DEBOUNCE cycles before accepting new kbd status

#define KBD_DEBOUNCE 20

 
/******************************************************************************
*                                                                             *
*                               IMPLEMENTATION                                *
*                                                                             *
******************************************************************************/


void kbd_init(void)
{
   
  // Make Y input and X high impedance (input)
  KBD_DDR = KBD_DDR & ~(KBD_Y1 | KBD_Y2 | KBD_Y3 | KBD_X1 | KBD_X2);
  
  // We want pullup on Y, and not on X
  KBD_PORT = (KBD_PORT | (KBD_Y1 | KBD_Y2 | KBD_Y3)) & ~(KBD_X1 | KBD_X2);
  
  x_now = 0;
  kbd_status = 0;
  debounce = 0;
  new_kbd = 0;
  
}
 
void kbd_handler(void)
{
  unsigned char pins;
  unsigned char ys=0;
  unsigned char old_kbd;
  
  // Sample pins and release X signals
  pins = ~KBD_PIN;
  KBD_DDR &= ~(KBD_X1 | KBD_X2);
  
  // Translate X/Y pairs to 
  
  if (pins & KBD_Y1)
    ys |= 1;

  if (pins & KBD_Y2)
    ys |= 2;
    
  if (pins & KBD_Y3)
    ys |= 4;
    
  old_kbd = new_kbd;
    
  if (x_now == KBD_X1) {
    // Place active keys in low nibble
    new_kbd = (new_kbd & 0xF0) | ys;
    
    // Pull X2 low now
    x_now = KBD_X2;
    KBD_DDR |= KBD_X2;
  }
  else {
    // Place active keys in high nibble
    new_kbd = (new_kbd & 0x0F) | (ys << 4);
    
    // Pull X1 low now
    x_now = KBD_X1;
    KBD_DDR |= KBD_X1;
  }
  
  if (old_kbd != new_kbd) {
    // There were changes on keyboard. Reset debounce
    debounce = KBD_DEBOUNCE;
  }
  else {
    if (debounce > 0) {
      debounce--;
      if (debounce == 0)
        // Debounce finished. New keys are accepted
        kbd_status = new_kbd;
    }
  }
  
}

