Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 66 additions & 26 deletions components/lcd/esp_lcd_ssd1681/esp_lcd_panel_ssd1681.c

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions components/lcd/esp_lcd_ssd1681/esp_lcd_ssd1681_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@
#define SSD1681_CMD_SWRST 0x12
// --- Driver output control
#define SSD1681_CMD_OUTPUT_CTRL 0x01
#define SSD1681_PARAM_OUTPUT_CTRL ((uint8_t[]) {0xc7, 0x00, 0x00})
// Chipset wants (total row count - 1) i.e. 250 rows = 249 here
#define SSD1681_PARAM_OUTPUT_CTRL(rows) ((uint8_t[]) {(rows-1) & 0xFF, (rows-1) >> 8, 0x00})
// --- Data Entry Sequence Setting
#define SSD1681_CMD_DATA_ENTRY_MODE 0x11
// A [1:0] = ID[1:0], A[2] = AM
// the address counter is updated in the X direction
// AM = 0, the address counter is updated in the X direction
// 000 - Y decrement, X decrement
#define SSD1681_PARAM_DATA_ENTRY_MODE_0 0x00
// 001 – Y decrement, X increment
#define SSD1681_PARAM_DATA_ENTRY_MODE_1 0x01
// 010 - Y increment, X decrement
#define SSD1681_PARAM_DATA_ENTRY_MODE_2 0x02
// 011 - Y increment, X increment
// AM = 1, the address counter is updated in the Y direction
#define SSD1681_PARAM_DATA_ENTRY_MODE_3 0x03
// AM = 1, the address counter is updated in the Y direction
// 100 - Y decrement, X decrement
#define SSD1681_PARAM_DATA_ENTRY_MODE_4 0x04
// 101 – Y decrement, X increment
Expand Down
12 changes: 9 additions & 3 deletions components/lcd/esp_lcd_ssd1681/examples/epaper_demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This example shows how to use SSD1681 e-paper display driver from Component mana
### Hardware Required

* An ESP development board
* An SSD1681 e-paper panel, with SPI interface
* An SSD1681 or SSD1680 e-paper panel, with SPI interface
* An USB cable for power supply and programming

### Hardware Connection
Expand Down Expand Up @@ -42,7 +42,9 @@ The GPIO number used by this example can be changed in [main.c](main/main.c).
### Software configuration

- Change all the `EXAMPLE_PIN` macro definition according to your hardware connection.
- If you are not using waveshare 1.54 inch V2 e-paper panel, please use the waveform lut provided by your panel vendor instead of using the demo built-in ones, or just simply comment the `epaper_panel_set_custom_lut` call and use the panel built-in waveform lut.
- Select the display model in esp_lcd_panel_ssd1681.h. Supported displays are 1.54 inch 200x200 and 2.7 inch 176x264. The 2.7 inch display has a SSD1680 controller.
- If you are not using waveshare 1.54 inch V2 e-paper panel, please use the waveform lut provided by your panel vendor instead of using the demo built-in ones, or just simply comment out the `epaper_panel_set_custom_lut` call and use the panel built-in waveform lut.
NOTE: Waveshare and GooDisplay epaper displays can default to use built-in LUT.

### Build and Flash

Expand Down Expand Up @@ -72,7 +74,10 @@ I (720) epaper_demo_plain: Drawing bitmap...
...
```

## Show Custom Bitmap
## Show Test Images

Images are found in img_bitmap.c and bitmaps_176x264.c. If you want to test other size panels you should make your own bitmap image files.
Choosing the display (see Software COnfiguration) also selects an appropriate test image.

As you could see from the demo, each bitmap is stored as an array. If you want to display your custom image, you need to convert your image to a bitmap array first.

Expand All @@ -87,6 +92,7 @@ You could convert your image to bitmap array by the following steps:
- Do not select the `Output in big-endian format` option.
- Click `Convert` and you get a `.c` file containing the bitmap array.

Programs image2cpp or img2cpp can generate bitmap.c files from JPEG or PNG inputs.
There are plenty of software alternative with such functionality, please pay attention to the scan mode if you prefer to use them.

The driver writes bitmap array to the vram in the sequence below by default:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
idf_component_register(SRCS "main.c" "img_bitmap.c"
idf_component_register(SRCS "bitmaps_176x264.c" "main.c" "img_bitmap.c"
INCLUDE_DIRS "")
1,105 changes: 1,105 additions & 0 deletions components/lcd/esp_lcd_ssd1681/examples/epaper_demo/main/bitmaps_176x264.c

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ const uint8_t BITMAP_200_200[] = { /* 0X00,0X01,0XC8,0X00,0XC8,0X00, */
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0X80, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0X80, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01, 0XFF,
0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X00, 0X01,
0XFF,
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ extern "C" {
extern const uint8_t BITMAP_200_200[];
extern const uint8_t BITMAP_128_64[];
extern const uint8_t BITMAP_64_128[];

extern const uint8_t BITMAP_176_264[];
extern const uint8_t BITMAP_264_176[];
extern const uint8_t TEXT_176_264[];
#ifdef __cplusplus
}
#endif
170 changes: 137 additions & 33 deletions components/lcd/esp_lcd_ssd1681/examples/epaper_demo/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
*
* SPDX-License-Identifier: CC0-1.0
*/
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG

// Full screen test controls
#define MIRROR_NO 1
#define MIRROR_X 1
#define MIRROR_Y 1
#define MIRROR_XY 1

// second part of test, smaller than full screen, move images around
#define SHOW_PENGUIN 1

#include <inttypes.h>
#include <string.h>
Expand All @@ -18,14 +28,27 @@
#include "driver/spi_common.h"
#include "driver/gpio.h"

#include "ssd1681_waveshare_1in54_lut.h"
//#include "ssd1681_waveshare_1in54_lut.h"
#include "img_bitmap.h"

// Test Images
#if WAVE_154
#define FULL_IMAGE BITMAP_200_200
#define FULL_INVERT false
#elif WAVE_27
//#define FULL_IMAGE BITMAP_176_264
#define FULL_IMAGE TEXT_176_264
#define FULL_INVERT true // the test image was constructed wrong with WHITE=0xff and BLACK=0x00
#endif

// SPI Bus
#define EPD_PANEL_SPI_CLK 1000000
#define EPD_PANEL_SPI_CMD_BITS 8
#define EPD_PANEL_SPI_PARAM_BITS 8
#define EPD_PANEL_SPI_MODE 0

// ESP32 GPIO pins used
#if 0
// e-Paper GPIO
#define EXAMPLE_PIN_NUM_EPD_DC 9
#define EXAMPLE_PIN_NUM_EPD_RST 4
Expand All @@ -34,6 +57,27 @@
// e-Paper SPI
#define EXAMPLE_PIN_NUM_MOSI 7
#define EXAMPLE_PIN_NUM_SCLK 6
#endif
#if 0
// e-Paper GPIO with ESP32C6
#define EXAMPLE_PIN_NUM_EPD_DC 8
#define EXAMPLE_PIN_NUM_EPD_RST 10
#define EXAMPLE_PIN_NUM_EPD_CS 5
#define EXAMPLE_PIN_NUM_EPD_BUSY 11
// e-Paper SPI
#define EXAMPLE_PIN_NUM_MOSI 15
#define EXAMPLE_PIN_NUM_SCLK 18
#endif
#if 1
// e-Paper GPIO with Waveshare ESP32S3
#define EXAMPLE_PIN_NUM_EPD_DC 8
#define EXAMPLE_PIN_NUM_EPD_RST 10
#define EXAMPLE_PIN_NUM_EPD_CS 5
#define EXAMPLE_PIN_NUM_EPD_BUSY 11
// e-Paper SPI
#define EXAMPLE_PIN_NUM_MOSI 15
#define EXAMPLE_PIN_NUM_SCLK 18
#endif

static const char *TAG = "epaper_demo_plain";

Expand All @@ -49,9 +93,46 @@ static bool give_semaphore_in_isr(const esp_lcd_panel_handle_t handle, const voi
return false;
}

// Copy a bitmap image to display buffer. Not used when FULL_IMAGE is same size as screen.
// Also the display buffer is usually assumed to be full screen.
uint8_t* crop_bitmap(const uint8_t* in_bitmap, int in_x, int in_y, int out_x, int out_y)
{
out_x = (out_x < in_x) ? out_x : in_x;
int b_dup = 0;
if (out_y > in_y) b_dup = 1;

ESP_LOGD(TAG, "out_x = %d, out_y = %d", out_x, out_y);
uint8_t *out_bitmap = heap_caps_malloc(DISPLAY_W * DISPLAY_H / 8, MALLOC_CAP_DMA);
memset(out_bitmap, 0, DISPLAY_W * DISPLAY_H / 8);
if (in_x == out_x && in_y == out_y) {
memcpy(out_bitmap, in_bitmap, in_x * in_y / 8);
}
else {
uint8_t *out_ptr = out_bitmap;
uint8_t *in_ptr = in_bitmap;
ESP_LOGD(TAG, "in_bitmap = %p", (void *)in_bitmap);
ESP_LOGD(TAG, "out_bitmap = %p", (void *)out_bitmap);
// row index
for (int iy = 0; iy < out_y; iy++) {
if (b_dup && iy >= in_y) { in_ptr = in_bitmap; b_dup = 0;}
// ix is column index. Inner loop copies a single row.
for (int ix = 0; ix < out_x/8; ix++) {
*out_ptr = *in_ptr;
out_ptr++; in_ptr++;
}
//ESP_LOGI(TAG, "crop_bitmap: row = %d, in_ptr idx = %d", iy, in_ptr-in_bitmap);
in_ptr += (in_x - out_x)/8;
}
ESP_LOGD(TAG, "crop_bitmap copied %d bytes", (out_ptr - out_bitmap));
}
return out_bitmap;
}

void app_main(void)
{
esp_err_t ret;
esp_log_level_set(TAG, ESP_LOG_DEBUG);

// --- Init SPI Bus
ESP_LOGI(TAG, "Initializing SPI Bus...");
spi_bus_config_t buscfg = {
Expand Down Expand Up @@ -120,13 +201,6 @@ void app_main(void)
static SemaphoreHandle_t epaper_panel_semaphore;
epaper_panel_semaphore = xSemaphoreCreateBinary();
xSemaphoreGive(epaper_panel_semaphore);
// --- Clear the VRAM of RED and BLACK
uint8_t *empty_bitmap = heap_caps_malloc(200 * 200 / 8, MALLOC_CAP_DMA);
memset(empty_bitmap, 0, 200 * 200 / 8);
epaper_panel_set_bitmap_color(panel_handle, SSD1681_EPAPER_BITMAP_RED);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, empty_bitmap);
epaper_panel_set_bitmap_color(panel_handle, SSD1681_EPAPER_BITMAP_BLACK);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, empty_bitmap);

// --- Register the e-Paper refresh done callback
// cbs does not have to be static for ssd1681 driver, for the callback ptr is copied, not pointed
Expand All @@ -135,74 +209,104 @@ void app_main(void)
};
epaper_panel_register_event_callbacks(panel_handle, &cbs, &epaper_panel_semaphore);

// --- Clear the VRAM of RED and BLACK
ESP_LOGI(TAG, "Clear Screen");
uint8_t *empty_bitmap = heap_caps_malloc(DISPLAY_W * DISPLAY_H / 8, MALLOC_CAP_DMA);
memset(empty_bitmap, 0, DISPLAY_W * DISPLAY_H / 8);
#if 0 // no RED on my panel
epaper_panel_set_bitmap_color(panel_handle, SSD1681_EPAPER_BITMAP_RED);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, empty_bitmap);
#endif
epaper_panel_set_bitmap_color(panel_handle, SSD1681_EPAPER_BITMAP_BLACK);
esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, empty_bitmap);
// refresh screen just so I can see that it is blank - not required
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));
// Maybe SPI operation is still going on, so set a delay
vTaskDelay(pdMS_TO_TICKS(5000));
heap_caps_free(empty_bitmap);

// --- Draw full-screen bitmap
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_LOGI(TAG, "Show image full-screen");

uint8_t *crop_image = heap_caps_malloc(DISPLAY_W * DISPLAY_H / 8, MALLOC_CAP_DMA);
memset(crop_image, 0, DISPLAY_W * DISPLAY_H / 8);
memcpy(crop_image, FULL_IMAGE, DISPLAY_W * DISPLAY_H / 8);
#if MIRROR_NO
ESP_LOGI(TAG, "Draw image NO Mirror");
xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, FULL_INVERT));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, crop_image));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

#endif
#if MIRROR_Y
ESP_LOGI(TAG, "Mirror Y axis");
vTaskDelay(pdMS_TO_TICKS(5000));
xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, true));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, FULL_INVERT));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, crop_image));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

#endif
#if MIRROR_X
ESP_LOGI(TAG, "Mirror X axis");
vTaskDelay(pdMS_TO_TICKS(5000));
xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, FULL_INVERT));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, crop_image));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

#endif
#if MIRROR_XY
ESP_LOGI(TAG, "Mirror X&Y axis");
vTaskDelay(pdMS_TO_TICKS(5000));
xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, true));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, FULL_INVERT));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, crop_image));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

#endif
#if SQUARE_PANEL // swap_xy tests: not implemented for non-square panels
// TODO: if display is normally portrait, then test swap_xy by inputting a landscape image
xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, true));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, BITMAP_200_200));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, BITMAP_200_200));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

xSemaphoreTake(epaper_panel_semaphore, portMAX_DELAY);
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, true));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true));
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 200, 200, BITMAP_200_200));
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, DISPLAY_W, DISPLAY_H, BITMAP_200_200));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));
#endif
heap_caps_free(crop_image);

#if SHOW_PENGUIN
// Show penguin images that are smaller than full screen.
// This code does not clear the display so the previous full screen image remains and goes thru
// various XY mirrors. Furthermore the full screen image may have been displayed with invert_color,
// if so it will not be inverted here.
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "Go to sleep mode...");
esp_lcd_panel_disp_on_off(panel_handle, false);
Expand Down Expand Up @@ -249,7 +353,7 @@ void app_main(void)
ESP_LOGI(TAG, "Drawing bitmap...");
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, 16, 0, 144, 64, BITMAP_128_64));
ESP_ERROR_CHECK(epaper_panel_refresh_screen(panel_handle));

#endif
vTaskDelay(pdMS_TO_TICKS(5000));
ESP_LOGI(TAG, "Go to sleep mode...");
esp_lcd_panel_disp_on_off(panel_handle, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ dependencies:
# I am specifying the path of the component because the component
# had not been published to the ESP Component Registry by the time
# I write this example.
path: "../../../"
path: "../../../"
Loading
Loading