//
//  Name:          ATVRxController.c
//
//
//  Revision History:
//
//   M. Gray        4 Sep 2003  V1.0  Initial release.
//
//   M. Gray       27 Mar 2004  V1.1  Updates based on ham fest display and ANSR-17 flight.
//
//
//  COPYRIGHT (c) 2003-04 Michael Gray, KD7LMO
// 
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//

// Hardware specific configuration.
#include <18f252.h>
#device ADC=10
#fuses HS,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT,NOLVP

// These compiler directives set the clock, I/O ports, RS-232, and I2C interfaces.
#use delay(clock=3686400)

#use fast_io(B)
#use fast_io(C)

#use rs232(baud=38400, xmit=PIN_C6, rcv=PIN_C7)
#use i2c (master, scl=PIN_C0, sda=PIN_C1)

// We define types that are used for all variables.  These are declared
// because each processor has a different sizes for int and long.
typedef int int8_t;
typedef unsigned int uint8_t;
typedef signed long int16_t;
typedef unsigned long uint16_t;
typedef int32 int32_t;
typedef unsigned int32 uint32_t;

// PROGRAMMING NOTE: Since we don't have embedded C++ for this processor,
// we try to use C in a manner that emulates classes and methods.  Each method
// and variable is prefaced by the name of the class.  The classes
// have a method that acts as a constructor and sometimes a destructor.
// Even though variables are declared as global, they are only accessed
// through the appropriate class method.  The order of the classes and
// variables is not important in the application because we only access
// the class through a method.

// Prototypes for all methods.
uint8_t adcGetAGC();
uint8_t adcGetBusVolt();
void adcInit();
void adcUpdate();

void cat524Write (uint8_t port, uint8_t data);

uint16_t fileGetFreq();
void fileSetFreq (uint16_t freq);
void fileUpdateEEPROM (uint8_t index, uint8_t value);

void guiInit();
void guiSetBar(uint8_t value);
void guiSetBusVoltage (uint8_t voltage);
void guiSetFreq (uint16_t freq);

void ioInit();
void ioInterrupt();
boolean ioIsSW1Pushed();
boolean ioIsSW2Pushed();
boolean ioIsSW3Pushed();

void lcdChar (uint8_t value);
void lcdCommand (uint8_t command);
void lcdCursor (uint8_t x, uint8_t y);
void lcdInit();
void lcdString(char *string);
void lcdText (uint8_t x, uint8_t y, char *text);
void lcdWrite (uint8_t data);

void sysGetLCDModeText (uint8_t mode, char *text);
void sysGetVersion (char *version);
void sysInit();
void sysSetFreq (uint16_t freq);

void timerInit();
void timerInterrupt();
boolean timerIsUpdated();

boolean tunerIsLocked();
void tunerSetFreq(uint16_t freq);

/*
 *   Class to handle the ADC.
 */
 
 // Accumulators that store a filter version of the bus and AGC voltage.
uint16_t adcBusVoltage, adcAGC;

// ADC channels for the desired measurements.
#define ADC_MAINBUS 0
#define ADC_AGC 2

/**
 *   Get the AGC value in the range of 0 to 15 where 0 is -80dBm
 *
 *   @return agc
 */
uint8_t adcGetAGC()
{
    uint8_t agc;

    // Scale 1 - 4 volts on the AGC to equal 0 - 15.
    agc = (uint8_t) (adcAGC / 82);

    if (agc < 5)
        return 0;

    if (agc > 15)
        return 15;

    return agc - 4;
}

/**
 *   Get the main bus voltage in 0.1 volt resolution.
 *
 *   @return voltage in 0.1 volt steps
 */
uint8_t adcGetBusVolt()
{
    // Transfer function.  0.4 is the reverse polarity diode drop
    //    x  * 494 * 3 *  1
    //  ----             --
    //  1024             80
    return (uint8_t) ((uint32_t) (adcBusVoltage) * 741l / 40960l) + 4;
}

/**
 *   Constructor.
 */
void adcInit()
{
    uint8_t i;

    // Setup the ADC.
    setup_adc_ports( ALL_ANALOG );
    setup_adc( ADC_CLOCK_DIV_32 );

    // Zero the ADC filters.
    adcBusVoltage = 0;
    adcAGC = 0;
    
    // Charge the ADC filters.
    for (i = 0; i < 32; ++i)
        adcUpdate();    
}

/**
 *   Read and filter the ADC channels for bus voltage and internal temperature.
 */
void adcUpdate()
{
    // Filter the analog values using a single pole low pass filter.
    set_adc_channel(ADC_AGC);
    delay_us(20);
    adcAGC = read_adc() + adcAGC - (adcAGC >> 1);

    set_adc_channel(ADC_MAINBUS);
    delay_us(20);
    adcBusVoltage = read_adc() + adcBusVoltage - (adcBusVoltage >> 3);
}


/*
 *   Class to handle the EEPROM file system.
 */
 enum FILE_SYSTEM_MAP
 {
    FILE_MSB_MAGIC = 0x00,
    FILE_LSB_MAGIC,
    FILE_FREQ_MSB,
    FILE_FREQ_LSB,
    FILE_BACK_LIGHT,
    FILE_COLOR,
    FILE_BRIGHT,
    FILE_CONT,
    FILE_TINT,
    FILE_LCD_MODE
 };
    

#define FILE_MSB_MAGIC_DEFAULT 0x43
#define FILE_LSB_MAGIC_DEFAULT 0xcf

/**
 *   Get the back light setting from the file system.
 *
 *   @return back light PWM
 */
uint8_t fileGetBackLightPWM()
{
    return read_eeprom (FILE_BACK_LIGHT);
}


/**
 *   Get the color setting from the file system.
 *
 *   @return color setting
 */
uint8_t fileGetColor()
{
    return read_eeprom (FILE_COLOR);
}

/**
 *   Get the color setting from the file system.
 *
 *   @return color setting
 */
uint8_t fileGetBright()
{
    return read_eeprom (FILE_BRIGHT);
}

/**
 *   Get the color setting from the file system.
 *
 *   @return color setting
 */
uint8_t fileGetContrast()
{
    return read_eeprom (FILE_CONT);
}

/**
 *   Get the color setting from the file system.
 *
 *   @return color setting
 */
uint8_t fileGetTint()
{
    return read_eeprom (FILE_TINT);
}

/**
 *   Get the LCD screen mode.
 *
 *   @return LCD screen mode
 */
uint8_t fileGetLCDMode()
{
    return read_eeprom (FILE_LCD_MODE);
}

/**
 *   Get the frequency in MHz from the EEPROM file system.
 *
 *   @return freq in MHz
 */
uint16_t fileGetFreq()
{
    return ((uint16_t) read_eeprom (FILE_FREQ_MSB) << 8) | (uint16_t) read_eeprom (FILE_FREQ_LSB);
}

/**
 *   Initialize the file system.
 *
 *
 *   @return true if file system valid; otherwise false
 */
boolean fileInit()
{
    if (read_eeprom(FILE_MSB_MAGIC) == FILE_MSB_MAGIC_DEFAULT && read_eeprom(FILE_LSB_MAGIC) == FILE_LSB_MAGIC_DEFAULT)
        return true;
        
    fileUpdateEEPROM (FILE_MSB_MAGIC, FILE_MSB_MAGIC_DEFAULT);
    fileUpdateEEPROM (FILE_LSB_MAGIC, FILE_LSB_MAGIC_DEFAULT);  
    
    return false;
} 

/**
 *   Store the tuner frequency in MHz in the EEPROM file system.
 *
 *   @param freq in MHz
 */
void fileSetFreq (uint16_t freq)
{
    fileUpdateEEPROM (FILE_FREQ_MSB, freq >> 8);
    fileUpdateEEPROM (FILE_FREQ_LSB, freq & 0xff);
}

/**
 *  Store the back light PWM <b>value</b> in the file system.
 */
void fileSetBackLightPWM (uint8_t value)
{
    fileUpdateEEPROM (FILE_BACK_LIGHT, value);
}

/**
 *  Store the color <b>value</b> in the file system.
 */
void fileSetColor (uint8_t value)
{
    fileUpdateEEPROM (FILE_COLOR, value);
}

/**
 *  Store the color <b>value</b> in the file system.
 */
void fileSetBright (uint8_t value)
{
    fileUpdateEEPROM (FILE_BRIGHT, value);
}

/**
 *  Store the color <b>value</b> in the file system.
 */
void fileSetContrast (uint8_t value)
{
    fileUpdateEEPROM (FILE_CONT, value);
}

/**
 *  Store the color <b>value</b> in the file system.
 */
void fileSetTint (uint8_t value)
{
    fileUpdateEEPROM (FILE_TINT, value);
}

/**
 *  Store the color <b>value</b> in the file system.
 */
void fileSetLCDMode (uint8_t value)
{
    fileUpdateEEPROM (FILE_LCD_MODE, value);
}

void fileUpdateEEPROM (uint8_t index, uint8_t value)
{
    if (read_eeprom (index) == value)
        return;
        
    write_eeprom (index, value);
}

/*
 *   Class to handle the user interface.
 */

/**
 *   Display the applicatino title and version.
 */
void guiVersion()
{
    char buffer[20];

    // Display the application name and version information on the LCD.
    sysGetVersion (buffer);
    
    lcdText (2, 1, buffer);
}

/**
 *  Display the labels.
 */
void guiLabel()
{
    char buffer[20];

    strcpy (buffer, "Freq xxxxMHz");
    lcdText (1, 1, buffer);

    strcpy (buffer, "Sig");
    lcdText (1, 2, buffer);
}

/**
 *   Set the AGC bar graph.
 *
 *   @param agc level in the range 0 to 15.
 */
void guiSetAGC (uint8_t agc)
{
    uint8_t i;
    char buffer[17];

    // Draw solid blocks for each MSB in the age value.
    for (i = 0; i < agc && i < 16; ++i)
        buffer[i] = '\377';

    // Fill in the rest with blanks.
    while (i < 16)
        buffer[i++] =' ';

    // NULL terminate the string.
    buffer[16] = 0;

    // Now display it.
    lcdText (5, 2, buffer);    
}


/**
 *   Set an indicator bar on the bottom line of the 2 x 20 LCD display
 *   that represents <b>value</b>.
 *
 *   @param value to display in the range 0 to 255
 */
void guiSetBar(uint8_t value)
{
    uint8_t i;
    
    lcdCursor (1, 2);
    
    for (i = 0; i < 20; ++i)
        lcdChar (' ');
        
    lcdCursor (1 + (uint8_t) ((uint16_t) value * 10l / 128l), 2); 
    lcdChar ('\377');
}


/**
 *   Display the bus voltage.
 *
 *   @param voltage in units of 0.1 volts
 */
void guiSetBusVoltage (uint8_t voltage)
{
    char buffer[10];

    sprintf (buffer, "%02d.%d", voltage / 10, voltage % 10);
    lcdText (1, 2, buffer);    
}

/**
 *   Display the frequency.
 *
 *   @param frequency in MHz
 */
void guiSetFreq (uint16_t freq)
{
    char buffer[10];

    sprintf (buffer, "%ld", freq);
    lcdText (6, 1, buffer);
}

/*
 *   Class to handle local I/O.
 */
boolean ioLastState1, ioLastState2, ioLastState3, ioTrigger1, ioTrigger2, ioTrigger3;
uint8_t ioPort0, ioPort1;

// Port 0 bit map.
#define IO_SCAN_DOWN 0x01
#define IO_SCAN_LEFT 0x02

#define IO_MODE_1 0x04
#define IO_MODE_2 0x08
#define IO_MODE_3 0x10
#define IO_MODE_MASK 0x1c
#define IO_MODE_SHIFT 2

#define IO_COMP_VIDEO 0x20

// Port 1 bit map.
#define IO_CAT524_PROG 0x01
#define IO_CAT524_DI 0x02
#define IO_CAT524_CS 0x04
#define IO_CAT524_CLK 0x08
#define IO_LED 0x10


void ioUpdatePorts()
{
    i2c_start();
    i2c_write(0x40);
    i2c_write(0x00);
    i2c_write(ioPort0);
    i2c_write(ioPort1);
    i2c_stop();

    i2c_start();
    i2c_write(0x40);
    i2c_write(0x02);
    i2c_write(ioPort0);
    i2c_write(ioPort1);
    i2c_stop();
}

void ioReadPort()
{
    uint8_t value;

    // Tell the MCP23016 we want to access the GP0 and GP1 registers.
    i2c_start();
    i2c_write(0x40);
    i2c_write(0x00);
    i2c_stop();

    // Read the contents of the register.
    i2c_start();
    i2c_write(0x41);
    value = i2c_read();
    value = i2c_read();
    i2c_stop();


    // Check for changes in the input state and set a flag for high to low transitions.
    // Since this routine is only called every 50mS, we will automatically debounce the switch.
    if (ioLastState1 != ((value & 0x20) == 0x020 ? TRUE : FALSE)) {
        if (ioLastState1)
            ioTrigger1 = TRUE;

        ioLastState1 = ~ioLastState1;
    } // END if

    if (ioLastState2 != ((value & 0x40) == 0x040 ? TRUE : FALSE)) {
        if (ioLastState2)
            ioTrigger2 = TRUE;

        ioLastState2 = ~ioLastState2;
    } // END if

    if (ioLastState3 != ((value & 0x80) == 0x080 ? TRUE : FALSE)) {
        if (ioLastState3)
            ioTrigger3 = TRUE;

        ioLastState3 = ~ioLastState3;
    } // END if
}

/**
 *   Constructor.
 */
void ioInit()
{
    // Initial output port settings.
    ioPort0 = 0x00;
    ioPort1 = 0x00;

    ioLastState1 = FALSE;
    ioLastState2 = FALSE;
    ioLastState3 = FALSE;

    ioTrigger1 = FALSE;
    ioTrigger2 = FALSE;
    ioTrigger3 = FALSE;

    // Specify the port I/O direction.
    i2c_start();
    i2c_write(0x40);
    i2c_write(0x06);
    i2c_write(0x00);
    i2c_write(0xe0);
    i2c_stop();

    ioUpdatePorts();
}

void ioSetCAT524Prog (boolean state)
{
    if (state)
        ioPort1 |= IO_CAT524_PROG;
    else
        ioPort1 &= ~IO_CAT524_PROG;

    ioUpdatePorts();
}

void ioSetCAT524Data (boolean state)
{
    if (state)
        ioPort1 |= IO_CAT524_DI;
    else
        ioPort1 &= ~IO_CAT524_DI;

    ioUpdatePorts();
}

void ioSetCAT524CS (boolean state)
{
    if (state)
        ioPort1 |= IO_CAT524_CS;
    else
        ioPort1 &= ~IO_CAT524_CS;

    ioUpdatePorts();
}

void ioSetCAT524Clk (boolean state)
{
    if (state)
        ioPort1 |= IO_CAT524_CLK;
    else
        ioPort1 &= ~IO_CAT524_CLK;

    ioUpdatePorts();
}


void ioScanDown (boolean state)
{
    if (state)
        ioPort0 |= IO_SCAN_DOWN;
    else
        ioPort0 &= ~IO_SCAN_DOWN;

    ioUpdatePorts();
}

void ioScanLeft (boolean state)
{
    if (state)
        ioPort0 |= IO_SCAN_LEFT;
    else
        ioPort0 &= ~IO_SCAN_LEFT;

    ioUpdatePorts();
}

void ioCompVideo (boolean state)
{
    if (state)
        ioPort0 |= IO_COMP_VIDEO;
    else
        ioPort0 &= ~IO_COMP_VIDEO;

    ioUpdatePorts();
}

void ioSetMode (uint8_t mode)
{
    ioPort0 = (ioPort0 & ~IO_MODE_MASK) | (mode << IO_MODE_SHIFT);

    ioUpdatePorts();
}

void ioSetLED (boolean state)
{
    if (state)
        ioPort1 &= ~IO_LED;
    else
        ioPort1 |= IO_LED;  

    ioUpdatePorts();
}

/**
 *   Determine if switch 1 was pushed.  This is a one shot method that
 *   debounces the switch contacts.
 *
 *   @return true if pushed; otherwise false
 */
boolean ioIsSW1Pushed()
{
    if (ioTrigger1) {
        ioTrigger1 = FALSE;
        return TRUE;
    }

    return FALSE;
}

/**
 *   Determine if switch 2 was pushed.  This is a one shot method that
 *   debounces the switch contacts.
 *
 *   @return true if pushed; otherwise false
 */
boolean ioIsSW2Pushed()
{
    if (ioTrigger2) {
        ioTrigger2 = FALSE;
        return TRUE;
    }

    return FALSE;
}

/**
 *   Determine if switch 3 was pushed.  This is a one shot method that
 *   debounces the switch contacts.
 *
 *   @return true if pushed; otherwise false
 */
boolean ioIsSW3Pushed()
{
    if (ioTrigger3) {
        ioTrigger3 = FALSE;
        return TRUE;
    }

    return FALSE;
}


#define CAT524_COLOR 0x00
#define CAT524_BRIGHT 0x02
#define CAT524_CONT 0x01
#define CAT524_TINT 0x03

/**
 *   Set the analog <b>port</b> to the value <b>data</b> in the range 0 to 255.
 *
 *   @param port index CAT524_xxx
 *   @param data analog value
 */
void cat524Write (uint8_t port, uint8_t data)
{
    uint8_t i;

    ioSetCAT524Data (FALSE);
    ioSetCAT524CS (TRUE);

    // Write the start bit.
    ioSetCAT524Data (TRUE);
    ioSetCAT524Clk (TRUE);
    ioSetCAT524Clk (FALSE);

    // Set the programming bit.
    ioSetCAT524Prog (TRUE);

    // Write A0 of the address.
    ioSetCAT524Data ((port & 0x01) ? TRUE : FALSE);
    ioSetCAT524Clk (TRUE);
    ioSetCAT524Clk (FALSE);

    // Write A1 of the address.
    ioSetCAT524Data ((port & 0x02) ? TRUE : FALSE);
    ioSetCAT524Clk (TRUE);
    ioSetCAT524Clk (FALSE);

    // Write the 8-data bits LSB first.
    for (i = 0; i < 8; ++i) {
        ioSetCAT524Data ((data & 0x01) ? TRUE : FALSE);
        data = data >> 1;

        ioSetCAT524Clk (TRUE);
        ioSetCAT524Clk (FALSE);
    }

    ioSetCAT524Data (FALSE);

    // Save the data to the CAT524 EEPROM.
    for (i = 0 ; i < 4; ++i) {
        delay_ms(1);
        ioSetCAT524Clk (TRUE);
        ioSetCAT524Clk (FALSE);
    }

    // Clean up.
    ioSetCAT524Prog (FALSE);
    ioSetCAT524CS (FALSE);
}

/*
 *   Class to handle the HD44780 LCD controller.
 */
const uint8_t LCD_OFFSET[] = { 0x00, 0x40, 0x14, 0x54 };

#define IO_LCD_BIT0 PIN_B4
#define IO_LCD_BIT1 PIN_B5
#define IO_LCD_BIT2 PIN_B6
#define IO_LCD_BIT3 PIN_B7

#define IO_LCD_RW PIN_B3
#define IO_LCD_E PIN_B2
#define IO_LCD_RS PIN_B1

/**
 *   Display the <b>character</b> on the LCD.
 *
 *   @param value character to display
 */
void lcdChar (uint8_t value)
{
    output_high (IO_LCD_RS);
    lcdWrite (value);
}

/**
 *   Clear the LCD display.
 */
void lcdClear()
{
    lcdCommand (0x01);
}

/**
 *   Send a command to the LCD system.
 */ 
void lcdCommand (uint8_t command)
{
    // Some commands take 1.64mS to complete, so we'll just wait on all of them.
    delay_ms (2);

    output_low (IO_LCD_RS);
    lcdWrite (command);
}

/**
 *   Move the LCD cursor to the coordinates (x, y) where (1, 1) is
 *   in the upper left-hand corner.
 *
 *   @param x coordinate
 *   @param y coordinate
 */
void lcdCursor(uint8_t x, uint8_t y)
{
    // Only allow the range (1, 1) to (20, 2)
    if (x == 0 || x > 20 || y == 0 || y > 2)
        return;

    output_low (IO_LCD_RS);
    lcdCommand (0x7f + x + LCD_OFFSET[y - 1]);
}

/**
 *   Initialize the LCD controller and display initial text.
 */
void lcdInit()
{
    // Reset the interface.
    lcdCommand (0x33);
    lcdCommand (0x33);
    lcdCommand (0x32);

    // Set 4-bit interface mode and display type.
    lcdCommand (0x28);

    // Enable the display with the cursor off.
    lcdCommand (0x0c);

    // Clear the display and home the cursor.
    lcdCommand (0x01);  

    // Move the cursor forward with each character.
    lcdCommand (0x06);
}

/**
 *   Display <b>string</b> on the LCD display.
 */
void lcdString(char *string)
{
    output_high (IO_LCD_RS);

    while (*string != 0)
        lcdWrite (*string++);
}

void lcdText (uint8_t x, uint8_t y, char *text)
{
    lcdCursor (x, y);
    lcdString (text);
}

/**
 *   Write a byte to the 4-bit interface.
 *
 *   @param data value to write to LCD controller
 */
void lcdWrite (uint8_t data)
{
    output_bit (IO_LCD_BIT3, ((data & 0x80) ? 1 : 0));
    output_bit (IO_LCD_BIT2, ((data & 0x40) ? 1 : 0));
    output_bit (IO_LCD_BIT1, ((data & 0x20) ? 1 : 0));
    output_bit (IO_LCD_BIT0, ((data & 0x10) ? 1 : 0));

    output_high (IO_LCD_E);
    delay_us(1);
    output_low (IO_LCD_E);

    output_bit (IO_LCD_BIT3, ((data & 0x08) ? 1 : 0));
    output_bit (IO_LCD_BIT2, ((data & 0x04) ? 1 : 0));
    output_bit (IO_LCD_BIT1, ((data & 0x02) ? 1 : 0));
    output_bit (IO_LCD_BIT0, ((data & 0x01) ? 1 : 0));

    output_high (IO_LCD_E);
    delay_us(1);
    output_low (IO_LCD_E);
}

/*
 *   Class to handle the system operations.
 */
const uint16_t SYS_VERSION = 0x0101;

#define FREQ_LOWER_RANGE 2300
#define FREQ_UPPER_RANGE 2500

// The time in 50mS increments before we save a change in the tuner frequency.
#define SYS_SAVE_FREQ_TIME 300

void sysInit()
{
    // Set default outputs before we configure the I/O state.
    output_b (0x00);    
    output_c (0x03);    

    // Configure the I/O ports direction.
    set_tris_b (0x00);
    set_tris_c (0x80);
}

/**
 *   Get the software title and version string.
 *
 *   @param version buffer to write version into
 */
void sysGetVersion (char *version)
{
    sprintf (version, "KD7LMO FM-ATV v%d.%d", (uint8_t) (SYS_VERSION >> 8), (uint8_t) (SYS_VERSION & 0xff));
}

enum SYS_OPER_MODE 
{
    MODE_FREQ,
    MODE_BACK_LIGHT,
    MODE_BRIGHT,
    MODE_CONTRAST,
    MODE_COLOR,
    MODE_TINT,
    MODE_LCD_MODE,
    MODE_SYS_INFO
};

void sysGetLCDModeText (uint8_t mode, char *text)
{
    switch (mode) {
        case 1:
            strcpy (text, "Normal - Right");
            break;

        case 2:
            strcpy (text, "Normal - Left ");
            break;

        case 3:
            strcpy (text, "Zoom 2        ");
            break;

        case 4:
            strcpy (text, "Zoom 1        ");
            break;

        case 5:
            strcpy (text, "Stretch 4:3   ");
            break;

        case 6:
            strcpy (text, "Normal 4:3    ");
            break;

        case 7:
            strcpy (text, "Wide 16:9     ");
            break;

        default:
            strcpy (text, "Unknown Mode  ");

    } // END switch
}

void sysRun()
{
    boolean runFlag, ledState, valueChangeFlag;
    uint8_t value, lastBusVoltage, lastAGC, versionState, newSetting, ledStateCounter;
    uint16_t freq, lastFreqChange;
    char buffer[20];
    SYS_OPER_MODE operMode;

    // Get the last frequency we used.
    freq = fileGetFreq();

    // Set the tuner and LCD.
    tunerSetFreq(freq);

    // Configure the state variables.
    runFlag = TRUE;
    lastBusVoltage = 0xff;
    lastAGC = 0;
    lastFreqChange = 0;
    ledStateCounter = 0;
    versionState = 0;
    ledState = FALSE;
    operMode = MODE_FREQ;
    valueChangeFlag = false;

    // Display the application version.
    guiVersion();

    // Display bars on the LCD screen during startup.
    while (versionState <= 40) {
        if (timerIsUpdated()) {
            ++versionState;

            if (versionState <= 20) {
                lcdCursor (versionState, 2);
                lcdChar (0x7e);
            } else {
                lcdCursor (versionState - 20, 2);
                lcdChar (' ');
            } // END if-else

            if (versionState == 40) {
                lcdClear();
                guiLabel();
                guiSetFreq (freq);
            } // END if
        } // END if
    } // END while

    //      
    while (runFlag) {
        // Processing that occurs every 50mS.
        if (timerIsUpdated()) {
            // Read the push buttons.
            ioReadPort();
        
            // Save the last frequency to EEPROM if it has changed and the user hasn't moved it recently.
            if (lastFreqChange <= SYS_SAVE_FREQ_TIME)
                if (++lastFreqChange == SYS_SAVE_FREQ_TIME)
                    if (freq != fileGetFreq())
                        fileSetFreq (freq);

            // Read the A/D value.
            adcUpdate();

            // Only update the displayed AGC bar graph if it changed.
            switch (operMode) {
                case MODE_FREQ:
                    // Only update the screen if the value changed since the last update.
                    value = adcGetAGC();

                    if (value != lastAGC) {
                        lastAGC = value;
                        guiSetAGC (value);
                    } // END if
                    break;

                case MODE_SYS_INFO:
                    value = adcGetBusVolt();

                    if (value != lastBusVoltage) {
                        lastBusVoltage = value;
                        guiSetBusVoltage (value);
                    } // END if                 
                    break;

            } // END switch

            // Heartbeat LED.
            if (++ledStateCounter == 10) {
                ledStateCounter = 0;
                ledState = ~ledState;
                ioSetLED (ledState);
            } // END if
        } // END if
    
        // Switch modes based on the select button.
        if (ioIsSW1Pushed()) {
            // Clear the screen.
            lcdClear();
        
            switch (operMode) {
                case MODE_FREQ:
                    operMode = MODE_BACK_LIGHT;
                    strcpy (buffer, "Back Light Level");
                    newSetting = fileGetBackLightPWM();
                    break;
                
                case MODE_BACK_LIGHT:
                    fileSetBackLightPWM (newSetting);
                    operMode = MODE_BRIGHT;
                    strcpy (buffer, "Brightness");
                    newSetting = fileGetBright();
                    break;
                
                case MODE_BRIGHT:
                    fileSetBright (newSetting);
                    operMode = MODE_CONTRAST;
                    strcpy (buffer, "Contrast");
                    newSetting = fileGetContrast();
                    break;
                
                case MODE_CONTRAST:
                    fileSetContrast (newSetting);
                    operMode = MODE_COLOR;
                    strcpy (buffer, "Chroma (Color)");
                    newSetting = fileGetColor();
                    break;
                
                case MODE_COLOR:
                    fileSetColor (newSetting);
                    operMode = MODE_TINT;
                    strcpy (buffer, "Phase (Tint)");
                    newSetting = fileGetTint();
                    break;
                
                case MODE_TINT:
                    fileSetTint (newSetting);
                    operMode = MODE_LCD_MODE;
                    strcpy (buffer, "Display Mode");
                    newSetting = fileGetLCDMode();
                    break;
                
                case MODE_LCD_MODE:
                    fileSetLCDMode (newSetting);
                    operMode = MODE_SYS_INFO;
                    sysGetVersion (buffer);
                    break;
                    
                case MODE_SYS_INFO:
                    operMode = MODE_FREQ;
                    strcpy (buffer, "Freq     MHz");                
                    break;
            } // END switch
            
            // Display the mode lable on the screen.
            lcdText (1, 1, buffer);
            
            // Display the current setting.
            switch (operMode) {
                case MODE_FREQ:
                    guiSetFreq (freq);
                    
                    lastAGC = 0;
                   
                    strcpy (buffer, "Sig");
                    lcdText (1, 2, buffer);
                    break;
                    
                case MODE_LCD_MODE:
                    sysGetLCDModeText (newSetting, buffer);
                    lcdText (1, 2, buffer);
                    break;
                    
                case MODE_SYS_INFO:
                    strcpy (buffer, "VDC");
                    lcdText (6, 2, buffer);
                    guiSetBusVoltage(adcGetBusVolt());
                    break;
                    
                default:
                    guiSetBar (newSetting);
                    break;                    
            } // END switch
        } // END if
        
        // Process the switch pushes.
        if (ioIsSW2Pushed()) {
            valueChangeFlag = true;
        
            switch (operMode) {
                case MODE_FREQ:
                    if (freq < FREQ_UPPER_RANGE) {
                        lastFreqChange = 0;
                        ++freq;
                    } // END if
                    break;
                    
                case MODE_LCD_MODE:
                    if (++newSetting == 8)
                        newSetting = 1;
                        
                    sysGetLCDModeText (newSetting, buffer);
                    lcdText (1, 2, buffer);
                    break;

                case MODE_SYS_INFO:
                    break;
                    
                default:
                    if (newSetting < 248)
                        newSetting += 8;
                
                    guiSetBar (newSetting);
                    break;                    
            } // END switch
        } // END switch
        

        if (ioIsSW3Pushed()) {
            valueChangeFlag = true;
        
            switch (operMode) {
                case MODE_FREQ:
                    if (freq > FREQ_LOWER_RANGE) {
                        lastFreqChange = 0;
                        --freq;
                    } // END if
                    break;
                    
                case MODE_LCD_MODE:
                    if (--newSetting == 0)
                        newSetting = 7;
                        
                    sysGetLCDModeText (newSetting, buffer);
                    lcdText (1, 2, buffer);
                    break;

                case MODE_SYS_INFO:
                    break;
                    
                default:
                    if (newSetting > 0)
                        newSetting -= 8;
                        
                    guiSetBar (newSetting);
                    break;                    
            } // END switch   
        } // END if     

        if (valueChangeFlag) {
            valueChangeFlag = false;
            
            switch (operMode) {
                case MODE_FREQ:
                    sysSetFreq (freq);
                    break;
                    
                case MODE_BACK_LIGHT:
                    set_pwm1_duty (newSetting);
                    break;
                    
                case MODE_BRIGHT:
                    cat524Write (CAT524_BRIGHT, newSetting);
                    break;
                    
                case MODE_CONTRAST:
                    cat524Write (CAT524_CONT, newSetting);
                    break;
                    
                case MODE_COLOR:
                    cat524Write (CAT524_COLOR, newSetting);
                    break;
                    
                case MODE_TINT:
                    cat524Write (CAT524_TINT, newSetting);
                    break;
                    
                case MODE_LCD_MODE:
                    ioSetMode (newSetting);
                    break;
            } // END switch
        } // END if
    } // END while
}

/**
 *   Set the receiver frequency and display it.
 *
 *   @param freq in MHz
 */
void sysSetFreq (uint16_t freq)
{
    guiSetFreq(freq);
    tunerSetFreq (freq);
}

/*
 *   Class to handle the timer functions.
 */
uint8_t timerInterruptCount;
uint16_t timerCompare;
boolean timerUpdateFlag, timerPWM;

// The number of CCP_2 timer clicks in a 5mS time slice.
// (ClockRate / 4) / (T1_DIV_BY_x) * 576) => (14745600 / 4) / (8 * 2304) = 5mS
#define TIMER_RATE 576

void timerInit()
{
    // Setup the counters that are used in the timerInterrupt function.
    timerInterruptCount = 0;
    timerUpdateFlag = FALSE;
    timerPWM = FALSE;

    // Configure CCP2 to interrupt at 5mS rate.
    timerCompare = TIMER_RATE;
    CCP_2 = timerCompare;
    set_timer1(0);
    setup_ccp2( CCP_COMPARE_INT );
    setup_timer_1( T1_INTERNAL | T1_DIV_BY_8 );
    
    // Setup CCP1 to generate PWM pulses at 275Hz.
    setup_ccp1(CCP_PWM);
    set_timer2(0);    
    setup_timer_2(T2_DIV_BY_16, 209, 1);
    
    set_pwm1_duty(fileGetBackLightPWM());

    // Setup our interrupts.
    enable_interrupts(GLOBAL);
    enable_interrupts(INT_CCP2);
}

// This ISR is called when the timer register matches the compare register.
// The interrupt rate is programmed to 5mS.
#INT_CCP2
void timerInterrupt()
{
    // Setup for the next interrupt.
    timerCompare += TIMER_RATE;
    CCP_2 = timerCompare;

    // Set a processing flag every 50mS.
    if (++timerInterruptCount == 10) {
        timerInterruptCount = 0;
        timerUpdateFlag = TRUE;
    } // END if
}

/** 
 *   Returns a flag that is set true every 50mS.  The method automatically resets to false after
 *   it returns true.
 *
 *   @return true if 50mS has passed; otherwise false
 */
boolean timerIsUpdated()
{
    if (timerUpdateFlag) {
        timerUpdateFlag = FALSE;
        return true;
    } // END if

    return false;
}

/*
 *   Class to handle the 2.4GHz tuner PLL.  The tuner utilizes the Mitel SA5055 2.6GHz
 *   synthesizer.
 */

/**
 *   Determine if the tuner PLL is locked
 *
 *   @return true if locked; otherwise false
 */
boolean tunerIsLocked()
{
    uint8_t returnCode;

    // Get the PLL controller status byte.
    i2c_start();
    i2c_write(0xc3);
    returnCode = i2c_read();
    i2c_stop();

    // Bit 6 is the PLL locked flag.
    if ((returnCode & 0x40) == 0x40)
        return TRUE;
    else
        return FALSE;
}

/** 
 *   Set the receiver frequency.  The IF is 479.5MHz so we set the frequency lower than the
 *   desired receive frequency.  The frequency is based on a 4MHz reference divided by 512.
 *   
 *   @param freq in MHz
 */
void tunerSetFreq(uint16_t freq)
{
    // Tell the I2C device to connect
    i2c_start();

    // Address the SA5055 PLL.
    i2c_write(0xc2);

    // Calculate the receive frequency minus the IF frequency of 479.5 MHz (3836 = 497.5 * 8)
    freq = (8 * freq) - 3836;   
    
    // Write the divider value.
    i2c_write(freq >> 8);
    i2c_write(freq & 0xff);

    i2c_write(0xce);
    i2c_write(0x00);

    i2c_stop();
}

/*
 *   Class to handle the video functions.
 */

/**
 *   Setup the video controller.
 */
void videoInit()
{
    cat524Write (CAT524_COLOR, fileGetColor());
    cat524Write (CAT524_BRIGHT, fileGetBright());
    cat524Write (CAT524_CONT, fileGetContrast());
    cat524Write (CAT524_TINT, fileGetTint());

    ioScanDown (TRUE);
    ioCompVideo (TRUE);
    ioSetMode (fileGetLCDMode());
}

// This is where we go after reset.
void main()
{
    // Initialize the base system.
    sysInit();

    // Wait for the hardware to come up after reset.
    delay_ms (100);

    // If the file system is not valid, then set default values.
    if (!fileInit()) {
        fileSetFreq (2418);
        fileSetBackLightPWM (0x80);
        fileSetColor (0x80);
        fileSetBright (0x80);
        fileSetContrast (0x80);
        fileSetTint (0x80);
        fileSetLCDMode (0x06);
    } // END if

    // Initialize the subsystems.
    adcInit();
    ioInit();
    lcdInit();
    timerInit();
    videoInit();

    // Now run the system.
    sysRun();
}