setPartialWindow() delays rest of the program

Hello this is my first post on the forum. I've been pouring over other threads and youtube for an answer to no avail. Hopefully it's a pretty simple answer. Maybe @ZinggJM or someone else can help me.

context:
I'm building a small low-power module with a waveshare 2.13" (250x122) monochrome display that has the SSD1680 driver chip. It is connected to an Adafruit eInk Feather Friend, which is soldered onto an Adafruit Feather 328P. I chose this hardware because of it's very compact length x width size, and because I originally thought I'd be using the Adafruit ThinkInk library. That was until I discovered that their library doesn't support partial updates for this 2.13" display :slightly_frowning_face: , so I'm now using ZinggJM's GxEPD2 library.

The Hardware seems to work just fine using GxEPD2 library once I got my pin mapping sorted out.

I am an Industrial Designer by training and experience. I am pretty handy with electronics and self taught when it comes to programing. No computer science degree here FYI. I'm pretty good at implementing methods from libraries as long as I have a good explanation on what they do.

Issue:
So... My issue now is that while my paged display code and use of setPartialWindow() appear to be working just fine, the use of setPartialWindow() seems to create a pause in the program execution while the screen is refreshing. As such, reading my button pins is super delayed. I have to press and hold the buttons down, and depending on the timing, it will print to serial monitor which button is pressed. Somtimes it will print right away if I pressed the button just as the screen refresh was finishing. If not, the print to serial monitor will be delayed for 1 to 2 full seconds before I see it.

What I need:
What I need is for the buttons to be quickly responsive so I can write code for them to guide the user through a rudimentary GUI interface that I will be building.

My own speculation:
Is there something I'm missing here about setPartialWindow()? Is there some way to have it not bog down the rest of the program? I can imagine there might be some way to put the screen to sleep, or maybe change the rate of screen updating? Like just once per second. Even then waiting a full second for my button pressing to respond will be super annoying for the user.

I've tried fiddling around with hybernate(), or powerOff. But I can not get it to have the affect I want. Just ends up flickering the display. Maybe I'm inserting it in the wrong place?

Anyway, I'll stop speculating and wait for a response.
Thank you all in advance!


#include <Adafruit_GFX.h> // Core graphics library
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>

#if defined(__AVR)
#define MAX_DISPLAY_BUFFER_SIZE 800
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213_B74, MAX_HEIGHT(GxEPD2_213_B74)> display(GxEPD2_213_B74(/*CS=*/ 9, /*DC=*/ 10, /*RST=8*/ -1, /*BUSY=7*/ -1)); // GDEM0213B74 122x250, SSD1680
#endif

#define upButton 3
#define downButton 2
#define checkButton A1
#define infoButton A0

void evenSlotTiming();//function that updates the 

long vTime = 60000;//vTime starts at a value of 1 minute in milliseconds

void setup() {
    display.init(115200); // default 10ms reset pulse, e.g. for bare panels with DESPI-C02
    Serial.begin(9600);
    
    display.setTextColor(GxEPD_BLACK);
    display.setFont(&FreeMonoBold9pt7b);
    display.setRotation(3);
    pinMode(upButton, INPUT_PULLUP);
    pinMode(downButton, INPUT_PULLUP);
    pinMode(checkButton, INPUT_PULLUP);
    pinMode(infoButton, INPUT_PULLUP);

    display.setFullWindow();
    display.firstPage();//This sets up the screen for the non-changing/updating graphics
    do{
      display.fillScreen(GxEPD_WHITE);
      display.setCursor(20,30);
      display.println("Next event in ");
    }while(display.nextPage());
}//END SETUP

void loop() {
    eventSlotTiming();//function that adds another hour to vTime each time it's run down to zero
    int timeRemain = (vTime-millis())/1000;//as millis increases, this takes the difference between millis and vTime and converts it to seconds

    display.setPartialWindow(165,15,80,20);
    display.firstPage();
    do{
      display.fillScreen(GxEPD_WHITE);
      display.drawRect(165,15,80,20,GxEPD_BLACK);
      display.setCursor(175,30);
      display.print(timeRemain);
      display.setCursor (200,30);
      display.print("sec");
     }while(display.nextPage());

     if(digitalRead(upButton)==LOW){
      Serial.println("upButton is pressed");
     }
     if(digitalRead(downButton)==LOW){
      Serial.println("downButton is pressed");
     }
     if(digitalRead(checkButton)==LOW){
      Serial.println("checkButton is pressed");
     }
     if(digitalRead(infoButton)==LOW){
      Serial.println("infoButton is pressed");
     }
     
}//END LOOP

void eventSlotTiming(){
      if(millis() >= vTime){
        vTime = millis() + 60000;//Every minute, this resets vTime variable to one minute into the future
      }
}

Updating a monitor, that being big or small will take time to do, as the hsync and vsync need to be timed together, and the screen refresh rate will always, take alot of processing

(OLD) So, As far as I know of, there is no way to speed it up, you can do something like
if (Serial.avalable =< 0) {}
or you could do what I did, and just have the master send binary through the serial, and have the slave decrypt it, that does speed it up just a bit.

You could also set the baud rate to its highest, but doing that would also cause some miscommunications. Just try to refrain from using Serial.readStringUltil(); cause that slows it much more that Serial.read();

But other than that, reading the Serial takes alot of time to do, and there is no work around yet, I may make a way to go around it so data processing can just work faster in the serial.

Again if you haven't, try and see if someone else posted a form with the same issue, and see if it was solved.

Thanks for your response @spiritxiv.
I don’t think the 9600 baud, or serial communication in general, is contributing to the full 1 to 2 sec delay I’m experiencing. If I comment out the setPartialWindow routine code, and leave just the setFullScreen, the button presses print to serial crazy fast. I’m not planning to use Serial at all in my final program. Just using it as a debugging tool for now.

From my experience of using it, setPartialWindow seems to take about 1 to 2 sec to complete. That is about how long it appears to delay the rest of the program.
I’ve read elsewhere that people get about 3 full partial updates per second from setPartialUpdate. If that’s true, it is many more updates/sec than I really need. Over 1-2 sec that could be like 6 full updates total, right?

I’m still wondering if there is any way to adjust the total number of updates performed by setPartialWindow. I hope I’m not reaching for straws here, but if I can reduce the number of updates setPartialWindow performs, I would in turn reduce how long it delays my program.

If more updates are needed to get the text to really go more solid black, I could call setPartialUpdate code on like a 3sec interval or something to achieve that. (Information displayed really only needs to change like once/min) unless a button is pressed.

I really don’t need a super-fast-responding screen.
I just want as little delay to the rest of my program, and no screen flicker like you get from setFullScreen.

I have definitely read around to try and find other people with this issue. I’m pretty new to the forum so if anyone sees this issue elsewhere, please point me to it. I will keep looking but most people seem to use these EP displays for 1-way informational displays. Like weather stations, or a picture displays.

An interactive GUI is not a very common example. I will keep looking, but I feel this is a really good topic to explore solutions to.

I found an excerpt from another post that seems related. See screen snip below.
Looks like using interrupts might be able to stop setPartialWindow() whenever a button is pressed. I've hardly ever used interrupts, but now seems like a good time to learn! I will repost my code here if I get something that works.

@MacMinister , Hi, welcome to the forum!

GxEPD2 is not made for your kind of application. Screen refresh takes time, and the refresh command to the display controller is not the last command to the controller.
Therefore a busy wait for termination of the BUSY signal is required.

But you can use a BusyCallback to cope with this, as in example GxEPD2_RotaryUsingBusyCallback.ino.

// GxEPD2_RotaryUsingBusyCallback.ino: an example showing use of busyCallback to handle time-critical services
// GxEPD2 uses busy wait on the BUSY line from EPDs by design, 
// to handle all combinations of paged display and differential refresh in a user friendly way.
// busyCallback can be used to overcome this restriction caused by busy wait.
// note: this feature is only available if the BUSY line is used, not if the default delay is used instead.

-jz-

1 Like

Thanks for your response @ZinggJM! I combed through that example sketch with the encoder and managed to copy the bits into my code that made it work!

For anyone looking at this, it's important that you have the BUSY line on your display connected to a real pin on your controller and you have it mapped correctly. If you have BUSY set to -1 it will use the standard delay and this code that allows your function to interrupt the display process will not work.

Thanks again ZinggJM!

Here is the my code with the BusyCallback code implemented for buttons instead of an encoder. Pulled from the example sketch GxEPD2_RotaryUsingBusyCallback.ino

#include <Adafruit_GFX.h> // Core graphics library
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>

#if defined(__AVR)
#define MAX_DISPLAY_BUFFER_SIZE 800
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213_B74, MAX_HEIGHT(GxEPD2_213_B74)> display(GxEPD2_213_B74(/*CS=*/ 9, /*DC=*/ 10, /*RST=8*/ -1, /*BUSY=7*/7)); // GDEM0213B74 122x250, SSD1680
#endif

#define upButton 3
#define downButton 2 
#define checkButton A1
#define infoButton A0

long vTime = 60000;//vTime starts at a value of 1 minute in milliseconds

void setup() {
    display.init(); // disable diagnostics to avoid delay and catch most rotary pulses
    Serial.begin(9600);
    
    display.setTextColor(GxEPD_BLACK);
    display.setFont(&FreeMonoBold9pt7b);
    display.setRotation(3);
    pinMode(upButton, INPUT_PULLUP);
    pinMode(downButton, INPUT_PULLUP);
    pinMode(checkButton, INPUT_PULLUP);
    pinMode(infoButton, INPUT_PULLUP);
    display.epd2.setBusyCallback(busyCallback); // register callback to be called during BUSY active time

    display.setFullWindow();
    display.firstPage();//This sets up the screen for the non-changing/updating graphics
    do{
      display.fillScreen(GxEPD_WHITE);
      display.setCursor(20,30);
      display.println("Next vote in ");
    }while(display.nextPage());
}//END SETUP----------------------------------------------------------------------------------------------

void loop() {
    eventSlotTiming();//function that adds another hour to vTime each time it's run down to zero
    int timeRemain = (vTime-millis())/1000;//as millis increases, this takes the difference between millis and vTime and converts it to seconds
    serviceButtons();

    display.setPartialWindow(165,15,80,20);
    display.firstPage();
    do{
      display.fillScreen(GxEPD_WHITE);
      display.drawRect(165,15,80,20,GxEPD_BLACK);
      display.setCursor(175,30);
      display.print(timeRemain);
      display.setCursor (200,30);
      display.print("sec");
     }while(display.nextPage());
}//END LOOP-----------------------------------------------------------------------------------------------

// busyCallback function called during waiting for BUSY to end, to service buttons
void busyCallback(const void* p)
{
  serviceButtons();
}//END busyCallback---------------------------------------------------------------------------------------

void serviceButtons(){
if(digitalRead(upButton)==LOW){
      Serial.println("upButton is pressed");
     }
     if(digitalRead(downButton)==LOW){
      Serial.println("downButton is pressed");
     }
     if(digitalRead(checkButton)==LOW){
      Serial.println("checkButton is pressed");
     }
     if(digitalRead(infoButton)==LOW){
      Serial.println("infoButton is pressed");
     }
}//END serviceButtons-------------------------------------------------------------------------------------

void eventSlotTiming(){
      if(millis() >= vTime){
        vTime = millis() + 60000;//Every minute, this resets vTime variable to one minute into the future
      }
}//END eventSlotTiming------------------------------------------------------------------------------------

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