//
// 
//  Name:          cutdown.c
//
//
//  Revision History:
//
//   M. Gray        1 Oct 2003  V1.00  Initial release.
//
//
//  COPYRIGHT (c) 2003 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 <16f84a.h>

#fuses HS,NOWDT,NOPROTECT,NOPUT

// These compiler directives set the clock and baud rate information.
#use delay(clock=4897000)
#use rs232(baud=4800, xmit=PIN_B7)
#use fast_io(A)
#use fast_io(B)

// We define types that are used for all variables.  These are
// declared because compilers have different sizes for int and long.
typedef int1 bool;
typedef unsigned int uint8;
//typedef signed long int16;
typedef unsigned long uint16;

// Public methods for each class.
#define IO_DEBUG PIN_A0
#define IO_LED PIN_A3
#define IO_RX PIN_B0
#define IO_WIRE1 PIN_B2
#define IO_WIRE2 PIN_B3

#define PWM_PRI_COUNT 478
#define PWM_DUTY_CYCLE_DELTA 14

/**
 *    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 crc16 (uint8 *buffer, uint16 length)
{
    uint8 i, bit;
    uint16 crc, value;

    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;
}

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

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

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

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

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

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

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

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

// Timer that determines when we turn off the LED and cut down wires.
uint16 powerDown;

// 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;

uint16 wirePWMDutyCycle;
uint16 wirePWMAccum;

// This ISR gets call every 209uS
#INT_RTCC
void timer0Interrupt()
{
    // 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

    // Counter to determine when to cycle LED.
    if (powerDown != 0)
        if (--powerDown == 0) {
            output_low (IO_WIRE1);
            output_low (IO_WIRE2);
            output_low (IO_LED);
            wirePWMDutyCycle = 0;
            wirePWMAccum = 0;
        }

    // PWM the cutdown wire output.
    if (wirePWMDutyCycle != 0) {
        // Wrap the accumulator at the PRI rate and fire the wires.
        if (++wirePWMAccum == PWM_PRI_COUNT) {
            wirePWMAccum = 0;
            output_high (IO_WIRE1);
            output_high (IO_WIRE2);

            // Increase the PWM duty cycle until we reach maximum.
            if (wirePWMDutyCycle < PWM_PRI_COUNT) {
                wirePWMDutyCycle += PWM_DUTY_CYCLE_DELTA;

                if (wirePWMDutyCycle > PWM_PRI_COUNT)
                    wirePWMDutyCycle = PWM_PRI_COUNT;
            } // END if
        } // END if

        // Once we reach the duty cycle, shut it off.
        if (wirePWMAccum == wirePWMDutyCycle) {
            output_low (IO_WIRE1);
            output_low (IO_WIRE2);
        } // END if
    } // END if
}

uint8 timerSyncChar(uint8 sync)
{
    uint8 i;

    while (!dataReady);
    dataReady = false;

    shiftCount = 8;

    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;
}

uint8 timerGetChar()
{
    uint8 i, value;

    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;
}

#define MODE_FRAMESYNC_1 1
#define MODE_FRAMESYNC_2 2
#define MODE_FRAMESYNC_3 3
#define MODE_FRAMESYNC_4 4
#define MODE_COMMAND 5
#define MODE_LENGTH 6
#define MODE_DATA 7
#define MODE_CRC_1 8
#define MODE_CRC_2 9

#define COMMAND_SELFTEST 0x21
#define COMMAND_CUTDOWN 0x22

#define INDEX_COMMAND 0
#define INDEX_LENGTH 1
#define INDEX_DATA_START 2
#byte OPTION_REG = 0x81

int main()
{
    uint8 mode, accum, commandIndex, commandBuffer[12];
    uint16 crc;
    boolean runFlag;

    // Set the output state before we configure the port direction.
    output_a (0x00);
    output_b (0x00);

    // Configure the I/O port direction.
    set_tris_a (0x00);
    set_tris_b (0x51);

    // Set all the state variables.
    accum = 0x00;
    bitCount = 0;
    dataReady = false;
    decodeBits = 0x00;
    decodeCount = 0;
    outputByte = 0x00;
    shiftCount = 0;
    rxByte = 0x00;
    powerDown = 9600;
    wirePWMDutyCycle = 0;
    wirePWMAccum = 0;


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

#asm
bsf OPTION_REG,3
#endasm 

    // Setup our interrupts.
    enable_interrupts(INT_TIMER0);
    enable_interrupts(GLOBAL);

    // Turn on the LED to indicate successful startup.  It will turn off in 2 seconds.
    output_high (IO_LED);

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

    // State machine to decode the data stream.  The stream contains:\
    // 0xcccc     Preamble
    // 0x18477025 Unique word
    // 0x21, 0x22 Command, cut down or self test
    // nn         Data Length
    // 1 .. nn    Data, up to 10 bytes
    // xx xx      16-bit CRC

    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();

                if (commandBuffer[INDEX_COMMAND] != COMMAND_CUTDOWN && commandBuffer[INDEX_COMMAND] != COMMAND_SELFTEST)
                    mode = MODE_FRAMESYNC_1;
                else
                    mode = MODE_LENGTH;
                break;

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

                if (commandBuffer[INDEX_LENGTH] > 10)
                    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 = crc16 (commandBuffer, commandIndex - 1);

                // If the CRC is valid process the command.
                if ((crc >> 8) == commandBuffer[commandIndex - 1] && (crc & 0xff) == commandBuffer[commandIndex])
                    switch (commandBuffer[INDEX_COMMAND]) {
                        case COMMAND_SELFTEST:
                            if (powerDown == 0) {
                                output_high (IO_LED);
                                powerDown = 2400;
                            } // END if
                            break;

                        case COMMAND_CUTDOWN:
                            if (powerDown == 0) {
                                output_high (IO_LED);
                                powerDown = 60000;

                                // Start out with a duty cycle of 3mS
                                wirePWMDutyCycle = PWM_DUTY_CYCLE_DELTA; 
                                wirePWMAccum = 0;

                                output_high (IO_WIRE1);
                                output_high (IO_WIRE2);
                            }
                            break;
                    } // END switch

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

                break;



        } // END switch
    } // END while

    return 0;

}