123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- /* -*- c -*- */
- /* Copyright: (C) 2005 - 2017 by Dipl.-Ing. Stefan Heesch
- * callsign: HB9TWS
- * email: radio@heesch.net
- */
- /* Morse drill addition: March 2019
- * Chris Baird VK2CJB <cjb@brushtail.apana.org.au>
- *
- * Usage:
- *
- * Straight key support only.
- * Holding down the key during power-up/reset starts the trainer
- * in Stefan's original 'decode only' mode.
- * Otherwise, it shows a pseudo-random word from a "100 most common
- * words in CW" list I found. Complete it correctly to get another
- * one. (And you just have to manage the letters, not the whole word
- * perfectly.) Repeat. Repeat. Repeat. Repeat...
- *
- * To change the testing pool of words, bang in "TEST", and then a
- * number --
- * 1 = "100 common CW words"
- * 2 = Single letters and digits
- * 3 = 8-letter uncommon-ish words
- * 4 = single letters and digits, with the morse code displayed
- * 5 = punctuation, with the morse (what I'm currently studying..)
- * 6 = a poem.. (that is some English text)
- * 7 = pseudo-callsigns
- * 0 = decode only mode
- *
- * Changing the sidetone is "nnFRQ", where nn is the frequency/100,
- * "06FRQ" for example makes it 600 Hz.
- *
- * "nnWPM" enters 'copy mode', where the trainer beeps out words from
- * the current test pool at the given nn-digits speed, and you would
- * be doing something like try to write them down. The included poem
- * is useful here. Hold down the key to exit.
- */
- #include <LiquidCrystal.h>
- #include <EEPROM.h>
- #include "morse.h"
- LiquidCrystal lcd(9, 8, 5, 4, 3, 2);
- int buzzer = A5;
- int morsekey = A4;
- int buzzerfreq = 700;
- #define KEY_DELAY 15 /* debounce limit, milliseconds */
- #define LINE_LENGTH 16
- cw m_Signal;
- int m_KeyState;
- int m_LastKeyState;
- char buffer[LINE_LENGTH+1];
- char m_Line[LINE_LENGTH+1];
- uint8_t m_Position;
- char last = ' ';
- /* ---------------------------------------------------------------------- */
- extern void morse_init (int speed);
- extern char morse_decode (cw input);
- extern char* morse_failed (void);
- extern int morse_speed (void);
- extern char morse_check (cw input);
- extern int morse_timeout (void);
- void newtestword (void);
- void flushline (void);
- void updatetestline (void);
- void show (char s);
- void copystring (char* s);
- void copymode (void);
- void decode ();
- int readmorsekey (void);
- void setup ();
- void loop ();
- /* ---------------------------------------------------------------------- */
- // Note the trailing spaces in the strings so that they concatenate
- const char commontext[] PROGMEM =
- "QRL QRM QRN QRQ QRS QRZ QTH QSB QSY "
- "TU TNX NAME RST CQ AGN ANT DX ES HI OM PSE PWR WX 73 "
- "ABOUT ALL AM AN AND ANY ARE AS AT BE BEEN BEFORE BUT BY CAN COME "
- "COULD DO DONT EVERY FIRST FOR FROM GO GREAT HAD HAS HAVE HE HER HIM "
- "HIS HOW IF IN INTO IS IT ITS JUST KNOW LIKE LITTLE MADE MAN MAY ME "
- "MEAN MEN MORE MUST MY NO NOT NOW OF OH ON ONE ONLY OR OTHER OUR OUT "
- "OVER PEOPLE REALLY RIGHT SAID SAY SHALL SHE SHOULD SO SOME SUCH THAN "
- "THAT THATS THE THEIR THEM THEN THERE THESE THEY THINK THIS TIME TO "
- "TWO UH UP UPON US VERY WAS WE WELL WERE WHAT WHEN WHERE WHICH WHO "
- "WILL WITH WORK WOULD YEAH YOU YOUR "
- "123 1971 1980 2011 1955 1945 812837 73 "
- "BENSBEST WIRE SISTER";
- const char longtext[] PROGMEM =
- "AFFECTED AFLUKING AHANKARA AMMONIUM BEROIDAE BLAHLAUT "
- "CLOWNERY COERCION DEJECTED DICLINIC DIDYMATE ECHOWISE "
- "ERGATOID EXCRESCE FASCISTA FJERDING FUNMAKER GEOBLAST "
- "HELICOID HERMITRY INERTIAL LENINITE LUTETIAN LYREBIRD "
- "MURRELET OROMETRY OUTVALUE OVEREDIT PANORAMA PEYERIAN "
- "REEDBUCK SKELPING SUBTOTAL SUITHOLD TOTAQUIN UNCOATED "
- "UNHERDED BETATRON BREWSTER CADUCARY DIPHENOL EMPOCKET "
- "FEEDBACK FRONDOUS HERODIAN IRONCLAD IZCATECO KAMMALAN "
- "NODICORN ORYZOMYS OXYUROUS PORINGLY RACHITIS ROSEMARY "
- "SHAVIANA SILICIUM STICKLER STRUMPET SYLPHISH THICKETY "
- "TURBINED TYRANNUS UNDERLAY UNHEIRED UNHINTED UPSTROKE";
- const char singletext[] PROGMEM =
- "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "
- "0 1 2 3 4 5 6 7 8 9";
- const char singletextcheat[] PROGMEM =
- "A_.- B_-... C_-.-. D_-.. E_. F_..-. G_--. H_.... I_.. J_.--- K_-.- "
- "L_.-.. M_-- N_-. O_--- P_.--. Q_--.- R_.-. S_... T_- U_..- V_...- "
- "W_.-- X_-..- Y_-.-- Z_--.. 0_----- 1_.---- 2_..--- 3_...-- 4_....- "
- "5_..... 6_-.... 7_--... 8_---.. 9_----.";
- const char symboltext[] PROGMEM =
- ",_--..-- ._.-.-.- ?_..--.. /_-..-. =_-...- -_-....- :_---... "
- ";_-.-.-. (_-.--. )_-.--.- '_.----. \"_.-..-. @_.--.-.";
- const char lawson[] PROGMEM =
- "ABOVE THE ASHES STRAIGHT AND TALL THROUGH FERNS WITH MOISTURE DRIPPING "
- "I CLIMB BENEATH THE SANDSTONE WALL MY FEET ON MOSSES SLIPPING "
- "LIKE RAMPARTS ROUND THE VALLEYS EDGE THE TINTED CLIFFS ARE STANDING "
- "WITH MANY A BROKEN WALL AND LEDGE AND MANY A ROCKY LANDING "
- "AND ROUND ABOUT THEIR RUGGED FEET DEEP FERNY DELLS ARE HIDDEN "
- "IN SHADOWED DEPTHS WHENCE DUST AND HEAT ARE BANISHED AND FORBIDDEN "
- "THE STREAM THAT CROONING TO ITSELF COMES DOWN A TIRELESS ROVER "
- "FLOWS CALMLY TO THE ROCKY SHELF AND THERE LEAPS BRAVELY OVER "
- "NOW POURING DOWN NOW LOST IN SPRAY WHEN MOUNTAIN BREEZES SALLY "
- "THE WATER STRIKES THE ROCK MIDWAY AND LEAPS INTO THE VALLEY "
- "NOW IN THE WEST THE COLOURS CHANGE THE BLUE WITH CRIMSON BLENDING "
- "BEHIND THE FAR DIVIDING RANGE THE SUN IS FAST DESCEND "
- "AND MELLOWED DAY COMES OVER THE PLACE AND SOFTENS RAGGED EDGES "
- "THE RISING MOONS GREAT PLACID FACE LOOKS GRAVELY OVER THE LEDGES "
- "= BLUE MOUNTAINS BY HENRY LAWSON 1888.";
- const char callsigntemplate[] PROGMEM =
- "@n@@ @n@@@ n@n@@@ n@@n@@ @@n@@ @@n@@@ @@nn@@ "
- "VKn@@@ VKn@@ VKnF@@@ ZLn@@ ZLn@@@";
- const char* const texts[] PROGMEM =
- {
- 0, commontext, singletext, longtext, singletextcheat, symboltext,
- lawson, callsigntemplate
- };
- #define numtexts (sizeof(texts)/sizeof(char*))
- char testword[12];
- char testpnt;
- unsigned char testmode = 1;
- /* ---------------------------------------------------------------------- */
- /* pick a ~random~ word in a space-delimited string */
- unsigned int lp = 0;
- void newtestword (void)
- {
- unsigned int f = pgm_read_word(&texts[testmode]);
- int i = 1;
- if (testmode != 6) /* make the poem go sequentially */
- i += millis() & 63;
- for (; i; i--)
- {
- char c;
- int j = 0;
- while ((c = pgm_read_byte(f+lp)) > ' ')
- {
- testword[j++] = c;
- lp = (c ? lp+1 : 0);
- }
- testword[j] = 0;
- lp = (c == ' ' ? lp+1 : 0);
- }
- if (testword[1] == '_') testword[1] = 32;
- if (testmode == 7)
- {
- randomSeed (millis());
- for (i = 0; testword[i]; i++)
- {
- if (testword[i] == '@')
- do testword[i] = 65 + (random() % 26);
- while (testword[i] == 'Q'); /* most countries avoid it */
- if (testword[i] == 'n')
- testword[i] = 48 + (random() % 10);
- }
- }
- testpnt = 0;
- }
- /* ---------------------------------------------------------------------- */
- void flushline (void)
- {
- for (int i = 0; i < LINE_LENGTH-1; i++)
- m_Line[i] = ' ';
- m_Position = LINE_LENGTH-1;
- }
- /* ---------------------------------------------------------------------- */
- void updatetestline (void)
- {
- if (testmode)
- {
- int p;
- lcd.setCursor(0, 0);
- for (p = 0; testword[p]; p++)
- lcd.write((char)testword[p]);
- while (p++ < (LINE_LENGTH-6))
- lcd.write(' ');
- lcd.cursor();
- lcd.setCursor(testpnt, 0);
- }
- else
- lcd.noCursor();
- }
- /* ---------------------------------------------------------------------- */
- void show (char s)
- {
- m_Line[m_Position] = s;
- if (++m_Position > LINE_LENGTH-1)
- {
- for (int i = 0; i < LINE_LENGTH-1; i++)
- m_Line[i] = m_Line[i + 1];
- m_Line[LINE_LENGTH-1] = ' ';
- m_Position = LINE_LENGTH-1;
- }
- lcd.setCursor(0, 1);
- lcd.print(m_Line);
- updatetestline();
- }
- /* ---------------------------------------------------------------------- */
- int copywpm = 10;
- /* Idea from 'Efficient Storage Of Morse Code Character Codes',
- * Lawrence Krakauer, page 36, Issue 14, BYTE 1976-10 */
- const unsigned char morse_table[] PROGMEM =
- {
- 'A', 0b110, 'B', 0b10001, 'C', 0b10101, 'D', 0b1001,
- 'E', 0b10, 'F', 0b10100, 'G', 0b1011, 'H', 0b10000,
- 'I', 0b100, 'J', 0b11110, 'K', 0b1101, 'L', 0b10010,
- 'M', 0b111, 'N', 0b101, 'O', 0b1111, 'P', 0b10110,
- 'Q', 0b11011, 'R', 0b1010, 'S', 0b1000, 'T', 0b11,
- 'U', 0b1100, 'V', 0b11000, 'W', 0b1110, 'X', 0b11001,
- 'Y', 0b11101, 'Z', 0b10011, '0', 0b111111, '1', 0b111110,
- '2', 0b111100, '3', 0b111000, '4', 0b110000, '5', 0b100000,
- '6', 0b100001, '7', 0b100011, '8', 0b100111, '9', 0b101111,
- ',', 0b1110011,'.', 0b1101010,'?', 0b1001100,'/', 0b101001,
- '=', 0b110001, '-', 0b1100001,':', 0b1000111,';', 0b1010101,
- '(', 0b101101, ')', 0b1101101,'\'',0b1011110,'"', 0b1010010,
- '@', 0b1010110,
- 0, 0
- };
- void copystring (char* s)
- {
- int dit = 2000 / copywpm;
- while (*s)
- {
- int i;
- if (*s == '_') break;
- for (i = 0; pgm_read_byte(&morse_table[i]); i += 2)
- if (pgm_read_byte(&morse_table[i]) == *s) break;
- if (pgm_read_byte(&morse_table[i]))
- {
- unsigned char b = pgm_read_byte(&morse_table[i+1]);
- while (b != 1)
- {
- tone (buzzer, buzzerfreq);
- if (b & 1) delay (3*dit); /* do dash (3 dits) */
- else delay (dit); /* do dit (1 dit) */
- noTone (buzzer);
- delay(dit); /* do inter-symbol delay (1 dit) */
- b >>= 1;
- }
- }
- else
- delay (5*dit); /* assume it's a space (7-2 dits) */
- delay (2*dit); /* do intra-letter space (3-1 dits) */
- s++;
- }
- }
- void copymode (void)
- {
- do
- {
- lcd.clear();
- flushline();
- lcd.noCursor();
- lcd.print(F("COPY.KEY TO EXIT"));
- newtestword();
- copystring(testword);
- for (int i = 0; testword[i]; i++)
- {
- if (readmorsekey()) break;
- delay(750);
- }
- }
- while (readmorsekey()== 0);
- }
- /* ---------------------------------------------------------------------- */
- void decode()
- {
- static char cmdbuf[5];
- cw input;
- input.level = m_Signal.level;
- input.duration = m_Signal.duration;
- char s = morse_decode(input);
- if (s > 0)
- {
- last = s;
- switch (s)
- {
- case 'a': show('a'); show('r'); break;
- case 'k': show('k'); show('a'); break;
- case 's': show('s'); show('k'); break;
- case DECODING_ERROR: /*show('_');*/ break;
- default: show(s); break;
- }
- if (s != ' ')
- {
- lcd.setCursor(0, 0);
- if (s == DECODING_ERROR)
- {
- for (char *p = morse_failed(); *p; p++)
- show(*p);
- }
- else
- {
- sprintf(buffer, " %2d wpm", morse_speed() / 5);
- lcd.print(buffer);
- updatetestline();
- }
- }
- /* command trigger */
- for (int i = 4; i; i--) cmdbuf[i] = cmdbuf[i-1];
- cmdbuf[0] = s;
- if ((cmdbuf[4] == 'T') && (cmdbuf[3] == 'E') && (cmdbuf[2] == 'S') &&
- (cmdbuf[1] == 'T'))
- {
- if ((s > 47) && (s < (48+numtexts)))
- {
- delay (50);
- testmode = s - 48;
- updateeeprom();
- lp = 0;
- newtestword();
- tone (buzzer, 800); delay (100);
- tone (buzzer, 1600); delay (100);
- noTone(buzzer);
- lcd.clear();
- if (testmode == 0) lcd.print (F("DECODE MODE"));
- flushline();
- }
- }
- /* copy mode */
- if ((cmdbuf[2] == 'W') && (cmdbuf[1] == 'P') && (cmdbuf[0] == 'M'))
- {
- copywpm = (cmdbuf[4]*10) + cmdbuf[3] - 480 - 48;
- if ((copywpm > 4) && (copywpm < 41))
- copymode();
- }
- /* change sidetone */
- if ((cmdbuf[2] == 'F') && (cmdbuf[1] == 'R') && (cmdbuf[0] == 'Q'))
- {
- int i = (cmdbuf[4]*10) + cmdbuf[3] - 480 - 48;
- if ((i > 1) && (i < 30))
- {
- delay (50);
- tone (buzzer, 1000); delay (50);
- tone (buzzer, 900); delay (50);
- noTone(buzzer);
- buzzerfreq = i * 100;
- updateeeprom();
- }
- }
- /* jym joke */
- if ((cmdbuf[4] == 'R') && (cmdbuf[3] == 'U') && (cmdbuf[2] == 'O') &&
- (cmdbuf[1] == 'K') && (cmdbuf[0] == '?'))
- {
- copywpm = morse_speed() / 5;
- copystring ("HUG ME");
- }
- /* completed */
- if ((testmode) && (s == testword[testpnt]))
- {
- testpnt++;
- if (testword[testpnt] < 33)
- {
- delay (50);
- tone (buzzer, 1200); delay (50);
- tone (buzzer, 1600); delay (50);
- noTone(buzzer);
- newtestword();
- flushline();
- }
- }
- }
- /* Update speed and check for new word */
- s = morse_check(input);
- if (s == ' ' && last != ' ' && m_Position > 0)
- {
- last = s;
- show(s);
- }
- }
- /* ---------------------------------------------------------------------- */
- int readmorsekey (void)
- {
- return digitalRead(morsekey) > 0 ? 0 : 1;
- }
- /* ---------------------------------------------------------------------- */
- void updateeeprom (void)
- {
- EEPROM.write(0,testmode);
- EEPROM.write(1, buzzerfreq >> 8);
- EEPROM.write(2, buzzerfreq & 255);
- }
- void setup()
- {
- testmode = EEPROM.read(0);
- buzzerfreq = (EEPROM.read(1) << 8) + EEPROM.read(2);
- if (testmode > numtexts) testmode = 1;
- if ((buzzerfreq < 100) || (buzzerfreq > 3000)) buzzerfreq = 700;
- for (int i = 0; i<LINE_LENGTH-1; i++)
- {
- buffer[i] = 0;
- m_Line[i] = ' ';
- }
- buffer[LINE_LENGTH - 1] = 0;
- m_Line[LINE_LENGTH - 1] = 0;
- flushline();
- digitalWrite(morsekey, HIGH);
- pinMode(morsekey, INPUT_PULLUP);
- if (readmorsekey())
- testmode = 0; /* key down -> start with original decoder */
- lcd.begin(LINE_LENGTH, 2);
- lcd.setCursor(0, testmode ? 0 : 1);
- lcd.print(F(" Morse Trainer"));
- lcd.setCursor(0, testmode ? 1 : 0);
- lcd.print(F("HB9TWS & VK2CJB"));
- tone(buzzer, 324); delay(300);
- tone(buzzer, 648); delay(300);
- tone(buzzer, 1292); delay(300);
- noTone(buzzer); delay(1500);
- morse_init(60);
- while (readmorsekey()) continue; /* key up before continuing */
- m_KeyState = 0;
- m_LastKeyState = 0;
- m_Signal.level = 0;
- m_Signal.duration = 0;
- updateeeprom();
- lcd.clear();
- newtestword();
- updatetestline();
- if (testmode == 0) lcd.print (F("DECODE MODE"));
- }
- /* ---------------------------------------------------------------------- */
- void loop()
- {
- static unsigned long m_Start = 0;
- static unsigned long m_DebounceTime;
- unsigned long now = millis();
- if (m_Start == 0)
- {
- m_Start = now;
- m_DebounceTime = now;
- return;
- }
- m_KeyState = readmorsekey();
- if (m_KeyState != m_LastKeyState)
- m_DebounceTime = millis();
- if (now - m_DebounceTime > KEY_DELAY)
- {
- if (m_Signal.level != m_KeyState)
- {
- m_Signal.duration = now - m_Start;
- decode();
- m_Signal.level = m_KeyState;
- m_Start = now;
- }
- if (now - m_Start > morse_timeout() && m_Signal.level == 0)
- {
- m_Signal.duration = now - m_Start;
- decode();
- m_Start = now;
- }
- }
- /* side tone */
- m_LastKeyState = m_KeyState;
- if (m_KeyState == 1)
- tone(buzzer, buzzerfreq);
- else
- noTone(buzzer);
- }
- /* ---------------------------------------------------------------------- */
|