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