/****************************************************************************
DARK01.C

This is a darkroom lamp control system.


                   ---------
           +5--20-|Vdd      |
           +5---1-|Mclr     |
          Gnd---8-|Vss      |
          Gnd--19-|Vss      |
         4MHz--10-|Xtal     |
             ---9-|Xtal     |
                  |         |
                  | 16F873  |-26-->--- Relay K2 safe lamp
In from           |         |-27-->--- Relay K3 enlarger lamp
photocell         |         |-28-->--- audible alarm
amplifier --->--2-|A0       |
                  |         |
                  |         |
Switch A ---->-21-|B0       |
Switch B ---->-22-|B1       |
Switch B ---->-23-|B2       |              ---------
Switch B ---->-24-|B3     C4|-15-->----11-|D4       |
                  |       C5|-16-->----12-|D5       |
                  |       C6|-17-->----13-|D6       |
                  |       C7|-18-->----14-|D7       |
                  |         |             |         |-3--20K pot (contrast)
                  |       C3|-14-->-----6-|EN       |
                  |       C2|-13-->-----4-|RS       |
                  |         |             |         |
                  |         |        +5-2-|         |
                  |         |       Gnd-1-|         |
                  |         |       Gnd-5-|         |
                  |         |             | DISPLAY |
                   ---------               ---------

*/
#case
#include < 16F873.H >
#include < jonsinc.h >
#device *=16 ADC=10

#fuses XT, NOPROTECT, PUT, NOWDT, BROWNOUT, NOLVP, NOCPD, NOWRT

/* INTERNAL EEPROM ADDRESS ASSIGNMENTS */
#define ENLARGE_TIME        0
#define TIMER_MINUTES       1
#define TIMER_SECONDS       2
#define PAPER_SPEED_HI      3
#define PAPER_SPEED_LO      4

/* LCD STUFF */
#define LCD_D0  PIN_C4
#define LCD_D1  PIN_C5
#define LCD_D2  PIN_C6
#define LCD_D3  PIN_C7
#define LCD_EN  PIN_C3
#define LCD_RS  PIN_C2
#define FIRST_LINE  0x00
#define SECOND_LINE 0x40
#define CLEAR_DISP  0x01

#define SWITCH_A            PIN_B0
#define SWITCH_B            PIN_B1
#define SWITCH_C            PIN_B2
#define SWITCH_D            PIN_B3
#define RELAY_SAFELAMP      PIN_B4
#define RELAY_ENLARGER      PIN_B5
#define ALERT               PIN_B7

#define CAL_PAPER_SPEED     500
#define AUTOREPEAT_DELAY    150
#define DEBOUNCE_DELAY      20

#use delay ( clock=4000000 )
#use standard_io ( A )
#use standard_io ( B )
#use standard_io ( C )

void DisplayMenuFocus ( void );
void DisplayMenuExpose ( void );
void DisplayMenuTimer ( void );
void LCD_Init ( void );
void LCD_SetPosition ( unsigned int cX );
void LCD_PutChar ( unsigned int cX );
void LCD_PutCmd ( unsigned int cX );
void LCD_PulseEnable ( void );
void LCD_SetData ( unsigned int cX );
void DebounceSwitchA ( void );
void DebounceSwitchB ( void );
void DebounceSwitchC ( void );
void DebounceSwitchD ( void );

static char cEnlargeTime, cMenu;
static char cSafeLamp, cEnlargerLamp;
static char cAlarmMinutes, cAlarmSeconds;
static long iPaperSpeed;

/******************************************************************************/

void main ( void )
    {
    char cX;

    // preset all output bits
    output_high ( RELAY_SAFELAMP );
    output_low ( RELAY_ENLARGER );
    output_low ( ALERT );

    // initilize screen
    LCD_Init();
    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "DARKROOM SYSTEM " );
    LCD_SetPosition ( SECOND_LINE );
    printf ( LCD_PutChar, "      v1.1      " );
    delay_ms ( 1000 );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "    Jon Fick    " );
    LCD_SetPosition ( SECOND_LINE );
    printf ( LCD_PutChar, "    01/20/03    " );
    delay_ms ( 500 );
    LCD_PutCmd ( CLEAR_DISP );

    /* RESTORE PREVIOUS STATE */
    cEnlargeTime =  read_eeprom ( ENLARGE_TIME );
    if ( cEnlargeTime > 99 )
        {
        cEnlargeTime = 10;              // default if EEPROM not written before
        }
    cAlarmMinutes =  read_eeprom ( TIMER_MINUTES );
    if ( cAlarmMinutes > 59 )
        {
        cAlarmMinutes = 0;              // default if EEPROM not written before
        }
    cAlarmSeconds =  read_eeprom ( TIMER_SECONDS );
    if ( ( cAlarmSeconds > 59 ) || ( ( cAlarmSeconds % 10 ) != 0 ) )
        {
        cAlarmSeconds = 0;              // default if EEPROM not written before or not an increment of 10
        }
    iPaperSpeed = ( 256 * read_eeprom ( PAPER_SPEED_HI ) ) + read_eeprom ( PAPER_SPEED_LO );
    if ( ( iPaperSpeed > 1000 ) || ( ( iPaperSpeed % 50 ) != 0 ) )
        {
        iPaperSpeed = 500;              // default if EEPROM not written before or not an increment of 50
        }

    cSafeLamp = ON;                     // safelamp state

    port_b_pullups ( TRUE );            // turn on port B pullups
    setup_adc_ports ( RA0_ANALOG );     /* these three statements set up the ADC */
    setup_adc ( ADC_CLOCK_INTERNAL );   /* clock source */
    set_adc_channel ( 0 );              /* select channel */

    // check if setup mode if SWITCH_A held down during reset
    if ( input ( SWITCH_A ) == LOW )
        {
        LCD_PutCmd ( CLEAR_DISP );
        LCD_SetPosition ( FIRST_LINE );
        printf ( LCD_PutChar, "Paper speed     " );
        LCD_SetPosition ( SECOND_LINE );
        printf ( LCD_PutChar, "NXT DEC INC     " );
        DebounceSwitchA();
        while ( TRUE )
            {
            if ( input ( SWITCH_A ) == LOW )
                {
                write_eeprom ( PAPER_SPEED_HI, iPaperSpeed / 256 );        // save speed to EEPROM
                write_eeprom ( PAPER_SPEED_LO, iPaperSpeed % 256 );
                break;                      // drop out to main menu sequence
                }
            if ( input ( SWITCH_B ) == LOW )
                {
                if ( iPaperSpeed > 50 )
                    {
                    iPaperSpeed -= 50;      // decrement by 50
                    delay_ms ( AUTOREPEAT_DELAY );       // autorepeat delay
                    }
                }
            if ( input ( SWITCH_C ) == LOW )
                {
                if ( iPaperSpeed <= 949 )
                    {
                    iPaperSpeed += 50;     // increment by 50
                    delay_ms ( AUTOREPEAT_DELAY );       // autorepeat delay
                    }
                }
            // display adjusted paper speed
            LCD_SetPosition ( FIRST_LINE + 12 );
            printf ( LCD_PutChar, "%03lu", iPaperSpeed );
            }
        }

    while ( TRUE )
        {
        LCD_PutCmd ( CLEAR_DISP );
        LCD_SetPosition ( FIRST_LINE );
        printf ( LCD_PutChar, "Select...       " );
        LCD_SetPosition ( SECOND_LINE );
        printf ( LCD_PutChar, "SAF FOC EXP TIMR" );
        while ( TRUE )
            {
            if ( input ( SWITCH_A ) == LOW )
                {
                DebounceSwitchA();
                if ( cSafeLamp == ON )          // toggle safelight
                    {
                    cSafeLamp = OFF;
                    output_low ( RELAY_SAFELAMP );
                    }
                else
                    {
                    cSafeLamp = ON;
                    output_high ( RELAY_SAFELAMP );
                    }
                break;
                }
            if ( input ( SWITCH_B ) == LOW )
                {
                DebounceSwitchB();
                DisplayMenuFocus();
                break;
                }
            if ( input ( SWITCH_C ) == LOW )
                {
                DebounceSwitchC();
                DisplayMenuExpose();
                break;
                }
            if ( input ( SWITCH_D ) == LOW )
                {
                DebounceSwitchD();
                DisplayMenuTimer();
                break;
                }
            delay_ms ( 100 );
            }
        }
    }

void DisplayMenuFocus ( void )
    {
    char cCnt, cAdcCnt;
    long iAdc, iAdcAvg;
    float fAdc;

    cAdcCnt = 0;       // reset to zero readings count
    iAdcAvg = 0;       // reset average to zero
    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "Expo Meter      " );
    LCD_SetPosition ( SECOND_LINE );
    printf ( LCD_PutChar, "RET             " );
    output_high ( RELAY_ENLARGER );
    while ( TRUE )
        {
        if ( input ( SWITCH_A ) == LOW )
            {
            DebounceSwitchA();
            break;                  // drop out
            }
        iAdc = read_adc();          // read ADC
        if ( cAdcCnt++ < 20 )         // if less than ten counts
            {
            iAdcAvg += iAdc;        // add ADC reading into average
            }
        else
            {
            iAdcAvg /= 20;          // calculate average
            // scale reading
            fAdc = ( float ) iAdcAvg / 1024 * 200 * iPaperSpeed / CAL_PAPER_SPEED;
            // display reading
            LCD_SetPosition ( FIRST_LINE + 11 );
            printf ( LCD_PutChar, "%3.0f%%  ", fAdc );
            cAdcCnt = 0;    // reset
            iAdcAvg = 0;    // reset
            }
        delay_ms ( 10 );
        if ( cCnt > 200 )        // turn safelight off if here more than 2 seconds
            {
            output_low ( RELAY_SAFELAMP );
            }
        }
    output_low ( RELAY_ENLARGER );
    output_high ( RELAY_SAFELAMP );
    cSafeLamp = ON;
    }

void DisplayMenuExpose ( void )
    {
    char cEnlargeLampState, cCnt;

    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "Exposure     sec" );
    LCD_SetPosition ( SECOND_LINE );
    printf ( LCD_PutChar, "RET DEC BEG INC " );
    DebounceSwitchD();                          // prevent repetitive cycling
    while ( TRUE )
        {
        if ( input ( SWITCH_A ) == LOW )
            {
            DebounceSwitchA();
            break;                      // drop out
            }
        if ( input ( SWITCH_B ) == LOW )
            {
            if ( cEnlargeTime != 1 )
                {
                cEnlargeTime--;         // decrement
                delay_ms ( AUTOREPEAT_DELAY );       // autorepeat delay
                }
            }
        if ( input ( SWITCH_D ) == LOW )
            {
            if ( cEnlargeTime != 99 )
                {
                cEnlargeTime++;         // increment
                delay_ms ( AUTOREPEAT_DELAY );       // autorepeat delay
                }
            }
        if ( input ( SWITCH_C ) == LOW )            // start exposure
            {
            DebounceSwitchD();                  // prevent repetitive cycling
            write_eeprom ( ENLARGE_TIME, cEnlargeTime );        // save time to EEPROM
            cCnt = cEnlargeTime;                    // get time
            LCD_PutCmd ( CLEAR_DISP );
            LCD_SetPosition ( FIRST_LINE );
            printf ( LCD_PutChar, "Exposing  %02u sec", cCnt );     // display initial time
            LCD_SetPosition ( SECOND_LINE );
            printf ( LCD_PutChar, "            RET " );
            cEnlargeLampState = ON;
            output_high ( RELAY_ENLARGER );         // turn enlarge lamp on
            while ( cEnlargeLampState == ON )
                {
                // delay
                delay_ms ( 984 );                           // delay one second, adjusted
                // decrement time
                cCnt--;                                     // decrement count
                // display new time
                LCD_SetPosition ( FIRST_LINE + 10 );
                printf ( LCD_PutChar, "%02u", cCnt );       // display new value
                // check if time done
                if ( cCnt == 0 )                            // if counted down to zero
                    {
                    cEnlargeLampState = OFF;                // falls out of while()
                    }
                // check if stop switch pressed
                if ( input ( SWITCH_D ) == LOW )    // if button pressed
                    {
                    cEnlargeLampState = OFF;
                    }
                }
            output_low ( RELAY_ENLARGER );      // turn enlarger lamp off
            for ( cCnt = 0; cCnt < 255; cCnt++ )
                {
                output_high ( ALERT );      // beep
                delay_us ( 120 );
                output_low ( ALERT );
                delay_us ( 120 );
                }
            break;
            }
        // display adjusted time
        LCD_SetPosition ( FIRST_LINE + 10 );
        printf ( LCD_PutChar, "%02u", cEnlargeTime );
        }
    }

void DisplayMenuTimer ( void )
    {
    char cM, cS, cTimerState, cAlarmState, cCnt;

    //cM = cAlarmMinutes;                 // get presets
    //cS = cAlarmSeconds;
    LCD_PutCmd ( CLEAR_DISP );
    LCD_SetPosition ( FIRST_LINE );
    printf ( LCD_PutChar, "Timer       :   " );
    LCD_SetPosition ( SECOND_LINE );
    printf ( LCD_PutChar, "RET DEC INC BEG " );
    DebounceSwitchD();                          // prevent repetitive cycling
    while ( TRUE )
        {
        if ( input ( SWITCH_A ) == LOW )
            {
            DebounceSwitchA();
            break;                          // drop out
            }
        if ( input ( SWITCH_B ) == LOW )
            {
            if ( cAlarmSeconds != 0 )       // seconds isn't zero
                {
                cAlarmSeconds -= 10;        // decrement seconds by 10
                }
            else                            // seconds is zero
                {
                if ( cAlarmMinutes != 0 )
                    {
                    cAlarmSeconds = 50;     // wrap seconds to 50
                    cAlarmMinutes--;        // decrement minutes
                    }
                else
                    {
                    // do nothing, don't decrement any lower
                    }
                }
            delay_ms ( AUTOREPEAT_DELAY );       // autorepeat delay
            }
        if ( input ( SWITCH_C ) == LOW )
            {
            if ( cAlarmSeconds != 50 )      // seconds isn't 50
                {
                cAlarmSeconds += 10;        // increment seconds by 10
                }
            else                            // seconds is 50
                {
                if ( cAlarmMinutes != 59 )
                    {
                    cAlarmSeconds = 0;      // wrap seconds to 00
                    cAlarmMinutes++;        // increment minutes
                    }
                else
                    {
                    // do nothing, don't increment any higher
                    }
                }
                delay_ms ( AUTOREPEAT_DELAY );       // autorepeat delay
            }
        if ( input ( SWITCH_D ) == LOW )            // start timer
            {
            DebounceSwitchD();
            write_eeprom ( TIMER_MINUTES, cAlarmMinutes );        // save time to EEPROM
            write_eeprom ( TIMER_SECONDS, cAlarmSeconds );        // save time to EEPROM
            cM = 0;                 // start at zero
            cS = 0;
            LCD_PutCmd ( CLEAR_DISP );
            LCD_SetPosition ( FIRST_LINE );
            printf ( LCD_PutChar, "Timing    %02u:%02u   ", cM, cS );  // display preset time
            LCD_SetPosition ( SECOND_LINE );
            printf ( LCD_PutChar, "            RET " );
            cTimerState = ON;
            cAlarmState = OFF;      // default to alarm off
            while ( cTimerState == ON )
                {
                if ( ( cM == cAlarmMinutes ) && ( cS == cAlarmSeconds ) &&
                     ( ( cAlarmMinutes != 0 ) || ( cAlarmSeconds != 0 ) ) )
                    {
                    cAlarmState = ON;           // turn alarm on
                    LCD_SetPosition ( SECOND_LINE + 8 );
                    printf ( LCD_PutChar, "SIL" );
                    }
                if ( cAlarmState == OFF )
                    {
                    // delay
                    delay_ms ( 943 );               // delay one second, adjusted
                    }
                else
                    {
                    // beep
                    for ( cCnt = 0; cCnt < 255; cCnt++ )
                        {
                        output_high ( ALERT );      // beep
                        delay_us ( 120 );
                        output_low ( ALERT );
                        delay_us ( 120 );
                        }
                    // delay
                    delay_ms ( 878 );               // delay remainder of one second, adjusted
                    if ( input ( SWITCH_C ) == LOW )
                        {
                        cAlarmState = OFF;          // silence
                        LCD_SetPosition ( SECOND_LINE + 8 );
                        printf ( LCD_PutChar, "   " );
                        }
                    }
                // increment time
                if ( cS != 59 )                 // if seconds isn't 59
                    {
                    cS++;                       // increment seconds
                    }
                else                            // if seconds is zero
                    {
                    if ( cM != 59 )             // if minutes isn't 59
                        {
                        cS = 00;                // wrap seconds to 00
                        cM++;                   // increment minutes
                        }
                    else
                        {
                        cTimerState = OFF;
                        }
                    }
                // display new time
                LCD_SetPosition ( FIRST_LINE + 10 );
                printf ( LCD_PutChar, "%02u", cM );
                LCD_SetPosition ( FIRST_LINE + 13 );
                printf ( LCD_PutChar, "%02u", cS );
                // check if stop switch pressed
                if ( input ( SWITCH_D ) == LOW )    // if button pressed
                    {
                    cTimerState = OFF;
                    }
                }
            break;
            }
        // display adjusted time
        LCD_SetPosition ( FIRST_LINE + 10 );
        printf ( LCD_PutChar, "%02u", cAlarmMinutes );
        LCD_SetPosition ( FIRST_LINE + 13 );
        printf ( LCD_PutChar, "%02u", cAlarmSeconds );
        }
    }

void DebounceSwitchA ( void )
    {
    char cX;

    for  ( cX = 0; cX < DEBOUNCE_DELAY; cX++ )
        {
        if ( input ( SWITCH_A ) == LOW )
            {
            cX = 0;
            }
        delay_ms ( 1 );
        }
    }

void DebounceSwitchB ( void )
    {
    char cX;

    for  ( cX = 0; cX < DEBOUNCE_DELAY; cX++ )
        {
        if ( input ( SWITCH_B ) == LOW )
            {
            cX = 0;
            }
        delay_ms ( 1 );
        }
    }

void DebounceSwitchC ( void )
    {
    char cX;

    for  ( cX = 0; cX < DEBOUNCE_DELAY; cX++ )
        {
        if ( input ( SWITCH_C ) == LOW )
            {
            cX = 0;
            }
        delay_ms ( 1 );
        }
    }

void DebounceSwitchD ( void )
    {
    char cX;

    for  ( cX = 0; cX < DEBOUNCE_DELAY; cX++ )
        {
        if ( input ( SWITCH_D ) == LOW )
            {
            cX = 0;
            }
        delay_ms ( 1 );
        }
    }

/******************************************************************************/

void LCD_Init ( void )
    {
    LCD_SetData ( 0x00 );
    delay_ms ( 200 );       /* wait enough time after Vdd rise */
    output_low ( LCD_RS );
    LCD_SetData ( 0x03 );   /* init with specific nibbles to start 4-bit mode */
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_PulseEnable();
    LCD_SetData ( 0x02 );   /* set 4-bit interface */
    LCD_PulseEnable();      /* send dual nibbles hereafter, MSN first */
    LCD_PutCmd ( 0x2C );    /* function set (all lines, 5x7 characters) */
    LCD_PutCmd ( 0x0C );    /* display ON, cursor off, no blink */
    LCD_PutCmd ( 0x01 );    /* clear display */
    LCD_PutCmd ( 0x06 );    /* entry mode set, increment */
    }

void LCD_SetPosition ( unsigned int cX )
    {
    /* this subroutine works specifically for 4-bit Port A */
    LCD_SetData ( swap ( cX ) | 0x08 );
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );
    LCD_PulseEnable();
    }

void LCD_PutChar ( unsigned int cX )
    {
    /* this subroutine works specifically for 4-bit Port A */
    output_high ( LCD_RS );
    LCD_SetData ( swap ( cX ) );     /* send high nibble */
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );     /* send low nibble */
    LCD_PulseEnable();
    output_low ( LCD_RS );
    }

void LCD_PutCmd ( unsigned int cX )
    {
    /* this subroutine works specifically for 4-bit Port A */
    LCD_SetData ( swap ( cX ) );     /* send high nibble */
    LCD_PulseEnable();
    LCD_SetData ( swap ( cX ) );     /* send low nibble */
    LCD_PulseEnable();
    }

void LCD_PulseEnable ( void )
    {
    output_high ( LCD_EN );
    delay_us ( 10 );
    output_low ( LCD_EN );
    delay_ms ( 5 );
    }

void LCD_SetData ( unsigned int cX )
    {
    output_bit ( LCD_D0, cX & 0x01 );
    output_bit ( LCD_D1, cX & 0x02 );
    output_bit ( LCD_D2, cX & 0x04 );
    output_bit ( LCD_D3, cX & 0x08 );
    }