diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 73e38daa..bb474e1d 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -28,6 +28,7 @@ add_subdirectory(icp10125) add_subdirectory(scd4x) add_subdirectory(hub75) add_subdirectory(uc8151) +add_subdirectory(uc8159) add_subdirectory(uc8151_legacy) add_subdirectory(pwm) add_subdirectory(servo) @@ -37,4 +38,3 @@ add_subdirectory(vl53l5cx) add_subdirectory(pcf85063a) add_subdirectory(pms5003) add_subdirectory(sh1107) - diff --git a/drivers/uc8159/CMakeLists.txt b/drivers/uc8159/CMakeLists.txt new file mode 100644 index 00000000..97c2e239 --- /dev/null +++ b/drivers/uc8159/CMakeLists.txt @@ -0,0 +1 @@ +include(uc8159.cmake) \ No newline at end of file diff --git a/drivers/uc8159/uc8159.cmake b/drivers/uc8159/uc8159.cmake new file mode 100644 index 00000000..30cc9083 --- /dev/null +++ b/drivers/uc8159/uc8159.cmake @@ -0,0 +1,10 @@ +set(DRIVER_NAME uc8159) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${DRIVER_NAME}.cpp) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_spi) diff --git a/drivers/uc8159/uc8159.cpp b/drivers/uc8159/uc8159.cpp new file mode 100644 index 00000000..a8aa8c9b --- /dev/null +++ b/drivers/uc8159/uc8159.cpp @@ -0,0 +1,231 @@ +#include "uc8159.hpp" + +#include +#include + +namespace pimoroni { + + enum reg { + PSR = 0x00, + PWR = 0x01, + POF = 0x02, + PFS = 0x03, + PON = 0x04, + BTST = 0x06, + DSLP = 0x07, + DTM1 = 0x10, + DSP = 0x11, + DRF = 0x12, + IPC = 0x13, + PLL = 0x30, + TSC = 0x40, + TSE = 0x41, + TSW = 0x42, + TSR = 0x43, + CDI = 0x50, + LPD = 0x51, + TCON = 0x60, + TRES = 0x61, + DAM = 0x65, + REV = 0x70, + FLG = 0x71, + AMV = 0x80, + VV = 0x81, + VDCS = 0x82, + PWS = 0xE3, + TSSET = 0xE5 + }; + + bool UC8159::is_busy() { + return !gpio_get(BUSY); + } + + void UC8159::busy_wait() { + while(is_busy()) { + tight_loop_contents(); + } + } + + void UC8159::reset() { + gpio_put(RESET, 0); sleep_ms(10); + gpio_put(RESET, 1); sleep_ms(10); + busy_wait(); + } + + void UC8159::init() { + // configure spi interface and pins + spi_init(spi, 3'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); + }; + + void UC8159::setup() { + reset(); + busy_wait(); + + /* + Resolution Setting + 10bit horizontal followed by a 10bit vertical resolution + we'll let struct.pack do the work here and send 16bit values + life is too short for manual bit wrangling + */ + uint16_t resolution[2] = { + __builtin_bswap16(width), + __builtin_bswap16(height) + }; + + command(TRES, 4, { + (uint8_t *)resolution + }); + + /* + Panel Setting + 0b11000000 = Resolution select, 0b00 = 640x480, our panel is 0b11 = 600x448 + 0b00100000 = LUT selection, 0 = ext flash, 1 = registers, we use ext flash + 0b00010000 = Ignore + 0b00001000 = Gate scan direction, 0 = down, 1 = up (default) + 0b00000100 = Source shift direction, 0 = left, 1 = right (default) + 0b00000010 = DC-DC converter, 0 = off, 1 = on + 0b00000001 = Soft reset, 0 = Reset, 1 = Normal (Default) + 0b11 = 600x448 + 0b10 = 640x400 + */ + + command(PSR, { + (uint8_t)((width == 640) ? 0b10101111 : 0b11101111), + 0x08 // UC8159 7-colour + }); + + command(PWR, { + (0x06 << 3) | // ??? - not documented in UC8159 datasheet + (0x01 << 2) | // SOURCE_INTERNAL_DC_DC + (0x01 << 1) | // GATE_INTERNAL_DC_DC + 0x01, // LV_SOURCE_INTERNAL_DC_DC + 0x00, // VGx_20V + 0x23, // UC8159_7C + 0x23 // UC8159_7C + }); + + /* + Set the PLL clock frequency to 50Hz + 0b11000000 = Ignore + 0b00111000 = M + 0b00000111 = N + PLL = 2MHz * (M / N) + PLL = 2MHz * (7 / 4) + PLL = 2,800,000 ??? + */ + command(PLL, 0x3C); + + command(TSE, 0x00); + + /* + VCOM and Data Interval setting + 0b11100000 = Vborder control (0b001 = LUTB voltage) + 0b00010000 = Data polarity + 0b00001111 = Vcom and data interval (0b0111 = 10, default) + */ + command(CDI, (1 << 5) | 0x17); + + /* + Gate/Source non-overlap period + 0b11110000 = Source to Gate (0b0010 = 12nS, default) + 0b00001111 = Gate to Source + */ + command(TCON, 0x22); + + // Disable externalflash + command(DAM, 0x00); + + command(PWS, 0xAA); + + command(PFS, 0x00); + + //power_off(); + } + + void UC8159::power_off() { + busy_wait(); + command(POF); // turn off + } + + void UC8159::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 UC8159::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 UC8159::command(uint8_t reg, std::initializer_list values) { + command(reg, values.size(), (uint8_t *)values.begin()); + } + + void UC8159::pixel(int x, int y, int v) { + // bounds check + if(x < 0 || y < 0 || x >= width || y >= height) return; + + // pointer to byte in framebuffer that contains this pixel + uint8_t *p = &frame_buffer[(x / 2) + (y * width / 2)]; + + uint8_t o = (x & 0b1) * 4; // bit offset within byte + uint8_t m = ~(0b1111 << o); // bit mask for byte + uint8_t b = v << o; // bit value shifted to position + + *p &= m; // clear bits + *p |= b; // set value + } + + void UC8159::update(bool blocking) { + if(blocking) { + busy_wait(); + } + + setup(); + + command(DTM1, (width * height) / 2, frame_buffer); // transmit framebuffer + busy_wait(); + + command(PON); // turn on + busy_wait(); + + command(DRF); // start display refresh + busy_wait(); + + if(blocking) { + busy_wait(); + + command(POF); // turn off + } + } + +} diff --git a/drivers/uc8159/uc8159.hpp b/drivers/uc8159/uc8159.hpp new file mode 100644 index 00000000..b4f83438 --- /dev/null +++ b/drivers/uc8159/uc8159.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include "pico/stdlib.h" +#include "hardware/spi.h" +#include "hardware/gpio.h" + +#include "../../common/pimoroni_common.hpp" + +namespace pimoroni { + + class UC8159 { + //-------------------------------------------------- + // Variables + //-------------------------------------------------- + private: + // screen properties + uint16_t width; + uint16_t height; + + // highest possible resolution is 160x296 which at 1 bit per pixel + // requires 5920 bytes of frame buffer + //uint8_t frame_buffer[5920] = {0}; + uint8_t *frame_buffer; + + spi_inst_t *spi = PIMORONI_SPI_DEFAULT_INSTANCE; + + // interface pins with our standard defaults where appropriate + uint CS = SPI_BG_FRONT_CS; + uint DC = 27; + uint SCK = SPI_DEFAULT_SCK; + uint MOSI = SPI_DEFAULT_MOSI; + uint BUSY = 26; + uint RESET = 25; + + bool inverted = false; + + public: + UC8159(uint16_t width, uint16_t height) : + width(width), height(height), frame_buffer(new uint8_t[width * height / 2]) { + } + + UC8159(uint16_t width, uint16_t height, uint8_t *frame_buffer) : + width(width), height(height), frame_buffer(frame_buffer) { + } + + UC8159(uint16_t width, uint16_t height, + spi_inst_t *spi, + uint CS, uint DC, uint SCK, uint MOSI, + uint BUSY = PIN_UNUSED, uint RESET = PIN_UNUSED) : + width(width), height(height), + frame_buffer(new uint8_t[width * height / 2]), + spi(spi), + CS(CS), DC(DC), SCK(SCK), MOSI(MOSI), BUSY(BUSY), RESET(RESET) {} + + UC8159(uint16_t width, uint16_t height, + uint8_t *frame_buffer, + spi_inst_t *spi, + uint CS, uint DC, uint SCK, uint MOSI, + uint BUSY = PIN_UNUSED, uint RESET = PIN_UNUSED) : + width(width), height(height), + frame_buffer(frame_buffer), + spi(spi), + CS(CS), DC(DC), SCK(SCK), MOSI(MOSI), BUSY(BUSY), RESET(RESET) {} + + + //-------------------------------------------------- + // Methods + //-------------------------------------------------- + public: + void init(); + void busy_wait(); + bool is_busy(); + void reset(); + void setup(); + void power_off(); + + void command(uint8_t reg, size_t len, const uint8_t *data); + void command(uint8_t reg, std::initializer_list values); + void command(uint8_t reg, const uint8_t data) {command(reg, 0, &data);}; + void command(uint8_t reg) {command(reg, 0, nullptr);}; + void data(size_t len, const uint8_t *data); + + void update(bool blocking = true); + void pixel(int x, int y, int v); + }; + +}