I built a simple Morse Code program - in need of optimisation

Hello Arduino friends,

I wanted to expand on the given task in the tutorial, and this is what I came up with (code below).
You enter a string into a char array and let the Arduino parse it as morse code via the status LED.

But my code seems unnecessarily long and redundant.
I have an idea to shorten it intelligently, but I don’t know if thats possible:
If you look at the switch expression and the corresponding morseX() functions, a simple way to cut down the repetitive text scheme would be to insert the currently selected char from the for loop into the actual code.

So for example, if my for loop is iterating through the char array “PARIS”, it would start with ‘P’ and then insert this letter into all the code snippets automatically to create the correct switch response.

Do you understand what I mean? I basically want to cut down the source code, because I feel like there would be a way more compact and maybe even efficient way of coding this.

Any suggestions? :slight_smile:

Thanks!!

//Assigning the status LED a variable
const unsigned int LED_PIN = 13;

//Declaring the variables, such as WpM (Words per Minute) and based on that, dits and dahs (which are the basic symbols of Morse Code).
const unsigned int WpM = 5;
const unsigned int DIT = 1200/WpM;
const unsigned int DAH = 3*DIT;

//This is the char array that stores the String to be morsed (IMPORTANT: String must be UPPERCASE).
char sen[] = "PARIS";

void setup() {

  //Setting the Status LED
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  //Main function to morse the whole char array one by one.
  morseAll();
}

void morseAll() {
  //Looping through the whole array.
  for (int i = 0; i < sizeof(sen); i++) {

    /*For each letter (A-Z) there is a corresponding function.
      For SPACE (' ') the pause is longer 'halt()', as specified in Morse Code standards.
      If there is an undefined symbol in the array, the LED will hyperventilate().
      After each letter, the LED will pause() as specified in the Morse Code standards.
      It's important, that the letters are uppercase. 
    */
    switch (sen[i]) {
      case 'A':
        morseA();
        break;
      case 'B':
        morseB();
        break;
      case 'C':
        morseC();
        break;
      case 'D':
        morseD();
        break;
      case 'E':
        morseE();
        break;
      case 'F':
        morseF();
        break;
      case 'G':
        morseG();
        break;
      case 'H':
        morseH();
        break;
      case 'I':
        morseI();
        break;
      case 'J':
        morseJ();
        break;
      case 'K':
        morseK();
        break;
      case 'L':
        morseL();
        break;
      case 'M':
        morseM();
        break;
      case 'N':
        morseN();
        break;
      case 'O':
        morseO();
        break;
      case 'P':
        morseP();
        break;
      case 'Q':
        morseQ();
        break;
      case 'R':
        morseR();
        break;
      case 'S':
        morseS();
        break;
      case 'T':
        morseT();
        break;
      case 'U':
        morseU();
        break;
      case 'V':
        morseV();
        break;
      case 'W':
        morseW();
        break;
      case 'X':
        morseX();
        break;
      case 'Y':
        morseY();
        break;
      case 'Z':
        morseZ();
        break;
      //For SPACE (' ') the pause is longer 'halt()', as specified in Morse Code standards.
      case ' ':
        halt();
        break;
      //If there is an undefined symbol in the array, the LED will hyperventilate().
      default:
        hyperventilate();
    }
    //After each letter, the LED will pause() as specified in the Morse Code standards.
    pause();
  }
}

//Morse functions for each letter (A-Z).

void morseA() {
  dit();
  dah();
}

void morseB() {
  dah();
  dit();
  dit();
  dit();
}

void morseC() {
  dah();
  dit();
  dah();
  dit();
}

void morseD() {
  dah();
  dit();
  dit();
}

void morseE() {
  dit();
}

void morseF() {
  dit();
  dit();
  dah();
  dit();
}

void morseG() {
  dah();
  dah();
  dit();
}

void morseH() {
  dit();
  dit();
  dit();
  dit();
}

void morseI() {
  dit();
  dit();
}

void morseJ() {
  dit();
  dah();
  dah();
  dah();
}

void morseK() {
  dah();
  dit();
  dah();
}

void morseL() {
  dit();
  dah();
  dit();
  dit();
}

void morseM() {
  dah();
  dah();
}

void morseN() {
  dah();
  dit();
}

void morseO() {
  dah();
  dah();
  dah();
}

void morseP() {
  dit();
  dah();
  dah();
  dit();
}

void morseQ() {
  dah();
  dah();
  dit();
  dah();
}

void morseR() {
  dit();
  dah();
  dit();
}

void morseS() {
  dit();
  dit();
  dit();
}

void morseT() {
  dah();
}

void morseU() {
  dit();
  dit();
  dah();
}

void morseV() {
  dit();
  dit();
  dit();
  dah();
}

void morseW() {
  dit();
  dah();
  dah();
}

void morseX() {
  dah();
  dit();
  dit();
  dah();
}

void morseY() {
  dah();
  dit();
  dah();
  dah();
}

void morseZ() {
  dah();
  dah();
  dit();
  dit();
}

//Morse functions for dits and dahs, as well as pause(), halt() and hyperventilate().

void dit () {
  digitalWrite(LED_PIN, HIGH);
  delay(DIT);
  digitalWrite(LED_PIN, LOW);
  delay(DIT);
}

void dah () {
  digitalWrite(LED_PIN, HIGH);
  delay(DAH);
  digitalWrite(LED_PIN, LOW);
  delay(DIT);
}

void pause() {
  delay(DAH);
}

void halt() {
  delay(7*DIT);
}

void hyperventilate() {
  for (int i = 0; i < 5; i++) {
    digitalWrite(LED_PIN, HIGH);
    delay(80);
    digitalWrite(LED_PIN, LOW);
    delay(80);
  }
}

This is strangely familiar.

Qdeathstar:
This is strangely familiar.

COMPETITION ($40 prize)- Morse Code - Bar Sport - Arduino Forum

Sorry, but I don't see any information that helps me with my question here. I am looking for a more optimised code, not necessarily more optimised function.

Use arrays, see this example.

Yes Larry,

It is a very good solution.

Jacques

Here’s one I built. It has a couple more optimizations.

// Pin 13 has an LED connected on most Arduino boards.
const byte led = 13;
const int DotLength = 92;

const byte messageString[] PROGMEM = "  Arduinos Rock.  ";

const byte MorseCodeArray[] PROGMEM = {
  0, 0, 0x52, 0, 0, 0, 0, 0x5E, 0x6D, 0x6D, 0, 0, 0x73, 0x61, 0x55, 0x32,             // Special chars
  0x3F, 0x2F, 0x27, 0x23, 0x21, 0x20, 0x30, 0x38, 0x3C, 0x3E, 0x78, 0, 0, 0, 0, 0x4C, // 0-9, :
  0, 5, 0x18, 0x1A, 0xC, 2, 0x12, 0xE, 0x10, 4, 0x17, 0xD, 0x14, 7, 6, 0xF,           // A-O
  0x16, 0x1D, 0xA, 8, 3, 9, 0x11, 0xB, 0x19, 0x1B, 0x1C, 0, 0, 0, 0, 0,               // P-Z
  0, 5, 0x18, 0x1A, 0xC, 2, 0x12, 0xE, 0x10, 4, 0x17, 0xD, 0x14, 7, 6, 0xF,           // a-o
  0x16, 0x1D, 0xA, 8, 3, 9, 0x11, 0xB, 0x19, 0x1B, 0x1C, 0, 0, 0, 0, 0                // p-z
};

void setup() {
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
}

void BlinkDot()
{
  digitalWrite(led, HIGH);
  delay(DotLength);
  digitalWrite(led, LOW);
  delay(DotLength);
}

void BlinkDash()
{
  digitalWrite(led, HIGH);
  delay(DotLength * 3);
  digitalWrite(led, LOW);
  delay(DotLength);
}

void EndOfLetter()
{
  delay(DotLength * 2);
}

void EndOfWord()
{
  delay(DotLength * 4);
}

void BlinkLetterCode(byte LetterCode)
{
  if (LetterCode > 1)
  {
    BlinkLetterCode(LetterCode >> 1);
    if (LetterCode & 1)
      BlinkDash();
    else
      BlinkDot();
  }
  else
    EndOfLetter();
}

void loop() {
  int i;
  char ch;

  for (i = 0;; ++i)
  {
    ch = pgm_read_byte(messageString + i);
    if (ch == 0)
      break;
    if (ch == ' ')
      EndOfWord();
    else if (ch > ' ' && ch <= 0x7F)
    {
      ch = pgm_read_byte(MorseCodeArray + ch - 0x20);
      BlinkLetterCode(ch);
    }
  }
}

Basically, each ASCII character letter is encoded into a byte in the MorseCodeArray. The first 1 bit signals the beginning of the data. After that, 0’s are dots and 1’s are dashes. So A is encoded as 5, which is 00000101 in binary – the leading zero bits are ignored, the first one bit signals the beginning of the data, and the data bits are the 0 and the 1, which translate into dot dash. B is 0x18 in hex which is 00011000 in binary, which corresponds to dash dot dot dot. And so forth.

The BlinkLetterCode function is a recursive function that decodes the binary byte into dots and dashes. It could easily be coded in a loop, but the data would have to be reversed.

PROGMEM, and the corresponding pgm_read_byte calls put the data into flash instead of RAM.

What kind of efficiency are you aiming for? Speed? Code size? Maintainability?

Jimmus:
Here’s one I built. It has a couple more optimizations.

// Pin 13 has an LED connected on most Arduino boards.

const byte led = 13;
const int DotLength = 92;

const byte messageString PROGMEM = "  Arduinos Rock.  ";

const byte MorseCodeArray PROGMEM = {
 0, 0, 0x52, 0, 0, 0, 0, 0x5E, 0x6D, 0x6D, 0, 0, 0x73, 0x61, 0x55, 0x32,             // Special chars
 0x3F, 0x2F, 0x27, 0x23, 0x21, 0x20, 0x30, 0x38, 0x3C, 0x3E, 0x78, 0, 0, 0, 0, 0x4C, // 0-9, :
 0, 5, 0x18, 0x1A, 0xC, 2, 0x12, 0xE, 0x10, 4, 0x17, 0xD, 0x14, 7, 6, 0xF,           // A-O
 0x16, 0x1D, 0xA, 8, 3, 9, 0x11, 0xB, 0x19, 0x1B, 0x1C, 0, 0, 0, 0, 0,               // P-Z
 0, 5, 0x18, 0x1A, 0xC, 2, 0x12, 0xE, 0x10, 4, 0x17, 0xD, 0x14, 7, 6, 0xF,           // a-o
 0x16, 0x1D, 0xA, 8, 3, 9, 0x11, 0xB, 0x19, 0x1B, 0x1C, 0, 0, 0, 0, 0                // p-z
};

void setup() {
 pinMode(led, OUTPUT);
 digitalWrite(led, LOW);
}

void BlinkDot()
{
 digitalWrite(led, HIGH);
 delay(DotLength);
 digitalWrite(led, LOW);
 delay(DotLength);
}

void BlinkDash()
{
 digitalWrite(led, HIGH);
 delay(DotLength * 3);
 digitalWrite(led, LOW);
 delay(DotLength);
}

void EndOfLetter()
{
 delay(DotLength * 2);
}

void EndOfWord()
{
 delay(DotLength * 4);
}

void BlinkLetterCode(byte LetterCode)
{
 if (LetterCode > 1)
 {
   BlinkLetterCode(LetterCode >> 1);
   if (LetterCode & 1)
     BlinkDash();
   else
     BlinkDot();
 }
 else
   EndOfLetter();
}

void loop() {
 int i;
 char ch;

for (i = 0;; ++i)
 {
   ch = pgm_read_byte(messageString + i);
   if (ch == 0)
     break;
   if (ch == ’ ')
     EndOfWord();
   else if (ch > ’ ’ && ch <= 0x7F)
   {
     ch = pgm_read_byte(MorseCodeArray + ch - 0x20);
     BlinkLetterCode(ch);
   }
 }
}



Basically, each ASCII character letter is encoded into a byte in the MorseCodeArray. The first 1 bit signals the beginning of the data. After that, 0's are dots and 1's are dashes. So A is encoded as 5, which is 00000101 in binary -- the leading zero bits are ignored, the first one bit signals the beginning of the data, and the data bits are the 0 and the 1, which translate into dot dash. B is 0x18 in hex which is 00011000 in binary, which corresponds to dash dot dot dot. And so forth.

The BlinkLetterCode function is a recursive function that decodes the binary byte into dots and dashes. It could easily be coded in a loop, but the data would have to be reversed.

PROGMEM, and the corresponding pgm_read_byte calls put the data into flash instead of RAM.

Great! That’s what I was looking for, overall good optimisations! Thank you for the code.
Since I am still new to the world of binaries, I will have to take my time understanding the BlinkLetterCode function.
But the idea of using bytes for each letter is probably the most efficient way of storing the letters, right?
Also, how does using Flash improve the situation (practically) over RAM? It’s faster, of course, but is that even needed if you want the signals to be human read?

Thanks for your input!

aarg:
What kind of efficiency are you aiming for? Speed? Code size? Maintainability?

Mostly code size as well as program size. Speed is not much of an issue, since I want it to be readable with the eyes. And what exactly do you mean with maintainability?

Cheers!

Bene:
Also, how does using Flash improve the situation (practically) over RAM?

For a small program you won't necessarily see a benefit. However, if you later decide to add a list of canned messages or add some features/functions you may approach the limits of RAM (UNO = 2K). This is not good as the stack also lives in RAM. Putting those messages in PROGMEM (UNO = 32K) frees up the RAM space they occupied, albeit at the price of a slightly more involved method of accessing them.

Bene:
And what exactly do you mean with maintainability?

Generally, a structure that makes it easy to find and correct problems, and add new features.

Here is just another fun way to decode Morse... This doesn't blink any lights but just shows the decode from a binary tree.

static char Code[] = " ETIANMSURWDKGOHVF.L.PJBXCYZQ";  //binary MORSE tree blank A-Z

void setup() {
  Serial.begin(9600);
  Serial.println(MorseOut("Hello World"));
  Serial.println(MorseOut("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
}
void loop() {
}

String MorseOut(String MString) {
  String codeString = "";
  char *text = MString.c_str();
  uint8_t index;
  while (text[0] != 0) {
    index = 28;
    if (text[0] > 32) text[0] &= 0xDF;      //Convert to uppercase
    for (;text[0] != Code[index]; --index); //Find char in binary tree 
    if (!index) codeString += "   ";        //Add Space between words
    else codeString += getDotDash(index);   //Build Code for this character and add to codeString
    ++text;                                 //Get next character in the string
  }
  return codeString;                        //Return Morse code for Mstring as dots dashes and spaces
}

String getDotDash(uint8_t n) {
  if (n > 0) return getDotDash((n - 1) / 2) + ((n & 1) ? '.' : '-'); // Recurse binary tree odd=dot even=dash
  return " ";
}