morse code flasher - lookup table question

Thanks for the quick reply!

So, how would I iterate thru the individual characters of the string? Will I be able to grab each character this way?

Sorry. I pre-warned ya that it's been a while. :slight_smile:

Interesting project. I use a slightly different approach in the example morse transmitter that ships with Bitlash that may be worth considering.

In this implementation, the morse data is encoded as a table of bytes, bit-packed. Three bits of length and five bits of morse data for each character, with a little overflow table for symbols that don't fit. All in PROGMEM, of course.

Code is here for the taking: bitlash/morse2.pde at master · billroy/bitlash · GitHub

Good luck with your project,

-br

AlphaTango:
I've seen the various morse code flashers, but none like I want.

I made a Morse lookup table using ascii - example:

const char *table[] = {
    ".-",
    "-...",
    "-.-.",
    "-..",
    ".",
    "..-.",
// etc......
};

Then parse the string I want to send as Morse code, use the number as an array index (i.e. table

), then parse the "-" and "." from the string until the null terminator.

Make sense?

Billroy - Thanks for the response and example. I'm impressed. When I looked at the code, I was immediately overwelmed. LOL! Too complex for me at this juncture. Thanks none the less.

Krupski - yeah, this is more what I had in mind. So, are you telling me that I can literally use the individual characters for the index?? I had no idea. That makes things simpler. Is that * a reference to pointer in this instance? If so, you're shifting thru the symbols, correct?

Here is some ideas:

"e" should be a dit, not a dah, yes?

In Morse code the length of a dah is usually three times a dit, I believe.

I would use three values for the dit and dah values: 1 = dit, 3 = dah, and 0 = no blink. The need for the third value is to allow you to pad the unused array elements with a value since some only use one like "e", while others like "1" use five

Use a two-dimensional array and initialize the second element with your dit and dah data. The offset into the first element of the array can be the character's ASCII value. Thus, if you have an int array named didah, then didah[65] = "1","3","0","0","0" which represents "A" (ascii 65) and would return dit dah 0 0 0

You could then use a function that is passed the value from each of the values in didah[65]. The value could then be multiplied by 500 to achieve the delay time.

I'm sure you get the idea...

read a character from the Serial object
convert character to ASCII and store in a variable
then pass the variable into a function that enters a for/loop to read each of the elements at that offset and passes the array value to your blink function
the blink function turns ON the LED for 500 * the number milliseconds -- 1/2 second for a dit, 1.5 seconds for a dah, and 0 seconds for the padding
don't forget the pause between letters

I hope I've been clear.

Good luck, and ask if you have further question.

Thanks MaJiG! I think you were clear, but it's a little more complicated than I can comprehend at this point. It's been a long time since I coded anything. I think what Krupski said is more what I had in mind, although you have given me some ideas to make it even simpler.

Thanks!

Here is a example that only recognizes a or b or c (either case).

const int spd = 300;    // milliseconds for a dit

// the character array (only holds abc)
int didah[3][5] = 
{
  { 1, 3, 0, 0, 0 },
  { 3, 1, 1, 1, 0 },
  { 3, 1, 3, 1, 0 }
};

void setup()
{
  pinMode(13, OUTPUT);    // pin 13 has the onboard LED, so no external parts
  
  Serial.begin(9600);
  Serial.println("---==START==---");
}

void loop()
{
  int mychar = 0;
  
  // check for a character
  if (Serial.available() > 0)
  {
     //here: there is a char coming in, so let's read it in
     mychar  = Serial.read();    // mychar now holds an ASCII value of the typed char
     
     if( mychar > 96 )
     {
       mychar -= 97 ; 
     }
     else
     {
       mychar -= 65 ;
     }
     
     // we need to keep within the array's limits
     if( (mychar >= 0) && (mychar <= 3) )
     {
        morsechar( mychar );
     }
  }
}

void morsechar( int x )
{
     for(int i = 0 ; i < 5 ; i++ )
     {
        Serial.println(didah[x][i]);
        blip( didah[x][i] );
     }  
}

void blip( int d )
{
  if( (d == 1) || (d == 3) )
  {
    d *= spd ;
    
    digitalWrite(13, HIGH);
    delay( d );
  
    digitalWrite(13, LOW);
    delay(spd * 5);       // arbitrary 5 dit off delay
  }
}

you would need to expand the array to accommodate all the chars and adjust or eliminate the -= adjustment.

It runs on a bare Arduino UNO so it is easy to see.

Try it and let me know.

There's morse code all around us ...

#if defined(ARDUINO) && ARDUINO >= 100
  #include "Arduino.h"
#else
  #include "WProgram.h"
#endif

#include <ctype.h>

#if   defined(_BOARD_CEREBOT_MX4CK_)
const uint8_t       pinLED          = 64;               // ON-BOARD LED1
#elif defined(_BOARD_UNO_)
const uint8_t       pinLED          = 13;               // ON-BOARD LED1
#else
#error "!!!Target is unknown board!!!"
#endif


const unsigned long DELAY_CASE      = 150UL;

const unsigned long DELAY_DOT       = DELAY_CASE;       // MILLISECONDS
const unsigned long DELAY_DOT       = DELAY_INTRA;
const unsigned long DELAY_DASH      = 3 * DELAY_CASE;
const unsigned long DELAY_CHAR      = 3 * DELAY_CASE;
const unsigned long DELAY_WORD      = 7 * DELAY_CASE;
const unsigned long DELAY_MESSAGE   = 3 * 1000;         // 3 SECONDS

const uint8_t       LED_OFF         = LOW;              // SYNONYMN FOR READABILITY
const uint8_t       LED_ON          = HIGH;             // SYNONYMN FOR READABILITY


#define SIZEOF_ARRAY(ARRAY)         (sizeof(ARRAY) / sizeof(ARRAY[0]))

enum { EOS, DOT, DASH, MASK = 3 };

struct sequence_pair_t
{
    const char      character;                          // character
    const uint16_t  encoding;                           // sequence, zero terminated 'C' string
};

const sequence_pair_t  morse_lookup[] =
{
    // STRING OF 2-BIT VALUES ENCODE THE MORSE SEQUENCE, LSB -> MSB
    //
    // 00   END-OF-SEQUENCE
    // 01   '.'
    // 10   '-'
    // 11   UNUSED

    // EXAMPLES:
    //
    //  { 'A',    0b0000000000001101 }    // ".-"     EOS  EOS  EOS  EOS  EOS  EOS DASH  DOT
    //, { '0',    0b0000001010101010 }    // "-----"  EOS  EOS  EOS DASH DASH DASH DASH DASH

    
    // --- Numeric
      { '0',    0b0000001010101010 }    // "-----"
    , { '1',    0b0000001010101001 }    // ".----"
    , { '2',    0b0000001010100101 }    // "..---"
    , { '3',    0b0000001010010101 }    // "...--"
    , { '4',    0b0000001001010101 }    // "....-"
    , { '5',    0b0000000101010101 }    // "....."
    , { '6',    0b0000000101010110 }    // "-...."
    , { '7',    0b0000000101011010 }    // "--..."
    , { '8',    0b0000000101101010 }    // "---.."
    , { '9',    0b0000000110101010 }    // "----."
    
    // --- Alphabetic
    , { 'A',    0b0000000000001001 }    // ".-"
    , { 'B',    0b0000000001010110 }    // "-..."
    , { 'C',    0b0000000001100110 }    // "-.-."
    , { 'D',    0b0000000000010110 }    // "-.."
    , { 'E',    0b0000000000000001 }    // "."
    , { 'F',    0b0000000001100101 }    // "..-."
    , { 'G',    0b0000000000011010 }    // "--."
    , { 'H',    0b0000000001010101 }    // "...."
    , { 'I',    0b0000000000000101 }    // ".."
    , { 'J',    0b0000000010101001 }    // ".---"
    , { 'K',    0b0000000000100110 }    // "-.-"
    , { 'L',    0b0000000001011001 }    // ".-.."
    , { 'M',    0b0000000000001010 }    // "--"
    , { 'N',    0b0000000000000110 }    // "-."
    , { 'O',    0b0000000000101010 }    // "---"
    , { 'P',    0b0000000001101001 }    // ".--."
    , { 'Q',    0b0000000010011010 }    // "--.-"
    , { 'R',    0b0000000000011001 }    // ".-."
    , { 'S',    0b0000000000010101 }    // "..."
    , { 'T',    0b0000000000000010 }    // "-"
    , { 'U',    0b0000000000100101 }    // "..-"
    , { 'V',    0b0000000010010101 }    // "...-"
    , { 'W',    0b0000000000101001 }    // ".--"
    , { 'X',    0b0000000010010110 }    // "-..-"
    , { 'W',    0b0000000010100110 }    // "-.--"
    , { 'Z',    0b0000000001011010 }    // "--.."
    
    // --- Punctuation
    , { ',',    0b0000101001011010 }    // "--..--"
    , { '.',    0b0000100110011001 }    // ".-.-.-"
    , { ':',    0b0000010101101010 }    // "---..."
    , { '?',    0b0000010110100101 }    // "..--.."
    , { '-',    0b0000100101010110 }    // "-....-"
    , { '/',    0b0000000110010110 }    // "-..-."
    , { '(',    0b0000100110100110 }    // "-.--.-"
    , { ')',    0b0000100110100110 }    // "-.--.-"
    
//  , { '\'', ".----." }  // the windows IDE has problem with escaped character literals!
//  , { '\"', ".-..-." }  // the windows IDE has problem with escaped character literals!
};


void blinkDot()
{
    digitalWrite(pinLED, LED_ON);

    delay(DELAY_DOT);

    digitalWrite(pinLED, LED_OFF);
}


void blinkDash()
{
    digitalWrite(pinLED, LED_ON);

    delay(DELAY_DASH);

    digitalWrite(pinLED, LED_OFF);
}


void blinkCharacterSequence(uint16_t encoding)
{
    do
    {
        switch ( encoding & MASK )
        {
            case DOT:   blinkDot();      break;
            case DASH:  blinkDash();     break;
        }

        if ( encoding >>= 2 )
        {
            delay(DELAY_INTRA);
        }

    } while ( EOS != (encoding & MASK) );
}


void blinkCharactor(char ch)
{
    const int cEntries = SIZEOF_ARRAY(morse_lookup);
    
    for ( int i = cEntries; i--; )
    {
        if ( ch == morse_lookup[i].character )
        {
            blinkCharacterSequence(morse_lookup[i].encoding);

            return;
        }
    }
}


void loop()
{
    // Initialize message using "string" syntax which has invisible
    // terminating 0

    static const char  szMessage[] = { "Hello AlphaTango" };

    for ( int i = 0; szMessage[i]; )
    {
        blinkCharactor(toupper(szMessage[i++]));
        
        delay(((' ' != szMessage[i]) ? DELAY_CHAR : DELAY_WORD));
    }
    
    delay(DELAY_MESSAGE);
}


void setup()
{
    pinMode(pinLED, OUTPUT);
    digitalWrite(pinLED, LED_OFF);
    
    delay(DELAY_MESSAGE);
}

Your 'B' is actually a "D". B is dah dit dit dit. A listening ratio of 1.0 for a dit to 1.5 for a dah, is more pleasing. Visually you might try 1.0 to 2.0. Once you get to 23-25 words per minute, it all sounds like music. :wink:

You can try this code I wrote last May, it's tested and works on my Uno rev3.

// flashes  morse code from text on serial port
// special characters to adjust wpm and frequency

char c;                  // read off serial port
int pin = 8;
float dot = 200.0;       // duration of dot for 5 wpm
void setup() {
  pinMode(8, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  while(Serial.available()) {
    c = Serial.read();
    switch(c) {
      case '!' : dot*=1.1; break;        // slower by 10%
      case '@' : dot*=.9; break;         // faster by 10%
      case '%' : report(); break;        // show status 
      case ' ' : delay(7*dot); break;
      case 'a' : flash("01"); break;
      case 'b' : flash("1000"); break;
      case 'c' : flash("1010"); break;
      case 'd' : flash("100"); break;
      case 'e' : flash("0"); break;
      case 'f' : flash("0010"); break;
      case 'g' : flash("110"); break;
      case 'h' : flash("0000"); break;
      case 'i' : flash("00"); break;
      case 'j' : flash("0111"); break;
      case 'k' : flash("101"); break;
      case 'l' : flash("0100"); break;
      case 'm' : flash("11"); break;
      case 'n' : flash("10"); break;
      case 'o' : flash("111"); break;
      case 'p' : flash("0110"); break;
      case 'q' : flash("1101"); break;
      case 'r' : flash("010"); break;
      case 's' : flash("000"); break;
      case 't' : flash("1"); break;
      case 'u' : flash("001"); break;
      case 'v' : flash("0001"); break;
      case 'w' : flash("011"); break;
      case 'x' : flash("1001"); break;
      case 'y' : flash("1011"); break;
      case 'z' : flash("1100"); break;
      case 'A' : flash("01"); break;
      case 'B' : flash("1000"); break;
      case 'C' : flash("1010"); break;
      case 'D' : flash("100"); break;
      case 'E' : flash("0"); break;
      case 'F' : flash("0010"); break;
      case 'G' : flash("110"); break;
      case 'H' : flash("0000"); break;
      case 'I' : flash("00"); break;
      case 'J' : flash("0111"); break;
      case 'K' : flash("101"); break;
      case 'L' : flash("0100"); break;
      case 'M' : flash("11"); break;
      case 'N' : flash("10"); break;
      case 'O' : flash("111"); break;
      case 'P' : flash("0110"); break;
      case 'Q' : flash("1011"); break;
      case 'R' : flash("010"); break;
      case 'S' : flash("000"); break;
      case 'T' : flash("1"); break;
      case 'U' : flash("001"); break;
      case 'V' : flash("0001"); break;
      case 'W' : flash("011"); break;
      case 'X' : flash("1001"); break;
      case 'Y' : flash("1011"); break;
      case 'Z' : flash("1100"); break;
      case '0' : flash("11111"); break;
      case '1' : flash("01111"); break;
      case '2' : flash("00111"); break;
      case '3' : flash("00011"); break;
      case '4' : flash("00001"); break;
      case '5' : flash("00000"); break;
      case '6' : flash("10000"); break;
      case '7' : flash("11000"); break;
      case '8' : flash("11100"); break;
      case '9' : flash("11110"); break;
      case '.' : flash("010101"); break;
      case ',' : flash("110011"); break;
      default  : delay(7*dot); break;
    }
  }
}

void flash(char x[]) {
  for(int i=0; i<strlen(x); i++) {
    if(x[i]=='1') { 
      digitalWrite(pin, HIGH);        // dash
      delay(3*dot);
      digitalWrite(pin, LOW);
    }
    else {
      digitalWrite(pin, HIGH);        // dot
      delay(dot);
      digitalWrite(pin, LOW);
    }
  delay(dot);               // end of flash
  }
  delay(3*dot);             // end of character
}

void report() {
  Serial.print(1000/dot);
  Serial.print(" wpm, ");
}

Thanks guys! I was hoping to avoid typing all the crazy-long lookup tables. That was the reason for my "S=000" to make 'S" transmit "dot dot dot". I like simple. :slight_smile:

AlphaTango:
Krupski - yeah, this is more what I had in mind. So, are you telling me that I can literally use the individual characters for the index?? I had no idea. That makes things simpler. Is that * a reference to pointer in this instance? If so, you're shifting thru the symbols, correct?

Yes.

A simplified example: Imagine you only want to do Morse letters "A" through "E".

Take the character, subtract 'A' from it (makes A == 0, B == 1, etc...) then use it to index into the Morse array:

const char *morse[5] = {
    ".-", // A = 0
    "-...", // B = 1
    "-.-.", // C = 2
    "-..", // D = 3
    "." // E = 4
    // etc......
};

int m = 'C'; // canned letter C

m -= 'A'; // normalize to 0

for (int i = 0; i < strlen(morse[m]); i++) {
    if (morse[m][i] == "." {
        // generate a dot
    } else {
        // generate a dash
    }
}

Of course, you need to do the entire range, and save yourself grief by uppercasing the letters so you can handle lowercase input seamlessly.

Hope this helps.

Thanks Krupski! That's definitely what I had in mind, though I was going to use binary (0s for dits, 1s for dahs) for the pattern.

Can you explain why you're subtracting 'A' from each character? I don't quite understand that.

AlphaTango:
Thanks Krupski! That's definitely what I had in mind, though I was going to use binary (0s for dits, 1s for dahs) for the pattern.

Can you explain why you're subtracting 'A' from each character? I don't quite understand that.

Sure. If the letter "A" comes in, it's ASCII value is 65 decimal. To convert it to a zero based index, you subtract 65 from it (i.e. 'A').

So:
A=65-65 = 0
B=66-65 = 1
C=67-65 = 2
...etc..

see?

Yes, sir. Perfectly. Thank you!

JavaMan, In looking back over the posts, I realize that I overlooked your post. What you have written is VERY similar to what I had in mind when I wrote the pseudocode, with the exception of your case statement. I visualized using a lookup_table (array).

Very nice! Very clean code!

Thanks!

JavaMan, In looking back over the posts, I realize that I overlooked your post. What you have written is VERY similar to what I had in mind when I wrote the pseudocode, with the exception of your case statement. I visualized using a lookup_table (array).

Very nice! Very clean code!

Thanks!

AlphaTango,

Thanks for the kind words. I used the case statement instead of an array because it seemed like a more intuitive way to add additional characters. By the way, the comment regarding frequency adjustment is an artifact of another version that beeps instead of flashes.

Tom

Can't figure out why I can't get the code I adapted from this thread to work.

I'm using a DHT22 sensor with a 10756W shield on a Lolin NodeMCU V3.
Connections as follows:
DHT22 "-" to Lolin Lower Right "G"
DHT22 "+" to Lolin Lower Right "3V"
DHT22 "Out" to Lolin "D2"
I want the inbuilt LED to output DHT22 readings in morse code every 15s and have the serial monitor printing the same readings
The problem is that the inbuilt LED stays on forever :confused:

Can someone help me? Hope it's not to messy (noob) and sorry if I'm waking up a dead thread.

Code:

#include <DHT_U.h> //is necesary?

/**
   Example for reading temperature and humidity
   using the DHT22 and ESP8266

   Copyright (c) 2016 Losant IoT. All rights reserved.
   https://www.losant.com
*/

#include "DHT.h"

#define DHTPIN 4     // what digital pin the DHT22 is conected to
#define DHTTYPE DHT22   // there are multiple kinds of DHT sensors

//Morse
// flashes  morse code from text on serial port
// special characters to adjust wpm and frequency

char c;                  // read off serial port
int pin = LED_BUILTIN;
float dot = 200.0;       // duration of dot for 5 wpm

// Class declaration
DHT dht(DHTPIN, DHTTYPE);

// On a NodeMCU board the built-in led is on GPIO pin 2
#define LED_BUILTIN 2


void setup() {
  //Morse LED
  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output

  //Serial data
  Serial.begin(9600);

  // Wait for serial to initialize.
  while (!Serial) { }

  Serial.println("Device Started");
  Serial.println("-------------------------------------");
  Serial.println("Running DHT!");
  Serial.println("-------------------------------------");
}

int timeSinceLastRead = 0;

void loop() {
  // Report every 2 seconds.
  if (timeSinceLastRead > 2000) {
    // Reading temperature or humidity takes about 250 milliseconds!
    // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
    float h = dht.readHumidity();
    // Read temperature as Celsius (the default)
    float t = dht.readTemperature();
    // Read temperature as Fahrenheit (isFahrenheit = true)
    float f = dht.readTemperature(true);

    // Check if any reads failed and exit early (to try again).
    if (isnan(h) || isnan(t) || isnan(f)) {
      Serial.println("Failed to read from DHT sensor!");
      timeSinceLastRead = 0;
      return;
    }

    // Compute heat index in Fahrenheit (the default)
    float hif = dht.computeHeatIndex(f, h);
    // Compute heat index in Celsius (isFahreheit = false)
    float hic = dht.computeHeatIndex(t, h, false);

    Serial.print("H ");
    Serial.print(h);
    Serial.print("  .  ");
    Serial.print("T ");
    Serial.print(t);
    Serial.print("  ..  ");
    Serial.print("S ");
    Serial.print(hic);
    Serial.print("   ...   ");
    Serial.print('\n');

    while (Serial.available()) {
      c = Serial.read();
      switch (c) {
        case '!' : dot *= 1.1; break;      // slower by 10%
        case '@' : dot *= .9; break;       // faster by 10%
        case '%' : report(); break;        // show status
        case ' ' : delay(7 * dot); break;
        case 'a' : flash("01"); break;
        case 'b' : flash("1000"); break;
        case 'c' : flash("1010"); break;
        case 'd' : flash("100"); break;
        case 'e' : flash("0"); break;
        case 'f' : flash("0010"); break;
        case 'g' : flash("110"); break;
        case 'h' : flash("0000"); break;
        case 'i' : flash("00"); break;
        case 'j' : flash("0111"); break;
        case 'k' : flash("101"); break;
        case 'l' : flash("0100"); break;
        case 'm' : flash("11"); break;
        case 'n' : flash("10"); break;
        case 'o' : flash("111"); break;
        case 'p' : flash("0110"); break;
        case 'q' : flash("1101"); break;
        case 'r' : flash("010"); break;
        case 's' : flash("000"); break;
        case 't' : flash("1"); break;
        case 'u' : flash("001"); break;
        case 'v' : flash("0001"); break;
        case 'w' : flash("011"); break;
        case 'x' : flash("1001"); break;
        case 'y' : flash("1011"); break;
        case 'z' : flash("1100"); break;
        case 'A' : flash("01"); break;
        case 'B' : flash("1000"); break;
        case 'C' : flash("1010"); break;
        case 'D' : flash("100"); break;
        case 'E' : flash("0"); break;
        case 'F' : flash("0010"); break;
        case 'G' : flash("110"); break;
        case 'H' : flash("0000"); break;
        case 'I' : flash("00"); break;
        case 'J' : flash("0111"); break;
        case 'K' : flash("101"); break;
        case 'L' : flash("0100"); break;
        case 'M' : flash("11"); break;
        case 'N' : flash("10"); break;
        case 'O' : flash("111"); break;
        case 'P' : flash("0110"); break;
        case 'Q' : flash("1011"); break;
        case 'R' : flash("010"); break;
        case 'S' : flash("000"); break;
        case 'T' : flash("1"); break;
        case 'U' : flash("001"); break;
        case 'V' : flash("0001"); break;
        case 'W' : flash("011"); break;
        case 'X' : flash("1001"); break;
        case 'Y' : flash("1011"); break;
        case 'Z' : flash("1100"); break;
        case '0' : flash("11111"); break;
        case '1' : flash("01111"); break;
        case '2' : flash("00111"); break;
        case '3' : flash("00011"); break;
        case '4' : flash("00001"); break;
        case '5' : flash("00000"); break;
        case '6' : flash("10000"); break;
        case '7' : flash("11000"); break;
        case '8' : flash("11100"); break;
        case '9' : flash("11110"); break;
        case '.' : flash("010101"); break;
        case ',' : flash("110011"); break;
        default  : delay(7 * dot); break;
      }

    }

    timeSinceLastRead = 0;
    Serial.flush();
  }
  delay(15000); //15s delay
  timeSinceLastRead += 15000;
}

void flash(char x[]) {
  for (int i = 0; i < strlen(x); i++) {
    if (x[i] == '1') {
      digitalWrite(LED_BUILTIN, LOW);        // dah
      // turn the LED on (the built-in led on a Node MCU board is active low)
      delay(3 * dot);
      digitalWrite(LED_BUILTIN, HIGH);
      // turn the LED off (the built-in led on a Node MCU board is active low)
    }
    else {
      digitalWrite(LED_BUILTIN, LOW);        // dit
      // turn the LED on (the built-in led on a Node MCU board is active low)
      delay(dot);
      digitalWrite(LED_BUILTIN, HIGH);
      // turn the LED off (the built-in led on a Node MCU board is active low)
    }
    delay(dot);               // end of flash
  }
  delay(3 * dot);           // end of character
}

void report() {
  Serial.print(1000 / dot);
  Serial.print(" wpm, ");
}

The data that you pass to the flash() function has NOTHING to do with the data that you read from the DHT or that you send TO the serial port.

    Serial.flush();

Do you KNOW what this function does? If not, why are you calling it? If you do, explain just WHY it is necessary to call it.