-
Notifications
You must be signed in to change notification settings - Fork 187
How pixels are driven in a parallel epaper with epdiy
This page will be created in several days, in sections, that will be slowly build while the code is analyzed. The basics have been outlined by Petteri in blog EssentialScrap I will try to describe here on my own words and research how this is done with epdiy v7 (ESP32S3 version). If you find mistakes please send me your critics, corrections or anything you want to make your point to martin(AT)cale.es
It's important to know that the Clock CKV in epdiy is controlled using the RMT driver. Relevant code section in lcd_driver.c
https://github.com/vroland/epdiy/blob/main/src/output_lcd/lcd_driver.c#L156-L162
In ESP32S3 the LCD module is used to send the parallel data using RGB LCD mode:
https://github.com/vroland/epdiy/blob/main/src/output_lcd/lcd_driver.c#L519-L526
VCOM is the common electrode on the front side of the display, and it provides the reference voltage against which the driving voltages act. This is usually printed on a small adhesive label. If it's not don't worry it's usually a value between -1.5 V and -2.5 V. This value you can set in V7 code talking to the TPS65185 via I2C using:
// sets the VCOM to -1.96 V
epd_set_vcom(1960);
The Gate and Source drivers are shift registers, all electrical power (VCC) is 3.3 volts only, except the high voltages needed:
+15 and -15 volt are used to drive the pixels (Darker or Lighter) The other two voltages that you can see that are generated by TPS65185 and a helping circuit in epdiy v7:
-20 and +22 V are to drive the Gate of each transistor pixel and let pass the +15 or -15 volt to the pixel itself.
The "Select row" driver is the easy one. Best explained in EssentialScrap:
How Gate driver works: You shift in a single "1" bit, and then "0" bits every time you want to advance to the next row. The "1" bit will shift through the register until it falls off the far end.
to start a frame, you toggle GMODE (gate output enable) high and activate SPV (start pulse vertical, active low) for one CKV clock cycle. After that you toggle CKV for each row you want to advance. It seems that the gate driver is only on while the CKV is high, and also that the on-off ratio of CKV is important. It seems necessary to have CKV off much longer than it is on, otherwise the image leaks to other rows also.
This is the driver that actually drives the pixels itself applying the higher voltages. This part is for me the critical point about why this displays are particularly hard to drive:
You shift in 800 pixels of data, 2 bits per pixel. After all the data is in the shift register, you pulse the latch enable to store it into the latch. Then you pulse the output enable when you want to write out the row.
This part that is documented with more details in Petteri's website is the one that defines how the image is displayed and particularly the grays.
Bit 1 | Bit 0 | Action |
---|---|---|
0 | 0 | No action |
0 | 1 | Darken |
1 | 0 | Lighten |
1 | 1 | No action |
You can see also this lookup table reflected in lut.h
// Make a block of 4 pixels lighter on the EPD.
#define CLEAR_BYTE 0B10101010
// Make a block of 4 pixels darker on the EPD.
#define DARK_BYTE 0B01010101
As you can see you can only "darken" or "lighten" a specific pixel. All the rest should be done with many passes (Called Frames or Phases in epdiy). For example if you apply Darken to the first row pixels 4 times and the one below only 2 times, starting from pure white, while sending a 00 to all lines below: Then the first one will be darker than the one below. The other rows will be still white.
The same to do a partial upgrade, you will shift the Gate driver to the area it needs to be updated, then send 00 (No ops) till you find the beginning of the rectangle, and send only the relevant rows to that part of the line that needs to be updated. Repeating the same action in the row below and so on till the rectangle you want to update is covered.
Where the rendering takes place: /output_lcd/render_lcd.c render uses a line queue
The line queue logic: /output_common/line_queue.c
This was answered with Valentin help since I'm not the best with the timings & driving part.
Q GATE driver - where is the part in the code that selects the row?
The row is selected by the RMT signal that is output to CKV. In the ESP32-S3, we just configure it once and then let it repeat for the number of rows:
https://github.com/vroland/epdiy/blob/main/src/output_lcd/lcd_driver.c#L681-L683
Because the LCD peripheral outputs lines at a fixed rate, we can just configure the signals in a way that they always are in sync.
Q How are the waveforms timings working in v7 S3 version?
Currently, V7 uses a static timing that is configurable per display:
https://github.com/vroland/epdiy/blob/main/src/output_lcd/lcd_driver.c#L170
I.e., it does not vary by waveform. This is how the vendor waveforms work, all phases have the same time. It's not impossible to also implement the timings with V7, I just didn't do it yet. Instead of the timings, the vendor waveforms use many cycles of black or white and use the non-linearity of in the movements of the particles to achieve the desired grayscale without varying the timings. (NOTE: Prior v7, also non S3 version, the waveforms have some timings in the data. This is not implemented so far and like this is how Waveforms work in another controllers)
Q What are values in existing waveforms that are picked up by the code, and where are this timings taking place, so we can draw using grayscales?
Only for our own, pre V7 waveforms, the waveforms are constructed in a way that the timings are doing the grayscale. There it is just the longer, the darker (or lighter). With V7, it's rather "the more times we output black, the darker it gets".
Q MODE_GC16 should somehow respect waveform matrix instructions to go from GRAY1 to GRAY2, can you please explain in more detail?
When going from gray 1 to gray 2, we have to do it by doing a specific sequence of driving white / black a specific number of times to arrive at the desired grayscale. The waveform is a big table, that tells you depending on temperature and mode, how many steps you need to take (called phases). And then, to do an update you drive a frame to the display for each phase. For each step ("phase") in this update process there is a subtable (in the code referred to as the LUT), that tells you for eacg gray1 abd gray2, what you should do in that phase for that pixel:
Drive white, black, or no-op. We do that until we completed all the phases, at which point the update is complete.
Q Even if we see it happening in all the display, really the update happens row by row, right?
Yes, it happens row by row, but when scanning the matrix we only apply a charge to the row as far as I know, and then the particles start moving much slower, that's why at the speed we are usually updating it looks like everything is moving at once.
epdiy.readthedocs.io WiKi rev. 1.0.1 Last updated Sept, 2023. Firmware & Hardware: vroland WiKi: martinberlin & collaborators