/**************************************************************************** MORSERX.C 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> #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 ); byte Listen ( void ); char Decode ( byte 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 */ byte 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 }; byte 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 ); } byte Listen ( void ) { byte 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 ( byte cCodeVal ) { byte 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 ); }