Arduino - Type error, help please

djsfantasi

Joined Apr 11, 2010
9,156
I just noticed the "goto" statement in yout if statement. We need to find a way to code that logic, so the "goto" is not needed. I'm of the camp that says goto is rarely, if not ever, needed. If it is used to pass control out of a code block with locally defined variables, yu are going to get memory corruption. I'd much rather use break to exit while and for loops and if I have to, define a boolean to indicate the break occurred and then goto the desired location... IF THATS THE ONLY WAY TO GET THE FLOW NEEDED. In your case, a band-aid approach would be to create another loop and when you execute the break command, execution would fall to the outer (new) loop end and it would repeat.

Oh, and I also just noticed your use of label: and its associated goto's. You are re-defining a string over and over, each time you goto label:. This is bound to cause memory problems. I'd move the definition of test_str to the beginning of the function, where it will only be executed once.

You may find this (click here->) library useful when debugging. You can keep track of free memory with debug statements (Serial.println)
 

Thread Starter

flat5

Joined Nov 13, 2008
403
Edit: (May 15!)
"Oh, and I also just noticed your use of label: and its associated goto's. You are re-defining a string over and over, each time you goto label:. This is bound to cause memory problems."

User input defines a string. I should sketch a goto loop that just defines a string and prints it. See if it crashes.

A while loop will not work. I have to break the function into a few functions and see how that goes.
Edit: This is the key. Clean up the prog and remove redundancies.
Organize stuff into procedures.
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Well, testing do - while anyway.
If legitimate characters are input after the prompt, sometimes the first one is ignored. Error traps are inconsistant.
May a do loop have more than one while?
Code:
void subset_test()   // Select characters for study
{
  unsigned int randNumber; String send_char; String answers;
  byte chk;
  String test_str;
  do // <--------------------------- area of interest. sometimes reports the correct error. sometimes hangs on the second pass
  {
    chk = true;
    test_str = "";
    Serial.flush();
    Serial.println(F("Input characters. Then press Enter to continue")); // must be a better way to say this
    Serial.setTimeout(10000);
    test_str.concat(Serial.readStringUntil('\r')); // 10 seconds or 'Enter' key
    Serial.setTimeout(1000); // reset key timeout to a more reasonable length
    test_str.toLowerCase();
    if (test_str.length() > 43)
    {
      Serial.println(F("Max:44 characters"));
      chk = false;
      while (chk == false); // hopefully jumps back to 'do'
    }
    for (unsigned int t = 0; t < test_str.length(); t++) // test to see if the string has duplicates
    {
      char h; // overcoming the string vs char compile error by CASTING
      h = char(test_str.charAt(t));
      if (h == char(test_str.charAt(t + 1))) //-------------------------
      {
        Serial.println(F("Repeated character!")); // "Repeated character not allowed."
        chk = false;
        while (chk == false); // hopefully jumps back to 'do'
      }
    }
  } while (chk == false); <--------------------- working on this right now ---------------------------------------



  char* send_codes[test_str.length()];
  for (unsigned int t = 0; t < test_str.length(); t++) // build string to be sent
  {
    randNumber = random(test_str.length() + 1);
    if (answers.indexOf(test_str[randNumber]) != -1) t = t - 1; // character already in array. try again
    else
    {
      answers.concat(test_str[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < 44; i++)
      {
        if (test_str.substring(randNumber, randNumber + 1) == code_char[i]) // to index the correct code string
        {
          send_codes[t] = codes[i];
        }
        //   else { // Serial.print(test_str.substring(randNumber)); Serial.println(" is not a character in the Morse array.");
        //}          // Still working on this error trap
      }
    }
  }
  for (unsigned int t = 0; t < test_str.length(); t++) // send the code
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char);       // function call
  }
  Serial.println(answers);
}
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
updated code. full program.
t option still troublesome. Sometimes the first character entered by user is not in the string.
Edit: randNumber = random(test_str.length()); <---I caught a bug! Was (test_str.length() + 1)
t is better now :)
Code:
// Morse Receive Practice v1.1
// by flat5 May-13-2015
// choice of tone/notone or external oscillator.
// Plug speaker to Analog pin8 and gnd. Use a resistor or pot in series if you want to. I used 220 ohm.
#include <Arduino.h>

unsigned int pitch = 0;          // *** I'm using an oscillator. You might want to use 600, for example. ***
unsigned long int baud = 115200; // *** 115200 57600 9600 make sure the term prog matches!               ***

byte term = true; //false        // *** If you are not using a good terminal program and the screen has weird
//                               // characters at the beginning of some lines change this from true to false

byte signalPin = 13; // will control oscillator speaker
unsigned int wpm = 15;           // Change this to any default value
unsigned int elementWait = 1200 / wpm;
unsigned int row = 43;           // do not make this number larger than 43!
unsigned int space = 2; // time between characters. It is good to send the chacters faster than the space between them for practice.

char* codes[] =
{ ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
  "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", // 26 letters
  "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",     // 10 numbers
  "--..--", ".-.-.-", "..--..", "..--.", "---...", ".-..-.", ".----.", "-...-"                  //  8 punctuation
};
char* code_char[] =
{ "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", // 26 letters
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",                // 10 numbers
  ",", ".", "?", "!", ":", "\"", "'", "="                          // 8 punctuation, so far - 44 characters
};

void setup() {
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud); //115200 - 9600
  randomSeed(analogRead(0));
  code_chart();
  menu();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start"));
  Serial.println(F("'s'(value) to adjust letter space (1-255)"));
  Serial.println(F("'w'(value) to adjust wpm (Example: w20)"));
  Serial.println(F("'p'(value) to set tone pitch <p31-24000 or 'p' for off"));
  Serial.println(F("'r'(value) to set number of characters to send (1-88)"));
  Serial.println(F("'t'(characters) to input characters to send (44 max)"));
  Serial.println(F("'u' to send the punctuation characters"));
  Serial.println(F("'m' to display this menu"));
  Serial.println(F("'c' to clear screen on real terminals"));
  Serial.println(F("'d' to show Morse code chart"));
  Serial.print("Currently: s:"); Serial.print(space); Serial.print(" w:"); Serial.print(wpm);
  Serial.print(" r:"); Serial.print(row); Serial.print(" P:"); Serial.println(pitch);
  //Serial.println("Currently: s:"+space+" w:"+wpm+" r:"+row+" P:"+pitch+"\n"); // boo hoo. wish this worked
  Serial.println();
}

void parser()
{
  Serial.print("<"); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  unsigned int value = 0;
  do
  {
    digits = Serial.available();
  }
  while (digits < 1);
  char keypress = Serial.read();
  do
  {
    digits = Serial.available();
  }
  while (digits < 0);
  value = Serial.parseInt();
  Serial.flush();
  if (term == true) Serial.write(8);
  switch (keypress)
  {
    case 'm': case 'M': case 'h': case 'H': case '?': // display menu
      {
        menu();
        break;
      }
    case ' ': // Spacebar. generate code
      {
        random_code();
        break;
      }
    case 't': case 'T': // send a selected group of characters
      {
        subset_test();
        break;
      }
    case 'u': case 'U': // send the punctuation characters
      {
        send_punc();
        break;
      }
    case 's': case 'S': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print("Letter space time: "); Serial.println(space);
        break;
      }
    case 'w': case 'W': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print("wpm: "); Serial.println(wpm);
        break;
      }
    case 'r': case 'R': // how many to send (a row)
      {
        if (value != 0 && value < 89)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        { Serial.print(F("value (1-88), presently ")); Serial.println(row);
          break;
        }
      }
    case 'c': case 'C':
      {
        refresh_Screen(); // a real terminal is required for this to work
        break;
      }
    case 'p': case 'P':
      {
        pitch = value;
        if (pitch == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (pitch < 31 || pitch > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
        tone(8, pitch, 400); // give a sample
        break;
      }
    case 'd': case 'D':
      {
        refresh_Screen();
        code_chart();
        break;
      }
  }
}

void refresh_Screen() // not compatable with all terminal programs
{
  if (term == true)
  {
    Serial.write(27); // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27); // ESC
    Serial.write("[H"); // cursor to home
  }
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void random_code() // send a random character's Morse equivlent
{
  String send_char; String answers; String answers2;
  char* send_codes[row]; //array starts at 0. User inputs how many characters in a run (row on screen)
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces) but the last check needs a kludge
  {
    unsigned int randNumber = random(44); // 0 - 43, all characters.
    // see if the randNumber character is in string 'answers' or 'answers2' already.
    if (t < 44)
    {
      if (answers.indexOf(code_char[randNumber]) != -1) t = t - 1; // character already in array. try again
      else
      {
        answers.concat(code_char[randNumber]); answers.concat(" "); // add a space after each character for readability
        send_codes[t] = (codes[randNumber]); // build array of code character strings to be sent
      }
    }
    if (t > 43 )
    {
      if (answers2.indexOf(code_char[randNumber]) != -1) t = t - 1;
      else
      {
        answers2.concat(code_char[randNumber]); answers2.concat(" ");
        send_codes[t] = (codes[randNumber]);
      }
    }
  }
  for (unsigned int t = 0; t < row ; t++)
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char); // function call
  }
  Serial.print(answers); Serial.println(answers2 + "\n"); //Serial.println(); // show test answers & answers2, if any
}

void subset_test()   // Select characters for study -------still working on this one
{
  unsigned int randNumber; String send_char; String answers; String test_str;
  Serial.flush();
  //if (term == true) Serial.write(8);
  Serial.println(F("Input characters. Then press Enter to continue")); // must be a better way to say this
  Serial.setTimeout(10000);
  test_str.concat(Serial.readStringUntil('\r')); // 10 seconds or 'Enter' key
  Serial.setTimeout(1000); // reset key timeout to a more reasonable length
  test_str.toLowerCase();
  if (test_str.length() > 43)
  {
    Serial.println(F("Max:44 characters - quiting routine"));
    return; // quit the routine
  }
  for (unsigned int t = 0; t < test_str.length(); t++) // test to see if the string has duplicates
  {
    char h; // overcoming the string vs char compile error by CASTING
    h = char(test_str.charAt(t));
    if (h == char(test_str.charAt(t + 1))) //-------------------------
    {
      Serial.println(F("Repeated character! - quiting routine")); // "Repeated character not allowed."
      return; // quit the routine
    }
  }
  delay(2000); // pick up a pencil time
  char* send_codes[test_str.length()];
  for (unsigned int t = 0; t < test_str.length(); t++) // build string to be sent
  {
    randNumber = random(test_str.length()); // (test_str.length() + 1) was a bug that caused a missing character in output
    if (answers.indexOf(test_str[randNumber]) != -1) t = t - 1; // character already in array. try again
    else
    {
      answers.concat(test_str[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < 44; i++)
      {
        if (test_str.substring(randNumber, randNumber + 1) == code_char[i]) // to index the correct code string
        {
          send_codes[t] = codes[i];
        }
        //else { // Serial.print(test_str.substring(randNumber)); Serial.println(" is not a character in the Morse array.");
        //} // Still working on this error trap. Not really needed. A wrong character is not processed (ignored).
      }
    }
  }
  for (unsigned int t = 0; t < test_str.length(); t++) // send the code
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char); // function call
  }
  Serial.println(answers + "\n");
}

void send_punc() // send just punctuation characters
{
  String punc = (",.?!:\"'="); String answers; unsigned int randNumber; String send_char; unsigned int rand_order[8];
  for (unsigned int t = 0; t < 8; t++)
  {
    randNumber = random(8);
    if (answers.indexOf(punc[randNumber]) != -1) t = t - 1; // character already in array. try again
    else
    {
      answers.concat(punc[randNumber]); // build answers string
      answers.concat(" ");              // add a space after each character for readability
      rand_order[t] = randNumber;
    }
  }
  for (unsigned int t = 0; t < 8; t++)
  {
    send_char = codes[rand_order[t] + 36]; // get code string for character in answers[index]
    send_code(send_char); // function call
  }
  Serial.println(answers + "\n");
}

void send_code(String send_char)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break it down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:")); // I wish I had more control over auto format. Like an IDE comment instruction to leave it alone.
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 44; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}
void print_line(unsigned int i)
{
  Serial.print("(");
  Serial.print(code_char[i]);
  Serial.print(")");
  Serial.print(codes[i]);
  Serial.print(" ");
}
Morse sender.jpg
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Cleaned up the code. Restructured the flow and cut some redundancies. Everything works but I'm sure the code could be more Elle Gante.
Code:
// Morse Receive Practice v2.2
// by flat5 May-15-2015
// choice of tone/notone or external oscillator.
// Plug speaker to Analog pin8 and gnd. Use a resistor or pot in series if you want to. I used 220 ohm.
#include <Arduino.h>

unsigned int pitch = 0;          // *** I'm using an oscillator. You might want to use 600, for example. ***
const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the term prog matches!   ***

byte term = true; //false        // *** If you are not using a good terminal program and the screen has weird
//                               // *** characters at the beginning of some lines change this from true to false
byte Get_ready = 2000;           // *** pause before test characters are sent. You might want to change this ***

const byte signalPin = 13; // will control oscillator speaker
unsigned int wpm = 20;           // Change this to any default value
unsigned int elementWait = 1200 / wpm;
unsigned int row = 43;           // do not make this number larger than 43!
unsigned int space = 3; // Default time between characters. It's good to send characters faster than the space between them for practice.
String testStr; unsigned int randNumber; String send_char; String answers; unsigned int error = 0;
char* codes[] =
{ ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
  "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", // 26 letters
  "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",     // 10 numbers
  "--..--", ".-.-.-", "..--..", "..--.", "---...", ".-..-.", ".----.", "-...-"                  //  8 punctuation
};
char* code_char[] =
{ "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", // 26 letters
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",                // 10 numbers
  ",", ".", "?", "!", ":", "\"", "'", "="                          // 8 punctuation, so far - 44 characters
};

void setup() {
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud); //115200 - 9600 - 300 - 1
  randomSeed(analogRead(0));
  code_chart();
  menu();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start"));
  Serial.println(F("'s'(value) to adjust letter space (1-255)"));
  Serial.println(F("'w'(value) to adjust wpm (Example: w20)"));
  Serial.println(F("'p'(value) to set tone pitch <p31-24000 or 'p' for off"));
  Serial.println(F("'r'(value) to set number of characters to send (1-88)"));
  Serial.println(F("'t'to select characters to send (44 max) or just Enter to clear them"));
  Serial.println(F("'u' to send the punctuation characters"));
  Serial.println(F("'m' to display this menu"));
  Serial.println(F("'c' to clear screen on real terminals"));
  Serial.println(F("'d' to show Morse code chart"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" w:")); Serial.print(wpm);
  Serial.print(F(" r:")); Serial.print(row); Serial.print(F(" P:")); Serial.print(pitch);
  Serial.print(F(" TestString:")); Serial.println(testStr);
  Serial.println();
}

void parser()
{
  Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  unsigned int value = 0;
  do
  {
    digits = Serial.available();
  }
  while (digits < 1);
  char keypress = Serial.read();
  do
  {
    digits = Serial.available();
  }
  while (digits < 0);
  value = Serial.parseInt();
  Serial.flush();
  if (term == true) Serial.write(8);
  switch (keypress)
  {
    case 'm': case 'M': case 'h': case 'H': case '?': case 'i': case 'I': // display menu
      {
        if (keypress == 'i' || keypress == 'I')
        {
          refresh_Screen();
          Serial.println(F("Morse Receive Practice v2.2 developed by Barry Block\n"));
        }
        menu();
        break;
      }
    case ' ': // Spacebar. generate code
      {
        random_code();
        break;
      }
    case 't': case 'T': // send a selected group of characters
      {
        build_testStr();
        break;
      }
    case 'u': case 'U': // send the punctuation characters
      {
        testStr = ",.?!:\"'=";
        Serial.println(F("TestStr set to puncuation characters: ,.?!:\"'="));
        break;
      }
    case 's': case 'S': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 'w': case 'W': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'r': case 'R': // how many to send (a row)
      {
        if (value != 0 && value < 89)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        { Serial.print(F("value (1-88), presently ")); Serial.println(row);
          break;
        }
      }
    case 'c': case 'C':
      {
        refresh_Screen(); // a real terminal is required for this to work
        break;
      }
    case 'p': case 'P':
      {
        pitch = value;
        if (pitch == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (pitch < 31 || pitch > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
        tone(8, pitch, 400); // give a sample
        break;
      }
    case 'd': case 'D':
      {
        refresh_Screen();
        code_chart();
        break;
      }
  }
}

void refresh_Screen() // not compatable with all terminal programs
{
  if (term == true)
  {
    Serial.write(27); // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27); // ESC
    Serial.write("[H"); // cursor to home
  }
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void random_code() // send a random character's Morse equivlent
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  String answers2;
  char* send_codes[row]; //array starts at 0. User inputs how many characters in a run (row on screen)
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    randNumber = random(44); // 0 - 43, all characters.
    // see if the randNumber character is in string 'answers' or 'answers2' already.
    if (t < 44)
    {
      if (answers.indexOf(code_char[randNumber]) != -1) t = t - 1; // character already in array. try again
      else
      {
        answers.concat(code_char[randNumber]); answers.concat(" "); // add a space after each character for readability
        send_codes[t] = (codes[randNumber]); // build string array of code characters to be sent, in Morse form
      }
    }
    if (t > 43 )
    {
      if (answers2.indexOf(code_char[randNumber]) != -1) t = t - 1;
      else
      {
        answers2.concat(code_char[randNumber]); answers2.concat(" ");
        send_codes[t] = (codes[randNumber]);
      }
    }
  }
  for (unsigned int t = 0; t < row ; t++)
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char);
  }
  Serial.println(answers); if (row > 43) Serial.println(answers2 + '\n'); // show test answers & answers2, if any
  answers = "";
}

void send_select() // called when testStr has proved legal
{
  char* send_codes[testStr.length()];
  for (unsigned int t = 0; t < testStr.length(); t++) // random up the string to be sent
  {
    randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t = t - 1; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < 44; i++)
      {
        if (testStr.substring(randNumber, randNumber + 1) == code_char[i]) // to index the correct code string
        {
          send_codes[t] = codes[i];
        }
      }
    }
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // send the code
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char);
  }
  Serial.println(answers + "\n"); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build(); // collect chars and put in testStr if they are legal
  if (error == 1)
  {
    error = 0;
    return;
  }
  Serial.print(F("TestString: ")); Serial.println(testStr);
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  unsigned int randNumber; String send_char; String answers;
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //build testString

  // now test the string for problems
  if (testStr.length() > 43)       // is the string too long? larger than the array of Morse characters
  {
    Serial.println(F("Max:44 characters - TestString nulled"));
    error = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; // overcoming the string vs char compile error by CASTING
    h = char(testStr.charAt(t));
    if (h == char(testStr.charAt(t + 1))) // position t+1 to the end of string
    {
      Serial.println(F("Repeated character! - TestString nulled"));
      error = 1;
      testStr = "";
      return;
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    error = 1; // setting a flag
    for (unsigned int i = 0; i < 44; i++) // check to see that the character is a known Morse char.
    {
      if (testStr.substring(s, s + 1) == code_char[i]) // if a match is found for this char set a flag. don't check further
      {
        error = 0;
        break;
      }
    }
    if (error == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring nulled"));
      testStr = "";
      error = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = "";
  char inChar;
  while (Serial.read() == '\r');
  Serial.flush();
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127) testStr = testStr + inChar; // screen gets filled with garbage without this check
    else if (inChar == '\r')
    {
      testStr.toLowerCase();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 44; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  Serial.print("("); Serial.print(code_char[i]); Serial.print(")"); Serial.print(codes[i]); Serial.print(" ");
}
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
I think the program is quite useful at this point. I'd like to know of a beginner's ham site to consider uploading it. First have to see what else is offered there.
Edit: added 8 more punctuation characters. Added a feature or two. Caught a few bugs.
Code:
// Morse Receive Practice v2.4
// by flat5 May-31-2015
// choice of tone/notone or external oscillator.
// Plug speaker to Analog pin8 and gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.

#include <Arduino.h>
const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the term prog matches!

byte term = true; //false        // *** If you are not using a good terminal program and the screen has weird
//                               //     characters at the beginning of some lines change this from true to false
unsigned int pitch = 830; //0    // *** I'm using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers Ex. 0,1,2,3 (in menu)
unsigned int space = 3;          // *** Default time between characters.
//                               //     It's good to send characters faster than the space between them for practice.

const byte signalPin = 13;       // *** will control oscillator speaker
unsigned int wpm = 20;           // *** Change this to any default value
unsigned int row = 52;           // *** do NOT make this number larger than the array - 52!
unsigned int testStrRow;
unsigned int elementWait = 1200 / wpm;
String testStr; unsigned int randNumber; String send_char; String answers; unsigned int error = 0;
char* codes[] =
{ ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
  "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", // 26 letters
  "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",     // 10 numbers
  "--..--", ".-.-.-", "..--..", "..--.", "---...", ".-..-.", ".----.", "-...-", ".--.-.",
  "..-.--", "-.--.", "-.--.-", "..--", ".-...", "-.-.-.", ".-.-."                               // 16 punctuation
};
char* code_char[] =
{ "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", // 26 letters
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",                // 10 numbers
  ",", ".", "?", "!", ":", "\"", "'", "=", "@", "_", "(", ")", "^", "&", ";", "+"               // 16 punctuation - 52 characters (0-51)
};

void setup() {
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud); //115200 - 9600 - 300 - 1
  randomSeed(analogRead(0));
  Serial.println(F("Morse Receive Practice v2.4 developed by Barry Block\n"));
  menu();
  code_chart();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust tone pitch <p31-24000 or 'p' for off"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-104)"));
  Serial.println(F("t select characters to send (52 max) or just Enter to clear 'TestString'"));
  Serial.println(F("n send numbers"));
  Serial.println(F("l send letters"));
  Serial.println(F("u send punctuation characters"));
  Serial.println(F("c clear screen on real terminals"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:"));Serial.print(row);
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  unsigned int value = 0;
  do
  {
    digits = Serial.available();
  }
  while (digits < 1);
  char keypress = Serial.read();
  delay(key_wait);
  do
  {
    digits = Serial.available();
  }
  while (digits < 0); // I do not understand this
  value = Serial.parseInt();
  Serial.flush();
  if (term == true) Serial.write(8); // backspace
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': case 'C':
      {
        refresh_Screen(); // a real terminal is required for this to work
        break;
      }
    case 'd': case 'D':
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'k': case 'K': // keypress timeout
      {
        if (value < 11)
        {
          key_wait = value * 1000;
        }
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': case 'L': // send the punctuation characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        Serial.println(F("TestString set to letters"));
        shortRow();
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        break;
      }
    case 'm': case 'M': case 'h': case 'H': case '?': case 'i': case 'I': // display menu
      {
        if (keypress == 'i' || keypress == 'I' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(F("Morse Receive Practice v2.4 developed by Barry Block\n"));
        }
        menu();
        break;
      }
    case 'n': case 'N': // send the punctuation characters
      {
        testStr = "1234567890";
        Serial.println(F("TestString set to numbers"));
        shortRow();
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        break;
      }
    case 'p': case 'P':
      {
        pitch = value;
        if (pitch == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (pitch < 31 || pitch > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
        tone(8, pitch, 400); // give a sample
        break;
      }
    case 'r': case 'R': // how many to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-104), presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 105)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.println(F("Too high a number!(1-104)"));
          break;
        }
      }
    case 's': case 'S': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': case 'T': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': case 'U': // send the punctuation characters
      {
        testStr = ",.?!:\"'=@_()^&;+";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        shortRow();
        if (testStrRow > 16)
        {
          Serial.println(F("1-16 characters!"));
          testStrRow = 16;
        }
        break;
      }
    case 'w': case 'W': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
  }
}

void refresh_Screen() // not compatible with all terminal programs
{
  if (term == true)
  {
    Serial.write(27); // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27); // ESC
    Serial.write("[H"); // cursor to home
  }
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void random_code() // send a random character's Morse equivalent
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  String answers2;
  char* send_codes[row]; //array starts at 0. User inputs how many characters in a run (row on screen)
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    randNumber = random(52); // 0 - 51, all characters.
    // see if the randNumber character is in string 'answers' or 'answers2' already.
    if (t < 52)
    {
      if (answers.indexOf(code_char[randNumber]) != -1) t = t - 1; // character already in array. try again
      else
      {
        answers.concat(code_char[randNumber]); answers.concat(" "); // add a space after each character for readability
        send_codes[t] = (codes[randNumber]); // build string array of code characters to be sent, in Morse form
      }
    }
    if (t > 51 )
    {
      if (answers2.indexOf(code_char[randNumber]) != -1) t = t - 1;
      else
      {
        answers2.concat(code_char[randNumber]); answers2.concat(" ");
        send_codes[t] = (codes[randNumber]);
      }
    }
  }
  for (unsigned int t = 0; t < row ; t++)
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char);
  }
  Serial.println(answers); if (row > 52) Serial.println(answers2 + '\n'); // show test answers & answers2, if any
  answers = "";
}

void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  char* send_codes[testStrRow];
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t = t - 1; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < 52; i++)
      {
        if (testStr.substring(randNumber, randNumber + 1) == code_char[i]) // to index the correct code string
        {
          send_codes[t] = codes[i];
        }
      }
    }
  }
  for (unsigned int t = 0; t < testStrRow; t++) // send the code
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char);
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build(); // collect chars and put in testStr if they are legal. if not, return error 1
  if (error == 1)
  {
    error = 0;
    return;
  }
  if (testStr != "") // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  unsigned int randNumber; String send_char; String answers;
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > 52)       // is the string too long? larger than the array of Morse characters
  {
    Serial.println(F("Max:52 characters - TestString cleared"));
    error = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; // overcoming the string vs char compile error by CASTING
    h = char(testStr.charAt(t));
    if (h == char(testStr.charAt(t + 1))) // position t+1 to the end of string
    {
      Serial.println(F("Repeated character! - TestString cleared"));
      error = 1;
      testStr = "";
      return;
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    error = 1; // setting a flag
    for (unsigned int i = 0; i < 52; i++) // check to see that the character is a known Morse char.
    {
      if (testStr.substring(s, s + 1) == code_char[i]) // if a match is found for this char set a flag. don't check further
      {
        error = 0;
        break;
      }
    }
    if (error == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      error = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = "";
  char inChar;
  while (Serial.read() == '\r');
  Serial.flush();
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127) testStr = testStr + inChar; // screen gets filled with garbage without this check
    else if (inChar == '\r')
    {
      testStr.toLowerCase();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void shortRow() // row length for the users input test string
{
  char inChar;
  String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  Serial.flush();
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58) inString = inString + inChar; // if a number add to string
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      return;
    }
  }
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 44; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 44; i < 52; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  Serial.print("["); Serial.print(code_char[i]); Serial.print("]"); Serial.print(codes[i]); Serial.print(" ");
}
Screen shot
Morse receive practice v2.4 (3).png
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Latest version 2.5. Added SDcard to store/send a random textfile.
This is pushing the memory to the limit so could use help optimising the code where possible.
Specific suggestions welcome as my coding skills are VERY basic. I'd like to think I've improved them by working on this project.
I will update the code and place it here rather than a new post.
Code:
// Morse Receive Practice v2.5
// by flat5 June-4-2015
// UK/US characters only
// choice of tone/notone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This has pushed the memory to the limit. Had to remove a few things. Could use some help to to improve this situation!

#include <Arduino.h>
#include <SPI.h>
#include <SD.h>
const int chipSelect = 4; // SD card select
byte SDready; // SD card installed, true or false
byte echo;
unsigned int fileCount = 10;     // *** number of textfiles in SD root folder. They must be named 0 1 2...9 no more!
//                               //     with no extension. They will be picked randomly. Had to save memory here.

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the term prog matches!

byte term = true; //false        // *** If you are not using a good terminal program and the screen has weird
//                               //     characters at the beginning of some lines change this from true to false
unsigned int pitch = 830; //0    // *** I'm using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers Ex. 0,1,2,3 (in menu)
unsigned int space = 3;          // *** Default time between characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using my circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
unsigned int row = 52;           // *** do NOT make this number larger than the array - 52!

unsigned int elementWait = 1200 / wpm;
String testStr; unsigned int testStrRow; unsigned int randNumber; String send_char; String answers; unsigned int error = 0;
char* codes[] =
{ ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
  "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", // 26 letters
  "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",     // 10 numbers
  "--..--", ".-.-.-", "..--..", "..--.", "---...", ".-..-.", ".----.", "-...-", ".--.-.",
  "..-.--", "-.--.", "-.--.-", "..--", ".-...", "-.-.-.", ".-.-."                               // 16 punctuation
};
char* code_char[] =
{ "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", // 26 letters
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",                // 10 numbers
  ",", ".", "?", "!", ":", "\"", "'", "=", "@", "_", "(", ")", "^", "&", ";", "+"               // 16 punctuation - 52 characters (0-51)
};

void setup() {
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud); //115200 - 9600 - 300 - 1
  randomSeed(analogRead(0));
  Serial.print(F("Initializing SD card...")); // see if the SD card is present and can be initialized:
  if (!SD.begin(chipSelect))
  {
    SDready = false;
    Serial.println(F("SD card failed, or not present"));
    delay(3000);
  }
  else
  {
    Serial.println(F("SD card initialized."));
    SDready = true;
    //countRootFiles();
  }
  refresh_Screen();
  Serial.println(F("Morse Receive Practice v2.5 developed by Barry Block\n"));
  menu();
  // code_chart();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust tone pitch <p31-24000 or 'p' for off"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-52)"));
  Serial.println(F("t select characters to send (52 max) or just Enter to clear 'TestString'"));
  if (SDready) Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
  Serial.println(F("n send numbers"));
  Serial.println(F("l send letters"));
  Serial.println(F("u send punctuation characters"));
  if (term) Serial.println(F("c clear screen on real terminals"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  unsigned int value = 0;
  do
  {
    digits = Serial.available();
  }
  while (digits < 1);
  char keypress = Serial.read();
  delay(key_wait);
  do
  {
    digits = Serial.available();
  }
  while (digits < 0); // I do not understand this
  value = Serial.parseInt();
  Serial.flush();
  if (term == true) Serial.write(8); // backspace
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': case 'C':
      {
        refresh_Screen(); // a real terminal is required for this to work
        break;
      }
    case 'd': case 'D':
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': case 'F':
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) {
          echo = true;
        }
        else {
          echo = false;
        }
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': case 'K': // keypress timeout
      {
        if (value < 11)
        {
          key_wait = value * 1000;
        }
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': case 'L': // send the punctuation characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        Serial.println(F("TestString set to letters"));
        shortRow();
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        break;
      }
    case 'm': case 'M': case 'h': case 'H': case '?': case 'i': case 'I': // display menu
      {
        if (keypress == 'i' || keypress == 'I' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(F("Morse Receive Practice v2.5 developed by Barry Block\n"));
        }
        menu();
        break;
      }
    case 'n': case 'N': // send the punctuation characters
      {
        testStr = "1234567890";
        Serial.println(F("TestString set to numbers"));
        shortRow();
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        break;
      }
    case 'p': case 'P':
      {
        pitch = value;
        if (pitch == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (pitch < 31 || pitch > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
        tone(8, pitch, 400); // give a sample
        break;
      }
    case 'r': case 'R': // how many to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-52), presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 53)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.println(F("Too high a number!(1-52)"));
          break;
        }
      }
    case 's': case 'S': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': case 'T': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': case 'U': // send the punctuation characters
      {
        testStr = ",.?!:\"'=@_()^&;+";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        shortRow();
        if (testStrRow > 16)
        {
          Serial.println(F("1-16 characters!"));
          testStrRow = 16;
        }
        break;
      }
    case 'w': case 'W': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
  }
}

void refresh_Screen() // not compatible with all terminal programs
{
  if (term == true)
  {
    Serial.write(27); // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27); // ESC
    Serial.write("[H"); // cursor to home
  }
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void word_space() {
  delay(elementWait * space * 2); // close enough
}

void random_code() // send a random character's Morse equivalent
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  char* send_codes[row]; //array starts at 0. User inputs how many characters in a run (row on screen)
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    randNumber = random(52); // 0 - 51, all characters.
    // see if the randNumber character is in string 'answers' or 'answers2' already.
    if (answers.indexOf(code_char[randNumber]) != -1) t = t - 1; // character already in array. try again
    else
    {
      answers.concat(code_char[randNumber]); answers.concat(" "); // add a space after each character for readability
      send_codes[t] = (codes[randNumber]); // build string array of code characters to be sent, in Morse form
    }
  }
  for (unsigned int t = 0; t < row ; t++)
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char);
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}

void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  char* send_codes[testStrRow];
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < 52; i++)
      {
        if (testStr.substring(randNumber, randNumber + 1) == code_char[i]) // to index the correct code string
        {
          send_codes[t] = codes[i];
        }
      }
    }
  }
  for (unsigned int t = 0; t < testStrRow; t++) // send the code
  {
    send_char = send_codes[t]; // send the code of a character
    send_code(send_char);
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build(); // collect chars and put in testStr if they are legal. if not, return error 1
  if (error == 1)
  {
    error = 0;
    return;
  }
  if (testStr != "") // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  unsigned int randNumber; String send_char; String answers;
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > 52)       // is the string too long? larger than the array of Morse characters
  {
    Serial.println(F("Max:52 characters - TestString cleared"));
    error = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; // overcoming the string vs char compile error by CASTING
    h = char(testStr.charAt(t));
    if (h == char(testStr.charAt(t + 1))) // position t+1 to the end of string
    {
      Serial.println(F("Repeated character! - TestString cleared"));
      error = 1;
      testStr = "";
      return;
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    error = 1; // set a flag
    for (unsigned int i = 0; i < 52; i++) // check to see that the character is a known Morse char.
    {
      if (testStr.substring(s, s + 1) == code_char[i]) // if a match is found for this char set a flag. don't check further
      {
        error = 0;
        break;
      }
    }
    if (error == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      error = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  while (Serial.read() == '\r');
  Serial.flush();
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127) testStr = testStr + inChar; // screen gets filled with garbage without this check
    else if (inChar == '\r')
    {
      testStr.toLowerCase();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  Serial.flush();
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58) inString = inString + inChar; // if a number add to string
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      return;
    }
  }
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 44; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 44; i < 52; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  Serial.print("["); Serial.print(code_char[i]); Serial.print("]"); Serial.print(codes[i]); Serial.print(" ");
}

void send_SDtext()
{
  String str; char fname[2]; // fails if > 3 !
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  str.toCharArray(fname, 2); // convert string to char array
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;
      send_char.toLowerCase();
      for (unsigned int i = 0; i < 52; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        if (send_char == code_char[i]) // if a match is found for this char, send it
        {
          send_char = codes[i];
          send_code(send_char);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}
// "That's all, Folks"
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
OK, I lied. This is a memory re-write and some cleanup. Gave me some room to add a feature. SD fileCount.
Dynamic memory used is only 51% now! I think moving the two big(ish) arrays to flash memory has improved the speed of the prog so I re-wrote and simplified things in a few places. Worth the effort. I'm learning! (maybe)
Next is to add saving and deleting a file on the SD card.
version 2.6
Code:
// Morse Receive Practice v2.6
// by flat5 June 6, 2015
// UK/US characters only
// choice of tone/notone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// This version is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 61 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// I may implement a few things I did not have the memory for.

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
String Brag = "Morse Receive Practice v2.6 developed by Barry Block\n";
const int chipSelect = 4; // SD card select
byte echo;
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the term prog matches!

byte term = true; //false        // *** If you are not using a good terminal program and the screen has weird
//                               //     characters at the beginning of some lines change this from true to false
unsigned int pitch = 830; //0    // *** I'm using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers Ex. 0,1,2,3 (in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using my circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
unsigned int row = 52;           // *** do NOT make this number larger than the array - 52!

unsigned int elementWait = 1200 / wpm;
String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int error; File root; unsigned int fileCount = 0;

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "_";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "^"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--"; const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";  const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----."; const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "..-.--"; const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "..--";   const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51
};

char buffer[7]; // make sure this is large enough for the largest string it must hold

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    Serial.print(F("Initializing SD card...")); // see if the SD card is present and can be initialized:
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("SD card failed, or not present"));
      delay(3000);
    }
    else
    {
      Serial.println(F("SD card initialized.")); // if using a good terminal you'll never see this :-)
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
  // code_chart();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust tone pitch <p31-24000 or 'p' for off"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-52)"));
  Serial.println(F("t select characters to send (52 max) or just Enter to clear 'TestString'"));
  if (SDready) Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
  Serial.println(F("n send numbers"));
  Serial.println(F("l send letters"));
  Serial.println(F("u send punctuation characters"));
  if (term) Serial.println(F("c clear screen on real terminals"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  if (fileCount > 0)
  {
    Serial.print(F(" SD files:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  unsigned int value = 0;
  do
  {
    digits = Serial.available();
  }
  while (digits < 1);
  char keypress = Serial.read();
  delay(key_wait);
  do
  {
    digits = Serial.available();
  }
  while (digits < 0); // I do not understand this
  value = Serial.parseInt();
  Serial.flush();
  if (term == true) Serial.write(8); // backspace
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': case 'C':
      {
        refresh_Screen(); // a real terminal is required for this to work
        break;
      }
    case 'd': case 'D':
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': case 'F':
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) {
          echo = true;
        }
        else {
          echo = false;
        }
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': case 'K': // keypress timeout
      {
        if (value < 11)
        {
          key_wait = value * 1000;
        }
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': case 'L': // send the letter characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        Serial.println(F("TestString set to letters"));
        shortRow();
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        break;
      }
    case 'm': case 'M': case 'h': case 'H': case '?': case 'i': case 'I': // display menu
      {
        if (keypress == 'i' || keypress == 'I' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': case 'N': // send the number characters
      {
        testStr = "1234567890";
        Serial.println(F("TestString set to numbers"));
        shortRow();
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        break;
      }
    case 'p': case 'P':
      {
        pitch = value;
        if (pitch == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (pitch < 31 || pitch > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
        tone(8, pitch, 400); // give a sample
        break;
      }
    case 'r': case 'R': // how many to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-52), presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 53)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.println(F("Too high a number!(1-52)"));
          break;
        }
      }
    case 's': case 'S': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': case 'T': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': case 'U': // send the punctuation characters
      {
        testStr = ",.?!:\"'=@_()^&;+";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        shortRow();
        if (testStrRow > 16)
        {
          Serial.println(F("1-16 characters!"));
          testStrRow = 16;
        }
        break;
      }
    case 'w': case 'W': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
  }
}

void refresh_Screen() // not compatible with all terminal programs
{
  if (term == true)
  {
    Serial.write(27); // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27); // ESC
    Serial.write("[H"); // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(52); // 0 - 51, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 52 chars). no jerkyness so no need to build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}
void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < 52; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i);
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build(); // collect chars and put in testStr if they are legal. if not, return error 1
  if (error == 1)
  {
    error = 0;
    return;
  }
  if (testStr != "") // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > 52)       // is the string too long? larger than the array of Morse characters
  {
    Serial.println(F("Max:52 characters - TestString cleared"));
    error = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; // overcoming the string vs char compile error by CASTING
    h = char(testStr.charAt(t));
    if (h == char(testStr.charAt(t + 1))) // position t+1 to the end of string
    {
      Serial.println(F("Repeated character! - TestString cleared"));
      error = 1;
      testStr = "";
      return;
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    error = 1; // set a flag
    for (unsigned int i = 0; i < 52; i++) // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer) // if a match is found for this char set a flag. don't check further
      {
        error = 0;
        break;
      }
    }
    if (error == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      error = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  while (Serial.read() == '\r');
  Serial.flush();
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void word_space() {
  delay(elementWait * space * 2); // close enough
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  Serial.flush();
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 44; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 44; i < 52; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  getStr(i);
  Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i);
  Serial.print(buffer); Serial.print(" ");
}

void send_SDtext()
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 999 files + 0. 1000 files
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < 52; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int)
{
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    fileCount++;                  // looks like count starts at the second file. this turns out not to be a problem. not sure why!
    if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    entry.close();
  }
  return fileCount;
}

// "That's all, Folks"
//...actually...(to be continued)
4.jpg
6.jpg
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Could use some help. I've spent lots of time but have not yet solved these three problems.
To do:
resolve file 0/no file problem. checking user input when no input or an input of zero
find a way to stop writing an SD file without an EOF marker.
sort the SD file list in less than 2000 bytes
Sketch uses 30,314 bytes (93%) of program storage space. Maximum is 32,256 bytes.
Global variables use 1,114 bytes (54%) of dynamic memory, leaving 934 bytes for local variables. Maximum is 2,048 bytes. Edit: some bug fixes.
Code:
// Morse Receive Practice v2.8.2
// by flat5 June 15, 2015
// UK/US characters only
// choice of tone/notone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and Gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// version 2.6 is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 64 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// v2.7 & v2.8 Some SD card options added. This ver, some code cleanup and a feature or two added.
// v2.8.a changed the behaviour of some options
// to do:
// resolve file 0/no file problem. find a way to stop writing an SD file without an EOF marker. sort the SD file list in less than 2000 bytes

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
String Brag = "Morse Receive Practice v2.8.2 developed by Barry Block\n";
const int chipSelect = 4; // SD card select
byte echo;
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the terminal program matches!

byte ANSI = true; //false        // *** If you are not using an ANSI terminal program and the screen has weird characters
//                               //     at the beginning of some lines change this from true to false. now a menu item
unsigned int pitch = 830; //0    // *** 0 if using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int soft = 950;         // *** find a pitch that is quieter in your setup
unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers (Ex. 0,1,2,3 in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using external circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
unsigned int row = 52;           // *** do NOT make this number larger or smaller than the array - 52!

unsigned int elementWait = 1200 / wpm; char keypress; unsigned int value; unsigned int Length; unsigned int fileCount = 0;
String sendfile = ""; String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int flag = 0;
File root; String filename;

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "_";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "^"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--"; const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";  const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----."; const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "..-.--"; const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "..--";   const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51
};

char buffer[7]; // make sure this is large enough for the largest string it must hold

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("SD card failed, or not present"));
      delay(3000);
    }
    else
    {
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
  // code_chart();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust pitch, p off, p7 decrement 10Hz, p8 increment 10Hz, p9 soft (preset)"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-52)"));
  Serial.println(F("t select characters to send (52 max) or just Enter to clear 'TestString'"));
  if (SDready)
  {
    Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
    Serial.println(F("x SD card menu"));
  }
  Serial.println(F("n(row) send numbers"));
  Serial.println(F("l(row) send letters"));
  Serial.println(F("u(row) send punctuation characters or u77 (or >) to not send them"));
  Serial.println(F("c clear screen or to toggle this option: c(number)"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  if (fileCount > 0)
  {
    Serial.print(F(" SD files:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  getChars();
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': // clear screen if ANSI is supported
      {
        if (value > 0)
        {
          if (ANSI == true) ANSI = false;
          else ANSI = true;
          break;
        }
        refresh_Screen(); // an ANSI terminal is required for this to work
        break;
      }
    case 'd': // display code chart
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': // send random textfile from SD card
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) {
          echo = true;
        }
        else {
          echo = false;
        }
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': // keypress timeout
      {
        if (value < 11) key_wait = value * 1000;
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': // send the letter characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        if (value == 0 ) testStrRow = 26;
        else testStrRow = value;
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        Serial.print(F("\nTestString set to letters. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'm': case 'h': case '?': case 'i': // display menu
      {
        if (keypress == 'i' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': // send the number characters
      {
        testStr = "1234567890";
        if (value == 0 ) testStrRow = 10;
        else testStrRow = value;
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        Serial.print(F("\nTestString set to numbers. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'p': // pitch
      {
        if (value == 7)
        {
          pitch = pitch - 10;
          if (pitch > 24000 || pitch < 31) pitch = 31; // unsigned int rollover
          confirm_pitch();
          break;
        }
        if (value == 8)
        {
          pitch = pitch + 10;
          if (pitch > 24000) pitch = 24000;
          confirm_pitch();
          break;
        }
        if (value == 9) // find a pitch that is much lower in volume than others
        {
          pitch = soft;
          confirm_pitch();
          break;
        }
        if (value == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (value < 31 || value > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        pitch = value;
        confirm_pitch();
        break;
      }
    case 'r': // how many chars to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-52), presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 53)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.println(F("Too high a number!(1-52)"));
          break;
        }
      }
    case 's': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': // send the punctuation characters or not
      {
        if (value > 76)
        {
          testStr = "1234567890abcdefghijklmnopqrstuvwxyz";
          Serial.println(F("TestString set to numbers & letters"));
          testStrRow = 36;
          break;
        }
        testStr = ",.?!:\"'=@_()^&;+";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        if (value == 0 ) testStrRow = 16;
        else testStrRow = value;
        if (testStrRow > 16)
        {
          Serial.println(F("1-16 characters!"));
          testStrRow = 16;
        }
        Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'w': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'x': // SD card sub menu
      {
        SDfiles();
        break;
      }
  }
}

void refresh_Screen() // requires an ANSI terminal. can be toggled. see menu display
{
  if (ANSI == true)
  {
    Serial.write(27); // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27); // ESC
    Serial.write("[H"); // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent and display answer. Heart of the program!
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(52); // 0 - 51, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 52 chars). no jerkyness so no need to build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}
void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < 52; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i); // get char pointed to by i and return it as 'buffer'
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build(); // collect chars and put in testStr if they are legal. if not, return flag 1
  if (flag == 1)
  {
    flag = 0;
    return;
  }
  if (testStr != "") // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > 52)       // is the string too long? larger than the array of Morse characters
  {
    Serial.println(F("Max:52 characters - TestString cleared"));
    flag = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; h = char(testStr.charAt(t)); // overcoming the string vs char compile error by CASTING
    for (int i = t + 1; i < testStr.length(); i++)
    {
      if (h == char(testStr.charAt(i)))
      {
        Serial.println(F("Repeated character! - TestString cleared"));
        flag = 1;
        testStr = "";
        return;
      }
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    flag = 1; // set the flag
    for (unsigned int i = 0; i < 52; i++) // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer) // if a match is found for this char set a flag. don't check further
      {
        flag = 0;
        break;
      }
    }
    if (flag == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      flag = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  while (Serial.read() == '\r');
  Serial.flush();
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void word_space() {
  delay(elementWait * space * 2); // close enough
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  Serial.flush();
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 44; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 44; i < 52; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  getStr(i);
  Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i);
  Serial.print(buffer); Serial.print(" ");
}

void send_SDtext()
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  if (sendfile != "")
  {
    str = sendfile; sendfile = ""; // a file has been specifically chosen
  }
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 999 files + 0. 1000 files
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < 52; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int)
{
  fileCount = 0;
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    String n = entry.name();
    if (n.toInt() != 0 || n == "0") // only include files starting with a number BUT file 0 will not be displayed unless exception is made
    {
      fileCount++;
      if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    }
    entry.close();
  }
  return fileCount;
}

void SDfiles()
{
  if (flag == 0) refresh_Screen();
  Serial.println(F("\nChoose Option:\n"));
  Serial.println(F("l list files"));
  Serial.println(F("r(filenumber) read file"));
  Serial.println(F("s(filenumber) send file"));
  Serial.println(F("d(filenumber) delete file"));
  Serial.println(F("w(filenumber) write file"));
  Serial.println(F("t1 (Load testString) t2 (Save testString) t3 (Delete testString)"));
  Serial.println(F("x return to main menu"));
  flag = 0;
  getChars();
  refresh_Screen();
  switch (keypress) // char
  {
    case 'd': // delete file
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be deleted by mistake\n"));
          flag = 1; SDfiles();
          return;
        }
        File root;
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          root = SD.open("/");
          SD.remove(fname);
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" Deleted."));
          root.close();
          countRootFiles(root, fileCount);
          return;
        }
        else
        {
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" not found."));
        }
        flag = 1; SDfiles();
        return;
      }
    case 'l': // list SD root files
      {
        printDirectory();
        flag = 1; SDfiles();
        return;
      }
    case 'r': //read textfile
      {
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          refresh_Screen();
          Serial.print(F("File: ")); Serial.println(fname); Serial.println();
          File dataFile = SD.open(fname, FILE_READ);
          while (dataFile.available())
          {
            Serial.write(dataFile.read());
          }
          dataFile.close();
          Serial.println();
        }
        else
        {
          Serial.println(F("\nfile not found"));
          Serial.println();
        }
        flag = 1; SDfiles();
        return;
      }
    case 's': // send textfile
      {
        sendfile = String(value);
        byte e = echo;
        echo = true;
        send_SDtext(); // flag to echo the chars rather than show at the end
        echo = e;      // reset 'global' echo
        flag = 1; SDfiles();
        return;
      }
    case 't': //teststring routines
      {
        if (value < 1 || value > 3)
        {
          Serial.println(F("\nNo such option. t1 load - t2 save - t3 delete"));
          flag = 1; SDfiles();
          return;
        }
        File myFile;
        if (value == 1) // read file
        {
          myFile = SD.open("/utility/t1");
          if (myFile)
          {
            testStr = "";
            Serial.print(F("\nloading testStr: "));
            while (myFile.available()) // read from the file until there's nothing else in it
            {
              char a = (myFile.read()); // read a char and add to testStr
              testStr = testStr + a;
            }
            myFile.close(); // close the file
            testStrRow = testStr.length();
            Serial.println(testStr); Serial.println();
          }
          else
          {
            Serial.println(F("\nError opening /utility/t1\n")); // if the file didn't open, print an error
          }
          flag = 1; SDfiles();
          return;
        }
        else if (value == 2) // write file
        {
          if (!SD.exists("test_str/"))
          {
            SD.mkdir("test_str");
          }
          SD.remove("/utility/t1");
          testStr.trim(); testStr.toLowerCase();
          myFile = SD.open("/utility/t1", FILE_WRITE);
          if (myFile) // if the file opened okay, write to it
          {
            Serial.print(F("\nSaving testString: "));
            myFile.print(testStr); // write the string to the file
            myFile.close(); // close the file
            Serial.println(testStr); Serial.println();
            testStrRow = testStr.length(); // store string length
          }
          else
          {
            Serial.println(F("error opening /utility/t1"));
          }
          menu();
          return;
        }
        else if (value == 3) // delete file
        {
          SD.remove("/utility/t1");
          delay(200); // just seemed like a good idea
          if (! SD.exists("/utility/t1")) Serial.println(F("testString file deleted\n"));
          menu();
          return;
        }
      }
    case 'w': // write textfile to SD card
      {
        //value = 65; // test file for now
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be altered by mistake")); // until a better solution is found
          flag = 1; SDfiles();
          return;
        }
        char fname[4]; byte inChar; filename = String(value); unsigned int i = 0;
        filename.toCharArray(fname, 4);
        File dataFile = SD.open(fname, FILE_WRITE);
        Serial.print(F("\nWaiting..."));
        delay(10);
        while (Serial.peek() < 1);
        Serial.println(F("OK, receiving data"));
        do
        {
          if (Serial.peek() > 0)
          {
            inChar = Serial.read(); if (inChar < 127) dataFile.write(inChar);
            i++; if (i > 150) Serial.print(F(".")); i = 0; // show something is happening
          }
        } while (inChar != 129); // 129 this is the best I can come up with. use an unused ANSI char to break out of the loop
        //                       // batchfile 129.bat ( @echo  >>%1 ) puts ANSI char 129 at end of a textfile. Then upload it.
        dataFile.close();
        Serial.println(); Serial.print(F("Data saved as file ")); Serial.println(fname); Serial.println();
        Serial.flush(); delay(100);
        flag = 1; SDfiles();
        return;
      }
    case 'x':
      {
        menu();
        return;
      }
    default:
      {
        Serial.println(F("Error - not an option\n"));
        flag = 1; SDfiles();
        return;
      }
  }
}

void printDirectory()
{
  unsigned int i = 0;
  File root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  if (ANSI == true) Serial.println(F("File\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize"));
  Serial.println();
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry) break;
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")
    {
      Serial.print(entry.name()); Serial.print("\t"); Serial.print(entry.size(), DEC); Serial.print("\t\t");
      i++;
      if (i > 4)
      {
        Serial.println();
        i = 0;
      }
    }
    entry.close();
  }
  root.close();
  Serial.println(); Serial.print(F("Files: ")); Serial.print(fileCount); Serial.println(); Serial.println();
}

void getChars()
{
  if (ANSI == true) Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  value = 0;
  do // wait till a key is pressed
  {
    digits = Serial.available();
  } while (digits < 1); // 1 == a key press
  keypress = Serial.read(); if (keypress > 64 && keypress < 97) keypress = keypress + 32;
  delay(key_wait);
  do
  {
    digits = Serial.available();
  } while (digits < 0); // I do not understand this
  value = Serial.parseInt();
  Serial.flush();
  if (ANSI == true) Serial.write(8); // backspace
}

void confirm_pitch()
{
  Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
  tone(8, pitch, 400); // give a sample
}
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Sketch uses 31,500 bytes (97%) of program storage space. Maximum is 32,256 bytes.
Global variables use 1,129 bytes (55%) of dynamic memory, leaving 919 bytes for local variables. Maximum is 2,048 bytes.

Code:
// Morse Receive Practice v2.9
// by flat5 June 17, 2015
// UK/US characters only
// choice of tone/notone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and Gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// version 2.6 is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 64 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// v2.7 & v2.8 Some SD card options added. This ver, some code cleanup and a feature or two added.
// v2.8.a changed the behaviour of some options
// v2.8.b added - and $ to the char* arrays. added array (size) variable to simplify adding new chars
// v2.9 adding a feature from Morse Machine. computer sends a char. you type the char correctly or computer sends it again
// to do:
// resolve file 0/no file problem. find a way to stop writing an SD file without an EOF marker. sort the SD file list in less than 2000 bytes

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
String Brag = "Morse Receive Practice v2.9 developed by Barry Block\n";
const int chipSelect = 4; // SD card select
byte echo;
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the terminal program matches!

byte ANSI = true; //false        // *** If you are not using an ANSI terminal program and the screen has weird characters
//                               //     at the beginning of some lines change this from true to false. now a menu item
unsigned int pitch = 830; //0    // *** 0 if using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int soft = 950;         // *** find a pitch that is quieter in your setup
unsigned int loud = 750;         // *** find a pitch that is louder  in your setup

unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers (Ex. 0,1,2,3 in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using external circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
unsigned int array = 54;         //     total number of Morse characters
unsigned int row = array;        // *** do NOT make this number larger than the array!

unsigned int elementWait = 1200 / wpm; char keypress; unsigned int value; unsigned int Length; unsigned int fileCount = 0;
String sendfile = ""; String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int flag = 0;
File root; String filename;

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "_";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "^"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";
const char str52[] PROGMEM = "-"; const char str53[] PROGMEM = "$";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51, str52, str53
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--"; const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";  const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----."; const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "..--.-"; const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "..--";   const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";  const char code52[] PROGMEM = "-....-"; const char code53[] PROGMEM = "...-..-";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51,
  code52, code53
};

char buffer[8]; // make sure this is large enough for the largest code string it must hold. $ == 7

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("SD card failed, or not present"));
      delay(3000);
    }
    else
    {
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
  // code_chart();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust pitch, p off, p7 decrement 10Hz, p8 increment 10Hz, p9 soft (preset) p99 loud (preset)"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-52)"));
  Serial.println(F("t select characters to send (52 max) or just Enter to clear 'TestString'"));
  if (SDready)
  {
    Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
    Serial.println(F("x SD card menu"));
  }
  Serial.println(F("z computer sends a character, you press the correct keyboard key"));
  Serial.println(F("n(row) send numbers"));
  Serial.println(F("l(row) send letters"));
  Serial.println(F("u(row) send punctuation characters or u77 (or >) to not send them"));
  Serial.println(F("c clear screen or to toggle this option: c(number)"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  if (fileCount > 0)
  {
    Serial.print(F(" SD files:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  getChars();
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': // clear screen if ANSI is supported
      {
        if (value > 0)
        {
          if (ANSI == true) ANSI = false;
          else ANSI = true;
          break;
        }
        refresh_Screen(); // an ANSI terminal is required for this to work
        break;
      }
    case 'd': // display code chart
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': // send random textfile from SD card
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) {
          echo = true;
        }
        else {
          echo = false;
        }
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': // keypress timeout
      {
        if (value < 11) key_wait = value * 1000;
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': // send the letter characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        if (value == 0 ) testStrRow = 26;
        else testStrRow = value;
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        Serial.print(F("\nTestString set to letters. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'm': case 'h': case '?': case 'i': // display menu
      {
        if (keypress == 'i' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': // send the number characters
      {
        testStr = "1234567890";
        if (value == 0 ) testStrRow = 10;
        else testStrRow = value;
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        Serial.print(F("\nTestString set to numbers. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'p': // pitch
      {
        if (value == 7)
        {
          pitch = pitch - 10;
          if (pitch > 24000 || pitch < 31) pitch = 31; // unsigned int rollover
          confirm_pitch();
          break;
        }
        if (value == 8)
        {
          pitch = pitch + 10;
          if (pitch > 24000) pitch = 24000;
          confirm_pitch();
          break;
        }
        if (value == 9) // find a pitch that is much lower in volume than others
        {
          pitch = soft;
          confirm_pitch();
          break;
        }
        if (value == 99) // find a pitch that is much higher in volume than others
        {
          pitch = 750;
          confirm_pitch();
          break;
        }
        if (value == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (value < 31 || value > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        pitch = value;
        confirm_pitch();
        break;
      }
    case 'r': // how many chars to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-")); Serial.print(array); Serial.print(F(", presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 53)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.print(F("Too high a number!(1-")); Serial.println(array);
          break;
        }
      }
    case 's': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': // send the punctuation characters or not
      {
        if (value > 76)
        {
          testStr = "1234567890abcdefghijklmnopqrstuvwxyz";
          Serial.println(F("TestString set to numbers & letters"));
          testStrRow = 36;
          break;
        }
        testStr = ",.?!:\"'=@_()^&;+-$";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        if (value == 0 ) testStrRow = 18;
        else testStrRow = value;
        if (testStrRow > 18)
        {
          Serial.println(F("1-18 characters!"));
          testStrRow = 18;
        }
        Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'w': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'x': // SD card sub menu
      {
        SDfilesMenu();
        break;
      }
    case 'z':
      {
        Morse_Machine();
      }
    default: break;
  }
}

void refresh_Screen() // requires an ANSI terminal. can be toggled. see menu display
{
  if (ANSI == true)
  {
    Serial.write(27); // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27); // ESC
    Serial.write("[H"); // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent and display answer. Heart of the program!
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(array); // 0 - 51, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 54 chars). no jerkyness so no need to build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}
void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i); // get char pointed to by i and return it as 'buffer'
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build(); // collect chars and put in testStr if they are legal. if not, return flag 1
  if (flag == 1)
  {
    flag = 0;
    return;
  }
  if (testStr != "") // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > array)       // is the string too long? larger than the array of Morse characters
  {
    Serial.print(F("Max:")); Serial.print(array); Serial.println(F(" characters - TestString cleared"));
    flag = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; h = char(testStr.charAt(t)); // overcoming the string vs char compile error by CASTING
    for (int i = t + 1; i < testStr.length(); i++)
    {
      if (h == char(testStr.charAt(i)))
      {
        Serial.println(F("Repeated character! - TestString cleared"));
        flag = 1;
        testStr = "";
        return;
      }
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    flag = 1; // set the flag
    for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer) // if a match is found for this char set a flag. don't check further
      {
        flag = 0;
        break;
      }
    }
    if (flag == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      flag = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  while (Serial.read() == '\r');
  Serial.flush();
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void word_space() {
  delay((elementWait * space * 2) + 1); // close enough, it's correct for the proper space of 3 times dit.
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  Serial.flush();
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 45; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 45; i < 54; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  getStr(i);
  Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i);
  Serial.print(buffer); Serial.print(" ");
}

void send_SDtext()
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  if (sendfile != "")
  {
    str = sendfile; sendfile = ""; // a file has been specifically chosen
  }
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 999 files + 0. 1000 files
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int)
{
  fileCount = 0;
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    String n = entry.name();
    if (n.toInt() != 0 || n == "0") // only include files starting with a number BUT file 0 will not be displayed unless exception is made
    {
      fileCount++;
      if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    }
    entry.close();
  }
  return fileCount;
}

void SDfilesMenu()
{
  Serial.flush();
  if (flag == 0) refresh_Screen();
  Serial.println(F("\nChoose Option:\n"));
  Serial.println(F("l list files"));
  Serial.println(F("r(filenumber) read file"));
  Serial.println(F("s(filenumber) send file"));
  Serial.println(F("d(filenumber) delete file"));
  Serial.println(F("w(filenumber) write file"));
  Serial.println(F("t1 (Load testString) t2 (Save testString) t3 (Delete testString)"));
  Serial.println(F("x return to main menu"));
  flag = 0;
  getChars();
  refresh_Screen();
  switch (keypress) // char
  {
    case 'd': // delete file
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be deleted by mistake\n"));
          flag = 1; SDfilesMenu();
          return;
        }
        File root;
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          root = SD.open("/");
          SD.remove(fname);
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" Deleted."));
          root.close();
          countRootFiles(root, fileCount);
          return;
        }
        else
        {
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" not found."));
        }
        flag = 1; SDfilesMenu();
        return;
      }
    case 'l': // list SD root files
      {
        printDirectory();
        flag = 1; SDfilesMenu();
        return;
      }
    case 'r': //read textfile
      {
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          refresh_Screen();
          Serial.print(F("File: ")); Serial.println(fname); Serial.println();
          File dataFile = SD.open(fname, FILE_READ);
          while (dataFile.available()) Serial.write(dataFile.read());
          dataFile.close();
        }
        else Serial.println(F("\nfile not found"));
        Serial.println();
        flag = 1; SDfilesMenu();
        return;
      }
    case 's': // send textfile
      {
        sendfile = String(value);
        byte e = echo;
        echo = true;
        send_SDtext(); // flag to echo the chars rather than show at the end
        echo = e;      // reset 'global' echo
        flag = 1; SDfilesMenu();
        return;
      }
    case 't': //teststring routines
      {
        if (value < 1 || value > 3)
        {
          Serial.println(F("\nNo such option. t1 load - t2 save - t3 delete"));
          flag = 1; SDfilesMenu();
          return;
        }
        File myFile;
        if (value == 1) // read file
        {
          myFile = SD.open("/utility/t1");
          if (myFile)
          {
            testStr = "";
            Serial.print(F("\nloading testStr: "));
            while (myFile.available()) // read from the file until there's nothing else in it
            {
              char a = (myFile.read()); // read a char and add to testStr
              testStr = testStr + a;
            }
            myFile.close(); // close the file
            testStrRow = testStr.length();
            Serial.println(testStr); Serial.println();
          }
          else
          {
            Serial.println(F("\nError opening /utility/t1\n")); // if the file didn't open, print an error
          }
          flag = 1; SDfilesMenu();
          return;
        }
        else if (value == 2) // write file
        {
          if (!SD.exists("utility/"))
          {
            SD.mkdir("utility");
          }
          SD.remove("/utility/t1");
          testStr.trim(); testStr.toLowerCase();
          myFile = SD.open("/utility/t1", FILE_WRITE);
          if (myFile) // if the file opened okay, write to it
          {
            Serial.print(F("\nSaving testString: "));
            myFile.print(testStr); // write the string to the file
            myFile.close(); // close the file
            Serial.println(testStr); Serial.println();
            testStrRow = testStr.length(); // store string length
          }
          else
          {
            Serial.println(F("error opening /utility/t1"));
          }
          menu();
          return;
        }
        else if (value == 3) // delete file
        {
          SD.remove("/utility/t1");
          delay(200); // just seemed like a good idea
          if (! SD.exists("/utility/t1")) Serial.println(F("testString file deleted\n"));
          menu();
          return;
        }
      }
    case 'w': // write textfile to SD card
      {
        //value = 65; // test file for now
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be altered by mistake")); // until a better solution is found
          flag = 1; SDfilesMenu();
          return;
        }
        char fname[4]; byte inChar; filename = String(value); unsigned int i = 0;
        filename.toCharArray(fname, 4);
        File dataFile = SD.open(fname, FILE_WRITE);
        Serial.print(F("\nWaiting..."));
        delay(10);
        while (Serial.peek() < 1);
        Serial.println(F("OK, receiving data"));
        do
        {
          if (Serial.peek() > 0)
          {
            inChar = Serial.read(); if (inChar < 127) dataFile.write(inChar);
            i++;
            if (i > 150)
            {
              Serial.print(F(".")); i = 0; // show something is happening
            }
          }
        } while (inChar != 129); // 129 this is the best I can come up with. use an unused ANSI char to break out of the loop
        //                       // batchfile 129.bat ( @echo  >>%1 ) puts ANSI char 129 at end of a textfile. Then upload it.
        dataFile.close();
        Serial.println(); Serial.print(F("Data saved as file ")); Serial.println(fname); Serial.println();
        delay(100); Serial.flush(); delay(100);
        flag = 1; SDfilesMenu();
        return;
      }
    case 'x':
      {
        menu();
        return;
      }
    default:
      {
        Serial.println(F("Error - not an option\n"));
        flag = 1; SDfilesMenu();
        return;
      }
  }
}

void printDirectory()
{
  unsigned int i = 0;
  File root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  if (ANSI == true) Serial.println(F("File\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize"));
  Serial.println();
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry) break;
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")
    {
      Serial.print(entry.name()); Serial.print("\t"); Serial.print(entry.size(), DEC); Serial.print("\t\t");
      i++;
      if (i > 4)
      {
        Serial.println();
        i = 0;
      }
    }
    entry.close();
  }
  root.close();
  Serial.println(); Serial.print(F("Files: ")); Serial.print(fileCount); Serial.println(); Serial.println();
}

void getChars()
{
  if (ANSI == true) Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  value = 0;
  do // wait till a key is pressed
  {
    digits = Serial.available();
  } while (digits < 1); // 1 == a key press
  keypress = Serial.read(); if (keypress > 64 && keypress < 97) keypress = keypress + 32; // upper to lower case
  delay(key_wait);
  do
  {
    digits = Serial.available();
  } while (digits < 0); // I do not understand this
  value = Serial.parseInt();
  Serial.flush();
  if (ANSI == true) Serial.write(8); // backspace
}

void confirm_pitch()
{
  Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
  tone(8, pitch, 400); // give a sample
}

void Morse_Machine()
{
  refresh_Screen(); code_chart();
  Serial.println(F("Press Enter to quit"));
  String y; char x; String u; byte t; String k; unsigned int w; unsigned int j;
  w = wpm; wpm = 30; elementWait = 1200 / wpm;
  do
  {
    if (testStr == "") // send any of the full array
    {
      int z = random(array);
      getStr(z);
      y = buffer; // char to send
      getCode(z);
      u = buffer; // code equivalent
    }
    else // send one of the teststring chars
    {
      int z = random(testStr.length());
      y = testStr.substring(z, z + 1);
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (y == buffer)
        {
          getCode(i);
          u = buffer;
          break;
        }
      }
    }
    while (k != "\r")
    {
      send_code(u);
      Serial.flush();
      getChars();
      k = String(keypress);
      if (k == y) break;
      if (k == "\r")
      {
        wpm = w; elementWait = 1200 / wpm;
        refresh_Screen(); menu();
        return;
      }
    }
  } while (k != "\r");
  return;
}
 
Last edited:

djsfantasi

Joined Apr 11, 2010
9,156
If any of your variables are in actuality static (as chipselect appears to be), you can use a #define to create an alias for the constant value, freeing up the storage used for the variable.

If any of your integer variables are always positive and will always have a value in the range of 0-255, you can use a data type of byte instead, saving a byte of memory.

I fear you will not save too much in this manner, but I offer it as a techbnique.

You can analyze your sketch by starting with an empty sketch, and adding your library includes, one at a time and note how much memory each one uses. Similarly, you can add in your code a piece or function at a time and note how much memory each uses (You will probably have to include stub functions for the sketch to compile) I think you may be surprised by what you find.

This will identify code/flash/EEPROM hogs. There may be cases where there is nothing you can do, but knowledge is power.
 

Thread Starter

flat5

Joined Nov 13, 2008
403
Thank you, djsfantasi. Are you suggesting I change 'unsigned int' to byte, where ever I can?

I changed 'const int chipSelect = 4;' to 'byte chipSelect = 4;'
The compiled sketch became 4 bytes bigger.

I'll read about #define.
Well, here is what Aduino has to say:
"In general, the const keyword is preferred for defining constants and should be used instead of #define."

Just tried const byte chipSelect...
Result was the same as const int.
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
I do need help with break, return, calling a function from a function.
My main loop is just a parser(). When the submenu SDfilesMenu() is called and I
stay there for a while calling it's routines the program gets unstable. Memory gets corrupted. I start to see wrong characters on screen and then it hangs. My guess is, the stack is being, what? corrupted, overloaded or something.

If someone would look and see how I'm using break and return maybe they could help me.
below line 730 where the routine starts.

Edit: Delete: Edit:
Break and return did not work to go back to a submenu/switch routine even if I nested the loop. Goto solved the problem. I'll try to explain.
void SDfilesMenu()
{ switch
case 'a' { print something; break;} <-- will not take me back to the top of SDfilesMenu()
using return does the same.

case 'a' { print something; SDfilesMenu();break; (or return) } will put me back to the top of the function but causes an instability. This may 'work' up to 13 times.

Goto label; does not require calling the function again and has no problem...yet.

Edit again: Maybe I should have wrapped all the case statements in while loops with a break at the end of each, or better, all the case statement in one while loop. I should try that but I'm happy with the label for now.
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
This has the improved recalling of the function. Function starts at line 731 (or did).
Code:
// Morse Receive Practice v2.9b
// by flat5 June 20, 2015
// UK/US characters only
// choice of tone/notone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and Gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// version 2.6 is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 64 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// v2.7 & v2.8 Some SD card options added. This ver, some code cleanup and a feature or two added.
// v2.8.a changed the behaviour of some options
// v2.8.b added - and $ to the char* arrays. added array (size) variable to simplify adding new chars
// v2.9 adding a feature from Morse Machine. computer sends a char. you type the char correctly or computer sends it again
// v.2.9a altered a routine that calls itself by using a label (goto) to improve stability
// v.2.9.b a few tweaks and corrections
// to do:
// resolve file 0/no file problem. find a way to stop writing an SD file without an EOF marker. sort the SD file list in less than 2000 bytes

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
String Brag = "Morse Receive Practice v2.9b developed by Barry Block\n";
const byte chipSelect = 4; // SD card select
byte echo;
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the terminal program matches!

byte ANSI = true; //false        // *** If you are not using an ANSI terminal program and the screen has weird characters
//                               //     at the beginning of some lines change this from true to false. now a menu item
unsigned int pitch = 830; //0    // *** 0 if using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int soft = 950;         // *** find a pitch that is quieter in your setup
unsigned int loud = 750;         // *** find a pitch that is louder  in your setup

unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers (Ex. 0,1,2,3 in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using external circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
unsigned int array = 54;         //     total number of Morse characters
unsigned int row = array;        // *** do NOT make this number larger than the array!

unsigned int elementWait = 1200 / wpm; char keypress; unsigned int value; unsigned int Length; unsigned int fileCount = 0;
String sendfile = ""; String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int flag = 0;
File root; String filename;

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "_";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "^"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";
const char str52[] PROGMEM = "-"; const char str53[] PROGMEM = "$";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51, str52, str53
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--"; const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";  const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----."; const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "..--.-"; const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "..--";   const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";  const char code52[] PROGMEM = "-....-"; const char code53[] PROGMEM = "...-..-";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51,
  code52, code53
};

char buffer[8]; // make sure this is large enough for the largest code string it must hold. $ == 7

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("SD card failed, or not present"));
      delay(3000);
    }
    else
    {
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
  // code_chart();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust pitch, p off, p7 decrement 10Hz, p8 increment 10Hz, p9 soft (preset) p99 loud (preset)"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-52)"));
  Serial.println(F("t select characters to send (52 max) or just Enter to clear 'TestString'"));
  if (SDready)
  {
    Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
    Serial.println(F("x SD card menu"));
  }
  Serial.println(F("z computer sends a character, you press the correct keyboard key"));
  Serial.println(F("n(row) send numbers"));
  Serial.println(F("l(row) send letters"));
  Serial.println(F("u(row) send punctuation characters or u77 (or >) to not send them"));
  Serial.println(F("c clear screen or to toggle this option: c(number)"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  if (fileCount > 0)
  {
    Serial.print(F(" SD files:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  getChars();
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': // clear screen if ANSI is supported
      {
        if (value > 0)
        {
          if (ANSI == true) ANSI = false;
          else ANSI = true;
          break;
        }
        refresh_Screen(); // an ANSI terminal is required for this to work
        break;
      }
    case 'd': // display code chart
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': // send random textfile from SD card
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) {
          echo = true;
        }
        else {
          echo = false;
        }
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': // keypress timeout
      {
        if (value < 11) key_wait = value * 1000;
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': // send the letter characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        if (value == 0 ) testStrRow = 26;
        else testStrRow = value;
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        Serial.print(F("\nTestString set to letters. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'm': case 'h': case '?': case 'i': // display menu
      {
        if (keypress == 'i' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': // send the number characters
      {
        testStr = "1234567890";
        if (value == 0 ) testStrRow = 10;
        else testStrRow = value;
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        Serial.print(F("\nTestString set to numbers. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'p': // pitch
      {
        if (value == 7)
        {
          pitch = pitch - 10;
          if (pitch > 24000 || pitch < 31) pitch = 31; // unsigned int rollover
          confirm_pitch();
          break;
        }
        if (value == 8)
        {
          pitch = pitch + 10;
          if (pitch > 24000) pitch = 24000;
          confirm_pitch();
          break;
        }
        if (value == 9) // find a pitch that is much lower in volume than others
        {
          pitch = soft;
          confirm_pitch();
          break;
        }
        if (value == 99) // find a pitch that is much higher in volume than others
        {
          pitch = 750;
          confirm_pitch();
          break;
        }
        if (value == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (value < 31 || value > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        pitch = value;
        confirm_pitch();
        break;
      }
    case 'r': // how many chars to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-")); Serial.print(array); Serial.print(F(", presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 53)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.print(F("Too high a number!(1-")); Serial.println(array);
          break;
        }
      }
    case 's': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': // send the punctuation characters or not
      {
        if (value > 76)
        {
          testStr = "1234567890abcdefghijklmnopqrstuvwxyz";
          Serial.println(F("TestString set to numbers & letters"));
          testStrRow = 36;
          break;
        }
        testStr = ",.?!:\"'=@_()^&;+-$";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        if (value == 0 ) testStrRow = 18;
        else testStrRow = value;
        if (testStrRow > 18)
        {
          Serial.println(F("1-18 characters!"));
          testStrRow = 18;
        }
        Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'w': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'x': // SD card sub menu
      {
        SDfilesMenu();
        break;
      }
    case 'z':
      {
        Morse_Machine();
      }
    default: break;
  }
}

void refresh_Screen() // requires an ANSI terminal. can be toggled. see menu display
{
  if (ANSI == true)
  {
    Serial.write(27);    // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27);    // ESC
    Serial.write("[H");  // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent and display answer. Heart of the program!
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(array); // 0 - 51, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 54 chars). no jerkyness so no need to build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}
void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i); // get char pointed to by i and return it as 'buffer'
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build(); // collect chars and put in testStr if they are legal. if not, return flag 1
  if (flag == 1)
  {
    flag = 0;
    return;
  }
  if (testStr != "") // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > array)       // is the string too long? larger than the array of Morse characters
  {
    Serial.print(F("Max:")); Serial.print(array); Serial.println(F(" characters - TestString cleared"));
    flag = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; h = char(testStr.charAt(t)); // overcoming the string vs char compile error by CASTING
    for (int i = t + 1; i < testStr.length(); i++)
    {
      if (h == char(testStr.charAt(i)))
      {
        Serial.println(F("Repeated character! - TestString cleared"));
        flag = 1;
        testStr = "";
        return;
      }
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    flag = 1; // set the flag
    for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer) // if a match is found for this char set a flag. don't check further
      {
        flag = 0;
        break;
      }
    }
    if (flag == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      flag = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  while (Serial.read() == '\r');
  Serial.flush();
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void word_space() {
  delay((elementWait * space * 2) + 1); // close enough, it's correct for the proper space of 3 times dit.
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  Serial.flush();
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 45; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 45; i < 54; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  getStr(i);
  Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i);
  Serial.print(buffer); Serial.print(" ");
}

void send_SDtext()
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  if (sendfile != "")
  {
    str = sendfile; sendfile = ""; // a file has been specifically chosen
  }
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 999 files + 0. 1000 files
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int)
{
  fileCount = 0;
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    String n = entry.name();
    if (n.toInt() != 0 || n == "0") // only include files starting with a number BUT file 0 will not be displayed unless exception is made
    {
      fileCount++;
      if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    }
    entry.close();
  }
  return fileCount;
}

void SDfilesMenu()
{
label:
  while (Serial.available()) Serial.read();
  if (flag == 0) refresh_Screen();
  Serial.println(F("\nChoose Option:\n"));
  Serial.println(F("l list files"));
  Serial.println(F("r(filenumber) read file"));
  Serial.println(F("s(filenumber) send file"));
  Serial.println(F("d(filenumber) delete file"));
  Serial.println(F("w(filenumber) write file"));
  Serial.println(F("t1 (Load testString) t2 (Save testString) t3 (Delete testString)"));
  Serial.println(F("x return to main menu"));
  flag = 0;
  getChars();
  refresh_Screen();
  switch (keypress) // char
  {
    case 'd': // delete file
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be deleted by mistake\n"));
          flag = 1;
          goto label;
        }
        File root;
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          root = SD.open("/");
          SD.remove(fname);
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" Deleted."));
          root.close();
          countRootFiles(root, fileCount);
          goto label;
        }
        else
        {
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" not found."));
        }
        flag = 1;
        goto label;
      }
    case 'l': // list SD root files
      {
        printDirectory();
        flag = 1;
        goto label;
      }
    case 'r': //read textfile
      {
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          refresh_Screen();
          Serial.print(F("File: ")); Serial.println(fname); Serial.println();
          File dataFile = SD.open(fname, FILE_READ);
          while (dataFile.available()) Serial.write(dataFile.read());
          dataFile.close();
        }
        else Serial.println(F("\nfile not found"));
        Serial.println();
        flag = 1;
        goto label;
      }
    case 's': // send textfile
      {
        sendfile = String(value);
        byte e = echo;
        echo = true;
        send_SDtext(); // flag to echo the chars rather than show at the end
        echo = e;      // reset 'global' echo
        flag = 1;
        goto label;
      }
    case 't': //teststring routines
      {
        if (value < 1 || value > 3)
        {
          Serial.println(F("\nNo such option. t1 load - t2 save - t3 delete"));
          flag = 1;
          goto label;
        }
        File myFile;
        if (value == 1) // read file
        {
          myFile = SD.open("/utility/t1");
          if (myFile)
          {
            testStr = "";
            Serial.print(F("\nloading testStr: "));
            while (myFile.available()) // read from the file until there's nothing else in it
            {
              char a = (myFile.read()); // read a char and add to testStr
              testStr = testStr + a;
            }
            myFile.close(); // close the file
            testStrRow = testStr.length();
            Serial.println(testStr); Serial.println();
          }
          else
          {
            Serial.println(F("\nError opening /utility/t1\n")); // if the file didn't open, print an error
          }
          flag = 1;
          goto label;
        }
        else if (value == 2) // write file
        {
          if (!SD.exists("utility/"))
          {
            SD.mkdir("utility");
          }
          SD.remove("/utility/t1");
          testStr.trim(); testStr.toLowerCase();
          myFile = SD.open("/utility/t1", FILE_WRITE);
          if (myFile) // if the file opened okay, write to it
          {
            Serial.print(F("\nSaving testString: "));
            myFile.print(testStr); // write the string to the file
            myFile.close(); // close the file
            Serial.println(testStr); Serial.println();
            testStrRow = testStr.length(); // store string length
          }
          else
          {
            Serial.println(F("error opening /utility/t1"));
          }
          goto label;
        }
        else if (value == 3) // delete file
        {
          SD.remove("/utility/t1");
          delay(200); // just seemed like a good idea
          if (! SD.exists("/utility/t1")) Serial.println(F("testString file deleted\n"));
          goto label;
        }
      }
    case 'w': // write textfile to SD card
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be altered by mistake")); // until a better solution is found
          flag = 1;
          goto label;
        }
        char fname[4]; byte inChar; filename = String(value); unsigned int i = 0;
        filename.toCharArray(fname, 4);
        File dataFile = SD.open(fname, FILE_WRITE);
        Serial.print(F("\nWaiting..."));
        delay(10);
        while (Serial.peek() < 1);
        Serial.println(F("OK, receiving data"));
        do
        {
          if (Serial.peek() > 0)
          {
            inChar = Serial.read(); if (inChar < 127) dataFile.write(inChar);
            i++;
            if (i > 150)
            {
              Serial.print(F(".")); i = 0; // show something is happening
            }
          }
        } while (inChar != 129); // 129 this is the best I can come up with. use an unused ANSI char to break out of the loop
        //                       // batchfile 129.bat ( @echo  >>%1 ) puts ANSI char 129 at end of a textfile. Then upload it.
        dataFile.close();
        Serial.println(); Serial.print(F("Data saved as file ")); Serial.println(fname); Serial.println();
        flag = 1;
        goto label;
      }
    case 'x':
      {
        menu();
        return;
      }
    default:
      {
        Serial.println(F("\nError - not an option\n"));
        flag = 1;
        goto label;
      }
  }
}

void printDirectory()
{
  unsigned int i = 0;
  File root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  if (ANSI == true) Serial.println(F("File\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize"));
  Serial.println();
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry) break;
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")
    {
      Serial.print(entry.name()); Serial.print("\t"); Serial.print(entry.size(), DEC); Serial.print("\t\t");
      i++;
      if (i > 4)
      {
        Serial.println();
        i = 0;
      }
    }
    entry.close();
  }
  root.close();
  Serial.println(); Serial.print(F("Files: ")); Serial.print(fileCount); Serial.println(); Serial.println();
}

void getChars()
{
  if (ANSI == true) Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  value = 0;
  do // wait till a key is pressed
  {
    digits = Serial.available();
  } while (digits < 1); // 1 == a key press
  keypress = Serial.read(); if (keypress > 64 && keypress < 97) keypress = keypress + 32; // upper to lower case
  delay(key_wait); // we now have the option
  do               // collect the value if any
  {
    digits = Serial.available();
  } while (digits < 0); // I do not understand this. why not 1?
  value = Serial.parseInt();
  Serial.flush();
  if (ANSI == true) Serial.write(8); // backspace
}

void confirm_pitch()
{
  Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
  tone(8, pitch, 400); // give a sample
}


void Morse_Machine()
{
  refresh_Screen(); code_chart();
  Serial.println(F("Press Enter to quit"));
  String y; char x; String u; byte t; String k; unsigned int w; unsigned int j;
  w = wpm; wpm = 25; elementWait = 1200 / wpm;
  do
  {
    if (testStr == "") // send any of the full array
    {
      unsigned int z = random(array);
      getStr(z);
      y = buffer; // char to send
      getCode(z);
      u = buffer; // code equivalent
    }
    else // send one of the teststring chars
    {
      unsigned int z = random(testStr.length());
      y = testStr.substring(z, z + 1);
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (y == buffer)
        {
          getCode(i);
          u = buffer;
          break;
        }
      }
    }
    while (k != "\r")
    {
      if (y == "_" || y == "^") break; // terminal progs I have tested have a problem with these characters
      send_code(u);
      Serial.flush();
      getChars();
      k = String(keypress);
      if (k == y) break;
      if (k == " ")
      {
        Serial.print(F("answer is: ")); Serial.println(y);
      }
      if (k == "\r")
      {
        wpm = w; elementWait = 1200 / wpm;
        refresh_Screen(); menu();
        return;
      }
    }
  } while (k != "\r");
  return;
}
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
I want to add a Control Structure to the C++ language. what if ()

Sketch uses 31,252 bytes (96%) of program storage space. Maximum is 32,256 bytes.
Global variables use 1,136 bytes (55%) of dynamic memory, leaving 912 bytes for local variables. Maximum is 2,048 bytes.

Code:
// Morse Receive Practice v2.9c
// by flat5 June 21, 2015
// UK/US characters only
// choice of tone/notone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and Gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// version 2.6 is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 64 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// v2.7 & v2.8 Some SD card options added. This ver, some code cleanup and a feature or two added.
// v2.8.a changed the behaviour of some options
// v2.8.b added - and $ to the char* arrays. added array (size) variable to simplify adding new chars
// v2.9 adding a feature from Morse Machine. computer sends a char. you type the char correctly or computer sends it again
// v.2.9a altered a routine that calls itself by using a label (goto) to improve stability
// v.2.9.b a few tweaks and corrections
// v2.9.c Morse Machine now does not repeat chars. sends string and makes a new one
// to do:
// resolve file 0/no file problem. find way to stop writing an SD file without EOF marker.

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
String Brag = "Morse Receive Practice v2.9c developed by Barry Block\n";
const byte chipSelect = 4; // SD card select
byte echo;
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the terminal program matches!

byte ANSI = true; //false        // *** If you are not using an ANSI terminal program and the screen has weird characters
//                               //     at the beginning of some lines change this from true to false. now a menu item
unsigned int pitch = 830; //0    // *** 0 if using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int soft = 950;         // *** find a pitch that is quieter in your setup
unsigned int loud = 750;         // *** find a pitch that is louder  in your setup

unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers (Ex. 0,1,2,3 in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using external circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
unsigned int array = 54;         //     total number of Morse characters
unsigned int row = array;        // *** do NOT make this number larger than the array!

unsigned int elementWait = 1200 / wpm; char keypress; unsigned int value; unsigned int Length; unsigned int fileCount = 0;
String sendfile = ""; String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int flag = 0;
File root; String filename;

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "-";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "$"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";
const char str52[] PROGMEM = "_"; const char str53[] PROGMEM = "^";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51, str52, str53
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--";  const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";   const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----.";  const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "-....-";  const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "...-..-"; const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";   const char code52[] PROGMEM = "..--.-"; const char code53[] PROGMEM = "..--";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51,
  code52, code53
};

char buffer[8]; // make sure this is large enough for the largest code string it must hold. $ == 7

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    Serial.println(F("Waking SD card"));
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("SD card failed, or not present"));
      delay(3000);
    }
    else
    {
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
  // code_chart();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust pitch, p off, p7 decrement 10Hz, p8 increment 10Hz, p9 soft (preset) p99 loud (preset)"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-52)"));
  Serial.println(F("t select characters to send (52 max) or just Enter to clear 'TestString'"));
  if (SDready)
  {
    Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
    Serial.println(F("x SD card menu"));
  }
  Serial.println(F("z computer sends a character, you press the correct keyboard key"));
  Serial.println(F("n(row) send numbers"));
  Serial.println(F("l(row) send letters"));
  Serial.println(F("u(row) send punctuation characters or u77 (or >) to not send them"));
  Serial.println(F("c clear screen or to toggle this option: c(number)"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  if (fileCount > 0)
  {
    Serial.print(F(" SD files:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  getChars();
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': // clear screen if ANSI is supported
      {
        if (value > 0)
        {
          if (ANSI == true) ANSI = false;
          else ANSI = true;
          break;
        }
        refresh_Screen(); // an ANSI terminal is required for this to work
        break;
      }
    case 'd': // display code chart
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': // send random textfile from SD card
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) {
          echo = true;
        }
        else {
          echo = false;
        }
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': // keypress timeout
      {
        if (value < 11) key_wait = value * 1000;
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': // send the letter characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        if (value == 0 ) testStrRow = 26;
        else testStrRow = value;
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        Serial.print(F("\nTestString set to letters. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'm': case 'h': case '?': case 'i': // display menu
      {
        if (keypress == 'i' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': // send the number characters
      {
        testStr = "1234567890";
        if (value == 0 ) testStrRow = 10;
        else testStrRow = value;
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        Serial.print(F("\nTestString set to numbers. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'p': // pitch
      {
        if (value == 7)
        {
          pitch = pitch - 10;
          if (pitch > 24000 || pitch < 31) pitch = 31; // unsigned int rollover
          confirm_pitch();
          break;
        }
        if (value == 8)
        {
          pitch = pitch + 10;
          if (pitch > 24000) pitch = 24000;
          confirm_pitch();
          break;
        }
        if (value == 9) // find a pitch that is much lower in volume than others
        {
          pitch = soft;
          confirm_pitch();
          break;
        }
        if (value == 99) // find a pitch that is much higher in volume than others
        {
          pitch = 750;
          confirm_pitch();
          break;
        }
        if (value == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (value < 31 || value > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        pitch = value;
        confirm_pitch();
        break;
      }
    case 'r': // how many chars to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-")); Serial.print(array); Serial.print(F(", presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 53)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.print(F("Too high a number!(1-")); Serial.println(array);
          break;
        }
      }
    case 's': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': // send the punctuation characters or not
      {
        if (value > 76)
        {
          testStr = "1234567890abcdefghijklmnopqrstuvwxyz";
          Serial.println(F("TestString set to numbers & letters"));
          if (value == 0 ) testStrRow = 36;
          else testStrRow = value;
          if (testStrRow > 36)
          {
            Serial.println(F("1-36 characters!"));
            testStrRow = 36;
          }
          Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
          break;
        }
        testStr = ",.?!:\"'=@_()^&;+-$";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        if (value == 0 ) testStrRow = 18;
        else testStrRow = value;
        if (testStrRow > 18)
        {
          Serial.println(F("1-18 characters!"));
          testStrRow = 18;
        }
        Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'w': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'x': // SD card sub menu
      {
        SDfilesMenu();
        break;
      }
    case 'z':
      {
        Morse_Machine();
        break;
      }
    default: break;
  }
}

void refresh_Screen() // requires an ANSI terminal. can be toggled. see menu display
{
  if (ANSI == true)
  {
    Serial.write(27);    // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27);    // ESC
    Serial.write("[H");  // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent and display answer. Heart of the program!
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(array); // 0 - 53, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 54 chars). no jerkyness so no need to build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}
void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i); // get char pointed to by i and return it as 'buffer'
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build();    // collect chars and put in testStr if they are legal. if not, return flag 1
  if (flag == 1)
  {
    flag = 0;
    return;
  }
  if (testStr != "")   // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > array)       // is the string too long? larger than the array of Morse characters
  {
    Serial.print(F("Max:")); Serial.print(array); Serial.println(F(" characters - TestString cleared"));
    flag = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; h = char(testStr.charAt(t)); // overcoming the string vs char compile error by CASTING
    for (int i = t + 1; i < testStr.length(); i++)
    {
      if (h == char(testStr.charAt(i)))
      {
        Serial.println(F("Repeated character! - TestString cleared"));
        flag = 1;
        testStr = "";
        return;
      }
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    flag = 1; // set the flag
    for (unsigned int i = 0; i < array; i++)          // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer)      // if a match is found for this char set a flag. don't check further
      {
        flag = 0;
        break;
      }
    }
    if (flag == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      flag = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x)
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() {
  delay(elementWait * space);
}

void word_space() {
  delay((elementWait * space * 2) + 1); // close enough, it's correct for the proper space of 3 times dit.
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart()
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 45; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 45; i < 54; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i)
{
  getStr(i);
  Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i);
  Serial.print(buffer); Serial.print(" ");
}

void send_SDtext()
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  if (sendfile != "")
  {
    str = sendfile; sendfile = ""; // a file has been specifically chosen
  }
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 999 files + 0. 1000 files
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int)
{
  fileCount = 0;
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    String n = entry.name();
    if (n.toInt() != 0 || n == "0") // only include files starting with a number BUT file 0 will not be displayed unless exception is made
    {
      fileCount++;
      if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    }
    entry.close();
  }
  return fileCount;
}

void SDfilesMenu()
{
label:
  while (Serial.available()) Serial.read();
  if (flag == 0) refresh_Screen();
  Serial.println(F("\nChoose Option:\n"));
  Serial.println(F("l list files"));
  Serial.println(F("r(filenumber) read file"));
  Serial.println(F("s(filenumber) send file"));
  Serial.println(F("d(filenumber) delete file"));
  Serial.println(F("w(filenumber) write file"));
  Serial.println(F("t1 (Load testString) t2 (Save testString) t3 (Delete testString)"));
  Serial.println(F("x return to main menu"));
  flag = 0;
  getChars();
  refresh_Screen();
  switch (keypress) // char
  {
    case 'd': // delete file
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be deleted by mistake\n"));
          flag = 1;
          goto label;
        }
        File root;
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          root = SD.open("/");
          SD.remove(fname);
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" Deleted."));
          root.close();
          countRootFiles(root, fileCount);
          goto label;
        }
        else
        {
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" not found."));
        }
        flag = 1;
        goto label;
      }
    case 'l': // list SD root files
      {
        printDirectory();
        flag = 1;
        goto label;
      }
    case 'r': //read textfile
      {
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          refresh_Screen();
          Serial.print(F("File: ")); Serial.println(fname); Serial.println();
          File dataFile = SD.open(fname, FILE_READ);
          while (dataFile.available()) Serial.write(dataFile.read());
          dataFile.close();
        }
        else Serial.println(F("\nfile not found"));
        Serial.println();
        flag = 1;
        goto label;
      }
    case 's': // send textfile
      {
        sendfile = String(value);
        byte e = echo;
        echo = true;
        send_SDtext(); // flag to echo the chars rather than show at the end
        echo = e;      // reset 'global' echo
        flag = 1;
        goto label;
      }
    case 't': //teststring routines
      {
        if (value < 1 || value > 3)
        {
          Serial.println(F("\nNo such option. t1 load - t2 save - t3 delete"));
          flag = 1;
          goto label;
        }
        File myFile;
        if (value == 1) // read file
        {
          myFile = SD.open("/utility/t1");
          if (myFile)
          {
            testStr = "";
            Serial.print(F("\nloading testStr: "));
            while (myFile.available()) // read from the file until there's nothing else in it
            {
              char a = (myFile.read()); // read a char and add to testStr
              testStr = testStr + a;
            }
            myFile.close(); // close the file
            testStrRow = testStr.length();
            Serial.println(testStr); Serial.println();
          }
          else
          {
            Serial.println(F("\nError opening /utility/t1\n")); // if the file didn't open, print an error
          }
          flag = 1;
          goto label;
        }
        else if (value == 2) // write file
        {
          if (!SD.exists("utility/"))
          {
            SD.mkdir("utility");
          }
          SD.remove("/utility/t1");
          testStr.trim(); testStr.toLowerCase();
          myFile = SD.open("/utility/t1", FILE_WRITE);
          if (myFile) // if the file opened okay, write to it
          {
            Serial.print(F("\nSaving testString: "));
            myFile.print(testStr); // write the string to the file
            myFile.close(); // close the file
            Serial.println(testStr); Serial.println();
            testStrRow = testStr.length(); // store string length
          }
          else
          {
            Serial.println(F("error opening /utility/t1"));
          }
          goto label;
        }
        else if (value == 3) // delete file
        {
          SD.remove("/utility/t1");
          delay(200); // just seemed like a good idea
          if (! SD.exists("/utility/t1")) Serial.println(F("testString file deleted\n"));
          goto label;
        }
      }
    case 'w': // write textfile to SD card
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be altered by mistake")); // until a better solution is found
          flag = 1;
          goto label;
        }
        char fname[4]; byte inChar; filename = String(value); unsigned int i = 0;
        filename.toCharArray(fname, 4);
        File dataFile = SD.open(fname, FILE_WRITE);
        Serial.print(F("\nWaiting..."));
        delay(10);
        while (Serial.peek() < 1);
        Serial.println(F("OK, receiving data"));
        do
        {
          if (Serial.peek() > 0)
          {
            inChar = Serial.read(); if (inChar < 127) dataFile.write(inChar);
            i++;
            if (i > 150)
            {
              Serial.print(F(".")); i = 0; // show something is happening
            }
          }
        } while (inChar != 129); // 129 this is the best I can come up with. use an unused ANSI char to break out of the loop
        //                       // batchfile 129.bat ( @echo  >>%1 ) puts ANSI char 129 at end of a textfile. Then upload it.
        dataFile.close();
        Serial.println(); Serial.print(F("Data saved as file ")); Serial.println(fname); Serial.println();
        flag = 1;
        goto label;
      }
    case 'x':
      {
        menu();
        return;
      }
    default:
      {
        Serial.println(F("\nError - not an option\n"));
        flag = 1;
        goto label;
      }
  }
}

void printDirectory()
{
  unsigned int i = 0;
  File root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  if (ANSI == true) Serial.println(F("File\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize"));
  Serial.println();
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry) break;
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")
    {
      Serial.print(entry.name()); Serial.print("\t"); Serial.print(entry.size(), DEC); Serial.print("\t\t");
      i++;
      if (i > 4)
      {
        Serial.println();
        i = 0;
      }
    }
    entry.close();
  }
  root.close();
  Serial.println(); Serial.print(F("Files: ")); Serial.print(fileCount); Serial.println(); Serial.println();
}

void getChars()
{
  if (ANSI == true) Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  value = 0;
  do // wait till a key is pressed
  {
    digits = Serial.available();
  } while (digits < 1); // 1 == a key press
  keypress = Serial.read(); if (keypress > 64 && keypress < 97) keypress = keypress + 32; //toLowerCase() is not for char
  delay(key_wait); // we now have the option (keypress)
  do               // collect the value if any
  {
    digits = Serial.available();
  } while (digits < 0); // I do not understand this. why not 1?
  value = Serial.parseInt();
  Serial.flush();
  if (ANSI == true) Serial.write(8); // backspace. remove prompt '<'
}

void confirm_pitch()
{
  Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
  tone(8, pitch, 400); // give a sample
}


void Morse_Machine()
{
  refresh_Screen(); code_chart();
  Serial.println(F("Press Enter to quit"));
  byte w = wpm; wpm = 25; elementWait = 1200 / wpm; // store wpm. set wpm for this routine.
  String a; String c; String s; String k; byte flag = 0; unsigned int u;
label:
  if (testStr == "") // build string of all chars & set a flag to kill the string on exit
  {
    flag = 1;
    for (unsigned int i = 0; i < array - 2; i++)         // problem with _ and ^ and keyboard input
    {
      getStr(i);
      testStr = testStr + buffer;                        // string of chars to work with
    }
  }
  answers = "";
  for (unsigned int t = 0; t < testStr.length(); t++)    // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]);
    }
  }                                                      // answers is testStr randomized. ready to send each char one at a time
  u = 0;
  while (true)
  {
    if (u == testStr.length()) goto label;       // spent 2 days trying not to use goto. sorry I did. best way to get out of a nest
    for (unsigned int i = 0; i < array - 2; i++)
    {
      a = answers.substring(u, u + 1);           // get and send a char
      getStr(i);
      if (a == buffer)
      {
        s = buffer; // Morse char
        getCode(i);
        c = buffer; // Morse string
        while (true)
        {
          send_code(c);
          getChars();
          k = String(keypress);
          if (k == s)
          {
            u++;
            break;
          }
          if (k == " ")
          {
            Serial.print(F("answer is: ")); Serial.println(s);
          }
          if (k == "\r")
          {
            if (flag == 1)
            {
              flag = 0;
              testStr = "";
            }
            wpm = w; elementWait = 1200 / wpm;
            refresh_Screen(); menu();
            return;
          }
        }
      }
    }
  }
}
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Code:
// Morse Receive Practice v2.9d
// by flat5 June 26, 2015
// UK/US characters only
// choice of tone/noTone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and Gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// version 2.6 is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 98 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// v2.7 & v2.8 Some SD card options added. This ver, some code cleanup and a feature or two added.
// v2.8.a changed the behaviour of some options
// v2.8.b added - and $ to the char* arrays. added array (size) variable to simplify adding new chars
// v2.9 adding a feature from Morse Machine. computer sends a char. you type the char correctly or computer sends it again
// v.2.9a altered a routine that calls itself by using a label (goto) to improve stability
// v.2.9.b a few tweaks and corrections
// v2.9.c Morse Machine now does not repeat chars. sends string and makes a new one
// v2.9.d New Morse Machine routine added for the punctuation characters. Send two of them 10 times each then add a third character.
// to do:
// resolve file 0/no file problem. find way to stop writing an SD file without EOF marker. or just move on to another program :-)

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
const String Brag = "Morse Receive Practice v2.9d developed by Barry Block\n";
const byte chipSelect = 4; // SD card select
byte echo;
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the terminal program matches!

byte ANSI = true; //false        // *** If you are not using an ANSI terminal program and the screen has weird characters
//                               //     at the beginning of some lines change this from true to false. now a menu item
unsigned int pitch = 830; //0    // *** 0 if using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int soft = 950;         // *** find a pitch that is quieter in your setup
unsigned int loud = 750;         // *** find a pitch that is louder  in your setup

unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers (Ex. 0,1,2,3 in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using external circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
const unsigned int array = 54;   //     total number of Morse characters
unsigned int row = array;        // *** do NOT make this number larger than the array!

unsigned int elementWait = 1200 / wpm; char keypress; unsigned int value; unsigned int fileCount = 0;
String sendfile = ""; String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int flag = 0;
File root; String filename; String holdStr;

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "-";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "$"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";
const char str52[] PROGMEM = "_"; const char str53[] PROGMEM = "^";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51, str52, str53
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--";  const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";   const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----.";  const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "-....-";  const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "...-..-"; const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";   const char code52[] PROGMEM = "..--.-"; const char code53[] PROGMEM = "..--";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51,
  code52, code53
};

char buffer[8]; // make sure this is large enough for the largest code string it must hold. $ == 7

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    Serial.println(F("Waking SD card"));
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("SD card not found"));
      delay(3000);
    }
    else
    {
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust pitch, p off, p7 decrement 10Hz, p8 increment 10Hz, p9 soft (preset) p99 loud (preset)"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-54)"));
  Serial.println(F("t select characters to send (54 max) or just Enter to clear 'TestString'"));
  if (SDready)
  {
    Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
    Serial.println(F("x SD card menu"));
  }
  Serial.println(F("z computer sends a character, you press the correct keyboard key"));
  Serial.println(F("z(number) special routine to learn the puncuation characters"));
  Serial.println(F("n(row) send numbers"));
  Serial.println(F("l(row) send letters"));
  Serial.println(F("u(row) send punctuation characters or u77 (or >) to not send them"));
  Serial.println(F("c clear screen or to toggle this option: c(number)"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  if (fileCount > 0)
  {
    Serial.print(F(" SD files:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  getChars();
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': // clear screen if ANSI is supported
      {
        if (value > 0)
        {
          if (ANSI == true) ANSI = false;
          else ANSI = true;
          break;
        }
        refresh_Screen(); // an ANSI terminal is required for this to work
        break;
      }
    case 'd': // display code chart
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': // send random textfile from SD card
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) {
          echo = true;
        }
        else {
          echo = false;
        }
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': // keypress timeout
      {
        if (value < 11) key_wait = value * 1000;
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': // send the letter characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        if (value == 0 ) testStrRow = 26;
        else testStrRow = value;
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        Serial.print(F("\nTestString set to letters. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'm': case 'h': case '?': case 'i': // display menu
      {
        if (keypress == 'i' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': // send the number characters
      {
        testStr = "1234567890";
        if (value == 0 ) testStrRow = 10;
        else testStrRow = value;
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        Serial.print(F("\nTestString set to numbers. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'p': // pitch
      {
        if (value == 7)
        {
          pitch = pitch - 10;
          if (pitch > 24000 || pitch < 31) pitch = 31; // unsigned int rollover
          confirm_pitch();
          break;
        }
        if (value == 8)
        {
          pitch = pitch + 10;
          if (pitch > 24000) pitch = 24000;
          confirm_pitch();
          break;
        }
        if (value == 9) // find a pitch that is much lower in volume than others
        {
          pitch = soft;
          confirm_pitch();
          break;
        }
        if (value == 99) // find a pitch that is much higher in volume than others
        {
          pitch = 750;
          confirm_pitch();
          break;
        }
        if (value == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (value < 31 || value > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        pitch = value;
        confirm_pitch();
        break;
      }
    case 'r': // how many chars to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-")); Serial.print(array); Serial.print(F(", presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 53)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.print(F("Too high a number!(1-")); Serial.println(array);
          break;
        }
      }
    case 's': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': // send the punctuation characters or not
      {
        if (value > 76)
        {
          testStr = "1234567890abcdefghijklmnopqrstuvwxyz";
          Serial.println(F("TestString set to numbers & letters"));
          if (value == 0 ) testStrRow = 36;
          else testStrRow = value;
          if (testStrRow > 36)
          {
            Serial.println(F("1-36 characters!"));
            testStrRow = 36;
          }
          Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
          break;
        }
        testStr = ",.?!:\"'=@_()^&;+-$";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        if (value == 0 ) testStrRow = 18;
        else testStrRow = value;
        if (testStrRow > 18)
        {
          Serial.println(F("1-18 characters!"));
          testStrRow = 18;
        }
        Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'w': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'x': // SD card sub menu
      {
        SDfilesMenu();
        break;
      }
    case 'z':
      {
        if (value > 0) flag = 2; // special case
        Morse_Machine();
        break;
      }
    default: break;
  }
}

void refresh_Screen() // requires an ANSI terminal. can be toggled. see menu display
{
  if (ANSI == true)
  {
    Serial.write(27);    // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27);    // ESC
    Serial.write("[H");  // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent and display answer. Heart of the program!
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(array); // 0 - 53, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 54 chars). no jerkyness so no need to pre-build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}
void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i); // get char pointed to by i and return it as 'buffer'
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build();    // collect chars and put in testStr if they are legal. if not, return flag 1
  if (flag == 1)
  {
    flag = 0;
    return;
  }
  if (testStr != "")   // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > array)       // is the string too long? larger than the array of Morse characters
  {
    Serial.print(F("Max:")); Serial.print(array); Serial.println(F(" characters - TestString cleared"));
    flag = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; h = char(testStr.charAt(t)); // overcoming the string vs char compile error by CASTING
    for (int i = t + 1; i < testStr.length(); i++)
    {
      if (h == char(testStr.charAt(i)))
      {
        Serial.println(F("Repeated character! - TestString cleared"));
        flag = 1;
        testStr = "";
        return;
      }
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    flag = 1; // set the flag
    for (unsigned int i = 0; i < array; i++)          // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer)      // if a match is found for this char set a flag. don't check further
      {
        flag = 0;
        break;
      }
    }
    if (flag == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      flag = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x) // actually send the sounds
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() { // silence between chars
  delay(elementWait * space);
}

void word_space() {  // silence between words
  delay((elementWait * space * 2) + 1); // close enough, it's correct for the proper space of 3 times dit.
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart() // show all Morse chars on screen
{
  unsigned int i;
  Serial.println(F("letters:"));
  for (i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("numbers:"));
  for (i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  Serial.println(F("punctuation:"));
  for (i = 36; i < 45; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (i = 45; i < 54; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void print_line(unsigned int i) // with the above to display chart
{
  getStr(i);
  Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i);
  Serial.print(buffer); Serial.print(" ");
}

void send_SDtext() // send and show a random text file stored on the SD card
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  if (sendfile != "")
  {
    str = sendfile; sendfile = ""; // a file has been specifically chosen
  }
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 999 files + 0. 1000 files
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i) // copy to buffer a string (one char) stored in flash memory (a)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i) // copy to buffer a string stored in flash memory (.-)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int) // get number of text files in SD root dir
{
  fileCount = 0;
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    String n = entry.name();
    if (n.toInt() != 0 || n == "0") // only include files starting with a number BUT file 0 will not be displayed unless exception is made
    {
      fileCount++;
      if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    }
    entry.close();
  }
  return fileCount;
}

void SDfilesMenu() // SD file options menu & routines
{
label:
  while (Serial.available()) Serial.read();
  if (flag == 0) refresh_Screen();
  Serial.println(F("\nChoose Option:\n"));
  Serial.println(F("l list files"));
  Serial.println(F("r(filenumber) read file"));
  Serial.println(F("s(filenumber) send file"));
  Serial.println(F("d(filenumber) delete file"));
  Serial.println(F("w(filenumber) write file"));
  Serial.println(F("t1 (Load testString) t2 (Save testString) t3 (Delete testString)"));
  Serial.println(F("x return to main menu"));
  flag = 0;
  getChars();
  refresh_Screen();
  switch (keypress) // char
  {
    case 'd': // delete file
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be deleted by mistake\n"));
          flag = 1;
          goto label;
        }
        File root;
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          root = SD.open("/");
          SD.remove(fname);
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" Deleted."));
          root.close();
          countRootFiles(root, fileCount);
          goto label;
        }
        else
        {
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" not found."));
        }
        flag = 1;
        goto label;
      }
    case 'l': // list SD root files
      {
        printDirectory();
        flag = 1;
        goto label;
      }
    case 'r': //read textfile
      {
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          refresh_Screen();
          Serial.print(F("File: ")); Serial.println(fname); Serial.println();
          File dataFile = SD.open(fname, FILE_READ);
          while (dataFile.available()) Serial.write(dataFile.read());
          dataFile.close();
        }
        else Serial.println(F("\nfile not found"));
        Serial.println();
        flag = 1;
        goto label;
      }
    case 's': // send textfile
      {
        sendfile = String(value);
        byte e = echo;
        echo = true;
        send_SDtext(); // flag to echo the chars rather than show at the end
        echo = e;      // reset 'global' echo
        flag = 1;
        goto label;
      }
    case 't': //teststring routines
      {
        if (value < 1 || value > 3)
        {
          Serial.println(F("\nNo such option. t1 load - t2 save - t3 delete"));
          flag = 1;
          goto label;
        }
        File myFile;
        if (value == 1) // read file
        {
          myFile = SD.open("/utility/t1");
          if (myFile)
          {
            testStr = "";
            Serial.print(F("\nloading testStr: "));
            while (myFile.available()) // read from the file until there's nothing else in it
            {
              char a = (myFile.read()); // read a char and add to testStr
              testStr = testStr + a;
            }
            myFile.close(); // close the file
            testStrRow = testStr.length();
            Serial.println(testStr); Serial.println();
          }
          else
          {
            Serial.println(F("\nError opening /utility/t1\n")); // if the file didn't open, print an error
          }
          flag = 1;
          goto label;
        }
        else if (value == 2) // write file
        {
          if (!SD.exists("utility/"))
          {
            SD.mkdir("utility");
          }
          SD.remove("/utility/t1");
          testStr.trim(); testStr.toLowerCase();
          myFile = SD.open("/utility/t1", FILE_WRITE);
          if (myFile) // if the file opened okay, write to it
          {
            Serial.print(F("\nSaving testString: "));
            myFile.print(testStr); // write the string to the file
            myFile.close(); // close the file
            Serial.println(testStr); Serial.println();
            testStrRow = testStr.length(); // store string length
          }
          else
          {
            Serial.println(F("error opening /utility/t1"));
          }
          goto label;
        }
        else if (value == 3) // delete file
        {
          SD.remove("/utility/t1");
          delay(200); // just seemed like a good idea
          if (! SD.exists("/utility/t1")) Serial.println(F("testString file deleted\n"));
          goto label;
        }
      }
    case 'w': // write textfile to SD card
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be altered by mistake")); // until a better solution is found
          flag = 1;
          goto label;
        }
        char fname[4]; byte inChar; filename = String(value); unsigned int i = 0;
        filename.toCharArray(fname, 4);
        File dataFile = SD.open(fname, FILE_WRITE);
        Serial.print(F("\nWaiting..."));
        delay(10);
        while (Serial.peek() < 1);
        Serial.println(F("OK, receiving data"));
        do
        {
          if (Serial.peek() > 0)
          {
            inChar = Serial.read(); if (inChar < 127) dataFile.write(inChar);
            i++;
            if (i > 150)
            {
              Serial.print(F(".")); i = 0; // show something is happening
            }
          }
        } while (inChar != 129); // 129 this is the best I can come up with. use an unused ANSI char to break out of the loop
        //                       // batchfile 129.bat ( @echo  >>%1 ) puts ANSI char 129 at end of a textfile. Then upload it.
        dataFile.close();
        Serial.println(); Serial.print(F("Data saved as file ")); Serial.println(fname); Serial.println();
        flag = 1;
        goto label;
      }
    case 'x':
      {
        menu();
        return;
      }
    default:
      {
        Serial.println(F("\nError - not an option\n"));
        flag = 1;
        goto label;
      }
  }
}

void printDirectory() // display the names and size of the text files in SD root
{
  unsigned int i = 0;
  File root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  if (ANSI == true) Serial.println(F("File\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize"));
  Serial.println();
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry) break;
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")
    {
      Serial.print(entry.name()); Serial.print("\t"); Serial.print(entry.size(), DEC); Serial.print("\t\t");
      i++;
      if (i > 4)
      {
        Serial.println();
        i = 0;
      }
    }
    entry.close();
  }
  root.close();
  Serial.println(); Serial.print(F("Files: ")); Serial.print(fileCount); Serial.println(); Serial.println();
}

void getChars() // get user input. option and value if any
{
  if (ANSI == true) Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  value = 0;
  do // wait till a key is pressed
  {
    digits = Serial.available();
  } while (digits < 1); // 1 == a key press
  keypress = Serial.read(); if (keypress > 64 && keypress < 97) keypress = keypress + 32; //toLowerCase() is not for char
  delay(key_wait); // we now have the option (keypress)
  do               // collect the value if any
  {
    digits = Serial.available();
  } while (digits < 0); // I do not understand this. why not 1?
  value = Serial.parseInt();
  Serial.flush();
  if (ANSI == true) Serial.write(8); // backspace. remove prompt '<'
}

void confirm_pitch() // play a sample of the new pitch
{
  Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
  tone(8, pitch, 400); // give a sample
}


void Morse_Machine() // computer sends code, one char. user presses appropriate key
{
  refresh_Screen(); code_chart();
  Serial.println(F("Press Enter to quit"));
  byte w = wpm; wpm = 25; elementWait = 1200 / wpm; // store wpm. set wpm for this routine.
  holdStr = testStr; String a; String c; String s; String k; unsigned int u = 0; unsigned int v = 2; unsigned int x = 0;
label:
  if (flag > 0 ) // special handling of punctuation routine if selected - z(number)
  {
    x++; // count chars sent
    String mmStr = ",.?!:\"'=@()&;+-$^_";
    testStr = mmStr.substring(0, v);
    if (x > v * 10) // each char sent 10 times
    {
      v++;          // add another char to testStr
      x = 1;        // reset the test counter
    }
  }
  if (testStr == "") // build string of all chars
  {
    for (unsigned int i = 0; i < array; i++)
    {
      getStr(i);
      testStr = testStr + buffer;                        // string of chars to work with
    }
  }
  answers = "";
  for (unsigned int t = 0; t < testStr.length(); t++)    // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else answers.concat(testStr[randNumber]);
  }                                                      // answers is testStr randomized. ready to send each char one at a time
  u = 0;
  while (true)
  {
    if (u == testStr.length()) goto label;       // spent 2 days trying not to use goto. sorry I did. best way to get out of a nest
    for (unsigned int i = 0; i < array; i++)
    {
      a = answers.substring(u, u + 1);           // get and send a char
      getStr(i);
      if (a == buffer)
      {
        s = buffer; // Morse char
        getCode(i);
        c = buffer; // Morse string
        while (true)                             // need a 'while' to break out of
        {
          send_code(c);
          getChars();
          k = String(keypress);
          if (v > 18) k = "\r";         // we are done with the punctuation routine
          if (keypress == 126) k = "^"; // special keyboard/terminal problem with ^ & _
          if (keypress == 127) k = "_"; // don't know why. just happy I found this
          if (k == s)                   // correct user input. send another char
          {
            u++; x++;
            break;
          }
          if (k == " ")                 // hint, and not too subtle :-)
          {
            Serial.print(F("answer is: ")); Serial.println(s);
          }
          if (k == "\r")                // cleanup, quit and return to parser()
          {
            flag = 0;
            testStr = holdStr;
            wpm = w; elementWait = 1200 / wpm;
            refresh_Screen(); menu();
            return;
          }
        }
      }
    }
  }
}
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Some code re-arranged as suggested by djsfantasi.
It has taken me time to understand some of his points.
Added a good routine for those who do not as yet know the code.

Sketch uses 31,840 bytes (98%) of program storage space. Maximum is 32,256 bytes.
Global variables use 1,164 bytes (56%) of dynamic memory, leaving 884 bytes for local variables. Maximum is 2,048 bytes.

Code:
// Morse Receive Practice v2.9e
// by flat5 June 27, 2015
// UK/US characters only
// choice of tone/noTone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and Gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// version 2.6 is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 98 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// v2.7 & v2.8 Some SD card options added. This ver, some code cleanup and a feature or two added.
// v2.8.a changed the behaviour of some options
// v2.8.b added - and $ to the char* arrays. added array (size) variable to simplify adding new chars
// v2.9 adding a feature from Morse Machine. computer sends a char. you type the char correctly or computer sends it again
// v.2.9a altered a routine that calls itself by using a label (goto) to improve stability
// v.2.9.b a few tweaks and corrections
// v2.9.c Morse Machine now does not repeat chars. sends string and makes a new one
// v2.9.d New Morse Machine routine added for the punctuation characters. Send two of them 10 times each then add a third character...
// v2.9.e generalize Morse Machine 'add a char' for letters, numbers, punctuation. some code cleanup
// to do:
// resolve file 0/no file problem or just move on to another program :-)

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
const String Brag = "Morse Receive Practice v2.9e developed by Barry Block\n";
const byte chipSelect = 4; // SD card select
byte echo;
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message

const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the terminal program matches!

byte ANSI = true; //false        // *** If you are not using an ANSI terminal program and the screen has weird characters
//                               //     at the beginning of some lines change this from true to false. now a menu item
unsigned int pitch = 830; //0    // *** 0 if using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int soft = 950;         // *** find a pitch that is quieter in your setup
unsigned int loud = 750;         // *** find a pitch that is louder  in your setup

unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers (Ex. 0,1,2,3 in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using external circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
const unsigned int array = 54;   //     total number of Morse characters
unsigned int row = array;        // *** do NOT make this number larger than the array!

unsigned int elementWait = 1200 / wpm; char keypress; unsigned int value; unsigned int fileCount = 0;
String sendfile = ""; String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int flag = 0;
File root; String filename; String holdStr; unsigned int mmRepete = 10;

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "-";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "$"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";
const char str52[] PROGMEM = "_"; const char str53[] PROGMEM = "^";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51, str52, str53
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--";  const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";   const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----.";  const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "-....-";  const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "...-..-"; const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";   const char code52[] PROGMEM = "..--.-"; const char code53[] PROGMEM = "..--";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51,
  code52, code53
};

char buffer[8]; // make sure this is large enough for the largest code string it must hold. $ == 7

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    Serial.println(F("Waking SD card"));
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("SD card not found"));
      delay(3000);
    }
    else
    {
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) adjust letter spacetime (1-255)"));
  Serial.println(F("k(value) adjust keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) adjust wpm (Example: w20)"));
  Serial.println(F("p(value) adjust pitch, p off, p7 decrement 10Hz, p8 increment 10Hz, p9 soft (preset) p99 loud (preset)"));
  Serial.println(F("r(value) adjust number of characters to send (row length: 1-54)"));
  Serial.println(F("t select characters to send (54 max) or just Enter to clear 'TestString'"));
  if (SDready)
  {
    Serial.println(F("f send a textfile from SD card. add a number for text echo (f4)"));
    Serial.println(F("x SD card menu"));
  }
  Serial.println(F("z computer sends a character, you press the corresponding key"));
  Serial.println(F("z(number) for those who do not yet know the code. z1(letters) z2(numbers) z3(punctuation) z>3(# of repeats)"));
  Serial.println(F("n(row) send numbers"));
  Serial.println(F("l(row) send letters"));
  Serial.println(F("u(row) send punctuation characters or u77 (or >) to not send them"));
  Serial.println(F("c clear screen or to toggle this option: c(number)"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  Serial.print(F(" zRepete:")); Serial.print(mmRepete);
  if (fileCount > 0)
  {
    Serial.print(F(" SDfiles:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  getChars();
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': // clear screen if ANSI is supported
      {
        if (value > 0)
        {
          if (ANSI == true) ANSI = false;
          else ANSI = true;
          break;
        }
        refresh_Screen(); // an ANSI terminal is required for this to work
        break;
      }
    case 'd': // display code chart
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': // send random textfile from SD card
      {
        if (SDready == false)
        {
          Serial.println(F("no SD card found"));
          break;
        }
        if (value > 0) echo = true;
        else echo = false;
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'k': // keypress timeout
      {
        if (value < 11) key_wait = value * 1000;
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': // send the letter characters
      {
        testStr = "abcdefghijklmnopqrstuvwxyz";
        if (value == 0 ) testStrRow = 26;
        else testStrRow = value;
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        Serial.print(F("\nTestString set to letters. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'm': case 'h': case '?': case 'i': // display menu
      {
        if (keypress == 'i' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': // send the number characters
      {
        testStr = "1234567890";
        if (value == 0 ) testStrRow = 10;
        else testStrRow = value;
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        Serial.print(F("\nTestString set to numbers. Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'p': // pitch
      {
        if (value == 7)
        {
          pitch = pitch - 10;
          if (pitch > 24000 || pitch < 31) pitch = 31; // unsigned int rollover
          confirm_pitch();
          break;
        }
        if (value == 8)
        {
          pitch = pitch + 10;
          if (pitch > 24000) pitch = 24000;
          confirm_pitch();
          break;
        }
        if (value == 9) // find a pitch that is much lower in volume than others
        {
          pitch = soft;
          confirm_pitch();
          break;
        }
        if (value == 99) // find a pitch that is much higher in volume than others
        {
          pitch = 750;
          confirm_pitch();
          break;
        }
        if (value == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (value < 31 || value > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        pitch = value;
        confirm_pitch();
        break;
      }
    case 'r': // how many chars to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-")); Serial.print(array); Serial.print(F(", presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.print(testStrRow); Serial.println(F(" characters will be sent while using current TestString"));
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value < 55)
        {
          row = value;
          Serial.print(row); Serial.println(F(" characters will be sent"));
          break;
        }
        else
        {
          Serial.print(F("Too high a number!(1-")); Serial.println(array);
          break;
        }
      }
    case 's': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space time: ")); Serial.println(space);
        break;
      }
    case 't': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': // send the punctuation characters or not
      {
        if (value > 76)
        {
          testStr = "1234567890abcdefghijklmnopqrstuvwxyz";
          Serial.println(F("TestString set to numbers & letters"));
          if (value == 0 ) testStrRow = 36;
          else testStrRow = value;
          if (testStrRow > 36)
          {
            Serial.println(F("1-36 characters!"));
            testStrRow = 36;
          }
          Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
          break;
        }
        testStr = ",.?!:\"'=@_()^&;+-$";
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        if (value == 0 ) testStrRow = 18;
        else testStrRow = value;
        if (testStrRow > 18)
        {
          Serial.println(F("1-18 characters!"));
          testStrRow = 18;
        }
        Serial.print(F("Characters to be sent:")); Serial.println(testStrRow);
        break;
      }
    case 'w': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'x': // SD card sub menu
      {
        SDfilesMenu();
        break;
      }
    case 'z':
      {
        if (value > 3)
        {
          mmRepete = value;
          Serial.print(F("Will repete ")); Serial.print(mmRepete); Serial.println(F(" times before adding next character"));
          break;
        }
        flag = value;
        Morse_Machine();
        break;
      }
    default: break;
  }
}

void refresh_Screen() // requires an ANSI terminal. can be toggled. see menu display
{
  if (ANSI == true)
  {
    Serial.write(27);    // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27);    // ESC
    Serial.write("[H");  // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent and display answer. Heart of the program!
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(array); // 0 - 53, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 54 chars). no jerkyness so no need to pre-build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}

void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i); // get char pointed to by i and return it as 'buffer'
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build();    // collect chars and put in testStr if they are legal. if not, return flag 1
  if (flag == 1)
  {
    flag = 0;
    return;
  }
  if (testStr != "")   // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string.\n"));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > array)       // is the string too long? larger than the array of Morse characters
  {
    Serial.print(F("Max:")); Serial.print(array); Serial.println(F(" characters - TestString cleared"));
    flag = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; h = char(testStr.charAt(t)); // overcoming the string vs char compile error by CASTING
    for (int i = t + 1; i < testStr.length(); i++)
    {
      if (h == char(testStr.charAt(i)))
      {
        Serial.println(F("Repeated character! - TestString cleared"));
        flag = 1;
        testStr = "";
        return;
      }
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    flag = 1; // set the flag
    for (unsigned int i = 0; i < array; i++)          // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer)      // if a match is found for this char set a flag. don't check further
      {
        flag = 0;
        break;
      }
    }
    if (flag == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      flag = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x) // actually send the sounds
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() { // silence between chars
  delay(elementWait * space);
}

void word_space() {  // silence between words
  delay((elementWait * space * 2) + 1); // close enough, it's correct for the proper space of 3 times dit.
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart() // show all Morse chars on screen
{
  Serial.println(F("letters:"));
  Lchart();
  Serial.println(F("numbers:"));
  Nchart();
  Serial.println(F("punctuation:"));
  Pchart();
}

void print_line(unsigned int i) // with the above to display chart
{
  getStr(i);
  Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i);
  Serial.print(buffer); Serial.print(" ");
}

void Lchart()
{
  for (unsigned int i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (unsigned int i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void Nchart()
{
  for (unsigned int i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void Pchart()
{
  for (unsigned int i = 36; i < 45; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
  for (unsigned int i = 45; i < 54; i++) {
    print_line(i);
  } Serial.println(); Serial.println();
}

void send_SDtext() // send and show a random text file stored on the SD card
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  if (sendfile != "")
  {
    str = sendfile; sendfile = ""; // a file has been specifically chosen
  }
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 999 files + 0. 1000 files
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i) // copy to buffer a string (one char) stored in flash memory (a)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i) // copy to buffer a string stored in flash memory (.-)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int) // get number of text files in SD root dir
{
  fileCount = 0;
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")//only include files starting with a number BUT file 0 will not be displayed unless exception is made
    {
      fileCount++;
      if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    }
    entry.close();
  }
  return fileCount;
}

void SDfilesMenu() // SD file options menu & routines
{
label:
  while (Serial.available()) Serial.read();
  if (flag == 0) refresh_Screen();
  Serial.println(F("\nChoose Option:\n"));
  Serial.println(F("l list files"));
  Serial.println(F("r(filenumber) read file"));
  Serial.println(F("s(filenumber) send file"));
  Serial.println(F("d(filenumber) delete file"));
  Serial.println(F("w(filenumber) write file"));
  Serial.println(F("t1 (Load testString) t2 (Save testString) t3 (Delete testString)"));
  Serial.println(F("x return to main menu"));
  flag = 0;
  getChars();
  refresh_Screen();
  switch (keypress) // char
  {
    case 'd': // delete file
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be deleted by mistake\n"));
          flag = 1;
          goto label;
        }
        File root;
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          root = SD.open("/");
          SD.remove(fname);
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" Deleted."));
          root.close();
          countRootFiles(root, fileCount);
          goto label;
        }
        else
        {
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" not found."));
        }
        flag = 1;
        goto label;
      }
    case 'l': // list SD root files
      {
        printDirectory();
        flag = 1;
        goto label;
      }
    case 'r': //read textfile
      {
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          refresh_Screen();
          Serial.print(F("File: ")); Serial.println(fname); Serial.println();
          File dataFile = SD.open(fname, FILE_READ);
          while (dataFile.available()) Serial.write(dataFile.read());
          dataFile.close();
        }
        else Serial.println(F("\nfile not found"));
        Serial.println();
        flag = 1;
        goto label;
      }
    case 's': // send textfile
      {
        sendfile = String(value);
        byte e = echo;
        echo = true;
        send_SDtext(); // flag to echo the chars rather than show at the end
        echo = e;      // reset 'global' echo
        flag = 1;
        goto label;
      }
    case 't': //teststring routines
      {
        if (value < 1 || value > 3)
        {
          Serial.println(F("\nNo such option. t1 load - t2 save - t3 delete"));
          flag = 1;
          goto label;
        }
        File myFile;
        if (value == 1) // read file
        {
          myFile = SD.open("/utility/t1");
          if (myFile)
          {
            testStr = "";
            Serial.print(F("\nloading testStr: "));
            while (myFile.available()) // read from the file until there's nothing else in it
            {
              char a = (myFile.read()); // read a char and add to testStr
              testStr = testStr + a;
            }
            myFile.close(); // close the file
            testStrRow = testStr.length();
            Serial.println(testStr); Serial.println();
          }
          else
          {
            Serial.println(F("\nError opening /utility/t1\n")); // if the file didn't open, print an error
          }
          flag = 1;
          goto label;
        }
        else if (value == 2) // write file
        {
          if (!SD.exists("utility/"))
          {
            SD.mkdir("utility");
          }
          SD.remove("/utility/t1");
          testStr.trim(); testStr.toLowerCase();
          myFile = SD.open("/utility/t1", FILE_WRITE);
          if (myFile) // if the file opened okay, write to it
          {
            Serial.print(F("\nSaving testString: "));
            myFile.print(testStr); // write the string to the file
            myFile.close(); // close the file
            Serial.println(testStr); Serial.println();
            testStrRow = testStr.length(); // store string length
          }
          else
          {
            Serial.println(F("error opening /utility/t1"));
          }
          goto label;
        }
        else if (value == 3) // delete file
        {
          SD.remove("/utility/t1");
          delay(200); // just seemed like a good idea
          if (! SD.exists("/utility/t1")) Serial.println(F("testString file deleted\n"));
          goto label;
        }
      }
    case 'w': // write textfile to SD card
      {
        if (value == 0)
        {
          Serial.println(F("File 0 is protected so that it will not be altered by mistake")); // until a better solution is found
          flag = 1;
          goto label;
        }
        char fname[4]; byte inChar; filename = String(value); unsigned int i = 0;
        filename.toCharArray(fname, 4);
        File dataFile = SD.open(fname, FILE_WRITE);
        Serial.print(F("\nWaiting..."));
        delay(10);
        while (Serial.peek() < 1);
        Serial.println(F("OK, receiving data"));
        do
        {
          if (Serial.peek() > 0)
          {
            inChar = Serial.read(); if (inChar < 127) dataFile.write(inChar);
            i++;
            if (i > 150)
            {
              Serial.print(F(".")); i = 0; // show something is happening
            }
          }
        } while (inChar != 129); // 129 this is the best I can come up with. use an unused ANSI char to break out of the loop
        //                       // batchfile 129.bat ( @echo  >>%1 ) puts ANSI char 129 at end of a textfile. Then upload it.
        dataFile.close();
        Serial.println(); Serial.print(F("Data saved as file ")); Serial.println(fname); Serial.println();
        flag = 1;
        goto label;
      }
    case 'x':
      {
        menu();
        return;
      }
    default:
      {
        Serial.println(F("\nError - not an option\n"));
        flag = 1;
        goto label;
      }
  }
}

void printDirectory() // display the names and size of the text files in SD root
{
  unsigned int i = 0;
  File root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  if (ANSI == true) Serial.println(F("File\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize"));
  Serial.println();
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry) break;
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")
    {
      Serial.print(entry.name()); Serial.print("\t"); Serial.print(entry.size(), DEC); Serial.print("\t\t");
      i++;
      if (i > 4)
      {
        Serial.println();
        i = 0;
      }
    }
    entry.close();
  }
  root.close();
  Serial.println(); Serial.print(F("Files: ")); Serial.print(fileCount); Serial.println(); Serial.println();
}

void getChars() // get user input. option and value if any
{
  if (ANSI == true) Serial.print(F("<")); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  value = 0;
  do // wait till a key is pressed
  {
    digits = Serial.available();
  } while (digits < 1); // 1 == a key press
  keypress = Serial.read(); if (keypress > 64 && keypress < 97) keypress = keypress + 32; //toLowerCase() is not for char
  delay(key_wait); // we now have the option (keypress)
  do               // collect the value if any
  {
    digits = Serial.available();
  } while (digits < 0); // I do not understand this. why not 1?
  value = Serial.parseInt();
  Serial.flush();
  if (ANSI == true) Serial.write(8); // backspace. remove prompt '<'
}

void confirm_pitch() // play a sample of the new pitch
{
  Serial.print(F("Tone pitch in Hz: ")); Serial.println(pitch, DEC);
  tone(8, pitch, 400); // give a sample
}

void Morse_Machine() // computer sends code, one char. user presses appropriate key
{
  refresh_Screen(); //code_chart();
  Serial.println(F("Press Enter to quit\n\n"));
  byte w = wpm; wpm = 25; elementWait = 1200 / wpm; // store wpm. set wpm for this routine.
  holdStr = testStr; String mmStr; String a; String c; String s; String k; unsigned int u = 0; unsigned int v = 2; unsigned int x = 0;
  if (flag == 1) {
    mmStr = "abcdefghijklmnopqrstuvwxyz";
    Lchart();  // show relavent part of code_chart()
  }
  if (flag == 2) {
    mmStr = "1234567890";
    Nchart();
  }
  if (flag == 3) {
    mmStr = ",.?!:\"'=@()&;+-$^_";
    Pchart();
  }
  if (testStr == "" && flag == 0)   // build string of all chars
  {
    code_chart();
    for (unsigned int i = 0; i < array; i++)
    {
      getStr(i);
      testStr = testStr + buffer;  // string of chars to work with
    }
  }
label:
  if (flag > 0 ) // special handling of routine if selected - z(number)
  {
    x++; // count chars sent
    testStr = mmStr.substring(0, v);
    if (x > v * mmRepete) // each char sent 'mmRepete' times. default is 10
    {
      v++;                // add another char to testStr
      x = 1;              // reset the test counter
    }
  }
  answers = "";
  for (unsigned int t = 0; t < testStr.length(); t++)    // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else answers.concat(testStr[randNumber]);
  }                                                      // answers is testStr randomized. ready to send each char one at a time
  u = 0;
  while (true)
  {
    if (u == testStr.length()) goto label;       // spent 2 days trying not to use goto. sorry I did. best way to get out of a nest
    for (unsigned int i = 0; i < array; i++)
    {
      a = answers.substring(u, u + 1);           // get and send a char
      getStr(i);
      if (a == buffer)
      {
        s = buffer; // Morse char
        getCode(i);
        c = buffer; // Morse string
        while (true)                             // need a 'while' to break out of
        {
          send_code(c);
          getChars();
          k = String(keypress);
          if (v > 18) k = "\r";         // we are done with the beginner routine
          if (keypress == 126) k = "^"; // special keyboard/terminal problem with ^ & _
          if (keypress == 127) k = "_"; // don't know why. just happy I found this
          if (k == s)                   // correct user input. send another char
          {
            u++; x++;
            break;
          }
          if (k == " ")                 // hint, and not too subtle :-)
          {
            Serial.print(F("answer is: ")); Serial.println(s);
          }
          if (k == "\r")                // cleanup, quit and return to parser()
          {
            flag = 0;
            testStr = holdStr;
            wpm = w; elementWait = 1200 / wpm;
            refresh_Screen(); menu();
            return;
          }
        }
      }
    }
  }
}
 
Last edited:

Thread Starter

flat5

Joined Nov 13, 2008
403
Added a Help textfile option using the SD card. Had to alter some print statements to free up 200 bytes :)
Added a bookmark option to routines z1, z2, z3.
Spent a few hours trying to fix a routine in the program. Turned out to be rare problem with the SD tf card! Helpfile updated.
Code:
// Morse Receive Practice v3
// by flat5 July 1, 2015
// UK/US characters only
// choice of tone/noTone or external oscillator circuit triggered by pin13.
// Plug speaker to Digital pin8 and Gnd. Use a resistor or pot in series if you want to. I used 220 ohms.
// A few more punctuation characters added. Added a feature or two. Caught a few unwanted features.
// v2.5 added SD card. randomly chooses textfile to send as Morse
// This had pushed the memory to the limit.
// version 2.6 is a rewrite of how static strings are stored. Only 51% of dynamic memory is used!
// NOTE - number of textfiles in SD root folder must be named 0 1 2...98...127...999
// Not tested beyond 98 yet! format to Fat32 & avoid 'too many files' error. array will not work with 4 digit numbers unless YOU change it
// v2.7 & v2.8 Some SD card options added. This ver, some code cleanup and a feature or two added.
// v2.8.a changed the behaviour of some options
// v2.8.b added - and $ to the char* arrays. added array (size) variable to simplify adding new chars
// v2.9 adding a feature from Morse Machine. computer sends a char. you type the char correctly or computer sends it again
// v.2.9a altered a routine that calls itself by using a label (goto) to improve stability
// v.2.9.b a few tweaks and corrections
// v2.9.c Morse Machine now does not repeat chars. sends string and makes a new one
// v2.9.d New Morse Machine routine added for the punctuation characters. Send two of them 10 times each then add a third character...
// v2.9.e generalize Morse Machine 'add a char' for letters, numbers, punctuation. some code cleanup
// v2.9.f strings, letters, numbers, punctuation now const strings.
//        at this point I'd be better off moving some strings back to dynamic memory
//        more string cleanup to get room for an SD card Help text file routine.
// v3     added bookmark to z1 z2 z3 routine. continue where you left off
// Sketch uses 32,086 bytes (99%) of program storage space. Maximum is 32,256 bytes.
// Global variables use 1,236 bytes (60%) of dynamic memory, leaving 844 bytes for local variables. Maximum is 2,048 bytes.

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include <SD.h>
const String Brag = "Morse Receive Practice v3 developed by Barry Block\n";
const String letters = "abcdefghijklmnopqrstuvwxyz";
const String numbers = "1234567890";
const String punctuation = ",.?!:\"'=@_()^&;+-$";

const byte chipSelect = 4; // SD card select
byte echo;
char* hfile = "/utility/MRP_Help.txt"; // *** a Help text file. Put it on the SD card using this suggested folder/name.
const unsigned long int baud = 115200; // *** 115200 57600 9600 300 2 make sure the terminal program matches!
byte SDready = true;             // *** SD card installed, true or false. set to false if no SD card to avoid error message
byte ANSI = true; //false        // *** If you are not using an ANSI terminal program and the screen has weird characters
//                               //     at the beginning of some lines change this from true to false. now a menu item
unsigned int pitch = 570; //0    // *** 0 if using an oscillator, not tone(). You might want to use 600Hz, for example.
unsigned int soft = 620;         // *** find a pitch that is quieter in your setup
unsigned int loud = 740;         // *** find a pitch that is louder  in your setup

unsigned int key_wait = 0000;    // *** pause to give you time to enter data, in milliseconds. for slow typers (Ex. 0,1,2,3 in menu)
unsigned int space = 3;          // *** Default time between Morse characters.
//                               //     It's good to send characters faster than the space between them for practice.
const byte signalPin = 13;       // *** will control oscillator speaker, if using external circuit. else connect sounder to D8
unsigned int wpm = 20;           // *** Change this to any default value
const unsigned int array = 54;   //     total number of Morse characters
unsigned int row = array;        // *** do NOT make this number larger than the array!

unsigned int elementWait = 1200 / wpm; char keypress; unsigned int value; unsigned int fileCount = 0;
String sendfile; String testStr; unsigned int testStrRow; String send_char; String answers; unsigned int flag = 0;
File root; String filename; String holdStr; unsigned int mmRepeat = 5; const String c = "Characters to be sent:";

const char str0[] PROGMEM = "a";  const char str1[] PROGMEM = "b";  const char str2[] PROGMEM = "c";  const char str3[] PROGMEM = "d";
const char str4[] PROGMEM = "e";  const char str5[] PROGMEM = "f";  const char str6[] PROGMEM = "g";  const char str7[] PROGMEM = "h";
const char str8[] PROGMEM = "i";  const char str9[] PROGMEM = "j";  const char str10[] PROGMEM = "k"; const char str11[] PROGMEM = "l";
const char str12[] PROGMEM = "m"; const char str13[] PROGMEM = "n"; const char str14[] PROGMEM = "o"; const char str15[] PROGMEM = "p";
const char str16[] PROGMEM = "q"; const char str17[] PROGMEM = "r"; const char str18[] PROGMEM = "s"; const char str19[] PROGMEM = "t";
const char str20[] PROGMEM = "u"; const char str21[] PROGMEM = "v"; const char str22[] PROGMEM = "w"; const char str23[] PROGMEM = "x";
const char str24[] PROGMEM = "y"; const char str25[] PROGMEM = "z";

const char str26[] PROGMEM = "0"; const char str27[] PROGMEM = "1"; const char str28[] PROGMEM = "2"; const char str29[] PROGMEM = "3";
const char str30[] PROGMEM = "4"; const char str31[] PROGMEM = "5"; const char str32[] PROGMEM = "6"; const char str33[] PROGMEM = "7";
const char str34[] PROGMEM = "8"; const char str35[] PROGMEM = "9";

const char str36[] PROGMEM = ","; const char str37[] PROGMEM = ".";  const char str38[] PROGMEM = "?"; const char str39[] PROGMEM = "!";
const char str40[] PROGMEM = ":"; const char str41[] PROGMEM = "\""; const char str42[] PROGMEM = "'"; const char str43[] PROGMEM = "=";
const char str44[] PROGMEM = "@"; const char str45[] PROGMEM = "-";  const char str46[] PROGMEM = "("; const char str47[] PROGMEM = ")";
const char str48[] PROGMEM = "$"; const char str49[] PROGMEM = "&";  const char str50[] PROGMEM = ";"; const char str51[] PROGMEM = "+";
const char str52[] PROGMEM = "_"; const char str53[] PROGMEM = "^";

// set up a table to refer to your strings.
const char* const strtable[] PROGMEM =
{
  str0, str1, str2, str3, str4, str5, str6, str7, str8, str9, str10, str11, str12, str13, str14, str15, str16, str17,
  str18, str19, str20, str21, str22, str23, str24, str25, str26, str27, str28, str29, str30, str31, str32, str33, str34,
  str35, str36, str37, str38, str39, str40, str41, str42, str43, str44, str45, str46, str47, str48, str49, str50, str51, str52, str53
};

// code strings // letters
const char code0[] PROGMEM = ".-";    const char code1[] PROGMEM = "-...";  const char code2[] PROGMEM = "-.-.";
const char code3[] PROGMEM = "-..";   const char code4[] PROGMEM = ".";     const char code5[] PROGMEM = "..-.";
const char code6[] PROGMEM = "--.";   const char code7[] PROGMEM = "....";  const char code8[] PROGMEM = "..";
const char code9[] PROGMEM = ".---";  const char code10[] PROGMEM = "-.-";  const char code11[] PROGMEM = ".-..";
const char code12[] PROGMEM = "--";   const char code13[] PROGMEM = "-.";   const char code14[] PROGMEM = "---";
const char code15[] PROGMEM = ".--."; const char code16[] PROGMEM = "--.-"; const char code17[] PROGMEM = ".-.";
const char code18[] PROGMEM = "...";  const char code19[] PROGMEM = "-";    const char code20[] PROGMEM = "..-";
const char code21[] PROGMEM = "...-"; const char code22[] PROGMEM = ".--";  const char code23[] PROGMEM = "-..-";
const char code24[] PROGMEM = "-.--"; const char code25[] PROGMEM = "--..";

// numbers
const char code26[] PROGMEM = "-----"; const char code27[] PROGMEM = ".----"; const char code28[] PROGMEM = "..---";
const char code29[] PROGMEM = "...--"; const char code30[] PROGMEM = "....-"; const char code31[] PROGMEM = ".....";
const char code32[] PROGMEM = "-...."; const char code33[] PROGMEM = "--..."; const char code34[] PROGMEM = "---..";
const char code35[] PROGMEM = "----.";

// punctuation
const char code36[] PROGMEM = "--..--";  const char code37[] PROGMEM = ".-.-.-"; const char code38[] PROGMEM = "..--..";
const char code39[] PROGMEM = "..--.";   const char code40[] PROGMEM = "---..."; const char code41[] PROGMEM = ".-..-.";
const char code42[] PROGMEM = ".----.";  const char code43[] PROGMEM = "-...-";  const char code44[] PROGMEM = ".--.-.";
const char code45[] PROGMEM = "-....-";  const char code46[] PROGMEM = "-.--.";  const char code47[] PROGMEM = "-.--.-";
const char code48[] PROGMEM = "...-..-"; const char code49[] PROGMEM = ".-...";  const char code50[] PROGMEM = "-.-.-.";
const char code51[] PROGMEM = ".-.-.";   const char code52[] PROGMEM = "..--.-"; const char code53[] PROGMEM = "..--";

const char* const codetable[] PROGMEM =
{
  code0, code1, code2, code3, code4, code5, code6, code7, code8, code9, code10, code11, code12, code13, code14, code15, code16, code17,
  code18, code19, code20, code21, code22, code23, code24, code25, code26, code27, code28, code29, code30, code31, code32, code33, code34,
  code35, code36, code37, code38, code39, code40, code41, code42, code43, code44, code45, code46, code47, code48, code49, code50, code51,
  code52, code53
};

char buffer[8]; // make sure this is large enough for the largest code string it must hold. $ == 7

void setup()
{
  pinMode(signalPin, OUTPUT);
  noTone(8);
  Serial.begin(baud);
  randomSeed(analogRead(0));
  if (SDready)
  {
    Serial.println(F("Waking SD card"));
    if (!SD.begin(chipSelect))
    {
      SDready = false;
      Serial.println(F("no SD card"));
      delay(3000);
    }
    else
    {
      SDready = true;
      countRootFiles(root, fileCount);
    }
  }
  refresh_Screen();
  Serial.println(Brag);
  menu();
}

void loop()
{
  parser();
}

void menu() {
  Serial.println(F("Send a random group of characters for Morse practice"));
  Serial.println(F("Press Spacebar to start test\n"));
  Serial.println(F("s(value) set letter spacetime (1-255)"));
  Serial.println(F("k(value) set keypress timeout in seconds(0-10)"));
  Serial.println(F("w(value) set wpm (Example: w20)"));
  Serial.println(F("p(value) set pitch, p off, p7 decrement 10Hz, p8 increment 10Hz, p9 soft (preset) p99 loud (preset)"));
  Serial.println(F("r(value) set number of characters to send (row length: 1-54)"));
  Serial.println(F("t enter characters to send (54 max) or just Enter to clear 'TestString'"));
  if (SDready)
  {
    Serial.println(F("f send a textfile from SD card. add number for text echo (f4)"));
    Serial.println(F("x SDcard menu"));
    Serial.println(F("h show helpfile"));
  }
  Serial.println(F("z computer sends testString character, you press corresponding key"));
  Serial.println(F("z(number) for those who do not yet know the code. z1(letters) z2(numbers) z3(punctuation) z>3(# of repeats)"));
  Serial.println(F("n(row) send numbers"));
  Serial.println(F("l(row) send letters"));
  Serial.println(F("u(row) send punctuation characters or u77 to not send them"));
  Serial.println(F("c clear screen or to toggle this option: c(number)"));
  Serial.println(F("d show Morse code chart"));
  Serial.println(F("m display this menu - i clear screen first"));
  Serial.print(F("Currently: s:")); Serial.print(space); Serial.print(F(" k:")); Serial.print(key_wait / 1000);
  Serial.print(F(" w:")); Serial.print(wpm); Serial.print(F(" P:")); Serial.print(pitch); Serial.print(F(" r:")); Serial.print(row);
  Serial.print(F(" Zrepeat:")); Serial.print(mmRepeat);
  if (fileCount > 0)
  {
    Serial.print(F(" SDfiles:")); Serial.print(fileCount);
  }
  if (testStr != "")
  {
    Serial.print(F(" TestString row:")); Serial.print(testStrRow); Serial.print(F(" TestString: ")); Serial.print(testStr);
  }
  Serial.println();
}

void parser()
{
  getChars();
  switch (keypress)
  {
    case ' ': // Spacebar. generate & send code
      {
        random_code();
        break;
      }
    case 'c': // clear screen if ANSI is supported
      {
        if (value > 0)
        {
          if (ANSI == true) ANSI = false;
          else ANSI = true;
          break;
        }
        refresh_Screen(); // an ANSI terminal is required for this to work
        break;
      }
    case 'd': // display code chart
      {
        refresh_Screen();
        code_chart();
        break;
      }
    case 'f': // send random textfile from SD card
      {
        if (value > 0) echo = true;
        else echo = false;
        refresh_Screen();
        send_SDtext();
        break;
      }
    case 'h': case '?':
      {
        if (SD.exists(hfile)) // display /utility/MRP_Help.txt
        {
          File dataFile = SD.open(hfile, FILE_READ);
          while (dataFile.available()) Serial.write(dataFile.read());
          dataFile.close();
        }
        else
        {
          Serial.println(); Serial.print(hfile); Serial.println(F(" not found\n"));
        }
        break;
      }
    case 'k': // keypress timeout
      {
        if (value < 11) key_wait = value * 1000;
        Serial.print(F("KeyWait: ")); Serial.println(key_wait / 1000);
        break;
      }
    case 'l': // send the letter characters
      {
        testStr = letters;
        if (testStrRow == 0 || testStrRow > 26) testStrRow = 26;
        if (value > 0) testStrRow = value;
        if (testStrRow > 26)
        {
          Serial.println(F("1-26 characters!"));
          testStrRow = 26;
        }
        Serial.print(F("\nTestString set to letters. ")); Serial.println(c + testStrRow);
        break;
      }
    case 'm': case 'i': // display menu
      {
        if (keypress == 'i' || keypress == '?')
        {
          refresh_Screen();
          Serial.println(Brag);
        }
        menu();
        break;
      }
    case 'n': // send the number characters
      {
        testStr = numbers;
        if (testStrRow == 0 || testStrRow > 10) testStrRow = 10;
        if (value > 0) testStrRow = value;
        if (testStrRow > 10)
        {
          Serial.println(F("1-10 characters!"));
          testStrRow = 10;
        }
        Serial.print(F("\nTestString set to numbers. ")); Serial.println(c + testStrRow);
        break;
      }
    case 'p': // pitch
      {
        if (value == 7)
        {
          pitch = pitch - 10;
          if (pitch > 24000 || pitch < 31) pitch = 31; // unsigned int rollover
          confirm_pitch();
          break;
        }
        if (value == 8)
        {
          pitch = pitch + 10;
          if (pitch > 24000) pitch = 24000;
          confirm_pitch();
          break;
        }
        if (value == 9) // find a pitch that is much lower in volume than others
        {
          pitch = soft;
          confirm_pitch();
          break;
        }
        if (value == 99) // find a pitch that is much higher in volume than others
        {
          pitch = 750;
          confirm_pitch();
          break;
        }
        if (value == 0)
        {
          pitch = 0;
          noTone(8);
          Serial.println(F("Tone off.")); // stare at the led or use an external oscillator triggered by pin D13
          break;
        }
        if (value < 31 || value > 24000)
        {
          Serial.println(F("Pitch range: 31-24000"));
          break;
        }
        pitch = value;
        confirm_pitch();
        break;
      }
    case 'r': // how many chars to send (a row)
      {
        if (value == 0)
        {
          Serial.print(F("row length (1-")); Serial.print(array); Serial.print(F(", presently ")); Serial.println(row);
          if (testStr != "")
          {
            Serial.print(F("TestString row length: ")); Serial.println(testStrRow);
          }
          break;
        }
        if (testStr != "")
        {
          if (value <= testStr.length())
          {
            testStrRow = value;
            Serial.println(c + testStrRow);
            break;
          }
          else
          {
            Serial.print(F("Too high a number! TestString is only ")); Serial.print(testStr.length()); Serial.println(F(" characters."));
            break;
          }
        }
        else if (value <= array)
        {
          row = value;
          Serial.println(c + row);
          break;
        }
        else
        {
          Serial.print(F("Too high a number!(1-")); Serial.println(array);
          break;
        }
      }
    case 's': // letter space time length (1 is same time as a dit)
      {
        if (value != 0) space = value; // value 0 not allowed. just report setting
        Serial.print(F("Letter space: ")); Serial.println(space);
        break;
      }
    case 't': // send a selected group of characters
      {
        testStrRow = 0;
        build_testStr();
        break;
      }
    case 'u': // send the punctuation characters or not
      {
        if (value > 76)
        {
          testStr = numbers + letters;
          Serial.println(F("TestString set to numbers & letters"));
          if (testStrRow == 0 || testStrRow > 36) testStrRow = 36;
          if (testStrRow > 36)
          {
            Serial.println(F("1-36 characters!"));
            testStrRow = 36;
          }
          Serial.println(c + testStrRow);
          break;
        }
        testStr = punctuation;
        Serial.print(F("TestString set to puncuation characters: ")); Serial.println(testStr);
        if (testStrRow == 0 || testStrRow > 18) testStrRow = 18;
        if (value > 0) testStrRow = value;
        if (testStrRow > 18)
        {
          Serial.println(F("1-18 characters!"));
          testStrRow = 18;
        }
        Serial.println(c + testStrRow);
        break;
      }
    case 'w': // wpm
      {
        if (value != 0) wpm = value; // value 0 not allowed. just report setting
        elementWait = 1200 / wpm; // dit = 1200 / wpm
        Serial.print(F("wpm: ")); Serial.println(wpm);
        break;
      }
    case 'x': // SD card sub menu
      {
        SDfilesMenu();
        break;
      }
    case 'z':
      {
        if (value > 3)
        {
          mmRepeat = value;
          Serial.print(F("Will repeat ")); Serial.print(mmRepeat); Serial.println(F(" times before adding next character"));
          break;
        }
        flag = value;
        Morse_Machine();
        break;
      }
    default: break;
  }
}

void refresh_Screen() // requires an ANSI terminal. can be toggled. see menu display
{
  if (ANSI == true)
  {
    Serial.write(27);    // ESC
    Serial.write("[2J"); // clear screen
    Serial.write(27);    // ESC
    Serial.write("[H");  // cursor to home
  }
}

void random_code() // send a random character's Morse equivalent and display answer. Heart of the program!
{
  if (testStr != "") // characters have been selected so just send them. Idea is to always use the Spacebar to send a test run
  {
    send_select();
    return;
  }
  for (unsigned int t = 0; t < row; t++) // each run will be 'row' characters (+ 'row' spaces)
  {
    unsigned int randNumber = random(array); // 0 - 53, all characters.
    // see if the randNumber character is in string 'answers' already.
    getStr(randNumber); // in it's way, returns 'buffer' which is the char string pointed to by the index 'randNumber'
    if (answers.indexOf(buffer) != -1) t = t - 1; // character already in array. reject and try again
    else
    { // answer string is built fast (even 54 chars). no jerkyness so no need to pre-build a code array to send
      answers.concat(buffer); answers.concat(" "); // add a space after each character for readability
      getCode(randNumber); // get the code string (.-..)
      send_code(buffer);
    }
  }
  Serial.println(answers); answers = ""; // show test answers and clear string
}

void send_select() // called when testStr has proved legal
{
  if (testStrRow == 0) testStrRow = testStr.length();
  for (unsigned int t = 0; t < testStrRow; t++) // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else
    {
      answers.concat(testStr[randNumber]); answers.concat(" "); // add a space after each character for readability
      for (unsigned int i = 0; i < array; i++)
      {
        getStr(i);
        if (testStr.substring(randNumber, randNumber + 1) == buffer) // to index the correct code string
        {
          getCode(i); // get char pointed to by i and return it as 'buffer'
          send_code(buffer);
        }
      }
    }
  }
  Serial.println(answers); answers = ""; // display test answers and clear the string for the next run
}

void build_testStr()   // Select characters for study (option t)
{
  inString_build();    // collect chars and put in testStr if they are legal. if not, return flag 1
  if (flag == 1)
  {
    flag = 0;
    return;
  }
  if (testStr != "")   // testStr is legal
  {
    Serial.print(F("TestString: ")); Serial.println(testStr);
  }
}

void inString_build() // get chars from user and check them. if ok add them to testStr
{
  //Serial.flush();
  Serial.println(F("Input characters & Enter to save a TestString or just Enter to clear the string."));
  testStr = collect_chars(testStr); //get user input
  if (testStr == "")
  {
    Serial.println(F("TestString cleared"));
    return;
  }
  // now test the string for problems
  if (testStr.length() > array)       // is the string too long? larger than the array of Morse characters
  {
    Serial.print(F("Max:")); Serial.print(array); Serial.println(F(" characters - TestString cleared"));
    flag = 1;
    testStr = "";
    return;
  }
  for (unsigned int t = 0; t < testStr.length(); t++) // check for duplicates
  {
    char h; h = char(testStr.charAt(t)); // overcoming the string vs char compile error by CASTING
    for (int i = t + 1; i < testStr.length(); i++)
    {
      if (h == char(testStr.charAt(i)))
      {
        Serial.println(F("Repeated character! - TestString cleared"));
        flag = 1;
        testStr = "";
        return;
      }
    }
  }
  for (unsigned int s = 0; s < testStr.length(); s++) // for each char of testStr...
  {
    flag = 1; // set the flag
    for (unsigned int i = 0; i < array; i++)          // check to see that the character is a known Morse char.
    {
      getStr(i);
      if (testStr.substring(s, s + 1) == buffer)      // if a match is found for this char set a flag. don't check further
      {
        flag = 0;
        break;
      }
    }
    if (flag == 1) // illegal character. kill testStr and return. don't check rest of testStr.
    {
      Serial.print(testStr.substring(s, s + 1));
      Serial.println(F(" is not a character in the Morse array. TestSring cleared"));
      testStr = "";
      flag = 1;
      return;
    }
  }
}

String collect_chars(String testStr) // user input to build testStr
{
  testStr = ""; char inChar;
  delay(10); // seems to have removed the odd glitch
  while (Serial.available() < 1)
  {
    delay(10); // seems to have removed the odd glitch
    inChar = Serial.read();
    if (inChar > 31 && inChar < 127)
    {
      testStr = testStr + inChar; // screen gets filled with garbage without this check
      Serial.print(inChar);       // echo user input to screen
    }
    else if (inChar == '\r')
    {
      Serial.println();
      testStr.toLowerCase();
      if (testStrRow == 0) testStrRow = testStr.length();
      return testStr;
    }
  }
}

void send_code(String send_char) // send the dits & dahs of the Morse string - codes[](Ex. .-..)
{
  for (unsigned int i = 0; i < send_char.length(); i++) // break the Morse char string down to it's elements and send
  {
    String x;
    x = send_char.substring(i, i + 1);
    dit_dah(x); // determine dit or dah and send
  }
  letter_space();
}

void dit_dah(String x) // actually send the sounds
{
  digitalWrite(signalPin, HIGH);
  if (pitch != 0) tone(8, pitch);
  if (x == ".") delay(elementWait); // milliseconds - one dit
  else if (x == "-") delay(elementWait * 3);
  digitalWrite(signalPin, LOW);
  noTone(8);
  delay(elementWait);
}

void letter_space() { // silence between chars
  delay(elementWait * space);
}

void word_space() {  // silence between words
  delay((elementWait * space * 2) + 1); // close enough, it's correct for the proper space of 3 times dit.
}

void shortRow() // row length for the users input test string
{
  char inChar; String inString;
  testStrRow = 0;
  Serial.println(F("Input how many characters to send or just Enter to send all"));
  delay(10);
  while (Serial.available() < 1)
  {
    delay(10);
    inChar = Serial.read();
    if (inChar > 47 && inChar < 58)
    {
      inString = inString + inChar; // if a number add to string
      Serial.print(inChar);
    }
    else if (inChar == '\r')
    {
      testStrRow = inString.toInt();
      if (testStrRow == 0) testStrRow = testStr.length();
      Serial.println();
      return;
    }
  }
}

void code_chart() // show all Morse chars on screen
{
  Serial.println(F("letters:"));
  Lchart();
  Serial.println(F("numbers:"));
  Nchart();
  Serial.println(F("punctuation:"));
  Pchart();
}

void Lchart()
{
  for (unsigned int i = 0; i < 13; i++) {
    print_line(i);
  } Serial.println(F("\n"));
  for (unsigned int i = 13; i < 26; i++) {
    print_line(i);
  } Serial.println(F("\n"));
}

void Nchart()
{
  for (unsigned int i = 26; i < 36; i++) {
    print_line(i);
  } Serial.println(F("\n"));
}

void Pchart()
{
  for (unsigned int i = 36; i < 45; i++) {
    print_line(i);
  } Serial.println(F("\n"));
  for (unsigned int i = 45; i < 54; i++) {
    print_line(i);
  } Serial.println(F("\n"));
}

void print_line(unsigned int i) // with the above to display chart
{
  getStr(i); Serial.print("["); Serial.print(buffer); Serial.print("]");
  getCode(i); Serial.print(buffer); Serial.print(" ");
}

void send_SDtext() // send and show a random text file stored on the SD card
{
  String str; char fname[4]; // filename size up to 999, 1000 files. might be enough :-)
  unsigned int rand = random(fileCount);
  str = String(rand);        // convert int to string
  if (sendfile != "")
  {
    str = sendfile; sendfile = ""; // a file has been specifically chosen
  }
  str.toCharArray(fname, 4); // convert string to char array. Note char size (4). good to 1000 files plus string terminator
  Serial.print(F("textfile: ")); Serial.println(fname); Serial.println();
  File dataFile = SD.open(fname, FILE_READ);
  // if the file is available, READ from it.
  // This routine is fast enough that >3000 wpm seems to be sent with no jerkyness.
  if (dataFile)
  {
    String send_char; int SDint; String printChar;
    while (dataFile.available())
    {
      SDint = dataFile.read();
      char c = char(SDint);   // int to char
      send_char = String(c);  // char to string
      printChar = send_char;  // keep the cap letters for screen printout
      send_char.toLowerCase();
      for (unsigned int i = 0; i < array; i++) // check to see that the character is a known Morse char or space
      {
        if (send_char < " " || send_char > "z")
        {
          break;
        }
        if (send_char == " ")
        {
          word_space();
          break;
        }
        getStr(i);
        if (send_char == buffer) // if a match is found for this char, get code and send it
        {
          getCode(i);
          send_code(buffer);
          break;
        }
      }
      if (echo == true) Serial.print(printChar); // It's fun to see the text as the code is being sent.
    }
    dataFile.close();
    Serial.println();
    if (echo == false)
    {
      File dataFile = SD.open(fname, FILE_READ);
      while (dataFile.available())
      {
        Serial.write(dataFile.read());
      }
      dataFile.close();
    }
    Serial.println();
  }
  else // if the file isn't open, pop up an error:
  {
    Serial.print(F("error opening ")); Serial.println(fname);
  }
}

void getStr(unsigned int i) // copy to buffer a string (one char) stored in flash memory (a)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(strtable[i])));
}

void getCode(unsigned int i) // copy to buffer a string stored in flash memory (.-)
{
  strcpy_P(buffer, (char*)pgm_read_word(&(codetable[i])));
}

unsigned int countRootFiles(File, unsigned int) // get number of text files in SD root dir
{
  fileCount = 0;
  File root;
  root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry)
    {
      break;
    }
    String n = entry.name();
    if (n.toInt() != 0 || n == "0") //only include files starting with number BUT file 0 will not display unless exception is made
    {
      fileCount++;
      if (fileCount > 999) fileCount = 999; // have to change array char string size for a bigger (4 digit) number
    }
    entry.close();
  }
  return fileCount;
}

void SDfilesMenu() // SD file options menu & routines
{
label:
  while (Serial.available()) Serial.read(); // have to make sure buffer is empty. options were being chosen!
  if (flag == 0) refresh_Screen();
  flag = 0;
  Serial.println();
  Serial.println(F("\nChoose Option:\n"));
  Serial.println(F("l list files"));
  Serial.println(F("r(filenumber) read file"));
  Serial.println(F("s(filenumber) send file"));
  Serial.println(F("d(filenumber) delete file"));
  Serial.println(F("w(filenumber) write file"));
  Serial.println(F("t1 (Load testString) t2 (Save testString) t3 (Delete testString)"));
  Serial.println(F("x return to main menu"));
  getChars();
  refresh_Screen();
  switch (keypress) // char
  {
    case 'd': // delete file
      {
        if (value == 0)
        {
          Serial.println(F("File 0 protected\n"));
          flag = 1;
          goto label;
        }
        File root;
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          root = SD.open("/");
          SD.remove(fname);
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" Deleted."));
          root.close();
          countRootFiles(root, fileCount);
          goto label;
        }
        else
        {
          Serial.print(F("File ")); Serial.print(fname); Serial.println(F(" not found."));
        }
        flag = 1;
        goto label;
      }
    case 'l': // list SD root files
      {
        printDirectory();
        flag = 1;
        goto label;
      }
    case 'r': //read textfile
      {
        refresh_Screen();
        char fname[4];
        filename = String(value);
        filename.toCharArray(fname, 4);
        if (SD.exists(fname))
        {
          File dataFile = SD.open(fname, FILE_READ);
          while (dataFile.available()) Serial.write(dataFile.read());
          dataFile.close();
        }
        else Serial.println(F("\nfile not found\n"));
        flag = 1;
        goto label;
      }
    case 's': // send textfile
      {
        sendfile = String(value);
        byte e = echo;
        echo = true;   // flag to echo the chars rather than show at the end
        send_SDtext(); 
        echo = e;      // reset 'global' echo
        flag = 1;
        goto label;
      }
    case 't': //teststring routines
      {
        if (value < 1 || value > 3)
        {
          Serial.println(F("\nNo such option"));
          flag = 1;
          goto label;
        }
        File myFile;
        if (value == 1) // read file
        {
          myFile = SD.open("/utility/t1");
          if (myFile)
          {
            testStr = "";
            Serial.print(F("\nloading testStr"));
            while (myFile.available()) // read from the file until there's nothing else in it
            {
              char a = (myFile.read()); // read a char and add to testStr
              testStr = testStr + a;
            }
            myFile.close(); // close the file
            testStrRow = testStr.length();
          }
          else
          {
            Serial.println(F("\nError opening /utility/t1\n")); // if the file didn't open, print an error
          }
          flag = 1;
          goto label;
        }
        else if (value == 2) // write file
        {
          if (!SD.exists("utility/")) SD.mkdir("utility");
          SD.remove("/utility/t1");
          myFile = SD.open("/utility/t1", FILE_WRITE);
          if (myFile) // if the file opened okay, write to it
          {
            Serial.println(F("\nSaving testString"));
            myFile.print(testStr); // write the string to the file
            myFile.close(); // close the file
          }
          else
          {
            Serial.println(F("error opening /utility/t1"));
          }
          flag = 1;
          goto label;
        }
        else if (value == 3) // delete file
        {
          SD.remove("/utility/t1");
          delay(100); // just seemed like a good idea
          if (! SD.exists("/utility/t1")) Serial.println(F("file deleted\n"));
          flag = 1;
          goto label;
        }
      }
    case 'w': // write textfile to SD card
      {
        if (value == 0)
        {
          Serial.println(F("File 0 protected")); // until a better solution is found
          flag = 1;
          goto label;
        }
        char fname[4]; byte inChar; filename = String(value); unsigned int i = 0;
        filename.toCharArray(fname, 4);
        File dataFile = SD.open(fname, FILE_WRITE);
        Serial.print(F("\nWaiting..."));
        delay(10);
        while (Serial.peek() < 1);
        Serial.println(F("receiving data"));
        do
        {
          if (Serial.peek() > 0)
          {
            inChar = Serial.read(); if (inChar < 127) dataFile.write(inChar);
            i++;
            if (i > 150)
            {
              Serial.print("."); i = 0; // show something is happening
            }
          }
        } while (inChar != 129); // 129 this is the best I can come up with. use an unused ANSI char to break out of the loop
        //                       // batchfile 129.bat ( @echo  >>%1 ) puts ANSI char 129 at end of a textfile. Then upload it.
        dataFile.close();
        Serial.print(F("\nFile saved as ")); Serial.println(fname);
        flag = 1;
        goto label;
      }
    case 'x':
      {
        menu();
        return;
      }
    default:
      {
        Serial.println(F("\nError - not an option\n"));
        flag = 1;
        goto label;
      }
  }
}

void printDirectory() // display the names and size of the text files in SD root
{
  unsigned int i = 0;
  File root = SD.open("/");
  root.rewindDirectory(); // Begin at the start of the directory
  if (ANSI == true) Serial.println(F("File\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize\t\tFile\tSize"));
  Serial.println();
  while (true)
  {
    File entry = root.openNextFile();
    if (! entry) break;
    String n = entry.name();
    if (n.toInt() != 0 || n == "0")
    {
      Serial.print(entry.name()); Serial.print(F("\t")); Serial.print(entry.size(), DEC); Serial.print(F("\t\t"));
      i++;
      if (i > 4)
      {
        Serial.println();
        i = 0;
      }
    }
    entry.close();
  }
  root.close();
  Serial.print(F("\r\n\nFiles: ")); Serial.println(fileCount);
}

void getChars() // get user input. option and value if any
{
  if (ANSI == true) Serial.print("<"); // It gives some indication that the prog has not crashed and is awaiting instructions
  unsigned int digits;
  value = 0;
  do // wait till a key is pressed
  {
    digits = Serial.available();
  } while (digits < 1); // 1 == a key press
  keypress = Serial.read(); if (keypress > 64 && keypress < 97) keypress = keypress + 32; //toLowerCase() is not for char
  delay(key_wait); // we now have the option (keypress)
  do               // collect the value if any
  {
    digits = Serial.available();
  } while (digits < 0); // I do not understand this. why not 1?
  value = Serial.parseInt();
  if (ANSI == true) Serial.write(8); // backspace. remove prompt '<'
}

void confirm_pitch() // play a sample of the new pitch
{
  Serial.print("Tone pitch in Hz: "); Serial.println(pitch, DEC);
  tone(8, pitch, 400); // give a sample
}

void Morse_Machine() // computer sends code, one char. user presses appropriate key
{
  byte w = wpm; wpm = 25; elementWait = 1200 / wpm; // store wpm. set wpm for this routine.
  holdStr = testStr; String mmStr; String a; String c; String s; String k; unsigned int u = 0; unsigned int v = 2; unsigned int x = 0;
  String fname = "/utility/mmData" + String(flag); // len 17 including 0 at end
  char f[17];               // without the *
  fname.toCharArray(f, 17); // this took a day to get right
  if (flag > 0 && flag < 4 && SDready)
  {
    if (SD.exists(f)) // load bookmark for z1, z2, z3 routine
    {
      File dataFile = SD.open(f, FILE_READ);
      byte buf[2];
      for (unsigned int i = 0; i < 2; i++) buf[i] = dataFile.read();
      v = buf[0]; x = buf[1];
      dataFile.close();
    }
  }
  refresh_Screen();
  Serial.println(F("Press Enter to quit\n"));
  if (flag == 1) {
    mmStr = letters;
    Lchart();  // show relevent part of code_chart()
  }
  if (flag == 2) {
    mmStr = numbers;
    Nchart();
  }
  if (flag == 3) {
    mmStr = punctuation;
    Pchart();
  }
  if (testStr == "" && flag == 0)   // build string of all chars
  {
    code_chart();
    for (unsigned int i = 0; i < array; i++)
    {
      getStr(i);
      testStr = testStr + buffer;  // string of chars to work with
    }
  }
label:
  if (flag > 0 )          // special handling of routine if selected - z(number)
  {
    x++;                  // count chars sent
    testStr = mmStr.substring(0, v);
    if (x > v * mmRepeat) // each char sent 'mmRepeat' times. default is 5
    {
      v++;                // add another char to testStr
      x = 1;              // reset the test counter
    }
  }
  answers = "";
  for (unsigned int t = 0; t < testStr.length(); t++)    // random up the string to be sent
  {
    unsigned int randNumber = random(testStr.length());
    if (answers.indexOf(testStr[randNumber]) != -1) t--; // character already in array. try again
    else answers.concat(testStr[randNumber]);
  }                                                      // answers is testStr randomized. ready to send each char one at a time
  u = 0;                                                 // u is char position in answer string
  while (true)                                           // k == s so get next char to send
  {
    if (u == testStr.length()) goto label;       // spent 2 days trying not to use goto. sorry I did. best way to get out of a nest
    for (unsigned int i = 0; i < array; i++)
    {
      a = answers.substring(u, u + 1);           // get and send a char
      getStr(i);
      if (a == buffer)
      {
        s = buffer; // Morse char
        getCode(i);
        c = buffer; // Morse string
        while (true)                             // need a 'while' to break out of
        {
          send_code(c);
          getChars();
          k = String(keypress);
          if (v > 18) k = "\r";         // we are done with the beginner routine
          if (keypress == 126) k = "^"; // special keyboard/terminal problem with ^ & _
          if (keypress == 127) k = "_"; // don't know why. just happy I found this
          if (k == s)                   // correct user input. send another char
          {
            u++; x++;
            break;
          }
          if (k == " ")                 // hint, and not too subtle :-)
          {
            Serial.print(F("answer: ")); Serial.println(s);
          }
          if (k == "\r")                // cleanup, save some data, quit and return to parser()
          {
            if (SDready && flag > 0)
            {
              Serial.println(F("\nBookmark: Save y/n - Delete d"));
              getChars();
              switch (keypress)
              {
                case 'd':
                  {
                    SD.remove(f); // if file does not exist, who cares?
                    break;
                  }
                case 'y':
                  {
                    if (!SD.exists("utility/")) SD.mkdir("utility");
                    SD.remove(f);
                    File dataFile = SD.open(f, FILE_WRITE);
                    dataFile.write(v); dataFile.write(x);  // not much of a file but if you just spent an hour taking the test...
                    dataFile.close();
                    break;
                  }
              }
            }
            flag = 0;
            testStr = holdStr;
            wpm = w; elementWait = 1200 / wpm;
            refresh_Screen(); menu();
            return;
          }
        }
      }
    }
  }
}
Change extension of 129.txt to .bat
 

Attachments

Last edited:
Top