commit 75095ea8cb930b42a4424c7bf553175e002e3f83 Author: imi415 Date: Thu Nov 4 19:24:55 2021 +0800 Added WFH0420CZ35 driver. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25eb886 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/cmake-build-* + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0065f6b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) + +project(epd-spi) + +set(EPD_SOURCES + "src/epd_common.c" + "src/epd_wfh0420cz35.c" +) + +set(EPD_INCLUDES + "include" +) + +add_library(${PROJECT_NAME} ${EPD_SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC ${EPD_INCLUDES}) diff --git a/include/epd_common.h b/include/epd_common.h new file mode 100644 index 0000000..44b28e8 --- /dev/null +++ b/include/epd_common.h @@ -0,0 +1,27 @@ +#ifndef EPD_COMMON_H +#define EPD_COMMON_H + +#include +#include + +typedef enum { EPD_OK = 0, EPD_FAIL } epd_ret_t; + +typedef struct { + epd_ret_t (*write_command_cb)(void *handle, uint8_t *command, uint32_t len); + epd_ret_t (*write_data_cb)(void *handle, uint8_t *data, uint32_t len); + epd_ret_t (*reset_cb)(void *handle); + epd_ret_t (*poll_busy_cb)(void *handle); + epd_ret_t (*delay_cb)(void *handle, uint32_t msec); +} epd_cb_t; + +typedef struct { + uint32_t x_start; + uint32_t x_end; + uint32_t y_start; + uint32_t y_end; +} epd_coord_t; + +#define EPD_ASSERT(x) if(!(x)) for(;;) { /* ABORT. */} +#define EPD_ERROR_CHECK(x) if(x != EPD_OK) return EPD_FAIL + +#endif \ No newline at end of file diff --git a/include/epd_private.h b/include/epd_private.h new file mode 100644 index 0000000..54188ca --- /dev/null +++ b/include/epd_private.h @@ -0,0 +1,8 @@ +#ifndef EPD_PRIVATE_H +#define EPD_PRIVATE_H + +#include "epd_common.h" + +epd_ret_t epd_common_execute_sequence(epd_cb_t *cb, void *user_data, uint8_t *seq, uint32_t seq_len); + +#endif \ No newline at end of file diff --git a/include/epd_wfh0420cz35.h b/include/epd_wfh0420cz35.h new file mode 100644 index 0000000..82abfca --- /dev/null +++ b/include/epd_wfh0420cz35.h @@ -0,0 +1,13 @@ +#ifndef EPD_WFH0420CZ35_H +#define EPD_WFH0420CZ35_h + +#include "epd_common.h" + +typedef struct { + void *user_data; + epd_cb_t cb; +} epd_wfh0420_t; + +epd_ret_t epd_wfh0420_upload(epd_wfh0420_t *epd, epd_coord_t *coord, uint8_t *red_data, uint8_t *bw_data); + +#endif \ No newline at end of file diff --git a/src/epd_common.c b/src/epd_common.c new file mode 100644 index 0000000..fcad200 --- /dev/null +++ b/src/epd_common.c @@ -0,0 +1,23 @@ +#include "epd_common.h" + +/** + * @brief Execute command sequence. + * Sequence format: 1 byte parameter length, 1 byte command, [length] bytes params. + * Parameter length does not include command itself. + * @param cb epd_cb_t callback + * @param user_data user pointer + * @param seq sequence array + * @param seq_len sequence length + * @return epd_ret_t + */ +epd_ret_t epd_common_execute_sequence(epd_cb_t *cb, void *user_data, uint8_t *seq, uint32_t seq_len) { + EPD_ASSERT(cb->write_command_cb != NULL); + + uint32_t i = 0; + while(i < seq_len) { + EPD_ERROR_CHECK(cb->write_command_cb(user_data, &seq[i + 1], seq[i] + 1)); + i += seq[i] + 2; + } + + return EPD_OK; +} \ No newline at end of file diff --git a/src/epd_wfh0420cz35.c b/src/epd_wfh0420cz35.c new file mode 100644 index 0000000..fc6bf39 --- /dev/null +++ b/src/epd_wfh0420cz35.c @@ -0,0 +1,118 @@ +#include "epd_wfh0420cz35.h" + +#include "epd_private.h" + +#define DTM1 0x10 +#define DTM2 0x13 + +static uint8_t epd_wfh0420_init_sequence[] = { + 0x03, 0x06, 0x17, 0x17, 0x17, +}; + +static epd_ret_t epd_wfh0420_init(epd_wfh0420_t *epd) { + EPD_ASSERT(epd); + EPD_ASSERT(epd->cb.reset_cb); + EPD_ASSERT(epd->cb.write_command_cb); + EPD_ASSERT(epd->cb.poll_busy_cb); + + EPD_ERROR_CHECK(epd->cb.reset_cb(epd->user_data)); + + EPD_ERROR_CHECK(epd_common_execute_sequence(&epd->cb, epd->user_data, epd_wfh0420_init_sequence, + sizeof(epd_wfh0420_init_sequence))); + + // Issue power on command + uint8_t tx_buf[2] = {0x04, 0x00}; + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 0x01)); + + EPD_ERROR_CHECK(epd->cb.poll_busy_cb(epd->user_data)); + + // Issue panel setting command, LUT from OTP. + tx_buf[0] = 0x00; + tx_buf[1] = 0x0F; + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 0x02)); + + return EPD_OK; +} + +static epd_ret_t epd_wfh0420_partial(epd_wfh0420_t *epd, epd_coord_t *coord) { + EPD_ASSERT(epd); + EPD_ASSERT(epd->cb.write_command_cb); + + // Partial In + uint8_t tx_buf[10] = {0x91, 0x00}; + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 1)); + + // Partial window + tx_buf[0] = 0x90; + tx_buf[1] = (coord->x_start >> 8U) & 0x01U; + tx_buf[2] = coord->x_start & 0xF8U; // Bank number, last 3 LSBs must be zero. + tx_buf[3] = (coord->x_end >> 8U) & 0x01U; + tx_buf[4] = coord->x_end & 0xF8U; // Same as above. + tx_buf[5] = (coord->y_start >> 8U) & 0x01U; + tx_buf[6] = coord->y_start & 0xFFU; + tx_buf[7] = (coord->y_end >> 8U) & 0x01U; + tx_buf[8] = coord->y_end & 0xFFU; + tx_buf[9] = 0x01; // Only scan partial area. + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 10)); + + return EPD_OK; +} + +static epd_ret_t epd_wfh0420_dtm(epd_wfh0420_t *epd, uint8_t dtm_num, uint8_t *data, uint32_t len) { + EPD_ASSERT(epd); + EPD_ASSERT(epd->cb.write_command_cb); + + uint8_t tx_buf[2] = {dtm_num}; + + // Issue DTM1 or DTM2 command + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 0x01)); + // Send data + EPD_ERROR_CHECK(epd->cb.write_data_cb(epd->user_data, data, len)); + // Issue DSP command. + tx_buf[0] = 0x11; + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 0x01)); + + return EPD_OK; +} + +static epd_ret_t epd_wfh0420_update_and_sleep(epd_wfh0420_t *epd) { + EPD_ASSERT(epd); + EPD_ASSERT(epd->cb.write_command_cb); + EPD_ASSERT(epd->cb.delay_cb); + + // Issue DRF command + uint8_t tx_buf[2] = {0x12, 0x00}; + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 0x01)); + + // Delay 100ms and wait for busy + EPD_ERROR_CHECK(epd->cb.delay_cb(epd->user_data, 100)); + EPD_ERROR_CHECK(epd->cb.poll_busy_cb(epd->user_data)); + + // Issue POF command + tx_buf[0] = 0x02; + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 0x01)); + EPD_ERROR_CHECK(epd->cb.poll_busy_cb(epd->user_data)); + + // Issue DSLP command + tx_buf[0] = 0x07; + tx_buf[1] = 0xA5; + EPD_ERROR_CHECK(epd->cb.write_command_cb(epd->user_data, tx_buf, 0x02)); + + return EPD_OK; +} + +epd_ret_t epd_wfh0420_upload(epd_wfh0420_t *epd, epd_coord_t *coord, uint8_t *red_data, uint8_t *bw_data) { + EPD_ERROR_CHECK(epd_wfh0420_init(epd)); + EPD_ERROR_CHECK(epd_wfh0420_partial(epd, coord)); + + uint8_t bank_start = coord->x_start / 8; // Wrap to bank + uint8_t bank_end = coord->x_end / 8; // Wrap to bank + uint32_t tx_len = (bank_end - bank_start + 1) * (coord->y_end - coord->y_start + 1); + + EPD_ERROR_CHECK(epd_wfh0420_dtm(epd, DTM1, bw_data, tx_len)); + EPD_ERROR_CHECK(epd_wfh0420_dtm(epd, DTM2, red_data, tx_len)); + + EPD_ERROR_CHECK(epd_wfh0420_update_and_sleep(epd)); + + return EPD_OK; +} \ No newline at end of file