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

Last pixel per line does not change on display #326

Open dapeda42 opened 1 week ago

dapeda42 commented 1 week ago

Hi,

I think there is a bug in function 'epd_hl_update_area' in file 'highlevel.c' for updating the back framebuffer. Basically, the last pixel/byte per line is NOT copied from front to back framebuffer. Consequently, the last pixel per line does NOT change on the display/screen. This effect (last pixel per line does NOT change) started me to investigate the mechanism with front and back frame buffers and updating the back frame buffer.

Relevant lines highlevel.c: 155: diff_area.x = 0; 156: diff_area.y = 0; 157: diff_area.width = epd_width(); 158: diff_area.height = epd_height(); 159: 160: int buf_width = epd_width(); 161: 162: for (int l = diff_area.y; l < diff_area.y + diff_area.height; l++) { 163: if (state->dirty_lines[l] > 0) { 164: uint8_t lfb = state->front_fb + buf_width / 2 l; 165: uint8_t lbb = state->back_fb + buf_width / 2 l; 166: 167: int x = diff_area.x; 168: int x_last = diff_area.x + diff_area.width - 1; 169: 170: if (x % 2) { 171: (lbb + x / 2) = ((lfb + x / 2) & 0xF0) | ((lbb + x / 2) & 0x0F); 172: x += 1; 173: } 174: 175: if (x_last % 2) { 176: (lbb + x_last / 2) = ((lfb + x_last / 2) & 0x0F) | ((lbb + x_last / 2) & 0xF0); 177: x_last -= 1; 178: } 179: 180: memcpy(lbb + (x / 2), lfb + (x / 2), (x_last - x) / 2); 181: } 182: }

######### Proposals ######### My proposals are:

  1. Line 175 should be 'if (!(x_last % 2)) {', i.e. considering even values in x_last, i.e. x_last=0,2,4.
  2. Line 180 should be 'memcpy(lbb + (x / 2), lfb + (x / 2), (x_last - x + 1) / 2);', i.e. adding 1 at inner last bracket for byte count.

By the way, instead of using the modulo-operator (%) for detecting even/odd, I would use the binary-and (&). I.e., instead of 'if (x % 2)' I would use 'if( x & 2 )'. The modulo-operator usually needs a multiplication, a division and a subtraction, i.e., three assembly instructions. The binary-and will be one assembly instruction. However, I suspect that the compiler also knows about this optimization and will replace 'x % 2' by 'x & 2'.

Another question: Lines 136ff (and also 155ff) are resetting 'diff_area' to the whole area of the framebuffer for drawing the front framebuffer and updating the back framebuffer. Why not using 'diff_area' here as returned by 'epd_difference_image_cropped' in line 121?

######### Investigation ######### I investigated the update of the back framebuffer as follows, using just one line with 8 pixels.

x: coordinate. front, back: color values of pixels in front and back frame buffer. addr: address of frame buffer byte. frame buffer: lower 4 bits: even pixel (x=0,2,4), upper 4 bits: odd pixels (x=1,3,5).

Case A: All 8 pixels differ in front and back frame buffer:

x 0 1 2 3 4 5 6 7 front 0 0 | 0 0 | 0 0 | 0 0 back 15 15 | 15 15 | 15 15 | 15 15 addr 0 | 1 | 2 | 3 x_first = 0 (even) x_last = 7 (odd) N_byte = (x_last-x_first+1)/2 = (7-0+1)/2 = 4 addr_start = x_first/2 = 0 --> copy 4 bytes from front to back starting at address 0

Case B: Upper 7 pixels differ in front and back frame buffer:

x 0 1 2 3 4 5 6 7 front 15 0 | 0 0 | 0 0 | 0 0 back 15 15 | 15 15 | 15 15 | 15 15 addr 0 | 1 | 2 | 3 x_first = 1 --> copy upper 4 bits from front to back at addr x_first/2=0, and x_first ++ (x_first = 2) x_last = 7 N_byte = (x_last-x_first+1)/2 = (7-2+1)/2 = 3 addr_start = x_first/2 = 1 --> copy 3 bytes from front to back starting at address 1

Case C: Lower 7 pixels differ in front and back frame buffer:

x 0 1 2 3 4 5 6 7 front 0 0 | 0 0 | 0 0 | 0 15 back 15 15 | 15 15 | 15 15 | 15 15 addr 0 | 1 | 2 | 3 x_first = 0 x_last = 6 --> copy lower 4 bits from front to back at addr x_last/2=3, and x_last --, x_last = 5 N_byte = (x_last-x_first+1)/2 = (5-0+1)/2 = 3 addr_start = x_first/2 = 0 --> copy 3 bytes from front to back starting at address 0

vroland commented 1 week ago

Hi, good catch on that last pixel, looks like a bug indeed! Regarding the modulo operator: All modern compilers optimize this away anyway, so I'd optimize for readability. Regarding the other qeustion: Previously there was an issue on the S3-based boards with LUT calculation and alignment. I'm onto fixing that, which should enable subsequent refactoring and lifting this inefficiency.

I'm in the process of adding test cases for any new changes, which we can hopefully run in the Wokwi emulator. So your example cases are a good starting point for that :)