smooth marquee text scrolling with TFT (Adafruit GFX and ST7735 libraries)

Dear experts,

For my newby project I am using a TFT display (Adafruit GFX and ST7735 libraries) with some lines of static text and one line with marquee text scrolling from right to left, see code below.

As I am shifting the text by one letter for each step it does not appear as a smooth marquee scrolling flow. Does anybody have an idea how I can achieve this, e.g. move the text by some pixels to the left on each steps instead of shifting it letter by letter in order to get a smooth marquee? Could not find an example in the libraries mentioned but maybe overlooked it.

Thank you very much for your ideas and help.

void loop()
{
  //Scroll Routine
  String text = (furtherstops[furtherstops_id]);

  const int width = 14; // width of the marquee display (in characters)

  // Loop once through the string

  for (int offset = 0; offset < text.length(); offset++)

  {
    // Construct the string to display for this iteration

    String t = "";
    for (int i = 0; i < width; i++)
      t += text.charAt((offset + i) % text.length());

    if (scroll_renew == (true))
    {
      scroll_renew = (false);  
      offset = (text.length());
      tft.fillScreen(ST7735_WHITE);
    }
    // Print the string for this iteration
    tft.setCursor(20, 74); // display position
    delay(200);    // Short delay so the text doesn't move too fast
    tft.print(t);
  }
}

I don't think there is a specific function in adafruit_GFX. (there should be really) Pity that, so then i guess you'd have to add it to it yourself. Shouldn't be to hard to do. Either one can have an extra version of the text() function that is a bit more accurate in where to put the text and allows printing only half of the character. (basically anything outside of a certain frame should not get printed)
Or you could a move rectangle around, i bit at a time. In a way a more universal method, which allows for not just text but also images can be scrolled that way, but that would mean you need read access to what is on screen, or access to the buffer, i would have to look at the libraries to see if that is viable, the first method is for sure doable.

Thank you for your valuable ideas. Just to move the text is fine for my project. I do not plan to move images around, just one single line (of text) would be perfect. So, the first method seems promising, although I admit I have basically no clue on how to implement this, still too inexperienced but at least I know that it could be done.

From adafruit_GFX

/**************************************************************************/
/*!
   @brief   Draw a single character
    @param    x   Bottom left corner x coordinate
    @param    y   Bottom left corner y coordinate
    @param    c   The 8-bit font-indexed character (likely ascii)
    @param    color 16-bit 5-6-5 Color to draw chraracter with
    @param    bg 16-bit 5-6-5 Color to fill background with (if same as color,
   no background)
    @param    size_x  Font magnification level in X-axis, 1 is 'original' size
    @param    size_y  Font magnification level in Y-axis, 1 is 'original' size
*/
/**************************************************************************/
void Adafruit_GFX::drawChar(int16_t x, int16_t y, unsigned char c,
                            uint16_t color, uint16_t bg, uint8_t size_x,
                            uint8_t size_y) {

  if (!gfxFont) { // 'Classic' built-in font

    if ((x >= _width) ||              // Clip right
        (y >= _height) ||             // Clip bottom
        ((x + 6 * size_x - 1) < 0) || // Clip left
        ((y + 8 * size_y - 1) < 0))   // Clip top
      return;

    if (!_cp437 && (c >= 176))
      c++; // Handle 'classic' charset behavior

    startWrite();
    for (int8_t i = 0; i < 5; i++) { // Char bitmap = 5 columns
      uint8_t line = pgm_read_byte(&font[c * 5 + i]);
      for (int8_t j = 0; j < 8; j++, line >>= 1) {
        if (line & 1) {
          if (size_x == 1 && size_y == 1)
            writePixel(x + i, y + j, color);
          else
            writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y,
                          color);
        } else if (bg != color) {
          if (size_x == 1 && size_y == 1)
            writePixel(x + i, y + j, bg);
          else
            writeFillRect(x + i * size_x, y + j * size_y, size_x, size_y, bg);
        }
      }
    }
    if (bg != color) { // If opaque, draw vertical line for last column
      if (size_x == 1 && size_y == 1)
        writeFastVLine(x + 5, y, 8, bg);
      else
        writeFillRect(x + 5 * size_x, y, size_x, 8 * size_y, bg);
    }
    endWrite();

  } else { // Custom font

    // Character is assumed previously filtered by write() to eliminate
    // newlines, returns, non-printable characters, etc.  Calling
    // drawChar() directly with 'bad' characters of font may cause mayhem!

    c -= (uint8_t)pgm_read_byte(&gfxFont->first);
    GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c);
    uint8_t *bitmap = pgm_read_bitmap_ptr(gfxFont);

    uint16_t bo = pgm_read_word(&glyph->bitmapOffset);
    uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height);
    int8_t xo = pgm_read_byte(&glyph->xOffset),
           yo = pgm_read_byte(&glyph->yOffset);
    uint8_t xx, yy, bits = 0, bit = 0;
    int16_t xo16 = 0, yo16 = 0;

    if (size_x > 1 || size_y > 1) {
      xo16 = xo;
      yo16 = yo;
    }

    // Todo: Add character clipping here

    // NOTE: THERE IS NO 'BACKGROUND' COLOR OPTION ON CUSTOM FONTS.
    // THIS IS ON PURPOSE AND BY DESIGN.  The background color feature
    // has typically been used with the 'classic' font to overwrite old
    // screen contents with new data.  This ONLY works because the
    // characters are a uniform size; it's not a sensible thing to do with
    // proportionally-spaced fonts with glyphs of varying sizes (and that
    // may overlap).  To replace previously-drawn text when using a custom
    // font, use the getTextBounds() function to determine the smallest
    // rectangle encompassing a string, erase the area with fillRect(),
    // then draw new text.  This WILL infortunately 'blink' the text, but
    // is unavoidable.  Drawing 'background' pixels will NOT fix this,
    // only creates a new set of problems.  Have an idea to work around
    // this (a canvas object type for MCUs that can afford the RAM and
    // displays supporting setAddrWindow() and pushColors()), but haven't
    // implemented this yet.

    startWrite();
    for (yy = 0; yy < h; yy++) {
      for (xx = 0; xx < w; xx++) {
        if (!(bit++ & 7)) {
          bits = pgm_read_byte(&bitmap[bo++]);
        }
        if (bits & 0x80) {
          if (size_x == 1 && size_y == 1) {
            writePixel(x + xo + xx, y + yo + yy, color);
          } else {
            writeFillRect(x + (xo16 + xx) * size_x, y + (yo16 + yy) * size_y,
                          size_x, size_y, color);
          }
        }
        bits <<= 1;
      }
    }
    endWrite();

  } // End classic vs custom font
}

If we ignore the custom fonts for now you can see that really all it does is write a pixel, But then it occurred to me that you can set the cursor to a specific pixel anyway, so if you would set the cursor only one pixel either way, all you needed to do would be to cover up wherever the old text was, this you could with printing a line. You will anyway see some movement as a result of the TFT being not to fast, but it can be done without modifying the library as well.

Thank you once again very very much. The prototype display I am trying to recreate has also some visible movement steps in it, so this is just perfect when there is still some visible movement (rather than letter by letter shifting which is a bit too static). When I basically understand you, I have to include the routine for "classic fonts" above in my loop function and adjust the settings e.g. cursor position accordingly? I am using the standard font size 1 out of the library but I would like (in future) to use slightly smaller letters but the same fonts type (just a few pixels less)... but step by step as my learning curve is rather steep and challenging anyway. Will I still be able to use the classic routine or will I have to use custom fonts approach then anyway when I intend to use smaller letters? I am just considering not to invest time in something I will have to change again later.

When I basically understand you, I have to include the routine for "classic fonts" above in my loop function and adjust the settings e.g. cursor position accordingly?

I would just call the text() and the setCursor() function (with only a pixel difference in a direction), but you will have to clean up what you leave behind, that means you should draw a line in the background color. The difference between the classic font and the custom font is mainly that the library does not support the overwrite using the background color, because not all characters may have the same width. You can of course just let the library use the same routine for the classic font, if you know all characters have the same width. That would need a small modification of the library.

perfect, thank you. I am using now the TomThumb.h Fonts from the official Adafruit_GFX_Library for the "scrolling line". So when I get it right this still is classified as a Classic Font as it's from the GFX library itself? So I can continue as you explained above? My code (shifting letter by letter, see my first post) is now anyway not working anymore with TomThumb.h Font as it is different in size and spacing compared to the default font I used before.

So when I get it right this still is classified as a Classic Font as it's from the GFX library itself ?

No i don't think so, only the default font is classified as such.
What you can do is modify adafruit_GFX to just treat all fonts the same.if (!gfxFont) { // 'Classic' built-in fontby simply changing it intoif (1) { Of course the best way is to make a separate version of adafruit_GFX and have both versions next to each other. This would also prevent your modification being overwritten in case of an update.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.