//
// bb_epaper
// Written by Larry Bank (bitbank@pobox.com)
// Project started 9/11/2024
//
// SPDX-FileCopyrightText: 2024 BitBank Software, Inc.
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// bb_ep.inl
// display interfacing/control code for bb_eink library
//
#ifndef __BB_EP__
#define __BB_EP__
// forward declarations
void InvertBytes(uint8_t *pData, int bLen);
void bbepSetPixelFast4Clr(void *pb, int x, int y, unsigned char ucColor);
void bbepSetPixelFast3Clr(void *pb, int x, int y, unsigned char ucColor);
void bbepSetPixelFast2Clr(void *pb, int x, int y, unsigned char ucColor);
void bbepSetPixelFast16Clr(void *pb, int x, int y, unsigned char ucColor);
void bbepSetPixelFast4Gray(void *pb, int x, int y, unsigned char ucColor);
int bbepSetPixel4Gray(void *pb, int x, int y, unsigned char ucColor);
int bbepSetPixel4Clr(void *pb, int x, int y, unsigned char ucColor);
int bbepSetPixel3Clr(void *pb, int x, int y, unsigned char ucColor);
int bbepSetPixel2Clr(void *pb, int x, int y, unsigned char ucColor);
int bbepSetPixel16Clr(void *pb, int x, int y, unsigned char ucColor);
void bbepSetPixel2ClrDither(void *pb, int x, int y, unsigned char ucColor);

// Color mapping tables for each type of display
// the 7 basic colors (and 9 unsupported) are translated into the correct colors
// for each display type
// #define BLACK 0, WHITE 1, YELLOW 2, RED 3, BLUE 4, GREEN 5, ORANGE 6
const uint8_t u8Colors_2clr[16] = {
    BBEP_BLACK, BBEP_WHITE, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK,
    BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK
};

const uint8_t u8Colors_3clr[16] = { // red and yellow are the same for these displays (one or the other)
    BBEP_BLACK, BBEP_WHITE, BBEP_RED, BBEP_RED, BBEP_BLACK, BBEP_RED, BBEP_BLACK, BBEP_RED,
    BBEP_RED, BBEP_BLACK, BBEP_RED, BBEP_BLACK, BBEP_RED, BBEP_BLACK, BBEP_RED, BBEP_BLACK
};
// 2-bit grayscale mode (bits are inverted)
const uint8_t u8Colors_4gray[16] = {
BBEP_GRAY3, BBEP_GRAY2, BBEP_GRAY1, BBEP_GRAY0, BBEP_GRAY3, BBEP_GRAY2, BBEP_GRAY1, BBEP_GRAY0,
BBEP_GRAY3, BBEP_GRAY2, BBEP_GRAY1, BBEP_GRAY0, BBEP_GRAY3, BBEP_GRAY2, BBEP_GRAY1, BBEP_GRAY0
};

// the 4-color mode
const uint8_t u8Colors_4clr[16] = {  // black white red yellow
    BBEP_BLACK, BBEP_WHITE, BBEP_RED, BBEP_YELLOW, BBEP_BLACK, BBEP_WHITE, BBEP_RED, BBEP_YELLOW,
    BBEP_BLACK, BBEP_WHITE, BBEP_RED, BBEP_YELLOW, BBEP_BLACK, BBEP_WHITE, BBEP_RED, BBEP_YELLOW
};

// the 4-color mode (red and yellow are swapped)
const uint8_t u8Colors_4clr_v2[16] = {  // black white red yellow
    BBEP_BLACK, BBEP_WHITE, BBEP_YELLOW, BBEP_RED, BBEP_BLACK, BBEP_WHITE, BBEP_YELLOW, BBEP_RED,
    BBEP_BLACK, BBEP_WHITE, BBEP_YELLOW, BBEP_RED, BBEP_BLACK, BBEP_WHITE, BBEP_YELLOW, BBEP_RED
}; 

const uint8_t u8Colors_7clr[16] = {  // ACeP 7-color
    BBEP_BLACK, BBEP_WHITE, BBEP_YELLOW, BBEP_RED, BBEP_BLUE, BBEP_GREEN, BBEP_ORANGE, BBEP_BLACK,
    BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK
};

const uint8_t u8Colors_spectra[16] = {  // Spectra 6
    BBEP_BLACK, BBEP_WHITE, BBEP_YELLOW, BBEP_RED, 0x05, 0x06, BBEP_BLACK, BBEP_BLACK,
    BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK, BBEP_BLACK
};


const uint8_t ucMirror[256] PROGMEM =
{0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240,
    8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248,
    4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244,
    12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
    2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
    10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
    6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
    14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
    1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241,
    9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249,
    5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245,
    13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253,
    3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243,
    11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251,
    7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247,
    15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255};

const uint8_t epd35r_init_sequence_full[] PROGMEM = {
    0x01, 0x12, // SW RESET
    BUSY_WAIT,
    0x02, 0x3c, 0x05,
    0x02, 0x18, 0x80,
    0x02, 0x22, 0xb1,
    0x01, 0x20,
    BUSY_WAIT,
    0x03, 0x1b, 0x17, 0x80, // reading temp, delay 5ms
    BUSY_WAIT,
    0x02, 0x4e, 0x00, // ram counter start
    0x03, 0x4f, 0x7f, 0x01,
    0x00
};

// init sequence for GDEM0097F51
const uint8_t epd097r_init_sequence_full[] PROGMEM = {
    0x03, UC8151_PSR, 0xcf, 0x8d,
    0x06, UC8151_PWR, 0x03, 0x10, 0x3f, 0x3f, 0x0d,
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x03, UC8151_TRES, 88, 184, // resolution
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    //    0x02, 0x00, 0x0f, // LUT from OTP
    0x02, UC8151_CDI, 0x5c, // inverted, white border
    0x00
};

const uint8_t epd213r2_init_sequence_full[] PROGMEM = {
    0x02, UC8151_PSR, 0xcf,
    0x06, UC8151_PWR, 0x03, 0x10, 0x3f, 0x3f, 0x0d,
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x04, UC8151_TRES, 122, 0, 250, // resolution
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    //    0x02, 0x00, 0x0f, // LUT from OTP
    0x02, UC8151_CDI, 0x17,
    0x00
};
const uint8_t epd213r2_init_sequence_fast[] PROGMEM = {
    0x02, UC8151_PSR, 0xbf, // LUT from register (0x20 or'd with first byte)
    0x06, UC8151_PWR, 0x03, 0x00, 0x26, 0x32, 3,
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
//    0x02, UC8151_VDCS, 0x04, // VCOM DC = -0.3V
 //   0x02, UC8151_PLL, 0x3a, // PLL set to 72Hz
    0x04, UC8151_TRES, 122, 0, 250, // resolution
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    //    0x02, 0x00, 0x0f, // LUT from OTP
    0x02, UC8151_CDI, 0x17,
    45, 0x20, // VCOM LUT
    0x00, 0x1F, 0x01, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x21, // WW lut (not used in BWR mode)
    0x00, 0x1F, 0x01, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x22, // LUTR
//    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x42, 0x01, 0x1f, 0x01, 0x1f, 0x02,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x23, // LUTW
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//    0x12, 0x01, 0x10, 0x01, 0x2f, 0x02,
//    0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x24, // LUTB
    0x21, 0x01, 0x10, 0x01, 0x2f, 0x02,
    0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00
};

// init sequence for GDEW042Z15
const uint8_t epd42r2_init_sequence_full[] PROGMEM = {
    0x02, UC8151_PSR, 0xcf,
    0x06, UC8151_PWR, 0x03, 0x10, 0x3f, 0x3f, 0x0d,
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x05, UC8151_TRES, 0x01, 0x90, 1, 0x2c, // resolution
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    //    0x02, 0x00, 0x0f, // LUT from OTP
    0x02, UC8151_CDI, 0x5c, // inverted, white border
    0x00
};

const uint8_t epd1085_init_full[] PROGMEM = {
    2, 0x4d, 0x55,
    2, 0xa6, 0x38,
    2, 0xb4, 0x5d, 
    2, 0xb6, 0x80,
    2, 0xb7, 0x00,
    2, 0xf7, 0x02,
    2, 0xae, 0xa0,
    2, 0xe0, 0x01,
    3, 0x00, 0x9f, 0x0d, // panel setting
    7, 0x06, 0x57, 0x24, 0x28, 0x32, 0x08, 0x48, // boost
    5, 0x61, 0x02, 0xa8, 0x01, 0xe0, // resolution
    5, 0x62, 0x00, 0x00, 0x00, 0x00,
    2, 0x60, 0x31,
    2, 0x50, 0x97,
    2, 0xe8, 0x01,
    1, 0x04, // power on
    BUSY_WAIT,
    0
};

const uint8_t epd42r2_init_sequence_fast[] PROGMEM = {
    0x02, UC8151_PSR, 0xbf, // LUT from register (0x20 or'd with first byte)
    0x06, UC8151_PWR, 0x03, 0x00, 0x26, 0x32, 3,
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
//    0x02, UC8151_VDCS, 0x04, // VCOM DC = -0.3V
 //   0x02, UC8151_PLL, 0x3a, // PLL set to 72Hz
    0x05, UC8151_TRES, 0x01, 0x90, 1, 0x2c, // resolution
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    //    0x02, 0x00, 0x0f, // LUT from OTP
    0x02, UC8151_CDI, 0x5c, // inverted, white border
    45, 0x20, // VCOM LUT
    0x00, 0x1F, 0x01, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x21, // WW lut (not used in BWR mode)
    0x00, 0x1F, 0x01, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x22, // LUTR
    0x80, 0x1F, 0x01, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x23, // LUTW
    0x40, 0x1F, 0x01, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    43, 0x24, // LUTB
    0x00, 0x1F, 0x01, 0x00, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00
};

// init sequence for 2.13" Random screen double connector
const uint8_t EPD213R_104x212_d_init_sequence_full[] PROGMEM = {
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x02, 0x00, 0x0f, // LUT from OTP
    0x02, UC8151_CDI, 0x5c, // inverted, white border
    0x00
};

const uint8_t epd37b_init_sequence_full[] PROGMEM = {
    0x01, 0x04, // power on
    BUSY_WAIT,
    0x02, 0x00, 0x1f, // panel init
    0x00
};

const uint8_t epd37b_init_sequence_fast[] PROGMEM = {
    0x01, 0x04, // power on
    BUSY_WAIT,
    0x02, 0x00, 0x1f, // panel init
    0x02, 0xe0, 0x02,
    0x02, 0xe5, 0x5f,
    0x02, 0x50, 0xd7,
    BUSY_WAIT,
    0x00
};

const uint8_t epd37b_init_sequence_part[] PROGMEM = {
    0x01, 0x04, // power on
    BUSY_WAIT,
    0x02, 0x00, 0x1f, // panel init
    0x02, 0xe0, 0x02,
    0x02, 0xe5, 0x6e,
    0x02, 0x50, 0xd7,
    BUSY_WAIT,
    0x00
};

const uint8_t epd37_init_sequence_full[] PROGMEM = {
    0x01, UC8151_PON, // Power on
    BUSY_WAIT,
    0x02, 0x00, 0x1f, // flip x
    0x02, 0x50, 0x97, // VCOM
    0x00
};

const uint8_t epd37_init_sequence_part[] PROGMEM = {
    0x01, UC8151_PON, // Power on
    BUSY_WAIT,
    0x02, 0x00, 0x1f, // flip x
    0x02, 0xe0, 0x02,
    0x02, 0xe5, 0x6e,
    0x02, 0x50, 0xd7, // VCOM
    0x00
};

// initialization sequence for 3.7" 240x416 e-paper
const uint8_t epd37xx_init_sequence_full[] PROGMEM = {
    0x03, UC8151_PSR, 0xdf, 0x8d,
    0x06, UC8151_PWR, 0x03, 0x10, 0x3f, 0x3f, 0x0d,
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x04, UC8151_TRES, 0xf0, 1, 0xa0, // resolution
    
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    0x02, UC8151_PFS, 0x00, // FRAMES_1
    0x02, UC8151_TSE, 0x00, // TEMP_INTERNAL | OFFSET_0
    0x02, UC8151_TCON, 0x22,
    0x02, UC8151_CDI, 0xd7, // inverted, white border
    0x02, UC8151_PLL, 0x09, // HZ_50
    0x00 // end of table
};

// initialization sequence for 2.9" 296x128 e-paper
const uint8_t epd29_init_sequence_full[] PROGMEM = {
    0x02, UC8151_PSR, 0x80 | 0x00 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01, // RES_128x296, LUT_OTP, FORMAT_BW, SHIFT_LEFT, BOOSTER_ON, RESET_NONE
    0x06, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x2b,
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    0x02, UC8151_PFS, 0x00, // FRAMES_1
    0x04, UC8151_TRES, 0x80, 1, 0x28, // resolution
    0x02, UC8151_TSE, 0x00, // TEMP_INTERNAL | OFFSET_0
    0x02, UC8151_TCON, 0x22,
    0x02, UC8151_CDI, 0x9c, // inverted, white border
    0x02, UC8151_PLL, 0x3a, // HZ_100
    0x00 // end of table
};

const uint8_t epd29_init_sequence_part[] PROGMEM = {
    0x02, UC8151_PSR, 0x80 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01, // RES_128x296, LUT_REG, FORMAT_BW, SHIFT_LEFT, BOOSTER_ON, RESET_NONE
    0x06, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x03,
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x04, UC8151_TRES, 0x80, 1, 0x28, // resolution
    0x02, UC8151_PLL, 0x3a, // HZ_100
    0x02, UC8151_VDCS, 0x12, // VCOM DC setting
    0x02, UC8151_CDI, 0x1C, // white border & non-inverted partial update old->new changes
    0x2d, UC8151_LUT_VCOM, // VCOM LUT
        0x00, 0x1e, 0x00, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        ,0x00, 0x00,
    0x2b, UC8151_LUT_WW, // WW LUT
        0x80, 0x1e, 0x00, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_BW,
        0x80, 0x1e, 0x00, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_WB,
        0x40, 0x1e, 0x00, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_BB,
        0x40, 0x1e, 0x00, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_VCOM2, // VCOM2 LUT = border
        0x00, 0x1e, 0x00, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00 // end of table
};
const uint8_t epd29_init_sequence_fast[] PROGMEM = {
    0x02, UC8151_PSR, 0x80 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01, // RES_128x296, LUT_REG, FORMAT_BW, SHIFT_LEFT, BOOSTER_ON, RESET_NONE
    0x06, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x03,
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x04, UC8151_TRES, 0x80, 1, 0x28, // resolution
    0x02, UC8151_PLL, 0x3a, // HZ_100
    0x02, UC8151_VDCS, 0x12, // VCOM DC setting
    0x02, UC8151_CDI, 0x1C, // white border & non-inverted partial update old->new changes
    0x2d, UC8151_LUT_VCOM, // VCOM LUT
        0x00, 0x10, 0x2e, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        ,0x00, 0x00,
    0x2b, UC8151_LUT_WW, // WW LUT
        0x60, 0x10, 0x2e, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_BW,
        0x60, 0x10, 0x2e, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_WB,
        0x90, 0x10, 0x2e, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_BB,
        0x90, 0x10, 0x2e, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_VCOM2, // VCOM2 LUT = border
        0x00, 0x10, 0x2e, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00 // end of table
};
const uint8_t epd29_init_sequence_gray[] PROGMEM = {
    0x02, UC8151_PSR, 0x80 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01, // RES_128x296, LUT_REG, FORMAT_BW, SHIFT_LEFT, BOOSTER_ON, RESET_NONE
    0x06, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x03,
    0x04, UC8151_BTST, 0x17,0x17,0x17, // booster soft-start config - START_10MS | STRENGTH_3 | OFF_6_58US
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x04, UC8151_TRES, 0x80, 1, 0x28, // resolution
    0x02, UC8151_PLL, 0x3a, // HZ_100
    0x02, UC8151_VDCS, 0x12, // VCOM DC setting
    0x02, UC8151_CDI, 0x0c, // white border & non-inverted partial update old->new changes
    43, 0x20, // LUT 0~3 gray
      0x00, 0x1A, 0x1a, 0x1a, 0x1a, 0x01,
//      0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x60, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
      0x00, 0x13, 0x0A, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x21, // WW
//      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x99, 0x1A, 0x1a, 0x1a, 0x1a, 0x01,
      0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
      0xA0, 0x13, 0x0A, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x22, // BW
      0x99, 0x1A, 0x1a, 0x1a, 0x1a, 0x01,
//      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x00, 0x14, 0x0A, 0x00, 0x00, 0x01,
      0x99, 0x0C, 0x01, 0x02, 0x05, 0x01, // gray 2 = 10
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x23, // WB
      0x99, 0x1A, 0x1a, 0x1a, 0x1a, 0x01,
//      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x00, 0x14, 0x0A, 0x00, 0x00, 0x01, // gray 1 = 01
      0x99, 0x0B, 0x04, 0x04, 0x01, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x24, // BB
      0x99, 0x1A, 0x1a, 0x1a, 0x1a, 0x01,
//      0x80, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x20, 0x14, 0x0A, 0x00, 0x00, 0x01, // gray 3 = 11
      0x50, 0x13, 0x01, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x25, // Border LUT
      0x99, 0x1A, 0x1a, 0x1a, 0x1a, 0x01,
//      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
      0xA0, 0x13, 0x0A, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00 // end of table
};
const uint8_t epd42r_init_sequence_full[] PROGMEM = {
    0x01, 0x12, // soft reset
    BUSY_WAIT,
    0x02, 0x74, 0x54, // set analog block control
    0x02, 0x7e, 0x3b, // set digital block control
    0x03, 0x2b, 0x04, 0x63, // ACVCOM
    0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart
    0x04, 0x01, 0x2b, 0x01, 0x00, // output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x31, // RAM X start/end
    0x05, 0x45, 0,0,0x2b, 0x01, // RAM Y start/end
    0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red)
    0x02, 0x18, 0x80, // temp sensor = internal
    0x02, 0x21, 0x00, // display update ctrl 1
    0x02, 0x22, 0xb1, // display update ctrl 2
    0x01, 0x20, // master activation
    BUSY_WAIT,
    0x02, 0x4e, 0x00, // RAM X counter
    0x03, 0x4f, 0x2b, 0x01, // RAM Y counter
    0x00
};

const uint8_t epd29r2_init_sequence_full[] PROGMEM = {
    6, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x09,
    4, UC8151_BTST, 0x17, 0x17, 0x17,
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0xcf,
    2, UC8151_CDI, 0x5c, // inverted, white border
    2, UC8151_PLL, 0x29,
    2, UC8151_VDCS, 0x0a,
    BUSY_WAIT, 
    4, UC8151_TRES, 0x80, 0x01, 0x28,
    0
};
const uint8_t epd29r_init_sequence_full[] PROGMEM = {
    0x01, 0x12, // soft reset
    BUSY_WAIT,
    0x02, 0x74, 0x54, // set analog block control
    0x02, 0x7e, 0x3b, // set digital block control
    0x03, 0x2b, 0x04, 0x63, // ACVCOM
    0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart
    0x04, 0x01, 0x27, 0x01, 0x00, // output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x0f, // RAM X start/end
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01, // RAM Y start/end
    0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red)
    0x02, 0x18, 0x80, // temp sensor = internal
    0x02, 0x21, 0x00, // display update ctrl 1
    0x02, 0x22, 0xb1, // display update ctrl 2
//    0x01, 0x20, // master activation
//    BUSY_WAIT,
    0x02, 0x4e, 0x00, // RAM X counter
    0x03, 0x4f, 0x27, 0x01, // RAM Y counter
    0x00
};

const uint8_t epd26r_init_sequence_full[] PROGMEM = {
    0x01, 0x12, // soft reset
    BUSY_WAIT,
    0x02, 0x74, 0x54, // set analog block control
    0x02, 0x7e, 0x3b, // set digital block control
    0x03, 0x2b, 0x04, 0x63, // ACVCOM
    0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart
    0x04, 0x01, 0x27, 0x01, 0x00, // output control
    0x02, 0x11, 0x03, // data entry mode 
    0x03, 0x44, 0x00, 0x12, // RAM X start/end
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01, // RAM Y start/end
    0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red)
    0x02, 0x18, 0x80, // temp sensor = internal
    0x02, 0x21, 0x00, // display update ctrl 1
    0x02, 0x22, 0xb1, // display update ctrl 2
    0x01, 0x20, // master activation
    BUSY_WAIT,
    0x02, 0x4e, 0x00, // RAM X counter
    0x03, 0x4f, 0x27, 0x01, // RAM Y counter
    0x00
};

// for 122x250 BWR
const uint8_t epd213r_inky_init_sequence_full[] PROGMEM = {
    0x01, 0x12, // soft reset
    BUSY_WAIT, 
    0x02, 0x74, 0x54, // set analog block control
    0x02, 0x7e, 0x3b, // set digital block control
    0x03, 0x2b, 0x04, 0x63, // ACVCOM
    0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart 
    0x04, 0x01, 249, 0x00, 0x00, // output control
    0x02, 0x11, 0x03, // data entry mode 
    0x03, 0x44, 0x00, 0xf, // RAM X start/end
    0x05, 0x45, 0x00, 0x00, 249, 0x00, // RAM Y start/end
    0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red)
    0x02, 0x18, 0x80, // temp sensor = internal 
    0x02, 0x21, 0x00, // display update ctrl 1
    0x02, 0x22, 0xb1, // display update ctrl 2
    0x01, 0x20, // master activation
    BUSY_WAIT,
    0x02, 0x4e, 0x00, // RAM X counter
    0x03, 0x4f, 249, 0x00, // RAM Y counter
    0x00
};

// for 152x152 BWR
const uint8_t epd154r_init_sequence_full[] PROGMEM = {
    0x01, 0x12, // soft reset
    BUSY_WAIT,
    0x02, 0x74, 0x54, // set analog block control
    0x02, 0x7e, 0x3b, // set digital block control
    0x03, 0x2b, 0x04, 0x63, // ACVCOM
    0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f, // Softstart
    0x04, 0x01, 0x97, 0x00, 0x00, // output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x12, // RAM X start/end
    0x05, 0x45, 0x00, 0x00, 0x97, 0x00, // RAM Y start/end
    0x02, 0x3c, 0x01, // border (0=bk,1=wh,2=red)
    0x02, 0x18, 0x80, // temp sensor = internal
    0x02, 0x21, 0x00, // display update ctrl 1
    0x02, 0x22, 0xb1, // display update ctrl 2
    0x01, 0x20, // master activation
    BUSY_WAIT,
    0x02, 0x4e, 0x00, // RAM X counter
    0x03, 0x4f, 0x97, 0x00, // RAM Y counter
    0x00
};
const uint8_t epd75_init_sequence_partial[] PROGMEM = {
    0x06, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f, 0x03,
//    0x02, 0x82, 0x26, // VCOM DC
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x02, UC8151_PSR, 0x3f, // from register 
    0x03, UC8151_CDI, 0x29, 0x07, // VCOM Data interval
    0x02, UC8151_TCON, 0x22,
    0x2d, UC8151_LUT_VCOM, // VCOM LUT
    0x00, 30, 5, 30, 5, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    ,0x00, 0x00,
    0x2b, UC8151_LUT_WW, // WW LUT
    0x88, 30, 5, 30, 5, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_BW,
    0x88, 30, 5, 30, 5, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_WB,
    0x44, 30, 5, 30, 5, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x2b, UC8151_LUT_BB,
    0x44, 30, 5, 30, 5, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_VCOM2,
    0x00, 30, 5, 30, 5, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00 // end of table
};
const uint8_t epd75_init_sequence_fast[] PROGMEM = {
    0x06, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f, 0x03,
//    0x02, 0x82, 0x26, // VCOM DC
    0x01, UC8151_PON, // power on
    BUSY_WAIT,
    0x02, UC8151_PSR, 0x3f, // from register
    0x03, UC8151_CDI, 0x29, 0x07, // VCOM Data interval
    0x02, UC8151_TCON, 0x22,
    0x2d, UC8151_LUT_VCOM, // VCOM LUT
    0x00, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x00, 0x01, 0x1e, 0x01, 0x00, 0x01, // These need to be 00 (off)
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // and have the same number
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // of frames as the other
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUTs in this update
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_WW, // WW LUT
    0x10, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x20, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_BW,
    0x10, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x20, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_WB,
    0x20, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x10, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_BB,
    0x20, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x10, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x2b, UC8151_LUT_VCOM2,
    0x00, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x00, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    
    0x00 // end of table
};

const uint8_t epd75_init_fast_gen2[] PROGMEM = {
    0x02, 0x00, 0x1f, // panel setting
    0x03, 0x50, 0x21, 0x07,
    0x01, 0x04, // power on
    BUSY_WAIT,
    0x05, 0x06, 0x27, 0x27, 0x18, 0x17,
    0x02, 0xe0, 0x02,
    0x02, 0xe5, 0x5a,
    0
};

const uint8_t epd75_init_partial_gen2[] PROGMEM = {
    0x01, 0x04, // power on
    BUSY_WAIT,
    0x02, 0xe0, 0x02,
    0x02, 0xe5, 0x5a,
    0x02, 0x00, 0x1f, // panel setting
    0x03, 0x50, 0x21, 0x07,
    0x01, 0x04, // power on
    BUSY_WAIT,
    0x05, 0x06, 0x27, 0x27, 0x18, 0x17,
    0x02, 0xe0, 0x02,
    0x02, 0xe5, 0x6e,
    0
};

// 1.54" 152x152
const uint8_t epd154a_init_sequence_full[] PROGMEM =
{
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0xdf, // panel setting
    4, UC8151_TRES, 0x98, 0, 0x98, // resolution
    2, UC8151_CDI, 0x97, // VCOM
    0
};
const uint8_t epd154a_init_sequence_part[] PROGMEM =
{
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0x3f, // panel setting
    6, UC8151_PWR, 0x03, 0x00, 0x21, 0x21, 0x03,
    4, UC8151_TRES, 0x98, 0, 0x98, // resolution
    2, UC8151_VDCS, 0x12,
    2, UC8151_CDI, 0x17, // VCOM
    37, 0x20, 0x00, 10, 0, 60, 10, 1, // VCOM LUT
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    37, 0x21, 0x00, 10, 0, 60, 10, 1,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    37, 0x22, 0x5a, 10, 0, 60, 10, 1,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    37, 0x23, 0x84, 10, 0, 60, 10, 1,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    37, 0x24, 0x00, 10, 0, 60, 10, 1,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0
};

const uint8_t epd29yr_init_sequence_full[] PROGMEM = 
{
    0x02, 0x4d, 0x78, 
    0x03, 0x00, 0x0f, 0x29, // PSR
    0x03, 0x01, 0x07, 0x00, // PWRR
    0x04, 0x03, 0x10, 0x54, 0x44, // POFS
    0x08, 0x06, 0x05, 0x00, 0x3f, 0x0a, 0x25, 0x12, 0x1a, // BTST_P
    0x02, 0x50, 0x37, // CDI
    0x03, 0x60, 0x02, 0x02, // TCON
    0x05, 0x61, 0x00, 128, 0x01, 296-256, // TRES
    0x02, 0xe7, 0x1c, 
    0x02, 0xe3, 0x22,
    0x04, 0xb4, 0xd0, 0xb5, 0x03,
    0x02, 0xe9, 0x01,
    0x02, 0x30, 0x08,
    0x01, 0x04, // PON 
    BUSY_WAIT,
    0x00
};
const uint8_t epd29yrh_init_sequence_full[] PROGMEM = 
{
    0x02, 0x4d, 0x78,
    0x03, 0x00, 0x0f, 0x29, // PSR
    0x03, 0x01, 0x07, 0x00, // PWRR
    0x04, 0x03, 0x10, 0x54, 0x44, // POFS
    0x08, 0x06, 0x05, 0x00, 0x3f, 0x0a, 0x25, 0x12, 0x1a, // BTST_P
    0x02, 0x50, 0x37, // CDI
    0x03, 0x60, 0x02, 0x02, // TCON
    0x05, 0x61, 0x00, 168, 0x01, 384-256, // TRES
    0x02, 0xe7, 0x1c,
    0x02, 0xe3, 0x22,
    0x04, 0xb4, 0xd0, 0xb5, 0x03,
    0x02, 0xe9, 0x01,
    0x02, 0x30, 0x08,
    0x01, 0x04, // PON
    BUSY_WAIT,
    0x00
};

const uint8_t epd37yr_init_full[] PROGMEM = 
{
   0x03, 0x00, 0x0f, 0x29,
   0x07, 0x01, 0x07, 0x00, 0x22, 0x78, 0x0a, 0x22,
   0x04, 0x03, 0x10, 0x54, 0x44,
   0x04, 0x06, 0xc0, 0xc0, 0xc0,
   0x02, 0x30, 0x08,
   0x02, 0x41, 0x00,
   0x02, 0x50, 0x37,
   0x03, 0x60, 0x02, 0x02,
   0x05, UC8151_TRES, 0x00, 0xf0, 0x01, 0xa0, // resolution
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00,
   0x02, 0xe7, 0x1c,
   0x02, 0xe3, 0x22,
   0x02, 0xff, 0xa5,
   0x07, 0xef, 0x01, 0x1e, 0x0a, 0x1b, 0x0b, 0x17,
   0x02, 0xc3, 0xfd,
   0x02, 0xdc, 0x01,
   0x02, 0xdd, 0x08,
   0x02, 0xde, 0x41,
   0x02, 0xfd, 0x01,
   0x02, 0xe8, 0x03,
   0x02, 0xda, 0x07,
   0x02, 0xc9, 0x00,
   0x02, 0xa8, 0x0f,
   0x02, 0xff, 0xe3, // exit
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0x02, 0xff, 0xa5,
   0x07, 0xef, 0x03, 0x1e, 0x0a, 0x1b, 0x0e, 0x15,
   0x02, 0xdc, 0x01,
   0x02, 0xdd, 0x08,
   0x02, 0xde, 0x41,
   0x02, 0xff, 0xe3,
   0
}; 

const uint8_t epd397yr_init_full[] PROGMEM =
{           
   0x03, 0x00, 0x2b, 0x29,
   0x05, 0x06, 0x0f, 0x8b, 0x93, 0xc1,
   0x02, 0x50, 0x37,
   0x05, UC8151_TRES, 0x03, 0x20, 0x02, 168, // resolution
   0x09, 0x62, 0x76, 0x76, 0x76, 0x5a, 0x9d, 0x8a, 0x76, 0x62,
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00,
   0x02, 0xe0, 0x10,
   0x02, 0xe7, 0xa4,
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on 
   BUSY_WAIT, 
   0        
};              

const uint8_t epd397yr_init_fast[] PROGMEM =
{   
   0x03, 0x00, 0x2b, 0x29,
   0x05, 0x06, 0x0f, 0x8b, 0x93, 0xc1,
   0x02, 0x50, 0x37,
   0x02, 0x30, 0x08,
   0x05, UC8151_TRES, 0x03, 0x20, 0x02, 168, // resolution
   0x09, 0x62, 0x76, 0x76, 0x76, 0x5a, 0x9d, 0x8a, 0x76, 0x62,
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00,
   0x02, 0xe0, 0x10,
   0x02, 0xe7, 0xa4,
   0x02, 0xe9, 0x01,
   0x02, 0xef, 0x01, // start of fast mode cmds
   0x02, 0xf6, 0x20,
   0x02, 0xef, 0x00,
   0x02, 0xe0, 0x12,
   0x02, 0xe6, 92,
   0x02, 0xa5, 0x00,
   BUSY_WAIT,
   0x01, 0x04, // power on 
   BUSY_WAIT,
   0
};

const uint8_t epd35yr_init_full[] PROGMEM =
{
   0x07, 0x66, 0x49, 0x55, 0x13, 0x5d, 0x05, 0x10,
   0x02, 0x49, 0x78,
   0x03, 0x00, 0x0f, 0x29,
   0x03, 0x01, 0x07, 0x00,
   0x04, 0x03, 0x10, 0x54, 0x44,
   0x08, 0x06, 0x0f, 0x0a, 0x2f, 0x25, 0x22, 0x2e, 0x21,
   0x02, 0x50, 0x37,
   0x03, 0x60, 0x02, 0x02,
   0x05, UC8151_TRES, 0x00, 184, 0x01, 0x80, // resolution
   0x02, 0xe7, 0x1c,
   0x02, 0xe3, 0x22,
   0x02, 0xb6, 0x6f,
   0x02, 0xb4, 0xd0,
   0x02, 0xe9, 0x01,
   0x02, 0x30, 0x08,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};

const uint8_t epd35yr_init_fast[] PROGMEM =
{
   0x07, 0x66, 0x49, 0x55, 0x13, 0x5d, 0x05, 0x10,
   0x02, 0x49, 0x78,
   0x03, 0x00, 0x0f, 0x29,
   0x03, 0x01, 0x07, 0x00,
   0x04, 0x03, 0x10, 0x54, 0x44,
   0x08, 0x06, 0x0f, 0x0a, 0x2f, 0x25, 0x22, 0x2e, 0x21,
   0x02, 0x50, 0x37,
   0x03, 0x60, 0x02, 0x02,
   0x05, UC8151_TRES, 0x00, 184, 0x01, 0x80, // resolution
   0x02, 0xe7, 0x1c,
   0x02, 0xe3, 0x22,
   0x02, 0xb6, 0x6f,
   0x02, 0xb4, 0xd0,
   0x02, 0xe9, 0x01,
   0x02, 0x30, 0x08,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0x02, 0xe0, 0x02,
   0x02, 0xe6, 92,
   0x02, 0xa5, 0x00,
   BUSY_WAIT,
   0
};

const uint8_t epd42yr_init_full[] PROGMEM =
{
   0x02, 0x4d, 0x78,
   0x03, 0x00, 0x0f, 0x29,
   0x08, 0x06, 0x0d, 0x12, 0x24, 0x25, 0x12, 0x29, 0x10, // BTST
   0x02, 0x30, 0x08,
   0x02, 0x50, 0x37,
   0x05, UC8151_TRES, 0x01, 144, 0x01, 44, // resolution (400x300)
   0x02, 0xae, 0xcf,
   0x02, 0xb0, 0x13,
   0x02, 0xbd, 0x07,
   0x02, 0xbe, 0xfe,
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};

const uint8_t epd42yr_init_fast[] PROGMEM =
{
   0x02, 0x4d, 0x78,
   0x03, 0x00, 0x0f, 0x29, // PSR
   0x03, 0x01, 0x07, 0x00, // PWRR
   0x04, 0x03, 0x10, 0x54, 0x44, // POFS
   0x08, 0x06, 0x0f, 0x0a, 0x2f, 0x25, 0x22, 0x2e, 0x21, // BTST_P
   0x02, 0x50, 0x37, // CDI
   0x05, UC8151_TRES, 0x01, 144, 0x01, 44, // resolution (400x300)
   0x02, 0xe3, 0x22,
   0x02, 0xb6, 0x6f,
   0x02, 0xb4, 0xd0,
   0x02, 0xe9, 0x01,
   0x02, 0x30, 0x08,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0x02, 0xe0, 0x02,
   0x02, 0xe6, 0x5a,
   0x02, 0xa5, 0x00,
   BUSY_WAIT,
   0
};

//
// This is the 'fast' update of approx 11 seconds
// The slow (18-25 second) update is not worth implementing
//
const uint8_t epd75yr_init_full[] PROGMEM =
{   
   EPD_RESET,
   BUSY_WAIT,
   0x03, 0x00, 0x0f, 0x29,
   0x05, 0x06, 0x0f, 0x8b, 0x93, 0xa1, // BTST
   0x02, 0x41, 0x00,
   0x02, 0x50, 0x37,
   0x03, 0x60, 0x02, 0x02,
   0x05, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0, // resolution (800x480)
   0x09, 0x62, 0x98, 0x98, 0x98, 0x75, 0xca, 0xb2, 0x98, 0x7e,
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00,
   0x02, 0xe7, 0x1c,
   0x02, 0xe3, 0x00,
   0x02, 0xe9, 0x01,
   0x02, 0x30, 0x08,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0x02, 0xe0, 0x02,
   0x02, 0xe6, 0x5a,
   0x02, 0xa5, 0x00,
   BUSY_WAIT,
   0
};

const uint8_t epd215yr_init_full[] PROGMEM =
{
    0x02, 0x4d, 0x78,
    0x03, 0x00, 0x0f, 0x29,
    0x03, 0x01, 0x07, 0x00,
    0x04, 0x03, 0x10, 0x54, 0x44,
    0x08, 0x06, 0x0f, 0x0a, 0x2f, 0x25, 0x22, 0x2e, 0x21,
    0x02, 0x30, 0x02,
    0x02, 0x41, 0x00,
    0x02, 0x50, 0x37,
    0x03, 0x60, 0x02, 0x02,
    0x05, 0x61, 0x0, 160, 0x01, 40,
    0x05, 0x65, 0x00, 0x00, 0x00, 0x00,
    0x02, 0xe7, 0x1c,
    0x02, 0xe3, 0x22,
    0x02, 0xe0, 0x00,
    0x02, 0xb4, 0xd0,
    0x02, 0xb5, 0x03,
    0x02, 0xe9, 0x01,
    0x01, 0x04, // power on
    BUSY_WAIT,
    0
};

const uint8_t epd579yr_init_full[] PROGMEM =
{
   0x02, 0xe0, 0x01, // OTP
   0x02, 0xa2, 0x01, // master enable
   0x03, 0x00, 0x07, 0x29, // PSR
   0x02, 0xa2, 0x00, // close
   0x02, 0xa2, 0x02, // slave enable
   0x03, 0x00, 0x03, 0x29, // psr
   0x02, 0xa2, 0x00, // close
   0x07, 0x01, 0x07, 0x00, 0x26, 0x78, 0x24, 0x26, // PWRR
   0x04, 0x06, 0xc0, 0xc0, 0xc0, // BTST_P
   0x02, 0x50, 0x97, // white border
   0x05, 0x61, 0x01, 140, 1, 16, // TRES
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00, // GSST
   0x02, 0xe3, 0x77,
   0x02, 0xff, 0xa5, // pwm adjust
   0x09, 0xef, 1, 30, 6, 30, 14, 28, 18, 16,
   0x02, 0xdb, 0x00,
   0x02, 0xcf, 0x00,
   0x02, 0xdf, 0x00,
   0x02, 0xfd, 0x01,
   0x02, 0xe8, 0x03,
   0x02, 0xdc, 0x00,
   0x02, 0xdd, 10,
   0x02, 0xde, 60,
   0x02, 0xff, 0xe3,
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0    
};      
            
const uint8_t epd579yr_init_fast[] PROGMEM =
{
   0x02, 0xe6, 60, // fast mode
   0x02, 0xe0, 0x03,
   BUSY_WAIT,
   0x01, 0xa5,
   BUSY_WAIT,
   0x02, 0xa2, 0x01,
   0x03, 0x00, 0x07, 0x29, // PSR
   0x02, 0xa2, 0x00,
   0x02, 0xa2, 0x02,
   0x03, 0x00, 0x07, 0x29, // PSR for second controller
   0x02, 0xa2, 0x00,
   0x07, 0x01, 0x07, 0x00, 0x26, 0x78, 0x24, 0x26,
   0x04, 0x06, 0xc0, 0xc0, 0xc0,
   0x02, 0x50, 0x97, // white border
   0x05, 0x61, 0x01, 140, 1, 16, // TRES
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00, // GSST
   0x02, 0xe3, 0x77, // PWS
   0x02, 0xff, 0xa5,
   0x09, 0xef, 1, 30, 6, 30, 14, 28, 18, 16, // pwm
   0x02, 0xdb, 0x00,
   0x02, 0xcf, 0x00,
   0x02, 0xdf, 0x00,
   0x02, 0xfd, 0x01,
   0x02, 0xe8, 0x03,
   0x02, 0xdc, 0x00,
   0x02, 0xdd, 10,
   0x02, 0xde, 60,
   0x02, 0xff, 0xe3,
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};
const uint8_t epd266yr_init_full[] PROGMEM = 
{
   0x02, 0x4d, 0x78,
   0x03, 0x00, 0x0f, 0x09,
   0x07, 0x01, 0x07, 0x00, 0x22, 0x78, 0x0a, 0x22,
   0x04, 0x03, 0x10, 54, 0x44,
   0x08, 0x06, 0x0f, 0x0a, 0x2f, 0x25 ,0x22, 0x2e, 0x21,
   0x02, 0x30, 0x08,
   0x02, 0x41, 0x00,
   0x02, 0x50, 0x37,
   0x03, 0x60, 0x02, 0x02,
   0x05, UC8151_TRES, 0x00, 184, 0x01, 104, // resolution
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00,
   0x02, 0xe7, 0x1c,
   0x02, 0xe3, 0x22,
   0x02, 0xe0, 0x00,
   0x02, 0xb4, 0xd0,
   0x02, 0xb5, 0x03,
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};

const uint8_t epd266yr_init_fast[] PROGMEM = 
{
   0x02, 0x4d, 0x78,
   0x03, 0x00, 0x0f, 0x09,
   0x07, 0x01, 0x07, 0x00, 0x22, 0x78, 0x0a, 0x22,
   0x04, 0x03, 0x10, 54, 0x44,
   0x08, 0x06, 0x0f, 0x0a, 0x2f, 0x25 ,0x22, 0x2e, 0x21,
   0x02, 0x30, 0x08,
   0x02, 0x41, 0x00,
   0x02, 0x50, 0x37,
   0x03, 0x60, 0x02, 0x02,
   0x05, UC8151_TRES, 0x00, 184, 0x01, 104, // resolution
   0x05, 0x65, 0x00, 0x00, 0x00, 0x00,
   0x02, 0xe7, 0x1c,
   0x02, 0xe3, 0x22,
   0x02, 0xe0, 0x00,
   0x02, 0xb4, 0xd0,
   0x02, 0xb5, 0x03,
   0x02, 0xe9, 0x01,
   0x02, 0xe0, 0x02, // fast cmds
   0x02, 0xe6, 0x68,
   0x01, 0xa5,
   BUSY_WAIT,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};

const uint8_t epd154yr_init_full[] PROGMEM = 
{
   0x02, 0x4d, 0x78,
   0x03, 0x00, 0x0f, 0x29,
   0x08, 0x06, 0x0d, 0x12, 0x30, 0x20, 0x19, 0x2a, 0x22,
   0x02, 0x50, 0x37,
   0x05, 0x61, 0x00, 0xc8, 0x00, 0xc8,
   0x02, 0xe9, 0x01,
   0x02, 0x30, 0x08,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};

const uint8_t epd154yr_init_fast[] PROGMEM =   
{
   0x02, 0x4d, 0x78,
   0x03, 0x00, 0x0f, 0x29,
   0x08, 0x06, 0x0d, 0x12, 0x30, 0x20, 0x19, 0x2a, 0x22,
   0x02, 0x50, 0x37,
   0x05, 0x61, 0x00, 0xc8, 0x00, 0xc8,
   0x02, 0xe9, 0x01,
   0x02, 0x30, 0x08,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0x02, 0xe0, 0x02,
   0x02, 0xe6, 0x5d,
   0x02, 0xa5, 0x00,
   BUSY_WAIT,
   0
};

const uint8_t epd213yr_init_full[] PROGMEM = 
{
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};

const uint8_t epd213yr_init_fast[] PROGMEM =
{
   BUSY_WAIT,
   0x02, 0xe0, 0x02,
   0x02, 0xe6, 90,
   0x01, 0xa5,
   BUSY_WAIT,
   0x02, 0xe9, 0x01,
   0x01, 0x04, // power on
   BUSY_WAIT,
   0
};

const uint8_t epd266yr_init_sequence_full[] PROGMEM = 
{
    0x02, 0x4d, 0x78,
    0x03, 0x00, 0x0f, 0x29, // PSR
    0x03, 0x01, 0x07, 0x00, // PWRR
    0x04, 0x03, 0x10, 0x54, 0x44, // POFS
    0x08, 0x06, 0x05, 0x00, 0x3f, 0x0a, 0x25, 0x12, 0x1a, // BTST_P
    0x02, 0x50, 0x37, // CDI
    0x03, 0x60, 0x02, 0x02, // TCON
    0x05, 0x61, 0x00, 184, 0x01, 360-256, // TRES
    0x02, 0xe7, 0x1c,
    0x02, 0xe3, 0x22,
    0x04, 0xb4, 0xd0, 0xb5, 0x03,
    0x02, 0xe9, 0x01,
    0x02, 0x30, 0x08,
    0x01, 0x04, // PON
    BUSY_WAIT,
    0x00
};

// M5Paper
const uint16_t epd47_it8951_init[] PROGMEM = {
    1, IT8951_TCON_SYS_RUN,
    2, IT8951_I80CPCR, 0x0001, // enable packed write
    3, 0x0039, 0x0001, 2300, // set VCOM to -2.30v
    0
};
// 1.54" 200x200 (SSD1681) 4-gray mode
const uint8_t epd154g_init_full[] PROGMEM =
{
    0x01, 0x12, // sw reset
    BUSY_WAIT,
    0x04, 0x01, 199, 0x00, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x18,
    0x05, 0x45, 0x00, 0x00, 0xc7, 0x00,
    0x02, 0x3c, 0x04, // border waveform (white)
    0x02, 0x18, 0x80, // read temp sensor
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,

    154, 0x32, // LUT
    0x55,0x55,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0x0,0x0,0x0,0x0, // white 11
    0x55,0x55,0xaa,0xaa,0xaa,0xaa,0xaa,0x50,0x0,0x0,0x0,0x0, // gray 10
    0x55,0x55,0xaa,0xaa,0x55,0x55,0x55,0xa0,0x0,0x0,0x0,0x0, // gray 01
    0x55,0x55,0xaa,0xaa,0x55,0x55,0x55,0x50,0x0,0x0,0x0,0x0,  // black 00
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,

    BUSY_WAIT,
   0
};

// 1.54" 200x200 (SSD1681) fast 4-gray mode
const uint8_t epd154g_init_fast[] PROGMEM =
{
    0x01, 0x12, // sw reset
    BUSY_WAIT,
    0x04, 0x01, 199, 0x00, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x18,
    0x05, 0x45, 0x00, 0x00, 0xc7, 0x00,
    0x02, 0x3c, 0x04, // border waveform (white)
    0x02, 0x18, 0x80, // read temp sensor
    //    0x03, 0x21, 0x00, 0x80, // display update control
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    
    154, 0x32, // LUT
    0xaa,0xaa,0xaa,0xaa,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, // white 11
    0xaa,0xaa,0xaa,0x50,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, // gray 10
    0x55,0x55,0x55,0xa0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, // gray 01
    0x55,0x55,0x55,0x50,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,  // black 00
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x1,0x1,0x0,0x1,0x1,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
    BUSY_WAIT,
    0
};
// 1.54" 200x200
const uint8_t epd154_init_sequence_full[] PROGMEM =
{
    0x01, 0x12, // sw reset
    BUSY_WAIT,
    0x04, 0x01, 199, 0x00, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x18,
    0x05, 0x45, 0x00, 0x00, 0xc7, 0x00,
    0x02, 0x3c, 0x05, // border waveform
    0x02, 0x18, 0x80, // read temp sensor
    //    0x03, 0x21, 0x00, 0x80, // display update control
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd154_init_sequence_fast[] PROGMEM = 
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    0x02, 0x18, 0x80, // read built-in temp sensor
    0x02, 0x22, 0xb1, // load temp value
    0x01, 0x20, // execute
    BUSY_WAIT,
    0x03, 0x1a, 0x64, 0x00, // write temp register
    0x02, 0x22, 0x91, // load temp value
    0x01, 0x20, // execute
    BUSY_WAIT,
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x18,
    0x05, 0x45, 0x00, 0x00, 199, 0x00,
    0x02, 0x3c, 0x80, // border waveform
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    0

};

const uint8_t epd154_init_sequence_part[] PROGMEM =
{
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x18,
    0x05, 0x45, 0x00, 0x00, 199, 0x00,
    0x02, 0x3c, 0x80, // border waveform
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    0
};

// partial (no flash) updates
const uint8_t epd154b_init_sequence_part[] PROGMEM =
{
    0x02, 0x11, 0x03,
    0x02, 0x3c, 0x80, // border waveform
    0x03, 0x44, 0x00, 0x18,
    0x05, 0x45, 0x00, 0x00, 199, 0x00,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    
    154, 0x32, // LUT
    0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0xF,0x0,0x0,0x0,0x0,0x0,0x0,
    0x1,0x1,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x0,0x0,0x0,0x0,0x0,0x0,0x0,
    0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
    
    2, 0x3f, 0x2, // ??
    2, 0x03, 0x17, // gate voltage
    4, 0x04, 0x41, 0xb0, 0x32, // source voltage
    2, 0x2c, 0x28, // VCOM
    11, 0x37, 0,0,0,0,0,0x40,0,0,0,0, // ??
    2, 0x3c, 0x80, // VCOM ??
    
    0x00 // end of table
};

const uint8_t epd213b_init_sequence_full[] PROGMEM =
{
    0x01, 0x12, // sw reset
    BUSY_WAIT,
    0x04, 0x01, 0xf9, 0x00, 0x00,
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0xf9, 0x00,
    0x02, 0x3c, 0x05, // border waveform
    0x03, 0x21, 0x00, 0x80, // display update control
    0x02, 0x18, 0x80,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd213b_init_sequence_part[] PROGMEM =
{
    0x02, 0x11, 0x03, // data direction
    0x03, 0x44, 0x00, 0x0f, // x start/end
    0x05, 0x45, 0x00, 0x00, 0xf9, 0x00, // y start/end
    0x02, 0x3c, 0x80, // border waveform
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    0x00 // end of table
};

const uint8_t epd579_init_full[] PROGMEM =
{
   EPD_RESET,
   BUSY_WAIT,
   0x01, 0x12, // soft reset
   BUSY_WAIT,
   0x02, 0x3c, 0xc0, // set border to white
   BUSY_WAIT,
   0
};

const uint8_t epd579_init_fast[] PROGMEM =
{
   EPD_RESET,
   0x01, 0x12, // soft reset
   BUSY_WAIT,
   0x02, 0x18, 0x80,
   0x02, 0x22, 0xb1,
   0x01, 0x20,
   BUSY_WAIT,
   0x03, 0x1a, 0x64, 0x00,
   0x02, 0x22, 0x91,
   0x01, 0x20,
   BUSY_WAIT,
//   0x02, 0x3c, 0x03,
//   BUSY_WAIT,
   0x00
};

const uint8_t epd579_init_part[] PROGMEM =
{
   EPD_RESET,
   0x01, 0x12, // soft reset
   BUSY_WAIT, 
   0x02, 0x11, 0x03,
   0x02, 0x3c, 0x80,

   0x02, 0x22, 0xc0,
   0x01, 0x20,
   BUSY_WAIT,
//   0x03, 0x1a, 0x64, 0x00,
//   0x02, 0x22, 0x91,
//   0x01, 0x20,
//   BUSY_WAIT,
   0x00
};

const uint8_t epd154z_init_full[] PROGMEM = 
{
   0x01, 0x12, // soft reset
   BUSY_WAIT,
   0x02, 0x11, 0x03,
   0x02, 0x3c, 0x01,
   0x00
};

const uint8_t epd154z_init_fast[] PROGMEM =   
{
   0x01, 0x12, // soft reset
   BUSY_WAIT,
   0x02, 0x3c, 0x01,
   0x02, 0x11, 0x03,
   0x02, 0x18, 0x80,
   0x02, 0x22, 0xb1,
   0x01, 0x20,
   BUSY_WAIT,
   0x03, 0x1a, 0x5a, 0x00,
   0x02, 0x22, 0x91,
   0x01, 0x20,
   BUSY_WAIT,
   0x00
};

const uint8_t epd154z_init_part[] PROGMEM =   
{
   0x01, 0x12, // soft reset
   BUSY_WAIT, 
   0x02, 0x3c, 0x01,
   0x02, 0x11, 0x03,
   0x02, 0x18, 0x80,
   0x02, 0x22, 0xb1,
   0x01, 0x20,
   BUSY_WAIT,
   0x03, 0x1a, 0x64, 0x00,
   0x02, 0x22, 0x91,
   0x01, 0x20,
   BUSY_WAIT,
   0x00
}; 

const uint8_t epd213z_init_full[] PROGMEM = 
{
   0x01, 0x12, // soft reset
   BUSY_WAIT,
   0x04, 0x01, 0xf9, 0x00, 0x00,
   0x02, 0x11, 0x03,
   0x03, 0x44, 0x00, 0x0f,
   0x05, 0x45, 0x00, 0x00, 0xf9, 0x00,
   0x02, 0x3c, 0x01,
   BUSY_WAIT,
   0x02, 0x18, 0x80,
   0x02, 0x4e, 0x00,
   0x03, 0x4f, 0x00, 0x00,
   BUSY_WAIT,
   0 // end
}; /* epd213z_init_full */

const uint8_t epd213z_init_gray[] PROGMEM =
{
   0x01, 0x12, // soft reset
   BUSY_WAIT,
   0x04, 0x01, 0xf9, 0x00, 0x00,
   0x02, 0x011, 0x03,
   0x03, 0x44, 0x00, 0x0f,
   0x05, 0x45, 0x00, 0x00, 0xf9, 0x00,
   0x02, 0x3c, 0x01,
   BUSY_WAIT,
   0x02, 0x18, 0x80,
   0x02, 0x4e, 0x00,
   0x03, 0x4f, 0x00, 0x00,
   BUSY_WAIT,
    91, 0x32, // LUT
    0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 0
    0x96, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 1
    0x99, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 2
    0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT VCOM
    0x0a, 0x0a, 0x07, 0x02, 0x00, // phases for 0 A,B,C,D
    0x02, 0x02, 0x00, 0x00, 0x00, // phases for 1 A,B
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, // 2-9 not needed
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
   0 // end
}; /* epd213z_init_gray */

const uint8_t epd213z_init_fast[] PROGMEM = 
{   
   0x01, 0x12, // soft reset
   BUSY_WAIT,
   0x04, 0x01, 0xf9, 0x00, 0x00,
   0x02, 0x011, 0x03,
   0x03, 0x44, 0x00, 0x0f,
   0x05, 0x45, 0x00, 0x00, 0xf9, 0x00,
   0x02, 0x3c, 0x01,
   BUSY_WAIT,
   0x02, 0x18, 0x80,
   0x02, 0x4e, 0x00,
   0x03, 0x4f, 0x00, 0x00,
   BUSY_WAIT,
   0x01, 0x12, // soft reset
   0x02, 0x18, 0x80,
   0x02, 0x22, 0xb1,
   0x01, 0x20,
   BUSY_WAIT,
   0x03, 0x1a, 0x64, 0x00,
   0x02, 0x22, 0x91,
   0x01, 0x20,
   BUSY_WAIT,
   0 // end
}; /* epd213z_init_fast */

const uint8_t epd29z_init_full[] PROGMEM = 
{
    BUSY_WAIT,
    0x04, 0x01,0x27,0x01,0x00,
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x3c, 0x01,
    0x02, 0x18, 0x80,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0, // end
}; /* epd29z_init_full */

const uint8_t epd29z_init_gray_fast[] PROGMEM =
{
    BUSY_WAIT,
    0x04, 0x01,0x27,0x01,0x00,
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x3c, 0x00,
    0x02, 0x18, 0x80,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    154, 0x32, // 4-gray LUT
    0x9a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 0
    0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 2
    0x99, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 1
    0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT VCOM
    0x0a, 0x0a, 0x00, 0x07, 0x01, 0x00, 0x00, // phases for 0 A,B,C,D
    0x03, 0x01, 0x00, 0x01, 0x03, 0x00, 0x00, // phases for 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2-11 not needed
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,

    0, // end
}; /* epd29z_init_gray_fast */

const uint8_t epd29z_init_gray[] PROGMEM = 
{
    BUSY_WAIT,
    0x04, 0x01,0x27,0x01,0x00,
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x3c, 0x00,
    0x02, 0x18, 0x80,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    154, 0x32, // 4-gray LUT
    0x9a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 0
    0x96, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 2
    0x99, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 1
    0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT VCOM
    0x0a, 0x0a, 0x00, 0x07, 0x01, 0x00, 0x00, // phases for 0 A,B,C,D
    0x03, 0x01, 0x00, 0x01, 0x03, 0x00, 0x00, // phases for 1 
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2-11 not needed
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0,

    0, // end
}; /* epd29z_init_gray */

const uint8_t epd29z_init_fast[] PROGMEM = 
{
    BUSY_WAIT,
    0x04, 0x01,0x27,0x01,0x00,
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x3c, 0x01,
    0x02, 0x18, 0x80,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x02, 0x22, 0xb1,
    0x01, 0x20,
    BUSY_WAIT,
    0x03, 0x1a, 0x64, 0x00,
    0x02, 0x22, 0x91,
    0x01, 0x20,
    BUSY_WAIT,
    0, // end
}; /* epd29z_init_fast */

const uint8_t epd294_init_sequence_full[] PROGMEM =
{
    //    0x02, 0x74, 0x54,
    //    0x02, 0x7e, 0x3b,
    //    0x03, 0x2b, 0x04, 0x63,
    //    0x05, 0x0c, 0x8f, 0x8f, 0x8f, 0x3f,
    0x04, 0x01, 0x27, 0x01, 0x00,
    0x02, 0x11, 0x03,
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x03, 0x21, 0x00, 0x80,
    0x02, 0x3c, 0xc0,
    0x02, 0x18, 0x80,
    0x02, 0x22, 0xb1,
    0x01, 0x20,
    BUSY_WAIT,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    0x00 // end of table
}; /* epd294_init_sequence_full[] */

const uint8_t epd102_init_sequence_full[] PROGMEM =
{
    2, 0x00, 0x5f, // panel setting
    3, 0x2a, 0x00, 0x00, // IC hidden instructions
    1, 0x04, // power on
    BUSY_WAIT,
    2, 0x50, 0x97, // VCOM
    0
};
const uint8_t epd102_init_sequence_part[] PROGMEM =
{
    0x2, 0xd2, 0x3f,
    0x2, 0x00, 0x6f, // panel setting
    0x5, 0x01, 0x03, 0x00, 0x26, 0x26, // power
    0x2, 0x06, 0x3f,
    0x3, 0x2a, 0x00, 0x00,
    0x2, 0x30, 0x13, // 50Hz
    0x2, 0x50, 0xf2,
    0x2, 0x60, 0x22,
    0x2, 0x82, 0x12, // -0.1v
    0x2, 0xe3, 0x33,
    // send LUTs
    43, 0x23,       // white
    0x60  ,0x01 ,0x01 ,0x00 ,0x00 ,0x01 ,
    0x80  ,0x0f ,0x00 ,0x00 ,0x00 ,0x01 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    43, 0x24,       // black
    0x90  ,0x01 ,0x01 ,0x00 ,0x00 ,0x01 ,
    0x40  ,0x0f ,0x00 ,0x00 ,0x00 ,0x01 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x00  ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,
    0x1, 0x4, // power on
    BUSY_WAIT,
    0
};

const uint8_t epd122_init_sequence_full[] PROGMEM =
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    
    0x04, 0x01, 0xaf, 0x00, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x17, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xbf, 0x00,
    0x02, 0x3c, 0x05, // border waveform
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd122_init_sequence_fast[] PROGMEM =
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    
    0x02, 0x11, 0x03, // data entry mode
    0x02, 0x18, 0x80, // read built-in temp sensor
    0x02, 0x22, 0xb1, // load temp value
    0x01, 0x20, // execute
    BUSY_WAIT,
    0x03, 0x1a, 0x64, 0x00, // write temp value
    0x02, 0x22, 0x91, // load temp
    0x01, 0x20, // execute
    BUSY_WAIT,
    0x03, 0x44, 0x00, 0x17, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xaf, 0x00,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd122_init_sequence_part[] PROGMEM =
{
    0x02, 0x3c, 0x80, // border waveform
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x17, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xaf, 0x00,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd293_init_sequence_full[] PROGMEM =
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    
    0x04, 0x01, 0x27, 0x01, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x0f, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x3c, 0xc0, // border waveform
    0x03, 0x21, 0x00, 0x80, // display update control
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};
// less flashing (fast) updates
const uint8_t epd293_init_sequence_fast[] PROGMEM =
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    0x02, 0x18, 0x80, // read built-in temp sensor
    0x02, 0x22, 0xb1, // load temp value
    0x01, 0x20, // execute
    BUSY_WAIT,
    0x03, 0x1a, 0x64, 0x00, // write temp register
    0x02, 0x22, 0x91, // load temp value
    0x01, 0x20, // execute
    BUSY_WAIT,
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x0f, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

// partial (no flash) updates
const uint8_t epd293_init_sequence_part[] PROGMEM =
{
    0x02, 0x11, 0x03,
    0x02, 0x3c, 0x80, // border waveform
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    0x00 // end of table
};

const uint8_t epd296_init_sequence_part[] PROGMEM =
{
    154, 0x32, // LUT
    0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ...
    0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2,
    0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00,

    0x02, 0x3f, 0x22,
    2, SSD1608_GATE_VOLTAGE, 0x17,
    4, SSD1608_SOURCE_VOLTAGE, 0x41, 0xb0, 0x32,
    2, SSD1608_WRITE_VCOM, 0x36,
    BUSY_WAIT,
    11, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
    2, SSD1608_WRITE_BORDER, 0x80,
    0x00 // end of table
};

// partial (no flash) updates
const uint8_t epd295_init_sequence_part[] PROGMEM =
{
    0x02, 0x11, 0x03,
    0x02, 0x3c, 0x80, // border waveform
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    
    91, 0x32, // LUT
    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x0a, 0x00, 0x00, 0x00, 0x00,
    0x0a, 0x00, 0x00, 0x00, 0x00,
    0x0a, 0x00, 0x00, 0x00, 0x00,
    0x0a, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    
    0x00 // end of table
};

// 4-gray updates
const uint8_t epd295_init_gray[] PROGMEM =
{
    0x02, 0x11, 0x03,
    0x02, 0x3c, 0x80, // border waveform
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    
    91, 0x32, // LUT
    0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 0
    0x96, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 1
    0x99, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 2
    0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT VCOM
    0x0a, 0x0a, 0x07, 0x02, 0x00, // phases for 0 A,B,C,D
    0x02, 0x02, 0x00, 0x00, 0x00, // phases for 1 A,B
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, // 2-9 not needed
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00,
     
    0x00 // end of table
};

const uint8_t epd266_init_sequence_full[] PROGMEM =
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    
    0x04, 0x01, 0x27, 0x01, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x12, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x3c, 0x05, // border waveform
    0x03, 0x21, 0x00, 0x80, // display update control
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
}; /* epd266_init_sequence_full[] */

const uint8_t epd27_ws_init_sequence_full[] = {
    1, UC8151_PON,
    BUSY_WAIT,
    3, 0x00, 0x1f, 0x0b, // panel setting
    5, UC8151_TRES, 0x01, 0x08, 0x00, 0xb0,
    2, 0x50, 0x97, // VCOM
    0
};

const uint8_t epd266_init_sequence_part[] PROGMEM =
{
    0x02, 0x11, 0x03,
    0x02, 0x3c, 0x80, // border waveform
    0x03, 0x44, 0x00, 0x12,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    
    0x00 // end of table
};

const uint8_t epd27_init_sequence_full[] PROGMEM =
{
    0x04, 0x01, 0x07, 0x01, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x15, // ram address
    0x05, 0x45, 0x00, 0x00, 0x07, 0x01,
    0x02, 0x3c, 0x80, // border color
    0x02, 0x18, 0x80, // read built-in temp sensor
    0x02, 0x4e, 0x00, // ram counter x
    0x03, 0x4f, 0x00, 0x00, // ram counter y
    0x02, 0x22, 0xb1,
    0x01, 0x20,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd27_init_sequence_part[] PROGMEM =
{
    0x02, 0x11, 0x03, // data entry mode
    0x02, 0x3c, 0x80, // border color
    0x03, 0x44, 0x00, 0x15, // ram address
    0x05, 0x45, 0x00, 0x00, 0x07, 0x01,
    0x02, 0x4e, 0x00, // ram counter x
    0x03, 0x4f, 0x00, 0x00, // ram counter y
    
    0x00 // end of table
};

const uint8_t ep7_init[] PROGMEM = 
{
   1, SSD1608_SW_RESET,
   BUSY_WAIT,
   4, 0x01, 0x7f, 0x02, 0x00, // gate set
   2, 0x03, 0x00, // gate voltage control
   4, 0x04, 0x41, 0xaa, 0x32, // set source voltage
   2, 0x11, 0x03, // byte direction
   2, 0x3c, 0x01, // border control
   6, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80, // boost strength
   2, 0x18, 0x80, // enable internal temperature sensor
   11, 0x37, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, // display options
   5, 0x44, 0x00, 0x00, 0xbf, 0x03, // x dir/start
   5, 0x45, 0x00, 0x00, 0x7f, 0x02, // y dir/start
   3, 0x4e, 0x00, 0x00, // x addr start
   3, 0x4f, 0x00, 0x00, // y addr start
   BUSY_WAIT,
   0
};

const uint8_t ep7_init_partial[] PROGMEM =
{   
    0x02, 0x11, 0x03,
    0x05, 0x44, 0x00, 0x00, 0xbf, 0x03,
    0x05, 0x45, 0x00, 0x00, 0x7f, 0x02,
    0x02, 0x3c, 0x01, // border waveform
    0x03, 0x4e, 0x00, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    0
};  

const uint8_t epd31_init_full[] PROGMEM =
{
  3, 0x00, 0x1e, 0x0d, // panel setting + soft reset
  BUSY_WAIT,
  3, 0x00, 0x1f, 0x0d, // no reset
  2, 0x50, 0x97,
  1, 0x04, // power on
  BUSY_WAIT,
 0,0,0,0
};

const uint8_t epd31_init_fast[] PROGMEM =
{
  3, 0x00, 0x1e, 0x0d, // panel setting + soft reset
  BUSY_WAIT,
  3, 0x00, 0x1f, 0x0d, // no reset
  2, 0xe0, 0x02, // CCSET
  2, 0xe5, 0x5a, // TSSET - temp
  2, 0x50, 0x97,
  1, 0x04, // power on
  BUSY_WAIT,
 0,0,0,0
};

const uint8_t epd31_init_part[] PROGMEM =
{
  3, 0x00, 0x1e, 0x0d, // panel setting + soft reset
  BUSY_WAIT,
//  3, 0x00, 0x1f, 0x0d, // no reset, LUT from OTP
  3, 0x00, 0x3f, 0x0d, // no reset, LUT from registers
  2, 0xe0, 0x02, // CCSET
  2, 0xe5, 0x79, // TSSET - temp
  2, 0x50, 0xd7,
// UC8253 has a very different LUT format compared to UC8179
// group rpt, lvl+cnt_1, lvl+cnt_1, lvl+cnt_2, lvl+cnt_2, rpt_1, rpt_2
        43, UC8151_LUT_VCOM, // VCOM LUT
        0x01, 0x09, 0x01, 0x00, 0x00, 0x01, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        43, UC8151_LUT_WW, // WW LUT
        0x01, 0x89, 0x01, 0x00, 0x00, 0x01, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        43, UC8151_LUT_BW,
        0x01, 0x89, 0x01, 0x00, 0x00, 0x01, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        43, UC8151_LUT_WB,
        0x01, 0x49, 0x01, 0x00, 0x00, 0x01, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        43, UC8151_LUT_BB,
        0x01, 0x49, 0x01, 0x00, 0x00, 0x01, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  1, 0x04, // power on
  BUSY_WAIT,
 0
};

// 2-bit grayscale fast mode initialization
const uint8_t epd426g_init_fast[] PROGMEM = 
{
    0x01, SSD1608_SW_RESET, 
    BUSY_WAIT,
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x06, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80, // set soft start
    0x04, 0x01, 0xdf, 0x01, 0x02, // driver output control
    0x02, 0x3c, 0x00, // border waveform (0=white, 3=black)
    0x02, 0x11, 0x02, // data entry mode
    0x05, 0x44, 0x1f, 0x03, 0x00, 0x00, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xdf, 0x01,
    0x03, 0x4e, 0x1f, 0x03,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT, 
    106, 0x32, // VCOM LUT (105 bytes)
       0xaa,0xaa,0xaa,0xaa,0x00,0x00,0x00,0x00,0x00,0x00, // 00-09 (white 11)
       0xaa,0xaa,0xaa,0x50,0x00,0x00,0x00,0x00,0x00,0x00, // 10-19 (gray 10)
       0x55,0x55,0x55,0xa0,0x00,0x00,0x00,0x00,0x00,0x00, // 20-29 (gray 01)
       0x55,0x55,0x55,0x50,0x00,0x00,0x00,0x00,0x00,0x00, // 30-39 (black 00)
       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 40-49 (VCOM L4)
       0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x00, // 50-59
       0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x00, // 60-69
       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 70-79
       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 80-89
       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 90-99
       0x22,0x22,0x22,0x22,0x22, // 100-104 (frame rate gives a total time of 588ms)
    0x02, 0x03, 0x17, // VGH
    0x4, 0x04, 0x41, 0xa8, 0x32, // VSH1,VSH2,VSL
    0x2, 0x2c, 0x30, // VCOM voltage
    0 // end
};

// 2-bit grayscale mode initialization
const uint8_t epd426g_init[] PROGMEM = 
{
    0x01, SSD1608_SW_RESET, 
    BUSY_WAIT,
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x06, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80, // set soft start
    0x04, 0x01, 0xdf, 0x01, 0x02, // driver output control
    0x02, 0x3c, 0x00, // border waveform (0=white, 3=black)
    0x02, 0x11, 0x02, // data entry mode
    0x05, 0x44, 0x1f, 0x03, 0x00, 0x00, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xdf, 0x01,
    0x03, 0x4e, 0x1f, 0x03,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    106, 0x32, // VCOM LUT (105 bytes)
       0x55,0x55,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0x00,0x00, // 00-09 (white 11)
       0x55,0x55,0xaa,0xaa,0xaa,0xaa,0xaa,0x50,0x00,0x00, // 10-19 (gray 10)
       0x55,0x55,0xaa,0xaa,0x55,0x55,0x55,0xa0,0x00,0x00, // 20-29 (gray 01)
       0x55,0x55,0xaa,0xaa,0x55,0x55,0x55,0x50,0x00,0x00, // 30-39 (black 00)
       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 40-49 (VCOM L4)
       0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x00, // 50-59
       0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x00, // 60-69
       0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x00, // 70-79
       0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x00, // 80-89
       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 90-99
       0x22,0x22,0x22,0x22,0x22, // 100-104 (frame rate = ~1.2 seconds)
    0x02, 0x03, 0x17, // VGH
    0x4, 0x04, 0x41, 0xa8, 0x32, // VSH1,VSH2,VSL
    0x2, 0x2c, 0x30, // VCOM voltage
    0 // end
}; // 2-bit grayscale mode

// 1-bit full update mode
const uint8_t epd426_init_full[] PROGMEM =
{   
    0x01, SSD1608_SW_RESET, 
    BUSY_WAIT,
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x06, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80, // set soft start
    0x04, 0x01, 0xdf, 0x01, 0x02, // driver output control
//    0x03, 0x21, 0x40, 0x00, // display update control
    0x02, 0x3c, 0x01, // border waveform
    0x02, 0x11, 0x02, // data entry mode
    0x05, 0x44, 0x1f, 0x03, 0x00, 0x00, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xdf, 0x01,
    0x03, 0x4e, 0x1f, 0x03,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT, 
    0x00 // end of table
};

// 1-bit fast update mode
const uint8_t epd426_init_fast[] PROGMEM =
{   
    0x01, SSD1608_SW_RESET, 
    BUSY_WAIT,
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x06, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80, // set soft start
    0x04, 0x01, 0xdf, 0x01, 0x02, // driver output control
    0x02, 0x3c, 0x01, // border waveform
    0x02, 0x11, 0x02, // data entry mode
    0x05, 0x44, 0x1f, 0x03, 0x00, 0x00, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xdf, 0x01,
    0x03, 0x4e, 0x1f, 0x03,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
// secret sauce for doing a 1.5s update
    0x02, 0x1a, 0x5a, // temp?
    0x02, 0x22, 0x91, // ??
    0x01, 0x20, // ??
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd42b_init_gray[] PROGMEM = 
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
   
    0x04, 0x01, 0x2b, 0x01, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x31, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0x2b, 0x01,
    0x02, 0x3c, 0x00, // border waveform
    0x02, 0x18, 0x80, // read built-in temp sensor
    229, 0x32, // custom LUT for SSD1683
    // 0: RP (group repeat count)
    // 1-4: 2-bit push code, 6-bit frame count
    // 5: SR repeat count for phases A/B
    // 6: SR repeat count for phases C/D
    // LUTC
    0x01, 0x10, 0x10, 0x0a, 0x07, 0x01, 0x01, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTWW
    0x01, 0x50, 0x90, 0x4a, 0x47, 0x01, 0x01, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTWB (10)
    0x01, 0x50, 0x90, 0x8f, 0x42, 0x01, 0x01, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTBW (01)
    0x01, 0x50, 0x90, 0x4f, 0x82, 0x01, 0x01, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTBB (00)
    0x01, 0x50, 0x90, 0x8a, 0x87, 0x01, 0x01, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unused
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unused
    0x02, // framerate of 100Hz
    0x00, 0x00, // XON (normal)
    0x22, // EOPT = display off sequence of to 2 scan frames to discharge TFT
    BUSY_WAIT,

    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};
const uint8_t epd42b_init_gray_fast[] PROGMEM = 
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    
    0x04, 0x01, 0x2b, 0x01, 0x00, // driver output control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x31, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0x2b, 0x01,
    0x02, 0x3c, 0x00, // border waveform
    0x02, 0x18, 0x80, // read built-in temp sensor
    229, 0x32, // custom LUT for SSD1683
    // 0: RP (group repeat count)
    // 1-4: 2-bit push code, 6-bit frame count
    // 5: SR repeat count for phases A/B
    // 6: SR repeat count for phases C/D
    // LUTC
    0x01, 0x0a, 0x07, 0x00, 0x00, 0x01, 0x00, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTWW
    0x01, 0x4a, 0x47, 0x00, 0x00, 0x01, 0x00, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTWB (10)
    0x01, 0x8f, 0x42, 0x00, 0x00, 0x01, 0x00, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTBW (01)
    0x01, 0x4f, 0x82, 0x00, 0x00, 0x01, 0x00, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    // LUTBB (00)
    0x01, 0x8a, 0x87, 0x00, 0x00, 0x01, 0x00, // group 0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 1
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 2
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 3
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 4
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // group 5
    
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unused
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unused
    0x02, // framerate of 100Hz
    0x00, 0x00, // XON (normal)
    0x22, // EOPT = display off sequence of to 2 scan frames to discharge TFT
    BUSY_WAIT,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd42b_init_sequence_full[] PROGMEM =
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    
    0x04, 0x01, 0x2b, 0x01, 0x00, // driver output control
    0x03, 0x21, 0x40, 0x00, // display update control
    0x02, 0x11, 0x03, // data entry mode
    0x03, 0x44, 0x00, 0x31, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0x2b, 0x01,
    0x02, 0x3c, 0x05, // border waveform
    0x02, 0x18, 0x80, // read built-in temp sensor
    
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0x00 // end of table
};

const uint8_t epd42b_init_sequence_fast[] PROGMEM =
{
    0x01, SSD1608_SW_RESET,
    BUSY_WAIT,
    3, 0x21, 0x40, 0x00,
    2, 0x3c, 0x05,
    2, 0x1a, 0x6e, // temp register
    2, 0x22, 0x91, // load temp
    1, 0x20,
    BUSY_WAIT,
    2, 0x11, 0x3, // data entry mode
    0x03, 0x44, 0x00, 0x31, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0x2b, 0x01,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    BUSY_WAIT,
    0
};

const uint8_t epd42b_init_sequence_part[] PROGMEM =
{
    0x03, 0x21, 0x00, 0x00,
    0x02, 0x11, 0x03, // data entry mode
    0x02, 0x3c, 0x80, // border color
    0x03, 0x44, 0x00, 0x31, // ram address
    0x05, 0x45, 0x00, 0x00, 0x2b, 0x01,
    0x02, 0x4e, 0x00, // ram counter x
    0x03, 0x4f, 0x00, 0x00, // ram counter y
    0x00 // end of table
};

const uint8_t epd426_init_part[] PROGMEM =
{
    0x03, 0x21, 0x00, 0x00,
    0x02, 0x3c, 0x80, // border color
    0x02, 0x11, 0x02, // data entry mode
    0x05, 0x44, 0x1f, 0x03, 0x00, 0x00, // ram start/end
    0x05, 0x45, 0x00, 0x00, 0xdf, 0x01,
    0x03, 0x4e, 0x1f, 0x03,
    0x03, 0x4f, 0x00, 0x00,

    0x00 // end of table
};

const uint8_t epd29b_init_sequence_full[] PROGMEM = {
    1, SSD1608_SW_RESET,
    BUSY_WAIT,
    2, 0x74, 0x54,
    2, 0x7e, 0x3b,
    4, 0x01, 39, 1, 0, // gate setting (height -1)
    2, 0x11, 0x3, // data entry mode
    2, 0x3c, 0x03, // black border
    2, 0x2c, 0x55, // VCOM
    2, 0x03, 0x17, // gate drive voltage
    4, 0x04, 0x41, 0xac, 0x32, // source driving voltage
    2, 0x3a, 0x07, // dummy line period
    2, 0x3b, 0x04, // get line width
    2, 0x4e, 0x00, // ram x start
    3, 0x4f, 0x00, 0x00, // ram y start
    BUSY_WAIT,
    0
};
const uint8_t epd213_104x212_init_sequence_full[] PROGMEM = {
    1, SSD1608_SW_RESET,
    BUSY_WAIT,
    2, 0x74, 0x54,
    2, 0x7e, 0x3b,
    4, 0x01, 211, 0, 0, // gate setting (height -1)
    2, 0x11, 0x3, // data entry mode
    2, 0x3c, 0x03, // black border
    2, 0x2c, 0x55, // VCOM
    2, 0x03, 0x17, // gate drive voltage
    4, 0x04, 0x41, 0xac, 0x32, // source driving voltage
    2, 0x3a, 0x07, // dummy line period
    2, 0x3b, 0x04, // get line width
    2, 0x4e, 0x00, // ram x start
    3, 0x4f, 0x00, 0x00, // ram y start
    BUSY_WAIT,
    0
};

const uint8_t epd213_122x250_init_sequence_full[] PROGMEM = {
    1, SSD1608_SW_RESET,
    BUSY_WAIT,
    2, 0x74, 0x54,
    2, 0x7e, 0x3b,
    4, 0x01, 249, 0, 0, // gate setting (height -1)
    2, 0x11, 0x3, // data entry mode
    2, 0x3c, 0x03, // black border
    2, 0x2c, 0x55, // VCOM
    2, 0x03, 0x15, // gate drive voltage
    4, 0x04, 0x41, 0xa8, 0x32, // source driving voltage
    2, 0x3a, 0x30, // dummy line period
    2, 0x3b, 0x0a, // get line width
    2, 0x4e, 0x00, // ram x start
    3, 0x4f, 0x00, 0x00, // ram y start
    BUSY_WAIT,
    0
};

const uint8_t epd213_122x250_init_sequence_part[] PROGMEM = {
    2, 0x3c, 0x80, // border
    2, 0x11, 0x3, // data entry mode
    3, 0x44, 0x00, 0xf,
    5, 0x45, 0x00, 0x00, 0xf9, 0x00,
    2, 0x4e, 0x00, // ram x start
    3, 0x4f, 0x00, 0x00, // ram y start
    
    0
};
//const uint8_t epd75_init_sequence_fast[] PROGMEM = {
//    2, 0x00, 0x1f, // panel setting
//    3, 0x50, 0x10, 0x07, // VCOM
//    1, 0x04, // Power On
//    BUSY_WAIT,
//    5, 0x06, 0x27, 0x27, 0x18, 0x17, // booster soft start
//    2, 0xe0, 0x02,
//    2, 0xe5, 0x5a,
//    0
//};

const uint8_t epd583_init_sequence_full[] PROGMEM = {
    2, UC8151_PSR, 0x9f,
    6, UC8151_PWR, 0x03, 0x00, 0x2b, 0x2b, 0x2b,
    1, UC8151_PON,
    BUSY_WAIT,
    4, UC8151_BTST, 0x17, 0x17, 0x17,
    2, UC8151_PFS, 0x00,
    2, UC8151_TSE, 0x00,
    2, UC8151_TCON, 0x22,
    2, UC8151_CDI, 0xd7,
    2, UC8151_PLL, 0x3a,
    5, UC8151_TRES, 0x02, 0x88, 0x01, 0xe0,
    2, UC8151_VDCS, 0x12,
    0
};

const uint8_t epd42_init_sequence_full[] PROGMEM = {
    1, UC8151_PON,
    BUSY_WAIT,
    3, 0x00, 0x1f, 0x0b, // panel setting
    5, UC8151_TRES, 0x01, 0x90, 0x01, 0x2c,
    2, 0x50, 0x97, // VCOM
    0
};

const uint8_t epd42_init_sequence_part[] PROGMEM = {
    3, 0x00, 0x1f, 0x0b, // panel setting
//    2, 0x50, 0x97, // VCOM
    5, 0x01, 0x03,0x00,0x2b,0x2b, // power setting
    4, 0x06, 0x17,0x17,0x17, // boost soft start
    1, 0x04, // power on - this takes 12ms on the 4.2" 400x300
    BUSY_WAIT,
    3, 0x00, 0xbf, 0x0b, // panel setting
    2, 0x30, 0x3c, // 3A 100HZ
    5, 0x61, 0x01, 0x90, 0x01, 0x2c, // resolution 400x300
    2, 0x82, 0x12, // vcom_DC
    2, 0x50, 0xB7, // mode
    MAKE_LUTS, // dynamically generate partial update LUTS
    0x00 // end of table
};

const uint8_t epd42_init_sequence_fast[] PROGMEM = {
    1, UC8151_PON,
    BUSY_WAIT,
    3, 0x00, 0x1f, 0x0b, // panel setting
//    2, 0x50, 0x97, // VCOM
    5, 0x01, 0x03,0x00,0x2b,0x2b, // power setting
    4, 0x06, 0x17,0x17,0x17, // boost soft start
    1, 0x04, // power on
    BUSY_WAIT,
    3, 0x00, 0xbf, 0x0b, // panel setting
    2, 0x30, 0x3c, // 3A 100HZ
    5, 0x61, 0x01, 0x90, 0x01, 0x2c, // resolution 400x300
    2, 0x82, 0x12, // vcom_DC
    2, 0x50, 0xB7, // mode
    45, 0x20, // VCOM LUT
    0x00, 0x01, 0x20, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x21, // WW LUT
    0x10, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x20, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x22, // BW LUT
    0x10, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x20, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x23, // WB LUT
    0x20, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x10, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x24, // BB LUT
    0x20, 0x01, 0x10, 0x01, 0x00, 0x01,
    0x10, 0x01, 0x1e, 0x01, 0x00, 0x01,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    
    0x00 // end of table
};

const uint8_t epd294_init_sequence_part[] =
{
    0x0b, 0x37, 0,0,0,0,0,0x40,0,0,0,0,
    0x04, 0x01, 0x27, 0x01, 0x00,
    0x02, 0x11, 0x01,
    0x03, 0x44, 0x00, 0x0f,
    0x05, 0x45, 0x00, 0x00, 0x27, 0x01,
    0x03, 0x21, 0x00, 0x80,
    0x02, 0x3c, 0xc0,
    0x02, 0x22, 0xc0,
    0x01, 0x20,
    BUSY_WAIT,
    0x02, 0x4e, 0x00,
    0x03, 0x4f, 0x00, 0x00,
    0x00 // end of sequence
}; /* epd294_init_sequence_part */

const uint8_t epd213_inky_init_sequence_full[] PROGMEM = {
    5, UC8151_PWR, 0x07, 0x00, 0x0a, 0x00,
    4, UC8151_BTST, 0x07, 0x07, 0x07,
    1, UC8151_PON, // power on
    BUSY_WAIT,
    2, UC8151_PSR, 0xcf,
    2, 0x50, 0x07,
    2, 0x30, 0x29,
    4, UC8151_TRES, 104, 0, 212,
    2, 0x82, 0x0a,
    
    0
};

// fast 2-bit (4 grayscale mode) for GDEY075T7 (older model)
const uint8_t epd75_old_gray_init_fast[] PROGMEM = {
    6, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f, 0x03,
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0x3f,
    5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0,
    2, 0x15, 0x00,
    3, UC8151_CDI, 0x00, 0x07,
    2, UC8151_TCON, 0x22,
    3, 0x50, 0x00, 0x07, // VCOM LUTBD (00=white border, 30=black border)
    2, UC8151_VDCS, 0x1c, // VCOM DC
// Each row of the LUT has 6 bytes with the following meanings:
// Byte 0 = 4 2-bit patterns (MSB first), 00=neutral, 01=black 10=white
// Bytes 1-4 = counts for each pattern
// Byte 5 = repeat count for the whole sequence
    43, 0x20, // VCOM LUT
      0x00, 0x22, 0x04, 0x04, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x21, // WW (GRAY3)
      0xa0, 0x22, 0x04, 0x04, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x22, // BW (GRAY2)
      0x60, 0x23, 0x04, 0x03, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x23, // WB (GRAY1)
      0x90, 0x20, 0x09, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x24, // BB (GRAY0)
      0x50, 0x22, 0x04, 0x04, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x25, // Border LUT
      0xa0, 0x22, 0x04, 0x04, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    2, 0xe0, 0x00, // CCSET
    2, 0x41, 0x00, // TSE
    1, 0x04, // Power on
    BUSY_WAIT,
    0 // end
};

// fast 2-bit (4 grayscale mode) for GDEY075T7-D2 (newer model)
const uint8_t epd75_new_gray_init_fast[] PROGMEM = {
    6, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f, 0x03,
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0x3f,
    5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0,
    2, 0x15, 0x00,
    3, UC8151_CDI, 0x00, 0x07,
    2, UC8151_TCON, 0x22,
    3, 0x50, 0x00, 0x07, // VCOM LUTBD (00=white border, 30=black border)
    2, UC8151_VDCS, 0x12, // VCOM DC
// Each row of the LUT has 6 bytes with the following meanings:
// Byte 0 = 4 2-bit patterns (MSB first), 00=neutral, 01=black 10=white
// Bytes 1-4 = counts for each pattern
// Byte 5 = repeat count for the whole sequence
    43, 0x20, // VCOM LUT
      0x00, 0x12, 0x04, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x21, // WW (GRAY3)
      0x80, 0x12, 0x04, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x22, // BW (GRAY1)
      0x90, 0x11, 0x05, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x23, // WB (GRAY2)
      0x60, 0x11, 0x05, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x24, // BB (GRAY0)
      0x40, 0x12, 0x04, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x25, // Border LUT
      0xa0, 0x12, 0x04, 0x01, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    2, 0xe0, 0x00, // CCSET
    2, 0x41, 0x00, // TSE
    1, 0x04, // Power on
    BUSY_WAIT,
    0 // end
};

// 2-bit (4 grayscale mode) for GDEY075T7 (older model)
const uint8_t epd75_old_gray_init[] PROGMEM = {
    6, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f, 0x03,
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0x3f,
    5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0,
    2, 0x15, 0x00,
    3, UC8151_CDI, 0x00, 0x07,
    2, UC8151_TCON, 0x22,
    3, 0x50, 0x00, 0x07, // VCOM LUTBD (00=white border, 30=black border)
    2, UC8151_VDCS, 0x12, // VCOM DC
    43, 0x20, // LUT 0~3 gray
      0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x00, 0x14, 0x14, 0x0a, 0x00, 0x01,
      0x00, 0x19, 0x0f, 0x00, 0x00, 0x01,
      0x00, 0x20, 0x01, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x21, // WW
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x0a, 0x00, 0x01,
      0x20, 0x14, 0x0A, 0x0a, 0x00, 0x01,
      0xA0, 0x13, 0x0A, 0x04, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x22, // BW
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x19, 0x19, 0x00, 0x00, 0x01,
      0x10, 0x19, 0x0F, 0x00, 0x00, 0x01,
      0x99, 0x11, 0x04, 0x06, 0x06, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x23, // WB
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x19, 0x19, 0x00, 0x00, 0x01,
      0x10, 0x19, 0x0F, 0x00, 0x00, 0x01,
      0x99, 0x10, 0x06, 0x08, 0x03, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x24, // BB
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x00, 0x12, 0x12, 0x0e, 0x00, 0x01,
      0x40, 0x12, 0x16, 0x00, 0x00, 0x01,
      0x50, 0x23, 0x01, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x25, // Border LUT
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
      0xA0, 0x13, 0x0A, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    2, 0xe0, 0x00, // CCSET
    2, 0x41, 0x00, // TSE
    1, 0x04, // Power on
    BUSY_WAIT,
    0 // end
};

// 2-bit (4 grayscale mode) for GDEY075T7 (older model with too light output)
const uint8_t epd75_old_gray_init2[] PROGMEM = {
    6, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f, 0x03,
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0x3f,
    5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0,
    2, 0x15, 0x00,
    3, UC8151_CDI, 0x00, 0x07,
    2, UC8151_TCON, 0x22,
    3, 0x50, 0x00, 0x07, // VCOM LUTBD (00=white border, 30=black border)
    2, UC8151_VDCS, 0x12, // VCOM DC
    43, 0x20, // LUT 0~3 gray
      0x00, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x00, 0x14, 0x14, 0x0a, 0x00, 0x01,
      0x00, 0x19, 0x0f, 0x00, 0x00, 0x01,
      0x00, 0x20, 0x01, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x21, // WW
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x0a, 0x00, 0x01,
      0x20, 0x14, 0x0A, 0x0a, 0x00, 0x01,
      0xA0, 0x13, 0x0A, 0x04, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x22, // BW
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x19, 0x19, 0x00, 0x00, 0x01,
      0x10, 0x19, 0x0F, 0x00, 0x00, 0x01,
      0x99, 0x11, 0x04, 0x06, 0x06, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x23, // WB
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x19, 0x19, 0x00, 0x00, 0x01,
      0x10, 0x19, 0x0F, 0x00, 0x00, 0x01,
      0x99, 0x06, 0x10, 0x08, 0x03, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x24, // BB
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x00, 0x12, 0x12, 0x0e, 0x00, 0x01,
      0x40, 0x12, 0x16, 0x00, 0x00, 0x01,
      0x50, 0x23, 0x01, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    43, 0x25, // Border LUT
      0x40, 0x0A, 0x00, 0x00, 0x00, 0x01,
      0x90, 0x14, 0x14, 0x00, 0x00, 0x01,
      0x10, 0x14, 0x0A, 0x00, 0x00, 0x01,
      0xA0, 0x13, 0x0A, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    2, 0xe0, 0x00, // CCSET
    2, 0x41, 0x00, // TSE
    1, 0x04, // Power on
    BUSY_WAIT,
    0 // end
};

// 2-bit (4 grayscale mode)
const uint8_t epd75_gray_init[] PROGMEM = {
    2, 0x00, 0x1f, // panel setting
    3, 0x50, 0x90, 0x07, // VCOM (bit 7 set disables the border color - stays white in our case)
    1, UC8151_PON, // power on
    BUSY_WAIT,
    5, 0x06, 0x27, 0x27, 0x18, 0x17, // booster soft start
    2, 0xe0, 0x02, // 1.5s
    2, 0xe5, 0x5f, // 4gray mode
    0
}; // 2-bit grayscale mode

const uint8_t epd397g_init_full[] PROGMEM = {
    1, 0x12, // soft reset
    BUSY_WAIT, 
    2, 0x18, 0x80, // enable temp sensor
    6, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80,
    4, 0x01, 0xdf, 0x01, 0x02, // driver output control (height-1)
    2, 0x3c, 0x01, // border color
    2, 0x11, 0x01, // data entry mode
    5, 0x44, 0x00, 0x00, 0x1f, 0x03, // set RAM x start/end
    5, 0x45, 0xdf, 0x01, 0x00, 0x00, // set RAM y start/end
    3, 0x4e, 0x00, 0x00, // RAM x address
    3, 0x4f, 0x00, 0x00, // RAM y address
    BUSY_WAIT,
    2, 0x1a, 0x5a, // 4-gray mode
    0 // end
};

const uint8_t epd397_init_full[] PROGMEM = {
    1, 0x12, // soft reset
    BUSY_WAIT,
    2, 0x18, 0x80, // enable temp sensor
    6, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80,
    4, 0x01, 0xdf, 0x01, 0x02, // driver output control (height-1)
    2, 0x3c, 0x01, // border color
    2, 0x11, 0x01, // data entry mode
    5, 0x44, 0x00, 0x00, 0x1f, 0x03, // set RAM x start/end
    5, 0x45, 0xdf, 0x01, 0x00, 0x00, // set RAM y start/end
    3, 0x4e, 0x00, 0x00, // RAM x address
    3, 0x4f, 0x00, 0x00, // RAM y address
    BUSY_WAIT,
    0 // end
};

const uint8_t epd397_init_fast[] PROGMEM = {
    1, 0x12, // soft reset
    BUSY_WAIT,
    6, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80,
    4, 0x01, 0xdf, 0x01, 0x02, // driver output control (height-1)
    2, 0x3c, 0x01, // border color
    2, 0x11, 0x01, // data entry mode
    5, 0x44, 0x00, 0x00, 0x1f, 0x03, // set RAM x start/end
    5, 0x45, 0xdf, 0x01, 0x00, 0x00, // set RAM y start/end
    3, 0x4e, 0x00, 0x00, // RAM x address
    3, 0x4f, 0x00, 0x00, // RAM y address
    BUSY_WAIT,
    2, 0x18, 0x80,
    2, 0x1a, 0x6a, // fast mode (1.5s)
    0 // end
};

const uint8_t epd397_init_part[] PROGMEM = {
    1, 0x12, // soft reset
    BUSY_WAIT,
    2, 0x18, 0x80,
    6, 0x0c, 0xae, 0xc7, 0xc3, 0xc0, 0x80,
    4, 0x01, 0xdf, 0x01, 0x02, // driver output control (height-1)
    2, 0x3c, 0x80, // border color
    2, 0x11, 0x01, // data entry mode
    5, 0x44, 0x00, 0x00, 0x1f, 0x03, // set RAM x start/end
    5, 0x45, 0xdf, 0x01, 0x00, 0x00, // set RAM y start/end
    3, 0x4e, 0x00, 0x00, // RAM x address
    3, 0x4f, 0x00, 0x00, // RAM y address
    BUSY_WAIT,
    0 // end
};

const uint8_t epd75_init_sequence_full[] PROGMEM = {
    5, UC8151_PWR, 0x07, 0x07, 0x3f, 0x3f,
    1, UC8151_PON,
    BUSY_WAIT,
    2, UC8151_PSR, 0x1f,
    5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0,
    2, 0x15, 0x00, 
    3, UC8151_CDI, 0x21, 0x07,
    2, UC8151_TCON, 0x22,
    0
};

const uint8_t epd74r_init[] PROGMEM = {
    0x02, 0x65, 0x01,
    0x01, 0xab,
    0x02, 0x65, 0x00,
    0x01, 0x04,
    BUSY_WAIT,
    0x03, 0x01, 0x37, 0x00, // 0x05, 0x05,
    0x03, 0x00, 0xcf, 0x08,
//    0x02, 0xe5, 0x03,
//    0x02, 0x03, 0x00,
    0x04, 0x06, 0xc7, 0xcc, 0x28,
    0x02, 0x30, 0x3c,
    0x02, 0x41, 0x00,
    0x02, 0x50, 0x77,
    0x02, 0x60, 0x22,
    0x05, 0x61, 0x02, 0x80, 0x01, 0x80,
//    0x02, 0x65, 0x01,
//    0x02, 0x65, 0x00,
//    0x01, 0x40,
//    BUSY_WAIT,
//    0x02, 0x8d, 0x80,
    0x02, 0x82, 0x1e,
    0x02, 0xe5, 0x03,
//    0x01, 0x02,
//    BUSY_WAIT,
    0x01, 0x04,
    BUSY_WAIT,
//    0x02, 0x65, 0x01,
//    0x02, 0x65, 0x00,
//    0x01, 0x40,
//    BUSY_WAIT,
//    0x02, 0x8d, 0xc0,
//    0x02, 0x30, 0x2a,
    0x00
};

// GDEY073D46 800x480 7-color init sequence
const uint8_t epd73_init[] PROGMEM = {
    7, 0xaa, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18, // CMD H
    7, UC8151_PWR, 0x3f, 0x00, 0x32, 0x2a, 0x0e, 0x2a, // power setting
    3, UC8151_PSR, 0x5f, 0x69, // panel setting
    5, UC8151_PFS, 0x00, 0x54, 0x00, 0x44,
    5, 0x05, 0x40, 0x1f, 0x1f, 0x2c, // BTST1
    5, 0x06, 0x6f, 0x1f, 0x1f, 0x25, // BTST2
    5, 0x08, 0x6f, 0x1f, 0x1f, 0x22, // BTST3
    3, 0x13, 0x00, 0x04, // IPC
    2, 0x30, 0x3c,
//    2, UC8151_PLL, 0x02,
    2, UC8151_TSE, 0x00,
    2, UC8151_CDI, 0x3f,
    3, UC8151_TCON, 0x02, 0x00,
    5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0, // resolution 800x480
    2, UC8151_VDCS, 0x1e,
    2, 0x84, 0x00, // T_VCDS
    2, 0x86, 0x00, // AGID
    2, UC8151_PWS, 0x2f,
    2, 0xe0, 0x00, // CCSET
    2, 0xe6, 0x00, // TSSET
    1, UC8151_PON, // power on
    BUSY_WAIT,
    0
};
// Spectra 6 (GDEP073E01) 800x480 7-color init sequence
const uint8_t epd73_spectra_init[] PROGMEM = {
    7, 0xaa, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18, // CMD H
    7, UC8151_PWR, 0x3f, 0x00, 0x32, 0x2a, 0x0e, 0x2a, // power setting
    3, UC8151_PSR, 0x5f, 0x69, // panel setting
    5, UC8151_PFS, 0x00, 0x54, 0x00, 0x44,
    5, 0x05, 0x40, 0x1f, 0x1f, 0x2c, // BTST1
    5, 0x06, 0x6f, 0x1f, 0x16, 0x25, // BTST2
    5, 0x08, 0x6f, 0x1f, 0x1f, 0x22, // BTST3
    3, 0x13, 0x00, 0x04, // IPC
    2, UC8151_PLL, 0x02,
//    2, 0x30, 0x3c,
    2, UC8151_TSE, 0x00,
    2, UC8151_CDI, 0x3f,
    3, UC8151_TCON, 0x02, 0x00,
    5, UC8151_TRES, 0x03, 0x20, 0x01, 0xe0, // resolution 800x480
    2, UC8151_VDCS, 0x1e,
    2, 0x84, 0x01, // T_VCDS
    2, 0x86, 0x00, // AGID
    2, UC8151_PWS, 0x2f,
    2, 0xe0, 0x00, // CCSET
    2, 0xe6, 0x00, // TSSET
    1, UC8151_PON, // power on
    BUSY_WAIT,
    0
};

const uint8_t epd75r_init[] PROGMEM = {
    5, 0x01, 0x07,0x07,0x3f,0x3f, // power setting
    1, 0x04, // power on
    BUSY_WAIT,
    2, 0x00, 0x0f, // panel setting
    5, 0x61, 0x03,0x20,0x01,0xe0, // resolution 800x480
    2, 0x15, 0x00,
    3, 0x50, 0x11, 0x07, // VCOM
    2, 0x60, 0x22, // TCON
    5, 0x65, 0x00, 0x00, 0x00, 0x00, // start row/col
    0
};

const uint8_t epd41_init_sequence_full[] PROGMEM = {
    BUSY_WAIT,
    3, UC8151_PSR, 0x2f, 0x00,
    5, UC8151_PWR, 0x37, 0x00, 0x05, 0x05,
    2, UC8151_PFS, 0x00,
    4, UC8151_BTST, 0xc7, 0xc7, 0x1d,
    2, UC8151_PLL, 0x3c,
    2, UC8151_TSE, 0x00,
    2, UC8151_CDI, 0x37,
    2, UC8151_TCON, 0x22,
    5, UC8151_TRES, 0x02, 0x80, 0x01, 0x90,
    2, UC8151_PWS, 0xaa, 
// second set of commands from example (seems unnecessary)
    3, UC8151_PSR, 0x2f, 0x08,
    6, UC8151_PWR, 0x37, 0x00, 0x05, 0x05, 0x00,
    5, UC8151_TRES, 0x02, 0x80, 0x01, 0x90,
    1, UC8151_PON, // power on
    BUSY_WAIT,
    0x00
};

const uint8_t epd583r_init[] PROGMEM = {
    0x03, 0x01, 0x37, 0x00, // power setting
    0x03, 0x00, 0xcf, 0x08, // panel setting
    0x04, 0x06, 0xc7, 0xcc, 0x28, // boost
    0x02, 0x30, 0x3a, // PLL 15s refresh
    0x02, 0x41, 0x00, // temperature
    0x02, 0x50, 0x77, // VCOM
    0x02, 0x60, 0x22, // TCON
    0x05, 0x61, 0x02, 0x58, 0x01, 0xc0, // RES
    0x02, 0x82, 0x28, // temp range
    0x02, 0xe5, 0x03, // flash mode
    0x01, 0x04, // power on
    BUSY_WAIT,
    0x00
};          

const uint8_t epd81c_init_full[] PROGMEM = {
    BUSY_WAIT,
    7, 0xaa, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18,
    2, UC8151_PWR, 0x3f,
    3, UC8151_PSR, 0x5f, 0x69,
    BUSY_WAIT,
    5, 0x05, 0x40, 0x1f, 0x1f, 0x2c,
    5, 0x08, 0x6f, 0x1f, 0x1f, 0x22,
    5, UC8151_BTST, 0x6f, 0x1f, 0x16, 0x16,
    5, UC8151_PFS, 0x00, 0x54, 0x00, 0x44,
    3, UC8151_TCON, 0x02, 0x00,
    2, UC8151_PLL, 0x08,
    5, UC8151_TRES, 0x02, 0x00, 0x02, 0x40,
    BUSY_WAIT,
    2, UC8151_CDI, 0x3f,
    2, UC8151_PWS, 0x2f,
    2, UC8151_VDCS, 0x01,
    2, 0xe0, 0x01,
    5, UC8151_BTST, 0x6f, 0x1f, 0x16, 0x25,
    1, UC8151_PON,
    BUSY_WAIT, 
    0x00
};

#ifdef NO_RAM
uint8_t u8Cache[128]; // buffer a single line of up to 1024 pixels
#else // we need a larger cache for 4-bit panels
uint8_t u8Cache[512];
#endif
//
// Definitions for each supported panel
// The order tracks that of the enumerated panel types
// ** ONLY ADD NEW PANELS TO THE END OF THE LIST **
//
const EPD_PANEL panelDefs[] PROGMEM = {
// 0
    {0,0,0,NULL,NULL,NULL,0,0,NULL}, // undefined panel
    {400, 300, 0, epd42_init_sequence_full, epd42_init_sequence_fast, epd42_init_sequence_part, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP42_400x300
    {400, 300, 0, epd42b_init_sequence_full, epd42b_init_sequence_fast, epd42b_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP42B_400x300
    {122, 250, 0, epd213_122x250_init_sequence_full, NULL, epd213_122x250_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP213_122x250 WaveShare
    {122, 250, 0, epd213z_init_full, epd213z_init_fast, epd213b_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP213B_122x250, newer Inky phat 2.13" B/W
    {128, 296, 0, epd293_init_sequence_full, epd293_init_sequence_fast, epd293_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP293_128x296
    {128, 296, 0, epd294_init_sequence_full, NULL, NULL, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP294_128x296
    {128, 296, 0, epd293_init_sequence_full, epd293_init_sequence_fast, epd295_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP295_128x296
    {128, 296, 0, epd295_init_gray, NULL, NULL, BBEP_4GRAY, BBEP_CHIP_SSD16xx, u8Colors_4gray}, // EP295_128x296_4GRAY
    {152, 296, 0, epd266_init_sequence_full, NULL, epd266_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP266_152x296
// 10
    {80, 128, 0, epd102_init_sequence_full, NULL, epd102_init_sequence_part, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP102_80x128
    {176, 264, 0, epd27_init_sequence_full, NULL, epd27_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP27B_176x264
    {128, 296, 0, epd29r_init_sequence_full, NULL, NULL, BBEP_3COLOR, BBEP_CHIP_SSD16xx, u8Colors_3clr}, // EP29R_128x296
    {192, 176, 0, epd122_init_sequence_full, epd122_init_sequence_fast, epd122_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP122_192x176
    {152, 152, 0, epd154r_init_sequence_full, NULL, NULL, BBEP_3COLOR, BBEP_CHIP_SSD16xx, u8Colors_3clr}, // EP154R_152x152
    {400, 300, 0, epd42r_init_sequence_full, NULL, NULL, BBEP_3COLOR, BBEP_CHIP_SSD16xx, u8Colors_3clr}, // EP42R_400x300, Inky wHAT 4.2" 400x300 B/W/R
    {400, 300, 0, epd42r2_init_sequence_full, epd42r2_init_sequence_fast, NULL, BBEP_RED_SWAPPED | BBEP_3COLOR, BBEP_CHIP_UC81xx, u8Colors_3clr}, // EP42R2_400x300
    {240, 416, 0, epd37_init_sequence_full, NULL, epd37_init_sequence_part, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP37_240x416
    {240, 416, 0, epd37b_init_sequence_full, epd37b_init_sequence_fast, epd37b_init_sequence_part, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP37B_240x416
    {104, 212, 0, epd213_inky_init_sequence_full, NULL, NULL, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP213_104x212, older InkyPHAT black and white
// 20
    {800, 480, 0, epd75_init_sequence_full, epd75_init_sequence_fast, epd75_init_sequence_partial, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP75_800x480 (old)
    {800, 480, 0, epd75_init_sequence_full, epd75_init_fast_gen2, epd75_init_partial_gen2, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP75_800x480_GEN2 (new)
    {800, 480, 0, epd75_old_gray_init, epd75_old_gray_init_fast, NULL, BBEP_4GRAY, BBEP_CHIP_UC81xx, u8Colors_4gray}, // EP75_800x480_4GRAY
    {800, 480, 0, epd75_gray_init, epd75_new_gray_init_fast, NULL, BBEP_4GRAY, BBEP_CHIP_UC81xx, u8Colors_4gray}, // EP75_800x480_4GRAY_GEN2
    {800, 480, 0, epd75_old_gray_init2, NULL, NULL, BBEP_4GRAY, BBEP_CHIP_UC81xx, u8Colors_4gray}, // EP75_800x480_4GRAY_V2 (darker)
    {128, 296, 0, epd29_init_sequence_full, epd29_init_sequence_fast, epd29_init_sequence_part, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // Badger 2040
    {128, 296, 0, epd29_init_sequence_gray, NULL, NULL, BBEP_4GRAY, BBEP_CHIP_UC81xx, u8Colors_4gray}, // Badger 2040 4Gray mode
    {122, 250, 1, epd213r_inky_init_sequence_full, NULL, NULL, BBEP_3COLOR, BBEP_CHIP_SSD16xx, u8Colors_3clr}, // EP213R_122x250 Inky phat 2.13" B/W/R
    {200, 200, 0, epd154_init_sequence_full, epd154_init_sequence_fast, epd154_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP154_200x200
    {200, 200, 0, epd154_init_sequence_full, epd154_init_sequence_fast, epd154b_init_sequence_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP154B_200x200
// 30
    {184, 360, 0, epd266yr_init_sequence_full, NULL, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP266YR_184x360
    {128, 296, 0, epd29yr_init_sequence_full, NULL, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr}, // EP29YR_128x296
    {168, 384, 0, epd29yrh_init_sequence_full, NULL, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx ,u8Colors_4clr}, // EP29YR_168x384
    {648, 480, 0, epd583_init_sequence_full, NULL, NULL, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP583_648x480
    {128, 296, 0, epd293_init_sequence_full, epd293_init_sequence_fast, epd296_init_sequence_part, BBEP_PARTIAL2, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP296_128x296 Waveshare 2.9" V2 B/W
    {152, 296, 0, epd26r_init_sequence_full, NULL, NULL, BBEP_3COLOR, BBEP_CHIP_SSD16xx, u8Colors_3clr}, // EP26R_152x296 Solum ESL harvested 2.6" panel B/W/R
//    {540, 960, 0, (const uint8_t *)epd47_it8951_init, NULL, NULL, 0, BBEP_CHIP_IT8951}, // EP47_540x960 = M5Paper (original)
    {800, 480, 0, epd73_init, NULL, NULL,  BBEP_7COLOR, BBEP_CHIP_UC81xx, u8Colors_7clr}, // EP73_800x480 GEDY073D4 6 7-color 800x480
    {800, 480, 0, epd73_spectra_init, NULL, NULL,  BBEP_7COLOR, BBEP_CHIP_UC81xx, u8Colors_spectra}, // EP73_800x480 Spectra 6 7-color 800x480
    {640, 384, 0, epd74r_init, NULL, NULL,  BBEP_3COLOR | BBEP_4BPP_DATA, BBEP_CHIP_UC81xx, u8Colors_3clr}, // EP74R_640x384, 3-color 640x384
    {600, 448, 0, epd583r_init, NULL, NULL,  BBEP_3COLOR | BBEP_4BPP_DATA, BBEP_CHIP_UC81xx, u8Colors_3clr}, // EP583R_600x448, 3-color 600x448
// 40
    {800, 480, 0, epd75r_init, NULL, NULL, BBEP_3COLOR | BBEP_RED_SWAPPED, BBEP_CHIP_UC81xx, u8Colors_3clr}, // EP75R_800x480, waveshare 7.5 800x480 B/W/R
    {800, 480, 0, epd426_init_full, epd426_init_fast, epd426_init_part, BBEP_NEEDS_EXTRA_INIT, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP426_800x480
    {800, 480, 0, epd426g_init, epd426g_init_fast, NULL, BBEP_4GRAY | BBEP_NEEDS_EXTRA_INIT, BBEP_CHIP_SSD16xx, u8Colors_4gray}, // EP426_800x480_4GRAY
    {128, 296, 0, epd29r2_init_sequence_full, NULL, NULL, BBEP_RED_SWAPPED | BBEP_3COLOR, BBEP_CHIP_UC81xx, u8Colors_3clr}, // EP29R2_128x296 Adafruit 2.9" 128x296 Tricolor FeatherWing
    {640, 400, 0, epd41_init_sequence_full, NULL, NULL, BBEP_4BPP_DATA, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP41_640x400 Eink ED040TC1
    {1024, 576, 0, epd81c_init_full, NULL, NULL, BBEP_SPLIT_BUFFER | BBEP_7COLOR, BBEP_CHIP_UC81xx, u8Colors_spectra}, // 8.1" 1024x576 dual cable Spectra 6 EP81_SPECTRA_1024x576
    {960, 640, 0, ep7_init, NULL, ep7_init_partial, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP7_960x640 (ED070EC1)
    {122, 250, 0, epd213r2_init_sequence_full, epd213r2_init_sequence_fast, NULL, BBEP_RED_SWAPPED | BBEP_3COLOR, BBEP_CHIP_UC81xx, u8Colors_3clr}, // EP213R2_122x250 3 color
    {128, 296, 0, epd29z_init_full, epd29z_init_fast, epd29z_init_full, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // CrowPanel 2.9 (EP29Z_128x296)
    {128, 296, 0, epd29z_init_gray, epd29z_init_gray_fast, NULL, BBEP_4GRAY, BBEP_CHIP_SSD16xx, u8Colors_4gray}, // CrowPanel 2.9 (EP29Z_128x296_4GRAY)
// 50
    {122, 250, 0, epd213z_init_full, epd213z_init_fast, epd213z_init_full, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // CrowPanel 2.13 (EP213Z_122x250)
    {122, 250, 0, epd213z_init_gray, epd213z_init_gray, epd213z_init_gray, BBEP_4GRAY, BBEP_CHIP_SSD16xx, u8Colors_4gray}, // CrowPanel 2.13 (EP213Z_122x250) 4-gray mode
    {152, 152, 0, epd154z_init_full, epd154z_init_fast, epd154z_init_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // CrowPanel 1.54" EP154Z_152x152
    {792, 272, 0, epd579_init_full, epd579_init_fast, NULL, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // CrowPanel 5.79" (EP579_792x272)
    {122, 250, 0, epd213yr_init_full, epd213yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP213YR_122x250
    {240, 416, 0, epd37yr_init_full, NULL, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP37YR_240x416
    {184, 384, 0, epd35yr_init_full, epd35yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP35YR_184x384
    {800, 480, 0, epd397yr_init_full, epd397yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP397YR_800x480
    {200, 200, 0, epd154yr_init_full, epd154yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP154YR_200x200
    {184, 360, 0, epd266yr_init_full, epd266yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP266YR2_184x360
// 60
    {400, 300, 0, epd42yr_init_full, epd42yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP42YR_400x300
//    {792, 272, 0, epd579yr_init_full, epd579yr_init_fast, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP579YR_792x272
    {160, 296, 0, epd215yr_init_full, epd215yr_init_full, NULL, BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP215YR_160x296
    {680, 480, 0, epd1085_init_full, NULL, NULL, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP1085_1360x480
    {240, 320, 0, epd31_init_full, epd31_init_fast, epd31_init_part, 0, BBEP_CHIP_UC81xx, u8Colors_2clr}, // EP31_240x320
    {800, 480, 0, epd75yr_init_full, NULL, NULL, BBEP_NEEDS_EXTRA_INIT | BBEP_4COLOR, BBEP_CHIP_UC81xx, u8Colors_4clr_v2}, // EP75YR_800x480
    {200, 200, 0, epd154g_init_full, epd154g_init_fast, NULL, BBEP_4GRAY, BBEP_CHIP_SSD16xx, u8Colors_4gray}, // EP154_200x200
    {400, 300, 0, epd42b_init_gray, epd42b_init_gray_fast, NULL, BBEP_4GRAY, BBEP_CHIP_SSD16xx, u8Colors_4gray}, // EP42B_400x300_4GRAY
    {800, 480, 0, epd397_init_full, epd397_init_fast, epd397_init_part, 0, BBEP_CHIP_SSD16xx, u8Colors_2clr}, // EP397_800x480
    {800, 480, 0, epd397g_init_full, NULL, NULL, BBEP_4GRAY, BBEP_CHIP_SSD16xx, u8Colors_4gray}, // EP397_800x480_4GRAY
};
//
// Set the e-paper panel type
// This must be called before any other bb_epaper functions
//
int bbepSetPanelType(BBEPDISP *pBBEP, int iPanel)
{
    if (pBBEP == NULL || iPanel <= EP_PANEL_UNDEFINED || iPanel >= EP_PANEL_COUNT)
        return BBEP_ERROR_BAD_PARAMETER;
    
    switch (pBBEP->iOrientation) {
        case 0:
        case 180:
            pBBEP->width = pBBEP->native_width = panelDefs[iPanel].width;
            pBBEP->height = pBBEP->native_height = panelDefs[iPanel].height;
            break;
        case 90:
        case 270:
            pBBEP->width = pBBEP->native_height = panelDefs[iPanel].height;
            pBBEP->height = pBBEP->native_width = panelDefs[iPanel].width;
            break;
    }

    pBBEP->x_offset = panelDefs[iPanel].x_offset;
    pBBEP->chip_type = panelDefs[iPanel].chip_type;
    pBBEP->iFlags = panelDefs[iPanel].flags;
    pBBEP->iPasses = 32; // default LUT pushes for UC81xx partial update mode
    // If the memory is already allocated and the user is setting a
    // panel type which needs 2 planes, assume they have been allocated
    if (pBBEP->ucScreen && (pBBEP->iFlags & (BBEP_3COLOR | BBEP_4GRAY | BBEP_4COLOR))) {
       pBBEP->iFlags |= BBEP_HAS_SECOND_PLANE;
    }
    pBBEP->pInitFull = panelDefs[iPanel].pInitFull;
    pBBEP->pInitFast = panelDefs[iPanel].pInitFast;
    pBBEP->pInitPart = panelDefs[iPanel].pInitPart;
    pBBEP->pColorLookup = panelDefs[iPanel].pColorLookup;
    pBBEP->type = iPanel;
    // select the correct pixel drawing functions (2/3/4/7 color)
    if (pBBEP->iFlags & BBEP_4COLOR) {
        pBBEP->pfnSetPixel = bbepSetPixel4Clr;
        pBBEP->pfnSetPixelFast = bbepSetPixelFast4Clr;
    } else if (pBBEP->iFlags & BBEP_4GRAY) {
        pBBEP->pfnSetPixel = bbepSetPixel4Gray;
        pBBEP->pfnSetPixelFast = bbepSetPixelFast4Gray;
    } else if (pBBEP->iFlags & BBEP_3COLOR) {
        pBBEP->pfnSetPixel = bbepSetPixel3Clr;
        pBBEP->pfnSetPixelFast = bbepSetPixelFast3Clr;
    } else if (pBBEP->iFlags & BBEP_7COLOR) {
        pBBEP->pfnSetPixel = bbepSetPixel16Clr;
        pBBEP->pfnSetPixelFast = bbepSetPixelFast16Clr;
    } else { // must be B/W
        pBBEP->pfnSetPixel = bbepSetPixel2Clr;
        pBBEP->pfnSetPixelFast = bbepSetPixelFast2Clr;
    }
    return BBEP_SUCCESS;
} /* bbepSetPanelType() */

void bbepSetDitherPattern(BBEPDISP *pBBEP, uint8_t iPattern)
{
    if (iPattern >= DITHER_COUNT) return;

    pBBEP->iDither = iPattern;
    if (iPattern == DITHER_NONE) { // reset to default behavior
        if (pBBEP->iFlags & BBEP_4COLOR) {
            pBBEP->pfnSetPixelFast = bbepSetPixelFast4Clr;
        } else if (pBBEP->iFlags & BBEP_4GRAY) {
            pBBEP->pfnSetPixelFast = bbepSetPixelFast4Gray;
        } else if (pBBEP->iFlags & BBEP_3COLOR) {
            pBBEP->pfnSetPixelFast = bbepSetPixelFast3Clr;
        } else if (pBBEP->iFlags & BBEP_7COLOR) {
            pBBEP->pfnSetPixelFast = bbepSetPixelFast16Clr;
        } else { // must be B/W
            pBBEP->pfnSetPixelFast = bbepSetPixelFast2Clr;
        }
    } else { // use the dithered pixel drawing functions
        pBBEP->pfnSetPixelFast = bbepSetPixel2ClrDither;
    }
} /* bbepSetDitherPattern() */

int bbepCreateVirtual(BBEPDISP *pBBEP, int iWidth, int iHeight, int iFlags)
{
    if (pBBEP) {
        memset(pBBEP, 0, sizeof(BBEPDISP));
        pBBEP->native_width = pBBEP->width = iWidth;
        pBBEP->native_height = pBBEP->height = iHeight;
        pBBEP->iFlags = iFlags;
        pBBEP->chip_type = BBEP_CHIP_NONE;
    // select the correct pixel drawing functions (2/3/4 color)
        if (iFlags & BBEP_4COLOR) {
            pBBEP->pColorLookup = u8Colors_4clr;
            pBBEP->pfnSetPixel = bbepSetPixel4Clr;
            pBBEP->pfnSetPixelFast = bbepSetPixelFast4Clr;
        } else if (iFlags & BBEP_4GRAY) {
            pBBEP->pColorLookup = u8Colors_4clr;
            pBBEP->pfnSetPixel = bbepSetPixel4Gray;
            pBBEP->pfnSetPixelFast = bbepSetPixelFast4Gray;
        } else if (iFlags & BBEP_3COLOR) {
            pBBEP->pColorLookup = u8Colors_3clr;
            pBBEP->pfnSetPixel = bbepSetPixel3Clr;
            pBBEP->pfnSetPixelFast = bbepSetPixelFast3Clr;
        } else if (pBBEP->iFlags & BBEP_7COLOR) {
            pBBEP->pColorLookup = u8Colors_7clr;
            pBBEP->pfnSetPixel = bbepSetPixel16Clr;
            pBBEP->pfnSetPixelFast = bbepSetPixelFast16Clr;
        } else { // must be B/W
            pBBEP->pColorLookup = u8Colors_2clr;
            pBBEP->pfnSetPixel = bbepSetPixel2Clr;
            pBBEP->pfnSetPixelFast = bbepSetPixelFast2Clr;
        }
        return BBEP_SUCCESS;
    } else {
        return BBEP_ERROR_BAD_PARAMETER;
    }
}
// Put the ESP32 into light sleep for N milliseconds
void bbepLightSleep(uint32_t u32Millis, uint8_t bLightSleep)
{
#ifdef ARDUINO_ARCH_ESP32
  if (bLightSleep) {
      esp_sleep_enable_timer_wakeup(u32Millis * 1000L);
      esp_light_sleep_start();
  } else {
      delay(u32Millis);
  }
#else
  (void)bLightSleep;
  delay(u32Millis);
#endif
}
//
// Wait for the busy status line to show idle
// The polarity of the busy signal is reversed on the UC81xx compared
// to the SSD16xx controllers
//
void bbepWaitBusy(BBEPDISP *pBBEP)
{
    int iTimeout = 0;
    int iMaxTime = 5000; // for B/W panels

    if (!pBBEP) return;
    if (pBBEP->iBUSYPin == 0xff) return;
    delay(10); // give time for the busy status to be valid
    uint8_t busy_idle =  (pBBEP->chip_type == BBEP_CHIP_UC81xx) ? HIGH : LOW;
    delay(1); // some panels need a short delay before testing the BUSY line
    if (pBBEP->iFlags & (BBEP_3COLOR | BBEP_4COLOR | BBEP_7COLOR)) {
        iMaxTime = 30000; // multi-color panels can take a long time
    }
    while (iTimeout < iMaxTime) {
        if (digitalRead(pBBEP->iBUSYPin) == busy_idle) break;
        // delay(1);
        bbepLightSleep(20, pBBEP->bLightSleep); // save battery power by checking every 20ms
        iTimeout += 20;
    }
} /* bbepWaitBusy() */
//
// Return if panel is busy
//
bool bbepIsBusy(BBEPDISP *pBBEP)
{
    if (!pBBEP) return false;
    if (pBBEP->iBUSYPin == 0xff) return false;
    delay(10); // give time for the busy status to be valid
    uint8_t busy_idle =  (pBBEP->chip_type == BBEP_CHIP_UC81xx) ? HIGH : LOW;
    delay(1); // some panels need a short delay before testing the BUSY line
    return (digitalRead(pBBEP->iBUSYPin) != busy_idle);
} /* bbepWaitBusy() */
//
// Toggle the reset line to wake up the eink from deep sleep
//
void bbepWakeUp(BBEPDISP *pBBEP)
{
    if (!pBBEP) return;
    if (pBBEP->iRSTPin == 0xff) return;

    digitalWrite(pBBEP->iRSTPin, LOW);
    delay(10);
    digitalWrite(pBBEP->iRSTPin, HIGH);
    delay(20);
    bbepWaitBusy(pBBEP);
} /* bbepWakeUp() */
//
// Set the memory window for future writes into panel memory
//
void bbepSetAddrWindow(BBEPDISP *pBBEP, int x, int y, int cx, int cy)
{
    uint8_t uc[12];
    int i, tx, ty;
    
// DEBUG - for TRMNL mini 180 degree rotated display
    if (pBBEP->type == EP397_800x480 || pBBEP->type == EP397_800x480_4GRAY || pBBEP->type == EP426_800x480 || pBBEP->type == EP426_800x480_4GRAY) return;
    if (!pBBEP) return;
    if (pBBEP->iFlags & (BBEP_4COLOR | BBEP_7COLOR)) return;
    
#ifdef FUTURE
    if (pBBEP->chip_type == BBEP_CHIP_IT8951) {
        uint16_t u16Temp[6];
        u16Temp[0] = 0; // DEBUG (_endian_type << 8 | _pix_bpp << 4 | _rotate);
        u16Temp[1] = x;
        u16Temp[2] = y;
        u16Temp[3] = cx;
        u16Temp[4] = cy;
        bbepWriteIT8951CmdArgs(pBBEP, IT8951_TCON_LD_IMG_AREA, u16Temp, 5);
        return;
    }
#endif
    tx = x/8; // round down to next lower byte
    ty = y;
    cx = (cx + 7) & 0xfff8; // make width an even number of bytes
    if (pBBEP->chip_type == BBEP_CHIP_UC81xx) {
        bbepWriteCmd(pBBEP, UC8151_PTIN); // partial in
        bbepWriteCmd(pBBEP, UC8151_PTL); // partial window
        i = 0;
        tx *= 8;
        if (pBBEP->native_width >= 256) { // need 2 bytes per x
            uc[i++] = (uint8_t)(tx>>8); // start x
            uc[i++] = (uint8_t)tx;
            uc[i++] = (uint8_t)((tx+cx-1)>>8); // end x
            uc[i++] = (uint8_t)((tx+cx-1) | 7);
        } else {
            uc[i++] = tx; // start x
            uc[i++] = (tx+cx-1) | 7; // end x
        }
        if (pBBEP->native_height >= 250) {
            uc[i++] = (uint8_t)(ty>>8); // start y
            uc[i++] = (uint8_t)ty;
            uc[i++] = (uint8_t)((ty+cy-1)>>8); // end y
            uc[i++] = (uint8_t)(ty+cy-1);
        } else {
            uc[i++] = (uint8_t)ty;
            uc[i++] = (uint8_t)(ty+cy-1);
        }
        uc[i++] = 1; // refresh whole screen (0=refresh partial window only)
        bbepWriteData(pBBEP, uc, i);
        //       EPDWriteCmd(UC8151_PTOU); // partial out
    } else { // SSD16xx
        //        bbepCMD2(pBBEP, SSD1608_DATA_MODE, 0x3);
        bbepWriteCmd(pBBEP, SSD1608_SET_RAMXPOS);
        tx += pBBEP->x_offset;
        if (pBBEP->type == EP7_960x640 || pBBEP->type == EP426_800x480 || pBBEP->type == EP426_800x480_4GRAY) { // pixels, not bytes version
            if (pBBEP->type == EP7_960x640) {
                tx <<= 3;
            }
            uc[0] = (tx & 0xff);
            uc[1] = ((tx >> 8) & 0xff); // high byte
            uc[2] = (tx+cx-1) & 0xff; // low byte
            uc[3] = (tx+cx-1) >> 8; // high byte
            bbepWriteData(pBBEP, uc, 4);
            // set ram counter to start of this region
            bbepWriteCmd(pBBEP, SSD1608_SET_RAMXCOUNT);
            uc[0] = (tx & 0xff);
            uc[1] = (tx >> 8);
            bbepWriteData(pBBEP, uc, 2);
        } else { // bytes version
            uc[0] = tx; // start x (byte boundary)
            uc[1] = tx+((cx-1)>>3); // end x
            bbepWriteData(pBBEP, uc, 2);
            // set ram counter to start of this region
            bbepCMD2(pBBEP, SSD1608_SET_RAMXCOUNT, tx);
        }
        
        bbepWriteCmd(pBBEP, SSD1608_SET_RAMYPOS);
        if (pBBEP->type == EP426_800x480 || pBBEP->type == EP426_800x480_4GRAY) { // flipped y
            uc[2] = (uint8_t)ty; // start y
            uc[3] = (uint8_t)(ty>>8);
            uc[0] = (uint8_t)(ty+cy-1); // end y
            uc[1] = (uint8_t)((ty+cy-1)>>8);
        } else {
            uc[0] = (uint8_t)ty; // start y
            uc[1] = (uint8_t)(ty>>8);
            uc[2] = (uint8_t)(ty+cy-1); // end y
            uc[3] = (uint8_t)((ty+cy-1)>>8);
        }
        bbepWriteData(pBBEP, uc, 4);
        
        // set ram counter to start of this region
        uc[0] = ty;
        uc[1] = (ty>>8);
        bbepWriteCmd(pBBEP, SSD1608_SET_RAMYCOUNT);
        bbepWriteData(pBBEP, uc, 2);
        //        bbepCMD2(pBBEP, SSD1608_DATA_MODE, 0x3);
    }
    bbepWaitBusy(pBBEP);
} /* bbepSetAddrWindow() */
//
// Put the eink into light or deep sleep
//
void bbepSleep(BBEPDISP *pBBEP, int bDeep)
{
    if (!pBBEP) return;
    if (pBBEP->chip_type == BBEP_CHIP_UC81xx) {
        if (pBBEP->iFlags & BBEP_7COLOR) {
            bbepCMD2(pBBEP, UC8151_POFF, 0x00); // power off
            if (pBBEP->iFlags & BBEP_SPLIT_BUFFER) { // dual cable EPD
               pBBEP->iCSPin = pBBEP->iCS2Pin;
               bbepCMD2(pBBEP, UC8151_POFF, 0x00); // second controller
               pBBEP->iCSPin = pBBEP->iCS1Pin;
            }
        } else if (pBBEP->iFlags & BBEP_4COLOR) {
            bbepCMD2(pBBEP, 0x02, 0x00); // power off
            bbepWaitBusy(pBBEP);
            if (bDeep) {
                bbepCMD2(pBBEP, 0x07, 0xa5); // deep sleep
            }
        } else {
            bbepCMD2(pBBEP, UC8151_CDI, 0x17); // border floating
            bbepWriteCmd(pBBEP, UC8151_POFF); // power off
            bbepWaitBusy(pBBEP);
            if (bDeep) {
                bbepCMD2(pBBEP, UC8151_DSLP, 0xa5); // deep sleep
            }
        }
    } else {
        bbepCMD2(pBBEP, SSD1608_DEEP_SLEEP, (bDeep) ? 0x03 : 0x01); // deep sleep mode 1 keeps RAM,only uses about 1uA
    }
    pBBEP->is_awake = 0;
} /* bbepSleep() */

void bbepStartWrite(BBEPDISP *pBBEP, int iPlane)
{
    uint16_t u8Cmd; // AVR crashes w/odd number of bytes for stack vars
    
    if (!pBBEP) return;
    if (pBBEP->chip_type == BBEP_CHIP_UC81xx) {
        if (pBBEP->iFlags & BBEP_RED_SWAPPED) {
            if (iPlane == PLANE_0)
                u8Cmd = UC8151_DTM1;
            else
                u8Cmd = UC8151_DTM2;
        } else {
            if (iPlane == PLANE_0)
                u8Cmd = UC8151_DTM2;
            else
                u8Cmd = UC8151_DTM1;
        }
    } else { // SSD16xx
        if (iPlane == PLANE_0)
            u8Cmd = SSD1608_WRITE_RAM;
        else
            u8Cmd = SSD1608_WRITE_ALTRAM;
    }
    bbepWriteCmd(pBBEP, (uint8_t)u8Cmd);
} /* bbepStartWrite() */
//
// Dynamically generate partial update LUTS
// based on the iPasses variable
// (only UC81xx for now)
//
void bbepMakeLUTs(BBEPDISP *pBBEP)
{
    if (pBBEP->chip_type == BBEP_CHIP_UC81xx) {
        memset(u8Cache, 0, 44); // start with all 0's
        bbepWriteCmd(pBBEP, 0x20); // VCOM LUT
        // Setup the parameters common to all LUTs (6 bytes per row, 7 rows)
        // We only use 1 row to do all of the work
        // Byte 0 = 4 2-bit patterns, bytes 1-4 = counts for patterns, byte 5 = repeat count
        u8Cache[1] = (uint8_t)pBBEP->iPasses; // slot 0 = number of push passes
        u8Cache[2] = 1; // slot 1 = one pass to discharge all LUTs
        // slots 2 & 3 are 0's (disabled)
        u8Cache[5] = 1; // Number of repetitions for this row
        bbepWriteData(pBBEP, u8Cache, 44); // VCOM LUT is 44 bytes
        bbepWriteCmd(pBBEP, 0x21); // W->W LUT
        u8Cache[0] = 0x80; // 10 = push white, 00,00,00 = neutral
        bbepWriteData(pBBEP, u8Cache, 42); // W->W LUT is 42 bytes
        bbepWriteCmd(pBBEP, 0x22); // B->W LUT
        bbepWriteData(pBBEP, u8Cache, 42); // same pattern as W->W, we're pushing white
        bbepWriteCmd(pBBEP, 0x23); // W->B LUT
        u8Cache[0] = 0x40; // 01 = push black, 00,00,00 = neutral
        bbepWriteData(pBBEP, u8Cache, 42);
        bbepWriteCmd(pBBEP, 0x24); // B->B LUT
        bbepWriteData(pBBEP, u8Cache, 42); // same pattern as W->B, we're pushing black
        bbepWriteCmd(pBBEP, 0x25); // Border LUT
        u8Cache[0] = 0; // no activity
        bbepWriteData(pBBEP, u8Cache, 42);
    } else { // SSD16xx
    }
} /* bbepMakeLUTs() */
//
// More efficient means of sending commands, data and busy-pauses
//
void bbepSendCMDSequence(BBEPDISP *pBBEP, const uint8_t *pSeq)
{
    int iLen;
    uint8_t *s;
    
    if (pBBEP == NULL || pSeq == NULL) return;
    
    s = (uint8_t *)pSeq;
    while (s[0] != 0) { // A 0 length terminates the list
        iLen = *s++;
        if (iLen == MAKE_LUTS) {
            bbepMakeLUTs(pBBEP);
        } else if (iLen == BUSY_WAIT) {
            bbepWaitBusy(pBBEP);
        } else if (iLen == EPD_RESET) {
            bbepWakeUp(pBBEP);
        } else {
            bbepWriteCmd(pBBEP, s[0]);
            s++;
            if (iLen > 1) {
                bbepWriteData(pBBEP, s, iLen-1);
                s += (iLen-1);
            }
        }
    } // while more commands to send
} /* bbepSendCMDSequence() */

//
// Tests the BUSY line to see if you're connected to a
// SSD16xx or UC81xx panel
//
int bbepTestPanelType(BBEPDISP *pBBEP)
{
    if (!pBBEP) return BBEP_CHIP_NOT_DEFINED;
    if (pBBEP->iRSTPin != 0xff) {
        digitalWrite(pBBEP->iRSTPin, LOW);
        delay(40);
        digitalWrite(pBBEP->iRSTPin, HIGH);
        delay(50);
    }
    if (digitalRead(pBBEP->iBUSYPin))
        return BBEP_CHIP_UC81xx; // high state = UltraChip ready
    else
        return BBEP_CHIP_SSD16xx; // low state = Solomon ready
} /* bbepTestPanelType() */
//
// Fill the display with a color
// e.g. all black (0x00) or all white (0xff)
// if there is no backing buffer, write directly to
// the EPD's framebuffer
//
void bbepFill(BBEPDISP *pBBEP, unsigned char ucColor, int iPlane)
{
    uint8_t uc1=0, uc2=0;
    int y, iSize, iPitch;
    uint8_t ucCMD1, ucCMD2;
    
    if (pBBEP == NULL) return;
    ucColor = pBBEP->pColorLookup[ucColor & 0xf]; // translate the color for this display type
    pBBEP->iCursorX = pBBEP->iCursorY = 0;
    iPitch = ((pBBEP->native_width+7)/8);
    iSize = pBBEP->native_height * iPitch;
    if (pBBEP->iFlags & BBEP_7COLOR) {
        uc1 = ucColor | (ucColor << 4);
        iPitch = pBBEP->native_width / 2;
        iSize = pBBEP->native_height * iPitch;
    } else if (pBBEP->iFlags & BBEP_3COLOR) {
        if (ucColor == BBEP_WHITE) {
            uc1 = 0xff; uc2 = 0x00; // red plane has priority
        } else if (ucColor == BBEP_BLACK) {
            uc1 = 0x00; uc2 = 0x00;
        } else if (ucColor == BBEP_RED) {
            uc1 = 0x00; uc2 = 0xff;
        }
    } else if (pBBEP->iFlags & BBEP_4COLOR) {
        iPitch = ((pBBEP->native_width+3)/4);
        iSize = pBBEP->native_height * iPitch;
        iPlane = PLANE_0; // only 1 2-bit memory plane
        uc1 = ucColor | (ucColor << 2);
        uc1 |= (uc1 << 4); // set color in all 4 pixels of the byte
    } else if (pBBEP->iFlags & BBEP_4GRAY) {
        uc1 = (ucColor & 1) ? 0xff : 0x00;
        uc2 = (ucColor & 2) ? 0xff : 0x00;
    } else { // B/W
        if (ucColor == BBEP_WHITE) {
            uc1 = uc2 = 0xff;
        } else {
            uc1 = uc2 = 0x00;
        }
    }
    if (pBBEP->ucScreen) { // there's a local framebuffer, use it
        if (pBBEP->iFlags & BBEP_7COLOR) {
            memset(pBBEP->ucScreen, uc1, iSize);
            return;
        } else if ((pBBEP->iFlags & (BBEP_4GRAY | BBEP_3COLOR)) || iPlane == PLANE_BOTH) {
            memset(pBBEP->ucScreen, uc1, iSize);
            memset(&pBBEP->ucScreen[iSize], uc2, iSize);
        } else if (iPlane == PLANE_DUPLICATE) {
            memset(pBBEP->ucScreen, uc1, iSize);
            if (pBBEP->iFlags & BBEP_HAS_SECOND_PLANE) {
                memset(&pBBEP->ucScreen[iSize], uc1, iSize);
            }
        } else if (iPlane == PLANE_0) {
            memset(pBBEP->ucScreen, uc1, iSize);
        } else if (iPlane == PLANE_1 && (pBBEP->iFlags & BBEP_HAS_SECOND_PLANE)) {
            memset(&pBBEP->ucScreen[iSize], uc2, iSize);
        }
    } else { // write directly to the EPD's framebuffer
        if (pBBEP->iFlags & BBEP_3COLOR) {
            if (ucColor == BBEP_WHITE) {
                uc1 = 0xff; uc2 = 0x00; // red plane has priority
            } else if (ucColor == BBEP_BLACK) {
                uc1 = 0x00; uc2 = 0x00;
            } else if (ucColor == BBEP_RED) {
                uc1 = 0x00; uc2 = 0xff;
            }
        } else if (!(pBBEP->iFlags & (BBEP_4GRAY | BBEP_4COLOR))) { // for B/W, both planes get the same data
            if (ucColor == BBEP_WHITE) ucColor = 0xff;
            else if (ucColor == BBEP_BLACK) ucColor = 0;
            uc1 = uc2 = ucColor;
            if (pBBEP->iFlags & BBEP_4BPP_DATA) { // special case
                 // EInk 4.1" 640x400 uses 4-bpp data for 1-bpp images
                 uc1 = (ucColor == BBEP_WHITE) ? 0x00 : 0x11;
                 memset(u8Cache, uc1, pBBEP->native_width/2);
                 bbepWriteCmd(pBBEP, 0x10);
                 for (y=0; y<pBBEP->native_height; y++) {
                     bbepWriteData(pBBEP, u8Cache, pBBEP->native_width/2);
                 }
                 return;
            }
        }
        if (pBBEP->chip_type == BBEP_CHIP_UC81xx) {
            if (pBBEP->iFlags & (BBEP_RED_SWAPPED | BBEP_4COLOR)) {
                ucCMD1 = UC8151_DTM1;
                ucCMD2 = UC8151_DTM2;
            } else {
                ucCMD1 = UC8151_DTM2;
                ucCMD2 = UC8151_DTM1;
            }
        } else {
            ucCMD1 = SSD1608_WRITE_RAM;
            ucCMD2 = SSD1608_WRITE_ALTRAM;
        }
        // Write one or both memory planes to the EPD
        if (iPlane == PLANE_0 || iPlane == PLANE_DUPLICATE) { // write to first plane
            bbepSetAddrWindow(pBBEP, 0,0, pBBEP->native_width, pBBEP->native_height);
            bbepWriteCmd(pBBEP, ucCMD1);
            for (y=0; y<pBBEP->native_height; y++) {
                memset(u8Cache, uc1, iPitch); // the data is overwritten after each write
                bbepWriteData(pBBEP, u8Cache, iPitch);
            } // for y
        }
        if (iPlane == PLANE_1 || iPlane == PLANE_DUPLICATE) { // write to first plane
            bbepSetAddrWindow(pBBEP, 0,0, pBBEP->native_width, pBBEP->native_height);
            bbepWriteCmd(pBBEP, ucCMD2);
            for (y=0; y<pBBEP->native_height; y++) {
                memset(u8Cache, uc2, iPitch); // the data is overwritten after each write
                bbepWriteData(pBBEP, u8Cache, iPitch);
            } // for y
        }
    }
} /* bbepFill() */

int bbepRefresh(BBEPDISP *pBBEP, int iMode)
{

    if (iMode != REFRESH_FULL && iMode != REFRESH_FAST && iMode != REFRESH_PARTIAL)
        return BBEP_ERROR_BAD_PARAMETER;
    
    switch (iMode) {
        case REFRESH_FULL:
            if (!(pBBEP->iFlags & BBEP_NEEDS_EXTRA_INIT)) { // already sent?
                bbepSendCMDSequence(pBBEP, pBBEP->pInitFull);
            }
            if (pBBEP->iFlags & BBEP_SPLIT_BUFFER) {
               // Send the same sequence to the second controller
               pBBEP->iCSPin = pBBEP->iCS2Pin;
               bbepSendCMDSequence(pBBEP, pBBEP->pInitFull);
               pBBEP->iCSPin = pBBEP->iCS1Pin;
            }
            break;
        case REFRESH_FAST:
            if (!pBBEP->pInitFast) { // fall back to full
                bbepSendCMDSequence(pBBEP, pBBEP->pInitFull);
            } else {
                bbepSendCMDSequence(pBBEP, pBBEP->pInitFast);
            }
            break;
        case REFRESH_PARTIAL:
            if (!pBBEP->pInitPart) { // Do a fast or full refresh if partial not defined
                if (pBBEP->pInitFast) {
                    bbepSendCMDSequence(pBBEP, pBBEP->pInitFast);
                } else { // fall back to full
                    bbepSendCMDSequence(pBBEP, pBBEP->pInitFull);
                }
            } else {
                bbepSendCMDSequence(pBBEP, pBBEP->pInitPart);
            }
            break;
        default:
            return BBEP_ERROR_BAD_PARAMETER;
    } // switch on mode
    if (pBBEP->chip_type == BBEP_CHIP_UC81xx) {
        if (pBBEP->iFlags & (BBEP_4GRAY | BBEP_4COLOR | BBEP_7COLOR)) {
            bbepCMD2(pBBEP, UC8151_DRF, 0x00);
            if (pBBEP->iFlags & BBEP_SPLIT_BUFFER) {
               // Send the same sequence to the second controller
               pBBEP->iCSPin = pBBEP->iCS2Pin;
               bbepCMD2(pBBEP, UC8151_DRF, 0);
               pBBEP->iCSPin = pBBEP->iCS1Pin;
            }
        } else {
            bbepWriteCmd(pBBEP, UC8151_PTOU); // partial out (update the entire panel, not just the last memory window)
            bbepWriteCmd(pBBEP, UC8151_DRF);
        }
    } else {
        const uint8_t u8CMD[4] = {0xf7, 0xc7, 0xff, 0xc0}; // normal, fast, partial, partial2
        const uint8_t u8CMDz[4] = {0xf4, 0xc7, 0xfc, 0}; // special set for SSD1680
        const uint8_t u8CMDz2[4] = {0xf4, 0xc7, 0xdc, 0}; // special set #2 for SSD1680
        const uint8_t u8CMDz3[4] = {0xf7, 0xd7, 0xff, 0}; // special set #3
        if (pBBEP->iFlags & (BBEP_4GRAY | BBEP_3COLOR | BBEP_4COLOR)) {
            iMode = REFRESH_FAST;
        } // 3/4-color = 0xc7
        if (iMode == REFRESH_PARTIAL && pBBEP->iFlags & BBEP_PARTIAL2) {
            iMode = REFRESH_PARTIAL2; // special case for custom LUT
        }
        if (pBBEP->type == EP29Z_128x296 || pBBEP->type == EP213Z_122x250) {
            bbepCMD2(pBBEP, SSD1608_DISP_CTRL2, u8CMDz[iMode]);
        } else if (pBBEP->type == EP154Z_152x152) {
            bbepCMD2(pBBEP, SSD1608_DISP_CTRL2, u8CMDz2[iMode]);
        } else if (pBBEP->type == EP397_800x480 || pBBEP->type == EP397_800x480_4GRAY) {
            bbepCMD2(pBBEP, SSD1608_DISP_CTRL2, u8CMDz3[iMode]);
        } else if (pBBEP->type == EP42B_400x300_4GRAY) {
            bbepCMD2(pBBEP, SSD1608_DISP_CTRL2, 0xcf); // SSD1683 does things differently :(
        } else {
            bbepCMD2(pBBEP, SSD1608_DISP_CTRL2, u8CMD[iMode]);
        }
        bbepWriteCmd(pBBEP, SSD1608_MASTER_ACTIVATE); // refresh
    }
    return BBEP_SUCCESS;
} /* bbepRefresh() */

void bbepSetRotation(BBEPDISP *pBBEP, int iRotation)
{
    pBBEP->iScreenOffset = 0;
    pBBEP->iOrientation = iRotation;
    
    switch (iRotation) {
        default: return;
        case 0:
        case 180:
            pBBEP->width = pBBEP->native_width;
            pBBEP->height = pBBEP->native_height;
            break;
        case 90:
        case 270:
            pBBEP->width = pBBEP->native_height;
            pBBEP->height = pBBEP->native_width;
            break;
    }
} /* bbepSetRotation() */

void bbepWriteImage4bppSpecial(BBEPDISP *pBBEP, uint8_t ucCMD)
{
    int tx, ty, iPitch, iRedOff;
    uint8_t uc, ucSrcMask, *s, *d;
    // Convert the bit direction and write the data to the EPD
    // This particular controller has 4 bits per pixel where 0=black, 3=white, 4=red 
    // this wastes 50% of the time transmitting bloated info (only need 2 bits) 
    iPitch = ((pBBEP->native_width+7)/8);
    iRedOff = pBBEP->native_height * iPitch;

    if (ucCMD) {
        bbepWriteCmd(pBBEP, ucCMD); // start write
    }
    if (pBBEP->iOrientation == 0) {
      for (ty=0; ty<pBBEP->height; ty++) {
         d = u8Cache;
         s = &pBBEP->ucScreen[ty * (pBBEP->width/8)];
         ucSrcMask = 0x80;
         for (tx=0; tx<pBBEP->width; tx+=2) {
             uc = 0x33; // start with white/white
             if (!(s[0] & ucSrcMask)) {// src pixel = black
                uc = 0x03;
             }
             if (s[iRedOff] & ucSrcMask) { // red
                 uc = 0x43;
             }
             ucSrcMask >>= 1;
             if (!(s[0] & ucSrcMask)) {// src pixel = black
                 uc &= 0xf0;
             }
             if (s[iRedOff] & ucSrcMask) { // red
                  uc &= 0xf0; uc |= 0x4;
             }
             ucSrcMask >>= 1;
             if (ucSrcMask == 0) {
                ucSrcMask = 0x80;
                s++;
             }
             *d++ = uc; // store 2 pixels
         } // for tx
        bbepWriteData(pBBEP, u8Cache, pBBEP->width/2);
      } // for ty
    } else if (pBBEP->iOrientation == 180) {
        for (ty=pBBEP->height-1; ty>=0; ty--) {
            d = u8Cache;
            s = &pBBEP->ucScreen[((ty+1) * (pBBEP->width/8)) - 1];
            ucSrcMask = 1;
            for (tx=pBBEP->width-1; tx>=0; tx-=2) {
                uc = 0x33; // start with white/white
                if (!(s[0] & ucSrcMask)) // black
                    uc = 0x03;
                if (s[iRedOff] & ucSrcMask) // red
                    uc = 0x43;
                ucSrcMask <<= 1;
                if (!(s[0] & ucSrcMask)) // src pixel = black
                    uc &= 0xf0;
                if (s[iRedOff] & ucSrcMask) { // red
                    uc &= 0xf0; uc |= 0x4;
                }
                ucSrcMask <<= 1;
                if (ucSrcMask == 0) {
                    s--;
                    ucSrcMask = 1;
                }
                *d++ = uc; // store 2 pixels
            } // for tx
            bbepWriteData(pBBEP, u8Cache, pBBEP->width/2);
        } // for ty
    } else if (pBBEP->iOrientation == 90) {
        iPitch = pBBEP->width / 8;
        for (tx=0; tx<pBBEP->width; tx++) {
            d = u8Cache;
            ucSrcMask = 0x80 >> (tx & 7);
            s = &pBBEP->ucScreen[(tx>>3) + ((pBBEP->height-1) * iPitch)];
            for (ty=pBBEP->height-1; ty > 0; ty-=2) {
                uc = 0x33;
                if (!(s[0] & ucSrcMask))
                    uc = 0x03; // black
                if (s[iRedOff] & ucSrcMask)
                    uc = 0x43; // red
                s -= iPitch;
                if (!(s[0] & ucSrcMask)) {// src pixel = black
                    uc &= 0xf0;
                }
                if (s[iRedOff] & ucSrcMask) { // red
                    uc &= 0xf0; uc |= 0x4;
                }
                s -= iPitch;
                *d++ = uc; // store 2 pixels
            } // for ty
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/2);
        } // for tx
    } else if (pBBEP->iOrientation == 270) {
        iPitch = pBBEP->width / 8;
        for (tx=pBBEP->width-1; tx>=0; tx--) {
            d = u8Cache;
            ucSrcMask = 0x80 >> (tx & 7);
            s = &pBBEP->ucScreen[(tx>>3)];
            for (ty=pBBEP->height-1; ty > 0; ty-=2) {
                uc = 0x33;
                if (!(s[0] & ucSrcMask))
                    uc = 0x03; // black
                if (s[iRedOff] & ucSrcMask)
                    uc = 0x43; // red
                s += iPitch;
                if (!(s[0] & ucSrcMask)) {// src pixel = black
                    uc &= 0xf0;
                }
                if (s[iRedOff] & ucSrcMask) { // red
                    uc &= 0xf0; uc |= 0x4;
                }
                s += iPitch;
                *d++ = uc; // store 2 pixels
            } // for ty
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/2);
        } // for tx
  } // 270
} /* bbepWriteImage4bppSpecial() */

// special case for panels with 2 controllers
void bbepWriteImage4bppDual(BBEPDISP *pBBEP, uint8_t ucCMD)
{
    int tx, ty, iPitch;
    uint8_t uc, *s, *d;
        
    if (ucCMD) {
        pBBEP->iCSPin = pBBEP->iCS1Pin;
        bbepWriteCmd(pBBEP, ucCMD); // start write
        pBBEP->iCSPin = pBBEP->iCS2Pin;
        bbepWriteCmd(pBBEP, ucCMD);
    }
    if (pBBEP->iOrientation == 0) {
        iPitch = pBBEP->native_width / 2;
        s = pBBEP->ucScreen;
        for (ty=0; ty<pBBEP->height; ty++) {
            pBBEP->iCSPin = pBBEP->iCS1Pin;
            bbepWriteData(pBBEP, s, iPitch/2);
            pBBEP->iCSPin = pBBEP->iCS2Pin;
            bbepWriteData(pBBEP, &s[iPitch/2], iPitch/2);
            s += iPitch; // 2 pixels per byte
        } // for ty
    } else if (pBBEP->iOrientation == 180) {
        iPitch = pBBEP->native_width / 2;
        for (ty=pBBEP->height-1; ty>=0; ty--) {
            s = &pBBEP->ucScreen[(ty * iPitch) + iPitch - 1];
            // reverse the pixel direction
            for (tx=0; tx<pBBEP->native_width; tx+=2) {
                uc = *s--;
                uc = (uc >> 4) | (uc << 4); // swap nibbles
                u8Cache[tx/2] = uc;
            }
            pBBEP->iCSPin = pBBEP->iCS1Pin;
            bbepWriteData(pBBEP, u8Cache, iPitch/2);
            pBBEP->iCSPin = pBBEP->iCS2Pin;
            bbepWriteData(pBBEP, &u8Cache[iPitch/2], iPitch/2);
        } // for ty
    } else if (pBBEP->iOrientation == 90) {
        iPitch = pBBEP->native_height / 2;
        for (tx=0; tx<pBBEP->width; tx++) {
            d = u8Cache;
            for (ty=pBBEP->height-1; ty > 0; ty-=2) {
                s = &pBBEP->ucScreen[(tx>>1) + (ty * iPitch)];
                if (tx & 1) {
                    uc = (s[0] << 4) | (s[-iPitch] & 0x0f);
                } else {
                    uc = (s[0] & 0xf0) | (s[-iPitch] >> 4);
                }
                s -= iPitch*2;
                *d++ = uc; // store 4 pixels
            } // for ty
            pBBEP->iCSPin = pBBEP->iCS1Pin;
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/4);
            pBBEP->iCSPin = pBBEP->iCS2Pin;
            bbepWriteData(pBBEP, &u8Cache[pBBEP->height/4], pBBEP->height/4);
        } // for tx
    } else if (pBBEP->iOrientation == 270) {
        iPitch = pBBEP->native_height / 2;
        for (tx=pBBEP->width-1; tx>= 0; tx--) {
            d = u8Cache;
            for (ty=0; ty < pBBEP->height; ty+=2) {
                s = &pBBEP->ucScreen[(tx>>1) + (ty * iPitch)];
                if (tx & 1) {
                    uc = (s[0] << 4) | (s[iPitch] & 0x0f);
                } else {
                    uc = (s[0] & 0xf0) | (s[iPitch] >> 4);
                }
                s += iPitch*2;
                *d++ = uc; // store 4 pixels
            } // for ty
            pBBEP->iCSPin = pBBEP->iCS1Pin;
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/4);
            pBBEP->iCSPin = pBBEP->iCS2Pin;
            bbepWriteData(pBBEP, &u8Cache[pBBEP->height/4], pBBEP->height/4);     
        } // for tx
    }
    pBBEP->iCSPin = pBBEP->iCS1Pin; // reset CS to #1
} /* bbepWriteImage4bppDual() */

void bbepWriteImage4bpp(BBEPDISP *pBBEP, uint8_t ucCMD)
{
    int tx, ty, iPitch;
    uint8_t uc, *s, *d;
        
    if (ucCMD) {
        bbepWriteCmd(pBBEP, ucCMD); // start write
    }
    if (pBBEP->iOrientation == 0) {
        iPitch = pBBEP->native_width / 2;
        s = pBBEP->ucScreen;
        for (ty=0; ty<pBBEP->height; ty++) {
            bbepWriteData(pBBEP, s, iPitch);
            s += iPitch; // 2 pixels per byte
        } // for ty
    } else if (pBBEP->iOrientation == 180) {
        iPitch = pBBEP->native_width / 2;
        for (ty=pBBEP->height-1; ty>=0; ty--) {
            s = &pBBEP->ucScreen[(ty * iPitch) + iPitch - 1];
            // reverse the pixel direction
            for (tx=0; tx<pBBEP->native_width; tx+=2) {
                uc = *s--;
                uc = (uc >> 4) | (uc << 4); // swap nibbles
                u8Cache[tx/2] = uc;
            }
            bbepWriteData(pBBEP, u8Cache, iPitch);
        } // for ty
    } else if (pBBEP->iOrientation == 90) {
        iPitch = pBBEP->native_height / 2;
        for (tx=0; tx<pBBEP->width; tx++) {
            d = u8Cache;
            for (ty=pBBEP->height-1; ty > 0; ty-=2) {
                s = &pBBEP->ucScreen[(tx>>1) + (ty * iPitch)];
                if (tx & 1) {
                    uc = (s[0] << 4) | (s[-iPitch] & 0x0f);
                } else {
                    uc = (s[0] & 0xf0) | (s[-iPitch] >> 4);
                }
                s -= iPitch*2;
                *d++ = uc; // store 4 pixels
            } // for ty
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/2);
        } // for tx
    } else if (pBBEP->iOrientation == 270) {
        iPitch = pBBEP->native_height / 2;
        for (tx=pBBEP->width-1; tx>= 0; tx--) {
            d = u8Cache;
            for (ty=0; ty < pBBEP->height; ty+=2) {
                s = &pBBEP->ucScreen[(tx>>1) + (ty * iPitch)];
                if (tx & 1) {
                    uc = (s[0] << 4) | (s[iPitch] & 0x0f);
                } else {
                    uc = (s[0] & 0xf0) | (s[iPitch] >> 4);
                }
                s += iPitch*2;
                *d++ = uc; // store 4 pixels
            } // for ty
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/2);
        } // for tx
    }
} /* bbepWriteImage4bpp() */

void bbepWriteImage2bpp(BBEPDISP *pBBEP, uint8_t ucCMD)
{
int tx, ty, iPitch;
uint8_t *s, *d, uc, uc1, ucMask;
uint8_t *pBuffer;

//    if (pBBEP->pInitFast) { // fast mode on 4-color displays
//     // requires sending the init sequence BEFORE the image data
//        bbepSendCMDSequence(pBBEP, pBBEP->pInitFast);
//    }
    pBuffer = pBBEP->ucScreen;
    if (ucCMD) {
        bbepWriteCmd(pBBEP, ucCMD); // start write
    }
    // Convert the bit direction and write the data to the EPD
    if (pBBEP->iOrientation == 180) {
        for (ty=pBBEP->height-1; ty>=0; ty--) {
            d = u8Cache;
            s = &pBuffer[ty * pBBEP->width/4];
            for (tx=pBBEP->width-4; tx>=0; tx-=4) {
                uc = 0;
                ucMask = 0x03;
                uc1 = s[tx>>2];
                for (int pix=0; pix<8; pix +=2) { // reverse the direction of the 4 pixels
                    uc <<= 2; // shift down 1 pixel
                    uc |= ((uc1 & ucMask) >> pix);
                    ucMask <<= 2;
                }
                *d++ = uc; // store 4 pixels
            } // for tx
            bbepWriteData(pBBEP, u8Cache, pBBEP->width/4);
        } // for ty
    } else if (pBBEP->iOrientation == 0) {
        s = pBBEP->ucScreen;
        iPitch = (pBBEP->width == 122) ? 32: pBBEP->width/4;
        for (ty=0; ty<pBBEP->height; ty++) {
            bbepWriteData(pBBEP, s, iPitch);
            s += (pBBEP->width+3)/4; // 4 pixels per byte
        } // for ty
    } else if (pBBEP->iOrientation == 90) {
        for (tx=0; tx<pBBEP->width; tx++) {
            d = u8Cache;
            for (ty=pBBEP->height-1; ty > 0; ty-=4) {
                s = &pBuffer[(tx>>2) + (ty * (pBBEP->width/4))];
                uc = 0;
                ucMask = 0xc0 >> ((tx & 3) * 2);
                for (int pix=0; pix<4; pix++) {
                    uc <<= 2; // shift down 1 pixel
                    uc |= ((s[0] & ucMask) >> ((3-(tx&3))*2)); // inverted plane 0
                    s -= (pBBEP->width/4);
                }
                *d++ = uc; // store 4 pixels
            } // for ty
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/4);
        } // for tx
    } else if (pBBEP->iOrientation == 270) {
        for (tx=pBBEP->width-1; tx>=0; tx--) {
            d = u8Cache;
            for (ty=3; ty<pBBEP->height; ty+=4) {
                s = &pBuffer[(tx>>2) + (ty * pBBEP->width/4)];
                ucMask = 0xc0 >> ((tx & 3) * 2);
                uc = 0;
                for (int pix=0; pix<4; pix++) {
                    uc >>= 2;
                    uc |= ((s[0] & ucMask) << ((tx&3)*2)); // inverted plane 0
                    s -= (pBBEP->width/4);
                } // for pix
                *d++ = uc; // store 2 pixels
            } // for ty
            bbepWriteData(pBBEP, u8Cache, pBBEP->height/4);
        } // for x
  } // 270
} /* bbepWriteImage2bpp() */

//
// Write 1-bpp graphics to a display which wants to receive it as 4-bpp
//
static void bbepWriteImage1to4bpp(BBEPDISP *pBBEP, uint8_t ucCMD, uint8_t *pBuffer, int bInvert)
{
    int tx, ty;
    uint8_t *s, *d, uc;
    uint8_t ucInvert = 0;
    int iPitch;
// lookup table to convert 2 bits into 8
    const uint8_t u8Lookup[4] = {0x00, 0x01, 0x10, 0x11};

    iPitch = (pBBEP->width + 7) >> 3;
    if (bInvert) {
        ucInvert = 0xff; // red logic is inverted
    }
    if (ucCMD) {
        bbepWriteCmd(pBBEP, ucCMD); // start write
    }
    // Convert the bit direction and write the data to the EPD
    switch (pBBEP->iOrientation) {
        case 0:
            for (ty=0; ty<pBBEP->native_height; ty++) {
                d = u8Cache;
                s = &pBuffer[ty * iPitch];
                for (tx=0; tx<iPitch; tx++) {
                    uc = *s++;
                    *d++ = u8Lookup[uc >> 6];
                    *d++ = u8Lookup[(uc >> 4) & 3];
                    *d++ = u8Lookup[(uc >> 2) & 3];
                    *d++ = u8Lookup[uc & 3];
                }
                if (ucInvert) InvertBytes(u8Cache, iPitch*4);
                bbepWriteData(pBBEP, u8Cache, iPitch*4);
            } // for ty
            break;
    } // switch
} /* bbepWriteImage1to4bpp() */

//
// Write Image data (1-bpp entire plane) from RAM to the e-paper
// Rotate the pixels if necessary
// This version is specifically for the 272x792 5.79" panels
// since they split the memory into two halves
//
static void bbepWriteHalf(BBEPDISP *pBBEP, uint8_t ucCMD, uint8_t *pBuffer, int bInvert)
{
    int tx, ty;
    uint8_t *s, *d, ucSrcMask, ucDstMask, uc;
    uint8_t ucInvert = 0;
    int iPitch;

    iPitch = (pBBEP->native_width + 7) >> 3;
    if (bInvert) {
        ucInvert = 0xff; // red logic is inverted
    }
    if (ucCMD & 0x80) { // second half
        if (pBuffer) {
            pBuffer += (392/8); // start in the middle
        }
        if (ucCMD == 0xa4) {
        bbepWriteCmd(pBBEP, 0x91); // switch to second memory set
        u8Cache[0] = 4;
        bbepWriteData(pBBEP, u8Cache, 1);
        bbepWriteCmd(pBBEP, 0xc4);
        u8Cache[0] = 0x30; u8Cache[1] = 0;
        bbepWriteData(pBBEP, u8Cache, 2);
        bbepWriteCmd(pBBEP, 0xc5);
        u8Cache[0] = 0x0f; u8Cache[1] = 0x1; u8Cache[2] = u8Cache[3] = 0;
        bbepWriteData(pBBEP, u8Cache, 4);
        }
        bbepWriteCmd(pBBEP, 0xce);
        u8Cache[0] = 0x31;
        bbepWriteData(pBBEP, u8Cache, 1);
        bbepWriteCmd(pBBEP, 0xcf);
        u8Cache[0] = 0x0f; u8Cache[1] = 0x01;
        bbepWriteData(pBBEP, u8Cache, 2);
    } else {
        if (ucCMD == 0x24) {
        bbepWriteCmd(pBBEP, 0x11);
        u8Cache[0] = 0x5;
        bbepWriteData(pBBEP, u8Cache, 1);
        bbepWriteCmd(pBBEP, 0x44);
        u8Cache[0] = 0; u8Cache[1] = 0x31;
        bbepWriteData(pBBEP, u8Cache, 2);
        bbepWriteCmd(pBBEP, 0x45);
        u8Cache[0] = 0x0f; u8Cache[1] = 0x1; u8Cache[2] = u8Cache[3] = 0;
        bbepWriteData(pBBEP, u8Cache, 4);
        }
        bbepWriteCmd(pBBEP, 0x4e);
        u8Cache[0] = 0;
        bbepWriteData(pBBEP, u8Cache, 1);
        bbepWriteCmd(pBBEP, 0x4f);
        u8Cache[0] = 0x0f; u8Cache[1] = 0x01;
        bbepWriteData(pBBEP, u8Cache, 2);
    }
    bbepWaitBusy(pBBEP);
    bbepWriteCmd(pBBEP, ucCMD); // start write
    if (!pBuffer) { // just write 00s
        memset(u8Cache, 0, 272);
        for (tx=0; tx<400/8; tx++) {
            bbepWriteData(pBBEP, u8Cache, 272);
        }
        return;
    }
    // Convert the bit direction and write the data to the EPD
    switch (pBBEP->iOrientation) {
        case 0:
            for (tx=0; tx<400/8; tx++) {
                d = u8Cache;
                s = &pBuffer[tx];
                for (ty=0; ty<272; ty++) {
                    *d++ = *s;
                    s += iPitch;
                }
                if (ucInvert) InvertBytes(d, 272);
                bbepWriteData(pBBEP, u8Cache, 272);
            } // for ty
            break;
        case 90:
            for (tx=0; tx<pBBEP->width; tx++) {
                d = u8Cache;
                // need to pick up and reassemble every pixel
                ucDstMask = 0x80;
                uc = 0xff;
                ucSrcMask = 0x80 >> (tx & 7);
                for (ty=pBBEP->native_height-1; ty>=0; ty--) {
                    s = &pBuffer[(tx >> 3) + (ty * iPitch)];
                    if ((s[0] & ucSrcMask) == 0) uc &= ~ucDstMask;
                    ucDstMask >>= 1;
                    if (ucDstMask == 0) {
                        *d++ = (uc ^ ucInvert);
                        ucDstMask = 0x80;
                        uc = 0xff;
                    }
                } // for ty
                *d++ = (uc ^ ucInvert); // store final partial byte
                bbepWriteData(pBBEP, u8Cache, (pBBEP->native_width+7)/8);
            } // for tx
            break;
        case 180:
            for (ty=pBBEP->native_height-1; ty>=0; ty--) {
                d = u8Cache;
                s = &pBuffer[ty * iPitch];
                for (tx=iPitch-1; tx>=0; tx--) {
                    *d++ = (ucMirror[s[tx]] ^ ucInvert);
                } // for tx
                bbepWriteData(pBBEP, u8Cache, iPitch);
            } // for ty
            break;
        case 270:
            for (tx=pBBEP->width-1; tx>=0; tx--) {
                d = u8Cache;
                // reassemble every pixel
                ucDstMask = 0x80;
                uc = 0xff;
                ucSrcMask = 0x80 >> (tx & 7);
                for (ty=0; ty<pBBEP->native_height; ty++) {
                    s = &pBuffer[(tx>>3) + (ty * iPitch)];
                    if ((s[0] & ucSrcMask) == 0) uc &= ~ucDstMask;
                    ucDstMask >>= 1;
                    if (ucDstMask == 0) {
                        *d++ = (uc ^ ucInvert);
                        ucDstMask = 0x80;
                        uc = 0xff;
                    }
                } // for ty
                *d++ = (uc ^ ucInvert); // store final partial byte
                bbepWriteData(pBBEP, u8Cache, (pBBEP->native_width+7)/8);
            } // for x
            break;
    } // switch on orientation
} /* bbepWriteHalf() */

//
// Write Image data (1-bpp entire plane) from RAM to the e-paper
// Rotate the pixels if necessary
//
static void bbepWriteImage(BBEPDISP *pBBEP, uint8_t ucCMD, uint8_t *pBuffer, int bInvert)
{
    int tx, ty;
    uint8_t *s, *d, ucSrcMask, ucDstMask, uc;
    uint8_t ucInvert = 0;
    int iPitch;
    
    iPitch = (pBBEP->width + 7) >> 3;
    if (bInvert) {
        ucInvert = 0xff; // red logic is inverted
    }
    if (ucCMD) {
        bbepWriteCmd(pBBEP, ucCMD); // start write
    }
    // Convert the bit direction and write the data to the EPD
    switch (pBBEP->iOrientation) {
        case 0:
            for (ty=0; ty<pBBEP->native_height; ty++) {
                d = u8Cache;
                s = &pBuffer[ty * iPitch];
                memcpy(d, s, iPitch);
                if (ucInvert) InvertBytes(d, iPitch);
                bbepWriteData(pBBEP, u8Cache, iPitch);
            } // for ty
            break;
        case 90:
            for (tx=0; tx<pBBEP->width; tx++) {
                d = u8Cache;
                // need to pick up and reassemble every pixel
                ucDstMask = 0x80;
                uc = 0xff;
                ucSrcMask = 0x80 >> (tx & 7);
                for (ty=pBBEP->height-1; ty>=0; ty--) {
                    s = &pBuffer[(tx >> 3) + (ty * iPitch)];
                    if ((s[0] & ucSrcMask) == 0) uc &= ~ucDstMask;
                    ucDstMask >>= 1;
                    if (ucDstMask == 0) {
                        *d++ = (uc ^ ucInvert);
                        ucDstMask = 0x80;
                        uc = 0xff;
                    }
                } // for ty
                *d++ = (uc ^ ucInvert); // store final partial byte
                bbepWriteData(pBBEP, u8Cache, (pBBEP->native_width+7)/8);
            } // for tx
            break;
        case 180:
            for (ty=pBBEP->native_height-1; ty>=0; ty--) {
                d = u8Cache;
                s = &pBuffer[ty * iPitch];
                for (tx=iPitch-1; tx>=0; tx--) {
                    *d++ = (ucMirror[s[tx]] ^ ucInvert);
                } // for tx
                bbepWriteData(pBBEP, u8Cache, iPitch);
            } // for ty
            break;
        case 270:
            for (tx=pBBEP->width-1; tx>=0; tx--) {
                d = u8Cache;
                // reassemble every pixel
                ucDstMask = 0x80;
                uc = 0xff;
                ucSrcMask = 0x80 >> (tx & 7);
                for (ty=0; ty<pBBEP->height; ty++) {
                    s = &pBuffer[(tx>>3) + (ty * iPitch)];
                    if ((s[0] & ucSrcMask) == 0) uc &= ~ucDstMask;
                    ucDstMask >>= 1;
                    if (ucDstMask == 0) {
                        *d++ = (uc ^ ucInvert);
                        ucDstMask = 0x80;
                        uc = 0xff;
                    }
                } // for ty
                *d++ = (uc ^ ucInvert); // store final partial byte
                bbepWriteData(pBBEP, u8Cache, (pBBEP->native_width+7)/8);
            } // for x
            break;
    } // switch on orientation
} /* bbepWriteImage() */
//
// Write a region of the local framebuffer to the eink display
// it will be rounded to byte boundaries and must be in the native orientation
// of the display.
//
void bbepWriteRegion(BBEPDISP *pBBEP, int16_t x, int16_t y, int16_t w, int16_t h, int plane)
{
uint8_t *s;
int ty, iPitch;

    if (pBBEP == NULL || x < 0 || y < 0 || x + w >  pBBEP->native_width || y + h > pBBEP->native_height) return;
    if (pBBEP->iFlags) return; // DEBUG - only 1-bpp support for now
    iPitch = (pBBEP->native_width+7)/8;
    bbepSetAddrWindow(pBBEP, x, y, w, h);
    bbepStartWrite(pBBEP, plane);
    s = pBBEP->ucScreen;
    s += (iPitch * y);
    s += x/8;
    for (ty=0; ty<h; ty++) {
        bbepWriteData(pBBEP, s, (w+7)/8);
        s += iPitch; 
    }
} /* bbepWriteRegion() */

//
// Write the local copy of the memory plane(s) to the eink's internal framebuffer
//
int bbepWritePlane(BBEPDISP *pBBEP, int iPlane, int bInvert)
{
    uint8_t ucCMD1, ucCMD2;
    int iOffset;
    
    if (pBBEP == NULL || pBBEP->ucScreen == NULL || iPlane < PLANE_0 || iPlane > PLANE_FALSE_DIFF) {
        return BBEP_ERROR_BAD_PARAMETER;
    }
// Some panels need to be told their resolution before you can write data
// to the internal framebuffer (e.g. 4.26" 800x480), otherwise the writes
// are ignored.
    if (pBBEP->iFlags & BBEP_NEEDS_EXTRA_INIT) {
        bbepSendCMDSequence(pBBEP, pBBEP->pInitFull);
    }

    if (pBBEP->native_width != 792) { // special case of split buffer
        bbepSetAddrWindow(pBBEP, 0,0, pBBEP->native_width, pBBEP->native_height);
    }

    if (pBBEP->iFlags & BBEP_4BPP_DATA) { // special case for some 2 and 3-color panels
        if (pBBEP->iFlags & BBEP_3COLOR) {
            bbepWriteImage4bppSpecial(pBBEP, 0x10);
        } else {
            bbepWriteImage1to4bpp(pBBEP, 0x10, pBBEP->ucScreen, bInvert);
        }
        return BBEP_SUCCESS;
    }
    if (pBBEP->iFlags & BBEP_7COLOR) {
        if (pBBEP->iFlags & BBEP_SPLIT_BUFFER) { // dual cable EPD
           bbepWriteImage4bppDual(pBBEP, 0x10);
        } else {
           bbepWriteImage4bpp(pBBEP, 0x10);
        }
        return BBEP_SUCCESS;
    }
    if (pBBEP->iFlags & BBEP_4COLOR) { // 4-color only has 1 way to go
        bbepWriteImage2bpp(pBBEP, 0x10);
        return BBEP_SUCCESS;
    }
    if (pBBEP->chip_type == BBEP_CHIP_UC81xx) {
        if (pBBEP->iFlags & BBEP_RED_SWAPPED) {
            ucCMD1 = UC8151_DTM1;
            ucCMD2 = UC8151_DTM2;
        } else {
            ucCMD1 = UC8151_DTM2;
            ucCMD2 = UC8151_DTM1;
        }
    } else {
        ucCMD1 = SSD1608_WRITE_RAM;
        ucCMD2 = SSD1608_WRITE_ALTRAM;
    }
    iOffset = ((pBBEP->native_width+7)>>3) * pBBEP->native_height;
    if (pBBEP->iFlags & BBEP_3COLOR && iPlane == PLANE_DUPLICATE) {
        iPlane = PLANE_BOTH;
    } 
    if (pBBEP->native_width == 792) { // special case
        switch (iPlane) {
            case PLANE_0:
                bbepWriteHalf(pBBEP, ucCMD1, pBBEP->ucScreen, bInvert);
                bbepWriteHalf(pBBEP, ucCMD2, NULL, 0);
                bbepWriteHalf(pBBEP, ucCMD1 | 0x80, pBBEP->ucScreen, bInvert);
                bbepWriteHalf(pBBEP, ucCMD2 | 0x80, NULL, 0);
                break;
            case PLANE_1:
                bbepWriteHalf(pBBEP, ucCMD2, &pBBEP->ucScreen[iOffset], bInvert);
                bbepWriteHalf(pBBEP, ucCMD2 | 0x80, &pBBEP->ucScreen[iOffset], bInvert);    
                break;
            case PLANE_0_TO_1:
                bbepWriteHalf(pBBEP, ucCMD2, pBBEP->ucScreen, bInvert);
                bbepWriteHalf(pBBEP, ucCMD2 | 0x80, pBBEP->ucScreen, bInvert);
                break;
            case PLANE_BOTH:
                bbepWriteHalf(pBBEP, ucCMD1, pBBEP->ucScreen, bInvert);
                if (pBBEP->iFlags & BBEP_HAS_SECOND_PLANE) {
                    bbepWriteHalf(pBBEP, ucCMD2, &pBBEP->ucScreen[iOffset], bInvert);
                } else { // write 0's if no plane memory
                    bbepWriteHalf(pBBEP, ucCMD2, NULL, 0); 
                }
                bbepWriteHalf(pBBEP, ucCMD1 | 0x80, pBBEP->ucScreen, bInvert);
                if (pBBEP->iFlags & BBEP_HAS_SECOND_PLANE) {
                    bbepWriteHalf(pBBEP, ucCMD2 | 0x80, &pBBEP->ucScreen[iOffset], bInvert);
                } else { // write 0's if no plane memory
                    bbepWriteHalf(pBBEP, ucCMD2 | 0x80, NULL, 0);
                }
                break;
            case PLANE_DUPLICATE:
                bbepWriteHalf(pBBEP, ucCMD1, pBBEP->ucScreen, bInvert);
                bbepWriteHalf(pBBEP, ucCMD1 | 0x80, pBBEP->ucScreen, bInvert);
                bbepWriteHalf(pBBEP, ucCMD2, pBBEP->ucScreen, bInvert);
                bbepWriteHalf(pBBEP, ucCMD2 | 0x80, pBBEP->ucScreen, bInvert);
                break;

            case PLANE_FALSE_DIFF: // send inverted image to 'old' plane
                bbepWriteHalf(pBBEP, ucCMD1, pBBEP->ucScreen, 0);
                bbepWriteHalf(pBBEP, ucCMD1 | 0x80, pBBEP->ucScreen, 0);
                bbepWriteHalf(pBBEP, ucCMD2, pBBEP->ucScreen, 1);
                bbepWriteHalf(pBBEP, ucCMD2 | 0x80, pBBEP->ucScreen, 1);
                break;
        } // switch on plane option
    } else { // 'normal' panels
        switch (iPlane) {
            case PLANE_0:
                bbepWriteImage(pBBEP, ucCMD1, pBBEP->ucScreen, bInvert);
                break;
            case PLANE_1:
                bbepWriteImage(pBBEP, ucCMD2, &pBBEP->ucScreen[iOffset], bInvert);
                break;
            case PLANE_0_TO_1:
                bbepWriteImage(pBBEP, ucCMD2, pBBEP->ucScreen, bInvert);
                break;
            case PLANE_BOTH:
                bbepWriteImage(pBBEP, ucCMD1, pBBEP->ucScreen, bInvert);
                if (pBBEP->iFlags & BBEP_HAS_SECOND_PLANE) {
                    bbepWriteImage(pBBEP, ucCMD2, &pBBEP->ucScreen[iOffset], bInvert);
                }
                break;
            case PLANE_DUPLICATE:
                bbepWriteImage(pBBEP, ucCMD1, pBBEP->ucScreen, bInvert);
                bbepWriteImage(pBBEP, ucCMD2, pBBEP->ucScreen, bInvert);
                break;

            case PLANE_FALSE_DIFF: // send inverted image to 'old' plane
                bbepWriteImage(pBBEP, ucCMD1, pBBEP->ucScreen, 0);
                bbepWriteImage(pBBEP, ucCMD2, pBBEP->ucScreen, 1);
                break;
        } // switch on plane option
    } // normal panels
    return BBEP_SUCCESS;
} /* bbepWritePlane() */

#endif // __BB_EP__
