/****************************************************************************
COMBO2.C

PIC 16F84 Combination Lock
  - all combinations are six-digits
  - master combination is '083941'
  - four transient combinations may be programmed (all keys are allowed)
  - press within one-second after master combination:
      'A' to enter 1st transient combination
      'B' to enter 2nd transient combination
      'C' to enter 3rd transient combination
      'D' to enter 4th transient combination
      '#' to enter two-digit lamp ON seconds
      '*' to enter two-digit lock ON seconds
  - setting a feature to zeros deactivates it
  - resets to first expected key five seconds after last key is pressed

                    ---------          ----------
                   |  16F84  |        |          |                                  T
           +5 --14-|VCC    A0|-17-----|R0        |
           +5 ---4-|MCLR   A1|-18-----|R1        |
          Gnd ---5-|       A2|-1------|R2        |
         XTAL --16-|       A3|-2------|R3  KBD   |
         XTAL --15-|       B0|-6------|C0        |
                   |       B1|-7------|C1        |
                   |       B2|-8------|C2        |
                   |       B3|-9------|C3        |
                   |         |        |          |
                   |         |         ----------
                   |         |
 MANUAL SW -----12-|B6     B4|-10------------------- ILLUMINATION RELAY
 DISABLE SW ----11-|B5     B7|-13------------------- LOCK SOLENOID
                   |         |
                    ---------

        =============
        |  1 2 3 A  |  PB0
        |  4 5 6 B  |  PB1
        |  7 8 9 C  |  PB2
        |  * 0 # D  |  PB3
        =============
         PB4 5 6 7

Keyboard connector is:    C0 C1 C2 C3 R0 R1 R2 R3
PA0-3 is 4-row driver
PB0-3 is 4-column receiver
PB4 is lamp output (to illuminate keyboard, high active)
PB5 is lock disable switch input, low-active
PB6 is manual open switch input, low-active
PB7 is lock solenoid output (high active)
Uses 18mS watchdog timer

Oscillator = 4MHz crystal
Cycle time = 1uS

Jon Fick  05/14/08

***************************************************************************/
#include <16f84.h>
/* Set configuration bits in the PIC processor */
#fuses XT, NOPROTECT, WDT, PUT

#define     ON              1
#define     OFF             0
#define     HIGH            1
#define     LOW             0
#define     UP              1
#define     DOWN            0
#define     YES             1
#define     NO              0
#define     ACTIVE          1
#define     INACTIVE        0
#define     UCHAR           char
#define     UINT            long
#define     BIT             short
#define     POUND           12
#define     BLANK           0x20
#define     NOKEY           0xFF
#define     TRANS_BASE_A    6
#define     TRANS_BASE_B    13
#define     TRANS_BASE_C    20
#define     TRANS_BASE_D    27
#define     LAMP_TIME_BASE  34
#define     LOCK_TIME_BASE  37
#define     COMBO_ACTIVE_FLAG_OFFSET 6
#define     LAMP_ACTIVE_FLAG_OFFSET 2
#define     LOCK_ACTIVE_FLAG_OFFSET 2
#define     WAITKEY_TIMEOUT 75          /* at 15/second */
#define     RESET_KBD_COUNT 75          /* at 15/second */

#use delay ( clock = 4000000, restart_wdt )      /* sets appropriate compiler constants */
#use fast_io ( A )              /* don't set TRIS on each I/O statement */
#use fast_io ( B )
#zero_ram

/* initialize EEPROM */
#rom 0x2100 =   {
                0, 8, 3, 9, 4, 1,               /* master combo (6) */
                0, 0, 0, 0, 0, 0, INACTIVE,     /* transient combo A (6) and ACTIVE flag (1) */
                0, 0, 0, 0, 0, 0, INACTIVE,     /* transient combo B (6) and ACTIVE flag (1) */
                0, 0, 0, 0, 0, 0, INACTIVE,     /* transient combo C (6) and ACTIVE flag (1) */
                0, 0, 0, 0, 0, 0, INACTIVE,     /* transient combo D (6) and ACTIVE flag (1) */
                1, 5, ACTIVE,                   /* lamp time seconds (2) and ACTIVE flag (1) */
                0, 1, ACTIVE                    /* lock time seconds (2) and ACTIVE flag (1) */
                }

/* Use #byte to assign variables to a RAM locations */
#byte   PORT_A      =   5           /* set variable that maps to memory */
#byte   PORT_B      =   6
#bit    MANUAL_SW   =   PORT_B.6
#bit    DISABLE_SW  =   PORT_B.5
#bit    ILL_OUT     =   PORT_B.4
#bit    LOCK_OUT    =   PORT_B.7

char GetKey ( void );                           /* prototypes */
char WaitKey ( void );
void StartOpenLock ( char cCnt );
void StartLampTimer ( char cCnt );
void GetNewData ( char cBaseAddress, char cOffset, char cKeyCount, char cAllKeys );
void UpdateLampSetting ( void );
void UpdateLockSetting ( void );
void ClearFlags ( void );
void Flash ( void );

static long iLampCount, iLampLimit, iLockCount, iLockLimit;
static char cLampSeconds, cLockSeconds;
static char cGettingNewData, cKeyCnt;
static char cMAinvalid, cTAinvalid, cTBinvalid, cTCinvalid, cTDinvalid;
static char cTIinvalid, cTLinvalid;
static char cProgMode, cProgTimeoutCount, cResetKbdCount;

void main ( void )
    {
    char cX, cK;

    set_tris_a ( 0b11110000 );                  /* set inputs and outputs */
    set_tris_b ( 0b01101111 );
    port_b_pullups ( TRUE );                    /* enable pullups */
    setup_counters ( RTCC_INTERNAL, RTCC_DIV_256 );     /* 65mS roll */
    enable_interrupts ( INT_RTCC );             /* turn on timer interrupt */
    enable_interrupts ( GLOBAL );               /* enable interrupts */
    cKeyCnt = 0;
    ILL_OUT = OFF;                              /* turn off lamp relay */
    LOCK_OUT = OFF;                             /* turn off lock solenoid */
    UpdateLampSetting();                        /* update lamp timer count */
    UpdateLockSetting();                        /* update lock timer count */
    ClearFlags();
    cGettingNewData = NO;
    cProgMode = NO;

    while ( TRUE )
        {
        restart_wdt();                          /* kick dog */

        if ( MANUAL_SW == LOW )                 /* if manual open switch pressed */
                {
                StartLampTimer ( cLampSeconds );        /* turn lamp on */
                StartOpenLock( cLockSeconds );          /* energize solenoid */
                cKeyCnt = 0;
                while ( MANUAL_SW == LOW )      /* wait for switch to be released */
                        {
                        restart_wdt();
                        }
                }
        else
                {
                cK = GetKey();          /* check if key pressed */
                if ( cK != NOKEY )      /* validate combo as entered, could be aliased */
                        {
                        if ( DISABLE_SW == LOW )
                                {
                                StartOpenLock( cLockSeconds );  /* any key opens lock */
                                cKeyCnt = 0;
                                }
                        else
                                {
                                if ( cK != read_eeprom ( cKeyCnt ) )    /* if not master combo key */
                                        {
                                        cMAinvalid = ON;
                                        }
                                if ( cK != read_eeprom ( cKeyCnt + TRANS_BASE_A ) )     /* if not transient combo key */
                                        {
                                        cTAinvalid = ON;
                                        }
                                if ( cK != read_eeprom ( cKeyCnt + TRANS_BASE_B ) )     /* if not transient combo key */
                                        {
                                        cTBinvalid = ON;
                                        }
                                if ( cK != read_eeprom ( cKeyCnt + TRANS_BASE_C ) )     /* if not transient combo key */
                                        {
                                        cTCinvalid = ON;
                                        }
                                if ( cK != read_eeprom ( cKeyCnt + TRANS_BASE_D ) )     /* if not transient combo key */
                                        {
                                        cTDinvalid = ON;
                                        }
                                if ( ( cMAinvalid == OFF ) || ( cTAinvalid == OFF ) || ( cTBinvalid == OFF ) || ( cTCinvalid == OFF ) || ( cTDinvalid == OFF ) ) /* if any combo still good */
                                        {
                                        cKeyCnt++;                      /* point to next code in combo */
                                        }
                                else
                                        {
                                        cKeyCnt = 0;            /* restart count */
                                        ClearFlags();
                                        }
                                }
                        }
                }

        if ( cKeyCnt == 6 )             /* if one of the combos was correcct */
                {
                cKeyCnt = 0;
                StartOpenLock( cLockSeconds );  /* energize solenoid for programmed time */
                if ( cMAinvalid == OFF )        /* if master combo was entered */
                        {
                        for ( cX = 0; cX < 50; cX++ )           /* wait one second for a key */
                                {
                                cK = GetKey();
                                if ( cK != NOKEY )
                                        {
                                        cGettingNewData = YES;
                                        break;
                                        }
                                delay_ms ( 20 );
                                }
                        if ( cK == 'A' )        /* if changing transient combo */
                                {
                                GetNewData ( TRANS_BASE_A, COMBO_ACTIVE_FLAG_OFFSET, 6, YES );
                                }
                        if ( cK == 'B' )        /* if changing transient combo */                               {
                                GetNewData ( TRANS_BASE_B, COMBO_ACTIVE_FLAG_OFFSET, 6, YES );
                                }
                        if ( cK == 'C' )        /* if changing transient combo */
                                {
                                GetNewData ( TRANS_BASE_C, COMBO_ACTIVE_FLAG_OFFSET, 6, YES );
                                }
                        if ( cK == 'D' )        /* if changing transient combo */
                                {
                                GetNewData ( TRANS_BASE_D, COMBO_ACTIVE_FLAG_OFFSET, 6, YES );
                                }
                        if ( cK == '#' )        /* if changing lamp seconds */
                                {
                                GetNewData ( LAMP_TIME_BASE, LAMP_ACTIVE_FLAG_OFFSET, 2, NO );
                                UpdateLampSetting();
                                }
                        if ( cK == '*' )        /* if changing lock solenoid seconds */
                                {
                                GetNewData ( LOCK_TIME_BASE, LOCK_ACTIVE_FLAG_OFFSET, 2, NO );
                                UpdateLockSetting();
                                }
                        cGettingNewData = NO;
                        }
                ClearFlags();
                }
        }
    }


/******************************************************************************/
char WaitKey ( void )
        {
        char cK;

        while ( TRUE )
                {
                cK = getkey();
                if ( cK != NOKEY )
                        {
                        return ( cK );
                        }
                }
        }

char GetKey ( void )
    {
    char cKey;

    cKey = NOKEY;                               /* default is invalidated key */
    PORT_A = 0xF0;                              /* make all four rows low */
    delay_ms ( 5 );                             /* wait for row lines to settle */
    if ( ( PORT_B & 0x0F ) != 0x0F )            /* if a col is low */
        {
        delay_ms ( 5 );                         /* debounce */
        PORT_A = 0b11111110;                    /* try row 0 */
        if ( ( PORT_B & 0x0F ) == 0b00001110 )  /* check columns */
            {
            cKey = 1;
            }
        if ( ( PORT_B & 0x0F ) == 0b00001101 )
            {
            cKey = 2;
            }
        if ( ( PORT_B & 0x0F ) == 0b00001011 )
            {
            cKey = 3;
            }
        if ( ( PORT_B & 0x0F ) == 0b00000111 )
            {
            cKey = 'A';
            }
        PORT_A = 0b11111101;                    /* try row 1 */
        if ( ( PORT_B & 0x0F ) == 0b00001110 )  /* check columns */
            {
            cKey = 4;
            }
        if ( ( PORT_B & 0x0F ) == 0b00001101 )
            {
            cKey = 5;
            }
        if ( ( PORT_B & 0x0F ) == 0b00001011 )
            {
            cKey = 6;
            }
        if ( ( PORT_B & 0x0F ) == 0b00000111 )
            {
            cKey = 'B';
            }
        PORT_A = 0b11111011;                    /* try row 2 */
        if ( ( PORT_B & 0x0F ) == 0b00001110 )           /* check columns */
            {
            cKey = 7;
            }
        if ( ( PORT_B & 0x0F ) == 0b00001101 )
            {
            cKey = 8;
            }
        if ( ( PORT_B & 0x0F ) == 0b00001011 )
            {
            cKey = 9;
            }
        if ( ( PORT_B & 0x0F ) == 0b00000111 )
            {
            cKey = 'C';
            }
        PORT_A = 0b11110111;                    /* try row 3 */
        if ( ( PORT_B & 0x0F ) == 0b00001110 )           /* check columns */
            {
            cKey = '*';
            }
        if ( ( PORT_B & 0x0F ) == 0b00001101 )
            {
            cKey = 0;
            }
        if ( ( PORT_B & 0x0F ) == 0b00001011 )
            {
            cKey = '#';
            }
        if ( ( PORT_B & 0x0F ) == 0b00000111 )
            {
            cKey = 'D';
            }
        delay_ms ( 5 );
        }
    PORT_A = 0xF0;                          /* make all four rows low */
    while ( ( PORT_B & 0x0F ) != 0x0F )   /* wait until all columns are high */
        {
        delay_ms ( 5 );
        }
    PORT_A = 0xFF;                          /* make all four rows high */
    if ( ( cKey != NOKEY ) && ( cGettingNewData == NO ) )
        {
        StartLampTimer ( cLampSeconds );        /* turn lamp on anytime a key is pressed */
        }
    return ( cKey );
    }

void GetNewData ( char cBaseAddress, char cOffset, char cKeyCount, char cAllKeys )
        {
        char cCnt, cK;
        char cActive = NO;              /* default = inactive */

        cProgMode = YES;
        for ( cCnt = 0; cCnt < cKeyCount; cCnt++ )
                {
                cK = WaitKey();         /* get key */
                if ( ( cAllKeys == NO ) && ( cK > 9 ) ) /* if out of range */
                        {
                        cK = 0;         /* zero it */
                        }
                write_eeprom ( cBaseAddress + cCnt, cK );
                if ( cK != 0 )                  /* if any key is not zero... */
                        {
                        cActive = YES;          /* show that this field is active */
                        }
                }
        write_eeprom ( cBaseAddress + cOffset, cActive );       /* put in active flag */
        cProgMode = NO;
        }

void UpdateLampSetting ( void )
        {
        cLampSeconds = ( 10 * read_eeprom ( LAMP_TIME_BASE ) ) + read_eeprom ( LAMP_TIME_BASE + 1 );
        }

void UpdateLockSetting ( void )
        {
        cLockSeconds = ( 10 * read_eeprom ( LOCK_TIME_BASE ) ) + read_eeprom ( LOCK_TIME_BASE + 1 );
        }

void ClearFlags ( void )
        {
        cMAinvalid = OFF;       /* default is off until mismatch found */
        if ( read_eeprom ( TRANS_BASE_A + COMBO_ACTIVE_FLAG_OFFSET ) == NO )
                {
                cTAinvalid = ON;
                }
        else
                {
                cTAinvalid = OFF;
                }
        if ( read_eeprom ( TRANS_BASE_B + COMBO_ACTIVE_FLAG_OFFSET ) == NO )
                {
                cTBinvalid = ON;
                }
        else
                {
                cTBinvalid = OFF;
                }
        if ( read_eeprom ( TRANS_BASE_C + COMBO_ACTIVE_FLAG_OFFSET ) == NO )
                {
                cTCinvalid = ON;
                }
        else
                {
                cTCinvalid = OFF;
                }
        if ( read_eeprom ( TRANS_BASE_D + COMBO_ACTIVE_FLAG_OFFSET ) == NO )
                {
                cTDinvalid = ON;
                }
        else
                {
                cTDinvalid = OFF;
                }
        if ( read_eeprom ( LAMP_TIME_BASE + LAMP_ACTIVE_FLAG_OFFSET ) == NO )
                {
                cTIinvalid = ON;
                }
        else
                {
                cTIinvalid = OFF;
                }
        if ( read_eeprom ( LOCK_TIME_BASE + LOCK_ACTIVE_FLAG_OFFSET ) == NO )
                {
                cTLinvalid = ON;
                }
        else
                {
                cTLinvalid = OFF;
                }
        }

void StartOpenLock ( char cCnt )
        {
        disable_interrupts ( INT_RTCC );
        if ( cTLinvalid == NO )
                {
                LOCK_OUT = ON;                  /* energize solenoid */
                }
        iLockLimit = ( long ) cCnt * 15;        /* 15 tics per second */
        iLockCount = 0;                         /* zero timer for turning lock solenoid off */
        enable_interrupts ( INT_RTCC );
        }

void StartLampTimer ( char cCnt )
        {
        disable_interrupts ( INT_RTCC );
        if ( cTIinvalid == NO )                 /* if lamp was programmed for other than 0 seconds */
                {
                ILL_OUT = ON;                   /* turn on lamp */
                }
        iLampLimit = ( long ) cCnt * 15;        /* 15 tics per second */
        ilampCount = 0;                         /* zero timer for turning lamp off */
        cResetKbdCount = 0;                     /* zero timer for keyboard reset */
        enable_interrupts ( INT_RTCC );
        }

#int_rtcc
void TimerInterrupt ( void )          /* 65mS tic */
        {
        if ( cProgMode == YES )                         /* if in program mode */
                {
                if ( cProgTimeoutCount++ >= 8 )
                        {
                        ILL_OUT ^= 1;                   /* toggle lamp state to signal programming mode */
                        cProgTimeoutCount = 0;
                        }
                }
        else                                            /* if in normal mode */
                {
                if ( iLampCount++ > iLampLimit )        /* if timeout yet */
                        {
                        ILL_OUT = OFF;                  /* turn lamp off */
                        }
                if ( cResetKbdCount++ > RESET_KBD_COUNT )
                        {
                        cKeyCnt = 0;                    /* reset to first key */
                        }
                if ( iLockCount++ > iLockLimit )
                        {
                        LOCK_OUT = OFF;                 /* de-energize lock solenoid */
                        }
                }
        }