LCD Animation glitching strangely

Hello!

I found the code for a cool Mario Animation from DotAku (Jan Stapler): 16x2 LCD 8 bit Mario animation - YouTube
When I tried implementing it, strange glitches appear and I'm not sure how to fix it.
First, after every increment in the for-loop (see code below), Mario "teleports" back for one frame. He also doesn't leave the screen. I included a video of the problem below.

#include <dht.h>
  #include <Wire.h> 
  #include <LiquidCrystal_I2C.h>
  
  dht DHT;
  
  #define DHT11_PIN 7
  
  LiquidCrystal_I2C lcd(0x27,20,4);
  
  int f=30;                  //set frames per second
  int s;
  
  byte mario11[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00001,
  B00001,
  B00001,
  B00000,
  
  };
  byte mario12[8] = {
  B00001,
  B00001,
  B00001,
  B00001,
  B00000,
  B00000,
  B00000,
  B00000
  };
  byte mario13[8] = {
    B00000,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  
  };
  byte mario14[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110
  };
  
  byte mario15[8] = {
     B00000,
  B00000,
  B11000,
  B00000,
  B11000,
  B11100,
  B11000,
  B10000,
  
  };
  
  byte mario16[8] = {
  B00000,
  B10000,
  B10000,
  B10000,
  B00000,
  B00000,
  B10000,
  B00000
  };
  
  byte mario21[8] = {
     B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  };
  
  byte mario22[8] = {
  B00111,
  B00111,
  B00111,
  B00000,
  B00001,
  B00011,
  B00011,
  B00001
  };
  
  byte mario23[8] = {
     B00000,
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B01111,
  
  };
  
  byte mario24[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11001,
  B00000,
  B10000
  };
  byte mario25[8] = {
  B00000,
  B00000,
  B11100,
  B10000,
  B11100,
  B11110,
  B11100,
  B11000,
  
  };
  
  byte mario26[8] = {
  B11111,
  B11111,
  B10110,
  B11110,
  B11110,
  B11110,
  B00000,
  B00000,
  };
  
  byte mario31[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  
  };
  
  
  byte mario32[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00011,
  B00011,
  B00111,
  B00000
  };
  
  
  byte mario33[8] = {
  B00000,
  B00000,
  B00111,
  B01111,
  B01111,
  B11111,
  B11111,
  B11111,
  
  };
  
  byte mario34[8] = {
  B01111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B00111,
  B00111
  };
  
  byte mario35[8] = {
  B00000,
  B00000,
  B10000,
  B11110,
  B11000,
  B11110,
  B11111,
  B11110,
  
  };
  
  byte mario36[8] = {
  B11100,
  B11110,
  B11100,
  B11000,
  B11000,
  B10000,
  B00000,
  B10000,
  };
  
  byte mario41[8] = {
  B00000,
  B00011,
  B00111,
  B00111,
  B01111,
  B01111,
  B01111,
  B00011,
  
  };
  
  
  byte mario42[8] = {
  B01111,
  B01111,
  B01111,
  B01111,
  B00111,
  B00011,
  B00011,
  B00011
  };
  
  byte mario43[8] = {
  B00000,
  B11000,
  B11111,
  B11100,
  B11111,
  B11111,
  B11111,
  B11110,
  };
  
  byte mario44[8] = {
  B11100,
  B11110,
  B11110,
  B11110,
  B11100,
  B11100,
  B11110,
  B10000
  };
  
  
  byte mario51[8] = {
  B00000,
  B00001,
  B00011,
  B00011,
  B00111,
  B00111,
  B00111,
  B00001,
  };
  
  byte mario52[8] = {
  B11111,
  B11111,
  B11011,
  B00111,
  B01111,
  B11111,
  B11100,
  B01110,
  };
  
  byte mario53[8] = {
  B00000,
  B11100,
  B11111,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  };
  
  byte mario54[8] = {
  B11111,
  B11111,
  B11110,
  B11111,
  B11111,
  B01111,
  B00000,
  B00000,
  };
  
  
  
  byte mario55[8] = {
  B00000,
  B00000,
  B10000,
  B00000,
  B00000,
  B10000,
  B00000,
  B00000,
  };
  
  
  byte mario56[8] = {
  B11000,
  B11000,
  B10000,
  B10000,
  B10000,
  B10000,
  B00000,
  B00000,
  };
  
  
  byte mario61[8] = {
  B00000,
  B00000,
  B00000,
  B00001,
  B00001,
  B00011,
  B00011,
  B00011,
  };
  
  byte mario62[8] = {
  B00001,
  B00011,
  B00111,
  B01111,
  B01111,
  B11111,
  B11000,
  B00000,
  };
  
  
  
  byte mario63[8] = {
  B00000,
  B00000,
  B11110,
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  };
  
  byte mario64[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B11111,
  B11110,
  B11100,
  B11110,
  };
  
  byte mario65[8] = {
  B00000,
  B00000,
  B00000,
  B10000,
  B00000,
  B10000,
  B11000,
  B10000,
  };
  
  
  byte mario66[8] = {
  B00000,
  B10000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  };
  
  byte clean[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  B00000,
  };
  
  void printTemp() {
    lcd.setCursor(3,0);
    lcd.print(DHT.temperature);
    lcd.print(" Grad");
    lcd.setCursor(4,1);
    lcd.print(DHT.humidity);
    lcd.print("%");
  }
  
  void setup(){
    lcd.init();
    lcd.init();
    lcd.backlight();
  
    lcd.setCursor(0,0);
    lcd.print("T: ");
  
    lcd.setCursor(0,1);
    lcd.print("LF: ");
    
    lcd.setCursor(0,3);
    lcd.print("Chigi <3");
  
    s=1000/f;         //fps to ms
  }
  
  void loop()
  {
    int chk = DHT.read11(DHT11_PIN);
    printTemp();
    for (int a=10; a<17;a++) {
      int b=a+1;
      int c=a+2;
      int d=a+3;
       
      lcd.createChar(1, mario11);
      lcd.createChar(2, mario12);
      lcd.createChar(3, mario13);
      lcd.createChar(4, mario14);
      lcd.createChar(5, mario15);
      lcd.createChar(6, mario16);
      
      lcd.setCursor(a,2);
      lcd.write(1);
      lcd.setCursor(a,3);
      lcd.write(2);
      lcd.setCursor(b,2);
      lcd.write(3);
      lcd.setCursor(b,3);
      lcd.write(4);
      lcd.setCursor(c,2);
      lcd.write(5);
      lcd.setCursor(c,3);
      lcd.write(6);
      delay(s);
      
      lcd.createChar(1, mario21);
      lcd.createChar(2, mario22);
      lcd.createChar(3, mario23);
      lcd.createChar(4, mario24);
      lcd.createChar(5, mario25);
      lcd.createChar(6, mario26);
      
      lcd.setCursor(a,2);
      lcd.write(1);
      lcd.setCursor(a,3);
      lcd.write(2);
      lcd.setCursor(b,2);
      lcd.write(3);
      lcd.setCursor(b,3);
      lcd.write(4);
      lcd.setCursor(c,2);
      lcd.write(5);
      lcd.setCursor(c,3);
      lcd.write(6);
      delay(s);
      
      lcd.createChar(1, mario31);
      lcd.createChar(2, mario32);
      lcd.createChar(3, mario33);
      lcd.createChar(4, mario34);
      lcd.createChar(5, mario35);
      lcd.createChar(6, mario36);
      
      lcd.setCursor(a,2);
      lcd.write(1);
      lcd.setCursor(a,3);
      lcd.write(2);
      lcd.setCursor(b,2);
      lcd.write(3);
      lcd.setCursor(b,3);
      lcd.write(4);
      lcd.setCursor(c,2);
      lcd.write(5);
      lcd.setCursor(c,3);
      lcd.write(6);
      delay(s);
      
      lcd.createChar(1, mario41);
      lcd.createChar(2, mario42);
      lcd.createChar(3, mario43);
      lcd.createChar(4, mario44);
      lcd.createChar(7, clean);
      lcd.setCursor(a,2);
      lcd.write(7);
      lcd.setCursor(a,3);
      lcd.write(7);
      lcd.setCursor(b,2);
      lcd.write(1);
      lcd.setCursor(b,3);
      lcd.write(2);
      lcd.setCursor(c,2);
      lcd.write(3);
      lcd.setCursor(c,3);
      lcd.write(4);
      delay(s);
      
      
      lcd.createChar(1, mario51);
      lcd.createChar(2, mario52);
      lcd.createChar(3, mario53);
      lcd.createChar(4, mario54);
      lcd.createChar(5, mario55);
      lcd.createChar(6, mario56);
      lcd.createChar(7, clean);
      lcd.setCursor(a,2);
      lcd.write(7);
      lcd.setCursor(a,3);
      lcd.write(7);
      lcd.setCursor(b,2);
      lcd.write(1);
      lcd.setCursor(b,3);
      lcd.write(2);
      lcd.setCursor(c,2);
      lcd.write(3);
      lcd.setCursor(c,3);
      lcd.write(4);
      lcd.setCursor(d,2);
      lcd.write(5);
      lcd.setCursor(d,3);
      lcd.write(6);
      delay(s);
      
      lcd.createChar(1, mario61);
      lcd.createChar(2, mario62);
      lcd.createChar(3, mario63);
      lcd.createChar(4, mario64);
      lcd.createChar(5, mario65);
      lcd.createChar(6, mario66);
      lcd.setCursor(b,2);
      lcd.write(1);
      lcd.setCursor(b,3);
      lcd.write(2);
      lcd.setCursor(c,2);
      lcd.write(3);
      lcd.setCursor(c,3);
      lcd.write(4);
      lcd.setCursor(d,2);
      lcd.write(5);
      lcd.setCursor(d,3);
      lcd.write(6);
      delay(s);
    }
  }

Firstly, set your frame rate back to four and try again! Thirty frames per second is not appropriate on these displays. :astonished:

Other than that, I suspect some problem with your conversion of the code from a 1602 to a 2004 display.

And yes, I was a contributor to the original thread. :sunglasses:

Paul__B:
Firstly, set your frame rate back to four and try again! Thirty frames per second is not appropriate on these displays. :astonished:

Other than that, I suspect some problem with your conversion of the code from a 1602 to a 2004 display.

And yes, I was a contributor to the original thread. :sunglasses:

The framerate had nothing to do with it, I already tested that. I now customly changed the animation to a steady walk and it works perfectly fine, also with 60 frames per second (but I set it to 21 fps because of Framerule :wink: )

Actually, I suspect the "frame rate" is likely irrelevant due to the delays in the lcd.write instructions.

Bill will know "off the top of his head". :grinning:

What was your "customly change"?

Here it is. Looks pretty good for me.

The original seems to use a parallel connected lcd. Your lcd is using i2c. The speed might be different.

What your code is doing:

  • Upload character
  • Update textarea

The problem is that the display also draws the currently uploaded characters to the old place in the textarea.

Currently you are using 6 custom characters. This leaving 2 unused.
You could try to roll over all 8.

f1-6   : 123***
f7     : 1234**
f8     : S234**
f9-12  : *234**
f13    : *2341*
f14    : *S341*
f15-18 : **341*

S is a space.
In f7 character 1 is filled with clear pixels.
In f13 character 2 is filled with clear pixels.

Paul__B:
Actually, I suspect the "frame rate" is likely irrelevant due to the delays in the lcd.write instructions.

Bill will know "off the top of his head". :grinning:

Definitely not off the top of my head, but quite a bit can be calculated.

The "frame rate" in that code is not really a frame rate. It is actually a frame "freeze time" .
It is used to calculate an amount of time to leave the frame static or "frozen" on the display.
In terms of actual frame rate the code has assumed the overhead to get that frame drawn on the screen is zero, which is obviously not the case.

The actual overhead for drawing the "frame" is fairly significant particularly when using an i2c backpack and the library being used.

The amount of overhead from the writes to the display can be calculated.
There are 6 custom characters being created.
There is 1 command and 8 writes to create a custom character.
There are 6 setCursor() calls
The are 6 custom characters written to the display.

All of those are commands and writes are a single byte write transfer to the display and take the same amount of time.
There are total of 6 * ( 1 + 8 ) + 6 + 6 = 66 instructions being sent to the display per "frame".
The LiquidCrystal_I2C library takes 1543 us when using a 16mhz AVR to send a byte to the display
1543 * 66 = 101,838 us or roughly 100ms to update a frame.
That is nearly 1/10 of a second just to update the frame on the display.
That means that even if you set the frame rate to "infinity" (zero delay between frames to allow the human to see it),
the frame update rate would be roughly 10 frames / second max.

Parallel mode is quite a bit faster:
If using the LiquidCrystal library the byte transfer time is 284us for a total overhead of 18,744us or 18.7ms for 53 updates/sec max.

The hd44780 library is even faster.
If using the hd44780 library hd44780_pinIO class byte transfer time is 91us total overhead 6,006 us or 6ms for 166.7 updates/sec max.

But even when using an i2c backpack, the hd44780 library is much faster than LiquidCrystal_I2C.

If using the hd44780 library hd44780_I2Cexp (instead of LiquidCrystal_I2C) the byte overhead is 540us for a total of 35,650us
or 35.7ms or 28 updates/sec MAX


After the frame is drawn there must be some amount of time to keep the display static to give the human time to see it.

If higher frame rates than 6-8 frames / second or smoother animations is desired.
Then switching to a faster library, and code changes that reduces the number of instructions sent to the display per frame will be needed.

--- bill

And that's pretty much what I thought. :grinning: