vroland / epdiy

EPDiy is a driver board for affordable e-Paper (or E-ink) displays.
https://vroland.github.io/epdiy-hardware/
GNU Lesser General Public License v3.0
1.25k stars 178 forks source link

Areas of the screen not in the rendering area will become lighter in color. #297

Open lanistor opened 2 months ago

lanistor commented 2 months ago

Areas of the screen that are not in the rendering area will become lighter in color.

For example, screen is 1024*758, rendering area {x: 550, y: 30, width: 474, height: 60} for several times, the area {x: 76, y: 90, width: 474, height: 668} black color will fade. Like this:

截屏2024-04-15 01 43 46

No matter how I adjust the voltage, I can't fix this problem. Is there a way to keep areas' color of the screen that are not in the rendering area unaffected?

Testing environment:

Demo code:

epd_poweron();
epd_hl_update_area(&hl, updateMode, temperature, first->area);
epd_poweroff();
martinberlin commented 2 months ago

My feedback w/Kindle paper white https://youtu.be/HrZnDrv0QJ8?si=tgrA1ztUJoJH-S-3

lanistor commented 2 months ago

My feedback w/Kindle paper white

Got it👍

lanistor commented 2 months ago

Areas of the screen that are not in the rendering area will become lighter in color.

For example, screen is 1024*758, rendering area {x: 550, y: 30, width: 474, height: 60} for several times, the area {x: 76, y: 90, width: 474, height: 668} black color will fade. Like this: 截屏2024-04-15 01 43 46

No matter how I adjust the voltage, I can't fix this problem. Is there a way to keep areas' color of the screen that are not in the rendering area unaffected?

Testing environment:

  • board: v5
  • screen: 060XD4
  • waveform: tested epdiy_ED060XC3 and epdiy_ED060SCT, same behavior.
  • UI library: lvgl
  • mode: MODE_DU, using epd_hl_update_area
  • esp idf: release 4.4

Demo code:

epd_poweron();
epd_hl_update_area(&hl, updateMode, temperature, first->area);
epd_poweroff();

@martinberlin Is there a way to avoid color changes in non-rendering areas?

I think if no voltage is applied to an area, its color should not change.

martinberlin commented 2 months ago

Can you please post some program to replicate this? I know this happens mostly with SPI epapers. But still never saw it in parallel. It might be that display has some ghosts after partial refreshes, that's why e-readers make a full refresh every 10th or so page turn, but other than that it works good for me. But it can be that with this particular eink and program does not work for you. At least you should make a repository and post test cases so we can replicate it

lanistor commented 2 months ago

@martinberlin I found the problem, it was a problem with the code I rewrote.

I'm using below code to replace epd_hl_update_area():

epd_clear_area_cycles(area, 1, _clear_cycle_time);
epd_hl_update_area_directly(&hl, updateMode, temperature, area);

enum EpdDrawError epd_hl_update_area_directly(EpdiyHighlevelState* state,
                                              enum EpdDrawMode     mode,
                                              int                  temperature,
                                              EpdRect              area) {
  assert(state != NULL);
  // Not right to rotate here since this copies part of buffer directly

  bool previously_white = true;
  bool previously_black = false;

  memset(state->dirty_lines + area.y, 1, area.height);

  enum EpdDrawError err;
  if (previously_white) {
    err = epd_draw_base(epd_full_screen(), state->front_fb, area,
                        MODE_PACKING_2PPB | PREVIOUSLY_WHITE | mode,
                        temperature, state->dirty_lines, state->waveform);
  }

  for (int l = area.y; l < area.y + area.height; l++) {
    if (state->dirty_lines[l] > 0) {
      uint8_t* lfb = state->front_fb + EPD_WIDTH / 2 * l;  // to
      uint8_t* lbb = state->back_fb + EPD_WIDTH / 2 * l;  // from

      for (int x = area.x; x < area.x + area.width; x++) {
        if (x % 2) {
          *(lbb + x / 2) = (*(lfb + x / 2) & 0xF0) | (*(lbb + x / 2) & 0x0F);
        } else {
          *(lbb + x / 2) = (*(lfb + x / 2) & 0x0F) | (*(lbb + x / 2) & 0xF0);
        }
      }
    }
  }
  return err;
}

This method will clear afterimages and ensure faster rendering speed, but the problem is: the area rendered by this function, will become lighter in color, when other area rendering.

I wan't to rewrite epd_hl_update_area_directly, but not works, such as set state->difference_fb[l * EPD_WIDTH + x] = ((*(lfb + x / 2) & 0x0F) << 4) | (*(lbb + x / 2) & 0x0F); will not work.

Could you tell me the wrong of this function epd_hl_update_area_directly, and how to repair it?

martinberlin commented 2 months ago

Could you tell me the wrong of this function epd_hl_update_area_directly, and how to repair it?

I don’t really know since I never try to use this myself. I either use the high level library or I push the raw framebuffer and do a hl_update

lanistor commented 2 months ago

Could you tell me the wrong of this function epd_hl_update_area_directly, and how to repair it?

I don’t really know since I never try to use this myself. I either use the high level library or I push the raw framebuffer and do a hl_update

It should be because of i don't known the rendering logic of epdiy clearly, i havn't got the side effect of this way.

lanistor commented 2 months ago

@martinberlin I found the problem, it was a problem with the code I rewrote.

I'm using below code to replace epd_hl_update_area():

epd_clear_area_cycles(area, 1, _clear_cycle_time);
epd_hl_update_area_directly(&hl, updateMode, temperature, area);

enum EpdDrawError epd_hl_update_area_directly(EpdiyHighlevelState* state,
                                              enum EpdDrawMode     mode,
                                              int                  temperature,
                                              EpdRect              area) {
  assert(state != NULL);
  // Not right to rotate here since this copies part of buffer directly

  bool previously_white = true;
  bool previously_black = false;

  memset(state->dirty_lines + area.y, 1, area.height);

  enum EpdDrawError err;
  if (previously_white) {
    err = epd_draw_base(epd_full_screen(), state->front_fb, area,
                        MODE_PACKING_2PPB | PREVIOUSLY_WHITE | mode,
                        temperature, state->dirty_lines, state->waveform);
  }

  for (int l = area.y; l < area.y + area.height; l++) {
    if (state->dirty_lines[l] > 0) {
      uint8_t* lfb = state->front_fb + EPD_WIDTH / 2 * l;  // to
      uint8_t* lbb = state->back_fb + EPD_WIDTH / 2 * l;  // from

      for (int x = area.x; x < area.x + area.width; x++) {
        if (x % 2) {
          *(lbb + x / 2) = (*(lfb + x / 2) & 0xF0) | (*(lbb + x / 2) & 0x0F);
        } else {
          *(lbb + x / 2) = (*(lfb + x / 2) & 0x0F) | (*(lbb + x / 2) & 0xF0);
        }
      }
    }
  }
  return err;
}

This method will clear afterimages and ensure faster rendering speed, but the problem is: the area rendered by this function, will become lighter in color, when other area rendering.

I wan't to rewrite epd_hl_update_area_directly, but not works, such as set state->difference_fb[l * EPD_WIDTH + x] = ((*(lfb + x / 2) & 0x0F) << 4) | (*(lbb + x / 2) & 0x0F); will not work.

Could you tell me the wrong of this function epd_hl_update_area_directly, and how to repair it?

@vroland May you help me? I want to get the side effect of this rendering logic, and repair it.

lanistor commented 2 months ago

@martinberlin I found the problem, it was a problem with the code I rewrote. I'm using below code to replace epd_hl_update_area():

epd_clear_area_cycles(area, 1, _clear_cycle_time);
epd_hl_update_area_directly(&hl, updateMode, temperature, area);

enum EpdDrawError epd_hl_update_area_directly(EpdiyHighlevelState* state,
                                              enum EpdDrawMode     mode,
                                              int                  temperature,
                                              EpdRect              area) {
  assert(state != NULL);
  // Not right to rotate here since this copies part of buffer directly

  bool previously_white = true;
  bool previously_black = false;

  memset(state->dirty_lines + area.y, 1, area.height);

  enum EpdDrawError err;
  if (previously_white) {
    err = epd_draw_base(epd_full_screen(), state->front_fb, area,
                        MODE_PACKING_2PPB | PREVIOUSLY_WHITE | mode,
                        temperature, state->dirty_lines, state->waveform);
  }

  for (int l = area.y; l < area.y + area.height; l++) {
    if (state->dirty_lines[l] > 0) {
      uint8_t* lfb = state->front_fb + EPD_WIDTH / 2 * l;  // to
      uint8_t* lbb = state->back_fb + EPD_WIDTH / 2 * l;  // from

      for (int x = area.x; x < area.x + area.width; x++) {
        if (x % 2) {
          *(lbb + x / 2) = (*(lfb + x / 2) & 0xF0) | (*(lbb + x / 2) & 0x0F);
        } else {
          *(lbb + x / 2) = (*(lfb + x / 2) & 0x0F) | (*(lbb + x / 2) & 0xF0);
        }
      }
    }
  }
  return err;
}

This method will clear afterimages and ensure faster rendering speed, but the problem is: the area rendered by this function, will become lighter in color, when other area rendering. I wan't to rewrite epd_hl_update_area_directly, but not works, such as set state->difference_fb[l * EPD_WIDTH + x] = ((*(lfb + x / 2) & 0x0F) << 4) | (*(lbb + x / 2) & 0x0F); will not work. Could you tell me the wrong of this function epd_hl_update_area_directly, and how to repair it?

@vroland May you help me? I want to get the side effect of this rendering logic, and repair it.

It seems the problem of epd_clear_area_cycles.

vroland commented 2 months ago

Do you have an image / video of the area that gets lighter? Because to me that sounds a lot like improperly calibrated VCOM. Additionally, you could try if using the ED047TC2 waveform changes anything.

Also, why are you using your own epd_hl_update_area_directly instead of epd_hl_update_area?

lanistor commented 2 months ago

Do you have an image / video of the area that gets lighter? Because to me that sounds a lot like improperly calibrated VCOM. Additionally, you could try if using the ED047TC2 waveform changes anything.

Also, why are you using your own epd_hl_update_area_directly instead of epd_hl_update_area?

  1. I tried ED047TC2, has same behavior.
  2. I adjusted vcom to a suitable value, so it shouldn't be a problem with vcom.
  3. I use epd_clear_area_cycles + epd_hl_update_area_directly to eliminate afterimages, it will rendering black -> white -> content image, this will clear the afterimage,epd_hl_update_area cannot do this.

And, i tried not using epd_clear_area_cycles + epd_hl_update_area_directly, instead using rendering black -> white -> content image, with epd_hl_update_area, this problem disappears, so i think it may be epd_clear_area_cycles

martinberlin commented 2 months ago

Please answer this one:

Also, why are you using your own epd_hl_update_area_directly instead of epd_hl_update_area?

I also would like to understand what is the reason to use your own high level update area instead of the one that is already built in

lanistor commented 2 months ago

Please answer this one:

Also, why are you using your own epd_hl_update_area_directly instead of epd_hl_update_area?

I also would like to understand what is the reason to use your own high level update area instead of the one that is already built in

This way can eliminate afterimages (epd_clear_area_cycles + epd_hl_update_area_directly), while epd_hl_update_area cannot.

vroland commented 2 months ago

Can you show a video of both methods? I don't really see why they would behave different, so seeing the outcome would help.

lanistor commented 2 months ago

Can you show a video of both methods? I don't really see why they would behave different, so seeing the outcome would help.

Before

IMG_9951

After

IMG_9953

Video

https://github.com/lanistor/assets/blob/master/epaper/060XD4_out_paint_area.mp4

lanistor commented 2 months ago

Can you show a video of both methods? I don't really see why they would behave different, so seeing the outcome would help.

Before

IMG_9951

After

IMG_9953

Video

https://github.com/lanistor/assets/blob/master/epaper/060XD4_out_paint_area.mp4

@vroland Is this helpful for resolving the problem?

vroland commented 2 months ago

That makes it more clear, thanks. Curious, I don't really know why that is. I'll mark it as a bug for now and try to reproduce it some time. Do you have a minimal example for reproducing the problem handy?

lanistor commented 2 months ago

That makes it more clear, thanks. Curious, I don't really know why that is. I'll mark it as a bug for now and try to reproduce it some time. Do you have a minimal example for reproducing the problem handy?

I made a minimal example : https://github.com/lanistor/epaper-tester

I can reproduct problem with ED060XD4, but cannot reproduct with XC3, XCH, do you have a ED060XD4?

vroland commented 2 months ago

Argh, I think I only have an XC3... But I can try my luck.

martinberlin commented 2 months ago

I will check because Lanistor sent me 2 XD4 and still have one around so if I find it I can send it to you in next package 📦 Just checked so far have a 060XCD

lanistor commented 2 months ago

I will check because Lanistor sent me 2 XD4 and still have one around so if I find it I can send it to you in next package 📦 Just checked so far have a 060XCD

As i remembered, i sent you 2 XCD, not XD4. I could send XD4 to @vroland .

lanistor commented 2 months ago

Argh, I think I only have an XC3... But I can try my luck.

@vroland Could you give me your address? I could send you 2 XD4. Here is my email: lanistor@163.com.

lanistor commented 1 month ago

Hi, @vroland, @martinberlin,

I found another very interesting performance that will help solve this problem: when the circuit board is powered on again, it will slowly display the last rendered content. 2024-05-10 00 39 19 (1)

Here is a video of my experiment: https://github.com/lanistor/assets/blob/master/epaper/060XCD_2024-05-10.mp4

The test code for video recording is here https://github.com/lanistor/epaper-tester/tree/test/case-2, the branch is test/case-2.

And here is my experimental using case:

  1. Turn on the power and a black background will appear on the entire screen.
  2. Render the block a few times at the top, then a few times at the bottom
  3. Disconnect the power and then turn it on again. When rendering the top block, the shape of the block will appear at the bottom.
  4. Repeat the above steps, but turn the power off after 3 minutes and then turn it on again. At this time, the shape of the bottom block will fade; if you turn the power on again after 5 minutes, the shape of the bottom block will be almost invisible.
  5. If delay 2000ms between epd_clear_area_cycles and epd_hl_update_area_directly, the problem almost disappeared. The longer the time, the better the effect.
    epd_poweron();
    epd_clear_area_cycles(area, 1, _clear_cycle_time);
    vTaskDelay(pdMS_TO_TICKS(2000));
    epd_hl_update_area_directly(&hl, updateMode, temperature, area);
    epd_poweroff();
  6. I tested the brand new XCD and XCH and both had this problem; on the second-hand XD4, this problem will be more serious.

This is really a very strange behavior. The only explanation I can think of is: the electrode on the back of the screen has a weak capacitance, which stores a small amount of electricity. When the screen is powered on, these weak currents will cause the displacement of the particles. Because the second-hand screen (XD4) has been used for a long time, the capacitance will increase, causing the particle displacement to increase.

I found the problem, but I don't know how to solve it. This problem is really interesting, and it's beyond my knowledge. May you find out the way to resolve this problem?


Another test case

If call epd_push_pixels to push white color instead of calling epd_clear_area_cycles, the old area will became darker than expected. If count of white and count of black are not equal, it will aggravate the problem, but the behavior is opposite (the color becomes darker or lighter).

  // for (int i = 0; i < 10; i++) {
  //   epd_push_pixels(area, _clear_cycle_time, 0);
  // }
  for (int i = 0; i < 10; i++) {
    epd_push_pixels(area, _clear_cycle_time, 1);
  }

Test behavior: (this case branch: test/case-3 ). IMG_0007

vroland commented 1 month ago

Interesting, looks like this particular display model is very sensitive to residual charge / charge buildup. Maybe if we could dump the on-chip waveform we can fix this? Or we have to experiment with the waveforms we have. But this would be my guess that with the waveforms we have there is some imbalanced charge buildup.

lanistor commented 1 month ago

Interesting, looks like this particular display model is very sensitive to residual charge / charge buildup. Maybe if we could dump the on-chip waveform we can fix this? Or we have to experiment with the waveforms we have. But this would be my guess that with the waveforms we have there is some imbalanced charge buildup.

I got the knowledge here: https://github.com/vroland/epdiy/issues/226#issuecomment-1895893829 .

vroland commented 1 month ago

@lanistor I got your displays today, now I'm going to try replicating it when I have the time :) I already tried the display with a V7 board, there it didn't behave so different, but I'll try to reproduce it with a V5 board when I have the opportunity.

vroland commented 1 month ago

I tried to compile your example repo and I get an error with idf 4.4.5:

/home/vroland/dev/misc/epaper-tester/components/lvgl_epaper_drivers/lvgl_helpers.c: In function 'lvgl_driver_init':
/home/vroland/dev/misc/epaper-tester/components/lvgl_epaper_drivers/lvgl_helpers.h:48:25: error: 'LV_HOR_RES_MAX' undeclared (first use in this function); did you mean 'LV_HOR_RES'?

Any particular version of the dependencies I need?

martinberlin commented 1 month ago

Probably a different version of LVGL but here I packed a way to get all in a single git clone call:

https://github.com/martinberlin/lv_port_esp32-epaper/wiki/LVGL-9.0

LV_HOR_RES_MAX & LV_HOR_VER_MAX correspond if I remember well to display Width and Height.

lanistor commented 1 month ago

I tried to compile your example repo and I get an error with idf 4.4.5:

/home/vroland/dev/misc/epaper-tester/components/lvgl_epaper_drivers/lvgl_helpers.c: In function 'lvgl_driver_init':
/home/vroland/dev/misc/epaper-tester/components/lvgl_epaper_drivers/lvgl_helpers.h:48:25: error: 'LV_HOR_RES_MAX' undeclared (first use in this function); did you mean 'LV_HOR_RES'?

Any particular version of the dependencies I need?

@vroland Have you changed the version of lvgl? The default version is v8.3, and it doesn't need the LV_HOR_RES_MAX.

If using the following code to start project, it won't need LV_HOR_RES_MAX, and i can start it correctly on a new folder:

git clone git@github.com:lanistor/epaper-tester.git
cd epaper-tester
git submodule update --init --recursive

idf.py build

And idf 4.4 is OK.

vroland commented 1 month ago

Ok, now it compiles but the screen doesn't update. Maybe I'll have to try with a different board when I have the chance.