// ****************************************
// Arduino Morse code Trainer by Tom Lewis, N4TL. February 21, 2016
// This sketch sends a few random Morse code characters, then waits for the student to send them back with an external keyer.
// If the same characters are sent back the sketch sends new random characters. If they are wrong the sketch sends the same characters over.
// The PS2 keyboard allows the user to change some operational parameters.
// See the CW Trainer article.docx file for more information.
// 73 N4TL
// Uses code by Glen Popiel, KW5GP, found in his book, Arduino for Ham Radio, published by the ARRL.
// Modified and added LCD display by Glen Popiel - KW5GP
// Uses Arduino Morse Library by Erik Linder SM0RVV and Mark VandeWettering K6HX
// Contact: sm0rvv at google mail. Released 2011 under GPLv3 Version 0.2
// Uses MORSE ENDECODER Library by raronzen
// Copyright (C) 2010, 2012 raron GNU GPLv3 license (http://www.gnu.org/licenses)
// Contact: [email protected] (not checked too often..)
// Details: http://raronoff.wordpress.com/2010/12/16/morse-endecoder/
// Uses the library for the Adafruit RGB 16x2 LCD Shield by Limor Fried/Ladyada
// for Adafruit Industries http://www.adafruit.com/products/714. BSD license.
// Oct. 2016 - Modified to replace PS-2 keyboard functions with LCD menus and buttons
// by Mike Hughes, KC1DMR. Latest source at https://github.com/mfhughes128/cw-trainer
// Updated 12-20-2017 by Mike Hughes, random number fix
// *****************************************/
#include <avr/pgmspace.h>
#include <EEPROM.h>
#include "./Morse.h"
#include "./MorseEnDecoder.h" // Morse EnDecoder Library
// #include <Adafruit_RGBLCDShield.h>
#include <LiquidCrystal_I2C.h>
// #include <utility/Adafruit_MCP23017.h>
#include <Adafruit_MCP23X17.h>
// These #defines make it easy to set the LCD backlight color
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#define WHITE 0x7
// Most I2C LCD's have an I2C Address of either 0x27 or 0x3F
// If the LCD doesn't work with one address, try the other
#define LCD_I2C_ADDRESS 0x27
// #define LCD_I2C_ADDRESS 0x3F
// Define the size of the LCD. Most LCD's are either 16x2 or 20x4
#define LCD_ROW_COUNT 4 // Number of Rows
#define LCD_COL_COUNT 20 // Number of Characters per Row
// Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); // LCD class
LiquidCrystal_I2C lcd(LCD_I2C_ADDRESS, LCD_COL_COUNT, LCD_ROW_COUNT);
char line_buf[17];
// Application preferences global
// Char set values:
// 1 = 26 alpha characters
// 2 = numbers
// 3 = punctuation characters
// 4 = all characters in alphabetical order
// 5 = all characters in Koch order. Two prefs set range-
// KOCH_NUM is number to use
// KOCH_SKIP is number to skip
// 6 = reserved
#define SAVED_FLG 0 // will be 170 if settings have been saved to EEPROM
#define GROUP_NUM 1 // expected number of cw characters to be received
#define GROUP_DLY 2 // delay before sending (in 0.01 sec increments)
#define KEY_SPEED 3 // morse keying speed (WPM)
#define CHAR_SET 4 // defines which character set to send the student.
#define KOCH_NUM 5 // how many character to use
#define KOCH_SKIP 6 // characters to skip in the Koch table
#define OUT_MODE 7 // 0 = Key, 1 = Speaker
#define NUM_PREFS 8 // number of entries in the preference list
byte prefs[NUM_PREFS]; // Table of preference values
//=========================================
// There is a document at the ARRL that tells how to measure CW speed by sending PARIS.
// The length of time it takes to send PARIS in seconds divided in to 60 gives the speed in WPM
// with the Key_speed_adj = -2,
// and the characters delay = 0, the measured output speed is
// 20 wpm measured to be 19.9 wpm
// 25 wpm measured to be 25.3 wpm
// 30 wpm measured to be 30.8 wpm
//
// and the characters delay = 10, the measured output speed is
// 20 wpm measured to be 17.7 wpm
// 25 wpm measured to be 21.6 wpm
// 30 wpm measured to be 25.6 wpm
//
// and the characters delay = 20, the measured output speed is
// 20 wpm measured to be 15.7 wpm
// 25 wpm measured to be 18.9 wpm
// 30 wpm measured to be 21.9 wpm
//=========================================
int Key_speed_adj = -2; // correction for keying speed
// IO definitions
const byte morseInPin = 2; // Pin for input
const byte beep_pin = 11; // Pin for CW tone
const byte key_pin = 12; // Pin for CW Key
//====================
// Setup Function
//====================
void setup()
{
// Start serial debug port
Serial.begin(9600);
while (!Serial);
Serial.println("N4TL CW Trainer");
// Start LCD
// lcd.begin(LCD_COL_COUNT, LCD_ROW_COUNT);
// lcd.setBacklight(WHITE);
lcd.init();
lcd.backlight();
lcd.clear();
lcd.setCursor(0, 0);
// Initialize application preferences
prefs_init();
} // end setup()
//====================
// Main Loop Function
//====================
void loop()
{
paris_test();
} // end loop()
//=====================================
// "PARIS" test routine
//=====================================
void paris_test()
{
char cw_tx[] = "PARIS";
// Morse sender parameters
byte _speed;
byte _pin;
byte _mode;
boolean done = false;
// Setup Morse sender
_speed = prefs[KEY_SPEED] + Key_speed_adj;
switch (prefs[OUT_MODE]) {
case 0: // Digital (key) output
_pin = key_pin;
_mode = 0;
break;
case 1: // Analog (beep) output
_pin = beep_pin;
_mode = 1;
break;
}
Serial.print('_mode: ');
Serial.println(_mode);
Morse morse = Morse(_pin, _speed, _mode);
// Loop sending until a button is pressed
Serial.print("\nTop of the send loop ");
lcd.clear();
lcd.setCursor(0, 0); // Set the cursor to top line, left
delay(1000); // one second between each paris
// Send characters
for (int i = 0; i < 5; i++)
{
if (prefs[GROUP_DLY] > 0) { //Wait out delay between characters
delay(prefs[GROUP_DLY] * 10);
}
lcd.print(cw_tx[i]); // Display the sent char
morse.send(cw_tx[i]); // Send the character
Serial.print(cw_tx[i]); // debug print
}
} // end of paris_test()
//===========================
// Restore app preferences from EEPROM if
// values are saved, else set to defaults.
// Send values to serial port on debug.
//===========================
void prefs_init()
{
// Restore app settings from the EEPROM if the saved
// flag value is 170, otherwise init to defaults.
if (EEPROM.read(0) == 170)
{
for (int idx = 0; idx < NUM_PREFS; idx++)
{
prefs_set(idx, EEPROM.read(idx));
}
}
else
{
prefs_set(SAVED_FLG, 0); // Prefs not saved
prefs_set(GROUP_NUM, 1); // Send/receive groups of 1 char to start
prefs_set(GROUP_DLY, 0); // Send/receive with no delay
prefs_set(KEY_SPEED, 25); // Send at 25 wpm to start
prefs_set(CHAR_SET, 5); // Use Koch order char set
prefs_set(KOCH_NUM, 5); // Use first 5 char in Koch set
prefs_set(KOCH_SKIP, 0); // Don't skip over any char to start
prefs_set(OUT_MODE, 1); // Output to speaker
}
}
//========================
// Set preference specified in arg1 to value in arg2
// Constrain prefs values to defined limits
// Echo value to serial port
//========================
byte prefs_set(byte pref, int val)
{
const byte lo_lim[] {0, 1, 0, 20, 1, 1, 0, 0}; // Table of lower limits of preference values
const byte hi_lim[] {170, 15, 30, 30, 6, 40, 39, 1}; // Table of uppper limits of preference values
byte new_val;
byte indx;
// Set new value
indx = constrain(pref, 0, NUM_PREFS - 1); // Constrain index, just to be safe
new_val = constrain(val, lo_lim[indx], hi_lim[indx]); // Set new value within defined limits
// Dispatch on preference index to do debug print
switch (indx) {
case SAVED_FLG:
Serial.print("Saved flag = ");
break;
case GROUP_NUM:
Serial.print("Group size = ");
break;
case GROUP_DLY:
Serial.print("Inter-character Delay = ");
break;
case KEY_SPEED:
Serial.print("Key speed = ");
break;
case CHAR_SET:
Serial.print("Character set = ");
break;
case KOCH_NUM:
Serial.print("Koch number = ");
break;
case KOCH_SKIP:
if (new_val >= prefs[KOCH_NUM]) new_val = prefs[KOCH_NUM] - 1;
Serial.print("Skip = ");
break;
case OUT_MODE:
Serial.print("Output mode = ");
break;
default:
Serial.print("Preference index out of range\n");
return new_val;
}
// Print and save new value before returning it
Serial.println(new_val);
prefs[indx] = new_val;
return new_val;
}