From f4fd82bd7edae114d83a5d387055ad0df3d44b6a Mon Sep 17 00:00:00 2001 From: AVartanyan <42091996+AramVartanyan@users.noreply.github.com> Date: Mon, 26 Sep 2022 23:49:24 +0300 Subject: [PATCH] Add support for SSD1680 e-paper controller The driver is based on the construction of IL3820 (SSD1608), which is very similar from command point of view. It is tested on ESP32-S2 MCU and GoodDisplay GDEY029T94. --- CMakeLists.txt | 2 + README.md | 1 + component.mk | 1 + lvgl_helpers.c | 2 +- lvgl_helpers.h | 2 + lvgl_spi_conf.h | 1 + lvgl_tft/Kconfig | 20 ++- lvgl_tft/disp_driver.c | 8 + lvgl_tft/disp_driver.h | 2 + lvgl_tft/ssd1680.c | 373 +++++++++++++++++++++++++++++++++++++++++ lvgl_tft/ssd1680.h | 114 +++++++++++++ 11 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 lvgl_tft/ssd1680.c create mode 100644 lvgl_tft/ssd1680.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 407802a..157555b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,8 @@ elseif(CONFIG_LV_TFT_DISPLAY_CONTROLLER_FT81X) list(APPEND SOURCES "lvgl_tft/FT81x.c") elseif(CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820) list(APPEND SOURCES "lvgl_tft/il3820.c") +elseif(CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680) + list(APPEND SOURCES "lvgl_tft/ssd1680.c") elseif(CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A) list(APPEND SOURCES "lvgl_tft/jd79653a.c") elseif(CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D) diff --git a/README.md b/README.md index 522c0e1..e407186 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ swap of RGB565 color on the LVGL configuration menuconfig (it's not handled auto | IL3820 | e-Paper | SPI | 1: 1byte per pixel | No | | UC8151D/ GoodDisplay GDEW0154M10 DES | e-Paper | SPI | 1: 1byte per pixel | No | | FitiPower JD79653A/ GoodDisplay GDEW0154M09 | e-Paper | SPI | 1: 1byte per pixel | No | +| SSD1680 / GoodDisplay GDEY029T94 | e-Paper | SPI | 1: 1byte per pixel | No | ## Supported indev controllers diff --git a/component.mk b/component.mk index 0017d0b..78511a8 100644 --- a/component.mk +++ b/component.mk @@ -25,6 +25,7 @@ $(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1306),lvgl_tft/ssd1 $(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_FT81X),lvgl_tft/EVE_commands.o) $(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_FT81X),lvgl_tft/FT81x.o) $(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820),lvgl_tft/il3820.o) +$(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1306),lvgl_tft/ssd1680.o) $(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A),lvgl_tft/jd79653a.o) $(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D),lvgl_tft/uc8151d.o) $(call compile_only_if,$(CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875),lvgl_tft/ra8875.o) diff --git a/lvgl_helpers.c b/lvgl_helpers.c index 57ab7dd..3656103 100644 --- a/lvgl_helpers.c +++ b/lvgl_helpers.c @@ -154,7 +154,7 @@ bool lvgl_spi_driver_init(int host, int dma_channel, int quadwp_pin, int quadhd_pin) { - assert((0 <= host) && (SPI_HOST_MAX > host)); + //assert((0 <= host) && (SPI_HOST_MAX > host)); const char *spi_names[] = { "SPI1_HOST", "SPI2_HOST", "SPI3_HOST" }; diff --git a/lvgl_helpers.h b/lvgl_helpers.h index c010174..61a111a 100644 --- a/lvgl_helpers.h +++ b/lvgl_helpers.h @@ -66,6 +66,8 @@ extern "C" { #define DISP_BUF_SIZE (LV_HOR_RES_MAX * DISP_BUF_LINES) #elif defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820) #define DISP_BUF_SIZE (LV_VER_RES_MAX * IL3820_COLUMNS) +#elif defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680) +#define DISP_BUF_SIZE (LV_VER_RES_MAX * SSD1680_COLUMNS) #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875 #define DISP_BUF_SIZE (LV_HOR_RES_MAX * 40) #elif defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_GC9A01) diff --git a/lvgl_spi_conf.h b/lvgl_spi_conf.h index 662be98..1bcfe15 100644 --- a/lvgl_spi_conf.h +++ b/lvgl_spi_conf.h @@ -126,6 +126,7 @@ extern "C" { defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_SH1107) || \ defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_FT81X) || \ defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820) || \ + defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680) || \ defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A) || \ defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_ILI9163C) diff --git a/lvgl_tft/Kconfig b/lvgl_tft/Kconfig index 4a74ad2..3990c81 100644 --- a/lvgl_tft/Kconfig +++ b/lvgl_tft/Kconfig @@ -148,6 +148,14 @@ menu "LVGL TFT Display controller" bool help IL3820 epaper display controller. + + config LV_TFT_DISPLAY_CONTROLLER_SSD1680 + bool + help + SSD1680 e-paper display controller for GoodDisplay GDEY029T94. + The resolution must be set as per a portrait mode: + LV_HOR_RES_MAX -> 128 and LV_VER_RES_MAX -> 296 + And the actual use mode currently is Landskape only. config LV_TFT_DISPLAY_CONTROLLER_JD79653A bool @@ -326,6 +334,12 @@ menu "LVGL TFT Display controller" select LV_TFT_DISPLAY_CONTROLLER_IL3820 select LV_TFT_DISPLAY_PROTOCOL_SPI select LV_TFT_DISPLAY_MONOCHROME + config LV_TFT_DISPLAY_USER_CONTROLLER_SSD1680 + bool "SSD1680" + select LV_TFT_DISPLAY_CONTROLLER_SSD1680 + select LV_TFT_DISPLAY_PROTOCOL_SPI + select LV_TFT_DISPLAY_MONOCHROME + select LV_TFT_USE_CUSTOM_SPI_CLK_DIVIDER config LV_TFT_DISPLAY_USER_CONTROLLER_JD79653A bool "JD79653A" select LV_TFT_DISPLAY_CONTROLLER_JD79653A @@ -521,7 +535,7 @@ menu "LVGL TFT Display controller" default LV_TFT_SPI_CLK_DIVIDER_5 if LV_TFT_DISPLAY_CONTROLLER_ILI9481 default LV_TFT_SPI_CLK_DIVIDER_3 if LV_TFT_DISPLAY_CONTROLLER_HX8357 default LV_TFT_SPI_CLK_DIVIDER_10 if LV_TFT_DISPLAY_CONTROLLER_SH1107 - default LV_TFT_SPI_CLK_DIVIDER_16 if LV_TFT_DISPLAY_CONTROLLER_JD79653A || LV_TFT_DISPLAY_CONTROLLER_UC8151D + default LV_TFT_SPI_CLK_DIVIDER_16 if LV_TFT_DISPLAY_CONTROLLER_JD79653A || LV_TFT_DISPLAY_CONTROLLER_UC8151D || LV_TFT_DISPLAY_CONTROLLER_SSD1680 default LV_TFT_SPI_CLK_DIVIDER_2 config LV_TFT_SPI_CLK_DIVIDER_1 @@ -907,8 +921,8 @@ menu "LVGL TFT Display controller" Configure the display Reset pin here. config LV_DISP_PIN_BUSY - int "GPIO for Busy" if LV_TFT_DISPLAY_CONTROLLER_IL3820 || LV_TFT_DISPLAY_CONTROLLER_JD79653A || LV_TFT_DISPLAY_CONTROLLER_UC8151D - default 35 if LV_TFT_DISPLAY_CONTROLLER_IL3820 || LV_TFT_DISPLAY_CONTROLLER_JD79653A || LV_TFT_DISPLAY_CONTROLLER_UC8151D + int "GPIO for Busy" if LV_TFT_DISPLAY_CONTROLLER_IL3820 || LV_TFT_DISPLAY_CONTROLLER_JD79653A || LV_TFT_DISPLAY_CONTROLLER_UC8151D || LV_TFT_DISPLAY_CONTROLLER_SSD1680 + default 35 if LV_TFT_DISPLAY_CONTROLLER_IL3820 || LV_TFT_DISPLAY_CONTROLLER_JD79653A || LV_TFT_DISPLAY_CONTROLLER_UC8151D || LV_TFT_DISPLAY_CONTROLLER_SSD1680 default 35 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 default 21 if IDF_TARGET_ESP32C3 diff --git a/lvgl_tft/disp_driver.c b/lvgl_tft/disp_driver.c index 153ca31..11548ef 100644 --- a/lvgl_tft/disp_driver.c +++ b/lvgl_tft/disp_driver.c @@ -33,6 +33,8 @@ void *disp_driver_init(void) FT81x_init(); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 il3820_init(); +#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680 + ssd1680_init(); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875 ra8875_init(); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_GC9A01 @@ -99,6 +101,8 @@ void disp_driver_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * FT81x_flush(drv, area, color_map); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 il3820_flush(drv, area, color_map); +#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680 + ssd1680_flush(drv, area, color_map); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875 ra8875_flush(drv, area, color_map); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_GC9A01 @@ -122,6 +126,8 @@ void disp_driver_rounder(lv_disp_drv_t * disp_drv, lv_area_t * area) sh1107_rounder(disp_drv, area); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 il3820_rounder(disp_drv, area); +#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680 + ssd1680_rounder(disp_drv, area); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A jd79653a_lv_rounder_cb(disp_drv, area); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D @@ -140,6 +146,8 @@ void disp_driver_set_px(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_ sh1107_set_px_cb(disp_drv, buf, buf_w, x, y, color, opa); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 il3820_set_px_cb(disp_drv, buf, buf_w, x, y, color, opa); +#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680 + ssd1680_set_px_cb(disp_drv, buf, buf_w, x, y, color, opa); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A jd79653a_lv_set_fb_cb(disp_drv, buf, buf_w, x, y, color, opa); #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D diff --git a/lvgl_tft/disp_driver.h b/lvgl_tft/disp_driver.h index 2f5bcdf..62b9fca 100644 --- a/lvgl_tft/disp_driver.h +++ b/lvgl_tft/disp_driver.h @@ -42,6 +42,8 @@ extern "C" { #include "FT81x.h" #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 #include "il3820.h" +#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1680 +#include "ssd1680.h" #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_RA8875 #include "ra8875.h" #elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_GC9A01 diff --git a/lvgl_tft/ssd1680.c b/lvgl_tft/ssd1680.c new file mode 100644 index 0000000..8fb43b8 --- /dev/null +++ b/lvgl_tft/ssd1680.c @@ -0,0 +1,373 @@ +/** +@file ssd1680.c +@brief Tested with GoodDisplay GDEY029T94 e-paper 2.9" monochrome display +@version 1.0 +@date 2022-09-20 +@author Aram Vartanyan, based on the il3820 driver by Juergen Kienhoefer + */ + +/********************* + * INCLUDES + *********************/ +#include "disp_spi.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "ssd1680.h" + +/********************* + * DEFINES + *********************/ + #define TAG "SSD1680" + +/** + * ssd1680 compatible EPD controller driver. + */ + +#define BIT_SET(a,b) ((a) |= (1U<<(b))) +#define BIT_CLEAR(a,b) ((a) &= ~(1U<<(b))) + +/* Number of pixels? */ +#define SSD1680_PIXEL (LV_HOR_RES_MAX * LV_VER_RES_MAX) + +#define EPD_PANEL_NUMOF_COLUMS EPD_PANEL_WIDTH +#define EPD_PANEL_NUMOF_ROWS_PER_PAGE 8 + +/* Are pages the number of bytes to represent the panel width? in bytes */ +#define EPD_PANEL_NUMOF_PAGES (EPD_PANEL_HEIGHT / EPD_PANEL_NUMOF_ROWS_PER_PAGE) + +#define SSD1680_PANEL_FIRST_PAGE 0 +#define SSD1680_PANEL_LAST_PAGE (EPD_PANEL_NUMOF_PAGES - 1) +#define SSD1680_PANEL_FIRST_GATE 0 +#define SSD1680_PANEL_LAST_GATE (EPD_PANEL_NUMOF_COLUMS - 1) + +#define SSD1680_PIXELS_PER_BYTE 8 +#define EPD_PARTIAL_CNT 5 + +//uint8_t ssd1680_scan_mode = SSD1680_DATA_ENTRY_XIYDX; //another approach +uint8_t ssd1680_scan_mode = SSD1680_DATA_ENTRY_XIYIY; //as per the il3820 driver + +static uint8_t partial_counter = 0; + +static uint8_t ssd1680_border_init[] = {0x05}; //init +static uint8_t ssd1680_border_part[] = {0x80}; //partial update +//A2 - 1 Follow LUT; A1:A0 - 01 LUT1; +//static uint8_t ssd1680_border[] = {0x03}; //old LUT3 + +/* Static functions */ + +static void ssd1680_update_display(bool isPartial); +static inline void ssd1680_set_window( uint16_t sx, uint16_t ex, uint16_t ys, uint16_t ye); +static inline void ssd1680_set_cursor(uint16_t sx, uint16_t ys); +static inline void ssd1680_waitbusy(int wait_ms); +static inline void ssd1680_hw_reset(void); +static inline void ssd1680_command_mode(void); +static inline void ssd1680_data_mode(void); +static inline void ssd1680_write_cmd(uint8_t cmd, uint8_t *data, size_t len); +static inline void ssd1680_send_cmd(uint8_t cmd); +static inline void ssd1680_send_data(uint8_t *data, uint16_t length); + +/* Required by LVGL */ +void ssd1680_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + /* Each byte holds the data of 8 pixels, linelen is the number of bytes + * we need to cover a line of the display. */ + size_t linelen = EPD_PANEL_WIDTH / 8; //SSD1680_COLUMNS = (EPD_PANEL_WIDTH / 8) + uint8_t *buffer = (uint8_t *) color_map; + uint16_t x_addr_counter = 0; + uint16_t y_addr_counter = 0; + + /* Set the cursor at the beginning of the graphic RAM */ +#if defined (CONFIG_LV_DISPLAY_ORIENTATION_PORTRAIT) + x_addr_counter = EPD_PANEL_WIDTH - 1; + y_addr_counter = EPD_PANEL_HEIGHT - 1; +#endif + ssd1680_init(); + + if (!partial_counter) { + ESP_LOGD(TAG, "Refreshing in FULL"); + ssd1680_send_cmd(SSD1680_CMD_WRITE1_RAM); + for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++){ + ssd1680_send_data(buffer, linelen); + buffer += SSD1680_COLUMNS; + } + ssd1680_send_cmd(SSD1680_CMD_WRITE2_RAM); + for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++){ + ssd1680_send_data(buffer, linelen); + buffer += SSD1680_COLUMNS; + } + ssd1680_update_display(false); + partial_counter = EPD_PARTIAL_CNT; + } else { + //update partial + ssd1680_hw_reset(); + ssd1680_write_cmd(SSD1680_CMD_BWF_CTRL, ssd1680_border_part, 1); + ssd1680_set_window(area->x1, area->x2, area->y1, area->y2); + ssd1680_set_cursor(x_addr_counter, y_addr_counter); + + ssd1680_send_cmd(SSD1680_CMD_WRITE1_RAM); + for(size_t row = 0; row <= (EPD_PANEL_HEIGHT - 1); row++) { + ssd1680_send_data(buffer, linelen); + buffer += SSD1680_COLUMNS; //(128/8)x296 = 4736 + } + ssd1680_update_display(true); + partial_counter--; + } + ssd1680_deep_sleep(); + /* IMPORTANT!!! + * Inform the graphics library that you are ready with the flushing */ + lv_disp_flush_ready(drv); +} + +/* Specify the start/end positions of the window address in the X and Y + * directions by an address unit. + * + * @param sx: X Start position. + * @param ex: X End position. + * @param ys: Y Start position. + * @param ye: Y End position. + */ +static inline void ssd1680_set_window( uint16_t sx, uint16_t ex, uint16_t ys, uint16_t ye) +{ + uint8_t tmp[4] = {0}; + + tmp[0] = sx / 8; + tmp[1] = ex / 8; //0x0F-->(15+1)*8=128 -> ex = EPD_PANEL_WIDTH + //tmp[1] = (ex / 8) - 1; + + /* Set X address start/end */ + ssd1680_write_cmd(SSD1680_CMD_RAM_XPOS_CTRL, tmp, 2); + + //0x0127-->(295+1)=296 + //a % b = a - (a/b)*b + tmp[0] = ys % 256; + tmp[1] = ys / 256; + tmp[2] = ye % 256; + tmp[3] = ye / 256; + /* Set Y address start/end */ + ssd1680_write_cmd(SSD1680_CMD_RAM_YPOS_CTRL, tmp, 4); +} + +/* Make initial settings for the RAM X and Y addresses in the address counter + * (AC). + * + * @param sx: RAM X address counter. + * @param ys: RAM Y address counter. + */ +static inline void ssd1680_set_cursor(uint16_t sx, uint16_t ys) +{ + uint8_t tmp[2] = {0}; + + tmp[0] = sx / 8; + ssd1680_write_cmd(SSD1680_CMD_RAM_XPOS_CNTR, tmp, 1); + + tmp[0] = ys % 256; + tmp[1] = ys / 256; + ssd1680_write_cmd(SSD1680_CMD_RAM_YPOS_CNTR, tmp, 2); +} + +/* After sending the RAM content we need to send the commands: + * - Display Update Control 2 + * - Master Activation + */ +static void ssd1680_update_display(bool isPartial) +{ + uint8_t tmp = 0; + + if(isPartial) { + tmp = 0xFF; //Display mode 2 - Partial update + } else { + tmp = 0xF7; //Display mode 1 - Full update + } + /* Display Update Control */ + ssd1680_write_cmd(SSD1680_CMD_UPDATE_CTRL2, &tmp, 1); + /* Activate Display Update Sequence */ + ssd1680_write_cmd(SSD1680_CMD_MASTER_ACTIVATION, NULL, 0); + /* Poll BUSY signal. */ + ssd1680_waitbusy(SSD1680_WAIT); +} + +/* Rotate the display by "software" when using PORTRAIT orientation. + * BIT_SET(byte_index, bit_index) clears the bit_index pixel at byte_index of + * the display buffer. + * BIT_CLEAR(byte_index, bit_index) sets the bit_index pixel at the byte_index + * of the display buffer. */ +void ssd1680_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t* buf, + lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, + lv_color_t color, lv_opa_t opa) +{ + uint16_t byte_index = 0; + uint8_t bit_index = 0; + +#if defined (CONFIG_LV_DISPLAY_ORIENTATION_PORTRAIT) + //This part doesn't work for now. Display prints random dots. + byte_index = x + ((y >> 3) * EPD_PANEL_HEIGHT); + bit_index = y & 0x7; + + if (color.full) { + BIT_SET(buf[byte_index], 7 - bit_index); + } else { + uint16_t mirrored_idx = (EPD_PANEL_HEIGHT - x) + ((y >> 3) * EPD_PANEL_HEIGHT); + BIT_CLEAR(buf[mirrored_idx], 7 - bit_index); + } +#elif defined (CONFIG_LV_DISPLAY_ORIENTATION_LANDSCAPE) + byte_index = y + ((x >> 3) * EPD_PANEL_HEIGHT); + bit_index = x & 0x7; + + if (color.full) { + BIT_SET(buf[byte_index], 7 - bit_index); + } else { + BIT_CLEAR(buf[byte_index], 7 - bit_index); + } +#else +#error "Unsupported orientation used" +#endif +} + +/* Required by LVGL */ +void ssd1680_rounder(lv_disp_drv_t * disp_drv, lv_area_t *area) +{ + area->x1 = area->x1 & ~(0x7); + area->x2 = area->x2 | (0x7); + + /* Update the areas as needed. + * For example it makes the area to start only on 8th rows and have Nx8 pixel height.*/ +} + +/* main initialization routine */ +void ssd1680_init(void) +{ + uint8_t tmp[3] = {0}; + + /* Initialize non-SPI GPIOs */ + gpio_pad_select_gpio(SSD1680_DC_PIN); + gpio_set_direction(SSD1680_DC_PIN, GPIO_MODE_OUTPUT); + + gpio_pad_select_gpio(SSD1680_BUSY_PIN); + gpio_set_direction(SSD1680_BUSY_PIN, GPIO_MODE_INPUT); + +#if SSD1680_USE_RST + gpio_pad_select_gpio(SSD1680_RST_PIN); + gpio_set_direction(SSD1680_RST_PIN, GPIO_MODE_OUTPUT); + + /* Harware reset */ + ssd1680_hw_reset(); +#endif + + /* Busy wait for the BUSY signal to go low */ + ssd1680_waitbusy(SSD1680_WAIT); + + /* Software reset */ + ssd1680_write_cmd(SSD1680_CMD_SW_RESET, NULL, 0); + + ssd1680_waitbusy(SSD1680_WAIT); + + /* Driver output control */ + tmp[0] = (EPD_PANEL_HEIGHT - 1) & 0xFF; //0x27 + tmp[1] = (EPD_PANEL_HEIGHT >> 8 ); //0x01 + tmp[2] = 0x00; // GD = 0; SM = 0; TB = 0; //0x00 + ssd1680_write_cmd(SSD1680_CMD_GDO_CTRL, tmp, 3); + + /* Configure entry mode */ + ssd1680_write_cmd(SSD1680_CMD_ENTRY_MODE, &ssd1680_scan_mode, 1); + + /* Configure the window */ + ssd1680_set_window(0, EPD_PANEL_WIDTH - 1, 0, EPD_PANEL_HEIGHT - 1); + + /* Select border waveform for VBD */ + ssd1680_write_cmd(SSD1680_CMD_BWF_CTRL, ssd1680_border_init, 1); + + /* Display update control 1 */ + tmp[0] = 0x00; //A7:0 Normal RAM content option + tmp[1] = 0x80; //B7 Source Output Mode: Available source from S8 to S167 + tmp[2] = 0x00; //do not send + ssd1680_write_cmd(SSD1680_CMD_UPDATE_CTRL1, tmp, 2); + + /* Read Build-in Temperature sensor */ + tmp[0] = 0x80; //A7:0 Internal sensor + tmp[1] = 0x00; //do not send + ssd1680_write_cmd(SSD1680_CMD_READ_INT_TEMP, tmp, 1); + + /*set RAM x (0) and y (295) address count */ + ssd1680_set_cursor(0, EPD_PANEL_HEIGHT - 1); + ssd1680_waitbusy(SSD1680_WAIT); +} + +/* Enter deep sleep mode */ +void ssd1680_deep_sleep(void) +{ + uint8_t data[] = {0x01}; + + /* Wait for the BUSY signal to go low */ + ssd1680_waitbusy(SSD1680_WAIT); + + ssd1680_write_cmd(SSD1680_CMD_SLEEP_MODE1, data, 1); + vTaskDelay(100 / portTICK_RATE_MS); // 100ms delay +} + +static inline void ssd1680_waitbusy(int wait_ms) +{ + int i = 0; + + vTaskDelay(10 / portTICK_RATE_MS); // 10ms delay + + for(i = 0; i < (wait_ms * 10); i++) { + if(gpio_get_level(SSD1680_BUSY_PIN) != SSD1680_BUSY_LEVEL) { + return; + } + vTaskDelay(10 / portTICK_RATE_MS); + } + ESP_LOGE( TAG, "busy exceeded %dms", i*10 ); +} + +/* Set HWRESET */ +static inline void ssd1680_hw_reset(void) +{ + gpio_set_level(SSD1680_RST_PIN, 0); + vTaskDelay(SSD1680_RESET_DELAY / portTICK_RATE_MS); + gpio_set_level(SSD1680_RST_PIN, 1); + vTaskDelay(SSD1680_RESET_DELAY / portTICK_RATE_MS); +} + +/* Set DC signal to command mode */ +static inline void ssd1680_command_mode(void) +{ + gpio_set_level(SSD1680_DC_PIN, 0); +} + +/* Set DC signal to data mode */ +static inline void ssd1680_data_mode(void) +{ + gpio_set_level(SSD1680_DC_PIN, 1); +} + +static inline void ssd1680_write_cmd(uint8_t cmd, uint8_t *data, size_t len) +{ + disp_wait_for_pending_transactions(); + ssd1680_command_mode(); + disp_spi_send_data(&cmd, 1); + + if (data != NULL) { + ssd1680_data_mode(); + disp_spi_send_data(data, len); + } +} + +/* Send cmd to the display */ +static inline void ssd1680_send_cmd(uint8_t cmd) +{ + disp_wait_for_pending_transactions(); + ssd1680_command_mode(); + disp_spi_send_data(&cmd, 1); +} + +/* Send length bytes of data to the display */ +static inline void ssd1680_send_data(uint8_t *data, uint16_t length) +{ + disp_wait_for_pending_transactions(); + ssd1680_data_mode(); + disp_spi_send_colors(data, length); +} + diff --git a/lvgl_tft/ssd1680.h b/lvgl_tft/ssd1680.h new file mode 100644 index 0000000..c033ce5 --- /dev/null +++ b/lvgl_tft/ssd1680.h @@ -0,0 +1,114 @@ +/** + * @file ssd1680.h + * + */ + +#ifndef SSD1680_H +#define SSD1680_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef LV_LVGL_H_INCLUDE_SIMPLE +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif +#include "sdkconfig.h" + +#define EPD_PANEL_WIDTH LV_HOR_RES_MAX /* 128 */ +#define EPD_PANEL_HEIGHT LV_VER_RES_MAX /* 296 */ + +/* 128 = panel width */ +#define SSD1680_COLUMNS (EPD_PANEL_WIDTH / 8) + +#define SSD1680_DC_PIN CONFIG_LV_DISP_PIN_DC +#define SSD1680_RST_PIN CONFIG_LV_DISP_PIN_RST +#define SSD1680_USE_RST CONFIG_LV_DISP_USE_RST +#define SSD1680_BUSY_PIN CONFIG_LV_DISP_PIN_BUSY +#define SSD1680_BUSY_LEVEL 1 //chip is busy if the pin level is high + +/* SSD1680 commands */ +#define SSD1680_CMD_GDO_CTRL 0x01 //Driver output control +#define SSD1680_CMD_GDV_CTRL 0x03 +#define SSD1680_CMD_SDV_CTRL 0x04 +#define SSD1680_CMD_SOFTSTART 0x0c +#define SSD1680_CMD_GSCAN_START 0x0f + +#define SSD1680_CMD_SLEEP_MODE1 0x10 //enter deep sleep 1 - retain RAM +#define SSD1680_CMD_SLEEP_MODE2 0x11 //enter deep sleep 2 - cannot retain RAM +//After entering Deep sleep Mode, BUSY will stay high. +//To Exit Deep Sleep a HWRESET should be sent. + +#define SSD1680_CMD_ENTRY_MODE 0x11 //Data entry mode +#define SSD1680_CMD_SW_RESET 0x12 //SWRESET +#define SSD1680_CMD_TSENS_CTRL 0x1a +#define SSD1680_CMD_READ_INT_TEMP 0x18 //Read built-in temperature sensor +#define SSD1680_CMD_MASTER_ACTIVATION 0x20 //Activate Display Update Sequence +#define SSD1680_CMD_UPDATE_CTRL1 0x21 //Display update control +#define SSD1680_CMD_UPDATE_CTRL2 0x22 //Display Update Control +#define SSD1680_CMD_WRITE1_RAM 0x24 //Write Black (0) and Wite (1) image to RAM +#define SSD1680_CMD_WRITE2_RAM 0x26 //Write RED (1) and NonRED (0) image to RAM +#define SSD1680_CMD_VCOM_SENSE 0x28 +#define SSD1680_CMD_VCOM_SENSE_DURATON 0x29 +#define SSD1680_CMD_PRGM_VCOM_OTP 0x2a +#define SSD1680_CMD_VCOM_VOLTAGE 0x2c +#define SSD1680_CMD_PRGM_WS_OTP 0x30 +#define SSD1680_CMD_UPDATE_LUT 0x32 +#define SSD1680_CMD_PRGM_OTP_SELECTION 0x36 +#define SSD1680_CMD_WRITE_DISPL_OPT 0x37 +#define SSD1680_CMD_BWF_CTRL 0x3c //BorderWavefrom +#define SSD1680_CMD_RAM_XPOS_CTRL 0x44 //set Ram-X address start/end position +#define SSD1680_CMD_RAM_YPOS_CTRL 0x45 //set Ram-Y address start/end position +#define SSD1680_CMD_RAM_XPOS_CNTR 0x4e //set RAM x address count to 0; +#define SSD1680_CMD_RAM_YPOS_CNTR 0x4f //set RAM y address count to 0X199; + +/* Data entry sequence modes */ +#define SSD1680_DATA_ENTRY_MASK 0x07 +#define SSD1680_DATA_ENTRY_XDYDX 0x00 +#define SSD1680_DATA_ENTRY_XIYDX 0x01 +#define SSD1680_DATA_ENTRY_XDYIX 0x02 +#define SSD1680_DATA_ENTRY_XIYIX 0x03 +#define SSD1680_DATA_ENTRY_XDYDY 0x04 +#define SSD1680_DATA_ENTRY_XIYDY 0x05 +#define SSD1680_DATA_ENTRY_XDYIY 0x06 +#define SSD1680_DATA_ENTRY_XIYIY 0x07 + +/* Options for display update */ +#define SSD1680_CTRL1_INITIAL_UPDATE_LL 0x00 +#define SSD1680_CTRL1_INITIAL_UPDATE_LH 0x01 +#define SSD1680_CTRL1_INITIAL_UPDATE_HL 0x02 +#define SSD1680_CTRL1_INITIAL_UPDATE_HH 0x03 + +/* Options for display update sequence */ +#define SSD1680_CTRL2_ENABLE_CLK 0x80 +#define SSD1680_CTRL2_ENABLE_ANALOG 0x40 +#define SSD1680_CTRL2_TO_INITIAL 0x08 +#define SSD1680_CTRL2_TO_PATTERN 0x04 +#define SSD1680_CTRL2_DISABLE_ANALOG 0x02 +#define SSD1680_CTRL2_DISABLE_CLK 0x01 + +#define SSD1680_SLEEP_MODE_DSM 0x01 +#define SSD1680_SLEEP_MODE_PON 0x00 + +/* time constants in ms */ +#define SSD1680_RESET_DELAY 20 //At least 10ms delay +#define SSD1680_BUSY_DELAY 1 +// normal wait time max 20 times x 10ms +#define SSD1680_WAIT 20 + +void ssd1680_init(void); +void ssd1680_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map); +void ssd1680_rounder(lv_disp_drv_t * disp_drv, lv_area_t *area); +void ssd1680_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t* buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa); + +void ssd1680_deep_sleep(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif /* __SSD1680_REGS_H__ */