An Arduino Morse Reader

Download as pdf or txt
Download as pdf or txt
You are on page 1of 4

Carrying on the Practical Way by Tony Jones G7ETW

• E-mail: [email protected]

W An Arduino
elcome to my second
Arduino project,
a Morse reader I
call ARD071, Fig. 1.

Morse Reader
Writing this, I’m glad
I developed my Morse sender (ARD070,
PW November) first because this is much
more complicated. A Morse reader has to
process an audio stream first into dots and
dashes, then group them into single Morse
characters, then translate those into text
and display the output − all in real time! Tony Jones G7ETW follows up his Morse Tutor project
To understand this, take a look at Fig.
2. Morse is binary; a low-to-high change (November 2017) with an Arduino-based Morse reader.
means a character element (a dot or a
dash) has just started, and the converse
signifies one has finished. By timing the
states, character elements can be identi-
fied. An Arduino read takes about 1ms,
and at 20 Words per Minute (WPM) a dot
lasts 60ms, so this is all very doable. The
detailed logic is explained in the Decoding
sidebar.
I got ARD071 working using a digital
data stream first. I connected ARD070’s
pin 13 − the LED flasher output − to
ARD071’s digital pin 3. This worked per-
fectly and allowed me to refine the detec-
tion, decoding and display logic with solid
binary input. To see one Arduino sending
random text and another one displaying it
a second later (look for the ‘HHQ’ series in
Fig. 1) was quite spooky, actually. Why the
delay? ARD070 displays text in five-char-
acter groups before it sends it whereas
ARD071 has to wait until each character is
finished before decoding can begin.
A digital-feed only Morse reader was
not the objective, so I reconfigured for an Fig. 1: The completed project in breadboard form.
analogue waveform.
To test, I used ARD070 again, taking
the pulse-width modulated (PWM) audio Dash Dot Dot Dot
output (pin 11) to ARD071’s pin A0 and
found that incoming Morse, regardless of
Dot gap

speed, was displaying as endless letter Es


arriving at 1200 WPM! The reason is sim-
ple − with symmetrical PWM like this, half
of each pulse-cycle is zero. An Arduino A B C D E F G H I J K
analogue Read consequently stands a 3-dot
gap
50% chance of returning zero and mis-
detecting the end of a dash or dot.
Some audio processing was required Fig. 2: Processing the Morse code for ‘DE’ in the input stream. See Decoding sidebar for detailed
and, for simplicity, I used a Lowpass RC explanation.
filter − see Sidebar 2. Figs. 3a and 3b
show how the filter allows the envelope of who has ever repaired one knows, the reversed!
the signal to be passed. The overall level D70 has an odd design in that the ‘ring’ But does ARD071 work with real
is reduced (5V to 2V), but we still get a connection on the speaker jack socket Morse, on air? In all honesty, I don’t know.
definite difference between highs and lows is at 5V and the ‘tip’ output is a series of I have tried it but I live in a flat and I don’t
so all is well. ARD071 worked perfectly negative pulses, Fig. 3c. Arduinos read have external antennas, so the signals I re-
with this in place. only positive voltages, so to get audio in ceived were too noisy. I am confident it will
I tried a Datong D70 next. As anyone from a D70, the ground and signal must be work, though, with a louder, cleaner signal.

40 Practical Wireless March 2018

COTPW 4-5 pages.indd 40 18/01/2018 10:38


Fig. 3a: The raw PWM output from the Ard70 on 3b: Signal from 3a after the RC filter - the level 3c: The raw (negative-going) output from the
a 5V scale. is not dropping to zero in the envelope. Datong D70 on a 5V scale.

I look forward to doing more testing. go back to using a digital input. But that is Conclusion
another project. That’s it for ARD071. It works really well
The Hardware Isolation for such simple code and is a joy to watch
I used an Arduino Mega2560 board this ARD071 has to be connected to the audio- running. Since starting in Arduino develop-
time, again a genuine one. This board has out socket of what’s driving it. It would be ment, I have learned a lot and I have other
four times as much memory as an Uno better, for both pieces of kit, if they were projects in mind, for example, a laser-op-
and over twice as many pins, but my main isolated. Opto-isolators using infra-red erated remote antenna switcher. It is great
reason for using it was simply to show LEDs and phototransistors are available fun combining IT and amateur radio in this
another Arduino variant. An Uno should be and would provide valuable protection. way and I urge you to have a go.
fine for this project. Slave Audio Out
A sixteen (char) by two (row) display If ARD071 is plugged into a headphone
was adequate for ARD070, which only had socket, there is no sound to hear. It would
to display five characters at a time. For be easy to put a sounder in, driven off a Decoding
ARD071 this would have been very limit- PWM output pin. There are even speech Look at point A on Fig. 2. We are at
ing, so I used a Sainsmart 20 character by boards for Arduino, so ARD071 could the end of a character-to-character
four row display I bought on Amazon for speak the message out loud, albeit as a three dot gap. The input has gone
£11.98. This is an I2C type display - see string of letters and numbers. To break it high, so a new character has begun.
Sidebar 3. into words − now that is a challenge! The gone-high time is stored and a
See Fig. 4 for a schematic. The build is Better Character Decoding series of reads return high. No action
very easy. There are three sync-indication Decoding at the moment is done by a is necessary for this character yet but
LEDs. LED1 shows the dots and dashes sequential array search. There is a ‘binary the last character read is stored as
coming in and LED3 echoes them when tree’ method that decodes characters in dots and dashes and still needs to be
the code has ‘locked on’. If LED2 is used, flight, for example, if two dots have al- decoded. For this explanation, let’s
it will be much fainter. ready been detected and the next element ignore that.
is another dot, then the only possibilities At point D the input has flipped
The Code are S, H, V, 4 and 5. This should be faster and the gone-low time is stored.
ARD071 V1.4 uses more advanced C syn- but would be quite complex to code. The time the waveform was high is
tax than ARD070 v3.5 (the version printed ARD071 can only handle A-Z and 0-9. calculated and by comparing this with
in PW) did, and features character pointers Adding punctuation and other procedural a dot’s length we know we just had a
and arrays. Pointers in C are generally characters would also be useful. dash. This becomes the first stored
used to pass values to and from functions Combined Single-Arduino Sender and element for this character.
and while this may seem more complex, it Reader Reads continue, returning lows.
actually simplifies the code. See Sidebar 4 The idea of a combined sender and reader We’re in a gap, either between char-
for a brief explanation. was suggested by a PW reader who acter elements or between charac-
e-mailed me. My thanks to all those who ters.
Improvements did contact me after that earlier article; it At point E the waveform goes
The following are possible areas for im- is always nice to get reader feedback and high again. The low gap’s duration is
provement. that particular idea is an excellent one. The calculated and the code finds it was
Ambient Sound Input data structures and variables are compat- a dot. We’re still in this character and
ARD071 does not take a microphone in- ible and some of the code such as the another element is starting.
put. I did try to get this working, using Ar- display logic could be common. The cycle repeats and as we go
duino electret microphone boards and my A rotary encoder would be needed for through F, G and H we piece together
own LM358 microphone preamplifiers. But controls (to free up analogue pins A4 and dash dot dot.
electret microphones are just too sensitive A5 - see Sidebar 3 again) and that would We’re at point K now. This time the
and even in a quiet room the background make the final product smaller and more low gap was three dots long so K is
noise pickup is too much for the beeps to modern. the start of a new character. The dash
stand out enough. A custom board would be nice too. dot dot we have stored is decoded
I think the answer is to make an exter- Sometimes I feel I‘ve unleashed a into N and displayed.
nal CW digitiser, which would allow me to monster...

March 2018 Practical Wireless 41

COTPW 4-5 pages.indd 41 18/01/2018 10:39


Carrying on the Practical Way

RC Lowpass Filter +5V


This is a ‘first order’ filter because it 5V

has one stage.


As the incoming frequency rises,
Arduino
the reactance of the capacitor drops Mega2560
and the signal is increasingly ground- 5V
ed. At low frequencies the reactance
SDA SDA 20 column x 4 row LCD with I2C
is very high so the voltage at the out- SCL SCL
interface
Low-pass filter
put is much nearer to that at the input.
0V
The formula to determine the cut- R1
2k2
off frequency is: A0 D12

f = 1 / (2 ×π × R × C) C1
0V
1µ All LEDs are 5V types (with internal resistor)
R is 2.2kΩ and C is 1µF, giving a LED1 LED2 LED3 0V All are optional, though LED3 is highly recommended
cut-off frequency of about 70Hz. This
was a pretty arbitrary decision − it just
needed to be low compared to the Fig. 4: Overall schematic, with Arduino and display.
PWM frequency (around 700Hz).
See Useful Links for an online RC Useful Links:
filter calculator. Lowpass Filter calculator: C-pointers: Dan Gookin’s Beginning
https://tinyurl.com/ya2t7auf Programming with C, chapters 18 and 19,
I2C: https://tinyurl.com/y8j6uwv9 I found very good. If you want more of a
LiquidCrystal_I2C.h library: reference (no pun intended), there is:
https://tinyurl.com/p2uzjw8 http://boredzo.org/pointers
I2C LCD Displays
I2C, short for ‘Inter-Integrated Circuit’
was developed by Philips in the 1970s C Pointers p1 = &x
as a way to network boards under Explaining pointers is not easy. I shall do p2 = &y
central control, each of them able to my best. This sets p1 to the location of x and
send and receive data via a ‘bus’. Every variable has its own memory p2 to y’s location.
An I2C display connects to Arduino location. In C parlance, p1 ‘points to’ x.
using four standard connections: 5V, Say there are three integer variables p1 has a value of 100.
ground, SDA (data) and SCL (clock). x, y and z at locations 100, 200 and 300. *p1 has a value of 10.
On an Arduino Uno, SDA and SCL Say there are two integer pointer Do not read on until that makes
also connect to analogue pins A4 and variables p1 and p2. (Tutorials often sense. Conceptually, that is all there is
A5, as a PW reader painfully dis- call these px and py, giving the impres- to pointers.
covered when using one to build his sion that the variable names matter. z = *p1
ARD070. They don’t. Types do but that’s another This sets z to 10.
Boards connected to an I2C bus minefield.) *p1 = *p2
system have unique addresses. My An integer pointer is a pointer that This makes x = y
LCD display’s address is hex 27 − it holds the address of another integer. *p2 = z
was documented in the Amazon list- There are two pointer ‘operators’, This sets y to z, which is what x had
ing! This seems to be standard but it’s which allow programmers to do things before we started. After these instruc-
wise find this out when purchasing a with pointers: tions have taken place x, y and z have
display. • The ampersand ‘address of’ operator values of 20, 10 and 10. Precisely this
Please note the inclusion of the gives access to a variable’s location and code can be found in sort routines.
Wire.h and LiquidCrystal_I2c.h librar- is how pointers are setup. This could have been done far more
ies, which provide support for the • The asterisk ‘value at address’ opera- simply without pointers, of course. But
display. tor gives access to pointed-to variable’s moving data around takes process-
Wire.h is a standard Arduino value and can be used like peek and ing time. Three variables’ values have
library but LiquidCrystal_I2C.h is not. poke in BASIC. changed but only one actual move-
I learned of this by reading the Ama- All clear so far? ment of data − the setting of z − took
zon reviews of the display I bought. Here are some pointers in action. place. The other changes were achieved
Previous purchasers recommended a integers x, y and z exist with values by swapping pointers. If z had had a
library made available by F Malpar- of 10, 20 and 30. pointer too, all of this could have taken
tida and that’s what I used. Two pointers are declared. place with no data moving at all, which
Please look in Useful Links for Int * p1 is why so many C string functions use
more information on I2C and Liquid- Int * p2 pointers all the time.
Crystal_I2C.h (where to get it and how Their contents are indeterminate until Please see Useful Links for a much
to install it). they are initialised. better introduction to use of C pointers.

42 Practical Wireless March 2018

COTPW 4-5 pages.indd 42 18/01/2018 10:39


//------------------ start sketch ARD71 readtm = millis ();
// D71 sketch written by Tony Jones September 2017 if (prevmd != micdig) {
// V1.4 22 Nov 2017 switch (micdig) {
// if high, a gap has just ended.. if low, a dash or a dot has just ended
#include <Wire.h> case HIGH : {
#include <LiquidCrystal_I2C.h> if (wentlowwtm > 0) {
return (readtm - wentlowwtm);
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); } break;
}
const char * cwalph[] = {“.-”, // A,0th element case LOW : {
“-...”, // B if (wentlowwtm == 0) {
“-.-.”, // C wentlowwtm = readtm;
“-..”, // D } break;
“.”, // E }
“..-.”, // F } // end case
“--.”, // G prevmd = micdig; digitalWrite(cwledop, micdig);
“....”, // H } // end if
“..”, // I } //end while
“.---”, // J
“-.-”, // K }
“.-..”, // L //------------------ endd findgap
“--”, // M //------------------ start loop
“-.”, // N void loop()
“---”, // O {
“.--.”, // P int j, k, twodot;
“--.-”, // Q char segsch[6], showstr [LCDCHARS];
“.-.”, // R boolean micdig, prevmd;
“...”, // S unsigned long hightm, lowwtm, readtm, wenthightm, wentlowwtm;
“-”, // T
“..-”, // U prevmd = LOW;
“...-”, // V wenthightm = 0; wentlowwtm = 0;
“.--”, // W
“-..-”, // X strcpy(segsch, “”); j = -1; k = -1;
“-.--”, // Y
“--..”, // Z,25th element while (HIGH) {
“-----”, // 0,26th element micval = analogRead (A0); micdig = (micval > miclvl) ;
“.----”, // 1 readtm = millis ();
“..---”, // 2 if (prevmd != micdig) {
“...--”, // 3 switch (micdig) { // if high, a gap has just ended.. if low, a dash or a
“....-”, // 4 dot has just ended
“.....”, // 5 case HIGH : {
“-....”, // 6 wenthightm = readtm;
“--...”, // 7 lowwtm = readtm - wentlowwtm;
“---..”, // 8 if (wentlowwtm > 0 && lowwtm > twodotlen) {
“----.”}; // 9,35th element; segsch[++j] = ‘\0’;
// compiler substitutions LCDmanage (cwdecode (segsch)); j = -1; // cwdecode returns integer
eg E is 69
#define ASCAVAL 65 // decimal value of character ‘A’ in ASCII }
#define ASC0VAL 48 // decimal value of character ‘0’ in ASCII break;
#define CWPATTERNS 35 // A to Z, 0 to 9 makes 36 minus 1 because its an array }
#define NUMBERLETTERS 26 // A to Z case LOW : {
wentlowwtm = readtm;
#define TWOSEC 2000 hightm = readtm - wenthightm;
#define LCDCHARS 16 // I decided not use all the width - can’t remember why if (wenthightm > 0 && hightm > twodotlen) {
#define LCDTOTCHARS 20 // change LCDTOTCHARS just once to use different lcd display segsch[++j] = ‘-’;
#define LCDROWS 4 // change LCDROWS just once to use different lcd display } else {
segsch[++j] = ‘.’;
// constants }
const int cwledop = 12; break;
}
// changing global variables } // end case
unsigned long dotgaplen, longaplen, twodotlen; prevmd = micdig; digitalWrite(cwledop, micdig);
int miclvl, micval; } // end if
} // endwhile
char LCDline1 [LCDCHARS + 1] ;
char LCDline2 [LCDCHARS + 1] ; }// bot func
char LCDline3 [LCDCHARS + 1] ; //------------------ enddd loop

//------------------ start setup // ------------------begin called functions


void setup()
{ //------------------ start cwdecode
pinMode (cwledop, OUTPUT); int cwdecode( char * cwchar)
pinMode (A0, INPUT); { int x;

unsigned long gap; for (x = 0; x < CWPATTERNS; x++) {


int i, maxmic, minmic; if (strcmp(cwchar, cwalph [x]) == 0) {
break;
Serial.begin(9600); }
}
lcd.begin(LCDTOTCHARS, LCDROWS);
lcd.clear(); lcd.setCursor(0, 0); lcd.print(F(“D71 V1.4”)); if (x < NUMBERLETTERS) {
delay(TWOSEC); return x += ASCAVAL; // a letter
}
strcpy(LCDline1,” “); else {
strcpy(LCDline2,” “); return x += (ASC0VAL - NUMBERLETTERS); // a number
strcpy(LCDline3,” “); }

maxmic = 0; minmic = 2000; }


for (i=0; i<500; i++) { //------------------ enddd cwdecode
micval = analogRead (A0); delay(2); void LCDmanage (int nextchar)
if (micval > maxmic) { maxmic = micval; } {
if (micval < minmic) { minmic = micval; }
} char x = nextchar;
miclvl = 10 + (1.1 * minmic); // constant 10 needed when minmic is very low eg int linelen = strlen(LCDline1);
when reading off ARD070 float adjgap,spd;

dotgaplen = 30000; longaplen = 0; adjgap = (float) (longaplen / (3.0 * dotgaplen));


for (i = 0; i < 4; i++) {
gap = findgap(); if (linelen == LCDCHARS) {
if (gap < dotgaplen ) { strcpy(LCDline3, LCDline2);
dotgaplen = gap ; strcpy(LCDline2, LCDline1);
} // end if linelen = 0;
if (gap > longaplen ) { }
longaplen = gap ;
} // end if lcd.clear();
} // end for loop lcd.setCursor(0, 0); spd = float( 1200.0 / dotgaplen ); // 1200 ms is one dot
twodotlen = dotgaplen * 2; at 1 wpm
} lcd.print(spd, 1); lcd.print(F(“ wpm “));
//------------------ enddd setup lcd.print(F(“gap “)); lcd.print(adjgap, 1); lcd.print(F(“ dots”));
//------------------ start findgap
unsigned long findgap () LCDline1 [linelen] = x; LCDline1 [linelen + 1] = ‘\0’;
{
boolean micdig, prevmd; lcd.setCursor(0, 1); lcd.print(LCDline1);
unsigned long gap, lowwtm, readtm, wentlowwtm; lcd.setCursor(0, 2); lcd.print(LCDline2);
lcd.setCursor(0, 3); lcd.print(LCDline3);
prevmd = 0; wentlowwtm = 0;
int i; }

while (gap == 0) { //-------------------enddd called functions


micval = analogRead (A0); micdig = (micval > miclvl) ; //------------------ enddd sketch ARD71

Sketch Code: Here is the code. It is also available to download from my website.

March 2018 Practical Wireless 43

COTPW 4-5 pages.indd 43 18/01/2018 10:39

You might also like