Live plot graph refreshing causes flicker

I'm creating a code for an ozone analyser project, which reads RS232 data, parses the data, and displays it in the form of a live graph on an LCD display. The issue I'm having is plotting at 1Hz redraws the full graph when the buffer is full, as it is a circular buffer. Once the buffer is full, the whole graph needs redrawn to move the points back 1 index in the buffer. This causes the screen to flicker when redrawing, which makes it difficult to properly view the data. Can someone help me reduce this flicker? Here is my code:

Main:

#include "libraries.h"
#include "config.h"

// Function to check and return the status as "OK" or "NOT OK"
String getStatus(const String &hexValue) {
  if (hexValue == "0000") {
    return "OK";
  } else {
    return "NOT OK";
  }
}

float stringToFloat(String str) {
  int spaceIndex = str.indexOf(' ');
  if (spaceIndex != -1) {
    str = str.substring(0, spaceIndex);
  }
  return str.toFloat();
}

float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

String cleanValue(const String &valueStr) {
  float val = valueStr.toFloat();
  // Use 1 decimal place; adjust as needed
  return String(val, 2);
}

// =================================================================================================
// DataPacket Class
// =================================================================================================

class DataPacket {
public:
  String date;
  String timeStr;
  String concentration;
  String pressure;
  String dirtiness;
  String status;

  DataPacket()
    : date(""),
      timeStr(""),
      concentration(""),
      pressure(""),
      dirtiness(""),
      status("") {}

  void parse(String packet) {
    String sections[6];
    int fieldCount = splitPacket(packet, sections);

    if (fieldCount == 6) {
      date = sections[0];
      timeStr = sections[1];
      concentration = sections[2];
      pressure = sections[3];
      dirtiness = sections[4];
      status = sections[5];
    } else {
      Serial.println(" Error: Malformed packet. Expected 6 fields, received " + fieldCount);
    }
  }

  int splitPacket(String packet, String sections[]) {
    int fieldCount = 0;
    int lastCommaIndex = -1;

    while (fieldCount <= 6) {  // Now properly checks for all 6 fields
      int commaIndex = packet.indexOf(',', lastCommaIndex + 1);
      if (commaIndex == -1) {
        // If there's no more comma, the rest of the string is the last section
        sections[fieldCount++] = packet.substring(lastCommaIndex + 1);
        break;
      }
      sections[fieldCount++] = packet.substring(lastCommaIndex + 1, commaIndex);
      lastCommaIndex = commaIndex;
    }

    return fieldCount;
  }

  void printParsedData() {
    Serial.println("Parsed Data:");
    Serial.println("Date: " + date);
    Serial.println("Time: " + timeStr);
    Serial.println("Concentration: " + concentration);
    Serial.println("Pressure: " + pressure);
    Serial.println("Dirtiness: " + dirtiness);
    Serial.println("Status: " + status);
  }
};

DataPacket data;

// Function to display all RS232 data
void printRS232Data(const String &date, const String &time, const String &concentration,
                    const String &pressure, const String &dirtiness, const String &status) {
  static String lastDate = "", lastTime = "", lastConcentration = "", lastPressure = "",
                lastDirtiness = "", lastStatus = "";
  static bool titlesPrinted = false;

  if (currentPage == 1) {
    if (!titlesPrinted) {
      // Print labels only once
      printData(10, 50, "Date:", "", WHITE);
      printData(10, 80, "Time:", "", WHITE);
      printData(10, 110, "Concentration:", "", WHITE);
      printData(10, 140, "Pressure:", "", WHITE);
      printData(10, 170, "Dirtiness:", "", WHITE);
      printData(10, 200, "Status:", "", WHITE);
      titlesPrinted = true;
    }

    // Define value positions (adjust X to avoid overwriting label)
    int valueX = 250;

    if (date != lastDate) {
      display.fillRect(valueX, 50, 190, 30, BLACK);  // Clear only the value area
      printData(valueX, 50, "", date, WHITE);
      lastDate = date;
    }

    if (time != lastTime) {
      display.fillRect(valueX, 80, 190, 30, BLACK);
      printData(valueX, 80, "", time, WHITE);
      lastTime = time;
    }

    if (concentration != lastConcentration) {
      display.fillRect(valueX, 110, 190, 30, BLACK);
      printData(valueX, 110, "", concentration, WHITE);
      lastConcentration = concentration;
    }

    if (pressure != lastPressure) {
      display.fillRect(valueX, 140, 190, 30, BLACK);
      printData(valueX, 140, "", pressure, WHITE);
      lastPressure = pressure;
    }

    if (dirtiness != lastDirtiness) {
      display.fillRect(valueX, 170, 190, 30, BLACK);
      printData(valueX, 170, "", dirtiness, WHITE);
      lastDirtiness = dirtiness;
    }

    if (status != lastStatus) {
      display.fillRect(valueX, 200, 190, 30, BLACK);
      printData(valueX, 200, "", status, WHITE);
      lastStatus = status;
    }
  } else {
    titlesPrinted = false;  // Reset when switching pages
  }
}


class displayData {
public:

  // Function to check if the "Next" button was pressed
  bool nextButtonPressed(int touchX, int touchY) {
    if (touchX >= nextButtonX1 && touchX <= nextButtonX2 && touchY >= nextButtonY1 && touchY <= nextButtonY2) {
      return true;  // "Next" button was pressed
    }
    return false;  // "Next" button was not pressed
  }

  // Function to check if the "Previous" button was pressed
  bool prevButtonPressed(int touchX, int touchY) {
    if (touchX >= prevButtonX1 && touchX <= prevButtonX2 && touchY >= prevButtonY1 && touchY <= prevButtonY2) {
      return true;  // "Previous" button was pressed
    }
    return false;  // "Previous" button was not pressed
  }

  void overviewPage(DataPacket &data) {
    pageIsGraph = false;
    String HEADER = "BMT965 ST Data";  // Text to display
    display.setTextSize(2);            // Set text size, adjust as needed
    display.setTextColor(WHITE);       // Text color
    // Calculate the text bounds
    int16_t textX = 200, textY = 20;  // Start from x = 0, y = 10 for top
    uint16_t textWidth, textHeight;
    display.getTextBounds(HEADER, textX, textY, &textX, &textY, &textWidth, &textHeight);

    // Calculate the x-coordinate to center the text
    int centerX = (display.width() - textWidth) / 2;

    // Set cursor to the centered position
    display.setCursor(centerX, textY);

    // Draw the text
    display.print(HEADER);

    // Determine if the status is OK or NOT OK
    String status = getStatus(data.status);

    // Set text size and color
    display.setTextSize(2);
    display.setTextColor(WHITE);  // Text color

    // Print the RS232 data in sections
    printRS232Data(cleanValue(data.date), cleanValue(data.timeStr), cleanValue(data.concentration), cleanValue(data.pressure), cleanValue(data.dirtiness), status);
  }

  void pageSelect(DataPacket &data) {
    static int previousPage = -1;  // Remember the previous page

    // Check if the page is a graph page and if the function hasn't been called yet
    if (pageIsGraph && !isCalled) {
      drawGraph();
      isCalled = true;
    }

    if (pageIsGraph) plotGraph();

    FT6336U_TouchPointType touchPoints = touchScreen.scan();

    if (touchPoints.touch_count > 0) {
      int touchX = touchPoints.tp[0].x;
      int touchY = touchPoints.tp[0].y;
      // for touch position debugging
      Serial.print("x = ");
      Serial.print(touchX);
      Serial.print("      y = ");
      Serial.println(touchY);


      if (nextButtonPressed(touchX, touchY)) {
        currentPage++;
        if (currentPage > totalPages) currentPage = 1;
      }

      if (prevButtonPressed(touchX, touchY)) {
        currentPage--;
        if (currentPage < 1) currentPage = totalPages;
      }
    }

    // If page changed, redraw content
    if (currentPage != previousPage) {
      display.fillScreen(BLACK);  // Clear the screen before drawing new content
      drawNav();
      pageIsGraph = false;
      isCalled = false;

      switch (currentPage) {
        case 1:
          overviewPage(data);
          break;
        case 2:
          concentrationPage(data);
          break;
        case 3:
          pressurePage(data);
          break;
        case 4:
          dirtinessPage(data);
          break;
        case 5:
          statusInfoPage(data);
          break;
        default:
          currentPage = 1;
          overviewPage(data);
          break;
      }
      dataCount = 0;
      for (int i = 0; i < maxDataPoints; i++) {
        dataValues[i] = 0;
      }
      if (pageIsGraph) {
        // Clear the graph area (but not the axes or grid)
        display.fillRect(graphX - 5, graphY - 5, graphWidth + 10, graphHeight + 10, BLACK);
        drawGraph();
      }

      previousPage = currentPage;
    }

    display.setTextSize(1);
    display.setCursor(400, 300);
    display.setTextColor(WHITE);
    display.print("Page ");
    display.print(currentPage);
    display.print("/");
    display.print(totalPages);
  }

  void drawNav() {
    display.fillRect(10, 10, 20, 20, GREY);
    display.fillRect(450, 10, 20, 20, GREY);
    display.drawLine(14, 20, 26, 14, WHITE);
    display.drawLine(14, 20, 26, 26, WHITE);
    display.drawLine(454, 14, 466, 20, WHITE);
    display.drawLine(454, 26, 466, 20, WHITE);
  }

  void drawGraph() {
    // Draw the title at the top of the graph
    display.setCursor(graphX + (graphWidth / 4), graphY - 30);  // Adjust title position
    display.setTextColor(WHITE);
    display.setTextSize(2);  // Title size

    // Draw the gridlines (horizontal and vertical)
    for (int i = 0; i <= gridYNum; i++) {
      int yPosition = graphY + i * (graphHeight / gridYNum);  // Y position for horizontal gridlines
      display.drawLine(graphX, yPosition, graphX + graphWidth, yPosition, GREY);
    }

    for (int i = 0; i <= gridXNum; i++) {
      int xPosition = graphX + i * (graphWidth / gridXNum);  // X position for vertical gridlines
      display.drawLine(xPosition, graphY, xPosition, graphY + graphHeight, GREY);
    }

    // Thick X axis (bottom)
    for (int i = 0; i < axisThickness; i++) {
      display.drawFastHLine(graphX, graphY + graphHeight + i, graphWidth, WHITE);
    }

    // Thick Y axis (left)
    for (int i = 0; i < axisThickness; i++) {
      display.drawFastVLine(graphX + i, graphY, graphHeight, WHITE);
    }

    display.setCursor(220, 290);
    display.print("Time");

    // Draw Y-axis labels (min and max)
    display.setTextSize(1);  // Smaller text for axis labels
    display.setTextColor(WHITE);

    // Max Y value (top-left of graph)
    display.setCursor(graphX - 30, graphY - 5);  // Adjust -30 if needed for width
    display.print(maxY);

    // Min Y value (bottom-left of graph)
    display.setCursor(graphX - 30, graphY + graphHeight - 5);  // Same x, lower y
    display.print("0");
  }

  void plotGraph() {
    int spacing = 6;  // Spacing between each data point (40px)

    // Redraw the updated graph title
    display.setCursor(graphX + (graphWidth / 4), graphY - 30);
    display.setTextColor(WHITE);
    display.setTextSize(2);

    // Check if it's time to add a new data point (every 1 second)
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
      // Clear the title area (adjust coordinates and size as needed)
      display.fillRect(graphX + (graphWidth / 4), graphY - 40, 250, 30, BLACK);
      display.print(graphTitle);

      // Shift all data points to the left
      if (dataCount >= maxDataPoints) {
        display.fillRect(graphX - 5, graphY - 5, graphWidth + 10, graphHeight + 10, BLACK);
        drawGraph();
        for (int i = 0; i < maxDataPoints - 1; i++) {
          dataValues[i] = dataValues[i + 1];
        }
        dataValues[maxDataPoints - 1] = plotValue;
      } else {
        // If we haven't filled the array yet, just add the new value
        dataValues[dataCount] = plotValue;
        dataCount++;
      }
    }

    // Plot all data points and connect them with lines
    for (int i = 0; i < dataCount; i++) {
      if (i == 0 && dataValues[i] == 0) continue;

      int x = graphX + i * spacing;
      int y = (int)mapFloat(dataValues[i], 0.0, (float)maxY, graphY + graphHeight, graphY);


      display.fillCircle(x, y, 1, RED);

      if (i > 0) {
        int prevX = graphX + (i - 1) * spacing;
        int prevY = (int)mapFloat(dataValues[i - 1], 0.0, (float)maxY, graphY + graphHeight, graphY);
        display.drawLine(prevX, prevY, x, y, WHITE);
      }
    }
  }

  void concentrationPage(DataPacket &data) {
    pageIsGraph = true;
    // Parse the concentration value from the data packet
    float graphConcentrationValue = stringToFloat(data.concentration);
    graphTitle = "Ozone: " + cleanValue(data.concentration) + "g/Nm3";
    plotValue = graphConcentrationValue;
    maxY = 150;
  }

  void pressurePage(DataPacket &data) {
    pageIsGraph = true;
    // Parse the concentration value from the data packet
    float graphPressureValue = stringToFloat(data.pressure);
    graphTitle = "Pressure: " + cleanValue(data.pressure) + " bar";
    plotValue = graphPressureValue;
    maxY = 4;
  }

  void dirtinessPage(DataPacket &data) {
    pageIsGraph = true;
    // Parse the concentration value from the data packet
    float graphDirtinessValue = stringToFloat(data.dirtiness);
    graphTitle = "Dirtiness: " + cleanValue(data.dirtiness) + "%";
    plotValue = graphDirtinessValue;
    maxY = 100;
  }

  void statusInfoPage(DataPacket &data) {
    pageIsGraph = false;
    String HEADER = "Status Information";  // Text to display
    display.setTextSize(2);                // Set text size, adjust as needed
    display.setTextColor(WHITE);           // Text color
    // Calculate the text bounds
    int16_t textX = 200, textY = 20;  // Start from x = 0, y = 10 for top
    uint16_t textWidth, textHeight;
    display.getTextBounds(HEADER, textX, textY, &textX, &textY, &textWidth, &textHeight);
    // Calculate the x-coordinate to center the text
    int centerX = (display.width() - textWidth) / 2;
    // Set cursor to the centered position
    display.setCursor(centerX, textY);
    // Draw the text
    display.print(HEADER);
    String statusHex = data.status;                                    // Get the status value from the data object
    unsigned long statusValue = strtoul(statusHex.c_str(), NULL, 16);  // Convert hex string to unsigned long
    display.setTextSize(2);
    display.setTextColor(WHITE);
    int yPos = 30;        // Starting Y position for the text
    int xPos = 10;        // Starting X position for the text
    int lineHeight = 30;  // Space between lines
    // Print each status bit's information
    display.setCursor(xPos, yPos);
    yPos += lineHeight;
    // Check each bit and print the corresponding status
    if (statusValue & 0x0001) {  // Bit 0: Lamp Low Warning
      display.setCursor(xPos, yPos);
      display.print("Bit 0: Lamp Low Warning");
      yPos += lineHeight;
    }
    if (statusValue & 0x0002) {  // Bit 1: Lamp Low Error
      display.setCursor(xPos, yPos);
      display.print("Bit 1: Lamp Low Error");
      yPos += lineHeight;
    }
    if (statusValue & 0x0004) {  // Bit 2: Lamp Off Error
      display.setCursor(xPos, yPos);
      display.print("Bit 2: Lamp Off Error");
      yPos += lineHeight;
    }
    if (statusValue & 0x0008) {  // Bit 3: Dirty Warning
      display.setCursor(xPos, yPos);
      display.print("Bit 3: Dirty Warning");
      yPos += lineHeight;
    }
    if (statusValue & 0x0010) {  // Bit 4: Dirty Error
      display.setCursor(xPos, yPos);
      display.print("Bit 4: Dirty Error");
      yPos += lineHeight;
    }
    if (statusValue & 0x0020) {  // Bit 5: Overpressure Error
      display.setCursor(xPos, yPos);
      display.print("Bit 5: Overpressure Error");
      yPos += lineHeight;
    }
    if (statusValue & 0x0040) {  // Bit 6: Overrange Error
      display.setCursor(xPos, yPos);
      display.print("Bit 6: Overrange Error");
      yPos += lineHeight;
    }
    if (statusValue & 0x0080) {  // Bit 7: EEPROM Error
      display.setCursor(xPos, yPos);
      display.print("Bit 7: EEPROM Error");
      yPos += lineHeight;
    }
    if (statusValue & 0x0100) {  // Bit 8: Zeroing
      display.setCursor(xPos, yPos);
      display.print("Bit 8: Zeroing");
      yPos += lineHeight;
    }
    if (statusValue & 0x0200) {  // Bit 9: Warmup
      display.setCursor(xPos, yPos);
      display.print("Bit 9: Warmup");
      yPos += lineHeight;
    }
    if (statusValue & 0x0400) {  // Bit 10: Lamp High Error
      display.setCursor(xPos, yPos);
      display.print("Bit 10: Lamp High Error");
      yPos += lineHeight;
    }
    if (statusValue & 0x4000) {  // Bit 14: Low Alarm
      display.setCursor(xPos, yPos);
      display.print("Bit 14: Low Alarm");
      yPos += lineHeight;
    }
    if (statusValue & 0x8000) {  // Bit 15: High Alarm
      display.setCursor(xPos, yPos);
      display.print("Bit 15: High Alarm");
      yPos += lineHeight;
    }
    if (statusValue == 0x0000) {
      display.setCursor(xPos, yPos);
      display.print("All systems OK.");
    }
  }

  void updatePlotValue(DataPacket &data) {
    switch (currentPage) {
      case 2:
        plotValue = stringToFloat(data.concentration);
        graphTitle = "Ozone: " + cleanValue(data.concentration) + "g/Nm3";
        maxY = 150;
        break;
      case 3:
        plotValue = stringToFloat(data.pressure);
        graphTitle = "Pressure: " + cleanValue(data.pressure) + " bar";
        maxY = 4;
        break;
      case 4:
        plotValue = stringToFloat(data.dirtiness);
        graphTitle = "Dirtiness: " + cleanValue(data.dirtiness) + "%";
        maxY = 100;
        break;
      default:
        break;
    }
  }
};

displayData displayManager;

// =================================================================================================
// SerialReader Class (uses Serial1 directly)
// =================================================================================================

class SerialReader {
public:
  String serialString = "";

  bool read() {
    while (Serial1.available() > 0) {
      char incomingByte = Serial1.read();
      if (incomingByte == '\r') {
        return true;
      }
      if (isPrintable(incomingByte)) {
        serialString += incomingByte;
      }
    }
    return false;
  }

  String getPacket() {
    String packet = serialString;
    serialString = "";
    return packet;
  }

  void processData(DataPacket &data, displayData &displayManager) {
    if (read()) {
      String packet = getPacket();
      if (packet.startsWith("*")) {
        Serial.println("Command response: " + packet);
        return;
      }
      Serial.print("Raw Data: ");
      Serial.println(packet);
      data.parse(packet);
      if (data.date != "") {
        data.printParsedData();
        String status = getStatus(data.status);
        printRS232Data(data.date, data.timeStr, data.concentration, data.pressure, data.dirtiness, status);
        displayManager.updatePlotValue(data);  // Call updatePlotValue here
      }
    }
  }
};


SerialReader reader;

// =================================================================================================
// BitmapDrawer Class
// =================================================================================================

class BitmapDrawer {
public:
  Adafruit_ST7796S &display;
  BitmapDrawer(Adafruit_ST7796S &disp)
    : display(disp) {}

  void draw(const char *filename, int x, int y) {
    File bmpFile = SD.open(filename);
    if (!bmpFile) return;
    if (read16(bmpFile) != 0x4D42) return;

    read32(bmpFile);  // Skip headers
    read32(bmpFile);
    uint32_t bmpImageoffset = read32(bmpFile);
    uint32_t headerSize = read32(bmpFile);
    if (headerSize != 40) return;

    int bmpWidth = read32(bmpFile);
    int bmpHeight = read32(bmpFile);
    if (read16(bmpFile) != 1) return;
    if (read16(bmpFile) != 24) return;

    read32(bmpFile);
    read32(bmpFile);
    read32(bmpFile);
    read32(bmpFile);
    read32(bmpFile);
    read32(bmpFile);

    uint8_t rowPad = (4 - (bmpWidth * 3) % 4) % 4;
    if (bmpHeight < 0) bmpHeight = -bmpHeight;

    bmpFile.seek(bmpImageoffset);

    float scaleX = 480.0 / bmpWidth;
    float scaleY = 320.0 / bmpHeight;
    float scale = min(scaleX, scaleY);
    int scaledWidth = bmpWidth * scale;
    int scaledHeight = bmpHeight * scale;

    for (int row = scaledHeight - 1; row >= 0; row--) {
      for (int col = scaledWidth - 1; col >= 0; col--) {
        uint8_t b = bmpFile.read();
        uint8_t g = bmpFile.read();
        uint8_t r = bmpFile.read();
        uint16_t color = display.color565(r, g, b);
        display.drawPixel(x + col, y + (scaledHeight - 1 - row), color);
      }
      for (int p = 0; p < rowPad; p++) bmpFile.read();
    }
    bmpFile.close();
  }

private:
  uint16_t read16(File &f) {
    uint16_t result;
    ((uint8_t *)&result)[0] = f.read();
    ((uint8_t *)&result)[1] = f.read();
    return result;
  }

  uint32_t read32(File &f) {
    uint32_t result;
    ((uint8_t *)&result)[0] = f.read();
    ((uint8_t *)&result)[1] = f.read();
    ((uint8_t *)&result)[2] = f.read();
    ((uint8_t *)&result)[3] = f.read();
    return result;
  }
};

void setupDisplay() {
  display.init(320, 480, 0, 0, ST7796S_RGB);
  display.setRotation(1);
  display.fillScreen(BLACK);
  BitmapDrawer drawer(display);
  drawer.draw("/TRIOGEN.BMP", 0, 0);
  delay(1000);
  display.fillScreen(BLACK);
}


void printData(int x, int y, const String &label, const String &data, uint16_t color) {
  if (currentPage == 1) {
    display.setCursor(x, y);
    display.setTextColor(WHITE);
    display.setTextSize(2);
    display.print(label);
    display.print("");
    display.print(data);
  }
}


void setup() {
  Serial1.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
  SD.begin(SD_CS);
  touchScreen.begin();
  setupDisplay();
  display.setRotation(3);
  Serial.begin(9600);
}

void loop() {
  currentMillis = millis();
  tp = touchScreen.scan();
  displayManager.pageSelect(data);
  reader.processData(data, displayManager);
}

Config:

// config.h
#ifndef CONFIG_H
#define CONFIG_H

// Pin definitions
#define SD_CS 5
#define CPT_INT 6
#define CPT_RST 7
#define TFT_DC 8
#define TFT_RST 9
#define TFT_CS 10
#define CPT_SDA 11
#define CPT_SCL 12
#define TX_PIN 17
#define RX_PIN 18


// SPI configuration
#define SPI_FREQUENCY 16000000

// display objects (LCD and CPT touch screen)
Adafruit_ST7796S display(TFT_CS, TFT_DC, TFT_RST);
FT6336U touchScreen(CPT_SDA, CPT_SCL, CPT_RST, CPT_INT);

// structure for storing touch data
FT6336U_TouchPointType tp;

uint16_t GREY = display.color565(180, 180, 180);
uint16_t BLACK = display.color565(255, 255, 255);
uint16_t WHITE = display.color565(0, 0, 0);
uint16_t RED = display.color565(255, 255, 0);
uint16_t GREEN = display.color565(255, 0, 255);
uint16_t BLUE = display.color565(0, 255, 255);
uint16_t MAGENTA = display.color565(0, 255, 0);

const int graphX = 60;        // X position of the graph (left margin)
const int graphY = 40;        // Y position of the graph (top margin)
const int graphWidth = 360;   // Graph width
const int graphHeight = 240;  // Graph height

const int gridXNum = 10;   // Number of gridlines on X axis (columns)
const int gridYNum = 10;  // Number of gridlines on Y axis (rows)


// Coordinates for Previous and Next buttons
int prevButtonX1 = 0;    
int prevButtonY1 = 380;  
int prevButtonX2 = 100;  
int prevButtonY2 = 480;  

int nextButtonX1 = 0;    
int nextButtonY1 = 0;    
int nextButtonX2 = 100;  
int nextButtonY2 = 100;  

const int axisThickness = 2;  // Thickness of the axes

String graphTitle = "";  // Default to empty string

// Variable to track whether you're on the graph page
bool pageIsGraph = false;  // Default to false, change to true when you're on the graph page
bool isCalled = false;     //Default to false, checks if drawGraph() func has already been called

int currentPage = 1;
int totalPages = 5;  // Total number of pages

float plotValue;
int maxY;

unsigned long previousMillis = 0;
unsigned long currentMillis;
const long interval = 1000;  // 1 second interval
const int maxDataPoints = 61;
float dataValues[maxDataPoints];
int dataCount = 0;

#endif  // CONFIG_H

(Yes, I know I'm using a lot of global variables)

Libraries:

// libraries.h
#ifndef LIBRARIES_H
#define LIBRARIES_H

#include <SPI.h>
#include <FS.h>
#include <SD.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7796S.h>
#include <FT6336U.h>
#include <Fonts/FreeSansBold18pt7b.h>

#endif  // LIBRARIES_H

I have tried encapsulating the drawGraph() function in startWrite and endWrite for batching the commands, but this appears to brick the code.

I still need to fix comments etc, but I just want to try reduce the flicker for now. I also know my colour definitions are weird, but they work (Cheap Chinese LCD).

Not sure what type of board you're using, or if your MCU has enough memory etc, but I think the following resources might help, especially using Offscreen Canvas:

Rather than clearing rectangles to background color and then writing new data:

  1. [edit] IF old data is not equal to new data
  2. store old data
  3. change text color to background color
  4. write the old data (again, in background color)
  5. change text color to foreground color
  6. write the new data (in foreground color).
2 Likes

An offscreen buffer is one way, but as @embeddedkiddie points out, you might not have enough memeory.

This kinda thing

      display.fillScreen(BLACK);  // Clear the screen before drawing new content

makes the redrawing flashy and obvious. A common trick to get around this takes a bit of care but works well, and that is to never clear the screen you are repainting, but to carefully "undraw" what you had before, then draw whatever the new thing is.

You can undraw or erase selectively. With text, draw it again in the background colour, then draw it for real.

With other graphic elements it is the same. You'll get challenged by things that overlap, like a pair of lines that intersect. Undrawing can leave little signs of having been done.

a7

Here is a demo of undrawing and redrawing graphics on an OLED.

I've tried using canvas and even using the 1 bit canvas has visible rewrites. I tried implementing 2 more colours and writing a GFXCanvas2 function so I could use it in my project, but I think redrawing is the quickest I can make it, even if it isn't as smooth as I'd want.

1 Like

Redraw only the changed data.

The problem is as it is live data, all data needs redrawn as all the data points are shifted 1 point to the left. As soon as the buffer is full, it moves all data points 1 to the left to add the new point, therefore no matter what the reading is (except if I had readings with 0 fluctuation at all), I'll need to redraw everything.

Only data that has changed needs to be redrawn.

This looks like where you store current data...

Make one more array with a different name

float dataValuesOld[maxDataPoints];

After you plot the new data... before you get new data... transfer the new data array to the "Old" data array and immediately before you draw the new data, change the color to background and draw the "Old" data.

array++; // increment element
data[array] = datapoint; // store data in array
.
.
color(background_color);
draw(dataOld[array]); // "erase" last data by writing over in background color
color(foreground_color);
draw(data[array]); // draw new data
.
.
dataOld[array] = data[array]; // move current data array to "old" data array

You could also erase-old/draw-new without redrawing the full array, so your line updates as the trace moves... something like this....

2 Likes

Nice. Medical equipment uses that sweep method rather than scrolling for UX reasons.

A mostly static display is, in that context, easier to read and interpret than a scrolling display.

There's less activity overall, and the data stays aligned with timing marks that may be displayed along with the data, or imposed bt a reticle.

a7

1 Like

Cool. I tried a display.drawLine(xVal - 1, SCREEN_MIDDLE + (yValLast * zVal), xVal - 1, SCREEN_MIDDLE + (yVal * zVal), SH110X_WHITE ); instead of a pixel to get denser lines during wilder motions.

1 Like

Implemented and credited. <3

1 Like