commit 5dfa5fbed53401821eea7343a631e6f68ade581a Author: imi415 Date: Sun Jan 10 20:20:14 2021 +0800 Initial commit. diff --git a/README.md b/README.md new file mode 100644 index 0000000..42969b5 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Driver library for DKE Group 2.13" EPD + +## Supported model: +* `DKE DEPG0213RHS75AF1CP` + +## Usage Example: + +Config initialization: + +```C + depg0213_epd_t epd = { + .user_data = &hspi2, + .cb = { + .reset_cb = _epd_reset_cb, + .poll_busy_cb = _epd_poll_busy, + .write_cmd_cb = _epd_write_cmd_cb, + .write_data_cb = _epd_write_data_cb + } + }; + + depg0213_epd_init(&epd); +``` + +Callbacks: + +```C +depg0213_ret_t _epd_reset_cb(void *handle) { + // Implement your hard reset function here, + // RESET pin needs to be pulled low for at least 200us. + return DEPG0213_OK; +} + +depg0213_ret_t _epd_poll_busy(void *handle) { + // Return DEPG0213_ERROR if timeout or error occurred. + // Implement your GPIO read function here. + return DEPG0213_OK; +} + +depg0213_ret_t _epd_write_cmd_cb(void *handle, uint8_t *cmd, uint8_t len) { + // handle is the user pointer + // The first byte of the command need to set D/CX to low, + // while the rest of the command parameters requires D/CX to be high. + // Implement your SPI transmit function here. + return DEPG0213_OK; +} + +depg0213_ret_t _epd_write_data_cb(void *handle, uint8_t *data, uint16_t len) { + // handle is the user pointer + // Set D/CX to high while transmitting data. + // Implement your SPI transmit function here. + return DEPG0213_OK; +} + +``` + +## Notes: + +Pixels of this panel is organized in the similar way as SSD1306, but has 212 columns and 13 pages. +Therefore, when `HORIZONTAL` or `HORIZONTAL_INVERSE` modes are selected, the frame needs to be +transferred as pages. However, this controller chip has the function which can change the +self-increment direction of the internal pointer, so `VERTICAL` modes are not affected anyway. + +![A figure explaining the layout of the panel](assets/panel_organization.png) \ No newline at end of file diff --git a/assets/panel_organization.png b/assets/panel_organization.png new file mode 100644 index 0000000..5685b02 Binary files /dev/null and b/assets/panel_organization.png differ diff --git a/depg0213_epd.c b/depg0213_epd.c new file mode 100644 index 0000000..92c9d97 --- /dev/null +++ b/depg0213_epd.c @@ -0,0 +1,231 @@ +#include "depg0213_epd.h" + +uint8_t depg0213_dke_init_sequence[] = { + 0x01, 0x74, 0x54, // Set analog block control + 0x01, 0x7E, 0x3B, // Set digital block control + 0x03, 0x01, 0xF9, 0x00, 0x00, // Set display size and driver output control + 0x01, 0x3C, 0x01, // Set border + 0x01, 0x2C, 0x5A, // Set VCOM value + 0x01, 0x03, 0x17, // Gate voltage settings + 0x03, 0x04, 0x41, 0xAC, 0x32, // Source voltage settings + 0x01, 0x3A, 0x02, // Frame setting 1 + 0x01, 0x3B, 0x0D, // Frame setting 2 + 0x01, 0x18, 0x80, // Set built-in temperature sensor + 0x01, 0x22, 0xB1, // Load LUT 1 + 0x00, 0x20 // Load LUT 2 +}; + +uint8_t depg0213_dke_lut_full[] = { + 0x80,0x60,0x40,0x00,0x00,0x00,0x00, //LUT0: BB: VS 0 ~7 + 0x10,0x60,0x20,0x00,0x00,0x00,0x00, //LUT1: BW: VS 0 ~7 + 0x80,0x60,0x40,0x00,0x00,0x00,0x00, //LUT2: WB: VS 0 ~7 + 0x10,0x60,0x20,0x00,0x00,0x00,0x00, //LUT3: WW: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT4: VCOM: VS 0 ~7 + + 0x03,0x03,0x00,0x00,0x02, // TP0 A~D RP0 + 0x09,0x09,0x00,0x00,0x02, // TP1 A~D RP1 + 0x03,0x03,0x00,0x00,0x02, // TP2 A~D RP2 + 0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3 + 0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4 + 0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5 + 0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6 +}; + +uint8_t depg0213_dke_lut_part[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT0: BB: VS 0 ~7 + 0x80,0x00,0x00,0x00,0x00,0x00,0x00, //LUT1: BW: VS 0 ~7 + 0x40,0x00,0x00,0x00,0x00,0x00,0x00, //LUT2: WB: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT3: WW: VS 0 ~7 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT4: VCOM: VS 0 ~7 + + 0x0A,0x00,0x00,0x00,0x00, // TP0 A~D RP0 + 0x00,0x00,0x00,0x00,0x00, // TP1 A~D RP1 + 0x00,0x00,0x00,0x00,0x00, // TP2 A~D RP2 + 0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3 + 0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4 + 0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5 + 0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6 +}; + + +depg0213_ret_t _depg0213_software_reset(depg0213_epd_t *epd) { + uint8_t sw_reset_cmd = 0x12; // SW RST + + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, &sw_reset_cmd, 0x01)); + + return DEPG0213_OK; +} + +depg0213_ret_t _depg0213_init_seq(depg0213_epd_t *epd) { + uint16_t i = 0; + + while(i < sizeof(DEPG0213_PANEL_SELECTION)) { + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, &DEPG0213_PANEL_SELECTION[i + 1], DEPG0213_PANEL_SELECTION[i] + 1)); + i += DEPG0213_PANEL_SELECTION[i] + 2; + } + + return DEPG0213_OK; +} + +#if(!DEPG0213_LUT_OTP) + +depg0213_ret_t _depg0213_load_lut(depg0213_epd_t *epd) { + uint8_t lut_command = 0x32; + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, &lut_command, 0x01)); + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, DEPG0213_LUT_FULL_SELECTION, 70)); + + return DEPG0213_OK; +} + +#endif + +depg0213_ret_t depg0213_epd_init(depg0213_epd_t *epd) { + // HW Reset + DEPG0213_ERROR_CHECK(epd->cb.reset_cb(epd->user_data)); + DEPG0213_ERROR_CHECK(epd->cb.poll_busy_cb(epd->user_data)); + + // SW Reset + DEPG0213_ERROR_CHECK(_depg0213_software_reset(epd)); + DEPG0213_ERROR_CHECK(epd->cb.poll_busy_cb(epd->user_data)); + + DEPG0213_ERROR_CHECK(depg0213_epd_direction(epd, DEPG0213_VERTICAL)); + + DEPG0213_ERROR_CHECK(_depg0213_init_seq(epd)); + DEPG0213_ERROR_CHECK(epd->cb.poll_busy_cb(epd->user_data)); + +#if(!DEPG0213_LUT_OTP) + DEPG0213_ERROR_CHECK(_depg0213_load_lut(epd)); +#endif + + epd->deep_sleep = 0; + + return DEPG0213_OK; +} + +depg0213_ret_t depg0213_epd_update(depg0213_epd_t *epd) { + + if(epd->deep_sleep) { + DEPG0213_ERROR_CHECK(depg0213_epd_init(epd)); + } + + uint8_t command[2] = { 0x22, 0xC7 }; + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, command, 0x02)); + command[0] = 0x20; + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, command, 0x01)); + DEPG0213_ERROR_CHECK(epd->cb.poll_busy_cb(epd->user_data)); + + return DEPG0213_OK; +} + +depg0213_ret_t depg0213_epd_load(depg0213_epd_t *epd, uint8_t *bw_image, uint8_t *red_image) { + + if(epd->deep_sleep) { + DEPG0213_ERROR_CHECK(depg0213_epd_init(epd)); + } + + uint8_t command[5] = { 0x4E, 0x0C, 0x4F, 0xD3, 0x00 }; + + switch(epd->direction) { + case DEPG0213_VERTICAL_INVERSE: + // X->0x00 + command[1] = 0x00; + + // Y->0x00 + command[3] = 0x00; + break; + case DEPG0213_HORIZONTAL: + // X->0x00 + command[1] = 0x00; + + // NO CHANGE FOR Y AXIS + break; + case DEPG0213_HORIZONTAL_INVERSE: + // NO CHANGE FOR X AXIS + + // Y-> 0x00 + command[3] = 0x00; + break; + case DEPG0213_VERTICAL: + default: + break; + } + + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, command, 0x02)); + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, &command[2], 0x03)); + uint8_t wr_command = 0x24; + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, &wr_command, 0x01)); + DEPG0213_ERROR_CHECK(epd->cb.write_data_cb(epd->user_data, bw_image, 2756)); // 104 / 8 * 212 + wr_command = 0x26; + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, &wr_command, 0x01)); + DEPG0213_ERROR_CHECK(epd->cb.write_data_cb(epd->user_data, red_image, 2756)); // 104 / 8 * 212 + + DEPG0213_ERROR_CHECK(depg0213_epd_update(epd)); + + return DEPG0213_OK; +} + +depg0213_ret_t depg0213_epd_deepsleep(depg0213_epd_t *epd) { + if(epd->deep_sleep) return DEPG0213_OK; + + uint8_t deepsleep_command[2] = { 0x10, 0x01 }; + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, deepsleep_command, 0x02)); + + epd->deep_sleep = 1; + + return DEPG0213_OK; +} + +depg0213_ret_t depg0213_epd_direction(depg0213_epd_t *epd, depg0213_direction_t direction) { + + // Default mode: VERTICAL, X: 0x0C->0x00, Y: 0xD3->0x00, AM-|Y-|X- + uint8_t cmd_ram_x_address[] = { 0x44, 0x0C, 0x00 }; // CMD, START, END + uint8_t cmd_ram_y_address[] = { 0x45, 0xD3, 0x00, 0x00, 0x00 }; // CMD, START_L, START_H, END_L, END_H + uint8_t cmd_data_entry[] = { 0x11, 0x00 }; // CMD, SCAN_X->DESC, SCAN_Y->DESC, AM->0 [2:0]=AM|Y+|X+ + + switch(direction) { + case DEPG0213_VERTICAL_INVERSE: + // START: 0x00, END: 0x0C + cmd_ram_x_address[1] = 0x00; + cmd_ram_x_address[2] = 0x0C; + + // START: 0x00, END: 0xD3 + cmd_ram_y_address[1] = 0x00; + cmd_ram_y_address[3] = 0xD3; + + // AM-|Y+|X+ + cmd_data_entry[1] = 0x03; + break; + case DEPG0213_HORIZONTAL: + // START: 0x00, END: 0x0C + cmd_ram_x_address[1] = 0x00; + cmd_ram_x_address[2] = 0x0C; + + // NO CHANGE FOR Y AXIS + + // AM+|Y-|X+ + cmd_data_entry[1] = 0x05; + break; + case DEPG0213_HORIZONTAL_INVERSE: + // NO CHANGE FOR X AXIS + + // START: 0x00, END: 0xD3 + cmd_ram_y_address[1] = 0x00; + cmd_ram_y_address[3] = 0xD3; + + // AM+|Y+|X- + cmd_data_entry[1] = 0x06; + break; + case DEPG0213_VERTICAL: + default: + break; + } + + // Write mode to screen. + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, cmd_ram_x_address, 0x03)); + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, cmd_ram_y_address, 0x05)); + DEPG0213_ERROR_CHECK(epd->cb.write_cmd_cb(epd->user_data, cmd_data_entry, 0x02)); + + epd->direction = direction; + + return DEPG0213_OK; +} \ No newline at end of file diff --git a/depg0213_epd.h b/depg0213_epd.h new file mode 100644 index 0000000..502f4cd --- /dev/null +++ b/depg0213_epd.h @@ -0,0 +1,51 @@ +#ifndef __depg0213_EPD_H +#define __depg0213_EPD_H + +#include + +#define DEPG0213_PANEL_SELECTION depg0213_dke_init_sequence + +#define DEPG0213_LUT_OTP 0 + +#if(!DEPG0213_LUT_OTP) + +#define DEPG0213_LUT_FULL_SELECTION depg0213_dke_lut_full +#define DEPG0213_LUT_PART_SELECTION depg0213_dke_lut_part + +#endif + +typedef enum { + DEPG0213_OK, + DEPG0213_ERROR +} depg0213_ret_t; + +typedef enum { + DEPG0213_VERTICAL, + DEPG0213_HORIZONTAL, + DEPG0213_VERTICAL_INVERSE, + DEPG0213_HORIZONTAL_INVERSE +} depg0213_direction_t; + +typedef struct { + depg0213_ret_t (*reset_cb)(void *handle); + depg0213_ret_t (*write_cmd_cb)(void *handle, uint8_t *cmd, uint8_t len); + depg0213_ret_t (*write_data_cb)(void *handle, uint8_t *data, uint16_t len); + depg0213_ret_t (*poll_busy_cb)(void *handle); +} depg0213_cb_t; + +typedef struct { + void *user_data; + uint8_t deep_sleep; + depg0213_direction_t direction; + depg0213_cb_t cb; +} depg0213_epd_t; + +#define DEPG0213_ERROR_CHECK(x) if(x != DEPG0213_OK) return DEPG0213_ERROR + +depg0213_ret_t depg0213_epd_init(depg0213_epd_t *epd); +depg0213_ret_t depg0213_epd_update(depg0213_epd_t *epd); +depg0213_ret_t depg0213_epd_load(depg0213_epd_t *epd, uint8_t *bw_image, uint8_t *red_image); +depg0213_ret_t depg0213_epd_deepsleep(depg0213_epd_t *epd); +depg0213_ret_t depg0213_epd_direction(depg0213_epd_t *epd, depg0213_direction_t direction); + +#endif \ No newline at end of file