ST7735 Menu item smooth scrolling

I am a newbie in Arduino programming. First of all thanks to cbm80amiga for his fast Arduino_ST7735_Fast library. Its really fast for smooth scrolling text.

I had made a menu item using 1.8" ST7735 display, 3 push button and this library.


But I had an issue with smooth menu scrolling. When I pressed next or
previous button for next or previous menu item it made a little flickering
, which is unexpected for me. Because the scrolling text with rectangle is smooth. But menu i had created was no!

How can I solve the flickering problem?.

Here is menu code:

#define SCR_WD   128
#define SCR_HT   160
#include <SPI.h>
#include <Adafruit_GFX.h>

#if (__STM32F1__) // bluepill
#define TFT_CS  PA2
#define TFT_DC  PA1
#define TFT_RST PA0
//#include <Arduino_ST7735_STM.h>
#else
#define TFT_CS        8
#define TFT_RST        7 // Or set to -1 and connect to Arduino RESET pin
#define TFT_DC         6
#include <Arduino_ST7735_Fast.h>
#endif

Arduino_ST7735 lcd = Arduino_ST7735(TFT_DC, TFT_RST, TFT_CS);
byte menuPos, menuScreen, markerPos, menuStartAt;
const char* const menu[12] PROGMEM  = {"Binary to Decimal", "Binary to Octal", "Binary to Hexa Deci", "Decimal to Binary", "Decimal to Ocatl", "Decimal to Hexa Deci", "Octal to Binary",
                                       "Octal to decimal", "Octal to Hexa Deci", "Hexa-Decimal to Binary", "Hexa-Decimal to Ocatl", "Hexa-Deci to Decimal"
                                      };
byte MENU_LENGTH =  sizeof(menu) / sizeof(menu[0]);
#define btnEnt 4
#define btnUp 3
#define btnDwn 2
#define MENU_ROW_HEIGHT 12
#define LCD_ROWS 12

void setup() {
  Serial.begin(9600);
  pinMode(btnUp, INPUT_PULLUP);
  pinMode(btnDwn, INPUT_PULLUP);
  pinMode(btnEnt, INPUT_PULLUP);
  lcd.init();
  lcd.fillScreen(BLACK);
  delay(2000);
  showMenu();
}

void loop() {
  if (isButtonDown(btnDwn) == true) {
    if (menuPos < MENU_LENGTH - 1) {
      menuPos++;
      if (menuPos - menuStartAt > 12 )
        menuStartAt++;
      showMenu();
    }
    delay(50);
  }
  
  if (isButtonDown(btnUp) == true) {
    if (menuPos > 0) {
      menuPos--;
      if (menuPos - menuStartAt < 0 && menuStartAt != 0)
        menuStartAt--;
      showMenu();
    }
    delay(50);
  }
  
  if (isButtonDown(btnEnt) == true) {
    if (menuPos == 0) {
      test1();
    }
    else if (menuPos == 1) {
      //test2();
    }
    else if (menuPos == 2) {
      //test2();
    }
    else if (menuPos == 3) {
      //test3();
    }
    else if (menuPos == 4) {
      //test4();
    }
    else if (menuPos == 5) {
      //test5();
    }
    else if (menuPos == 6) {
      //test6();
    }
    else if (menuPos == 7) {
      //test7();
    } else if (menuPos == 8) {
      //test1();
    }
    else if (menuPos == 9) {
      //test8();
    }
    else if (menuPos == 10) {
      //test9();
    }

    else if (menuPos == 11) {
      //test10();
    } else if (menuPos == 12) {
      //test11();
    }
    delay(100);
  }
}

bool isButtonDown(byte pin) {
  if (digitalRead(pin) == LOW) {
    delay(30);
    if (digitalRead(pin) == LOW)
      return true;
    return false;
  }
  return false;
}

void showMenu() {
  for (byte i = menuStartAt; i < (menuStartAt + LCD_ROWS); i++) {
    byte markerY = (i - menuStartAt) * MENU_ROW_HEIGHT;
    if (i == menuPos) {
      lcd.setTextColor(BLACK);
      lcd.fillRect(0, markerY, lcd.width(), MENU_ROW_HEIGHT, GREEN);
    }
    else {
      lcd.setTextColor(WHITE);
      lcd.fillRect(0, markerY, lcd.width(), MENU_ROW_HEIGHT, BLACK);
    }
    if (i >= MENU_LENGTH)
      continue;
    lcd.setCursor(3, markerY + 2);
    lcd.print((char*)pgm_read_word(&(menu[i])));
  }
}

void test1(){
  lcd.fillScreen(BLACK);
  lcd.setTextColor(WHITE);
  lcd.setCursor(1, 1);
  lcd.println(F("Waiting to \nregister sim \ncard"));
  delay(3000);
  showMenu();
}

Here is scrolling text code:

#define SCR_WD 128
#define SCR_HT 160

#include <SPI.h>
#include <Adafruit_GFX.h>

#if (__STM32F1__) // bluepill
#define TFT_CS  PA2
#define TFT_DC  PA1
#define TFT_RST PA0
//#include <Arduino_ST7735_STM.h>
#else
#define TFT_CS        8
#define TFT_RST        7 // Or set to -1 and connect to Arduino RESET pin
#define TFT_DC         6
#include <Arduino_ST7735_Fast.h>
#endif

Arduino_ST7735 lcd = Arduino_ST7735(TFT_DC, TFT_RST, TFT_CS);

#include "RREFont.h"
#include "rre_arialb_16.h"

RREFont font;

// needed for RREFont library initialization, define your fillRect
void customRect(int x, int y, int w, int h, int c) { return lcd.fillRect(x, y, w, h, c); }

void setup() 
{
  Serial.begin(9600);
  lcd.init();
  font.init(customRect, SCR_WD, SCR_HT); // custom fillRect function and screen width and height values

  for(int i=0;i<SCR_HT;i+=4) {
    uint8_t r,g,b;
    lcd.rgbWheel(i*512L/SCR_HT,&r,&g,&b);
    lcd.fillRect(0,i,SCR_WD,4,RGBto565(r,g,b));
  }

  font.setFont(&rre_arialb_16); font.setSpacing(2);
  font.setScale(2);
  font.setColor(BLACK);
  font.printStr(ALIGN_CENTER,30+2,"SCROLL"); font.printStr(ALIGN_CENTER,70+2,"DEMO");
  font.setColor(WHITE);
  font.printStr(ALIGN_CENTER,30,"SCROLL"); font.printStr(ALIGN_CENTER,70,"DEMO");
  font.setScale(1);
  delay(2000);
}

int bgCols[] = {2,4,6,8,10,8,6,4};
char *scrollTxt[] = {"","This","is","an example","of","super-smooth","scrolling","with regular",
                     "AVR Arduino,","ILI9163C","1280x128 LCD","library","and","RRE Fonts",""};

int c=0,t=0;
int maxy=160;
unsigned long ms;

void loop()
{
  // full screen scrolling
  lcd.setScrollArea(0,0);
  font.setSpacing(2);
  for(int l=0;l<3;l++)
  for(int i=0;i<maxy;i++) {
    ms = millis();
    lcd.setScroll(i);
    int y=i+SCR_HT-16;
    if(y>=maxy) y-=maxy;
    if((i%16)==0) {
      lcd.fillRect(0,y,128,16,RGBto565(0,0,bgCols[c]<<4));
      font.printStr(ALIGN_CENTER,y+1,scrollTxt[t]);
      if(++c>=sizeof(bgCols)/sizeof(bgCols[0])) c=0;
      if(++t>=sizeof(scrollTxt)/sizeof(scrollTxt[0])) t=0;
      //Serial.println(millis()-ms); // less than 25ms per line
    }
    while(millis()-ms<25);
  }

  // scrolling with fixed top area
  lcd.fillRect(0,0,128,3,RGBto565(220,0,220));
  lcd.fillRect(0,3,128,32-6,RGBto565(180,0,180));
  lcd.fillRect(0,32-3,128,3,RGBto565(140,0,140));
  font.setScale(1); font.setSpacing(1);
  font.setColor(YELLOW);
  font.printStr(ALIGN_CENTER,8,"Fixed Top Area");
  font.setColor(WHITE);
  font.setScale(1); font.setSpacing(3);
  lcd.setScrollArea(32, 0);
  for(int l=0;l<3;l++)
  for(int i=32;i<maxy;i++) {
    ms = millis();
    lcd.setScroll(i);
    int y=i+SCR_HT-16-32;
    if(y>=maxy) {y-=maxy; y+=32;}
    if((i%16)==0) {
      lcd.fillRect(0,y,128,16,RGBto565(0,0,bgCols[c]<<4));
      font.printStr(ALIGN_CENTER,y+1,scrollTxt[t]);
      if(++c>=sizeof(bgCols)/sizeof(bgCols[0])) c=0;
      if(++t>=sizeof(scrollTxt)/sizeof(scrollTxt[0])) t=0;
    }
    while(millis()-ms<25);
  }
}

Update: When I use fillRect() the flickering occured.
But if I use drawRact() there was no flickering issue.

Fast, smooth and delay () in loop are not compatible, they never work. Why do you use a quick display control and then slow it down again with delay () in loop ()?

1 Like

Yes, you are right. But if don't put this delay() the menu item will not properly selected. Some times when i press next button it goes for two times. When i put this delay() it works fine.

Because I don't know other better way as I am a newbie :worried:.

Update: When I comment all delay() there was nothing changed.

Please @Deltaflyer can you explain how can I do it?

Look at the Bounce2 library. It does the button debouncing without using delay()...

#define SCR_WD   128
#define SCR_HT   160
#include <SPI.h>
#include <Adafruit_GFX.h>

#if (__STM32F1__) // bluepill
#define TFT_CS  PA2
#define TFT_DC  PA1
#define TFT_RST PA0
//#include <Arduino_ST7735_STM.h>
#else
#define TFT_CS        8
#define TFT_RST        7 // Or set to -1 and connect to Arduino RESET pin
#define TFT_DC         6
#include <Arduino_ST7735_Fast.h>
#endif

#include <Bounce2.h>

Arduino_ST7735 lcd = Arduino_ST7735(TFT_DC, TFT_RST, TFT_CS);
byte menuPos, menuScreen, markerPos, menuStartAt;
const char* const menu[12] PROGMEM  = {"Binary to Decimal", "Binary to Octal", "Binary to Hexa Deci", "Decimal to Binary", "Decimal to Ocatl", "Decimal to Hexa Deci", "Octal to Binary",
                                       "Octal to decimal", "Octal to Hexa Deci", "Hexa-Decimal to Binary", "Hexa-Decimal to Ocatl", "Hexa-Deci to Decimal"
                                      };
byte MENU_LENGTH =  sizeof(menu) / sizeof(menu[0]);
#define btnEnt 4
#define btnUp 3
#define btnDwn 2
#define MENU_ROW_HEIGHT 12
#define LCD_ROWS 12

Bounce2::Button buttonEnter = Bounce2::Button();
Bounce2::Button buttonUp = Bounce2::Button();
Bounce2::Button buttonDown = Bounce2::Button();
const int debounceTime = 10;

void setup() {
  Serial.begin(9600);
  //pinMode(btnUp, INPUT_PULLUP);
  //pinMode(btnDwn, INPUT_PULLUP);
  //pinMode(btnEnt, INPUT_PULLUP);
  buttonEnter.attach( btnEnt, INPUT_PULLUP );
  buttonEnter.interval(debounceTime);  // debounce time, ms
  buttonEnter.setPressedState(LOW); // INPUT_PULLUP means LOW == pressed
  buttonUp.attach( btnUp, INPUT_PULLUP );
  buttonUp.interval(debounceTime);  // debounce time, ms
  buttonUp.setPressedState(LOW); // INPUT_PULLUP means LOW == pressed
  buttonDown.attach( btnDwn, INPUT_PULLUP );
  buttonDown.interval(debounceTime);  // debounce time, ms
  buttonDown.setPressedState(LOW); // INPUT_PULLUP means LOW == pressed

  lcd.init();
  lcd.fillScreen(BLACK);
  delay(2000);
  showMenu();
}

void loop() {
  // update button state every time through loop
  buttonEnter.update();
  buttonUp.update();
  buttonDown.update;

  if (buttonDown.pressed()) {
    if (menuPos < MENU_LENGTH - 1) {
      menuPos++;
      if (menuPos - menuStartAt > 12 )
        menuStartAt++;
      showMenu();
    }
  }

  if (buttonUp.pressed()) {
    if (menuPos > 0) {
      menuPos--;
      if (menuPos - menuStartAt < 0 && menuStartAt != 0)
        menuStartAt--;
      showMenu();
    }
  }

  if (buttonEnter.pressed()) {
    Serial.print("Running test "); Serial.println(menuPos);
    switch ( menuPos ) {
      case  0: test0();   break;
      //case  1: test1();   break;
      //case  2: test2();   break;
      //case  3: test3();   break;
      //case  4: test4();   break;
      //case  5: test5();   break;
      //case  6: test6();   break;
      //case  7: test7();   break;
      //case  8: test8();   break;
      //case  9: test9();   break;
      //case 10: test10();  break;
      //case 11: test11();  break;
      //case 12: test12();  break;
    }
  }
}

void showMenu() {
  for (byte i = menuStartAt; i < (menuStartAt + LCD_ROWS); i++) {
    byte markerY = (i - menuStartAt) * MENU_ROW_HEIGHT;
    if (i == menuPos) {
      lcd.setTextColor(BLACK);
      lcd.fillRect(0, markerY, lcd.width(), MENU_ROW_HEIGHT, GREEN);
    }
    else {
      lcd.setTextColor(WHITE);
      lcd.fillRect(0, markerY, lcd.width(), MENU_ROW_HEIGHT, BLACK);
    }
    if (i >= MENU_LENGTH)
      continue;
    lcd.setCursor(3, markerY + 2);
    lcd.print((char*)pgm_read_word(&(menu[i])));
  }
}

void test0() {
  lcd.fillScreen(BLACK);
  lcd.setTextColor(WHITE);
  lcd.setCursor(1, 1);
  lcd.println(F("Waiting to \nregister sim \ncard"));
  delay(3000);
  showMenu();
}
1 Like

@blh64 Many thanks for your help..... Your code without delay() is working perfectly.

But the flickering problem wasn't solved. I think delay is not take effect on screen update.

Redrawing the rectangle is the problem. How can I update the triangle without redrawing. This is the redrawing line:

for (byte i = menuStartAt; i < (menuStartAt + LCD_ROWS); i++) {
    byte markerY = (i - menuStartAt) * MENU_ROW_HEIGHT;
    if (i == menuPos) {
      lcd.setTextColor(BLACK);
      lcd.fillRect(0, markerY, lcd.width(), MENU_ROW_HEIGHT, GREEN);
    }
    else {
      lcd.setTextColor(WHITE);
      lcd.fillRect(0, markerY, lcd.width(), MENU_ROW_HEIGHT, BLACK);
    }
    if (i >= MENU_LENGTH)
      continue;
    lcd.setCursor(3, markerY + 2);
    lcd.print((char*)pgm_read_word(&(menu[i])));
  }

Finally the below code solved my problem (Some mejor modification needed :grinning: ) :

#include <SPI.h>
#include <Adafruit_GFX.h>
#include "RREFont.h"
#include "rre_fjg_8x16.h"
#include "rre_5x8.h"

#define TFT_CS   8
#define TFT_RST  7
#define TFT_DC   6

#define SCR_WD   128
#define SCR_HT   160

// use 3 debouncing capacitors (100nF seems to be enough)
#define encoderPinA    4
#define encoderPinB    3
#define encoderButton  2
#include <Arduino_ST7735_Fast.h>

Arduino_ST7735 lcd = Arduino_ST7735(TFT_DC, TFT_RST, TFT_CS);

RREFont font;
// needed for RREFont library initialization, define your fillRect
void customRect(int x, int y, int w, int h, int c) {
  return lcd.fillRect(x, y, w, h, c);
}

#include <EEPROM.h>

// -------------------------
volatile int encoderPos = 0, encoderPosOld = 0, encoderStep = 2;

void initEncoder()
{
  encoderPos = 0;
  pinMode(encoderPinA,   INPUT_PULLUP);
  pinMode(encoderPinB,   INPUT_PULLUP);
  pinMode(encoderButton, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(encoderPinA), readEncoderInt, CHANGE);  // encoder pin on interrupt 0 = pin 2
  attachInterrupt(digitalPinToInterrupt(encoderButton), buttonInt, CHANGE);  // encoder pin on interrupt 1 = pin 3
}

void buttonInt() {}

void readEncoderInt()
{
  //(digitalRead(encoderPinA) == digitalRead(encoderPinB)) ? encoderPos++ : encoderPos--;
  uint8_t pd = PIND & B10100; // inputs #2 and #4 direct reading
  ((pd == B10100) || (pd == B00000)) ? encoderPos++ : encoderPos--;
}

int readButton()
{
  const long btDebounce = 30;
  static long btTime = 0;
  static int lastState = HIGH;
  int val = 0, state = digitalRead(encoderButton);
  if (state == LOW && lastState == HIGH) {
    btTime = millis();
    val = 0;
  }
  if (state == HIGH && lastState == LOW && millis() - btTime >= btDebounce) {
    val = 1;
  }
  lastState = state;
  return val;
}

// -------------------------
long readVcc()
{
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA, ADSC));
  result = ADCL;
  result |= ADCH << 8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

float readIntTemp()
{
  long result;
  // Read temperature sensor against 1.1V reference
  ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
  delay(5); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA, ADSC));
  result = ADCL;
  result |= ADCH << 8;
  result = (result - 125) * 1075;
  return result / 10000.0;
}

// -------------------------
#define MAXSIN 255
const uint8_t sinTab[91] PROGMEM = {
  0, 4, 8, 13, 17, 22, 26, 31, 35, 39, 44, 48, 53, 57, 61, 65, 70, 74, 78, 83, 87, 91, 95, 99, 103, 107, 111, 115, 119, 123,
  127, 131, 135, 138, 142, 146, 149, 153, 156, 160, 163, 167, 170, 173, 177, 180, 183, 186, 189, 192, 195, 198, 200, 203, 206, 208, 211, 213, 216, 218,
  220, 223, 225, 227, 229, 231, 232, 234, 236, 238, 239, 241, 242, 243, 245, 246, 247, 248, 249, 250, 251, 251, 252, 253, 253, 254, 254, 254, 254, 254,
  255
};

int fastSin(int i)
{
  while (i < 0) i += 360;
  while (i >= 360) i -= 360;
  if (i < 90)  return (pgm_read_byte(&sinTab[i])); else if (i < 180) return (pgm_read_byte(&sinTab[180 - i])); else if (i < 270) return (-pgm_read_byte(&sinTab[i - 180])); else
    return (-pgm_read_byte(&sinTab[360 - i]));
}

int fastCos(int i)
{
  return fastSin(i + 90);
}

char buf[100];

void drawGauge1(int level)
{
  int sx, sy, xs0, ys0, xe0, ye0, xs1, ys1, xe1, ye1;
  int cx = SCR_WD / 2;
  int cy = SCR_HT / 2;
  int rx0 = 40, ry0 = 40;
  int rx1 = 63, ry1 = 63;
  int mina = -60;
  int maxa = 180 + 60;
  for (int i = mina; i < maxa; i += 15) { // 22 pieces
    sx = fastCos(i - 180);
    sy = fastSin(i - 180);
    xs0 = cx + sx * rx0 / MAXSIN;
    ys0 = cy + sy * ry0 / MAXSIN;
    xe0 = cx + sx * rx1 / MAXSIN;
    ye0 = cy + sy * ry1 / MAXSIN;
    sx = fastCos(i - 180 + 10);
    sy = fastSin(i - 180 + 10);
    xs1 = cx + sx * rx0 / MAXSIN;
    ys1 = cy + sy * ry0 / MAXSIN;
    xe1 = cx + sx * rx1 / MAXSIN;
    ye1 = cy + sy * ry1 / MAXSIN;
    int l = 20 * (i - mina) / (maxa - mina);
    uint16_t c = (l < level) ? lcd.rgbWheel(512L * l / 20) : RGBto565(60, 60, 60);
    lcd.fillTriangle(xs0, ys0, xe0, ye0, xe1, ye1, c);
    lcd.fillTriangle(xs1, ys1, xe1, ye1, xs0, ys0, c);
  }
  snprintf(buf, 10, "%02d", level);
  font.setColor(WHITE, BLACK);
  font.printStr(ALIGN_CENTER, SCR_HT / 2 - 7, buf);
}

// -------------------------

char *menuTxt[] = {
  "Set value",     // 0
  "Help",          // 1
  "MCU Temp",      // 2
  "VCC/Battery",   // 3
  "EEPROM dump",   // 4
  "Graph",         // 5
  "Bckgrnd color", // 6
  "Item color",    // 7
  "Frame color",   // 8
  "Slider color",  // 9
  "About",         // 10
  "Reboot"         // 11
};

const int itemHt = 20;
const int numMenus = sizeof(menuTxt) / sizeof(char*);
const int numScrLines = SCR_HT / itemHt; // 160/20=8
int menuSel = 0, menuSelOld = 0;
int menuStart = 0;
int menuMode = -1; // -1 -> menu of options, 0..n -> option
int storedPos = 0;

uint16_t bgCol     = RGBto565(30, 30, 140);
uint16_t frameCol  = RGBto565(255, 255, 40);
uint16_t itemCol   = RGBto565(220, 220, 220);
uint16_t sliderCol = RGBto565(20, 180, 180);

void showHelp()
{
  lcd.fillScreen(RGBto565(150, 0, 0));
  font.setColor(WHITE);
  font.printStr(ALIGN_CENTER, 4, "Help");
  font.setColor(YELLOW);
  font.setCR(1);
  font.setCharMinWd(0);
  font.printStr(5, 24, "Use encoder to select menu item.\nPress button to exit.");
  font.setCR(0);
}

void showSelected(char *txt)
{
  lcd.fillScreen(RGBto565(150, 0, 150));
  font.setColor(WHITE);  
  font.printStr(10, 10, "Selected:");
  font.setColor(YELLOW);  
  font.printStr(10, 30, txt);
}

void printMenuItem(int y, char *item)
{
  font.setColor(itemCol);
  font.printStr(3, 2 + y * itemHt, item);
}

void printMenu(int full = 0)
{
  int n = numMenus < numScrLines ? numMenus : numScrLines;
  for (int i = 0; i < n; i++) {
    formatMenu(menuTxt[i + menuStart], buf, 14);
    full ? lcd.fillRect(0, i * itemHt, SCR_WD, itemHt, bgCol) : lcd.fillRect(3, 2 + i * itemHt, 120 - 4, 16, bgCol);
    printMenuItem(i, buf);
  }
}

void setMenu(int m)
{
  menuMode = m;
  storedPos = encoderPos;
  encoderPos = 0;
}

void endMenu(int butt)
{
  if (!butt) return;
  menuMode = -1;
  initMenu();
  encoderPos = storedPos;
}

void formatMenu(char *in, char *out, int num)
{
  int j = strlen(in);
  strncpy(out, in, j);
  for (; j < num; j++) out[j] = ' ';
  out[j] = 0;
}

void drawMenuSlider()
{
  //int ht = 10;
  int ht = (SCR_HT - 4) / numMenus;
  int n = (SCR_HT - 4 - ht) * menuSel / (numMenus - 1);
  lcd.drawRect(SCR_WD - 6, 0, 6, SCR_HT, sliderCol);
  lcd.fillRect(SCR_WD - 6 + 2, 2, 2, SCR_HT - 4, bgCol);
  lcd.fillRect(SCR_WD - 6 + 2, n + 2, 2, ht, sliderCol);
}

void drawFrame(int sel, int stat)
{
  lcd.drawRect(0, (sel - menuStart)*itemHt, 120, itemHt - 1, stat ? frameCol : bgCol);
}

void initMenu()
{
  font.setFont(&rre_fjg_8x16);
  font.setCharMinWd(8);
  font.setSpacing(1);
  font.setColor(WHITE);
  printMenu(1);
  drawMenuSlider();
  drawFrame(menuSel, 1);
}

void setValue()
{
  if (encoderPos < 0) encoderPos = 0;
  if (encoderPos > 40) encoderPos = 40;
  drawGauge1(encoderPos / encoderStep);
}

// -------------
int colR, colG, colB;
int colRold, colGold, colBold;
int setRGBMode = 0;
int colBarWd = 96;
int colBarY0 = 5;
int colBarY = 40;
int colBarHt = 30;
int encoderMin = 0;
int encoderMax = 255;

void setColorInit(uint16_t *c)
{
  colR = (*c & 0xf800) >> 8;
  colG = (*c & 0x7e0) >> 3;
  colB = (*c & 0x1f) << 3;
  colRold = colGold = colBold = 0;
  lcd.fillScreen(BLACK);
  for (int i = 0; i < 32; i++) { // 96 pixels wide, 32 shades
    lcd.fillRect(2 + i * 3, colBarY0 + colBarY * 0, 3, colBarHt, RGBto565(i * 8, 0, 0));
    lcd.fillRect(2 + i * 3, colBarY0 + colBarY * 1, 3, colBarHt, RGBto565(0, i * 8, 0));
    lcd.fillRect(2 + i * 3, colBarY0 + colBarY * 2, 3, colBarHt, RGBto565(0, 0, i * 8));
  }
  lcd.drawRect(1, colBarY0 + colBarY * 0 - 1, colBarWd + 2, colBarHt + 2, RGBto565(128, 0, 0));
  lcd.drawRect(1, colBarY0 + colBarY * 1 - 1, colBarWd + 2, colBarHt + 2, RGBto565(0, 128, 0));
  lcd.drawRect(1, colBarY0 + colBarY * 2 - 1, colBarWd + 2, colBarHt + 2, RGBto565(0, 0, 128));
  lcd.fillRect(128 - 50 - 2, 160 - 25 - 2, 50, 25, *c); // cancel
  encoderMax = 4;
}

void setColorAction(uint16_t *col, int butt)
{
  if (encoderPos < encoderMin * encoderStep) encoderPos = encoderMin * encoderStep;
  if (encoderPos > encoderMax * encoderStep) encoderPos = encoderMax * encoderStep;
  int pos = encoderPos / encoderStep;
  int frw = 2 + 96 + 2;
  if (butt) {
    if (setRGBMode > 0) {
      encoderStep = 2;
      encoderPos = (setRGBMode - 1) * encoderStep;
      setRGBMode = 0;
      encoderMax = 4;
    } else {
      if (pos == 3 || pos == 4) {
        if (pos == 3) *col = RGBto565(colR, colG, colB);
        menuMode = -1;
        initMenu();
        encoderPos = storedPos;
        setRGBMode = 0;
        encoderStep = 2;
        return;
      }
      setRGBMode = pos + 1;
      encoderMax = 255;
      encoderStep = 1;
      if (setRGBMode == 1) {
        encoderPos = colR;
        lcd.drawRect(0, colBarY0 + colBarY * 0 - 2, frw, colBarHt + 4, RGBto565(255, 80, 80));
      } else if (setRGBMode == 2) {
        encoderPos = colG;
        lcd.drawRect(0, colBarY0 + colBarY * 1 - 2, frw, colBarHt + 4, RGBto565(80, 255, 80));
      } else if (setRGBMode == 3) {
        encoderPos = colB;
        lcd.drawRect(0, colBarY0 + colBarY * 2 - 2, frw, colBarHt + 4, RGBto565(80, 80, 255));
      }
      return;
    }
  }
  if (setRGBMode == 1) colR = pos; else if (setRGBMode == 2) colG = pos; else if (setRGBMode == 3) colB = pos;

  font.setColor(WHITE, BLACK);
  int xcol = 128 - 9 * 3 + 1;
  if (setRGBMode == 0 || setRGBMode == 1) {
    dtostrf(colR, 3, 0, buf);
    font.printStr(xcol, colBarY0 + colBarY * 0 + 8, buf);
  }
  if (setRGBMode == 0 || setRGBMode == 2) {
    dtostrf(colG, 3, 0, buf);
    font.printStr(xcol, colBarY0 + colBarY * 1 + 8, buf);
  }
  if (setRGBMode == 0 || setRGBMode == 3) {
    dtostrf(colB, 3, 0, buf);
    font.printStr(xcol, colBarY0 + colBarY * 2 + 8, buf);
  }
  font.setColor(WHITE);

  if (colRold != colR) {
    lcd.drawFastVLine(2 + 3 * colRold / 8, colBarY0 + colBarY * 0, colBarHt, RGBto565(colRold, 0, 0));
    lcd.drawFastVLine(2 + 3 * colR / 8,   colBarY0 + colBarY * 0, colBarHt, YELLOW);
    colRold = colR;
  }
  if (colGold != colG) {
    lcd.drawFastVLine(2 + 3 * colGold / 8, colBarY0 + colBarY * 1, colBarHt, RGBto565(0, colGold, 0));
    lcd.drawFastVLine(2 + 3 * colG / 8,   colBarY0 + colBarY * 1, colBarHt, YELLOW);
    colGold = colG;
  }
  if (colBold != colB) {
    lcd.drawFastVLine(2 + 3 * colBold / 8, colBarY0 + colBarY * 2, colBarHt, RGBto565(0, 0, colBold));
    lcd.drawFastVLine(2 + 3 * colB / 8,   colBarY0 + colBarY * 2, colBarHt, YELLOW);
    colBold = colB;
  }

  lcd.fillRect(       2, 160 - 25 - 2, 50, 25, RGBto565(colR, colG, colB)); // ok

  if (setRGBMode == 0) {
    lcd.drawRect(0, colBarY0 + colBarY * 0 - 2, frw, colBarHt + 4, BLACK);
    lcd.drawRect(0, colBarY0 + colBarY * 1 - 2, frw, colBarHt + 4, BLACK);
    lcd.drawRect(0, colBarY0 + colBarY * 2 - 2, frw, colBarHt + 4, BLACK);
    lcd.drawRect(       0, 160 - 25 - 4, 50 + 4, 25 + 4, BLACK);
    lcd.drawRect(128 - 50 - 4, 160 - 25 - 4, 50 + 4, 25 + 4, BLACK);
    switch (pos) {
      case 0: lcd.drawRect(0, colBarY0 + colBarY * 0 - 2, frw, colBarHt + 4, WHITE); break;
      case 1: lcd.drawRect(0, colBarY0 + colBarY * 1 - 2, frw, colBarHt + 4, WHITE); break;
      case 2: lcd.drawRect(0, colBarY0 + colBarY * 2 - 2, frw, colBarHt + 4, WHITE); break;
      case 3: lcd.drawRect(       0, 160 - 25 - 4, 50 + 4, 25 + 4, WHITE); break;
      case 4: lcd.drawRect(128 - 50 - 4, 160 - 25 - 4, 50 + 4, 25 + 4, WHITE); break;
    }
  }
}

// -------------

uint16_t reqBgCol = RGBto565(0, 80, 0);
int reqY = 110;

void reqInit()
{
  lcd.fillScreen(reqBgCol);
  font.setColor(WHITE);
  font.printStr(ALIGN_CENTER, 50, "Are you sure?");
  font.setColor(YELLOW);
  font.printStr(8, reqY, " OK ");
  font.printStr(ALIGN_RIGHT, reqY, " CANCEL ");
}

void reqAction()
{
  if (encoderPos < 0) encoderPos = 0;
  if (encoderPos > 1 * encoderStep) encoderPos = 1 * encoderStep;
  int pos = encoderPos / encoderStep;
  lcd.drawRect(8, reqY - 3, 4 * 9 - 2, 20, reqBgCol);
  lcd.drawRect(128 - 8 * 9, reqY - 3, 8 * 9 - 4, 20, reqBgCol);
  if (pos == 0) lcd.drawRect(8, reqY - 3, 4 * 9 - 2, 20, WHITE); else if (pos == 1) lcd.drawRect(128 - 8 * 9, reqY - 3, 8 * 9 - 4, 20, WHITE);
}

// -------------

void showBattery()
{
  char flt[10];
  long v = readVcc();
  lcd.fillScreen(BLACK);
  dtostrf(v / 1000.0, 1, 3, flt);
  snprintf(buf, 90, "Vcc=%sV", flt);
  font.setColor(WHITE);
  font.printStr(16, 30, buf);
  lcd.drawRect(10, 60, 128 - 20 - 8, 50, WHITE);
  lcd.fillRect(10 + 128 - 20 - 8, 60 + 15, 8, 20, WHITE);
  int bwd = 128 - 20 - 8 - 8l;
  uint16_t c = YELLOW;
  if (v > 3600) c = GREEN;
  if (v < 3100) c = RED;
  long fill = constrain(map(v, 2900, 4200, 0, bwd - 10), 0, bwd - 10);
  lcd.fillRect(14, 64, fill + 10, 50 - 8, c);
}

void showIntTemp()
{
  char flt[10];
  lcd.fillScreen(BLACK);
  dtostrf(readIntTemp(), 2, 1, flt);
  snprintf(buf, 90, "Temp=%s'C", flt);
  font.setColor(WHITE);
  font.printStr(ALIGN_CENTER, 70, buf);
}

void dumpEEPROM()
{
  //font.setFont(&rre_4x7); font.setCharMinWd(4);
  font.setFont(&rre_5x8); font.setCharMinWd(5);
  if (encoderPos >= (128 - 16) * 2) encoderPos = (128 - 16) * 2;
  int st = encoderPos / encoderStep;
  for (int j = 0; j < 16; j++) {
    int ii = st * 8 + j * 8;
    ii &= 0x3ff; // max 1kB
    snprintf(buf, 8, "%03X", ii);
    font.setColor(YELLOW, BLACK);
    font.printStr(0, j * 10, buf);
    for (int i = 0; i < 8; i++) {
      int v = EEPROM.read(ii + i);
      snprintf(buf, 8, "%02X", v);
      font.setColor(WHITE, BLACK);
      font.printStr(5 * 4 + 2 + i * 13, j * 10, buf);
    }
  }
}

// -------------

void menuItemInit()
{
  setMenu(menuSel);
  switch (menuMode) {
    case 0: lcd.fillScreen(BLACK); encoderPos = 5 * encoderStep; break; // for setValue()
    case 1: showHelp(); break;
    case 2: showIntTemp(); break;
    case 3: showBattery(); break;
    case 4: lcd.fillScreen(BLACK); break; // for dumpEEPROM()
    case 6: setColorInit(&bgCol); break;
    case 7: setColorInit(&itemCol); break;
    case 8: setColorInit(&frameCol); break;
    case 9: setColorInit(&sliderCol); break;
    case 11: reqInit(); break;
    default: showSelected(menuTxt[menuSel]);
  }
}

void menuItemAction(int butt)
{
  switch (menuMode) {
    case 0: setValue(); endMenu(butt); break;
    case 4: dumpEEPROM(); endMenu(butt); break;
    case 6: setColorAction(&bgCol, butt); break;
    case 7: setColorAction(&itemCol, butt); break;
    case 8: setColorAction(&frameCol, butt); break;
    case 9: setColorAction(&sliderCol, butt); break;
    case 11: reqAction(); endMenu(butt); break;
    default: endMenu(butt);
  }
}

void handleMenu()
{
  int butt = readButton();
  if (encoderPos < 0) encoderPos = 0;
  if (encoderPosOld == encoderPos && !butt) return;
  encoderPosOld = encoderPos;
  if (menuMode == -1) {
    menuSel = encoderPos / encoderStep;
    if (menuSel >= numMenus) {
      menuSel = numMenus - 1;
      encoderPos = menuSel * encoderStep;
    }
    if (menuSel >= menuStart + numScrLines) {
      menuStart = menuSel - numScrLines + 1;
      printMenu();
    }
    if (menuSel < menuStart) {
      menuStart = menuSel;
      printMenu();
    }
    if (menuSelOld != menuSel) {
      drawFrame(menuSelOld, 0);
      drawFrame(menuSel, 1);
      drawMenuSlider();
      menuSelOld = menuSel;
    }
    if (butt) menuItemInit();
  } else menuItemAction(butt);
}

void setup()
{
  Serial.begin(9600);
  lcd.init();
  //lcd.fillScreen(bgCol);
  font.init(customRect, SCR_WD, SCR_HT); // custom fillRect function and screen width and height values
  initEncoder();
  initMenu();
}

void loop()
{
  handleMenu();
}

time variables should be unsigned long

@blh64 thanks for your suggestion. I solved my problem temporarily with the above code.

But If there are any better method for smooth scrolling menu with st7735 display please recommend it.