Created
November 30, 2019 00:18
-
-
Save joeycastillo/4cad38cc8e5cb3d5010265bc1bbd92ba to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/**************************************************************************/ | |
/*! | |
@brief Custom lookup tables for full screen updates. Public domain (?) from Waveshare. | |
@note Video explainer: https://www.youtube.com/watch?v=MsbiO8EAsGw and more info: | |
https://benkrasnow.blogspot.com/2017/10/fast-partial-refresh-on-42-e-paper.html | |
@warning YOU CAN PERMANENELY DAMAGE YOUR DISPLAY BY ABUSING THESE LOOKUP TABLES! | |
Seriously, the controller is incredibly programmable and you can force all | |
kinds of voltages and timings onto the screen that it wasn't meant to deal | |
with. TWEAK AT YOUR OWN RISK! | |
*/ | |
/**************************************************************************/ | |
/* This looks a little like the dark arts, but basically: refreshing an e-paper screen is | |
a matter of setting a sequence of voltages across the pixels on the screen and VCOM, | |
the common voltage of the electrode up top. You might hear this referred to as a | |
"waveform", in that the flashing of high and low voltages creates a waveform that | |
drives the display. When you update the screen, the controller walks through a series | |
of steps or states. The panel has some programmed in, but we can also specify our own. | |
It's not much more complicated than that: these tables are just instructions for which | |
voltages to set and when. | |
The actual voltages are set in powerUp via the power setting register; our values are: | |
* VDH: 11v | |
* VDL: -11v | |
* VCM_DC: -1.5v | |
This is the format of the LUT for the IL0398: each line in the lookup table is one | |
state, consisting of six bytes: | |
Byte 1: Voltage level select. A series of four two-bit values: | |
D7 D6 D5 D4 D3 D2 D1 D0 | |
[LS0] [LS1] [LS2] [LS3] | |
These are different for the different LUTs so I'll explain them below. | |
Byte 2: Number of frames to hold LS0. | |
Byte 3: Number of frames to hold LS1. | |
Byte 4: Number of frames to hold LS2. | |
Byte 5: Number of frames to hold LS3. | |
Byte 6: Number of times to repeat this state. | |
If you see a row full of zeroes, basically nothing happens, since it's holding the | |
desired voltage for zero frames, zero times. | |
Also note that while we technically have to send over five tables (W->W, W->B, B->W, | |
B->B, VCOM), for a full refresh there's no difference between a pixel changing color | |
and remaining the same, so I'm only including three and we send two of them twice. | |
Partial refresh lookup tables are a TODO item and will require all five to be defined. | |
*/ | |
/* For the per-pixel tables, voltage level select is as follows: | |
00b: GND | |
01b: VDH | |
10b: VDL | |
*/ | |
const unsigned char OpenBook_IL0398::LUT_W[] PROGMEM = | |
{ | |
0x40, 0x17, 0x00, 0x00, 0x00, 0x02, // state 0 | |
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, // state 1 | |
0x40, 0x0A, 0x01, 0x00, 0x00, 0x01, // state 2 | |
0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, // state 3 | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // state 4 | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // state 5 | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // state 6 | |
}; | |
// so to unpack this table (which is for pixels that will be white after the refresh): | |
// State 0: 0x40 = 0b01000000, meaning: hold the pixel at VDH for 0x17 (23) frames; repeat twice. | |
// State 1: 0x90 = 0b10010000: hold pixel at VDL for 23 frames, then at VDH for 23 frames; repeat twice. | |
// State 2: 0x40 = 0b01000000: hold pixel at VDH for 10 frames, then hold at GND for 1 frame; repeat once. | |
// State 3: 0xA0 = 0b10100000: hold pixel at VDL for 14 frames, and then do it again; repeat twice. | |
const unsigned char OpenBook_IL0398::LUT_B[] PROGMEM = | |
{ | |
0x80, 0x17, 0x00, 0x00, 0x00, 0x02, | |
0x90, 0x17, 0x17, 0x00, 0x00, 0x02, | |
0x80, 0x0A, 0x01, 0x00, 0x00, 0x01, | |
0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
}; | |
// similarly for black pixels: | |
// State 0: 0x80 = 0b10000000: hold pixel at VDL for 23 frames; repeat twice. | |
// State 1: 0x90 = 0b10010000: hold pixel at VDL for 23 frames, then at VDH for 23 frames; repeat twice. | |
// State 2: 0x80 = 0b10000000: hold pixel at VDL for 10 frames, then hold at GND for 1 frame; repeat once. | |
// State 3: 0x50 = 0b01010000: hold pixel at VDH for 14 frames, and then do it again; repeat twice. | |
/* For the VCOM lookup table, voltage level select is as follows: | |
00b: VCM_DC | |
01b: VDH+VCM_DC (VCOMH) | |
10b: VDL+VCM_DC (VCOML) | |
11b: Floating | |
*/ | |
const unsigned char OpenBook_IL0398::LUT_VCOM[] PROGMEM = | |
{ | |
0x40, 0x17, 0x00, 0x00, 0x00, 0x02, | |
0x00, 0x17, 0x17, 0x00, 0x00, 0x02, | |
0x00, 0x0A, 0x01, 0x00, 0x00, 0x01, | |
0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
// the VCOM lookup table has two extra values, which we set to 0: | |
0x00, // ST_XON: if bit (1<<state) is set, all gates are on for that state | |
0x00, // ST_CHV: if bit (1<<state) is set, VCOM high voltage is set for that state. I do not know what this means but don't even want to try it. | |
}; | |
// and just to round it out, VCOM's status: | |
// State 0: 0x40 = 0b01000000 meaning hold VCOM at VCOMH for 23 frames and repeat twice. | |
// State 1-3: for the rest of the refresh we just hold VCOM at VCM_DC. | |
// You can see where this sequence describes the flashing effect that you see on a full panel refresh: | |
// * in phase 0, VCOM is at 9.5v, white pixels are at 11v (high) and black pixels are at -11v (low). You see kind of a half and half of what's on the screen now and what you're about to display. | |
// then VCOM is set to -1.5v and: | |
// * in phase 1, all the pixels go high, and then all the pixels go low, twice. Basically coaxing the ink out of where it was and into a known state. | |
// * in phase 2, the white pixels briefly go high, and the black pixels briefly go low. You get an inverted version of what you're about to display. Then all the pixels go to ground for one frame. | |
// * Finally, in phase 3, the white pixels go low and the black pixels go high, and we hold them there for 56 frames (14*2*2). This is the final state and where the pixels end up staying. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment