From 3d497800bbb4a069a62d989623ab16ce550ab312 Mon Sep 17 00:00:00 2001 From: imi415 Date: Sat, 22 Jan 2022 20:14:30 +0800 Subject: [PATCH] Added JLX LCD driver. --- CMakeLists.txt | 1 + include/lcd_panel_jlx256128g_920.h | 34 ++++++++ include/oled_panel_elw1501aa.h | 10 ++- src/lcd_panel_jlx256128g_920.c | 132 +++++++++++++++++++++++++++++ src/oled_panel_elw1501aa.c | 52 +++++++++--- 5 files changed, 215 insertions(+), 14 deletions(-) create mode 100644 include/lcd_panel_jlx256128g_920.h create mode 100644 src/lcd_panel_jlx256128g_920.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 081adba..6946a8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ set(EPD_SOURCES "src/epd_common.c" "src/epd_panel_wfh0420cz35.c" "src/epd_panel_gdew042t2.c" + "src/lcd_panel_jlx256128g_920.c" "src/oled_panel_elw1501aa.c" ) diff --git a/include/lcd_panel_jlx256128g_920.h b/include/lcd_panel_jlx256128g_920.h new file mode 100644 index 0000000..f8939e0 --- /dev/null +++ b/include/lcd_panel_jlx256128g_920.h @@ -0,0 +1,34 @@ +#ifndef LCD_PANEL_JLX256128G_920_H +#define LCD_PANEL_JLX256128G_920_H + +#include "epd_common.h" + +/** + * @brief JLX256128G-920 LCD + * Drive/Controller: Sitronix ST75256 + * Resolution: 256x128@2bpp(4 grayscale) + * Link: https://item.taobao.com/item.htm?id=595485128701 + * Datasheet: https://pan.baidu.com/s/1XVAYUiLdCP7uPqK4mrkUxQ Pass: e87n + */ + +typedef enum { + LCD_JLX256128G_MODE_BW = 0x10U, + LCD_JLX256128G_MODE_GS = 0x11U, +} lcd_jlx256128g_mode_t; + +typedef struct { + lcd_jlx256128g_mode_t mode; + uint16_t contrast; +} lcd_jlx256128g_config_t; + +typedef struct { + epd_cb_t cb; + void *user_data; + lcd_jlx256128g_config_t config; +} lcd_jlx256128g_t; + +epd_ret_t lcd_jlx256128g_init(lcd_jlx256128g_t *lcd); +epd_ret_t lcd_jlx256128g_power(lcd_jlx256128g_t *lcd, uint8_t on); +epd_ret_t lcd_jlx256128g_upload(lcd_jlx256128g_t *lcd, epd_coord_t *coord, uint8_t *data); + +#endif \ No newline at end of file diff --git a/include/oled_panel_elw1501aa.h b/include/oled_panel_elw1501aa.h index 65c8fe4..d4e1301 100644 --- a/include/oled_panel_elw1501aa.h +++ b/include/oled_panel_elw1501aa.h @@ -3,9 +3,17 @@ #include "epd_common.h" +/** + * @brief Futaba EWL1501AA(R) PMOLED + * Driver/Controller: Solomon Systech SSD1327 + * Resolution: 128x128@4bpp(16 grayscale) + * Link: https://www.futaba.com/oled/ + * Datasheet: https://www.futaba.com/wp-content/uploads/2021/08/ELW1501AA.pdf + */ + typedef struct { epd_cb_t cb; - void *user_data; + void *user_data; } oled_ewl1501aa_t; epd_ret_t oled_ewl1501aa_init(oled_ewl1501aa_t *ewl); diff --git a/src/lcd_panel_jlx256128g_920.c b/src/lcd_panel_jlx256128g_920.c new file mode 100644 index 0000000..ea9fe2d --- /dev/null +++ b/src/lcd_panel_jlx256128g_920.c @@ -0,0 +1,132 @@ +#include "lcd_panel_jlx256128g_920.h" + +#include "epd_private.h" + +#define PANEL_X_OFFSET 0 +#define PANEL_Y_OFFSET 32 + +static uint8_t lcd_jlx256128g_init_sequence[] = { + 0x00, 0x30, // Extension command EXT[1:0] = 0,0 + 0x00, 0x94, // Set power save mode, SLP = 0 + 0x00, 0x31, // Extension command EXT[1:0] = 0,1 + 0x01, 0xD7, 0x9F, // Set auto-read instruction, XARD = 1 + 0x03, 0x32, 0x00, 0x01, 0x03, // 32 - Set analog circuit, BE[1:0] = 0,1; BS[2:0] = 0,1,1 + 0x10, 0x20, 0x01, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, + 0x10, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F, // 31 - Set grey scale level + 0x04, 0xF0, 0x0D, 0x0D, 0x0D, 0x0D, // Frame rate, 0x0C - 69.0Hz + 0x00, 0x30, // Extension command EXT[1:0] = 0,0 + 0x02, 0x75, 0x00, 0x14, // 7 - Set page address, YS = 0x00, YE = 0x14 + 0x02, 0x15, 0x00, 0xFF, // 8 - Set column address, XS = 0x00, XE = 0xFF + 0x01, 0xBC, 0x00, // 9 - Data scan direction, MV = 0, MX = 0, MY = 0 + 0x03, 0xCA, 0x00, 0x9F, 0x20, // 5 - Display control, CLD = 0, DT = 0x7F, LF[4:0] = 16, FL = 0 + 0x01, 0xF0, 0x10, // 28 - Display mode: DM = 0(Mono) + 0x02, 0x81, 0x36, 0x04, // 21 - Set VOP, VOP = 0x136 + 0x01, 0x20, 0x0B, // 20 - Power control, VB = 1, VF = 1, VR = 1 +}; + +static epd_ret_t lcd_jlx256128g_reset(lcd_jlx256128g_t *lcd) { + EPD_ERROR_CHECK(lcd->cb.reset_cb(lcd->user_data)); + + return EPD_OK; +} + +static epd_ret_t lcd_jlx256128g_window(lcd_jlx256128g_t *lcd, epd_coord_t *coord) { + uint8_t pixel_per_byte = 4; + if (lcd->config.mode == LCD_JLX256128G_MODE_BW) { + pixel_per_byte = 8; + } + + if (coord->y_start % pixel_per_byte != 0 || coord->y_end % pixel_per_byte != pixel_per_byte - 1) { + return EPD_FAIL; + } + + if (coord->x_end > 255 || coord->x_start > coord->x_end) { + return EPD_FAIL; + } + + if (coord->y_end > 127 || coord->y_start > coord->y_end) { + return EPD_FAIL; + } + + uint8_t page_start = (coord->y_start + PANEL_Y_OFFSET) / pixel_per_byte; + uint8_t page_end = (coord->y_end + PANEL_Y_OFFSET) / pixel_per_byte; + + uint8_t col_cmd[] = {0x15, coord->x_start + PANEL_X_OFFSET, coord->x_end + PANEL_X_OFFSET}; + uint8_t page_cmd[] = {0x75, page_start, page_end}; + + EPD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, col_cmd, 0x03)); + EPD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, page_cmd, 0x03)); + + return EPD_OK; +} + +static epd_ret_t lcd_jlx256128g_config(lcd_jlx256128g_t *lcd) { + uint8_t contrast_command[] = {0x81, lcd->config.contrast & 0x3F, (lcd->config.contrast >> 6U) & 0x07}; + uint8_t mode_command[] = {0xF0, lcd->config.mode}; + + EPD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, contrast_command, 0x03)); + EPD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, mode_command, 0x02)); + + epd_coord_t coord = { + .x_start = 0, + .x_end = 255, + .y_start = 0, + .y_end = 127, + }; + + EPD_ERROR_CHECK(lcd_jlx256128g_window(lcd, &coord)); + + return EPD_OK; +} + +static inline uint32_t lcd_jlx256128g_data_size(lcd_jlx256128g_t *lcd, epd_coord_t *coord) { + uint8_t pix_per_byte = 8; + if (lcd->config.mode == LCD_JLX256128G_MODE_GS) { + pix_per_byte = 4; + } + + return (coord->y_end - coord->y_start + 1) * (coord->x_end - coord->x_start + 1) / pix_per_byte; +} + +epd_ret_t lcd_jlx256128g_init(lcd_jlx256128g_t *lcd) { + EPD_ERROR_CHECK(lcd_jlx256128g_reset(lcd)); + EPD_ERROR_CHECK(epd_common_execute_sequence(&lcd->cb, lcd->user_data, lcd_jlx256128g_init_sequence, + sizeof(lcd_jlx256128g_init_sequence))); + EPD_ERROR_CHECK(lcd_jlx256128g_config(lcd)); + EPD_ERROR_CHECK(lcd_jlx256128g_power(lcd, 1)); + + return EPD_OK; +} + +epd_ret_t lcd_jlx256128g_power(lcd_jlx256128g_t *lcd, uint8_t on) { + uint8_t tx_buf[] = {0xAF}; + if (!on) tx_buf[0] = 0xAE; + EPD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, tx_buf, 0x01)); + + return EPD_OK; +} + +epd_ret_t lcd_jlx256128g_upload(lcd_jlx256128g_t *lcd, epd_coord_t *coord, uint8_t *data) { + uint32_t data_size = (lcd->config.mode == LCD_JLX256128G_MODE_GS) ? 8192 : 4096; + if (coord != NULL) { + EPD_ERROR_CHECK(lcd_jlx256128g_window(lcd, coord)); + data_size = lcd_jlx256128g_data_size(lcd, coord); + } + + uint8_t dtm_cmd[] = {0x5C}; + EPD_ERROR_CHECK(lcd->cb.write_command_cb(lcd->user_data, dtm_cmd, 0x01)); + EPD_ERROR_CHECK(lcd->cb.write_data_cb(lcd->user_data, data, data_size)); + + if (coord != NULL) { + epd_coord_t new_coord = { + .x_start = 0, + .x_end = 255, + .y_start = 0, + .y_end = 127, + }; + + EPD_ERROR_CHECK(lcd_jlx256128g_window(lcd, &new_coord)); + } + + return EPD_OK; +} \ No newline at end of file diff --git a/src/oled_panel_elw1501aa.c b/src/oled_panel_elw1501aa.c index 01791a6..0116888 100644 --- a/src/oled_panel_elw1501aa.c +++ b/src/oled_panel_elw1501aa.c @@ -27,14 +27,6 @@ static epd_ret_t oled_ewl1501aa_reset(oled_ewl1501aa_t *oled) { return EPD_OK; } -epd_ret_t oled_ewl1501aa_init(oled_ewl1501aa_t *oled) { - EPD_ERROR_CHECK(oled_ewl1501aa_reset(oled)); - EPD_ERROR_CHECK(epd_common_execute_sequence(&oled->cb, oled->user_data, ewl1501aa_init_sequence, - sizeof(ewl1501aa_init_sequence))); - - return EPD_OK; -} - static epd_ret_t oled_ewl1501aa_window(oled_ewl1501aa_t *oled, epd_coord_t *coord) { if (coord->x_start % 2 != 0 || coord->x_end % 2 == 0) { return EPD_FAIL; @@ -60,29 +52,63 @@ static epd_ret_t oled_ewl1501aa_window(oled_ewl1501aa_t *oled, epd_coord_t *coor } static inline uint32_t oled_ewl1501aa_data_size(epd_coord_t *coord) { - return (coord->x_end - coord->x_start) * (coord->y_end - coord->y_start) / 2; + return (coord->x_end - coord->x_start + 1) * (coord->y_end - coord->y_start + 1) / 2; } +/** + * @brief Initialize OLED panel + * As OLEDs and LCDs need to be driven constantly, also with + * less overhead and higher data rate, initialization sequence + * are moved to this seperate function. Call this function before + * sending framebuffer data. + * @param oled pointer to oled_ewl1501aa_t + * @return epd_ret_t EPD_OK for success, EPD_FAIL for error. + */ +epd_ret_t oled_ewl1501aa_init(oled_ewl1501aa_t *oled) { + EPD_ERROR_CHECK(oled_ewl1501aa_reset(oled)); + EPD_ERROR_CHECK(epd_common_execute_sequence(&oled->cb, oled->user_data, ewl1501aa_init_sequence, + sizeof(ewl1501aa_init_sequence))); + + return EPD_OK; +} + +/** + * @brief Upload frame buffer to screen. + * + * @param oled pointer to oled_ewl1501aa_t + * @param coord pointer to epd_coord_t, can be NULL to upload full frame. + * @param data array pointer to new pixel data. + * @return epd_ret_t EPD_OK for success, EPD_FAIL for error. + */ epd_ret_t oled_ewl1501aa_upload(oled_ewl1501aa_t *oled, epd_coord_t *coord, uint8_t *data) { uint32_t data_size = 8192; - if(coord != NULL) { + if (coord != NULL) { EPD_ERROR_CHECK(oled_ewl1501aa_window(oled, coord)); data_size = oled_ewl1501aa_data_size(coord); } EPD_ERROR_CHECK(oled->cb.write_data_cb(oled->user_data, data, data_size)); - if(coord != NULL) { + if (coord != NULL) { epd_coord_t new_coord = { .x_start = 0, - .x_end = 127, + .x_end = 127, .y_start = 0, - .y_end = 127, + .y_end = 127, }; EPD_ERROR_CHECK(oled_ewl1501aa_window(oled, &new_coord)); } return EPD_OK; } +/** + * @brief Control the OLED drivers. + * LCDs and OLEDs needs to be constant driven in order to + * display a stable image, turn the drivers and source/gate scan on or off. + * + * @param oled pointer to oled_ewl1501aa_t + * @param on 0:off, anything else: on + * @return epd_ret_t EPD_OK for success, EPD_FAIL for error. + */ epd_ret_t oled_ewl1501aa_power(oled_ewl1501aa_t *oled, uint8_t on) { uint8_t cmd[2] = {0xAE, 0x00}; if (on) cmd[0] = 0xAF;