/****************************************************************************
MORSERX1.C

This version simply changes type byte to type char, and to uppercase on
defines.

This program listens to an incoming stream of Morse code.  It samples
the first 20 tones to determine the dot and dash times, then calculates
a discrimination threshold.  Thereafter it displays the decoded Morse code
to the LCD display.

RULES:
- a "dot" is the basic time unit
- a "dash" is three time units
- dot-dash spacing is one time unit
- spacing between words is seven time units

LM567 Tone Decoder (or other TTL Morse code source) goes into pin A1,
which is low active.  An LED is on pin A2 to reflect what is heard at
the input.  If pin A3 is high, a 40 character LCD is assumed, if low,
a 16 character display is assumed.

                  +5   +5                       +5
                   |    |                        |
                   14   4                        2
                ----------                ----------
   CodeIn---18-|          |-6---------11-|DB4   Vdd |
               |          |-7---------12-|DB5       |
               |          |-8---------13-|DB6       |
               |  16F84   |-9---------14-|DB7     Vo| 3----20K pot
  4MHz XTAL-16-|          |              |    LCD   |
       XTAL-15-|          |-10---------6-|EN        |
               |          |-11---------4-|R/S       |
               |          |-1--LED       |          |
               |          |-2--16/40     |  RW  Vss |
                ----------                ----------
                     5                      1   5
                     |                      |   |
                    Gnd                    Gnd Gnd

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

#include < 16F84.h >
#include < jonsinc.h >

#fuses XT, NOWDT, NOPUT, NOPROTECT

#use standard_io ( A )
#use standard_io ( B )
#use delay ( clock = 4000000 )
// #use rs232 ( BAUD = 9600, XMIT = PIN_A3 )   // set up alternative output to RS232 terminal

#define LCD_D0  PIN_B0
#define LCD_D1  PIN_B1
#define LCD_D2  PIN_B2
#define LCD_D3  PIN_B3
#define LCD_EN  PIN_B4
#define LCD_RS  PIN_B5
#define FIRST_LINE  0
#define SECOND_LINE 0x40
#define CLEAR_DISP  0x01

#define CODE_IN     PIN_A1
#define LED         PIN_A2
#define LCD_WIDTH   PIN_A3
#define SAMPLES     20
#define SAMPLE_DELAY 5

void Learn ( void );
char Listen ( void );
char Decode ( char cCodeVal );
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 );

/* ALPHA CHARACTERS */
char const cMorseTable [ 51 ] = { 5, 1, 3, 6, 26,
                                  11, 28, 55, 34, 105,
                                  108, 93, 41, 16, 19,
                                  29, 4, 9, 23, 18,
                                  47, 38, 114, 96, 70,
                                  199, 20, 10, 12, 14,
                                  7, 13, 61, 31, 46,
                                  75, 81, 40, 48, 8,
                                  15, 17, 21, 2, 24,
                                  59, 32, 62, 44, 107,
                                  84 };

char const cCharTable [ 51 ] = { 'a', 'e', 'i', 'm', 'q',
                                 'u', 'y', '3', '7', '.',
                                 ')', '\'', '+', 'b', 'f',
                                 'j', 'n', 'r', 'v', 'z',
                                 '4', '8', ',', '-', ':',
                                 '$', 'c', 'g', 'k', 'o',
                                 's', 'w', '1', '5', '9',
                                 '?', '"', '/', '=', 'd',
                                 'h', 'l', 'p', 't', 'x',
                                 '2', '6', '0', '(', '_',
                                 ';' };

static char cThreshold;
static char cSpace;
static char cLcdWidth;

void main ( void )
        {
        char cX;

        if ( input ( LCD_WIDTH ) == LOW )
            {
            cLcdWidth = 16;     // set LCD for 16 character display
            }
        else
            {
            cLcdWidth = 40;     // set LCD for 40 character display
            }

        LCD_Init();

        LCD_SetPosition ( cLcdWidth );
        printf ( LCD_PutChar, "CALIBRATING..." );

        Learn();        // calibrate tone times

        LCD_PutCmd ( CLEAR_DISP );
        LCD_SetPosition ( cLcdWidth );

        while ( TRUE )      // do forever
            {
            cX = Decode ( Listen() );       // get character
            if ( cX != 0 )          // if valid character
                {
                printf ( LCD_PutChar, "%c", cX );   // display it
                // printf ( "%c", cX );  // alternative output to RS232 terminal
                }
            }
        }

void Learn ( void )
    {
    char cCnt, cTime, cX, cY, cHighest, cLowest;
    char cDuration [ SAMPLES ];
    long iAvg;

    for ( cCnt = 0; cCnt < SAMPLES; cCnt++ )    // for a number of tones
        {
        cTime = 0;
        while ( input ( CODE_IN ) == LOW );     // wait during any tone in progress
        while ( input ( CODE_IN ) == HIGH );    // wait while silent
        while ( input ( CODE_IN ) == LOW )  // get tone duration
            {
            cTime++;
            delay_ms ( SAMPLE_DELAY );      // discrimination granularity
            }
        cDuration [ cCnt ] = cTime; // store tone time
        }

    // find lowest and highest tone times
    cLowest = cDuration [ 0 ];      // get first time
    cHighest = cDuration [ 0 ];     // get first time
    for ( cCnt = 1; cCnt < SAMPLES; cCnt++ )
        {
        cX = cDuration [ cCnt ];    // get next time
        if ( cX < cLowest )         // if this time is less than last time
            {
            cLowest = cX;           // save new lowest value
            }
        if ( cX > cHighest )
            {
            cHighest = cX;      // save new highest value
            }
        }

    // calculate threshold time
    cX = cLowest + ( ( cHighest - cLowest ) / 2 );
    cThreshold = cX;

    // find average of dot times
    iAvg = 0;
    for ( cCnt = 0, cY = 0; cCnt < SAMPLES; cCnt++ )
        {
        cX = cDuration [ cCnt ];    // get time
        if ( cX < cThreshold )      // if this time is a dot
            {
            iAvg += cX;     // add in value
            cY++;               // count values
            }
        }
    cX = iAvg / cY;     // average dot time

    // calculate space time
    cSpace = cX * 5;        // five dot times (really should be seven)

    printf ( LCD_PutChar, "SPEED = %u mS ", cThreshold * SAMPLE_DELAY );
    delay_ms ( 1500 );
    }

char Listen ( void )
    {
    char cPositionMult, cEncodedVal, cTime, cX;

    cEncodedVal = 0;        // default to 0 (invalid code)
    cPositionMult = 1;      // start at 1

    while ( TRUE )
        {
        if ( input ( CODE_IN ) == LOW )    // if tone heard
            {
            output_high ( LED );    // turn on LED during tone
            cTime = 0;
            while ( input ( CODE_IN ) == LOW ) // time the tone
                {
                delay_ms ( SAMPLE_DELAY );
                cTime++;
                }
            output_low ( LED );     // turn off LED after tone
            // Discriminate tone, hash into encoded value,
            // a dot = 1, a dash = 2, multiply by the position multiplier,
            // then double the position multiplier for next time
            cX = ( cTime > cThreshold ) ? 2 * cPositionMult : 1 * cPositionMult;
            cEncodedVal += cX;      // add into total
            cPositionMult *= 2;     // double the position multiplier
            }
        else        // discriminate spaces
            {
            cTime = 0;
            while ( input ( CODE_IN ) == HIGH )  // time the space
                {
                delay_ms ( SAMPLE_DELAY );
                cTime++;
                if ( cTime > cSpace )
                    {
                    return ( cEncodedVal );       // return encoded value
                    }
                }
            }
        }
    }

char Decode ( char cCodeVal )
    {
    char cPtr;

    for ( cPtr = 0; cPtr < 51; cPtr++ )     // search table
        {
        if ( cMorseTable [ cPtr ] == cCodeVal ) // if encoded value is in table
        {
        return ( cCharTable [ cPtr ] ); // get it's character equivalent
            }
        }
    return ( 0 );   // character not found, invalid instead
    }

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 ( 0x20 );    /* function set (1 lines, 5x7 characters) */
    LCD_PutCmd ( 0x0E );    /* display ON, cursor on, no blink */
    LCD_PutCmd ( 0x01 );    /* clear display */
    LCD_PutCmd ( 0x07 );    /* entry mode set, increment & scroll left */
    }

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