Scrolling matrix display

I'm working on making a scrolling LED matrix display. I'm storing a 1-bit bitmap of the entire display content that's being scrolled in ram and then using a moving pointer to update the display with a portion of the ram area. It my own idea, but I don't know if I'm reinventing the wheel or there's a better way to do it. It's working beautifully; the problem is that I'm burning way too much ram with this technique. A 100-character message will use 600 bytes of ram with my 5x7 font, which seems excessive when the MCU only has 1 or 2k.

What do people normally do to implement a scrolling display?

They keep the message data in the flash memory area, rather than in SRAM. See PROGMEM - Arduino Reference

dxw00d:
They keep the message data in the flash memory area, rather than in SRAM. See PROGMEM - Arduino Reference

I'm using program memory to store font and symbols, but for the actual message, I'm dynamically building it at run time.

You must have some kind of array that represents the led matrix? this is 8x8? so at any one time you only need to read the sizeof matrix from PROGMEM.

Oracle:
I'm working on making a scrolling LED matrix display. I'm storing a 1-bit bitmap of the entire display content that's being scrolled in ram and then using a moving pointer to update the display with a portion of the ram area. It my own idea, but I don't know if I'm reinventing the wheel or there's a better way to do it. It's working beautifully; the problem is that I'm burning way too much ram with this technique. A 100-character message will use 600 bytes of ram with my 5x7 font, which seems excessive when the MCU only has 1 or 2k.

What do people normally do to implement a scrolling display?

You are taking your message which is in RAM and decoding it with the font which is in flash to precreate a bitmap which is necessarily large, in RAM.

Why?

I have a scroller and I do basically what you do but there is no problem decoding the text via the font on the fly with no need to store the bitmap anywhere except in the driver/shift registers. The Arduino is fast enough for that, at least it was for me with 6 8x8 displays.

JoeN:
I have a scroller and I do basically what you do but there is no problem decoding the text via the font on the fly with no need to store the bitmap anywhere except in the driver/shift registers. The Arduino is fast enough for that, at least it was for me with 6 8x8 displays.

Very nice looking display. That's what I'm working on, currently I have one 8x8 matrix on breadboard wired as you described and I just ordered some PCBs for 7219 backpacks to build a row of modules, exactly what you have but I haven't decided how many modules I'll use yet.

Is your actual display as choppy as the video? I thought about decoding the font on the fly but figured it would make the timing more difficult and just feel kludgy in code. Are you using a blocking delay or using millis() to make sure the time interval between updating the display is constant? Being more careful with the time between updates could make your scroll more smoothly.

Right now, my code is incredibly simple, I don't even need to read all the symbols from the font. For a time display when I want a colon, I just set 2 columns to 0x24, and to make the colon blink seamlessly as it scrolls, I can blank and reset those 2 columns every second. Replicating that functionality without the buffered bitmap would be difficult. Same goes for any scrolling animations.

Anyway, thanks for your reply, I guess you've given me the right answer, which is to work out the more complicated code. Either that or upgrade my mega168 to a 328 and limit my messages to 150 characters or so :).

Edit: I didn't notice you posted code at first, I just read through it, it's not quite as difficult as I thought, so I really should spend some time trying to do it that way (at least if I don't want animations). I'm a bit confused by your triple loop, is it because your font is storing columns but you're updating the displays as rows so you're twisting around bits?

Is the delay(10) after the for i loop the only time keeping? If so, is it scrolling as fast as it's able right now?

It is choppy and I was not able to determine why exactly. The delay is to slow it down, it's not timekeeping. At first I thought the problem was that the amount of time through the loop was variable so I got the time at the start of the loop using the micros() function call and delayed the end of the loop variably as 10000 micros - the number of microseconds spent in the loop to make sure the time for every loop iteration was constant (10ms). However, that did nothing at all to fix the problem nor did some other stuff I tried before I benched it for the time being. Still not sure if it is the way I am using the MAX chips or what, but it doesn't seem to be because of variable execution time. The fact is, those bitwise operations are very fast and execute in constant time so I don't think that is part of the problem at all. All the other stuff is integer looping and comparison, these should all get translated to one clock instructions. Maybe it is in the driver library, or maybe it wasn't really designed for scrolling. I am not sure yet.

I just wanted to demonstrate that you can use bitmap operations to figure out "what bit you are on" when displaying text from a bitmapped font rather than blowing it up into a huge array in advance.

As far as the loops. You can see the data is being sent to the MAX chips using the MAX library here:

lc.setRow(i, j, outputbyte);

So the outer loop i determines the display, it runs 0 to 5. (the comment says "loop through our 8 displays" this is wrong)

The center loop j determine which row we are on, it runs 0 to 7. The MAX library sets a row at a time with a bitmapped byte.

The inner loop k sets up that byte so it too runs from 0 to 7. It copies bits from the font map for the current character into the output byte that will be sent to the MAX chip. It also does work to determine when we have advanced past the current character using the font length array or have advanced past the end of the message itself.

Too bad the MAX chips can't be used as "dumb" shift registers. You could just send a new column of data and let the last one fall off the most significant end of the register. Instead you are sending the whole damn dataset every time you want to advance the display a column.

I have attached some test code I used to perfect monochrome scrolling and the use of PROGMEM. The output routine is currently configured for testing on the Rainbowduino but was intended for MAX7219 chips. The final code went into a 16x8 MAX7219 driven matrix and would support 24x8 as it stands and *maybe 56x8 with minimal modification. *I say maybe because I'm not sure how fast it would scroll.

ScrollTest3.ino (13.8 KB)

Hi,

I am using JoeN's code for scrolling text and I was wondering if you ever fixed the choppiness? I was also wondering if there was a way using your code to display text that is short enough to display without scrolling?

I am little confused how your loops only which char is which in the array.

I am able to send text from the serial port and display it using your code. Thank you for posting it.

I am using a 24x8.)

Thanks.

I sort of got it to work. I created a new function that doesn't loop and resets the counters. Still can't get it to finish cleanly. What is a good way for it to stop when all the characters are displayed? Here is what I have.

void staticDisplay()
{
  
curcharix = 0;
curcharbit = 0;
curcharixsave = 0;
curcharbitsave = 0;
curcharixsave2 = 0;
curcharbitsave2 = 0;

clearDisplay();
  

  
    for (i=devCount-1;i>=0;i--) // Loop through our 8 displays
    {
      for (j=0;j<8;j++) // Set up rows on current  display
      {      
        byte outputbyte = 0;
   
        curchar = msg[curcharix];
   
        curcharixsave = curcharix;
        curcharbitsave = curcharbit;
      
        for (k=7;k>=0;k--) // Copy over data for 8 columns to current row and send it to current display
        {
          // This byte is the bitmap of the current character for the current row
          byte currentcharbits = Font8x5[((curchar-32)*8)+j];
      
          if (currentcharbits & (1<<curcharbit))
            outputbyte |= (1<<k);
      
          // advance the current character bit of current character
      
          curcharbit ++;
      
          if (curcharbit > lentbl_S[curchar-32]) // we are past the end of this character, so advance.
          {
            curcharbit = 0;
            curcharix += 1; 
            if (curcharix+1 > msgsize) curcharix=0; //This is what I was playing with.
            curchar = msg[curcharix];
          }
        }
      
        lc.setRow(i, j, outputbyte);
        
     

        if (j != 7) // if this is not the last row, roll back advancement, if it is, leave the counters advanced.
        {
          curcharix = curcharixsave;
          curcharbit = curcharbitsave;
        }

      }
    }

  

  
}

I got it to work. Here it is in case anyone needs it

void staticDisplay()
{
  
curcharix = 0;
curcharbit = 0;
curcharixsave = 0;
curcharbitsave = 0;
curcharixsave2 = 0;
curcharbitsave2 = 0;

clearDisplay();
  
bool endFlag = false;
  
    for (i=devCount-1;i>=0;i--) // Loop through our X displays
    {
      for (j=0;j<8;j++) // Set up rows on current  display
      {      
        byte outputbyte = 0;
   
        curchar = msg[curcharix];
   
        curcharixsave = curcharix;
        curcharbitsave = curcharbit;
      
        for (k=7;k>=0;k--) // Copy over data for 8 columns to current row and send it to current display
        {
          // This byte is the bitmap of the current character for the current row
          byte currentcharbits = Font8x5[((curchar-32)*8)+j];
      
          if (currentcharbits & (1<<curcharbit))
            outputbyte |= (1<<k);
      
          // advance the current character bit of current character
      
          curcharbit ++;
      
          if (curcharbit > lentbl_S[curchar-32]) // we are past the end of this character, so advance.
          {
            curcharbit = 0;
            curcharix += 1; 
            if (curcharix+1 > msgsize) k=-1; //This end the current display
            curchar = msg[curcharix];
            
          }
        }
      
        lc.setRow(i, j, outputbyte);
        
       if (endFlag) return;

        if (j != 7) // if this is not the last row, roll back advancement, if it is, leave the counters advanced.
        {
          curcharix = curcharixsave;
          curcharbit = curcharbitsave;
          if (curcharix+1 > msgsize) endFlag=true; //sets that we are done and exit after the next iteration
        }

      }
    }

  

  
}

I just noticed this message was back up on the list. Over the weekend, I soldered up this project on a pad per hole veroboard with a standalone ATMega328P replacing the Arduino on the board itself. I also increased the number of displays and 7219s to 8. I am going to rework the software soon to try to get rid of that scrolling choppiness bug. I think I may have to bypass the MAX7219 library and bitbang the chips myself. Hopefully I can figure it out. Glad you got it to work for you.

" I think I may have to bypass the MAX7219 library and bitbang the chips myself."
I would suggest using SPI.transfer() to send data to the MAX7129's. That will help your speed a lot.
Send out 64 bytes as one burst at whatever rate you update the display, with all devices daisy chained:

digitalWrite(SS_pins, LOW)
for (x=0; x<64; x=x+1){
SPI.transfer(displayArray[x]);
}
digitalWrite(SS_pins, HIGH);

speed it up more by using direct port manipulation to set & clear SS_pins:

PORTB = PORTB & B11111110; //clear bit 0
PORTB + PORTB | B00000001;  // set bit 0

make it really fast by skipping the for:next looping and just use 64 SPI.tranfers in a row

void loop(){
currentMillis = millis();
if ( (currentMillis - previousMillis) >=2){ // 2mS elapsed? 
// The following will be quick - (1/16,000,000 * 64 bytes * 8 bits/byte)* 10? for reading the array = 0.3mS ??
previousMillis = previousMillis+2;
PORTB = PORTB & B11111110; //clear bit 0, use whatever port/bit you have - such as the SS/D10 pin
SPI.transfer(displayArray[0]);
SPI.transfer(displayArray[1]);
SPI.transfer(displayArray[2]);
:
:
SPI.transfer(displayArray[61]);
SPI.transfer(displayArray[62]);
SPI.transfer(displayArray[63]);
PORTB + PORTB | B00000001;  // set bit 0
}
//update your array however you're going to
}

I am saving on memory by having no display buffer at all. The code generates the bits from the display message, display font, and current position every time it sends data to the MAX7219 chips, which is every time it scrolls a column over. But all of that interface code I can use, thank you.

I wondered about the SPI thing. I thought the max7219 was non-standard SPI unlike the max7221. But I see both ways of talking to it in different sketches. Maybe we could just modify the LEDCONTROL library to use SPI in the library.

Btw I figured out your font table and how that all works. I noticed ~ doesn't work but it should. What is the highest ASCII charc you use? I think I figured it was 32-136. Which after 127 it is the extended charc which doesn't display correctly, so I must be missing something.

RIght now I am working on a breadboard but the PCB's should be here in a few weeks.

Let me know if I can help with anything.

Thanks again, this has really helped a lot more than any other examples. I spent all weekend on this.

Edit: I have to admit the other ones that where smooth didn't use the LEDCONTROL library and just did SPI inside the sketch. Would be nice to see a library that uses SPI.

I just realized something, if we use SPI then the pin assignments will change right? Oops, I guess time to update my PCB for the next round but I can tweak the current one.

I never tried a tilde. I got the character set from a font generation program called cv_fonted which is here:

The font data for character 126 is 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, so clearly that is blank. If you want a tilde either download cv_fonted and use the editor or make a guess and roll your own. This data is row-wise so here is a guess:

12481248
________  0x00
________  0x00
________  0x00
_XX_____  0x06
X__X__X_  0x49
____XX__  0x30
________  0x00
________  0x00

{0x00, 0x00, 0x00, 0x06, 0x49, 0x30, 0x00, 0x00}

There is a newer version of cv_fonted which, while it is a lot better, it doesn't have an 8x8 font which is what I wanted.

Thanks. Then the length would be 8 right?

So are the ones after the tilde the extended ascii characters or you just used whatever it generated?

I might try this font table

Of course I can't use the whole table since it won't fit in memory, well it might on the 32u4, which I will be switching too once I get one.

So it's a tradeoff - 64 bytes (of 2K) and fast flicker free operation, or flickering operation with all kinds of data processing going on every byte.