Inky Frame 7.3 burn-in

(I know e-ink fatigue isn’t mechanically the same as phosphor burn-in, but it’s a useful term for the effect.)

I have an Inky Frame 7.3 that’s been running as an image frame with overlaid date and approximate time (and some sensor graphs, actually a whole thin-client setup), refreshing every ten minutes, for a few months. So that should be a few tens of thousands of refreshes.

This is a particularly drastic photo where it cleared to plain red with some white text because it was showing an error screen while the server it fetches images from was down:

It’s not normally that obvious, and it shakes out a bit if you do lots of manual clear-to-colors, but it’s definitely becoming subtly noticable. I’ve not had much luck finding actual hard specification data on these panels, but I was expecting more like millions of refrshes lifespan from what I’d gleaned—i.e. I wouldn’t be seeing this problem for years.

What I have noticed is the way the Inky refreshes the panel is not how I thought e-ink panel refreshes were supposed to work. To pinch a graphic from a completely unrelated bug report elsewhere:

The idea as I understand it that e-ink “afterburn” artefacts are a result of the ink in the cells not being pulled back to rest position and residual charges needing to be cancelled out, so first you need to “undo” the first image back to a blank slate. But what the Inky seems to do is draw the inverse of the new framebuffer contents, then flash its way inverting fullscreens to taupe, then draw the new image.

I’m saying “the Inky” instead of PicoGraphics, because it seems the refresh dance is really a single SPIO command once the graphics buffer is filled: pimoroni-pico/drivers/inky73/inky73.cpp at main · pimoroni/pimoroni-pico · GitHub (I assume the python version wraps the C++)

command(DRF, {0}); // start display refresh

I went digging for what might be the right panel to try to find an answer, and if I got it right that it’s https://www.good-display.com/product/442.html , looking at their ESP32 sample code, they define this fuction (slightly reformatted for brevity):

void PIC_display(const unsigned char* picData) {
  unsigned int i,j,k;
  unsigned char temp1,temp2;
  unsigned char data_H,data_L,data;
  
   //Acep_color(White); //Each refresh must be cleaned first   
  EPD_W21_WriteCMD(0x10);        
  for(i=0;i<480;i++) { 
    k=0;
    for(j=0;j<800/2;j++) {    
      /* snip converting pixels into data nibbles for forum post brevity */
      EPD_W21_WriteDATA(data);
    }
  } 
  
   //Refresh
    EPD_W21_WriteCMD(0x12);   //DISPLAY REFRESH   
    EPD_W21_WriteDATA(0x00);
    delay(1);   //!!!The delay here is necessary, 200uS at least!!!     
    lcd_chkstatus();          //waiting for the electronic paper IC to release the idle signal
}

So it’s doing the same thing of sending the new image data before the refresh command…but what I don’t is why //Acep_color(White); //Each refresh must be cleaned first is commented out. If it wasn’t, that function would do pretty much the same as this one, but where data is always plain white, before moving on to then sending the actual image data and doing a second refresh. (Reformatted again slightly:)

void Acep_color(unsigned char color) {
  unsigned int i,j;
  EPD_W21_WriteCMD(0x10);        
  for(i=0;i<480;i++) {
    for(j=0;j<800/2;j++) {
      EPD_W21_WriteDATA(color);
    }
  }
  
  //Refresh
  EPD_W21_WriteCMD(0x12);   //DISPLAY REFRESH   
  EPD_W21_WriteDATA(0x00);
  delay(1);              //!!!The delay here is necessary, 200uS at least!!!     
  lcd_chkstatus();          //waiting for the electronic paper IC to release the idle signal
}

(PicoGraphics also doesn’t do the delay marked with !!!exclamation marks!!! before busy-waiting as in this sample code, but I assume all that would cause is an early-return while the panel was still working and the risk of sending it more commands while busy—which doesn’t seem to be happening.)

So in summary:

  • What is the expected refresh-cycle lifespan of the e-ink panel? Is repainting every 10 minutes simply too aggressive?
  • Is the PicoGraphics library refreshing the panel properly? Should it be clearing to blank, or with a negative of the existing image, first?
    • If not, would it help to work around this for now by doing a two-stage manual clear, first to a negative image (or plain taupe?), display.update(), then to the target image, display.update()? At the cost of twice the refresh duration. (Inverting the current PG framebuffer contents in Python may be tricky…)

Thanks!

(When I say “not usually that bad”, this is after some color clears, a couple of days ago:


there’s still visible artefacting from where dates have been there, but it’s more in the “this is the beginnings of a problem” stage than a “my panel is ruined” one.)

Ok, I implemented clear-to-taupe-first, and it looks like the controller itself is doing the negation of the existing image during the flip phase…that’s consistent with seeing the new image before the plain-taupe phase when doing a single display.update().

Can’t tell if a problem that grew over months is solved yet, but I just caused another beautiful red screen tooling around with the server again, and after a day of double-refreshes it is looking a lot, lot better:

The CircuitPython driver has a specific “clear-the-screen” internal function. I think they send a full white image, do a full refresh and then send the real data. This really slows down updates, but maybe this helps?

That sounds a lot like what I’m currently doing (clear to taupe, refresh, draw the image, refresh).

Which is still not quite right, because that second refresh does a negative of the target image, then taupe again, then a the negative of the image and its final draw. The last two steps are correct, but it’s trying to clear taupe to taupe via the framebuffer contents which are not yet on the screen, which is both unnecessary and wrong (as I understand all of this). :/

I took some recordings of the two update mechanisms, moving from a natural photo scene to synthetic test image with big color blocks (both with overlay), so you can see the transition clearly. (The pause on a plain taupe screen in the middle is the pico doing image decoding.)

Single-update, just writing a new image then making a single update() call:

This is what the guides/examples seem to do. But I’m not convinced it’s correct that the display is showing a negative of the synthetic image before it’s cleared to blank—it should be flashing to negatives of the photo scene, right? Which is no longer in PicoGraphics’ memory, and possibly this single-buffer is the root problem.

Double-update, which is a taupe* fill, update(), image plot, and then update() again:

This is what I’m using now, and it seems to be a bit healthier at the cost of another 40 second cycle, but it’s still not quite right. After the manual clear, at about 40 seconds, the second update() is beginning with attempting to “undo” the new synthetic image that is not yet present, reaches it own clear state around the one minute mark, and then starts the process of flashing negative images of it actually pull the ink into its final state. So it’s both taking longer than it needs to, and possibly not quite keeping charges balanced.

It really looks like update() needs to know both the previous and new framebuffer contents, but I’m not sure what the fix is here, because this seems right down in the controller from code spelunking (which is why this is a support forum thread, not a GitHub PR…).

(* I’ve since switched out white for taupe, given the borders go full white during a clear too.)

1 Like