//
// 
//  Name:          ATVTxController.c
//
//
//  Revision History:
//
//   M. Gray        1 Mar 2004  V1.00  Initial release.
//
//
//  COPYRIGHT (c) 2004 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>

#fuses HS,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT,NOLVP

// These compiler directives set the clock and baud rate information.
#use delay(clock=4897000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)
#use i2c (master, scl=PIN_C5, sda=PIN_C4)

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

// We define types that are used for all variables.  These are
// declared because compilers have different sizes for int and long.

/// 8-bit signed number.
typedef int int8_t;

/// 8-bit unsigned number.
typedef unsigned int uint8_t;

/// 16-bit signed number.
typedef signed long int16_t;

/// 16-bit unsigned number.
typedef unsigned long uint16_t;

// Public methods for each class.

/// Power control pin for the S-Band power amplifier
#define IO_PA PIN_C1

/// Status LED.
#define IO_LED PIN_C2

/// Wireless RF receiver input.
#define IO_RX PIN_C3

uint8_t camASCIIToText(uint8_t text);
void camInit();
void camSendCommand (uint8_t *command, uint8_t length);
void camSetTime (uint8_t hours, uint8_t minutes);
void camSetTitle (uint8_t x, uint8_t y, uint8_t color, char *title);
void camSetZoom (uint16_t zoom);
boolean camWaitAckCompletion();

uint16_t sysCRC16 (uint8_t *buffer, uint16_t length);
void sysProcessCommand(uint8_t *command);
void sysRun();

void timerInterrupt();
uint8_t timerGetChar();
uint8_t timerGetTick();
void timerSetLEDTimeout();
boolean timerSyncChar(uint8_t sync);

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

/*
 *   Class to handle the Sony FCB-IX47 camera.
 */
static char CAM_DEFAULT_TITLE[] = "FM-ATV KD7LMO V1.0";

static uint8_t CAM_ADDRESS_SET[] = { 0x88, 0x30, 0x01, 0xFF };
static uint8_t CAM_IF_CLEAR[] = { 0x88, 0x01, 0x00, 0x01, 0xFF };
static uint8_t CAM_TITLE_CLEAR[] = { 0x81, 0x01, 0x04, 0x74, 0x00, 0xFF };
static uint8_t CAM_TITLE_ON[] = { 0x81, 0x01, 0x04, 0x74, 0x02, 0xFF };
static uint8_t CAM_TIME_ON[] = { 0x81, 0x01, 0x04, 0x72, 0x02, 0xFF };
static uint8_t CAM_MANUAL_FOCUS[] = { 0x81, 0x01, 0x04, 0x38, 0x03, 0xFF };
static uint8_t CAM_AF_TRIGGER[] = { 0x81, 0x01, 0x04, 0x18, 0x01, 0xFF };
static uint8_t CAM_WB_OUTDOOR[] = { 0x81, 0x01, 0x04, 0x35, 0x02, 0xFF };

static uint8_t ACK_SEQUENCE[] = { 0x90, 0x40, 0xff, 0x90, 0x50, 0xff };

/// Values used to set the color of the on screen text display.
enum CAM_COLOR {
    CAM_WHITE = 0x00,
    CAM_YELLOW,
    CAM_VIOLET,
    CAM_RED,
    CAM_CYAN,
    CAM_GREEN,
    CAM_BLUE
};

/// Camera zoom set to widest angle of view.
#define CAM_ZOOM_WIDE 0x0000

/// Cameara zoom set to nearest angle of view (without using digital zoom).
#define CAM_ZOOM_TELE 0x4000

/** 
 *   Convert ASCII character to the Sony Camera character mapping.  For example, A = 0x00, B=0x01, etc.
 *
 *   @param text ASCII to convert
 *
 *   @return Sony Camera character
 */
uint8_t camASCIIToText(uint8_t text)
{
    // Convert the block of letters A to Z.
    if (text >= 'A' && text <= 'Z')
        return text - 'A';

    // Convert the block of letters a to z. (The camera doesn't support lower case, so we just make them all upper case.
    if (text >= 'a' && text <= 'z')
        return text - 'a';
        
    // Convert the block of numbers 1 to 9.
    if (text >= '1' && text <= '9')
        return (0x1e + text - '1');
        
    // The rest we do character by character.        
    switch (text) {
        case ' ':
            return 0x1b;
            
        case '0':
            return 0x27;
            
        case '$':
            return 0x41;
            
        case '\"':
            return 0x49;
            
        case ':':
            return 0x4a;

        case '\'':
            return 0x4b;
            
        case '.':
            return 0x4c;
            
        case ',':
            return 0x4d;
            
        case '/':
            return 0x4e;
            
        case '-':
            return 0x4f;
        
        default:
            return 0x46;
    } // END switch
}

/**
 *   Setup the camera interface and default operation conditions.
 */
void camInit()
{
    // Send the required address and interface clear command first.
    camSendCommand (CAM_ADDRESS_SET, sizeof(CAM_ADDRESS_SET));
    camSendCommand (CAM_IF_CLEAR, sizeof(CAM_IF_CLEAR));
    
    // Set manual focus mode.
    camSendCommand (CAM_MANUAL_FOCUS, sizeof(CAM_MANUAL_FOCUS));    

    // Set the white balance to a steady state.
    camSendCommand (CAM_WB_OUTDOOR, sizeof(CAM_WB_OUTDOOR));

    // Zoom to wide angle.
    camSetZoom (CAM_ZOOM_WIDE);    

    // Clear any previous title and then turn on the title function.
    camSendCommand (CAM_TITLE_CLEAR, sizeof(CAM_TITLE_CLEAR));
    camSendCommand (CAM_TITLE_ON, sizeof(CAM_TITLE_ON));

    // Set the time to 9:00 AM, our nominal flight time
    camSetTime (9, 0);
    camSendCommand (CAM_TIME_ON, sizeof(CAM_TIME_ON));

    // Set a default title with software version information.
    camSetTitle (3, 1, CAM_BLUE, CAM_DEFAULT_TITLE);
}

/** 
 *   Send the binary block <b>command</b> of <b>length</b> bytes to the camera via the
 *   RS-232 control port
 *
 *   @param command pointer to buffer of binary data to send
 *   @param length of buffer in bytes
 */
void camSendCommand (uint8_t *command, uint8_t length)
{
    uint8_t i;
    
    for (i = 0; i < length; ++i)
        putc (command[i]);

    delay_ms(100);
}

/**
 *  Display the text <b>title</b> at coordinates (<b>x</b>,<b>y</b>) in the
 *  color <b>color</b> where the coordinates (0,0) are in the upper, left-hand
 *  portion of the screen.
 *
 *  @param x coordinate
 *  @param y coordinate
 *  @param color enumeration
 *  @param title NULL terminate string
 */
void camSetTitle (uint8_t x, uint8_t y, uint8_t color, char *title)
{
    uint8_t i, command[16];
    
    // Range check the data based on the camera limitations.
    if (x > 0x17 || y > 0x0a || color > 6)
        return;
    
    // Generate and send the 'Title Set 1' command packet.    
    command[0] = 0x81;
    command[1] = 0x01;
    command[2] = 0x04;
    command[3] = 0x73;
    
    command[4] = 0x00;
    command[5] = y;
    command[6] = x;
    command[7] = color;
    memset (command + 8, 0, 7);    
    command[15] = 0xff;
    
    camSendCommand(command, 16);

    // Generate and send the 'Title Set 2' command packet.    
    command[4] = 0x01;
    memset (command + 5, camASCIIToText(' '), 10);
    
    for (i = 5; i < 15 && *title != 0; ++i, ++title)
        command[i] = camASCIIToText(*title);
    
    camSendCommand(command, 16);

    // Generate and send the 'Title Set 3' command packet.    
    command[4] = 0x02;
    memset (command + 5, camASCIIToText(' '), 10);
    
    for (i = 5; i < 15 && *title != 0; ++i, ++title)
        command[i] = camASCIIToText(*title);
    
    camSendCommand(command, 16);
}

/**
 *   Set the on screen time display.
 *
 *   @param hours time in hours
 *   @param minutes time in minutes
 */
void camSetTime (uint8_t hours, uint8_t minutes)
{
    uint8_t command[15];

    // Ignore invalid requests.
    if (hours > 24 || minutes > 60)
        return;

    command[0] = 0x81;  // Camera address
    command[1] = 0x01;  // Set time command sequence.
    command[2] = 0x04;
    command[3] = 0x70;
    command[4] = 0x00;  // Year
    command[5] = 0x04;
    command[6] = 0x00;  // Month
    command[7] = 0x01;
    command[8] = 0x00;  // Day
    command[9] = 0x01;
    command[10] = hours / 10;  // Hour
    command[11] = hours % 10;
    command[12] = minutes / 10;  // Minute
    command[13] = minutes % 10;
    command[14] = 0xff;

    camSendCommand (command, 15);
}

/**
 *   Set the camera zoom in the range of 0x0000 (wide) to 0x4000 (telephoto).
 *
 *   @param zoom linear zoom factor
 */
void camSetZoom (uint16_t zoom)
{
    uint8_t command[9];

    command[0] = 0x81;  // Camera address
    command[1] = 0x01;  // Set time command sequence.
    command[2] = 0x04;
    command[3] = 0x47;
    command[4] = (zoom >> 12) & 0x0f;
    command[5] = (zoom >> 8) & 0x0f;
    command[6] = (zoom >> 4) & 0x0f;
    command[7] = zoom & 0x0f;
    command[8] = 0xff;

    camSendCommand (command, 9);
}

/**
 *   Wait for the ACK and completion of the command.
 *
 *   @return true if ACK and completion sequence received; otherwise false
 */
boolean camWaitAckCompletion()
{
    uint8_t value, ackIndex, startTime;
    
    startTime = timerGetTick();
    ackIndex = 0;
    
    for (;;) {
        if (kbhit()) {
            value = getch();
            
            if (value == ACK_SEQUENCE[ackIndex]) {
                ++ackIndex;
                
                if (ackIndex == sizeof(ACK_SEQUENCE))
                    return true;
            } else
                ackIndex = 0;
        } // END if kbhit
        
        // If we didn't get the ACK sequence within 2000mS, we will give up.
        if (startTime - timerGetTick() > 20)
            return false;
        
    } // END while
}

/*
 *   Class to handle the system functions.
 */
/// State machine waiting for frame sync byte 1.
#define MODE_FRAMESYNC_1 1

/// State machine waiting for frame sync byte 2.
#define MODE_FRAMESYNC_2 2

/// State machine waiting for frame sync byte 3.
#define MODE_FRAMESYNC_3 3

/// State machine waiting for frame sync byte 4.
#define MODE_FRAMESYNC_4 4

/// State machine waiting for command byte.
#define MODE_COMMAND 5

/// State machine waiting for length of incoming command.
#define MODE_LENGTH 6

/// State machine waiting for 0 .. n data bytes.
#define MODE_DATA 7

/// State machine waiting for CRC byte 1.
#define MODE_CRC_1 8

/// State machine waiting for CRC byte 2.
#define MODE_CRC_2 9

/// Remote command to reboot processor.
#define COMMAND_REBOOT 0x30

/// Remote command to display text at coordinates (x, y) in color c.
#define COMMAND_TITLE 0x31

/// Remote command to execute auto focus camera sequence.
#define COMMAND_FOCUS 0x32

/// Remote command to set the frequency.
#define COMMAND_SET_FREQ 0x33

/// Remote command to set the PA state.
#define COMMAND_SET_PA 0x34

/// Remote command to set the time.
#define COMMAND_SET_TIME 0x35

/// Index into buffer that specifies location of command byte.
#define INDEX_COMMAND 0

/// Index into buffer that specifies location of command length.
#define INDEX_LENGTH 1

/// Index into buffer that specifies start of data bytes.
#define INDEX_DATA_START 2

/// The maximum number of data bytes accepted in a command.
#define MAX_DATA 32

/**
 *    Calculate the CRC-16 CCITT of <b>buffer</b> that is <b>length</b> bytes long.
 *
 *    @param buffer Pointer to data buffer.
 *    @param length number of bytes in data buffer
 *
 *    @return CRC-16 of buffer[0 .. length]
 */
uint16_t sysCRC16 (uint8_t *buffer, uint16_t length)
{
    uint8_t i, bit, value;
    uint16_t crc;

    crc = 0xffff;

    for (i = 0; i < length; ++i) {
        value = buffer[i];

        for (bit = 0; bit < 8; ++bit) {
            crc ^= (value & 0x01);
            crc = ( crc & 0x01 ) ? ( crc >> 1 ) ^ 0x8408 : ( crc >> 1 );
            value = value >> 1;
        } // END for
    } // END for

    return crc ^ 0xffff;
}

/** 
 *   Process the wireless command in the binary buffer <b>commandBuffer</b>.  The buffer
 *   contains the following entries:<br><br>
 *
 *   INDEX_COMMAND    - Binary command<br>
 *   INDEX_LENGTH     - Length of the command in bytes<br>
 *   INDEX_DATA_START - Command specific data<br>
 *
 *   @param pointer to command buffer
 */
void sysProcessCommand(uint8_t *command)
{
    uint16_t freq;

    switch (command[INDEX_COMMAND]) {
        case COMMAND_TITLE:
            // NULL terminate the string.
            command[command[INDEX_LENGTH] + 2] = 0;

            camSetTitle (0, 0, CAM_WHITE, command + INDEX_DATA_START);
            break;

        case COMMAND_FOCUS:
            camSendCommand (CAM_AF_TRIGGER, sizeof(CAM_AF_TRIGGER));
            break;

        case COMMAND_SET_FREQ:
            freq = (uint16_t) (command[INDEX_DATA_START] << 8);
            freq |= (uint16_t) command[INDEX_DATA_START + 1];
            tunerSetFreq (freq);
            break;

        case COMMAND_SET_PA:
            if (command[INDEX_DATA_START] == 0x00)
                tunerSetState (false);

            if (command[INDEX_DATA_START] == 0xff)
                tunerSetState (true);
            break;

        case COMMAND_SET_TIME:
            camSetTime (command[INDEX_DATA_START], command[INDEX_DATA_START + 1]);
            break;
    } // END switch
}

/**
 *    Main run task. 
 */
void sysRun()
{
    uint8_t mode, accum, commandIndex, commandBuffer[MAX_DATA + 4];
    uint16_t crc;
    boolean runFlag;

    // Setup the state machine.
    runFlag = true;
    mode = MODE_FRAMESYNC_1;
    accum = 0;

    while (runFlag) {
        switch (mode) {
            case MODE_FRAMESYNC_1:
                if (timerSyncChar(0x18))
                    mode = MODE_FRAMESYNC_2;
                break;

            case MODE_FRAMESYNC_2:
                if (timerGetChar() != 0x47)
                    mode = MODE_FRAMESYNC_1;
                else
                    mode = MODE_FRAMESYNC_3;

                break;

            case MODE_FRAMESYNC_3:
                if (timerGetChar() != 0x70)
                    mode = MODE_FRAMESYNC_1;
                else
                    mode = MODE_FRAMESYNC_4;

                break;

            case MODE_FRAMESYNC_4:
                if (timerGetChar() != 0x25)
                    mode = MODE_FRAMESYNC_1;
                else
                    mode = MODE_COMMAND;
                break;

            case MODE_COMMAND:
                commandBuffer[INDEX_COMMAND] = timerGetChar();
                mode = MODE_LENGTH;
                break;

            case MODE_LENGTH:
                commandBuffer[INDEX_LENGTH] = timerGetChar();
                commandIndex = INDEX_DATA_START;

                if (commandBuffer[INDEX_LENGTH] > MAX_DATA)
                    mode = MODE_FRAMESYNC_1;
                else if (commandBuffer[INDEX_LENGTH] == 0)
                        mode = MODE_CRC_1;
                    else
                        mode = MODE_DATA;
                break;

            case MODE_DATA:
                commandBuffer[commandIndex] = timerGetChar();
    
                if (++commandIndex == commandBuffer[INDEX_LENGTH] + 2)
                    mode = MODE_CRC_1;
                break;

            case MODE_CRC_1:
                commandBuffer[commandIndex++] = timerGetChar();
                mode = MODE_CRC_2;  
                break;

            case MODE_CRC_2:
                // Save the LSB of the CRC.
                commandBuffer[commandIndex] = timerGetChar();

                // Calcualte the CRC of the buffer.
                crc = sysCRC16 (commandBuffer, commandIndex - 1);

                // If the CRC is valid process the command.
                if ((crc >> 8) == commandBuffer[commandIndex - 1] && (crc & 0xff) == commandBuffer[commandIndex]) {
                    timerSetLEDTimeout();
                    output_high (IO_LED);

                    if (commandBuffer[INDEX_COMMAND] == COMMAND_REBOOT)
                        runFlag = false;
                    else
                        sysProcessCommand(commandBuffer);
                }

                // No matter what, start the search over.
                mode = MODE_FRAMESYNC_1;

                break;
        } // END switch
    } // END while
}


/*
 *   Class to handle the timer functions and process the incoming wireless bit stream.
 */

/// Counts the length of time the input bit remains in the same state.
uint8_t subBitCount;

/// Number of bits we have accumulated before we manchester decode the input stream.
uint8_t decodeCount;

/// The last two bits we have received.  We look at these bits to decode the manchester bit stream.
uint8_t decodeBits;

/// The last byte we decoded from the input stream.  We must process this is data in 16-bit times.
uint8_t outputByte;

/// The number of bits we have accumulated in rxByte.  Once we have 8, we have a complete byte.
uint8_t bitCount;

/// Temporary register to hold each data bit as we receive it.
uint8_t rxByte;

/// Accumulator that shifts each received bit through it until we sync with the unique word.
uint8_t accum;

/// The number of bits lefts in outputByte.
uint8_t shiftCount;

/// Flag that is set when outputByte contains the latest 8-bits of data.
boolean dataReady;

/// Set to the last input state from the receiver.
boolean lastState;

/// Rolling 8-bit value that updates every 100mS.
uint8_t timeTick;

/// Counter that turns off LED after we receive and cross payload packet.
uint16_t ledTimeOut;

/**
 *   Process the timer 0 interrupt that occurs every 209uS.
 */
#INT_RTCC
void timerInterrupt()
{
    // Check the state of the input pin.
    if (input(IO_RX)) {
        if (!lastState) {
            subBitCount = 2;
            lastState = true;
        } else
            if (subBitCount != 0)
                if (--subBitCount == 0) {
                    subBitCount = 4;

                    // We have detected a 1 bit, so save it.
                    decodeBits = (decodeBits << 1) | 0x01;
                    ++decodeCount;          
                } // END if
    } else {
        if (lastState) {
            subBitCount = 2;
            lastState = false;
        } else
            if (subBitCount != 0)
                if (--subBitCount == 0) {
                    subBitCount = 4;

                    // We have detected a  bit, so save it.
                    decodeBits = (decodeBits << 1);
                    ++decodeCount;
                } // END if
    } // END if-else

    // Manchester decode the bit stream.
    if (decodeCount == 2) {
        switch (decodeBits) {
            // Resync the bit stream.
            case 0x00:
                decodeCount = 1;
                break;

            // A 0 to 1 transition indicates we have a 0 data bit.
            case 0x01:
                // Reset the counter that tracks the number of bits we look at.
                decodeCount = 0;

                // Clear the bit accumulator.
                decodeBits = 0x00;

                // Save a 0 in the output regsiter.
                rxByte = rxByte << 1;

                // Once we have a full byte, save it and start over.
                if (++bitCount == 8) {
                    outputByte = rxByte;
                    rxByte = 0x00;
                    bitCount = 0;
                    dataReady = true;
                } // END if
                break;

            // A 1 to 0 transition indicates we have a 1 data bit.
            case 0x02:
                // Reset the counter that tracks the number of bits we look at.
                decodeCount = 0;

                // Clear the bit accumulator.
                decodeBits = 0x00;

                // Save a 1 in the output register
                rxByte = (rxByte << 1) | 0x01;

                // Once we have a full byte, save it and start over.
                if (++bitCount == 8) {
                    outputByte = rxByte;
                    rxByte = 0x00;
                    bitCount = 0;
                    dataReady = true;
                } // END if

                break;

            // Resync the bit stream.
            case 0x03:
                decodeCount = 1;
                decodeBits = 0x01;
                break;
        } // END switch
    } // END if

    // Turn off the LED after 250mS.
    if (ledTimeOut != 0)
        if (--ledTimeOut == 0)
            output_low (IO_LED);
}

/** 
 *   Wait for and return an incoming character from the wireless link.
 *
 *   @return last character received
 */
uint8_t timerGetChar()
{
    uint8_t i, value;

    // When the flag is true, we have read 8-bits from the wireless interface.
    while (!dataReady);
    dataReady = false;

    value = 0;

    for (i = 0; i < 8; ++i) {
        accum = (accum << 1) | ((outputByte & 0x80) ? 0x01 : 0x00);
        outputByte = outputByte << 1;

        if (i == shiftCount)
            value = accum;
    }

    return value;
}

/**
 *   Rolling timer tick that updates every 100mS
 *
 *   @return timer tick
 */
uint8_t timerGetTick()
{
    return timeTick;
}

/**
 *  Initialize the timer variables and control.
 */
void timerInit()
{
    accum = 0x00;
    bitCount = 0;
    dataReady = false;
    decodeBits = 0x00;
    decodeCount = 0;
    ledTimeOut = 0x0000;
    outputByte = 0x00;
    shiftCount = 0;
    rxByte = 0x00;
    timeTick = 0;

    // Set the timer to fire at regular intervals.
    set_timer0(0);
    setup_timer_0( RTCC_INTERNAL | RTCC_DIV_1 | RTCC_8_BIT );
}

void timerSetLEDTimeout()
{
    ledTimeOut = 1196;
}

/** 
 *   Check the next 8 incoming bits on the wireless link and search for the <b>sync</b>
 *   byte.
 *
 *   @param sync byte pattern to search for
 *
 *   @return true if found in 8-bit times; otherwise false
 */
boolean timerSyncChar(uint8_t sync)
{
    uint8_t i;

    // When the flag is true, we have read 8-bits from the wireless interface.
    while (!dataReady);
    dataReady = false;

    shiftCount = 8;

    // Shift through 8-bits to see if we have a match.
    for (i = 0; i < 8; ++i) {
        accum = (accum << 1) | ((outputByte & 0x80) ? 0x01 : 0x00);
        outputByte = outputByte << 1;

        if (accum == sync)
            shiftCount = i;
    } // END for

    if (shiftCount == 8)
        return false;
    else
        return true;
}

/*
 *   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 transmit frequency.
 *   
 *   @param freq in MHz
 */
void tunerSetFreq(uint16_t freq)
{
    // Only allow operation in the 13cm band.
    if (freq < 2400 || freq > 2450)
        return;

    // Tell the I2C device to connect
    i2c_start();

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

    // Calcualte the divider based the 4MHz reference clock.
    freq = 8 * freq;
    
    // Write the divider value.
    i2c_write(freq >> 8);
    i2c_write(freq & 0xff);

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

    i2c_stop();
}

/**
 *   Control the power amplifier.
 *
 *   @state true to enable; otherwise false
 */
void tunerSetState (boolean state)
{
    if (state)
        output_high (IO_PA);
    else
        output_low (IO_PA);
}

int main()
{
    // Set the output state before we configure the port direction.
    output_b (0x00);
    output_c (0x00);

    // Configure the I/O port direction.
    set_tris_b (0x00);
    set_tris_c (0x88);
    
    // Turn on the LED to indicate we have started to boot.
    output_high (IO_LED);

    // Initialize the classes.
    timerInit();
    
    // Wait for the hardware and power converters to start.
    delay_ms(250);
    
    // Set default frequency.
    tunerSetFreq (2418);
    
    // Wait for the PLL to lock.
    while (!tunerIsLocked());
   
    // Wait for the camera subsystem to boot.
    delay_ms(6000);

    // Turn on the PA after everything else is running.
    tunerSetState (true);
    
    // Initialize the camera interface.
    camInit();

    // Turn off the LED to indicate we are going to a run state.
    output_low (IO_LED);
    
    // Enable all the desired interrupts.
    enable_interrupts(INT_TIMER0);
    enable_interrupts(GLOBAL);

    // Wait for wireless commands.   
    sysRun();    

    // When the application stops, just reboot.    
    reset_cpu();
    
    return 0;
}