#include "keypad.h"
// Limitations of this library:
// * Hardcoded to use PB0-6
// * Will only decode a single keypress at a time. The highest row and rightmost column have priority.

// This #define lets you customize the number of times a key has to be repeated in order to register.
// Keys will naturally bounce a bit when pressed, so this filters out any noise/bouncing.
#define DEBOUNCE_NUM 10

// This is the lookup table for the keypad separated by columns
char col1table[] = {'1','4','7','*'};
char col2table[] = {'2','5','8','0'};
char col3table[] = {'3','6','9','#'};

uint8_t debounce_counter = 0;

// tmpkey is used in decode_state to store the previous and current key values.
// There's no particular reason that it is an array.
char tmpkey[2];

void init_keypad(void)
{
    keypad_state = wait_state; // Initialize to the first state (Wait)
    key = 0;

    // Set PB4-6 as low outputs
    DDRB |= (1 << PB4)|(1 << PB5)|(1 << PB6);
    PORTB &= ~((1 << PB4)|(1 << PB5)|(1 << PB6));

    // Explicitly set PB0-3 inputs, even though DDRB defaults to 0x00
    // Set pullup resistors on PB0-3
    DDRB &= ~((1 << PB0)|(1 << PB1)|(1 << PB2)|(1 << PB3));
    PORTB |= (1 << PB0)|(1 << PB1)|(1 << PB2)|(1 << PB3);
}

void wait_state(void)
{
    // &0x0F is a mask that will allow me to discard the first top 4 bits in the byte.
    // This lets me easily check _only_ the row pins. If any are low, then a key has been pressed.
    if ( (PINB&0x0F) != 0x0F )
    {
        keypad_state = decode_state;
    }
    return;
}

void decode_state(void)
{
    uint8_t rows, row;
    rows = PINB&0x0F;
    row = 0;

    while ( (row < 4) && (rows&0x01) )
    {
        rows >>= 1;
        row++;
    }


    // Test the first column by setting it high
    PORTB |= (1 << PB4);
    // Page 48 and 49 of the ATtiny2313 datasheet talk about a synchronization delay when reading from PINB.
    // This nop is inserted so the signal has time to propagate to the PINB register.
    asm volatile("nop"::);
    if ((PINB&0x0F) == 0x0F)
    {
        // Set the first "stage" of tmpkey
        tmpkey[0] = col1table[row];
    }
    // Reset the column back to low before testing the next one
    PORTB &= ~(1 << PB4);

    // This chunk of code and the next are similar to the one above, only the columns have been changed.
    PORTB |= (1 << PB5);
    asm volatile("nop"::);
    if ((PINB&0x0F) == 0x0F)
    {
        tmpkey[0] = col2table[row];
    }
    PORTB &= ~(1 << PB5);

    PORTB |= (1 << PB6);
    asm volatile("nop"::);
    if ((PINB&0x0F) == 0x0F)
    {
        tmpkey[0] = col3table[row];
    }
    PORTB &= ~(1 << PB6);


    // If this is the first time decode_state has been entered, both parts of tmp_key need to be initialized.
    // This ensures that decode_state will be called again.
    if (debounce_counter == 0)
    {
        tmpkey[1] = tmpkey[0];
    }

    // If the previous and current values aren't equal, then drop back to wait_state and reset the debounce counter.
    if (tmpkey[1] != tmpkey[0])
    {
        debounce_counter = 0;
        keypad_state = wait_state;
        return;
    }
    // The commented out line isn't strictly necessary, since the only time it will be executed is when
    // they're already the same.
    //tmpkey[1] = tmpkey[0];

    debounce_counter++;

    // Once the input has been sufficiently debounced, acknowledge this as a valid keypress by setting
    // `key` and moving to the next state.
    if (debounce_counter > DEBOUNCE_NUM)
    {
        key = tmpkey[0];
        debounce_counter = 0;
        keypad_state = keypress_state;
    }
    return;
}

void keypress_state(void)
{
    // Similar to the if statement in wait_state, this one checks to see if the row pins go back to
    // their default state
    if( (PINB&0x0F) == 0x0F )
    {
        keypad_state = wait_state;
    }
    return;
}

// A simple wrapper to zero out the key once it's read.
// The final application won't need to know how long the key has been pressed, so this is fine.
char get_key(void)
{
    char ret_key = key;
    key = 0;
    return ret_key;
}
