Making a long 'memory' LED chase sequence

Hello all

I have been asked to see if I can come up with a way of sending morse code along a 4m strip of addressable LEDS.
You would type the text into the serial port, (or I can add a keyboard), and that text gets converted into dots 'n' dashes and it then travels the length of the LED strip at a set speed.

He would like it 'real time' so if you typed a single letter, then only that letter would travel the strip.
If you typed very quickly, then the entire message would scroll.

I have sat here scratching my head, but have yet to come up with a viable solution.

4m of led strip is 240 LEDS. What would be the best way of developing some kind of memory system to send that data along the line? Would you need a memory? Not so sure you do.
It's disposable data.

I originally thought the strip would constantly scroll even if there is no message, but that doesn't seem to be right.

I think you almost need to send each part of the message on it's own 0-240 increment as it's typed. Not sure how I can efficiently achieve that.

If you 'filled' the entire strip with morse code, that is a lot of scrolling loops!

Any ideas on where to possibly start?

Thanks

The state of the LEDs in the strip are already held in an array, so to make a sequence of dots/dashes scroll along the strip all you need to do is to is to shift each value along one position in the LED array and show the strip again

The best method of entering the text would be to type it all followed by and end of message character such as Carriage Return or Linefeed

All addressable LED code is based on a memory array that is - when and only when it is necessary to alter the pattern in the LED strip - read out into the strip.

So your code is based on manipulating that memory; shifting a pattern through it is one of the most common operations and facilitated by the libraries available,

As we so often explain, your first task is to assemble the hardware and experiment with the examples provided with the libraries.

I've never used an addressable-LED strip...

I think most of the libraries require you to load the whole pattern in memory, but the data is sent serially into the strip one-LED-at-a-time LED and the strip has it's own "memory" so technically it shouldn't be necessary to store the whole data pattern (depending on what you're doing). Each pixel can be generated just-before it's sent.

...Unless you want to reverse the direction of the "movement". In that case the processor needs to keep track because you can't read the strip... You can only write to it.

I do have it set up here in the living room.
I have thrown it onto a Mega (because that was to hand).

I understand selecting a certain pixel, and setting it's colour/brightness. And I can 'scroll' that pixel along the strip using a for loop or counter.

But, what you are saying is that I could effectively 'Place the dots and dashes in the say first 10 leds, then push that message along the strip?

I have been playing with the Fastled library and not found any reference to do that.
I will tinker further

Mods.... i am having real issues logging into this account. The reset password doesn't work, and nothing ever appears in my email. Can someone check it please. Thanks

To get attention from a moderator, you may flag your own message. They will understand.

The more I think about this project, the harder it gets.
Perhaps using FastLED is easier, because you may directly alter the array with leds.
I think a millis-timer might not work with FastLED with 240 leds, but FastLED has its own timing based support.
Reading data from the serial port is no problem either.
Converting a character to its morse code is also no problem.

Turning the character into morse code with the right delay between the characters and shifting that into the ledstrip is the hardest part.
Placing the morse code in the first 10 leds might be not so nice.

To make it look good, you need a buffer before the leds. Put the morsecode in the buffer and shift the buffer into the ledstrip. Do you think that you can do that ? You need a lot of code and variables.

Here are my thoughts:

  1. The LEDS are just an array of color values. If you want to move a 'dot' along the length of the strip, just set it at one end and then every x milliseconds move the whole array along by one in a loop copying the data over one spot. At the end of the loop set data[0] to the 'off' color
for (int i=sizeofLedArray-1; i>0; i--)
  leds[i] = leds[i-1];
leds[0] = the_off_color;
  1. On the question of what to do with 'new' data to be inserted, I would buffer it in the same array just by making the array longer. When you tell the FastLED library how many LEDs it has, these are omitted (ie, they are 'off the end' of the array that FastLED knows about). When new data needs to be displayed, the x LEDs off the end are set to the new data and they will automatically scroll in using the loop above.

Most of the demo LED chase sequences are full of blocking for loops, and if you want it responsive to serial, you may want to shift to non-blocking code.

Here's a non-blocking Neopixel setup in Wokwi:

phoneystark20201, is it for school ?
Then you should not click on this link: MorsecodeToLedstrip.ino - Wokwi Arduino and ESP32 Simulator
Start the simulation and type a few words in the (simulated) Serial Monitor in the lower-right of the screen.
I wanted to see for myself how it would look, but I don't know if I like it.

The code is not finished, it is bad in some parts.
I used a buffer, because of a zero-terminated string has a variable length, which makes it easier to add things and to shift it into the ledstrip.
The morse code gets into the buffer and the buffer is shifted into the ledstrip.

1 Like

It's actually for my local sea cadets. They have a radio room, and they wanted to have a strip of LED's across the front of the building facing the river (long story why).
I have a strip of waterproof RGB's I can donate.... couple of beers later and here I am #KeepYourMouthShut

When you say use a buffer, you mean an array? Out of my comfort zone here.

Let me ask this.

I illuminate leds 1-10 in different colours (as an example). I would need to store those colours, and then shift them along the line in the correct order.
If I achieve that, then maybe on the right track?

I have not written any code yet, as I was trying to get the principle correct on paper first

Interesting link Koepel... I will look at that further

I'm not sure you need to use a buffer for the incoming text data. The serial port already has one for that. It might not be big enough, but cross that bridge when you come to it.

When a text character arrives, translate it into a string of characters representing the Morse Code for that character, for example "A" would become *.***... where '*' represents a lit pixel and '.' represents a dark pixel. Note that a Morse dash is 3 pixels and the pause after the character is also 3 pixels. This encoding not only translates the character into dots and dashes but also the correct length and timing of those dots and dashes.

Use the scrolling code suggested by @marco_c. Draw the dots/dashes by inserting them, one pixel at a time, only into pixel #0 of the strip.

1 Like

@phoneystark20201 I can not make it easier. Sorry.
You can start with the moving leds. With the FastLED library it is:

for( int i=NUM_LEDS-1; i>0; i--)
{
  leds[i] = leds[i-1];
}
FastLED.show();                // make the leds[] data visible

Can you turn leds on and off ? Either in real life or in a simulation ?
Do you know how much power you need ? 420 * 60mA = 25.2A
That current can not go through the led strip, so you need extra power wires with GND and 5V to power the ledstrip in many places.

With the FastLED library, there is an option to limit the current, for example to 10A. The library will dim the leds when too many leds are on.


@PaulRB The "*.***..." needs to be stored somewhere before inserting those one pixel at a time. That is exactly what I did, with a little variation, I use "XXX X XXX XXX XXX X X X". If it is not an intermediate buffer, is there a better name for it?

I use strcat() to put thing in it and use buffer[0] to be shifted into the ledstrip and then move everything in the buffer to the left.
Once the buffer is empty "(strlen(buffer) == 0)", then I check if there is a new character via Serial.available().

Sorry, I had no idea. I'm not a wokwi user.

I was suggesting that using a buffer might be avoided (although if the Serial input buffer isn't big enough, might need to re-think). The pattern for each character would simply be used one character at a time, it would not need to be transferred to an intermediate buffer.

It might depend on the direction one scrolls the pixels and characters. LEDs scrolling left-to-right would need to consume characters in right-to-left order to show in natural left-to-right transcribed morse order.

@Koepel -- Cool simulation!

Maybe fill from the other end or "R" rotate the led strip 180 degrees so the code aren't backwards.

I think it would also be nice to loop the last LED state into the first LED state when idle. Something like:

  EVERY_N_MILLISECONDS( 100)           // from the FastLED library
  {
   CRGB savePixel = leds[NUMLEDS-1];
    // Move the pixels one pixel to the right.
    // If there is no morse data, then there is nothing to shift,
    // but it is shifted anyway.
    for( int i=NUM_LEDS-1; i>0; i--)
    {
      leds[i] = leds[i-1];
    }

    leds[0] = savePixel;            // circular loop colors as default

    // The buffer contains the new data to be entered into the ledstrip.
    int n = strlen( buffer);
    if( n > 0)
    {
      if( buffer[0] != ' ')
      {
        leds[0] = FOREGROUND_COLOR;
      }
      else {
        leds[0] = BACKGROUND_COLOR;
      }

      // Move buffer to the left.
      // The zero-terminator is also copied
      for( int i=0; i<n; i++)
      {
        buffer[i] = buffer[i+1];
      }
    }
    else // buffer empty
    {
      leds[0] = savePixel;
    }
    FastLED.show();                // make the leds[] data visible
  }

...but that seems to need clearing a better gap for new characters. \\\\\\\\\\\\\\ edited to fix with an else.

Since Wokwi doesn't appear to let you rotate the LED strip, Here's a version of @Koepel 's Wokwi code, changing the scrolling to be right-to-left and circular:

// MorsecodeToLedstrip.ino
//
// For: https://forum.arduino.cc/t/making-a-long-memory-led-chase-sequence/967584/
// https://wokwi.com/projects/325693840020210260
// 9 March 2022, Version 1, by Koepel, Public Domain
//   Working a little, not checked, not a good sketch, it is bad in some parts.
// Modified 10 March 2022 to scroll right-to-left and recycle pixels
//
//   By using a buffer between the conversion table and the ledstrip,
//   it is easier to translate the (compact) conversion table and shift
//   the pattern to the ledstrip.
//   That buffer also makes it easy to add color information.
//
//

#include <FastLED.h>

#define LED_PIN  5
#define NUM_LEDS 240     // 240 leds could be possible with an Arduino Uno

CRGB leds[NUM_LEDS];     // leds[0] is the first led on the left

#define BACKGROUND_COLOR CRGB::Blue       // the background color for the ledstrip
#define FOREGROUND_COLOR CRGB::Red        // the morse code foreground color

const int potPin = A0;

byte buffer[40];  // a buffer that holds the morse code to be shifted into the ledstrip

#define MORSE_DOT '.'
#define MORSE_DASH '-'
#define MORSE_INTERCODE ' '
#define MORSE_INTERCHAR '#'

#define MORSE_DOT_LENGTH 1
#define MORSE_DASH_LENGTH 3
#define MORSE_INTERCODE_LENGTH 3
#define MORSE_INTERCHAR_LENGTH 8

// The conversion text for the morse code is made for:
//   - showing the morse code in readable format.
//   - compact
//   - code that needs time to search is okay
const byte morseTable[] PROGMEM =
  "#  "            // something special, a space, that is a gap between words
  "#A.-"           // ASCII character is 'A' code is ".-" no spaces in code
  "#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--.."
  "#1.----"
  "#2..---"
  "#3...--"
  "#4....-"
  "#5....."
  "#6-...."
  "#7--..."
  "#8---.."
  "#9----."
  "#0-----"
  "#..-.-.-"
  "#;-.-.-."
  "#\--..--"
  "#?..--.."
  "#/-..-."
  "#\r  "                 // end of line, should be a larger gap
  "#\n  "                 // end of line, should be a larger gap
  "#";                    // end of list



void setup() 
{
  Serial.begin(115200);
  Serial.print( "The size of the morse code table is: ");
  Serial.print( sizeof( morseTable));
  Serial.println( " bytes");
  Serial.println("Enter the text");

  FastLED.addLeds<WS2811, LED_PIN, GRB>(leds, NUM_LEDS);

  // pre-fill with background.
  for( int i=0; i<NUM_LEDS; i++)
  {
    leds[i] = BACKGROUND_COLOR;
  }
  FastLED.show();
}


void loop() 
{
  EVERY_N_MILLISECONDS( 100)           // from the FastLED library
  {
    CRGB savePixel = leds[0];
    // Move the pixels one pixel to the right.
    // If there is no morse data, then there is nothing to shift,
    // but it is shifted anyway.
    //for( int i=NUM_LEDS-1; i>0; i--)
    for( int i=0 ; i < NUM_LEDS-1; i++)
    {
      leds[i] = leds[i+1];
    }

    leds[NUM_LEDS-1] = savePixel;            // background color as default
    //Serial.print(savePixel.r);
    //Serial.print('.');
    //Serial.print(savePixel.g);
    //Serial.print('.');
    //Serial.print(savePixel.b);
    //Serial.print(' ');
    // The buffer contains the new data to be entered into the ledstrip.
    int n = strlen( buffer);
    if( n > 0)
    {
      if( buffer[0] != ' ')
      {
        leds[NUM_LEDS -1] = FOREGROUND_COLOR;
      } else {
        leds[NUM_LEDS -1] = BACKGROUND_COLOR;
      }

      // Move buffer to the left.
      // The zero-terminator is also copied
      for( int i=0; i<n; i++)
      {
        buffer[i] = buffer[i+1];
      }
    }
    FastLED.show();                // make the leds[] data visible
  }

  if( strlen( buffer) == 0)        // ready to accept new data ?
  {
    if( Serial.available() > 0)
    {
      byte serialChar = Serial.read();
      serialChar = toupper( serialChar);          // no lower case in the morse code

      // search for character in the table
      bool marker = false;         // a test for '#' marker
      bool found = false;
      int index;
      byte morseChar;

      for( index=0; index<strlen_P(morseTable); index++)
      {       
        byte morseChar = pgm_read_byte_near( morseTable + index);
   
        if( marker)                  // was previous character the marker ?
        {
          if( serialChar == morseChar)   // found the ASCII character ?
          {
            found = true;
            break;
          }
        }

        if( morseChar = '#')         // found marker ?
          marker = true;
        else
          marker = false;
      }

      if( found)
      {
        // Convert the character into morse code and put it in the buffer
        // This is the hard part
        // For now, just use a fixed pattern
        index++;           // point to data after the ASCII character
        do
        {
          morseChar = pgm_read_byte_near( morseTable + index);

          if( morseChar == '#')   // marker for new character ?
          {
            break;
          }
          else
          {
            if( morseChar == '.')
              strcat( buffer, "X ");
            else if( morseChar == '-')
              strcat( buffer, "XXX ");
            else if( morseChar == ' ')
              strcat( buffer, " ");
          }
          
          index++;

        } while( morseChar != '\0');
        strcat( buffer, "   ");         // spacing between characters
      }
      else
      {
        Serial.print("Character not found in morse table: ");
        // The isPrintable() is a Arduino function
        // The isprint() is a 'C' function
        if( isprint( serialChar))        
        {
          Serial.print( (char) serialChar);
        }
        else
        {
          Serial.print( "0x");
          if( serialChar < 0x10)
            Serial.print( "0");
          Serial.print( serialChar, HEX);
        }
        Serial.println();
      }
    } 
  }
}

Which direction is normal ?
If looking at a machine that prints the morse-code on paper, then the paper outputs to the right ? So the morse code is going to the right ?

Click on a component in Wokwi and press 'r' to rotate it.

Normal would make an A look like it does in your code:

...and if a device was transcribing a signal to paper tape as dots and dashes, and then you were trying to read it, you'd read it from left to right with the tape coming out of the machine from your right to the left.

As is, the Wokwi simulation prints an A like this and scrolls it towards the right:

@PaulRB

There's an interesting character -> bitmask morse table in https://github.com/afarhan/ubitxv6/blob/master/morse.cpp#L1 With that, you'd need no more storage than the table and to keep track of which bit in the in the bitmask you've shifted to the pixel array.

Thanks for all your replies chaps.
I am going to mock this up this weekend with the full RGB strip and have a play.

I think it needs to scroll right to left, so you read it as a written message.
Maybe I can eventually make the scroll direction an option to the user.