From f4791a0280c5122f35fa2048488e4601f5dd6119 Mon Sep 17 00:00:00 2001 From: Silke Hofstra Date: Sat, 19 Oct 2019 16:35:03 +0200 Subject: [PATCH] epd_bw_spi: add generic black and white SPI e-Paper display driver Add an initial implementation of a generic driver for black and white e-paper displays. Includes parameters for the IL3829 display controller. --- dist/tools/doccheck/generic_exclude_patterns | 3 + drivers/epd_bw_spi/Makefile | 1 + drivers/epd_bw_spi/Makefile.dep | 3 + drivers/epd_bw_spi/Makefile.include | 1 + drivers/epd_bw_spi/epd_bw_spi.c | 342 ++++++++++++++++++ .../epd_bw_spi/include/epd_bw_spi_internal.h | 103 ++++++ .../epd_bw_spi/include/epd_bw_spi_params.h | 205 +++++++++++ drivers/include/epd_bw_spi.h | 266 ++++++++++++++ 8 files changed, 924 insertions(+) create mode 100644 drivers/epd_bw_spi/Makefile create mode 100644 drivers/epd_bw_spi/Makefile.dep create mode 100644 drivers/epd_bw_spi/Makefile.include create mode 100644 drivers/epd_bw_spi/epd_bw_spi.c create mode 100644 drivers/epd_bw_spi/include/epd_bw_spi_internal.h create mode 100644 drivers/epd_bw_spi/include/epd_bw_spi_params.h create mode 100644 drivers/include/epd_bw_spi.h diff --git a/dist/tools/doccheck/generic_exclude_patterns b/dist/tools/doccheck/generic_exclude_patterns index 1b74b8f441..c5a3f4d3d3 100644 --- a/dist/tools/doccheck/generic_exclude_patterns +++ b/dist/tools/doccheck/generic_exclude_patterns @@ -11,6 +11,9 @@ warning: Member BTN[0-9]_PIN \(macro definition\) of warning: Member BTN[0-9]_PORT \(macro definition\) of warning: Member BTN[0-9]_PRESSED \(macro definition\) of warning: Member BTN[0-9]_RELEASED \(macro definition\) of +warning: Member EPD_BW_SPI_CMD_[A-Z0-9_]* \(macro definition\) of +warning: Member EPD_BW_SPI_DISPLAY_UPDATE_OPTION_[A-Z0-9_]* \(macro definition\) of +warning: Member EPD_BW_SPI_WAIT_[A-Z0-9_]* \(macro definition\) of warning: Member FXOS8700_PARAM_ADDR \(macro definition\) of warning: Member FXOS8700_PARAM_I2C \(macro definition\) of warning: Member FXOS8700_PARAM_RENEW_INTERVAL \(macro definition\) of diff --git a/drivers/epd_bw_spi/Makefile b/drivers/epd_bw_spi/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/epd_bw_spi/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/epd_bw_spi/Makefile.dep b/drivers/epd_bw_spi/Makefile.dep new file mode 100644 index 0000000000..d4d37c8c3d --- /dev/null +++ b/drivers/epd_bw_spi/Makefile.dep @@ -0,0 +1,3 @@ +FEATURES_REQUIRED += periph_spi +FEATURES_REQUIRED += periph_gpio +USEMODULE += ztimer_msec diff --git a/drivers/epd_bw_spi/Makefile.include b/drivers/epd_bw_spi/Makefile.include new file mode 100644 index 0000000000..9db55be119 --- /dev/null +++ b/drivers/epd_bw_spi/Makefile.include @@ -0,0 +1 @@ +USEMODULE_INCLUDES += $(RIOTBASE)/drivers/epd_bw_spi/include diff --git a/drivers/epd_bw_spi/epd_bw_spi.c b/drivers/epd_bw_spi/epd_bw_spi.c new file mode 100644 index 0000000000..d5ff4bb371 --- /dev/null +++ b/drivers/epd_bw_spi/epd_bw_spi.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2022 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_epd_bw_spi + * + * @{ + * @file + * @brief Device driver implementation for the epd_bw_spi display controller + * + * @author Silke Hofstra + * @} + */ +#include +#include "byteorder.h" +#include "ztimer.h" + +#include "epd_bw_spi.h" +#include "epd_bw_spi_internal.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +static void epd_bw_spi_cmd_start(epd_bw_spi_params_t *p, uint8_t cmd, bool cont) +{ + DEBUG("[epd_bw_spi] cmd_start: command 0x%02x\n", cmd); + if (gpio_is_valid(p->busy_pin)) { + while ((bool)gpio_read(p->busy_pin) == p->busy_value) {} + } + gpio_clear(p->dc_pin); + spi_transfer_byte(p->spi, p->cs_pin, cont, (uint8_t)cmd); + gpio_set(p->dc_pin); +} + +static void epd_bw_spi_write_cmd(epd_bw_spi_params_t *p, uint8_t cmd, + const uint8_t *params, size_t plen) +{ + spi_acquire(p->spi, p->cs_pin, SPI_MODE_0, p->spi_clk); + epd_bw_spi_cmd_start(p, cmd, plen > 0); + if (plen) { + spi_transfer_bytes(p->spi, p->cs_pin, false, params, NULL, plen); + } + spi_release(p->spi); +} + +static void epd_bw_spi_wait(epd_bw_spi_params_t *p, uint32_t msec) +{ + if (gpio_is_valid(p->busy_pin)) { + DEBUG("[epd_bw_spi] wait: for busy bin\n"); + while ((bool)gpio_read(p->busy_pin) == p->busy_value) {} + } + else { + DEBUG("[epd_bw_spi] wait: for %" PRIu32 " milliseconds\n", msec); + ztimer_sleep(ZTIMER_MSEC, msec); + } +} + +static void epd_bw_spi_control2(epd_bw_spi_params_t *p, uint8_t option, uint32_t wait_msec) +{ + DEBUG("[epd_bw_spi] control2: options 0x%02x, wait %" PRIu32" ms\n", option, wait_msec); + + epd_bw_spi_write_cmd(p, EPD_BW_SPI_CMD_DISPLAY_UPDATE_CONTROL_2, &option, 1); + epd_bw_spi_write_cmd(p, EPD_BW_SPI_CMD_MASTER_ACTIVATION, NULL, 0); + epd_bw_spi_wait(p, wait_msec); +} + +int epd_bw_spi_init(epd_bw_spi_t *dev, const epd_bw_spi_params_t *params) +{ + memcpy(&dev->params, params, sizeof(epd_bw_spi_params_t)); + + /* Initialize the counter to the maximum + 1 to trigger full refresh */ + dev->partial_refresh_count = dev->params.partial_refresh_max; + + if (gpio_is_valid(dev->params.rst_pin)) { + if (gpio_init(dev->params.rst_pin, GPIO_OUT) != 0) { + DEBUG("[epd_bw_spi] init: error initializing the RST pin\n"); + return EPD_BW_SPI_RST_FAIL; + } + gpio_set(dev->params.rst_pin); + } + + if (gpio_is_valid(dev->params.busy_pin)) { + if (gpio_init(dev->params.busy_pin, GPIO_IN) != 0) { + DEBUG("[epd_bw_spi] init: error initializing the BUSY pin\n"); + return EPD_BW_SPI_BUSY_FAIL; + } + } + + if (!gpio_is_valid(dev->params.dc_pin) || + gpio_init(dev->params.dc_pin, GPIO_OUT) != 0) { + DEBUG("[epd_bw_spi] init: error initializing the DC pin\n"); + return EPD_BW_SPI_DC_FAIL; + } + + gpio_set(dev->params.dc_pin); + + int res = spi_init_cs(dev->params.spi, dev->params.cs_pin); + if (res != SPI_OK) { + DEBUG("[epd_bw_spi] init: error initializing the CS pin [%i]\n", res); + return res; + } + + return 0; +} + +void epd_bw_spi_display_init(epd_bw_spi_t *dev) +{ + le_uint16_t y_data[2] = { 0 }; + uint8_t y_size; + + if (dev->params.controller.size_y <= 255) { + y_data[0].u8[0] = dev->params.size_y - 1; + y_size = 2; + } + else { + y_data[0] = + byteorder_btols(byteorder_htons((dev->params.size_y - 1) & 0x01FF)); + y_size = 3; + } + + epd_bw_spi_wake(dev); + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_DRIVER_OUTPUT_CONTROL, + (uint8_t *)y_data, y_size); + + uint8_t data[] = { + 0xD7, /* Phase 1: 30 ms phase, sel 3, 6.58 us off */ + 0xD6, /* Phase 2: 30 ms phase, sel 3, 3.34 us off */ + 0x9D, /* Phase 3: 10 ms phase, sel 4, 1.54 us off */ + }; + + epd_bw_spi_write_cmd(&dev->params, + EPD_BW_SPI_CMD_BOOSTER_SOFT_START_CONTROL, data, 3); + + data[0] = dev->params.controller.vcom; + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_WRITE_VCOM_REGISTER, data, + 1); + + data[0] = 0x1A; /* 4 dummy line per gate */ + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_SET_DUMMY_LINE_PERIOD, + data, 1); + + data[0] = 0x08; /* 2 µs per line */ + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_SET_GATE_LINE_WIDTH, data, + 1); + + data[0] = (uint8_t)dev->params.entry_mode; + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_DATA_ENTRY_MODE_SETTING, + data, 1); +} + +void epd_bw_spi_activate(epd_bw_spi_t *dev) +{ + uint8_t option = + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_ENABLE_CLOCK | + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_ENABLE_CP; + + epd_bw_spi_control2(&dev->params, option, EPD_BW_SPI_WAIT_ACTIVATION); +} + +void epd_bw_spi_deactivate(epd_bw_spi_t *dev) +{ + uint8_t option = + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_DISABLE_CP | + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_DISABLE_OSC; + + epd_bw_spi_control2(&dev->params, option, EPD_BW_SPI_WAIT_ACTIVATION); +} + +void epd_bw_spi_init_full(epd_bw_spi_t *dev) +{ + epd_bw_spi_display_init(dev); + epd_bw_spi_set_area(dev, 0, dev->params.size_x, 0, dev->params.size_y); + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_WRITE_LUT_REGISTER, + dev->params.controller.lut_full, + dev->params.controller.lut_size); +} + +void epd_bw_spi_update_full(epd_bw_spi_t *dev) +{ + uint8_t option = + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_ENABLE_CLOCK | + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_ENABLE_CP | + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_PATTERN_DISPLAY; + + epd_bw_spi_control2(&dev->params, option, EPD_BW_SPI_WAIT_UPDATE_FULL); + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_NOP, NULL, 0); +} + +void epd_bw_spi_init_part(epd_bw_spi_t *dev) +{ + epd_bw_spi_display_init(dev); + epd_bw_spi_set_area(dev, 0, dev->params.size_x, 0, dev->params.size_y); + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_WRITE_LUT_REGISTER, + dev->params.controller.lut_part, + dev->params.controller.lut_size); +} + +void epd_bw_spi_update_part(epd_bw_spi_t *dev) +{ + epd_bw_spi_control2(&dev->params, + EPD_BW_SPI_DISPLAY_UPDATE_OPTION_PATTERN_DISPLAY, + EPD_BW_SPI_WAIT_UPDATE_PART); + + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_NOP, NULL, 0); +} + +void epd_bw_spi_init_auto(epd_bw_spi_t *dev) +{ + if (dev->partial_refresh_count < dev->params.partial_refresh_max) { + epd_bw_spi_init_part(dev); + } + else { + epd_bw_spi_init_full(dev); + } +} + +void epd_bw_spi_update_auto(epd_bw_spi_t *dev) +{ + if (dev->partial_refresh_count < dev->params.partial_refresh_max) { + epd_bw_spi_update_part(dev); + dev->partial_refresh_count++; + } + else { + epd_bw_spi_update_full(dev); + dev->partial_refresh_count = 0; + } +} + +void epd_bw_spi_write_ram(epd_bw_spi_t *dev) +{ + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_WRITE_RAM, NULL, 0); +} + +void epd_bw_spi_clear(epd_bw_spi_t *dev) +{ + epd_bw_spi_fill(dev, 0, dev->params.size_x, 0, dev->params.size_y, + EPD_BW_SPI_COLOR_WHITE); +} + +void epd_bw_spi_fill(epd_bw_spi_t *dev, uint8_t x1, uint8_t x2, uint16_t y1, + uint16_t y2, uint8_t color) +{ + epd_bw_spi_set_area(dev, x1, x2, y1, y2); + + spi_acquire(dev->params.spi, dev->params.cs_pin, SPI_MODE_0, + dev->params.spi_clk); + epd_bw_spi_cmd_start(&dev->params, EPD_BW_SPI_CMD_WRITE_RAM, true); + + uint16_t size = ((x2 - x1) >> 3) * (y2 - y1); + + for (uint16_t i = 0; i < size - 1; i++) { + spi_transfer_byte(dev->params.spi, dev->params.cs_pin, true, color); + } + spi_transfer_byte(dev->params.spi, dev->params.cs_pin, false, color); + + spi_release(dev->params.spi); +} + +void epd_bw_spi_fill_pixels(epd_bw_spi_t *dev, uint8_t x1, uint8_t x2, + uint16_t y1, uint16_t y2, + uint8_t *px) +{ + epd_bw_spi_set_area(dev, x1, x2, y1, y2); + epd_bw_spi_write_buffer(dev, px, (x2 - x1) * (y2 - y1)); +} + +void epd_bw_spi_set_area(epd_bw_spi_t *dev, uint8_t x1, uint8_t x2, uint16_t y1, + uint16_t y2) +{ + DEBUG("[epd_bw_spi] set_area: (%d,%d)-(%d,%d)\n", x1, y1, x2, y2); + + /* Set X bounds */ + uint8_t x_data[] = { + (x1 >> 3) & 0x1F, + ((x2 - 1) >> 3) & 0x1F, + }; + + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_SET_RAM_X, x_data, + sizeof x_data); + + /* Set Y bounds */ + le_uint16_t y_data[2] = { 0 }; + uint8_t y_size; + + if (dev->params.controller.size_y <= 255) { + y_data[0].u8[0] = y1; + y_data[0].u8[1] = y2 - 1; + y_size = 2; + } + else { + y_data[0] = byteorder_btols(byteorder_htons(y1 & 0x01FF)); + y_data[1] = byteorder_btols(byteorder_htons((y2 - 1) & 0x01FF)); + y_size = 4; + } + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_SET_RAM_Y, + (uint8_t *)y_data, y_size); + + /* Set counters to start positions */ + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_SET_RAM_X_ADDR_COUNTER, + x_data, 1); + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_SET_RAM_Y_ADDR_COUNTER, + (uint8_t *)y_data, 2); +} + +void epd_bw_spi_write_buffer(epd_bw_spi_t *dev, const uint8_t *buf, size_t len) +{ + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_WRITE_RAM, buf, len); +} + +void epd_bw_spi_sleep(epd_bw_spi_t *dev) +{ + uint8_t data[] = { 0x01 }; + + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_DEEP_SLEEP_MODE, data, 1); +} + +void epd_bw_spi_wake(epd_bw_spi_t *dev) +{ + /* Give a low pulse on the reset pin */ + if (gpio_is_valid(dev->params.rst_pin)) { + gpio_clear(dev->params.rst_pin); + ztimer_sleep(ZTIMER_MSEC, EPD_BW_SPI_WAIT_RESET); + gpio_set(dev->params.rst_pin); + ztimer_sleep(ZTIMER_MSEC, EPD_BW_SPI_WAIT_RESET); + } + + /* Turn off sleep mode */ + uint8_t data[] = { 0x00 }; + + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_DEEP_SLEEP_MODE, data, 1); + epd_bw_spi_wait(&dev->params, EPD_BW_SPI_WAIT_RESET); +} + +void epd_bw_spi_swreset(epd_bw_spi_t *dev) +{ + epd_bw_spi_write_cmd(&dev->params, EPD_BW_SPI_CMD_SWRESET, NULL, 0); +} diff --git a/drivers/epd_bw_spi/include/epd_bw_spi_internal.h b/drivers/epd_bw_spi/include/epd_bw_spi_internal.h new file mode 100644 index 0000000000..36cce777d0 --- /dev/null +++ b/drivers/epd_bw_spi/include/epd_bw_spi_internal.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_epd_bw_spi + * @{ + * + * @file + * @brief Device driver implementation for the epd_bw_spi display controller + * + * @author Silke Hofstra + * + * @} + */ + +#ifndef EPD_BW_SPI_INTERNAL_H +#define EPD_BW_SPI_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name EPD_BW_SPI SPI commands + * @brief Commands used for controlling EPD displays. + * These are surprisingly portable. + * @{ + */ +#define EPD_BW_SPI_CMD_DRIVER_OUTPUT_CONTROL (0x01) +#define EPD_BW_SPI_CMD_GATE_DRIVING_VOLTAGE_CONTROL (0x03) /* unused */ +#define EPD_BW_SPI_CMD_SOURCE_DRIVING_VOLTAGE_CONTROL (0x04) /* unused */ +#define EPD_BW_SPI_CMD_DISPLAY_CONTROL (0x07) /* unused */ +#define EPD_BW_SPI_CMD_GATE_AND_SOURCE_NON_OVERLAP_PERIOD_CONTROL (0x0B) /* unused */ +#define EPD_BW_SPI_CMD_BOOSTER_SOFT_START_CONTROL (0x0C) +#define EPD_BW_SPI_CMD_GATE_SCAN_START_POSITION (0x0F) /* unused */ +#define EPD_BW_SPI_CMD_DEEP_SLEEP_MODE (0x10) +#define EPD_BW_SPI_CMD_DATA_ENTRY_MODE_SETTING (0x11) +#define EPD_BW_SPI_CMD_SWRESET (0x12) +#define EPD_BW_SPI_CMD_TEMPERATURE_SENSOR_CONTROL_WRITE (0x1A) /* unused */ +#define EPD_BW_SPI_CMD_TEMPERATURE_SENSOR_CONTROL_READ (0x1B) /* unused */ +#define EPD_BW_SPI_CMD_TEMPERATURE_SENSOR_CONTROL_WRITE_CMD (0x1C) /* unused */ +#define EPD_BW_SPI_CMD_TEMPERATURE_SENSOR_CONTROL_LOAD (0x1D) /* unused */ +#define EPD_BW_SPI_CMD_MASTER_ACTIVATION (0x20) +#define EPD_BW_SPI_CMD_DISPLAY_UPDATE_CONTROL_1 (0x21) /* unused */ +#define EPD_BW_SPI_CMD_DISPLAY_UPDATE_CONTROL_2 (0x22) +#define EPD_BW_SPI_CMD_WRITE_RAM (0x24) +#define EPD_BW_SPI_CMD_READ_RAM (0x25) /* unused */ +#define EPD_BW_SPI_CMD_VCOM_SENSE (0x28) /* unused */ +#define EPD_BW_SPI_CMD_VCOM_SENSE_DURATION (0x29) /* unused */ +#define EPD_BW_SPI_CMD_PROGRAM_VCOM_OTP (0x2A) /* unused */ +#define EPD_BW_SPI_CMD_WRITE_VCOM_REGISTER (0x2C) +#define EPD_BW_SPI_CMD_READ_OTP_REGISTERS (0x2D) /* unused */ +#define EPD_BW_SPI_CMD_PROGRAM_WS_OTP (0x30) /* unused */ +#define EPD_BW_SPI_CMD_WRITE_LUT_REGISTER (0x32) +#define EPD_BW_SPI_CMD_READ_LUT_REGISTER (0x33) /* unused */ +#define EPD_BW_SPI_CMD_PROGRAM_OTP_SELECTION (0x36) /* unused */ +#define EPD_BW_SPI_CMD_OTP_SELECTION_CONTROL (0x37) /* unused */ +#define EPD_BW_SPI_CMD_SET_DUMMY_LINE_PERIOD (0x3A) +#define EPD_BW_SPI_CMD_SET_GATE_LINE_WIDTH (0x3B) +#define EPD_BW_SPI_CMD_BORDER_WAVEFORM_CONTROL (0x3C) /* unused */ +#define EPD_BW_SPI_CMD_SET_RAM_X (0x44) +#define EPD_BW_SPI_CMD_SET_RAM_Y (0x45) +#define EPD_BW_SPI_CMD_SET_RAM_X_ADDR_COUNTER (0x4E) +#define EPD_BW_SPI_CMD_SET_RAM_Y_ADDR_COUNTER (0x4F) +#define EPD_BW_SPI_CMD_NOP (0xFF) +/**@}*/ + +/** + * @name EPD_BW_SPI display update sequence option flags + * @brief Option flags for the EPD_BW_SPI_CMD_DISPLAY_UPDATE_CONTROL_2 command. + * The flags are executed in the order documented below. + * @{ + */ +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_ENABLE_CLOCK (1<<7) +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_ENABLE_CP (1<<6) +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_LOAD_TEMP (1<<5) +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_LOAD_LUT (1<<4) +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_INITIAL_DISPLAY (1<<3) +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_PATTERN_DISPLAY (1<<2) +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_DISABLE_CP (1<<1) +#define EPD_BW_SPI_DISPLAY_UPDATE_OPTION_DISABLE_OSC (1<<0) +/**@}*/ + +/** + * @name EPD_BW_SPI Waiting estimates + * @brief Waiting estimates in milliseconds which are used when the busy pin is not available. + * @{ + */ +#define EPD_BW_SPI_WAIT_UPDATE_FULL 1200 +#define EPD_BW_SPI_WAIT_UPDATE_PART 300 +#define EPD_BW_SPI_WAIT_ACTIVATION 80 +#define EPD_BW_SPI_WAIT_RESET 1 +/**@}*/ + +#ifdef __cplusplus +} +#endif +#endif /* EPD_BW_SPI_INTERNAL_H */ diff --git a/drivers/epd_bw_spi/include/epd_bw_spi_params.h b/drivers/epd_bw_spi/include/epd_bw_spi_params.h new file mode 100644 index 0000000000..b3a1b157a1 --- /dev/null +++ b/drivers/epd_bw_spi/include/epd_bw_spi_params.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2019 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_epd_bw_spi + * + * @{ + * @file + * @brief Default configuration for epd_bw_spi + * + * @author Silke Hofstra + */ + +#ifndef EPD_BW_SPI_PARAMS_H +#define EPD_BW_SPI_PARAMS_H + +#include "board.h" +#include "epd_bw_spi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Waveform lookup table for a full display refresh for IL3829. + */ +static const uint8_t epd_bw_spi_il3829_lut_default_full[] = { + 0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/** + * @brief Waveform lookup table for a partial display refresh for IL3829. + */ +static const uint8_t epd_bw_spi_il3829_lut_default_part[] = { + 0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/** + * @brief Configuration for IL3829 e-paper display controller + */ +#define EPD_BW_SPI_CONTROLLER_IL3829 { \ + .vcom = 0xA8, \ + .size_x = 200, \ + .size_y = 300, \ + .lut_size = sizeof epd_bw_spi_il3829_lut_default_full, \ + .lut_full = epd_bw_spi_il3829_lut_default_full, \ + .lut_part = epd_bw_spi_il3829_lut_default_part, \ +} + +/** + * @brief Configuration for SSD1607 e-paper display controller + */ +#define EPD_BW_SPI_CONTROLLER_SSD1607 EPD_BW_SPI_CONTROLLER_IL3829 + +/** + * @brief Configuration for SSD1673 e-paper display controller + */ +#define EPD_BW_SPI_CONTROLLER_SSD1673 { \ + .vcom = 0xA8, \ + .size_x = 150, \ + .size_y = 250, \ + .lut_size = sizeof epd_bw_spi_il3829_lut_default_full, \ + .lut_full = epd_bw_spi_il3829_lut_default_full, \ + .lut_part = epd_bw_spi_il3829_lut_default_part, \ +} + +/** + * @brief Configuration for SSD1608 e-paper display controller + */ +#define EPD_BW_SPI_CONTROLLER_SSD1608 { \ + .vcom = 0xA8, \ + .size_x = 240, \ + .size_y = 320, \ + .lut_size = sizeof epd_bw_spi_il3829_lut_default_full, \ + .lut_full = epd_bw_spi_il3829_lut_default_full, \ + .lut_part = epd_bw_spi_il3829_lut_default_part, \ +} + +#ifndef EPD_BW_SPI_DISPLAY_X +/** + * @brief Width of the display in pixels. + */ +#define EPD_BW_SPI_DISPLAY_X (200) +#endif + +#ifndef EPD_BW_SPI_DISPLAY_Y +/** + * @brief Height of the display in pixels. + */ +#define EPD_BW_SPI_DISPLAY_Y (200) +#endif + +#ifndef EPD_BW_SPI_PARAM_SPI +/** + * @brief SPI device the display is connected to. + */ +#define EPD_BW_SPI_PARAM_SPI (SPI_DEV(0)) +#endif + +#ifndef EPD_BW_SPI_PARAM_SPI_CLK +/** + * @brief SPI device clock speed. + */ +#define EPD_BW_SPI_PARAM_SPI_CLK (SPI_CLK_5MHZ) +#endif + +/** + * @brief SPI Chip select pin. + */ +#ifndef EPD_BW_SPI_PARAM_CS +#define EPD_BW_SPI_PARAM_CS (SPI_CS_UNDEF) +#endif + +/** + * @brief Data/command pin of the display. + */ +#ifndef EPD_BW_SPI_PARAM_DC +#define EPD_BW_SPI_PARAM_DC (GPIO_UNDEF) +#endif + +/** + * @brief Reset pin of the display. + */ +#ifndef EPD_BW_SPI_PARAM_RST +#define EPD_BW_SPI_PARAM_RST (GPIO_UNDEF) +#endif + +#ifndef EPD_BW_SPI_PARAM_BUSY +/** + * @brief Busy pin of the display. + */ +#define EPD_BW_SPI_PARAM_BUSY (GPIO_UNDEF) +#endif + +#ifndef EPD_BW_SPI_PARAM_BUSY_VAL +/** + * @brief Width of the display in pixels. + */ +#define EPD_BW_SPI_PARAM_BUSY_VAL (1) +#endif + +#ifndef EPD_BW_SPI_CONTROLLER +/** + * @brief Display controller. See epd_bw_spi_controller_t. + */ +#define EPD_BW_SPI_CONTROLLER EPD_BW_SPI_CONTROLLER_IL3829 +#endif + +#ifndef EPD_BW_SPI_ENTRY_MODE +/** + * @brief Data entry mode. See epd_bw_spi_entry_mode_t. + */ +#define EPD_BW_SPI_ENTRY_MODE EPD_BW_SPI_Y_INC_X_INC +#endif + +#ifndef EPD_BW_SPI_PARTIAL_REFRESH_MAX +/** + * @brief Maximum number of partial refreshes before a full refresh occurs. + * + * This is only used with epd_bw_spi_init_auto and @ref drivers_disp_dev. + */ +#define EPD_BW_SPI_PARTIAL_REFRESH_MAX (99) +#endif + +#ifndef EPD_BW_SPI_PARAMS +/** + * @brief Parameters to initialize the display with. + */ +#define EPD_BW_SPI_PARAMS { .spi = EPD_BW_SPI_PARAM_SPI, \ + .spi_clk = EPD_BW_SPI_PARAM_SPI_CLK, \ + .cs_pin = EPD_BW_SPI_PARAM_CS, \ + .dc_pin = EPD_BW_SPI_PARAM_DC, \ + .rst_pin = EPD_BW_SPI_PARAM_RST, \ + .busy_pin = EPD_BW_SPI_PARAM_BUSY, \ + .busy_value = EPD_BW_SPI_PARAM_BUSY_VAL, \ + .controller = EPD_BW_SPI_CONTROLLER, \ + .entry_mode = EPD_BW_SPI_ENTRY_MODE, \ + .size_x = EPD_BW_SPI_DISPLAY_X, \ + .size_y = EPD_BW_SPI_DISPLAY_Y, \ + .partial_refresh_max = \ + EPD_BW_SPI_PARTIAL_REFRESH_MAX, \ +} +#endif + +/** + * @brief Display driver configuration. + */ +static const epd_bw_spi_params_t epd_bw_spi_params[] = +{ + EPD_BW_SPI_PARAMS, +}; + +#ifdef __cplusplus +} +#endif +#endif /* EPD_BW_SPI_PARAMS_H */ +/**@}*/ diff --git a/drivers/include/epd_bw_spi.h b/drivers/include/epd_bw_spi.h new file mode 100644 index 0000000000..e66b7bc3e2 --- /dev/null +++ b/drivers/include/epd_bw_spi.h @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2019 Silke Hofstra + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_epd_bw_spi Generic black/white e-paper/e-ink SPI display driver. + * @ingroup drivers_display + * @brief Device driver for black/white e-ink/e-paper SPI displays. + * + * This driver provides functionality for working with black/white e-ink (e-paper) SPI displays. + * Various display controllers are currently supported out of the box, see @ref epd_bw_spi_params.h. + * Please open an issue or pull request with your controller details (size, Vcom, LUTs) + * if your display controller is not included yet. + * + * Use of this driver requires knowing the parameters of your display. + * See epd_bw_spi_params_t and @ref epd_bw_spi_params.h for more details on the parameters. + * Note that while the reset and busy pins are optional, using them is highly recommended. + * + * + * @{ + * @file + * @brief Generic black/white e-paper/e-ink display SPI driver. + * + * @author Silke Hofstra + */ + +#ifndef EPD_BW_SPI_H +#define EPD_BW_SPI_H + +#include "periph/spi.h" +#include "periph/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define EPD_BW_SPI_COLOR_WHITE (0xFF) /**< White (8x1 pixels) */ +#define EPD_BW_SPI_COLOR_BLACK (0x00) /**< Black (8x1 pixels) */ + +/** + * @brief Data entry mode settings. + * + * This setting affect the automatic increment/decrement of the address counters. + */ +typedef enum { + EPD_BW_SPI_Y_DEC_X_DEC = 0x0, /**< Y decrement, X decrement */ + EPD_BW_SPI_Y_DEC_X_INC = 0x1, /**< Y decrement, X increment */ + EPD_BW_SPI_Y_INC_X_DEC = 0x2, /**< Y increment, X decrement */ + EPD_BW_SPI_Y_INC_X_INC = 0x3, /**< Y increment, X increment */ +} epd_bw_spi_entry_mode_t; + +/** + * @brief Additional status codes for black/white SPI e-paper displays. + */ +enum { + EPD_BW_SPI_DC_FAIL = -5, + EPD_BW_SPI_RST_FAIL = -6, + EPD_BW_SPI_BUSY_FAIL = -7, +}; + +/** + * @brief Display controller parameters. + */ +typedef struct { + uint8_t vcom; /**< VCOM voltage level */ + const uint16_t size_x; /**< supported number of horizontal pixels */ + const uint16_t size_y; /**< supported number of vertical pixels */ + const uint8_t lut_size; /**< size of the waveform lookup table */ + const uint8_t *lut_full; /**< lookup table for a full display refresh */ + const uint8_t *lut_part; /**< lookup table for a partial display refresh */ +} epd_bw_spi_controller_t; + +/** + * @brief SPI display device initialisation parameters. + */ +typedef struct { + spi_t spi; /**< SPI device that the display is connected to */ + spi_clk_t spi_clk; /**< SPI clock speed to use */ + gpio_t cs_pin; /**< pin connected to the CHIP SELECT line */ + gpio_t dc_pin; /**< pin connected to the DC line */ + gpio_t rst_pin; /**< pin connected to the reset line (optional) */ + gpio_t busy_pin; /**< pin connected to the busy line (optional) */ + bool busy_value; /**< expected value for the busy pin + when the display is busy */ + bool dummy; /**< if device requires a dummy cycle before read */ + epd_bw_spi_controller_t controller; /**< display controller of the e-Paper display */ + epd_bw_spi_entry_mode_t entry_mode; /**< data entry mode */ + uint16_t size_x; /**< number of horizontal pixels in the display */ + uint16_t size_y; /**< number of vertical pixels in the display */ + uint16_t partial_refresh_max; /**< maximum number of partial refreshes to perform + before triggering a full refresh */ +} epd_bw_spi_params_t; + +/** + * @brief Device initialisation parameters. + */ +typedef struct { + epd_bw_spi_params_t params; /**< SPI display parameters */ + uint16_t partial_refresh_count; /**< number of partial refreshes since + the last full refresh */ +} epd_bw_spi_t; + +/** + * @brief Initialise the display. + * + * @param[out] dev Display to initialise. + * @param[in] params SPI Display parameters to use for initialisation. + */ +int epd_bw_spi_init(epd_bw_spi_t *dev, const epd_bw_spi_params_t *params); + +/** + * @brief Activate the display. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_activate(epd_bw_spi_t *dev); + +/** + * @brief Deactivate the display. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_deactivate(epd_bw_spi_t *dev); + +/** + * @brief Initialise the display for a full refresh. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_init_full(epd_bw_spi_t *dev); + +/** + * @brief Update the display with a full refresh. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_update_full(epd_bw_spi_t *dev); + +/** + * @brief Initialise the display for a partial refresh. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_init_part(epd_bw_spi_t *dev); + +/** + * @brief Update the display with a partial refresh. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_update_part(epd_bw_spi_t *dev); + +/** + * @brief Initialise the display for an automatic partial/full refresh. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_init_auto(epd_bw_spi_t *dev); + +/** + * @brief Update the display with an automatic partial/full refresh. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_update_auto(epd_bw_spi_t *dev); + +/** + * @brief Clear the entire display. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_clear(epd_bw_spi_t *dev); + +/** + * @brief Fill an area with a single color. + * + * @param[in] dev Device descriptor + * @param[in] x1 X coordinate of the first corner (multiple of 8). + * @param[in] x2 X coordinate of the opposite corner (multiple of 8). + * @param[in] y1 Y coordinate of the first corner. + * @param[in] y2 Y coordinate of the opposite corner. + * @param[in] color Color to use (`EPD_BW_SPI_COLOR_BLACK` or `EPD_BW_SPI_COLOR_WHITE`) + */ +void epd_bw_spi_fill(epd_bw_spi_t *dev, uint8_t x1, uint8_t x2, uint16_t y1, + uint16_t y2, + uint8_t color); + +/** + * @brief Fill an area with an array of pixels. + * + * Note that the length of the array should be the same as the number of pixels + * in the given area. + * + * @param[in] dev Device descriptor. + * @param[in] x1 X coordinate of the first corner (multiple of 8). + * @param[in] x2 X coordinate of the opposite corner (multiple of 8). + * @param[in] y1 Y coordinate of the first corner. + * @param[in] y2 Y coordinate of the opposite corner. + * @param[in] px Array of pixels to use. + */ +void epd_bw_spi_fill_pixels(epd_bw_spi_t *dev, uint8_t x1, uint8_t x2, + uint16_t y1, uint16_t y2, + uint8_t *px); + +/** + * @brief Set the area in which can be drawn. + * + * @param[in] dev Device descriptor. + * @param[in] x1 X coordinate of the first corner (multiple of 8). + * @param[in] x2 X coordinate of the opposite corner (multiple of 8). + * @param[in] y1 Y coordinate of the first corner. + * @param[in] y2 Y coordinate of the opposite corner. + */ +void epd_bw_spi_set_area(epd_bw_spi_t *dev, uint8_t x1, uint8_t x2, uint16_t y1, + uint16_t y2); + +/** + * @brief Write to the RAM of the epd_bw_spi controller. + * + * Together with `epd_bw_spi_set_area()`, this allows one to draw a pregenerated + * image on the screen. + * + * @param[in] dev Device descriptor. + * @param[in] buf Buffer to write to the display. + * @param[in] len Size of the buffer to write to the display. + */ +void epd_bw_spi_write_buffer(epd_bw_spi_t *dev, const uint8_t *buf, size_t len); + +/** + * @brief Set the display to deep sleep mode. + * + * After the display has gone to sleep, a wake can be triggered with the reset pin. + * Do not use this if no reset pin has been defined. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_sleep(epd_bw_spi_t *dev); + +/** + * @brief Wake the device. + * + * This doesn't do anything without using the reset pin. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_wake(epd_bw_spi_t *dev); + +/** + * @brief Perform a soft reset of the device. + * + * This resets all commands and parameters to their default values, + * except for sleep mode and the RAM. + * + * @param[in] dev Device descriptor. + */ +void epd_bw_spi_swreset(epd_bw_spi_t *dev); + +#ifdef __cplusplus +} +#endif +#endif /* EPD_BW_SPI_H */ +/** @} */