Morse Code Transmitter and Receiver - Working Code

Discussion on projects that are created by users and posted on mikroElektronika website.
Post Reply
Author
Message
Bill Legge
Posts: 235
Joined: 28 Oct 2007 03:16
Location: West Australia

Morse Code Transmitter and Receiver - Working Code

#1 Post by Bill Legge » 18 May 2023 07:03

I'm old enough to have learnt morse code and thought it would be fun to use some of the latest electronics to send and receive something archaic.
Both Tx and Rx use the EasyPicFusionv7 board with a PIC32MX795F512L MCU.
The Tx uses a TFT display to show Words Per Minute (WPM), pulse lengths and so on.

THE TRANSMITTER

Is the easiest to write and simply uses the function vdelay_ms() to create the 'DITS' (one unit of time) and the 'DAHS' (three units of time).
Inter-pulse delay is 1*DIT, inter-letter is 3*DIT' inter-word delay is 7*DIT.
Traditional, hand generated morse is usually in the region of 10 to 30 WPM. The word 'PARIS' is typically used as a test word.
My machine generates code works from 10 to 260 wpm where a DIT = 4[mS]
THE RECEIVER
Is a bit trickier as I've made it automatically track variations in morse speed.
Maybe of interest to coders: INT0 interrupt (on RD0 for morse input) and a rolling average to track changes in morse speed.
The key is to record 32 pulses that are either x[mS] or 3x[mS] long and average = 2x[mS]
Use the average to distinguish between DITs and DAhs, convert to 0s and 1s, convert this binary string to a decimal number and look up the appropriate morse code table.
The output is RS232 on UART2/USB UART A.

Two files below: The transmitter and the Receiver. The resources file ("Resources_Tahoma_26X29.h")for the transmitter is not included - if you want a copy let me know.
Good luck - comments, criticism and improvements welcomed.
Regards Bill Legge in Australia

Code: Select all

////////////////////////////////////////////////////////////////////////////////
// Project:         WVL_Fusion_Morse_Tx                                       //
// File:            WVL_Fusion_Morse_Tx.c                                     //
// Function:        Generate morse code 20 to 266wpm                          //
// MCU:             P32MX795F512L with 8MHz on-board Xtal                     //
// Board:           EasyPIC_Fusion_v7_for_PIC32                               //
// Power            7-12V input. 3.3V                                         //
// Compiler:        mikroC PRO for PIC32 V4.0                                 //
// Oscillator:      8Mhz Xtal, Div2. PLLX20 = 80MHz                           //
// Programmer:      On-board Mikro using PGEC2 and PGED2 on RB6 and RB7       //
// Author:          WVL                                                       //
// Date:            Mar 2023                                                  //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Morse code comprises tones, usually about 800Hz:                           //
// Dit 1 unit long                                                            //
// Dah 3 units long                                                           //
// Inter-pulse      1                                                         //
// Inter-character  3                                                         //
// Inter-word       7                                                         //
// Words-per-minute are usually calculated from 1200/dot[mS]                  //
// So, 1 Dit[ms] = 1200/wpm.                                                  //
// Hand generated morse is about 10 to 30 wpm but machine may be much faster  //
// Want 10 to 200 wpm                                                         //
// So Dit_times are: 10wpm = 120mS and 200wpm = 33mS                          //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// TFT module connections                                                     //
char TFT_DataPort at LATE;                                                    //
sbit TFT_RST at LATD7_bit;                                                    //
sbit TFT_BLED at LATD2_bit;                                                   //
sbit TFT_RS at LATD9_bit;                                                     //
sbit TFT_CS at LATD10_bit;                                                    //
sbit TFT_RD at LATD5_bit;                                                     //
sbit TFT_WR at LATD4_bit;                                                     //
char TFT_DataPort_Direction at TRISE;                                         //
sbit TFT_RST_Direction at TRISD7_bit;                                         //
sbit TFT_BLED_Direction at TRISD2_bit;                                        //
sbit TFT_RS_Direction at TRISD9_bit;                                          //
sbit TFT_CS_Direction at TRISD10_bit;                                         //
sbit TFT_RD_Direction at TRISD5_bit;                                          //
sbit TFT_WR_Direction at TRISD4_bit;                                          //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
#include "Resources_Tahoma_26X29.h"                                           //
#define  HEARTBEAT  LATG0_bit=~LATG0_bit                                      //
#define  TEXT_ENVELOPE   LATA3_bit                                            //
#define  WORD_ENVELOPE   LATA2_bit                                            //
#define  LETTER_ENVELOPE LATA1_bit                                            //
#define  PULSE_ENVELOPE  LATA0_bit                                            //
////////////////////////////////////////////////////////////////////////////////

void main(){
    // Comma count              1    3    3   4 5    6   7    8  9    10  11   12 13 14  15   16   17  18  18 19 20  21   22   23   24
    // Morse code            A  B    C    D   E F    G   H    I  J    K   L    M  N  0   P    Q    R   S   T U   V    W   X    Y    Z
    unsigned char CODE[]  = "01,1000,1010,100,0,0010,110,0000,00,0111,101,0100,11,10,111,0110,1101,010,000,1,001,0001,011,1001,1011,1100,";
    unsigned int code_number   = 0;          // count of CODE string
    unsigned int commas        = 0;          // commas until desired morse code
    unsigned int comma_count   = 0;          // commas so far
    unsigned char TEXT[]     = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"; // TEXT string 0-18, 19 characters plus '\0'
    unsigned int text_number   = 0;          // count of TEXT string
    unsigned int text_length = strlen(TEXT); // length of TEXT string
    unsigned int wpm           = 0;          // words per minute rasge from 4 to 64
    unsigned int dit_time      = 0;          // time of 1 dot in [mS]
    unsigned int dah_time      = 0;          // time of 1 dah in [mS]
    unsigned int ave_time      = 0;          // average of 1 dit and 1 dah
    unsigned int ADC_reading_1 = 0;          // 10bit, range 0 to 1023 for dit_time and wpm
    unsigned char txt[6];                    // for conversion unsigned int to string
    unsigned char letter =  0;               // next letter to be transmitted

    // Init MCU
    AD1PCFG = 0xFFFE;                   // configure AN0/RB0
    JTAGEN_bit = 0;                     // disable JTAG
    // Init pins
    TRISA0_bit = 0;                     // pulse envelope
    TRISA1_bit = 0;                     // letter envelope
    TRISA2_bit = 0;                     // word envelope
    TRISA3_bit = 0;                     // text envelope
    TRISD3_bit = 0;                     // audio output using SOUND function at 800Hz
    LATA       = 0;
    TRISG0_bit = 0;                     // heartbeat on RG0
    TRISB0_bit = 1;                     // input for ADC on RB0 for dot time and wpm
    // Init ADC to set morse speed
    ADC1_Init();
    // Init sound on RD3 for speaker on board
    Sound_Init(&PORTD,3);
    // Init TFT
    // TFT_Init(320, 240);             // old TFT controller
    TFT_Init_ILI9341_8bit(320, 240);   // new TFT controller Jan 2019
    Delay_ms(100);
    TFT_BLED = 1;
    // Get new readings to set wpm and dit_time.
    ADC_reading_1 = ADC1_Get_Sample(0);     // RB0, to set dot time and wpm
    // Calculate dit_time.
    // Good fast morse is about 20wpm.
    // Machine morse is about 5 to 50wpm
    // ADC is 0-1023, 1024/8 = 256 (right shift >>2)
    ADC_reading_1 = ADC_reading_1>>2;
    wpm = 10 + ADC_reading_1;               // slowest wpm is 10 max 266wpm
    dit_time = 1200/wpm;                    // range is about 120 - 4[mS]
    dah_time = 3*dit_time;                  // range is about 360 - 12[mS]
    ave_time = 2*dit_time;                  // range is about 240 - 8[mS]
    // Display on TFT
    TFT_Fill_Screen(CL_YELLOW);
    TFT_Set_Pen(CL_Black, 1);
    TFT_Set_Font(&Tahoma26X29, CL_BLUE, FO_HORIZONTAL);
    TFT_Write_Text("WVL_Fusion_Morse_Tx", 3, 10);
    TFT_Write_Text("ADC_1",   3, 50);
    TFT_Write_Text("WPM",     3, 80);
    TFT_Write_Text("DIT[mS]", 3, 110);
    TFT_Write_Text("DAH[mS]", 3, 140);
    TFT_Write_Text("AVE[mS]", 3, 170);
    WordToStr(ADC_reading_1,txt);
    TFT_Write_Text(txt,130,50);
    WordToStr(wpm,txt);
    TFT_Write_Text(txt,130,80);
    WordToStr(dit_time,txt);
    TFT_Write_Text(txt,130,110);
    WordToStr(dah_time,txt);
    TFT_Write_Text(txt,130,140);
    WordToStr(ave_time,txt);
    TFT_Write_Text(txt,130,170);
    
    while(1){
        TEXT_ENVELOPE = 1;
        // Get the next letter to be sent from TEXT string
        for(text_number=0;text_number<=text_length-1;text_number++){
            // Check if letter is a space (ASCII=32)
            if(TEXT[text_number]==32)Vdelay_ms(4*dit_time); // inter-word delay 1+2+4=7
            // Find the morse code for the next letter if not a space
            else{
                WORD_ENVELOPE = 1;
                commas = TEXT[text_number] - 65;    // ACII 'A' is 65
                // Step through the CODE until the correct number of commas has been counted
                code_number = 0;
                comma_count = 0;
                while(comma_count<commas){
                    if(CODE[code_number]==',')comma_count++;
                    code_number++;
                }
                LETTER_ENVELOPE = 1;
                // Send dits and dahs until ',' is read
                while(CODE[code_number] !=','){     // read string till next comma
                    if(CODE[code_number]=='0'){     // if next pulse is a dit
                        PULSE_ENVELOPE = 1;
                        Sound_Play(800,dit_time);
                        PULSE_ENVELOPE = 0;
                    }
                    else{                           // if next pulse is a dah
                        PULSE_ENVELOPE = 1;
                        Sound_Play(800,dah_time);
                        PULSE_ENVELOPE = 0;
                    }
                    code_number++;
                    // Close envelopes if next code is a ','
                    if(CODE[code_number]==','){
                        LETTER_ENVELOPE = 0;
                        // Close word envelope if next letter is a ' '
                        if(TEXT[text_number+1]==' ')WORD_ENVELOPE = 0;
                        // Close word and text envelope if next letter is string terminator
                        if(TEXT[text_number+1]==0){
                            WORD_ENVELOPE = 0;
                            TEXT_ENVELOPE = 0;
                        }
                    }
                    // Inter-pulse delay of 1
                    Vdelay_ms(dit_time);
                }
                // Inter-word delay of 1+2=3
                Vdelay_ms(2*dit_time);
            }
        }
        // Housekeeping
        HEARTBEAT;
        delay_ms(1000);
    }
}

Code: Select all

////////////////////////////////////////////////////////////////////////////////
// Project:         WVL_Fusion_Morse_Rx2                                      //
// File:            Main.c                                                    //
// Function:        Measure pulse length and period of morse pulses           //
//                  Use change on pin INT0 and 1mS global counter             //
//                  Uses rolling average to get average of dits and dahs      //
// MCU:             P32MX795F512L with 8MHz on-board Xtal                     //
// Board:           EasyPIC_Fusion_v7_for_PIC32                               //
// Power            7-12V input. 3.3V                                         //
// Compiler:        mikroC PRO for PIC32 V4.0                                 //
// Oscillator:      8Mhz Xtal, Div2. PLLX20 = 80MHz                           //
// Peripheral bus   Div by 1 = 80MHz                                          //
// Programmer:      On-board Mikro using PGEC2 and PGED2 on RB6 and RB7       //
// Author:          WVL                                                       //
// Date:            May 2023                                                  //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Decoding is based on the detection of a 'dit' in [mS]                      //
// A dit = 1 unit of time, A dah = 3 units of time. Average = 2               //
// Record 20 pulses and average then:                                         //
// inter-pulse time  = Av_time/2    low state < average                       //
// inter-letter time = Av_time*3/2  low state > average                       //
// inter-word time   = Av_time*7/2  low_state > average * 3                   //
// 0 or 'dit'        = Av_time/2    high state < average                      //
// 1 or 'dah'        = Av_time*3/2  high state > average                      //
// Using interrupts, maintain an average for the last 32 pulses               //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Defines                                                                    //
#define TRUE   1                                                              //
#define FALSE  0                                                              //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Global variables                                                           //
bit got_hi_time;                                                              //
bit got_lo_time;                                                              //
unsigned int count_ms = 0;                                                    //
unsigned int hi_time  = 0;                                                    //
unsigned int lo_time  = 0;                                                    //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
void U2(char c){UART2_Write(c);}                                              //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// System clock = 80MHz.  Prescale 1:8 = 10MHz = 0.1[uS] perios               //
// Preload = 10000. Overflow time = 1 ms                                      //
void InitTimer1(){                                                            //
  T1CON         = 0x8010;                                                     //
  T1IP0_bit         = 0;   // Int priority = 110 = 6                          //
  T1IP1_bit         = 1;                                                      //
  T1IP2_bit         = 1;                                                      //
  T1IF_bit         = 0;                                                       //
  T1IE_bit         = 1;                                                       //
  PR1                 = 10000;                                                //
  TMR1                 = 0;                                                   //
}                                                                             //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// TIMER1 interrupt every 1[mS] and increment global variables                //
// Use less priority than INT0 - used to detect edges of morse pulses         //
void Timer1Interrupt() iv IVT_TIMER_1 ilevel 6 ics ICS_AUTO {                 //
  T1IF_bit  = 0;            // clear interrupt                                //
  lo_time++;                                                                  //
  hi_time++;                                                                  //
  LATA0_bit = ~LATA0_bit;   // flash LED                                      //
}                                                                             //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// INT0 set up with priority and edge (rise=1 fall=0), flag and enable        //
void InitINT0(){                                                              //
    INT0IP2_bit = 1;    // set up INT0 to  priority 7                         //
    INT0IP1_bit = 1;                                                          //
    INT0IP0_bit = 1;                                                          //
    INT0EP_bit  = 1;    // rising edge                                        //
    INT0IF_bit  = 0;    // clear flag                                         //
    INT0IE_bit  = 1;    // enable interrupt                                   //
}                                                                             //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// INT0 interrupt of alternating rising and falling edge on RD0 pin           //
// Use TIMER1 interrupts to measure hi_time and lo_time in [mS]               //
void INT0Interrupt() iv IVT_EXTERNAL_0 ilevel 7 ics ICS_AUTO {                //
    INT0IF_bit = 0;                                                           //
    INT0IE_bit = 0;          // disable interrupt                             //                                           //
    if(PORTD.B0==1){                                                          //
        got_hi_time = FALSE;                                                  //
        got_lo_time = TRUE;                                                   //
        hi_time     = 0;                                                      //
        LATA1_bit   = 1;     // LED on                                        //
        INT0EP_bit  = 0;     // next interrupt on falling edge                //
    }                                                                         //                                                       //
    else{                                                                     //
        got_hi_time = TRUE;                                                   //
        got_lo_time = FALSE;                                                  //
        lo_time     = 0;                                                      //
        LATA1_bit   = 0;     // LED off                                       //
        INT0EP_bit  = 1;     // next interrupt on rising edge                 //
    }                                                                         //
    INT0IE_bit = 1;          // enable interrupt                              //
}                                                                             //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Calculate average of all hi pulses. DIT=1, DAH=3, Average = 2              //
// Use 32 pulses so average may be found by right shifting only               //
unsigned int Moving_Average(unsigned int next_number){                        //
    static unsigned int average_array[31] = {0}; // average over 32 pulses    //
    static unsigned short     index = 0;                                      //
    static unsigned long        sum = 0;                                      //
    // Subtract the oldest number from the prev sum, add the new number       //
    sum = sum - average_array[index] + next_number;                           //
    // Assign the next_number to the position in the array                    //
    average_array[index] = next_number;                                       //
    index++;                                                                  //
    if (index >= 31)index = 0;                                                //
    // Return the average. Same as total>>5 or divide by 32                   //
    return sum>>5;                                                            //
}                                                                             //
////////////////////////////////////////////////////////////////////////////////

void main(){
    // Block variables
    // Comma count         1    2    3   4 5    6   7    8  9    10  11   12 13 14  15   16   17  18  1819  20  21   22   23   24
    // Morse code       A  B    C    D   E F    G   H    I  J    K   L    M  N  0   P    Q    R   S   T U   V    W   X    Y    Z
    // Dits and Dahs    01,1000,1010,100,0,0010,110,0000,00,0111,101,0100,11,10,111,0110,1101,010,000,1,001,0001,011,1001,1011,1100
    unsigned char DECODE1[] = {69,84};                   // E,T with 1 dit or dah
    unsigned char DECODE2[] = {73,65,78,77};             // I,A,N,M with 2 dits or dahs
    unsigned char DECODE3[] = {83,85,82,87,68,75,71,79}; // S,U,R,W,D,K,G,O 3 dits or dahs
    unsigned char DECODE4[] = {72,86,70,99,76,99,80,74,66,88,67,89,90,81};  // H,V,F, ,L, ,P,J,B,X,C,Y,Z,Q 4 dits or dahs
    unsigned int commas        = 0;    // commas until desired morse code
    unsigned int comma_count   = 0;    // commas so far
    unsigned short     decimal = 0;    // conversion binary to digital, eg 1101=13, picks out 'Q' in DECODE4[]
    unsigned char     in_string[5];    // holds 0's and 1's of a received morse letter
    unsigned int        length = 0;    // number if 1's or 0's in the letter
    unsigned int             x = 0;    // general counter
    unsigned int  average_time = 1;    // average of last 32 pulses
    //unsigned char     my_string[6];    // conversion of unsigned int to string
    // Init MCU
    AD1PCFG = 0xFFFF;       // configure all pins digital
    JTAGEN_bit = 0;         // disable JTAG
    // Use advanced init for UART2 because peripheral bus is set to div8
    // frequency[kHz] should be 10,000 but that does not work???
    UART2_Init(115200);
    Delay_ms(100);          // wait for UART module to stabilize
    UART2_Write_Text("\r\nWVL_Fusion_Morse_Rx2\r\n");
    // Init pins, A0 for 1mS TIMER 1, A1 for high state
    TRISA = 0;              // all outputs for LEDs
    TRISD0_bit = 1;         // input for INT0 and morse pulses
    // Check port a leds working
    for(x=0;x<10;x++){LATA = ~LATA;delay_ms(100);}
    LATA = 0;

    InitINT0();             // on RD0
    InitTimer1();           // set to interrupt every 1[mS]
    got_hi_time = FALSE;
    got_lo_time = FALSE;
    EnableInterrupts();
    x = 0;
    
    // Collect 50 pulse times (dit=1 or dah=3) and average (average=2)
    while(x<50){
        if(got_hi_time==TRUE){
                got_hi_time=FALSE;
                Printout(U2,"%u\thi_time:%u\tMoving_Average:%u\r\n",x,hi_time,Moving_Average(hi_time));
                x++;
        }
    }

    while(1){
        if(got_hi_time==TRUE){
            got_hi_time=FALSE;
            // use latest hi_time to update moving average of all high pulses
            average_time = Moving_Average(hi_time);
            if(hi_time>average_time){        // it's a dah = 1!
                in_string[length] = '1';
            }
            else{                            // it's a dit = 0!
                in_string[length] = '0';
            }
            length++;
        }
        if(got_lo_time==TRUE){
            got_lo_time=FALSE;
            if(lo_time>average_time){        // inter-letter time so DECODE
                // Convert receives 0s and 1s to decimal
                switch (length){
                    case 1: decimal = in_string[0]-48;
                            UART2_Write(DECODE1[decimal]);
                            break;
                    case 2: decimal = in_string[0]-48;
                            decimal = decimal*2 + (in_string[1]-48);
                            UART2_Write(DECODE2[decimal]);
                            break;
                    case 3: decimal = in_string[0]-48;
                            decimal = decimal*2 + (in_string[1]-48);
                            decimal = decimal*2 + (in_string[2]-48);
                            UART2_Write(DECODE3[decimal]);
                            break;
                    case 4: decimal = in_string[0]-48;
                            decimal = decimal*2 + (in_string[1]-48);
                            decimal = decimal*2 + (in_string[2]-48);
                            decimal = decimal*2 + (in_string[3]-48);
                            UART2_Write(DECODE4[decimal]);
                }
                length = 0;
            }
            // Insert a space if lo_time is > 6*dits or 3*Average
            if(lo_time>=3*average_time) UART2_write(32);
            // New line if time_low is > 12 dits or 6*Average
            if(lo_time>6*average_time) UART2_Write_Text("\r\n");
        }
    }

}

User avatar
filip
mikroElektronika team
Posts: 11874
Joined: 25 Jan 2008 09:56

Re: Morse Code Transmitter and Receiver - Working Code

#2 Post by filip » 31 May 2023 07:39

Hi,

Thank you for your contribution!

Regards,
Filip.

jessepinkman
Posts: 1
Joined: 21 Mar 2024 09:05

Re: Morse Code Transmitter and Receiver - Working Code

#3 Post by jessepinkman » 21 Mar 2024 09:10

This is really cool. Your code sounds very well-structured, especially the way you handle different speeds in the receiver. I'm eager to build this myself.

Post Reply

Return to “User Projects”