Skip to content

Commit

Permalink
Refactor and test LUT functions
Browse files Browse the repository at this point in the history
Refactors waveform lookup calculations to be more maintainable and adds
tests to avoid regressions. In the process, line masking is simplified
and ordering problems in 2ppB mode are fixed (this should fix #284).
8ppB now supports both PREVIOUSLY_BLACK and PREVIOUSLY_WHITE.
  • Loading branch information
vroland authored Sep 24, 2024
1 parent 560b642 commit 2591847
Show file tree
Hide file tree
Showing 26 changed files with 3,591 additions and 377 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
format-check:
runs-on: ubuntu-latest
container:
image: "espressif/idf:release-v5.2"
image: "espressif/idf:release-v5.3"
steps:
- uses: actions/checkout@v4
- run: |
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(app_sources "src/epdiy.c"
"src/output_common/lut.S"
"src/output_common/line_queue.c"
"src/output_common/render_context.c"
"src/output_common/render_method.c"
"src/font.c"
"src/displays.c"
"src/diff.S"
Expand Down
4 changes: 4 additions & 0 deletions examples/fb_mode_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.10.0)
set(EXTRA_COMPONENT_DIRS "../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(firmware)
4 changes: 4 additions & 0 deletions examples/fb_mode_test/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set(app_sources "main.c")

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
idf_component_register(SRCS ${app_sources} REQUIRES epdiy)
203 changes: 203 additions & 0 deletions examples/fb_mode_test/main/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
* Visual tests for framebuffer modes that are not commonly used by the high-level API.
* Currently, includes 2 pixel per byte (ppB) with static origin color and 8ppB with static origin
* color.
*
* After running this, you should see two identical test images, with a "ladder" of black triangles
* next to a black rectangle with a ladder of white triangles on it.
*/

#include <esp_heap_caps.h>
#include <esp_log.h>
#include <esp_sleep.h>
#include <esp_timer.h>
#include <esp_types.h>
#include <esp_assert.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <epdiy.h>

#include "sdkconfig.h"

#define WAVEFORM EPD_BUILTIN_WAVEFORM

// choose the default demo board depending on the architecture
#ifdef CONFIG_IDF_TARGET_ESP32
#define DEMO_BOARD epd_board_v6
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
#define DEMO_BOARD epd_board_v7
#endif

// Singular framebuffer to use for all of the tests.
// Allocated for 2ppB, the least compact that we test here.
uint8_t* framebuffer;
int fb_size;

static inline void checkError(enum EpdDrawError err) {
if (err != EPD_DRAW_SUCCESS) {
ESP_LOGE("demo", "draw error: %X", err);
}
}

/**
* Clears the screen to white and resets the framebuffer.
*/
void clear() {
epd_poweron();
epd_clear();
epd_poweroff();
memset(framebuffer, 0xFF, fb_size);
}

/**
* Draw triangles at varying alignments into the framebuffer in 8ppB mode.
* start_line, start_column specify the start position.
* The bits that belong to a triangle are flipped, i.e., it is drawn at the
* inverse color to the background it is drawn onto.
*/
void draw_8bpp_triangles(int start_line, int start_column) {
start_column /= 8;
int line_bytes = epd_width() / 8;

for (int align = 0; align < 16; align++) {
for (int height = 0; height < 16; height++) {
for (int len = 0; len <= height; len++) {
int line = (start_line + 16 * align + height);
int column = align + len;
uint8_t* line_address = framebuffer + (line_bytes * line);
*(line_address + start_column + column / 8) ^= 1 << (column % 8);
}
}
}
}

/**
* Draw triangles at varying alignments into the framebuffer in 2ppB mode.
* start_line, start_column specify the start position.
* color specifies the color to draw in.
*/
void draw_2bpp_triangles(int start_line, int start_column, uint8_t color) {
int height = 16;

for (int align = 0; align < 16; align++) {
int x0 = start_column + align;
int y0 = start_line + height * align;
int x1 = x0;
int y1 = y0 + height - 1;
int x2 = x0 + height - 1;
int y2 = y0 + height - 1;

epd_fill_triangle(x0, y0, x1, y1, x2, y2, color, framebuffer);
}
}

void test_8ppB() {
EpdRect area = epd_full_screen();
enum EpdDrawMode mode;

// bytes in a line in 8ppB mode
int line_bytes = epd_width() / 8;

int start_line = 100;

// draw differently aligned black triangles to check for uniformity
draw_8bpp_triangles(start_line, 80);

int black_start_column = 160;

// draw a black area
for (int line = 0; line < 300; line++) {
uint8_t* line_address = framebuffer + (line_bytes * (start_line + line));
memset(line_address + black_start_column / 8, 0, 32);
}

// update the display. In the first update, white pixels are no-opps,
// in the second update, black pixels are no-ops.
epd_poweron();
mode = MODE_PACKING_8PPB | MODE_DU | PREVIOUSLY_WHITE;
checkError(epd_draw_base(area, framebuffer, area, mode, 25, NULL, NULL, &epdiy_ED047TC2));
epd_poweroff();

// draw white triangles on the black background
draw_8bpp_triangles(start_line, black_start_column + 16);

epd_poweron();
mode = MODE_PACKING_8PPB | MODE_DU | PREVIOUSLY_BLACK;
checkError(epd_draw_base(area, framebuffer, area, mode, 25, NULL, NULL, &epdiy_ED047TC2));
epd_poweroff();
}

void test_2ppB() {
EpdRect area = epd_full_screen();
enum EpdDrawMode mode;
int start_column = 500;
int start_line = 100;

// draw differently aligned black triangles to check for uniformity
draw_2bpp_triangles(start_line, start_column, 0);

int black_start_column = start_column + 60;

// draw a black area
EpdRect black_area = { .x = black_start_column, .y = 100, .width = 256, .height = 300 };
epd_fill_rect(black_area, 0, framebuffer);

// Do not overdraw the 8ppB image
uint8_t* drawn_columns = malloc(epd_width() / 2);
assert(drawn_columns != NULL);
memset(drawn_columns, 0, epd_width() / 2);
memset(drawn_columns + start_column / 2, 255, (epd_width() - start_column) / 2);

// update the display. In the first update, white pixels are no-opps,
// in the second update, black pixels are no-ops.
epd_poweron();
mode = MODE_PACKING_2PPB | MODE_DU | PREVIOUSLY_WHITE;
checkError(
epd_draw_base(area, framebuffer, area, mode, 25, NULL, drawn_columns, &epdiy_ED047TC2)
);
epd_poweroff();

// draw white triangles on the black background
draw_2bpp_triangles(start_line, black_start_column + 16, 255);

epd_poweron();
mode = MODE_PACKING_2PPB | MODE_DU | PREVIOUSLY_BLACK;
checkError(
epd_draw_base(area, framebuffer, area, mode, 25, NULL, drawn_columns, &epdiy_ED047TC2)
);
epd_poweroff();

free(drawn_columns);
}

void app_main() {
epd_init(&DEMO_BOARD, &ED060XC3, EPD_OPTIONS_DEFAULT);

// Set VCOM for boards that allow to set this in software (in mV).
// This will print an error if unsupported. In this case,
// set VCOM using the hardware potentiometer and delete this line.
epd_set_vcom(2100);

epd_set_lcd_pixel_clock_MHz(10);

fb_size = epd_width() * epd_height() / 2;
framebuffer = heap_caps_aligned_alloc(16, fb_size, MALLOC_CAP_SPIRAM);

clear();

test_8ppB();

memset(framebuffer, 0xFF, fb_size);

test_2ppB();

printf("going to sleep...\n");
epd_deinit();
esp_deep_sleep_start();
}
Empty file.
Loading

0 comments on commit 2591847

Please sign in to comment.