Vendored the panel's init/LUT/refresh register sequence from three
independent Waveshare reference drivers for this exact panel+controller
(RaspberryPi/c, ESP32, and the ESP32-S3-ePaper-13.3E6 ESP-IDF example),
which all agree byte-for-byte. The epd13in3e.c #error is gone; it
compiles clean and links (verified via /build-firmware ee02).
That vendor code also revealed the panel's SPI wire raster is a native
1200x1600 (portrait), not 1600x1200 as previously assumed -- rotated 90
degrees from the panel's landscape mount/marketing size. The old
assumption wasn't just a rotation bug: 1600x1200 and 1200x1600 don't
share a row stride, so packing at the wrong one would have shredded
images into a repeating diagonal garble on real hardware, not just
displayed them sideways. Fixed with a new PANEL_WIRE_TRANSPOSE in
image_pipeline.py, applied after the existing per-frame
ORIENTATION_TRANSPOSE, with a direction-agnostic regression test that
catches the stride bug specifically (a byte-count check alone can't,
since both orientations pack to the same total size).
A full ee02 build still fails, but no longer because of this driver --
main/{back,next,combo}_button.c call an ESP32-C6-only deep-sleep
GPIO-wakeup API with no ESP32-S3 fallback, a separate pre-existing gap
that was simply hidden behind the panel driver's old #error. See
docs/hardware.md for details; CI's continue-on-error on this board
stays in place until that's fixed too.
439 lines
15 KiB
C
439 lines
15 KiB
C
#include <string.h>
|
|
|
|
#include "driver/gpio.h"
|
|
#include "driver/spi_master.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "esp_check.h"
|
|
#include "esp_log.h"
|
|
#include "esp_rom_crc.h"
|
|
|
|
#include "epd13in3e.h"
|
|
|
|
/* Command bytes/register values below are a line-for-line transcription of
|
|
* Waveshare's official reference drivers for this exact panel+controller --
|
|
* confirmed identical across three independent sources (RaspberryPi/c,
|
|
* ESP32, and the ESP32-S3-ePaper-13.3E6 ESP-IDF example; see this
|
|
* component's header for repo paths). Same "don't clean these up" rule as
|
|
* epd7in3e.c: this class of panel controller has no public datasheet, so
|
|
* the vendor driver is the source of truth for every byte.
|
|
*
|
|
* Unlike epd7in3e's single chip-select, this panel is driven as two
|
|
* independent controllers sharing one CLK/MOSI/DC/RST/BUSY bus but with
|
|
* separate chip-selects (EPD_PIN_CS_MASTER/EPD_PIN_CS_SLAVE) -- most init
|
|
* commands broadcast to both (CS_ALL), a handful of power/boost commands
|
|
* go only to the master (which owns the shared analog rails), and actual
|
|
* frame data is split per-row into a left half (master) and right half
|
|
* (slave), 300 bytes each out of each 600-byte row. That split is why
|
|
* epd_write_frame below buffers the whole frame in PSRAM before sending
|
|
* anything (every row needs slicing in half before either half can go out),
|
|
* unlike epd7in3e.c's straight single-CS passthrough streaming.
|
|
*
|
|
* Pin numbers themselves are NOT from this vendor code -- Waveshare's
|
|
* ESP32-S3-ePaper-13.3E6 example targets Waveshare's own driver board, a
|
|
* different carrier than Seeed's EE02 this project actually uses, so its
|
|
* GPIO numbers don't apply here. EE02's pins remain sourced from a
|
|
* community-verified ESPHome integration (see this component's Kconfig),
|
|
* not an official reference. */
|
|
|
|
#define EPD_SPI_HOST SPI2_HOST
|
|
#define EPD_SPI_CHUNK_SIZE 4096
|
|
|
|
static const char *TAG = "epd13in3e";
|
|
|
|
#define EPD_CHECK(expr) ESP_RETURN_ON_ERROR((expr), TAG, #expr)
|
|
|
|
/* --- panel command opcodes --- */
|
|
#define PSR 0x00
|
|
#define PWR 0x01
|
|
#define POF 0x02
|
|
#define PON 0x04
|
|
#define BTST_N 0x05
|
|
#define BTST_P 0x06
|
|
#define DTM 0x10 /* data transfer (frame data) */
|
|
#define DRF 0x12 /* display refresh */
|
|
#define CDI 0x50
|
|
#define TCON 0x60
|
|
#define TRES 0x61
|
|
#define AN_TM 0x74
|
|
#define AGID 0x86
|
|
#define BUCK_BOOST_VDDN 0xB0
|
|
#define TFT_VCOM_POWER 0xB1
|
|
#define EN_BUF 0xB6
|
|
#define BOOST_VDDP_EN 0xB7
|
|
#define CCSET 0xE0
|
|
#define PWS 0xE3
|
|
#define CMD66 0xF0
|
|
#define DEEP_SLEEP 0x07
|
|
|
|
/* --- canned init parameter blobs (do NOT edit -- see top comment) --- */
|
|
static const uint8_t PSR_V[] = {0xDF, 0x69};
|
|
static const uint8_t PWR_V[] = {0x0F, 0x00, 0x28, 0x2C, 0x28, 0x38};
|
|
static const uint8_t POF_V[] = {0x00};
|
|
static const uint8_t DRF_V[] = {0x00};
|
|
static const uint8_t CDI_V[] = {0xF7};
|
|
static const uint8_t TCON_V[] = {0x03, 0x03};
|
|
static const uint8_t TRES_V[] = {0x04, 0xB0, 0x03, 0x20};
|
|
static const uint8_t CMD66_V[] = {0x49, 0x55, 0x13, 0x5D, 0x05, 0x10};
|
|
static const uint8_t EN_BUF_V[] = {0x07};
|
|
static const uint8_t CCSET_V[] = {0x01};
|
|
static const uint8_t PWS_V[] = {0x22};
|
|
static const uint8_t AN_TM_V[] = {0xC0, 0x1C, 0x1C, 0xCC, 0xCC, 0xCC, 0x15, 0x15, 0x55};
|
|
static const uint8_t AGID_V[] = {0x10};
|
|
static const uint8_t BTST_P_V[] = {0xE8, 0x28};
|
|
static const uint8_t BOOST_VDDP_EN_V[] = {0x01};
|
|
static const uint8_t BTST_N_V[] = {0xE8, 0x28};
|
|
static const uint8_t BUCK_BOOST_VDDN_V[] = {0x01};
|
|
static const uint8_t TFT_VCOM_POWER_V[] = {0x02};
|
|
|
|
static spi_device_handle_t s_spi;
|
|
|
|
static void epd_delay_ms(uint32_t ms)
|
|
{
|
|
vTaskDelay(pdMS_TO_TICKS(ms));
|
|
}
|
|
|
|
/* BUSY: LOW = busy, HIGH = idle -- same polarity/poll-interval reasoning
|
|
* as epd7in3e.c's identical comment (a tight 1ms-rounds-to-0-ticks poll
|
|
* starves the idle task badly enough to trip the watchdog). */
|
|
static void epd_wait_busy(void)
|
|
{
|
|
while (gpio_get_level((gpio_num_t)CONFIG_EPD_PIN_BUSY) == 0) {
|
|
epd_delay_ms(20);
|
|
}
|
|
}
|
|
|
|
static esp_err_t epd_spi_write(const uint8_t *data, size_t len)
|
|
{
|
|
while (len > 0) {
|
|
size_t n = len > EPD_SPI_CHUNK_SIZE ? EPD_SPI_CHUNK_SIZE : len;
|
|
spi_transaction_t t = {
|
|
.length = n * 8,
|
|
.tx_buffer = data,
|
|
};
|
|
EPD_CHECK(spi_device_polling_transmit(s_spi, &t));
|
|
data += n;
|
|
len -= n;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
/* Unlike epd7in3e.c's send_command/send_data, these do NOT touch CS --
|
|
* this panel's two independent chip-selects (and the "broadcast to both"
|
|
* vs "master only" split the init sequence needs) mean CS bracketing has
|
|
* to be the caller's decision, not baked into the byte-send primitive. */
|
|
static esp_err_t epd_send_command(uint8_t cmd)
|
|
{
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_DC, 0);
|
|
return epd_spi_write(&cmd, 1);
|
|
}
|
|
|
|
static esp_err_t epd_send_data(const uint8_t *data, size_t len)
|
|
{
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_DC, 1);
|
|
return epd_spi_write(data, len);
|
|
}
|
|
|
|
static esp_err_t epd_send_data_byte(uint8_t data)
|
|
{
|
|
return epd_send_data(&data, 1);
|
|
}
|
|
|
|
static void epd_cs_both(int level)
|
|
{
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_CS_MASTER, level);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_CS_SLAVE, level);
|
|
}
|
|
|
|
/* Sends `cmd` + its data blob to both controllers at once (most of the
|
|
* init sequence -- shared display-timing/power registers). */
|
|
static esp_err_t epd_cmd_both(uint8_t cmd, const uint8_t *data, size_t len)
|
|
{
|
|
epd_cs_both(0);
|
|
esp_err_t err = epd_send_command(cmd);
|
|
if (err == ESP_OK && data != NULL) {
|
|
err = epd_send_data(data, len);
|
|
}
|
|
epd_cs_both(1);
|
|
return err;
|
|
}
|
|
|
|
/* Sends `cmd` + its data blob to the master controller only -- the boost/
|
|
* VCOM power registers the master alone owns. */
|
|
static esp_err_t epd_cmd_master(uint8_t cmd, const uint8_t *data, size_t len)
|
|
{
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_CS_MASTER, 0);
|
|
esp_err_t err = epd_send_command(cmd);
|
|
if (err == ESP_OK && data != NULL) {
|
|
err = epd_send_data(data, len);
|
|
}
|
|
epd_cs_both(1);
|
|
return err;
|
|
}
|
|
|
|
/* 5-edge reset sequence (30ms each) -- per-vendor-source exact, more edges
|
|
* than epd7in3e.c's 3-edge/20ms reset. */
|
|
static void epd_reset(void)
|
|
{
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_RST, 1);
|
|
epd_delay_ms(30);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_RST, 0);
|
|
epd_delay_ms(30);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_RST, 1);
|
|
epd_delay_ms(30);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_RST, 0);
|
|
epd_delay_ms(30);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_RST, 1);
|
|
epd_delay_ms(30);
|
|
}
|
|
|
|
/* Power on, refresh, power off -- mirrors EPD_TurnOnDisplay()/
|
|
* EPD_13IN3E_TurnOnDisplay() in the reference drivers. */
|
|
esp_err_t epd_turn_on_display(void)
|
|
{
|
|
EPD_CHECK(epd_cmd_both(PON, NULL, 0));
|
|
epd_wait_busy();
|
|
|
|
epd_delay_ms(50);
|
|
EPD_CHECK(epd_cmd_both(DRF, DRF_V, sizeof(DRF_V)));
|
|
epd_wait_busy();
|
|
|
|
epd_delay_ms(50);
|
|
EPD_CHECK(epd_cmd_both(POF, POF_V, sizeof(POF_V)));
|
|
/* No busy-wait after POF -- matches every reference driver. */
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t epd_init(void)
|
|
{
|
|
gpio_config_t out_cfg = {
|
|
.pin_bit_mask = (1ULL << CONFIG_EPD_PIN_DC) | (1ULL << CONFIG_EPD_PIN_RST) |
|
|
(1ULL << CONFIG_EPD_PIN_CS_MASTER) | (1ULL << CONFIG_EPD_PIN_CS_SLAVE) |
|
|
(1ULL << CONFIG_EPD_PIN_POWER_EN),
|
|
.mode = GPIO_MODE_OUTPUT,
|
|
};
|
|
EPD_CHECK(gpio_config(&out_cfg));
|
|
|
|
gpio_config_t busy_cfg = {
|
|
.pin_bit_mask = (1ULL << CONFIG_EPD_PIN_BUSY),
|
|
.mode = GPIO_MODE_INPUT,
|
|
};
|
|
EPD_CHECK(gpio_config(&busy_cfg));
|
|
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_RST, 1);
|
|
epd_cs_both(1);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_POWER_EN, 0);
|
|
|
|
spi_bus_config_t bus_cfg = {
|
|
.mosi_io_num = CONFIG_EPD_PIN_MOSI,
|
|
.miso_io_num = -1,
|
|
.sclk_io_num = CONFIG_EPD_PIN_CLK,
|
|
.quadwp_io_num = -1,
|
|
.quadhd_io_num = -1,
|
|
.max_transfer_sz = EPD_SPI_CHUNK_SIZE,
|
|
};
|
|
EPD_CHECK(spi_bus_initialize(EPD_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO));
|
|
|
|
spi_device_interface_config_t dev_cfg = {
|
|
.clock_speed_hz = CONFIG_EPD_SPI_CLOCK_HZ,
|
|
.mode = 0,
|
|
.spics_io_num = -1, /* both chip-selects are bit-banged by hand above */
|
|
.queue_size = 1,
|
|
};
|
|
EPD_CHECK(spi_bus_add_device(EPD_SPI_HOST, &dev_cfg, &s_spi));
|
|
|
|
/* Panel power-enable rail (no equivalent on epd7in3e's board -- EE02
|
|
* gates it separately from the ESP32-S3 module's own supply). */
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_POWER_EN, 1);
|
|
epd_delay_ms(10);
|
|
|
|
epd_reset();
|
|
epd_wait_busy();
|
|
|
|
/* Master-only: shared analog-timing register. */
|
|
EPD_CHECK(epd_cmd_master(AN_TM, AN_TM_V, sizeof(AN_TM_V)));
|
|
|
|
/* Broadcast: display-timing/power-sequencing registers both
|
|
* controllers need identically. */
|
|
EPD_CHECK(epd_cmd_both(CMD66, CMD66_V, sizeof(CMD66_V)));
|
|
EPD_CHECK(epd_cmd_both(PSR, PSR_V, sizeof(PSR_V)));
|
|
EPD_CHECK(epd_cmd_both(CDI, CDI_V, sizeof(CDI_V)));
|
|
EPD_CHECK(epd_cmd_both(TCON, TCON_V, sizeof(TCON_V)));
|
|
EPD_CHECK(epd_cmd_both(AGID, AGID_V, sizeof(AGID_V)));
|
|
EPD_CHECK(epd_cmd_both(PWS, PWS_V, sizeof(PWS_V)));
|
|
EPD_CHECK(epd_cmd_both(CCSET, CCSET_V, sizeof(CCSET_V)));
|
|
EPD_CHECK(epd_cmd_both(TRES, TRES_V, sizeof(TRES_V)));
|
|
|
|
/* Master-only: boost/VCOM power programming. */
|
|
EPD_CHECK(epd_cmd_master(PWR, PWR_V, sizeof(PWR_V)));
|
|
EPD_CHECK(epd_cmd_master(EN_BUF, EN_BUF_V, sizeof(EN_BUF_V)));
|
|
EPD_CHECK(epd_cmd_master(BTST_P, BTST_P_V, sizeof(BTST_P_V)));
|
|
EPD_CHECK(epd_cmd_master(BOOST_VDDP_EN, BOOST_VDDP_EN_V, sizeof(BOOST_VDDP_EN_V)));
|
|
EPD_CHECK(epd_cmd_master(BTST_N, BTST_N_V, sizeof(BTST_N_V)));
|
|
EPD_CHECK(epd_cmd_master(BUCK_BOOST_VDDN, BUCK_BOOST_VDDN_V, sizeof(BUCK_BOOST_VDDN_V)));
|
|
EPD_CHECK(epd_cmd_master(TFT_VCOM_POWER, TFT_VCOM_POWER_V, sizeof(TFT_VCOM_POWER_V)));
|
|
|
|
ESP_LOGI(TAG, "EPD initialized (CLK=%d MOSI=%d CS_M=%d CS_S=%d DC=%d RST=%d BUSY=%d PWR_EN=%d)",
|
|
CONFIG_EPD_PIN_CLK, CONFIG_EPD_PIN_MOSI, CONFIG_EPD_PIN_CS_MASTER,
|
|
CONFIG_EPD_PIN_CS_SLAVE, CONFIG_EPD_PIN_DC, CONFIG_EPD_PIN_RST,
|
|
CONFIG_EPD_PIN_BUSY, CONFIG_EPD_PIN_POWER_EN);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t epd_write_frame(epd_read_fn_t read_fn, void *ctx, uint32_t *out_crc32)
|
|
{
|
|
ESP_RETURN_ON_FALSE(read_fn != NULL, ESP_ERR_INVALID_ARG, TAG, "read_fn required");
|
|
|
|
/* Every row has to be sliced into a left (master) and right (slave)
|
|
* half before either half can go out over SPI, so -- unlike
|
|
* epd7in3e.c's single-CS passthrough -- bytes can't be forwarded to
|
|
* the wire as they arrive. Buffer the whole ~938KB frame in PSRAM
|
|
* first (EE02's XIAO ESP32-S3 Plus has 8MB of it). */
|
|
uint8_t *frame = heap_caps_malloc(EPD_FRAME_BYTES, MALLOC_CAP_SPIRAM);
|
|
if (frame == NULL) {
|
|
ESP_LOGE(TAG, "OOM allocating %u-byte frame buffer", (unsigned)EPD_FRAME_BYTES);
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
size_t total = 0;
|
|
uint32_t crc = 0;
|
|
size_t n;
|
|
while (total < EPD_FRAME_BYTES &&
|
|
(n = read_fn(frame + total, EPD_FRAME_BYTES - total, ctx)) > 0) {
|
|
crc = esp_rom_crc32_le(crc, frame + total, n);
|
|
total += n;
|
|
}
|
|
|
|
if (total != EPD_FRAME_BYTES) {
|
|
/* Same invariant as epd7in3e.c: never touch the panel on a
|
|
* short/wrong-size stream -- the visible screen is left exactly
|
|
* as it was. */
|
|
ESP_LOGE(TAG, "Stream supplied %u bytes, expected %u -- aborting refresh",
|
|
(unsigned)total, (unsigned)EPD_FRAME_BYTES);
|
|
free(frame);
|
|
return ESP_ERR_INVALID_SIZE;
|
|
}
|
|
|
|
/* De-interleave into one half-buffer at a time and DMA it out as a
|
|
* single contiguous transfer (chunked internally by epd_spi_write) --
|
|
* far fewer, far larger SPI transactions than sending 1600 separate
|
|
* 300-byte rows per side. */
|
|
const size_t HALF_ROW = EPD_BYTES_PER_ROW / 2; /* 300 */
|
|
const size_t HALF_BUF = HALF_ROW * EPD_HEIGHT; /* 480000 */
|
|
uint8_t *half = heap_caps_malloc(HALF_BUF, MALLOC_CAP_SPIRAM);
|
|
if (half == NULL) {
|
|
ESP_LOGE(TAG, "OOM allocating %u-byte half-frame scratch buffer", (unsigned)HALF_BUF);
|
|
free(frame);
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
for (size_t r = 0; r < EPD_HEIGHT; r++) {
|
|
memcpy(half + r * HALF_ROW, frame + r * EPD_BYTES_PER_ROW, HALF_ROW);
|
|
}
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_CS_MASTER, 0);
|
|
esp_err_t err = epd_send_command(DTM);
|
|
if (err == ESP_OK) {
|
|
err = epd_send_data(half, HALF_BUF);
|
|
}
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_CS_MASTER, 1);
|
|
|
|
if (err == ESP_OK) {
|
|
for (size_t r = 0; r < EPD_HEIGHT; r++) {
|
|
memcpy(half + r * HALF_ROW, frame + r * EPD_BYTES_PER_ROW + HALF_ROW, HALF_ROW);
|
|
}
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_CS_SLAVE, 0);
|
|
err = epd_send_command(DTM);
|
|
if (err == ESP_OK) {
|
|
err = epd_send_data(half, HALF_BUF);
|
|
}
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_CS_SLAVE, 1);
|
|
}
|
|
|
|
free(half);
|
|
free(frame);
|
|
EPD_CHECK(err);
|
|
|
|
if (out_crc32 != NULL) {
|
|
*out_crc32 = crc;
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx)
|
|
{
|
|
esp_err_t err = epd_write_frame(read_fn, ctx, NULL);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
return epd_turn_on_display();
|
|
}
|
|
|
|
typedef struct {
|
|
const uint8_t *data;
|
|
size_t len;
|
|
size_t pos;
|
|
} epd_buf_ctx_t;
|
|
|
|
static size_t epd_buf_read(uint8_t *chunk, size_t chunk_size, void *ctx_)
|
|
{
|
|
epd_buf_ctx_t *c = (epd_buf_ctx_t *)ctx_;
|
|
size_t remaining = c->len - c->pos;
|
|
size_t n = remaining < chunk_size ? remaining : chunk_size;
|
|
if (n == 0) {
|
|
return 0;
|
|
}
|
|
memcpy(chunk, c->data + c->pos, n);
|
|
c->pos += n;
|
|
return n;
|
|
}
|
|
|
|
esp_err_t epd_display_buffer(const uint8_t *frame, size_t len)
|
|
{
|
|
epd_buf_ctx_t buf_ctx = { .data = frame, .len = len, .pos = 0 };
|
|
return epd_display_stream(epd_buf_read, &buf_ctx);
|
|
}
|
|
|
|
typedef struct {
|
|
uint8_t fill_byte;
|
|
size_t remaining;
|
|
} epd_fill_ctx_t;
|
|
|
|
static size_t epd_fill_read(uint8_t *chunk, size_t chunk_size, void *ctx_)
|
|
{
|
|
epd_fill_ctx_t *c = (epd_fill_ctx_t *)ctx_;
|
|
size_t n = c->remaining < chunk_size ? c->remaining : chunk_size;
|
|
if (n == 0) {
|
|
return 0;
|
|
}
|
|
memset(chunk, c->fill_byte, n);
|
|
c->remaining -= n;
|
|
return n;
|
|
}
|
|
|
|
esp_err_t epd_clear(epd_color_t color)
|
|
{
|
|
epd_fill_ctx_t fill_ctx = {
|
|
.fill_byte = (uint8_t)((color << 4) | color),
|
|
.remaining = EPD_FRAME_BYTES,
|
|
};
|
|
return epd_display_stream(epd_fill_read, &fill_ctx);
|
|
}
|
|
|
|
esp_err_t epd_sleep(void)
|
|
{
|
|
epd_cs_both(0);
|
|
EPD_CHECK(epd_send_command(DEEP_SLEEP));
|
|
EPD_CHECK(epd_send_data_byte(0xA5)); /* magic deep-sleep arg per every reference driver */
|
|
epd_cs_both(1);
|
|
|
|
epd_delay_ms(100);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_POWER_EN, 0);
|
|
gpio_set_level((gpio_num_t)CONFIG_EPD_PIN_RST, 0);
|
|
|
|
return ESP_OK;
|
|
}
|