#include "uc8151.hpp" #include #include namespace pimoroni { enum reg { PSR = 0x00, PWR = 0x01, POF = 0x02, PFS = 0x03, PON = 0x04, PMES = 0x05, BTST = 0x06, DSLP = 0x07, DTM1 = 0x10, DSP = 0x11, DRF = 0x12, DTM2 = 0x13, LUT_VCOM = 0x20, LUT_WW = 0x21, LUT_BW = 0x22, LUT_WB = 0x23, LUT_BB = 0x24, PLL = 0x30, TSC = 0x40, TSE = 0x41, TSR = 0x43, TSW = 0x42, CDI = 0x50, LPD = 0x51, TCON = 0x60, TRES = 0x61, REV = 0x70, FLG = 0x71, AMV = 0x80, VV = 0x81, VDCS = 0x82, PTL = 0x90, PTIN = 0x91, PTOU = 0x92, PGM = 0xa0, APG = 0xa1, ROTP = 0xa2, CCSET = 0xe0, PWS = 0xe3, TSSET = 0xe5 }; bool UC8151::is_busy() { if(BUSY == PIN_UNUSED) return false; return !gpio_get(BUSY); } void UC8151::busy_wait() { while(is_busy()) { tight_loop_contents(); } } void UC8151::reset() { if(RESET == PIN_UNUSED) return; gpio_put(RESET, 0); sleep_ms(10); gpio_put(RESET, 1); sleep_ms(10); busy_wait(); } void UC8151::default_luts() { command(LUT_VCOM, { 0x00, 0x64, 0x64, 0x37, 0x00, 0x01, 0x00, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0x00, 0x64, 0x64, 0x37, 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 }); command(LUT_WW, { 0x54, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0xa8, 0x64, 0x64, 0x37, 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 }); command(LUT_BW, { 0x54, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0xa8, 0x64, 0x64, 0x37, 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 }); command(LUT_WB, { 0xa8, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0x54, 0x64, 0x64, 0x37, 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 }); command(LUT_BB, { 0xa8, 0x64, 0x64, 0x37, 0x00, 0x01, 0x60, 0x8c, 0x8c, 0x00, 0x00, 0x04, 0x54, 0x64, 0x64, 0x37, 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 }); busy_wait(); } void UC8151::medium_luts() { command(LUT_VCOM, { 0x00, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x00, 0x23, 0x23, 0x00, 0x00, 0x02, 0x00, 0x16, 0x16, 0x0d, 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 }); command(LUT_WW, { 0x54, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0xa8, 0x16, 0x16, 0x0d, 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 }); command(LUT_BW, { 0x54, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0xa8, 0x16, 0x16, 0x0d, 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 }); command(LUT_WB, { 0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0x54, 0x16, 0x16, 0x0d, 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 }); command(LUT_BB, { 0xa8, 0x16, 0x16, 0x0d, 0x00, 0x01, 0x60, 0x23, 0x23, 0x00, 0x00, 0x02, 0x54, 0x16, 0x16, 0x0d, 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 }); busy_wait(); } void UC8151::fast_luts() { // 0x3c, 0x00, 0x2b, 0x2b, 0x24, 0x1a, ???? command(LUT_VCOM, { 0x00, 0x04, 0x04, 0x07, 0x00, 0x01, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0x00, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_WW, { 0x54, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0xa8, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_BW, { 0x54, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0xa8, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_WB, { 0xa8, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0x54, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_BB, { 0xa8, 0x04, 0x04, 0x07, 0x00, 0x01, 0x60, 0x0c, 0x0c, 0x00, 0x00, 0x02, 0x54, 0x04, 0x04, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(PLL, { HZ_200 }); busy_wait(); } void UC8151::turbo_luts() { // 0x3c, 0x00, 0x2b, 0x2b, 0x24, 0x1a, ???? command(LUT_VCOM, { 0x00, 0x01, 0x01, 0x02, 0x00, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_WW, { 0x54, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0xa8, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_BW, { 0x54, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0xa8, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_WB, { 0xa8, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0x54, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(LUT_BB, { 0xa8, 0x01, 0x01, 0x02, 0x00, 0x01, 0x60, 0x02, 0x02, 0x00, 0x00, 0x02, 0x54, 0x02, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); command(PLL, { HZ_200 }); busy_wait(); } void UC8151::init() { // configure spi interface and pins spi_init(spi, 12'000'000); gpio_set_function(DC, GPIO_FUNC_SIO); gpio_set_dir(DC, GPIO_OUT); gpio_set_function(CS, GPIO_FUNC_SIO); gpio_set_dir(CS, GPIO_OUT); gpio_put(CS, 1); gpio_set_function(RESET, GPIO_FUNC_SIO); gpio_set_dir(RESET, GPIO_OUT); gpio_put(RESET, 1); gpio_set_function(BUSY, GPIO_FUNC_SIO); gpio_set_dir(BUSY, GPIO_IN); gpio_set_pulls(BUSY, true, false); gpio_set_function(SCK, GPIO_FUNC_SPI); gpio_set_function(MOSI, GPIO_FUNC_SPI); setup(); }; void UC8151::setup() { reset(); uint8_t psr_setting = RES_128x296 | FORMAT_BW | BOOSTER_ON | RESET_NONE; psr_setting |= update_speed == 0 ? LUT_OTP : LUT_REG; psr_setting |= rotation == ROTATE_180 ? SHIFT_LEFT | SCAN_UP : SHIFT_RIGHT | SCAN_DOWN; command(PSR, 1, &psr_setting); switch(update_speed) { case 0: // Note: the defult luts are built in so we don't really need to flash them here // they are preserved above for posterity and reference mostly. break; case 1: medium_luts(); break; case 2: fast_luts(); break; case 3: turbo_luts(); break; default: break; } command(PWR, { VDS_INTERNAL | VDG_INTERNAL, VCOM_VD | VGHL_16V, 0b101011, 0b101011, 0b101011 }); command(PON); // power on busy_wait(); // booster soft start configuration command(BTST, { START_10MS | STRENGTH_3 | OFF_6_58US, START_10MS | STRENGTH_3 | OFF_6_58US, START_10MS | STRENGTH_3 | OFF_6_58US }); command(PFS, { FRAMES_1 }); command(TSE, { TEMP_INTERNAL | OFFSET_0 }); command(TCON, {0x22}); // tcon setting command(CDI, {(uint8_t)(inverted ? 0b10'01'1100 : 0b01'00'1100)}); // vcom and data interval command(PLL, { HZ_100 }); command(POF); busy_wait(); } void UC8151::power_off() { command(POF); } void UC8151::read(uint8_t reg, size_t len, uint8_t *data) { gpio_put(CS, 0); gpio_put(DC, 0); // command mode spi_write_blocking(spi, ®, 1); if(len > 0) { gpio_put(DC, 1); // data mode gpio_set_function(SCK, GPIO_FUNC_SIO); gpio_set_dir(SCK, GPIO_OUT); gpio_set_function(MOSI, GPIO_FUNC_SIO); gpio_set_dir(MOSI, GPIO_IN); for(auto i = 0u; i < len; i++) { int byte = i / 8; int bit = i % 8; gpio_put(SCK, true); bool value = gpio_get(MOSI); data[byte] |= value << (7-bit); gpio_put(SCK, false); } gpio_set_function(SCK, GPIO_FUNC_SPI); gpio_set_function(MOSI, GPIO_FUNC_SPI); } gpio_put(CS, 1); } void UC8151::command(uint8_t reg, size_t len, const uint8_t *data) { gpio_put(CS, 0); gpio_put(DC, 0); // command mode spi_write_blocking(spi, ®, 1); if(len > 0) { gpio_put(DC, 1); // data mode spi_write_blocking(spi, (const uint8_t*)data, len); } gpio_put(CS, 1); } void UC8151::data(size_t len, const uint8_t *data) { gpio_put(CS, 0); gpio_put(DC, 1); // data mode spi_write_blocking(spi, (const uint8_t*)data, len); gpio_put(CS, 1); } void UC8151::command(uint8_t reg, std::initializer_list values) { command(reg, values.size(), (uint8_t *)values.begin()); } bool UC8151::set_update_speed(int update_speed) { this->update_speed = (uint8_t)update_speed; setup(); return true; } uint32_t UC8151::update_time() { switch(update_speed) { case 0: return 4500; case 1: return 2000; case 2: return 800; case 3: return 250; default: return 4500; } } void UC8151::partial_update(PicoGraphics *graphics, Rect region) { // region.y is given in columns ("banks"), which are groups of 8 horiontal pixels // region.x is given in pixels uint8_t *fb = (uint8_t *)graphics->frame_buffer; if(blocking) { busy_wait(); } int cols = region.h / 8; int y1 = region.y / 8; int rows = region.w; int x1 = region.x; uint8_t partial_window[7] = { (uint8_t)(region.y), (uint8_t)(region.y + region.h - 1), (uint8_t)(region.x >> 8), (uint8_t)(region.x & 0xff), (uint8_t)((region.x + region.w - 1) >> 8), (uint8_t)((region.x + region.w - 1) & 0xff), 0b00000001 // PT_SCAN }; command(PON); // turn on command(PTIN); // enable partial mode command(PTL, sizeof(partial_window), partial_window); command(DTM2); for (auto dx = 0; dx < rows; dx++) { int sx = dx + x1; int sy = y1; data(cols, &fb[sy + (sx * (height / 8))]); } command(DSP); // data stop command(DRF); // start display refresh if(blocking) { off(); } } void UC8151::update(PicoGraphics *graphics) { uint8_t *fb = (uint8_t *)graphics->frame_buffer; if(blocking) { busy_wait(); } command(PON); // turn on command(PTOU); // disable partial mode command(DTM2, (width * height) / 8, fb); // transmit framebuffer command(DSP); // data stop command(DRF); // start display refresh if(blocking) { off(); } } void UC8151::off() { busy_wait(); command(POF); // turn off } }