Прошло 2 года.

This commit is contained in:
Кобелев Андрей Андреевич
2026-03-10 22:54:23 +03:00
parent c7636ebd6f
commit a111352dc5
313 changed files with 274971 additions and 1409 deletions

View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.16.0)
set(EXTRA_COMPONENT_DIRS "../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(vcom_calibration_helper)

View File

@@ -0,0 +1,3 @@
set(app_sources "main.c")
idf_component_register(SRCS ${app_sources} REQUIRES epdiy)

View File

@@ -0,0 +1,42 @@
/*
* Simple helper program enabling EPD power to allow for easier VCOM calibration.
*
* This is only needed for boards V5 or lower!
**/
#include <stdio.h>
#include <string.h>
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "epdiy.h"
// 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
void enable_vcom() {
epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K);
ESP_LOGI("main", "waiting for one second before poweron...");
vTaskDelay(1000);
ESP_LOGI("main", "enabling VCOMM...");
epd_poweron();
ESP_LOGI(
"main",
"VCOMM enabled. You can now adjust the on-board trimmer to the VCOM value specified on the "
"display."
);
while (1) {
vTaskDelay(1000);
};
}
void app_main() {
enable_vcom();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -0,0 +1,21 @@
FONT_DIR ?= /usr/share/fonts/TTF
FONTCONVERT ?= ../../scripts/fontconvert.py
IMGCONVERT ?= ../../scripts/imgconvert.py
.PHONY: fonts
fonts: \
main/firasans_20.h \
main/firasans_12.h \
main/img_zebra.h \
main/img_board.h \
main/img_giraffe.h \
main/img_beach.h
main/firasans_%.h: $(FONTCONVERT)
python3 $(FONTCONVERT) --compress FiraSans_$* $* $(FONT_DIR)/FiraSans-Regular.ttf $(FONT_DIR)/Symbola.ttf > $@
main/img_%.h: files/%.jpg
python3 $(IMGCONVERT) -i $< -o $@ -n img_$*
clean:
rm main/*.h

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -0,0 +1,4 @@
set(app_sources "main.c")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
idf_component_register(SRCS ${app_sources} REQUIRES epdiy)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,271 @@
/* Simple firmware for a ESP32 displaying a static image on an EPaper Screen.
*
* Write an image into a header file using a 3...2...1...0 format per pixel,
* for 4 bits color (16 colors - well, greys.) MSB first. At 80 MHz, screen
* clears execute in 1.075 seconds and images are drawn in 1.531 seconds.
*/
#include <esp_heap_caps.h>
#include <esp_log.h>
#include <esp_sleep.h>
#include <esp_timer.h>
#include <esp_types.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <epdiy.h>
#include "sdkconfig.h"
#include "firasans_12.h"
#include "firasans_20.h"
#include "img_beach.h"
#include "img_board.h"
#include "img_zebra.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
EpdiyHighlevelState hl;
void idf_setup() {
epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K);
// 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(1560);
hl = epd_hl_init(WAVEFORM);
// Default orientation is EPD_ROT_LANDSCAPE
epd_set_rotation(EPD_ROT_LANDSCAPE);
printf(
"Dimensions after rotation, width: %d height: %d\n\n",
epd_rotated_display_width(),
epd_rotated_display_height()
);
// The display bus settings for V7 may be conservative, you can manually
// override the bus speed to tune for speed, i.e., if you set the PSRAM speed
// to 120MHz.
// epd_set_lcd_pixel_clock_MHz(17);
heap_caps_print_heap_info(MALLOC_CAP_INTERNAL);
heap_caps_print_heap_info(MALLOC_CAP_SPIRAM);
}
#ifndef ARDUINO_ARCH_ESP32
void delay(uint32_t millis) {
vTaskDelay(millis / portTICK_PERIOD_MS);
}
#endif
static inline void checkError(enum EpdDrawError err) {
if (err != EPD_DRAW_SUCCESS) {
ESP_LOGE("demo", "draw error: %X", err);
}
}
void draw_progress_bar(int x, int y, int width, int percent, uint8_t* fb) {
const uint8_t white = 0xFF;
const uint8_t black = 0x0;
EpdRect border = {
.x = x,
.y = y,
.width = width,
.height = 20,
};
epd_fill_rect(border, white, fb);
epd_draw_rect(border, black, fb);
EpdRect bar = {
.x = x + 5,
.y = y + 5,
.width = (width - 10) * percent / 100,
.height = 10,
};
epd_fill_rect(bar, black, fb);
checkError(epd_hl_update_area(&hl, MODE_DU, epd_ambient_temperature(), border));
}
void idf_loop() {
// select the font based on display width
const EpdFont* font;
if (epd_width() < 1000) {
font = &FiraSans_12;
} else {
font = &FiraSans_20;
}
uint8_t* fb = epd_hl_get_framebuffer(&hl);
epd_poweron();
epd_clear();
int temperature = epd_ambient_temperature();
epd_poweroff();
printf("current temperature: %d\n", temperature);
epd_fill_circle(30, 30, 15, 0, fb);
int cursor_x = epd_rotated_display_width() / 2;
int cursor_y = epd_rotated_display_height() / 2 - 100;
EpdFontProperties font_props = epd_font_properties_default();
font_props.flags = EPD_DRAW_ALIGN_CENTER;
char srotation[32];
sprintf(srotation, "Loading demo...\nRotation: %d", epd_get_rotation());
epd_write_string(font, srotation, &cursor_x, &cursor_y, fb, &font_props);
int bar_x = epd_rotated_display_width() / 2 - 200;
int bar_y = epd_rotated_display_height() / 2;
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GL16, temperature));
epd_poweroff();
for (int i = 0; i < 6; i++) {
epd_poweron();
draw_progress_bar(bar_x, bar_y, 400, i * 10, fb);
epd_poweroff();
}
cursor_x = epd_rotated_display_width() / 2;
cursor_y = epd_rotated_display_height() / 2 + 100;
epd_write_string(
font, "Just kidding,\n this is a demo animation 😉", &cursor_x, &cursor_y, fb, &font_props
);
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GL16, temperature));
epd_poweroff();
for (int i = 0; i < 6; i++) {
epd_poweron();
draw_progress_bar(bar_x, bar_y, 400, 50 - i * 10, fb);
epd_poweroff();
vTaskDelay(1);
}
cursor_y = epd_rotated_display_height() / 2 + 200;
cursor_x = epd_rotated_display_width() / 2;
EpdRect clear_area = {
.x = 0,
.y = epd_rotated_display_height() / 2 + 100,
.width = epd_rotated_display_width(),
.height = 300,
};
epd_fill_rect(clear_area, 0xFF, fb);
epd_write_string(
font, "Now let's look at some pictures.", &cursor_x, &cursor_y, fb, &font_props
);
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GL16, temperature));
epd_poweroff();
delay(1000);
epd_hl_set_all_white(&hl);
EpdRect zebra_area = {
.x = epd_rotated_display_width() / 2 - img_zebra_width / 2,
.y = epd_rotated_display_height() / 2 - img_zebra_height / 2,
.width = img_zebra_width,
.height = img_zebra_height,
};
epd_draw_rotated_image(zebra_area, img_zebra_data, fb);
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GC16, temperature));
epd_poweroff();
delay(5000);
EpdRect board_area = {
.x = epd_rotated_display_width() / 2 - img_board_width / 2,
.y = epd_rotated_display_height() / 2 - img_board_height / 2,
.width = img_board_width,
.height = img_board_height,
};
epd_draw_rotated_image(board_area, img_board_data, fb);
cursor_x = epd_rotated_display_width() / 2;
cursor_y = board_area.y;
font_props.flags |= EPD_DRAW_BACKGROUND;
epd_write_string(font, "↓ Thats the V2 board. ↓", &cursor_x, &cursor_y, fb, &font_props);
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GC16, temperature));
epd_poweroff();
delay(5000);
epd_hl_set_all_white(&hl);
EpdRect border_rect = {
.x = 20,
.y = 20,
.width = epd_rotated_display_width() - 40,
.height = epd_rotated_display_height() - 40,
};
epd_draw_rect(border_rect, 0, fb);
cursor_x = 50;
cursor_y = 100;
epd_write_default(
font,
"➸ 16 color grayscale\n"
"➸ ~250ms - 1700ms for full frame draw 🚀\n"
"➸ Use with 6\" or 9.7\" EPDs\n"
"➸ High-quality font rendering ✎🙋\n"
"➸ Partial update\n"
"➸ Arbitrary transitions with vendor waveforms",
&cursor_x,
&cursor_y,
fb
);
EpdRect img_beach_area = {
.x = 0,
.y = epd_rotated_display_height() - img_beach_height,
.width = img_beach_width,
.height = img_beach_height,
};
epd_draw_rotated_image(img_beach_area, img_beach_data, fb);
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GC16, temperature));
epd_poweroff();
printf("going to sleep...\n");
epd_deinit();
esp_deep_sleep_start();
}
#ifndef ARDUINO_ARCH_ESP32
void app_main() {
idf_setup();
while (1) {
idf_loop();
};
}
#endif

View File

@@ -0,0 +1,30 @@
/**
* This is the Arduino wrapper for the "Demo" example.
* Please go to the main.c for the main example file.
*
* This example was developed for the ESP IoT Development Framework (IDF).
* You can still use this code in the Arduino IDE, but it may not look
* and feel like a classic Arduino sketch.
* If you are looking for an example with Arduino look-and-feel,
* please check the other examples.
*/
// Important: These are C functions, so they must be declared with C linkage!
extern "C" {
void idf_setup();
void idf_loop();
}
void setup() {
if (psramInit()) {
Serial.println("\nThe PSRAM is correctly initialized");
} else {
Serial.println("\nPSRAM does not work");
}
idf_setup();
}
void loop() {
idf_loop();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.16.0)
set(EXTRA_COMPONENT_DIRS "../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(dragon_example)

View File

@@ -0,0 +1,7 @@
A demo showing a Full-Screen Image
==================================
*The image size is chosen to fit a 1200 * 825 display!*
Image by David REVOY / CC BY (https://creativecommons.org/licenses/by/3.0)
https://commons.wikimedia.org/wiki/File:Durian_-_Sintel-wallpaper-dragon.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

View File

@@ -0,0 +1,3 @@
set(app_sources "main.c")
idf_component_register(SRCS ${app_sources} REQUIRES epdiy)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
/* Simple firmware for a ESP32 displaying a static image on an EPaper Screen */
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "dragon.h"
#include "epd_highlevel.h"
#include "epdiy.h"
EpdiyHighlevelState hl;
// 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
void idf_loop() {
EpdRect dragon_area = { .x = 0, .y = 0, .width = dragon_width, .height = dragon_height };
int temperature = 25;
epd_poweron();
epd_fullclear(&hl, temperature);
epd_copy_to_framebuffer(dragon_area, dragon_data, epd_hl_get_framebuffer(&hl));
enum EpdDrawError _err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
epd_poweroff();
vTaskDelay(1000);
}
void idf_setup() {
epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K);
epd_set_vcom(1560);
hl = epd_hl_init(EPD_BUILTIN_WAVEFORM);
}
#ifndef ARDUINO_ARCH_ESP32
void app_main() {
idf_setup();
while (1) {
idf_loop();
};
}
#endif

View File

@@ -0,0 +1,30 @@
/**
* This is the Arduino wrapper for the "Demo" example.
* Please go to the main.c for the main example file.
*
* This example was developed for the ESP IoT Development Framework (IDF).
* You can still use this code in the Arduino IDE, but it may not look
* and feel like a classic Arduino sketch.
* If you are looking for an example with Arduino look-and-feel,
* please check the other examples.
*/
// Important: These are C functions, so they must be declared with C linkage!
extern "C" {
void idf_setup();
void idf_loop();
}
void setup() {
if (psramInit()) {
Serial.println("\nThe PSRAM is correctly initialized");
} else {
Serial.println("\nPSRAM does not work");
}
idf_setup();
}
void loop() {
idf_loop();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -0,0 +1,4 @@
set(app_sources "main.c")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
idf_component_register(SRCS ${app_sources} REQUIRES epdiy)

View File

@@ -0,0 +1,223 @@
/*
* 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 "epd_display.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);
}
/**
* Clear an example area on the screen to white, does not reset the framebuffer.
*/
void clear_area(EpdRect clear_area) {
epd_poweron();
epd_clear_area(clear_area);
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();
// Clear at different positions
EpdRect area = { .x = 100, .y = epd_height() - 200, .width = 80, .height = 100 };
clear_area(area);
area.width += 1;
area.x += 100;
clear_area(area);
area.x += 101;
clear_area(area);
printf("going to sleep...\n");
epd_deinit();
esp_deep_sleep_start();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.16.0)
set(EXTRA_COMPONENT_DIRS "../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(grayscale_example)

View File

@@ -0,0 +1,3 @@
set(app_sources "main.c")
idf_component_register(SRCS ${app_sources} REQUIRES epdiy)

View File

@@ -0,0 +1,81 @@
/*
* Test program for displaying a grayscale pattern.
*
* Use this to calibrate grayscale timings for new displays or test alternative waveforms.
*/
#include <stdint.h>
#include <string.h>
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "epd_highlevel.h"
#include "epdiy.h"
// 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
#define WAVEFORM EPD_BUILTIN_WAVEFORM
EpdiyHighlevelState hl;
void write_grayscale_pattern(bool direction, uint8_t* fb) {
int ep_width = epd_width();
uint8_t grayscale_line[ep_width / 2];
if (direction) {
for (uint32_t i = 0; i < ep_width / 2; i++) {
uint8_t segment = i / (ep_width / 16 / 2);
grayscale_line[i] = (segment << 4) | segment;
}
} else {
for (uint32_t i = 0; i < ep_width / 2; i++) {
uint8_t segment = (ep_width / 2 - i - 1) / (ep_width / 16 / 2);
grayscale_line[i] = (segment << 4) | segment;
}
}
for (uint32_t y = 0; y < epd_height(); y++) {
memcpy(fb + ep_width / 2 * y, grayscale_line, ep_width / 2);
}
}
void loop() {
uint8_t* fb = epd_hl_get_framebuffer(&hl);
write_grayscale_pattern(false, fb);
int temperature = 25; // epd_ambient_temperature();
epd_poweron();
epd_clear();
enum EpdDrawError err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
epd_poweroff();
vTaskDelay(5000);
write_grayscale_pattern(true, fb);
epd_poweron();
err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
if (err != EPD_DRAW_SUCCESS) {
printf("Error in epd_hl_update_screen:%d\n", err);
}
epd_poweroff();
vTaskDelay(100000);
}
void IRAM_ATTR app_main() {
epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K);
epd_set_vcom(1560);
hl = epd_hl_init(WAVEFORM);
while (1) {
loop();
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(http-server)

View File

@@ -0,0 +1,34 @@
# HTTP server
Runs an HTTP server that will draw images on the screen.
Useful for setting up a small digital frame that can be remotely controlled.
## Config
1. In `main/server.h`, edit `WIFI_SSID` and `WIFI_PASSWORD` to match your wifi config
2. In `main/main.c`, edit `n_epd_setup` to refer to the right EPD screen
## Running
flash (`idf.py flash`), then connect the EPDiy to a power source (computer is fine).
The endpoints are:
1. `GET /`, prints the screen temp / height / width as headers
2. `POST /clear`, clears the screen
3. `POST /draw`, expects:
1. a body that is a binary stream already encoded to EPDiy's standards (like the one in `dragon.h`).
2. Headers `width`, `height`
3. Optional headers `x`,`y` (default to 0)
4. Optional header `clear`, if set to nonzero integer will force-clear the screen before drawing
## Helper script
`send_image.py` is a friendlier client.
```bash
$ ./send_image.py ESP_IP info
EpdInfo(width=1024, height=768, temperature=20)
$ ./send_image.py ESP_IP clear
# Clears the screen
$ ./send_image.y ESP_IP draw /tmp/spooder-man.png
# Draws on screen
```
Thanks to argparse, all arguments are visible with `--help`.
Requires `requests` and `PIL` (or Pillow)

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,8 @@
set(
app_sources "epd.c" "server.c" "main.c"
)
idf_component_register(
SRCS ${app_sources}
REQUIRES epdiy esp_wifi nvs_flash esp_http_server esp_netif
)

View File

@@ -0,0 +1,44 @@
#include "epd.h"
static EpdiyHighlevelState hl;
static EpdData data;
static inline void checkError(enum EpdDrawError err) {
if (err != EPD_DRAW_SUCCESS) {
ESP_LOGE("demo", "draw error: %X", err);
}
}
EpdData n_epd_data() {
return data;
}
void n_epd_setup(const EpdDisplay_t* display) {
epd_init(&epd_board_v7, display, EPD_LUT_64K);
epd_set_vcom(1560);
hl = epd_hl_init(EPD_BUILTIN_WAVEFORM);
epd_set_rotation(EPD_ROT_LANDSCAPE);
data.width = epd_rotated_display_width();
data.height = epd_rotated_display_height();
data.temperature = epd_ambient_temperature();
}
void n_epd_clear() {
epd_poweron();
epd_fullclear(&hl, data.temperature);
epd_poweroff();
}
void n_epd_draw(uint8_t* content, int x, int y, int width, int height) {
uint8_t* fb = epd_hl_get_framebuffer(&hl);
EpdRect area = {
.x = x,
.y = y,
.width = width,
.height = height,
};
epd_draw_rotated_image(area, content, fb);
epd_poweron();
checkError(epd_hl_update_screen(&hl, MODE_GC16, data.temperature));
epd_poweroff();
}

View File

@@ -0,0 +1,17 @@
#ifndef EPD_H
#define EPD_H
#include "epdiy.h"
#include <esp_log.h>
typedef struct {
int width;
int height;
int temperature;
} EpdData;
EpdData n_epd_data();
void n_epd_setup();
void n_epd_clear();
void n_epd_draw(uint8_t* content, int x, int y, int width, int height);
#endif /* EPD_H */

View File

@@ -0,0 +1,149 @@
#include "server.h"
#include "esp_http_server.h"
#include "epd.h"
#include "esp_heap_caps.h"
#include "settings.h"
#define WRITE_HEADER(req, buffer, name, format, src) \
sprintf(buffer, format, src); \
ESP_ERROR_CHECK(httpd_resp_set_hdr(req, name, buffer));
static esp_err_t http_index(httpd_req_t* req) {
EpdData data = n_epd_data();
char width[20], height[20], temperature[20];
WRITE_HEADER(req, width, "width", "%d", data.width);
WRITE_HEADER(req, height, "height", "%d", data.height);
WRITE_HEADER(req, temperature, "temperature", "%d", data.temperature);
const char* response = "Hello! Check headers\n";
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "200");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
esp_err_t http_clear(httpd_req_t* req) {
ESP_LOGI(__FUNCTION__, "Clear\n");
n_epd_clear();
const char* response = "Cleared\n";
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "200");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
esp_err_t http_draw(httpd_req_t* req) {
// optional headers: x,y. Default to 0
// required headers: height, width
// Content should be a stream of special bytes - we're reading 4 bits at a time.
int x, y, width, height, clear;
char header[20];
memset(header, 0, 20);
if (httpd_req_get_hdr_value_str(req, "clear", header, 20) == ESP_OK) {
sscanf(header, "%d", &clear);
} else {
clear = 0;
}
if (httpd_req_get_hdr_value_str(req, "x", header, 20) == ESP_OK) {
sscanf(header, "%d", &x);
} else {
x = 0;
}
if (httpd_req_get_hdr_value_str(req, "y", header, 20) == ESP_OK) {
sscanf(header, "%d", &y);
} else {
y = 0;
}
if (httpd_req_get_hdr_value_str(req, "width", header, 20) == ESP_OK) {
sscanf(header, "%d", &width);
} else {
char response[60];
sprintf(response, "Missing header width");
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "400");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
if (httpd_req_get_hdr_value_str(req, "height", header, 20) == ESP_OK) {
sscanf(header, "%d", &height);
} else {
char response[60];
sprintf(response, "Missing header height");
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "400");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
// READING STREAM
int req_size = req->content_len;
char* content = (char*)heap_caps_malloc(req_size, MALLOC_CAP_SPIRAM);
if (content == NULL) {
char msg[50];
sprintf(msg, "Failed to allocate %d chars\n", req_size);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg);
return ESP_ERR_INVALID_ARG;
}
int current_pos = 0;
int amount_recieved;
while ((amount_recieved = httpd_req_recv(req, (content + current_pos), req_size)) > 0) {
ESP_LOGI(__FUNCTION__, "Read %d bytes\n", amount_recieved);
current_pos += amount_recieved;
}
if (amount_recieved < 0) {
char msg[50];
heap_caps_free(content);
ESP_LOGE(msg, "Failed to read byets. Error code %d\n", amount_recieved);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, msg);
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(__FUNCTION__, "Done reading %d bytes out of %d\n", current_pos, req_size);
if (clear) {
n_epd_clear();
}
n_epd_draw(((uint8_t*)content), x, y, width, height);
heap_caps_free(content);
// Done reading
char response[100];
sprintf(
response, "x %d, y %d, width %d, height %d, byte count %d\n", x, y, width, height, req_size
);
httpd_resp_set_type(req, "text/plain");
httpd_resp_set_status(req, "200");
httpd_resp_send(req, response, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
void register_paths(httpd_handle_t server) {
{
httpd_uri_t uri
= { .uri = "/", .method = HTTP_GET, .handler = http_index, .user_ctx = NULL };
httpd_register_uri_handler(server, &uri);
}
{
httpd_uri_t uri
= { .uri = "/clear", .method = HTTP_POST, .handler = http_clear, .user_ctx = NULL };
httpd_register_uri_handler(server, &uri);
}
{
httpd_uri_t uri
= { .uri = "/draw", .method = HTTP_POST, .handler = http_draw, .user_ctx = NULL };
httpd_register_uri_handler(server, &uri);
}
}
void app_main(void) {
// Initialize NVS, needed for wifi
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
httpd_handle_t server = get_server();
if (server != NULL) {
register_paths(server);
}
n_epd_setup(&SCREEN_MODEL);
}

View File

@@ -0,0 +1,46 @@
#include "server.h"
httpd_handle_t get_server(void);
static void wifi_init_sta(void) {
// Initialize the ESP-NETIF
esp_netif_init();
esp_event_loop_create_default();
// Create default event loop
esp_netif_create_default_wifi_sta();
// Initialize the Wi-Fi driver
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Set Wi-Fi mode to station
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// Configure Wi-Fi connection
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
},
};
// Set the Wi-Fi configuration
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_connect());
}
httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.lru_purge_enable = true;
httpd_handle_t server = NULL;
ESP_ERROR_CHECK(httpd_start(&server, &config));
return server;
}
httpd_handle_t get_server(void) {
wifi_init_sta();
return start_webserver();
}

View File

@@ -0,0 +1,19 @@
#ifndef SERVER_H
#define SERVER_H
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#include "settings.h"
httpd_handle_t get_server(void);
#endif /* SERVER_H */

View File

@@ -0,0 +1,9 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#define WIFI_SSID "best ssid" // Replace with your Wi-Fi SSID
#define WIFI_PASSWORD "great password much wow" // Replace with your Wi-Fi password
#define SCREEN_MODEL ED060XC3
#endif /* SETTINGS_H */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
#!/usr/bin/env python3
import argparse
import requests
from typing import NamedTuple
import PIL
import PIL.Image
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("hostname")
subparsers = parser.add_subparsers(dest="command")
clear_parser = subparsers.add_parser("clear")
draw_parser = subparsers.add_parser("draw")
draw_parser.add_argument("-c", "--clear", action="store_true")
draw_parser.add_argument("file")
info_praser = subparsers.add_parser("info")
return parser.parse_args()
def clear(hostname):
requests.post(f"http://{hostname}/clear").raise_for_status()
class EpdInfo(NamedTuple):
width: int
height: int
temperature: int
@classmethod
def from_response(cls, resp):
return cls(
width=int(resp.headers["width"]),
height=int(resp.headers["height"]),
temperature=int(resp.headers["temperature"]),
)
class Dimensions(NamedTuple):
width: int
height: int
def info(hostname):
resp = requests.get(f"http://{hostname}")
resp.raise_for_status()
return EpdInfo.from_response(resp)
def image_refit(image: PIL.Image, bounder: Dimensions) -> PIL.Image:
bounder_ratio = bounder.width / bounder.height
image_width, image_height = image.size
image_width_by_height = int(image_height * bounder_ratio)
image_height_by_width = int(image_width / bounder_ratio)
if image_width > image_width_by_height:
new_dimensions = Dimensions(image_width_by_height, image_height)
else:
new_dimensions = Dimensions(image_width, image_height_by_width)
return PIL.ImageOps.fit(image, new_dimensions)
def convert_8bit_to_4bit(bytestring):
fourbit = []
for i in range(0, len(bytestring), 2):
first_nibble = int(bytestring[i] / 17)
second_nibble = int(bytestring[i + 1] / 17)
fourbit += [first_nibble << 4 | second_nibble]
fourbit = bytes(fourbit)
return fourbit
def draw(hostname, filename, clear):
inf = info(hostname)
img = PIL.Image.open(filename)
img = image_refit(img, Dimensions(width=inf.width, height=inf.height))
img = img.resize((inf.width, inf.height))
img = img.convert("L")
img_bytes = convert_8bit_to_4bit(img.tobytes())
requests.post(
f"http://{hostname}/draw",
headers={
"width": str(inf.width),
"height": str(inf.height),
"x": "0",
"y": "0",
"clear": "1" if clear else "0",
},
data=img_bytes,
)
def main():
args = parse_args()
if args.command == "clear":
clear(args.hostname)
elif args.command == "info":
print(info(args.hostname))
elif args.command == "draw":
draw(args.hostname, args.file, args.clear)
else:
raise Exception(f"Unknown command {args.command}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View File

@@ -0,0 +1,6 @@
# LilyGo T5 4.7 Inch Example
This is an example project to kick-start you using the LilyGo T5 4.7 inch EPaper display.
It also incorporates a few deepsleep methods to show how to use deepsleep to effectively save battery.
Additionally, it inherits and modifies the battery calculation code provided in examples by the manufacturer.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View File

@@ -0,0 +1,39 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common_env_data]
framework = arduino
board_build.f_cpu = 240000000L
upload_speed = 921600
monitor_speed = 115200
lib_deps =
Wire
https://github.com/vroland/epdiy.git
build_flags =
; device has PRSRAM
; and should be used for ram intensive display work
-DBOARD_HAS_PSRAM
; Setup display format and model via build flags
-DCONFIG_EPD_DISPLAY_TYPE_ED047TC1
-DCONFIG_EPD_BOARD_REVISION_LILYGO_T5_47
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = ${common_env_data.framework}
upload_speed = ${common_env_data.upload_speed}
monitor_speed = ${common_env_data.monitor_speed}
lib_deps = ${common_env_data.lib_deps}
build_flags = ${common_env_data.build_flags}
; uncomment if you want serial debugging with exception decoding
; build_type = debug
; monitor_filters = esp32_exception_decoder

View File

@@ -0,0 +1,254 @@
#include <Arduino.h>
// esp32 sdk imports
#include "esp_heap_caps.h"
#include "esp_log.h"
// epd
#include "epd_highlevel.h"
#include "epdiy.h"
// battery
#include <driver/adc.h>
#include "esp_adc_cal.h"
// deepsleep
#include "esp_sleep.h"
// font
#include "Firasans.h"
#define BATT_PIN 36
#define WAVEFORM EPD_BUILTIN_WAVEFORM
/**
* Upper most button on side of device. Used to setup as wakeup source to start from deepsleep.
* Pinout here https://ae01.alicdn.com/kf/H133488d889bd4dd4942fbc1415e0deb1j.jpg
*/
gpio_num_t FIRST_BTN_PIN = GPIO_NUM_39;
EpdiyHighlevelState hl;
// ambient temperature around device
int temperature = 20;
uint8_t* fb;
enum EpdDrawError err;
// CHOOSE HERE YOU IF YOU WANT PORTRAIT OR LANDSCAPE
// both orientations possible
EpdRotation orientation = EPD_ROT_PORTRAIT;
// EpdRotation orientation = EPD_ROT_LANDSCAPE;
int vref = 1100;
/**
* RTC Memory var to get number of wakeups via wakeup source button
* For demo purposes of rtc data attr
**/
RTC_DATA_ATTR int pressed_wakeup_btn_index;
/**
* That's maximum 30 seconds of runtime in microseconds
*/
int64_t maxTimeRunning = 30000000;
double_t get_battery_percentage() {
// When reading the battery voltage, POWER_EN must be turned on
epd_poweron();
delay(50);
Serial.println(epd_ambient_temperature());
uint16_t v = analogRead(BATT_PIN);
Serial.print("Battery analogRead value is");
Serial.println(v);
double_t battery_voltage = ((double_t)v / 4095.0) * 2.0 * 3.3 * (vref / 1000.0);
// Better formula needed I suppose
// experimental super simple percent estimate no lookup anything just divide by 100
double_t percent_experiment = ((battery_voltage - 3.7) / 0.5) * 100;
// cap out battery at 100%
// on charging it spikes higher
if (percent_experiment > 100) {
percent_experiment = 100;
}
String voltage = "Battery Voltage :" + String(battery_voltage) + "V which is around "
+ String(percent_experiment) + "%";
Serial.println(voltage);
epd_poweroff();
delay(50);
return percent_experiment;
}
void display_center_message(const char* text) {
// first set full screen to white
epd_hl_set_all_white(&hl);
int cursor_x = EPD_WIDTH / 2;
int cursor_y = EPD_HEIGHT / 2;
if (orientation == EPD_ROT_PORTRAIT) {
// height and width switched here because portrait mode
cursor_x = EPD_HEIGHT / 2;
cursor_y = EPD_WIDTH / 2;
}
EpdFontProperties font_props = epd_font_properties_default();
font_props.flags = EPD_DRAW_ALIGN_CENTER;
epd_write_string(&FiraSans_12, text, &cursor_x, &cursor_y, fb, &font_props);
epd_poweron();
err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
delay(500);
epd_poweroff();
delay(1000);
}
void display_full_screen_left_aligned_text(const char* text) {
EpdFontProperties font_props = epd_font_properties_default();
font_props.flags = EPD_DRAW_ALIGN_LEFT;
// first set full screen to white
epd_hl_set_all_white(&hl);
/************* Display the text itself ******************/
// hardcoded to start at upper left corner
// with bit of padding
int cursor_x = 10;
int cursor_y = 30;
epd_write_string(&FiraSans_12, text, &cursor_x, &cursor_y, fb, &font_props);
/********************************************************/
/************ Battery percentage display ****************/
// height and width switched here because portrait mode
int battery_cursor_x = EPD_WIDTH - 30;
int battery_cursor_y = EPD_HEIGHT - 10;
if (orientation == EPD_ROT_PORTRAIT) {
// switched x and y constants in portrait mode
battery_cursor_x = EPD_HEIGHT - 10;
battery_cursor_y = EPD_WIDTH - 30;
}
EpdFontProperties battery_font_props = epd_font_properties_default();
battery_font_props.flags = EPD_DRAW_ALIGN_RIGHT;
String battery_text = String(get_battery_percentage());
battery_text.concat("% Battery");
epd_write_string(
&FiraSans_12,
battery_text.c_str(),
&battery_cursor_x,
&battery_cursor_y,
fb,
&battery_font_props
);
/********************************************************/
epd_poweron();
err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
delay(500);
epd_poweroff();
delay(1000);
}
/**
* Powers off everything into deepsleep so device and display.
*/
void start_deep_sleep_with_wakeup_sources() {
epd_poweroff();
delay(400);
esp_sleep_enable_ext0_wakeup(FIRST_BTN_PIN, 0);
Serial.println("Sending device to deepsleep");
esp_deep_sleep_start();
}
/**
* Function that prints the reason by which ESP32 has been awaken from sleep
*/
void print_wakeup_reason() {
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Wakeup caused by external signal using RTC_IO");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("Wakeup caused by external signal using RTC_CNTL");
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Wakeup caused by timer");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println("Wakeup caused by touchpad");
break;
case ESP_SLEEP_WAKEUP_ULP:
Serial.println("Wakeup caused by ULP program");
break;
default:
Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason);
break;
}
}
/**
* Correct the ADC reference voltage. Was in example of lilygo epd47 repository to calc battery
* percentage
*/
void correct_adc_reference() {
esp_adc_cal_characteristics_t adc_chars;
esp_adc_cal_value_t val_type
= esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
Serial.printf("eFuse Vref:%u mV", adc_chars.vref);
vref = adc_chars.vref;
}
}
void setup() {
Serial.begin(115200);
correct_adc_reference();
// First setup epd to use later
epd_init(&epd_board_lilygo_t5_47, &ED097TC2, EPD_LUT_64K);
epd_set_vcom(1560);
hl = epd_hl_init(WAVEFORM);
epd_set_rotation(orientation);
fb = epd_hl_get_framebuffer(&hl);
epd_clear();
print_wakeup_reason();
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT0) {
Serial.println("Woken up by wakeup source button");
pressed_wakeup_btn_index++;
String message = String("Woken up from deepsleep times: ");
message.concat(String(pressed_wakeup_btn_index));
display_full_screen_left_aligned_text(message.c_str());
} else {
/* Non deepsleep wakeup source button interrupt caused start e.g. reset btn */
Serial.println("Woken up by reset button or power cycle");
const char* message = "Hello! You just turned me on.\nIn 30s I will go to deepsleep";
display_center_message(message);
}
}
void loop() {
/*
* Shutdown device after 30s always to conserve battery.
* Basically almost no battery usage then until next wakeup.
*/
if (esp_timer_get_time() > maxTimeRunning) {
Serial.println("Max runtime of 30s reached. Forcing deepsleep now to save energy");
display_center_message(
"Sleeping now.\nWake me up from deepsleep again\nwith the first button on my side"
);
delay(1500);
start_deep_sleep_with_wakeup_sources();
}
}

View File

@@ -0,0 +1,16 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.18)
# dependencies
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(screen_diag)
set(IDF_VER_SANE "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}")
if (IDF_VER_SANE VERSION_LESS "5.0.0")
message(FATAL_ERROR "screen_diag requires at least ESP-IDF v5.0.0")
endif()

View File

@@ -0,0 +1,189 @@
# Screen Diagnostics
This example app implements a simple shell to allow you to tinker with the display. You have access to all drawing functions from the _epd driver_, as well as system information, etc.
There are also pre-programmed algorithms (e.g. `render_stairs`, `render_grid`) which can be used to find pixel errors or display incompatibilities.
The `screen_diag` examples requires ESP-IDF v5.x or newer.
## Setup
Don't forget to set your display type in `epd_init` in `epd.c`!
First you need to flash the firmware:
```sh
idf.py flash
```
After that, you can enter the shell environment with:
```sh
idf.py monitor
```
Please note the [known issues](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/tools/idf-monitor.html#known-issues-with-idf-monitor) of the IDF Monitor for limitations.
## Usage
You can get a full list (see below) of all available commands with: `help`.
```
system_restart
Restarts the system.
free_heap_size
Returns the free heap size.
dump_heaps_info [<caps>]
Dumps heap information of all heaps matching the capability.
<caps> Heap caps to print. Default: MALLOC_CAP_DEFAULT
dump_tasks
Dumps all tasks with their names, current state and stack usage.
chip_info
Dumps chip information.
firmware_info
Dumps information about the ESP-IDF and the firmware.
get_time
Returns the time in microseconds since boot.
get_mac [<interface>]
Returns the MAC address for the given interface or the pre-programmed base
address.
<interface> Either "wifi_station", "wifi_ap", "bluetooth" or "ethernet"
get_rotation
Get current screen rotation.
set_rotation <rotation> [--inverted]
Changes screen rotation.
<rotation> screen rotation: "horizontal" or "portrait"
--inverted
get_width
Print screen width.
get_height
Print screen height.
get_pixel <posx> <posy>
Get pixel color in front buffer.
<posx> x position
<posy> y position
set_pixel <posx> <posy> [<color>]
Set pixel color in front buffer.
<posx> x position
<posy> y position
<color> color. default value: 0 (0x00)
clear_screen
Clear the entire screen and reset the front buffer to white.
full_clear_screen
Same as clear_screen, but also tries to get rid of any artifacts by cycling
through colors on the screen.
get_temp
Returns the ambient temperature.
power_on
Turns on the power of the display.
power_off
Turns off the power of the display.
draw_hline <x> <y> <len> [<color>]
Draw horizontal line.
<x> start x position
<y> start y position
<len> length of the line
<color> default value: 0x00
draw_vline <x> <y> <len> [<color>]
Draw vertical line.
<x> start x position
<y> start y position
<len> length of the line
<color> default value: 0x00
draw_line <start_x> <start_y> <end_x> <end_y> [<color>]
Draw line between two points.
<start_x> start x position
<start_y> start y position
<end_x> end x position
<end_y> end y position
<color> default value: 0x00
draw_rect <x> <y> <width> <height> [<color>]
Draw a rectangle.
<x> top left x position
<y> top left y position
<width> square width
<height> square height
<color> default value: 0x00
fill_rect <x> <y> <width> <height> [<color>]
Draw a filled rectangle.
<x> top left x position
<y> top left y position
<width> square width
<height> square height
<color> default value: 0x00
draw_circle <center_x> <center_y> <radius> [<color>]
Draw a circle.
<center_x> center x position
<center_y> center y position
<radius> circle radius
<color> default value: 0x00
fill_circle <center_x> <center_y> <radius> [<color>]
Draw a filled circle.
<center_x> center x position
<center_y> center y position
<radius> circle radius
<color> default value: 0x00
draw_triangle <x0> <y0> <x1> <y1> <x0> <y0> [<color>]
Draw a triangle from three different points.
<x0> first edge x position
<y0> first edge y position
<x1> second edge x position
<y1> second edge y position
<x0> third edge x position
<y0> third edge y position
<color> default value: 0x00
fill_triangle <x0> <y0> <x1> <y1> <x0> <y0> [<color>]
Draw a filled triangle from three different points.
<x0> first edge x position
<y0> first edge y position
<x1> second edge x position
<y1> second edge y position
<x0> third edge x position
<y0> third edge y position
<color> default value: 0x00
write_text [-s] <x> <y> [<color>] <msg>
Write text message to the screen using the sans-serif font by default.
<x> x position
<y> y position
<color> default value: 0x00
-s, --serif Use serif font rather than sans-serif.
<msg> Text to be printed.
render_stairs [<slope>] [<width>] [<color>]
Render multiple diagonal lines across the screen.
<slope> angle by which each diagonal line is drawn. default value: 3
<width> thickness of each diagonal line. default value: 100
<color> default value: 0x00
render_grid [<gutter>] [<color>]
Renders a grid across the whole screen. At a certain gutter size, cell info
will be printed as well.
<gutter> default value: 75
<color> default value: 0x00
help
Print the list of registered commands
```

View File

@@ -0,0 +1,4 @@
idf_component_register(SRCS "screen_diag.c" "epd.c" "commands.c" "commands/system.c"
"commands/screen.c" "commands/graphics.c" "commands/tests.c"
INCLUDE_DIRS "." "res/fonts"
REQUIRES epdiy console nvs_flash fatfs esp_app_format)

View File

@@ -0,0 +1,17 @@
#include "commands.h"
bool validate_color(uint8_t* inout_color, struct arg_int* arg) {
int user_color = arg->count != 0 ? arg->ival[0] : *inout_color;
if (user_color < 0 || user_color > 0xFF) {
printf(
"Invalid color %d (0x%02x): Must be in range 0x00 to 0xFF.\r\n",
user_color,
(uint8_t)user_color
);
return false;
}
*inout_color = (uint8_t)user_color;
return true;
}

View File

@@ -0,0 +1,76 @@
#pragma once
/* Helper functions and macros for common use cases,
* when registering or implementing commands.
*/
#include <stdbool.h>
#include <stdint.h>
#include <argtable3/argtable3.h>
#include <esp_console.h>
#ifndef ARRAY_SIZE
/**
* Returns size of a (static) C array.
*/
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#endif
/**
* Checks whether the given color argument is a valid color.
* I.e. when it's color value is possible to represent by an uint8_t (0x00 - 0xFF).
* If no color argument was provided by the user, returns the default color of 0x00.
*
* @param inout_color initial value will be used as default color,
* when the user did not specify a color.
* when successful, will be set to the given color.
* @param arg user-specified color argument
* @return whether the given color is valid
*/
bool validate_color(uint8_t* inout_color, struct arg_int* arg);
/**
* Determines the number of arguments stored in a struct (container).
* That is usually the number of arguments for a given command and
* can be used when initializing the arg_end parameter.
*
* This macro assumes, that
* 1. each argument inside the struct is referenced by a pointer,
* 2. each struct ends with an arg_end*,
* 3. there are no other members in the struct, besides different argument types.
*/
#define NARGS(container) ((sizeof(container) / sizeof(struct arg_end*)) - 1)
/**
* Handles argument validation for the command.
* Assumes that `argc` and `argv` variables are visible, thus should
* only be used inside the command implementation function.
*
* @param args_struct name of the (static) argument struct.
*/
#define HANDLE_ARGUMENTS(args_struct) \
{ \
int nerrors = arg_parse(argc, argv, (void**)&args_struct); \
if (nerrors > 0) { \
arg_print_errors(stdout, args_struct.end, argv[0]); \
return 1; \
} \
}
/**
* Get optional argument value if provided by the user. Otherwise use the default value.
*
* @param arg pointer to argument struct (e.g. struct arg_int*)
* @param accessor accessor used to retrieve the first value (e.g. ival for struct arg_int)
* @param default_value
*/
#define GET_ARG(arg, accessor, default_value) \
(arg)->count == 1 ? (arg)->accessor[0] : (default_value)
/**
* Alias for GET_ARG, specialized for int arguments.
*
* @param arg pointer to an struct arg_int
* @param default_value
*/
#define GET_INT_ARG(arg, default_value) GET_ARG(arg, ival, default_value)

View File

@@ -0,0 +1,375 @@
#include "graphics.h"
#include <stdio.h>
#include <string.h>
#include <argtable3/argtable3.h>
#include <esp_console.h>
#include "../commands.h"
#include "../epd.h"
#include "fonts.h"
static struct {
struct arg_int *x, *y;
struct arg_int* len;
struct arg_int* color;
struct arg_end* end;
} draw_hvline_args;
static struct {
struct arg_int *from_x, *from_y;
struct arg_int *to_x, *to_y;
struct arg_int* color;
struct arg_end* end;
} draw_line_args;
static struct {
struct arg_int *x, *y;
struct arg_int *width, *height;
struct arg_int* color;
struct arg_end* end;
} draw_rect_args;
static struct {
struct arg_int *x, *y;
struct arg_int* radius;
struct arg_int* color;
struct arg_end* end;
} draw_circle_args;
static struct {
struct arg_int *x0, *y0;
struct arg_int *x1, *y1;
struct arg_int *x2, *y2;
struct arg_int* color;
struct arg_end* end;
} draw_triangle_args;
static struct {
struct arg_int *x, *y;
struct arg_int* color;
struct arg_lit* serif;
struct arg_str* msg;
struct arg_end* end;
} write_text_args;
static int draw_hline(int argc, char* argv[]);
static int draw_vline(int argc, char* argv[]);
static int draw_line(int argc, char* argv[]);
static int draw_rect(int argc, char* argv[]);
static int fill_rect(int argc, char* argv[]);
static int draw_circle(int argc, char* argv[]);
static int fill_circle(int argc, char* argv[]);
static int draw_triangle(int argc, char* argv[]);
static int fill_triangle(int argc, char* argv[]);
static int write_text(int argc, char* argv[]);
void register_graphics_commands(void) {
// setup args
draw_hvline_args.x = arg_intn(NULL, NULL, "<x>", 1, 1, "start x position");
draw_hvline_args.y = arg_intn(NULL, NULL, "<y>", 1, 1, "start y position");
draw_hvline_args.len = arg_intn(NULL, NULL, "<len>", 1, 1, "length of the line");
draw_hvline_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
draw_hvline_args.end = arg_end(NARGS(draw_hvline_args));
draw_line_args.from_x = arg_intn(NULL, NULL, "<start_x>", 1, 1, "start x position");
draw_line_args.from_y = arg_intn(NULL, NULL, "<start_y>", 1, 1, "start y position");
draw_line_args.to_x = arg_intn(NULL, NULL, "<end_x>", 1, 1, "end x position");
draw_line_args.to_y = arg_intn(NULL, NULL, "<end_y>", 1, 1, "end y position");
draw_line_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
draw_line_args.end = arg_end(NARGS(draw_line_args));
draw_rect_args.x = arg_intn(NULL, NULL, "<x>", 1, 1, "top left x position");
draw_rect_args.y = arg_intn(NULL, NULL, "<y>", 1, 1, "top left y position");
draw_rect_args.width = arg_intn(NULL, NULL, "<width>", 1, 1, "square width");
draw_rect_args.height = arg_intn(NULL, NULL, "<height>", 1, 1, "square height");
draw_rect_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
draw_rect_args.end = arg_end(NARGS(draw_rect_args));
draw_circle_args.x = arg_intn(NULL, NULL, "<center_x>", 1, 1, "center x position");
draw_circle_args.y = arg_intn(NULL, NULL, "<center_y>", 1, 1, "center y position");
draw_circle_args.radius = arg_intn(NULL, NULL, "<radius>", 1, 1, "circle radius");
draw_circle_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
draw_circle_args.end = arg_end(NARGS(draw_circle_args));
draw_triangle_args.x0 = arg_intn(NULL, NULL, "<x0>", 1, 1, "first edge x position");
draw_triangle_args.y0 = arg_intn(NULL, NULL, "<y0>", 1, 1, "first edge y position");
draw_triangle_args.x1 = arg_intn(NULL, NULL, "<x1>", 1, 1, "second edge x position");
draw_triangle_args.y1 = arg_intn(NULL, NULL, "<y1>", 1, 1, "second edge y position");
draw_triangle_args.x2 = arg_intn(NULL, NULL, "<x0>", 1, 1, "third edge x position");
draw_triangle_args.y2 = arg_intn(NULL, NULL, "<y0>", 1, 1, "third edge y position");
draw_triangle_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
draw_triangle_args.end = arg_end(NARGS(draw_triangle_args));
write_text_args.x = arg_intn(NULL, NULL, "<x>", 1, 1, "x position");
write_text_args.y = arg_intn(NULL, NULL, "<y>", 1, 1, "y position");
write_text_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
write_text_args.serif = arg_litn("s", "serif", 0, 1, "Use serif font rather than sans-serif.");
write_text_args.msg = arg_strn(NULL, NULL, "<msg>", 1, 1, "Text to be printed.");
write_text_args.end = arg_end(NARGS(write_text_args));
// register commands
const esp_console_cmd_t commands[]
= { { .command = "draw_hline",
.help = "Draw horizontal line.",
.hint = NULL,
.func = &draw_hline,
.argtable = &draw_hvline_args },
{ .command = "draw_vline",
.help = "Draw vertical line.",
.hint = NULL,
.func = &draw_vline,
.argtable = &draw_hvline_args },
{ .command = "draw_line",
.help = "Draw line between two points.",
.hint = NULL,
.func = &draw_line,
.argtable = &draw_line_args },
{ .command = "draw_rect",
.help = "Draw a rectangle.",
.hint = NULL,
.func = &draw_rect,
.argtable = &draw_rect_args },
{ .command = "fill_rect",
.help = "Draw a filled rectangle.",
.hint = NULL,
.func = &fill_rect,
.argtable = &draw_rect_args },
{ .command = "draw_circle",
.help = "Draw a circle.",
.hint = NULL,
.func = &draw_circle,
.argtable = &draw_circle_args },
{ .command = "fill_circle",
.help = "Draw a filled circle.",
.hint = NULL,
.func = &fill_circle,
.argtable = &draw_circle_args },
{ .command = "draw_triangle",
.help = "Draw a triangle from three different points.",
.hint = NULL,
.func = &draw_triangle,
.argtable = &draw_triangle_args },
{ .command = "fill_triangle",
.help = "Draw a filled triangle from three different points.",
.hint = NULL,
.func = &fill_triangle,
.argtable = &draw_triangle_args },
{ .command = "write_text",
.help = "Write text message to the screen using the sans-serif font by default.",
.hint = NULL,
.func = &write_text,
.argtable = &write_text_args } };
for (size_t i = 0; i < ARRAY_SIZE(commands); ++i)
ESP_ERROR_CHECK(esp_console_cmd_register(&commands[i]));
}
static int draw_hline(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_hvline_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_hvline_args.color))
return 1;
epd_draw_hline(
draw_hvline_args.x->ival[0],
draw_hvline_args.y->ival[0],
draw_hvline_args.len->ival[0],
color,
g_framebuffer
);
update_screen();
return 0;
}
static int draw_vline(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_hvline_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_hvline_args.color))
return 1;
epd_draw_vline(
draw_hvline_args.x->ival[0],
draw_hvline_args.y->ival[0],
draw_hvline_args.len->ival[0],
color,
g_framebuffer
);
update_screen();
return 0;
}
static int draw_line(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_line_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_line_args.color))
return 1;
epd_draw_line(
draw_line_args.from_x->ival[0],
draw_line_args.from_y->ival[0],
draw_line_args.to_x->ival[0],
draw_line_args.to_y->ival[0],
color,
g_framebuffer
);
update_screen();
return 0;
}
static int draw_rect(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_rect_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_rect_args.color))
return 1;
EpdRect rect = { .x = draw_rect_args.x->ival[0],
.y = draw_rect_args.y->ival[0],
.width = draw_rect_args.width->ival[0],
.height = draw_rect_args.height->ival[0] };
epd_draw_rect(rect, color, g_framebuffer);
update_screen();
return 0;
}
static int fill_rect(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_rect_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_rect_args.color))
return 1;
EpdRect rect = { .x = draw_rect_args.x->ival[0],
.y = draw_rect_args.y->ival[0],
.width = draw_rect_args.width->ival[0],
.height = draw_rect_args.height->ival[0] };
epd_fill_rect(rect, color, g_framebuffer);
update_screen();
return 0;
}
static int draw_circle(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_circle_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_circle_args.color))
return 1;
epd_draw_circle(
draw_circle_args.x->ival[0],
draw_circle_args.y->ival[0],
draw_circle_args.radius->ival[0],
color,
g_framebuffer
);
update_screen();
return 0;
}
static int fill_circle(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_circle_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_circle_args.color))
return 1;
epd_fill_circle(
draw_circle_args.x->ival[0],
draw_circle_args.y->ival[0],
draw_circle_args.radius->ival[0],
color,
g_framebuffer
);
update_screen();
return 0;
}
static int draw_triangle(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_triangle_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_triangle_args.color))
return 1;
epd_draw_triangle(
draw_triangle_args.x0->ival[0],
draw_triangle_args.y0->ival[0],
draw_triangle_args.x1->ival[0],
draw_triangle_args.y1->ival[0],
draw_triangle_args.x2->ival[0],
draw_triangle_args.y2->ival[0],
color,
g_framebuffer
);
update_screen();
return 0;
}
static int fill_triangle(int argc, char* argv[]) {
HANDLE_ARGUMENTS(draw_triangle_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_triangle_args.color))
return 1;
epd_fill_triangle(
draw_triangle_args.x0->ival[0],
draw_triangle_args.y0->ival[0],
draw_triangle_args.x1->ival[0],
draw_triangle_args.y1->ival[0],
draw_triangle_args.x2->ival[0],
draw_triangle_args.y2->ival[0],
color,
g_framebuffer
);
update_screen();
return 0;
}
static int write_text(int argc, char* argv[]) {
HANDLE_ARGUMENTS(write_text_args)
uint8_t color = 0x00;
if (!validate_color(&color, draw_triangle_args.color))
return 1;
const EpdFont* font = &Alexandria;
if (write_text_args.serif->count)
font = &Amiri;
EpdFontProperties props = { .bg_color = 0x00, .fg_color = color };
int pos_x = write_text_args.x->ival[0];
int pos_y = write_text_args.y->ival[0];
epd_write_string(font, write_text_args.msg->sval[0], &pos_x, &pos_y, g_framebuffer, &props);
update_screen();
return 0;
}

View File

@@ -0,0 +1,3 @@
#pragma once
void register_graphics_commands(void);

View File

@@ -0,0 +1,286 @@
#include "screen.h"
#include <stdio.h>
#include <string.h>
#include <argtable3/argtable3.h>
#include <esp_console.h>
#include "../commands.h"
#include "../epd.h"
static struct {
struct arg_str* rotation;
struct arg_lit* inverted;
struct arg_end* end;
} set_rotation_args;
static struct {
struct arg_int *posx, *posy;
struct arg_end* end;
} get_pixel_args;
static struct {
struct arg_int *posx, *posy, *color;
struct arg_end* end;
} set_pixel_args;
static int get_rotation(int argc, char* argv[]);
static int set_rotation(int argc, char* argv[]);
static int get_width(int argc, char* argv[]);
static int get_height(int argc, char* argv[]);
static int get_pixel(int argc, char* argv[]);
static int set_pixel(int argc, char* argv[]);
static int clear_screen_cmd(int argc, char* argv[]);
static int full_clear_screen_cmd(int argc, char* argv[]);
static int get_temp(int argc, char* argv[]);
static int power_on(int argc, char* argv[]);
static int power_off(int argc, char* argv[]);
void register_screen_commands(void) {
// setup arguments
set_rotation_args.rotation = arg_strn(
NULL, NULL, "<rotation>", 1, 1, "screen rotation: \"horizontal\" or \"portrait\""
);
set_rotation_args.inverted = arg_litn(NULL, "inverted", 0, 1, "");
set_rotation_args.end = arg_end(NARGS(set_rotation_args));
get_pixel_args.posx = arg_intn(NULL, NULL, "<posx>", 1, 1, "x position");
get_pixel_args.posy = arg_intn(NULL, NULL, "<posy>", 1, 1, "y position");
get_pixel_args.end = arg_end(NARGS(get_pixel_args));
set_pixel_args.posx = arg_intn(NULL, NULL, "<posx>", 1, 1, "x position");
set_pixel_args.posy = arg_intn(NULL, NULL, "<posy>", 1, 1, "y position");
set_pixel_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "color. default value: 0 (0x00)");
set_pixel_args.end = arg_end(NARGS(set_pixel_args));
const esp_console_cmd_t commands[] = {
{ .command = "get_rotation",
.help = "Get current screen rotation.",
.hint = NULL,
.func = &get_rotation },
{ .command = "set_rotation",
.help = "Changes screen rotation.",
.hint = NULL,
.func = &set_rotation,
.argtable = &set_rotation_args },
{ .command = "get_width", .help = "Print screen width.", .hint = NULL, .func = &get_width },
{ .command = "get_height",
.help = "Print screen height.",
.hint = NULL,
.func = &get_height },
{ .command = "get_pixel",
.help = "Get pixel color in front buffer.",
.hint = NULL,
.func = &get_pixel,
.argtable = &get_pixel_args },
{ .command = "set_pixel",
.help = "Set pixel color in front buffer.",
.hint = NULL,
.func = &set_pixel,
.argtable = &set_pixel_args },
{ .command = "clear_screen",
.help = "Clear the entire screen and reset the front buffer to white.",
.hint = NULL,
.func = &clear_screen_cmd },
{ .command = "full_clear_screen",
.help = "Same as clear_screen, but also tries to get rid of any artifacts by cycling "
"through colors on the screen.",
.hint = NULL,
.func = &full_clear_screen_cmd },
{ .command = "get_temp",
.help = "Returns the ambient temperature.",
.hint = NULL,
.func = &get_temp },
{ .command = "power_on",
.help = "Turns on the power of the display.",
.hint = NULL,
.func = &power_on },
{ .command = "power_off",
.help = "Turns off the power of the display.",
.hint = NULL,
.func = &power_off }
};
for (size_t i = 0; i < ARRAY_SIZE(commands); ++i)
ESP_ERROR_CHECK(esp_console_cmd_register(&commands[i]));
}
static int get_rotation(int argc, char* argv[]) {
enum EpdRotation rot = epd_get_rotation();
if (rot == EPD_ROT_INVERTED_LANDSCAPE || rot == EPD_ROT_INVERTED_PORTRAIT)
printf("inverted ");
if (rot == EPD_ROT_LANDSCAPE)
printf("landscape\r\n");
else if (rot == EPD_ROT_PORTRAIT)
printf("portrait\r\n");
return 0;
}
static int set_rotation(int argc, char* argv[]) {
HANDLE_ARGUMENTS(set_rotation_args)
const char* rot_str = set_rotation_args.rotation->sval[0];
const bool invert = set_rotation_args.inverted->count == 1;
enum EpdRotation rot = EPD_ROT_LANDSCAPE;
if (!strcmp(rot_str, "landscape")) {
if (invert)
rot = EPD_ROT_INVERTED_LANDSCAPE;
else
rot = EPD_ROT_LANDSCAPE;
} else if (!strcmp(rot_str, "portrait")) {
if (invert)
rot = EPD_ROT_INVERTED_PORTRAIT;
else
rot = EPD_ROT_PORTRAIT;
}
epd_set_rotation(rot);
return 0;
}
static int get_width(int argc, char* argv[]) {
printf("%d\r\n", epd_rotated_display_width());
return 0;
}
static int get_height(int argc, char* argv[]) {
printf("%d\r\n", epd_rotated_display_height());
return 0;
}
static inline void swap(int* lhs, int* rhs) {
const int tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
struct coords {
int x, y;
};
/* Basically the _rotate() function from epd_driver.c */
static struct coords map_to_screen(int x, int y) {
switch (epd_get_rotation()) {
case EPD_ROT_LANDSCAPE:
break;
case EPD_ROT_PORTRAIT:
swap(&x, &y);
x = epd_width() - x - 1;
break;
case EPD_ROT_INVERTED_LANDSCAPE:
x = epd_width() - x - 1;
y = epd_height() - y - 1;
break;
case EPD_ROT_INVERTED_PORTRAIT:
swap(&x, &y);
y = epd_height() - y - 1;
break;
}
return (struct coords){ x, y };
}
/* Read the pixel data from the front buffer, because there is no function provided by the driver.
* Most importantly, we need to adjust the rotation of the incoming coordinates.
*/
static int get_pixel_color(int x, int y) {
const struct coords adjusted = map_to_screen(x, y);
if (adjusted.x < 0 || adjusted.x >= epd_width() || adjusted.y < 0
|| adjusted.y >= epd_height()) {
printf(
"Invalid coordinates (%d,%d): Must be withing the screen size (%d,%d).\r\n",
adjusted.x,
adjusted.y,
epd_width(),
epd_height()
);
return -1;
}
uint8_t pixel = g_framebuffer[adjusted.y * epd_width() / 2 + adjusted.x / 2];
uint8_t color = (adjusted.x % 2) ? (pixel & 0xF0) : (pixel & 0x0F);
// repeat color pattern
color |= (adjusted.x % 2) ? (color >> 4) : (color << 4);
return color;
}
static int get_pixel(int argc, char* argv[]) {
HANDLE_ARGUMENTS(get_pixel_args)
const int pos_x = get_pixel_args.posx->ival[0];
const int pos_y = get_pixel_args.posy->ival[0];
const int color = get_pixel_color(pos_x, pos_y);
if (color == -1) {
printf(
"Invalid coordinates (%d,%d): Must be withing the screen size (%d,%d).\r\n",
pos_x,
pos_y,
epd_rotated_display_width(),
epd_rotated_display_height()
);
return 1;
}
printf("Pixel (%d,%d) has color %d (0x%02x)\r\n", pos_x, pos_y, color, (uint8_t)color);
return 0;
}
static int set_pixel(int argc, char* argv[]) {
HANDLE_ARGUMENTS(set_pixel_args)
const int pos_x = set_pixel_args.posx->ival[0];
const int pos_y = set_pixel_args.posy->ival[0];
uint8_t color = 0x00;
if (!validate_color(&color, set_pixel_args.color))
return 1;
epd_draw_pixel(pos_x, pos_y, color, g_framebuffer);
printf("Set pixel (%d,%d) to color %d (0x%02x)\r\n", pos_x, pos_y, color, color);
update_screen();
return 0;
}
static int clear_screen_cmd(int argc, char* argv[]) {
clear_screen();
printf("Cleared screen.\r\n");
return 0;
}
static int full_clear_screen_cmd(int argc, char* argv[]) {
full_clear_screen();
printf("Cleared screen.\r\n");
return 0;
}
static int get_temp(int argc, char* argv[]) {
epd_poweron();
const float temp = epd_ambient_temperature();
epd_poweroff();
printf("%.2f\r\n", temp);
return 0;
}
static int power_on(int argc, char* argv[]) {
epd_poweron();
printf("Power turned on.\r\n");
return 0;
}
static int power_off(int argc, char* argv[]) {
epd_poweroff();
printf("Power turned off.\r\n");
return 0;
}

View File

@@ -0,0 +1,3 @@
#pragma once
void register_screen_commands(void);

View File

@@ -0,0 +1,236 @@
#include "system.h"
#include <stdio.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <argtable3/argtable3.h>
#include <esp_app_desc.h>
#include <esp_chip_info.h>
#include <esp_console.h>
#include <esp_heap_caps.h>
#include <esp_mac.h>
#include <esp_system.h>
#include <esp_timer.h>
#include "../commands.h"
static struct {
struct arg_int* caps;
struct arg_end* end;
} dump_heaps_args;
static struct {
struct arg_str* interface;
struct arg_end* end;
} get_mac_args;
static int system_restart(int argc, char* argv[]);
static int system_get_free_heap_size(int argc, char* argv[]);
static int system_dump_heaps_info(int argc, char* argv[]);
static int system_dump_chip_info(int argc, char* argv[]);
static int system_dump_firmware_info(int argc, char* argv[]);
static int system_get_time(int argc, char* argv[]);
static int system_get_mac(int argc, char* argv[]);
void register_system_commands(void) {
dump_heaps_args.caps
= arg_intn(NULL, NULL, "<caps>", 0, 1, "Heap caps to print. Default: MALLOC_CAP_DEFAULT");
dump_heaps_args.end = arg_end(NARGS(dump_heaps_args));
get_mac_args.interface = arg_strn(
NULL,
NULL,
"<interface>",
0,
1,
"Either \"wifi_station\", \"wifi_ap\", \"bluetooth\" or \"ethernet\""
);
get_mac_args.end = arg_end(NARGS(get_mac_args));
// register commands
const esp_console_cmd_t commands[] = {
{ .command = "system_restart",
.help = "Restarts the system.",
.hint = NULL,
.func = &system_restart },
{ .command = "free_heap_size",
.help = "Returns the free heap size.",
.hint = NULL,
.func = &system_get_free_heap_size },
{ .command = "dump_heaps_info",
.help = "Dumps heap information of all heaps matching the capability.",
.hint = NULL,
.func = &system_dump_heaps_info,
.argtable = &dump_heaps_args },
{ .command = "chip_info",
.help = "Dumps chip information.",
.hint = NULL,
.func = &system_dump_chip_info },
{ .command = "firmware_info",
.help = "Dumps information about the ESP-IDF and the firmware.",
.hint = NULL,
.func = &system_dump_firmware_info },
{ .command = "get_time",
.help = "Returns the time in microseconds since boot.",
.hint = NULL,
.func = &system_get_time },
{ .command = "get_mac",
.help
= "Returns the MAC address for the given interface or the pre-programmed base address.",
.hint = NULL,
.func = &system_get_mac,
.argtable = &get_mac_args }
};
for (size_t i = 0; i < ARRAY_SIZE(commands); ++i)
ESP_ERROR_CHECK(esp_console_cmd_register(&commands[i]));
}
static int system_restart(int argc, char* argv[]) {
esp_restart();
// unreachable
}
static int system_get_free_heap_size(int argc, char* argv[]) {
printf("Free heap size: %lu bytes.\r\n", esp_get_free_heap_size());
return 0;
}
static int system_dump_heaps_info(int argc, char* argv[]) {
HANDLE_ARGUMENTS(dump_heaps_args);
uint32_t caps
= dump_heaps_args.caps->count == 1 ? dump_heaps_args.caps->ival[0] : MALLOC_CAP_DEFAULT;
heap_caps_print_heap_info(caps);
return 0;
}
static const char* chip_model_str(esp_chip_model_t model) {
switch (model) {
case CHIP_ESP32:
return "ESP32";
case CHIP_ESP32S2:
return "ESP32S2";
case CHIP_ESP32S3:
return "ESP32S3";
case CHIP_ESP32C3:
return "ESP32C3";
case CHIP_ESP32H2:
return "ESP32H2";
case CHIP_ESP32C2:
return "ESP32C2";
default:
return "Unknown";
}
}
static int system_dump_chip_info(int argc, char* argv[]) {
esp_chip_info_t info;
esp_chip_info(&info);
printf(
"model: %s\r\n"
"features (0x%lX):\r\n"
"\tEMB_FLASH: %d\r\n"
"\tWIFI_BGN: %d\r\n"
"\tBLE:\t%d\r\n"
"\tBT:\t%d\r\n"
"\tIEEE802154: %d\r\n"
"\tEMB_PSRAM: %d\r\n"
"revision: %d.%d\r\n"
"cores: %d\r\n",
chip_model_str(info.model),
info.features,
(info.features & CHIP_FEATURE_EMB_PSRAM) != 0,
(info.features & CHIP_FEATURE_WIFI_BGN) != 0,
(info.features & CHIP_FEATURE_BLE) != 0,
(info.features & CHIP_FEATURE_BT) != 0,
(info.features & CHIP_FEATURE_IEEE802154) != 0,
(info.features & CHIP_FEATURE_EMB_PSRAM) != 0,
(info.revision / 100),
(info.revision % 100),
info.cores
);
return 0;
}
static int system_dump_firmware_info(int argc, char* argv[]) {
char hash[64];
esp_app_get_elf_sha256(hash, 64);
const esp_app_desc_t* app = esp_app_get_description();
printf(
"ESP-IDF version: %s\r\n"
"Firmware info:\r\n"
"\tmagic: 0x%lX\r\n"
"\tsecure_version: 0x%lX\r\n"
"\tversion: %s\r\n"
"\tproject_name: %s\r\n"
"\tcompile time/date: %s %s\r\n"
"\telf sha256: %s\r\n",
esp_get_idf_version(),
app->magic_word,
app->secure_version,
app->version,
app->project_name,
app->time,
app->date,
hash
);
return 0;
}
static int system_get_time(int argc, char* argv[]) {
printf("Time in microseconds since boot: %llu\r\n", esp_timer_get_time());
return 0;
}
static void print_mac(uint8_t mac[8]) {
// only print MAC-48 types
printf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
static int system_get_mac(int argc, char* argv[]) {
HANDLE_ARGUMENTS(get_mac_args);
uint8_t mac[8];
if (get_mac_args.interface->count == 0) {
esp_base_mac_addr_get(mac);
print_mac(mac);
return 0;
}
esp_mac_type_t type;
const char* arg = get_mac_args.interface->sval[0];
if (!strcmp(arg, "wifi_station"))
type = ESP_MAC_WIFI_STA;
else if (!strcmp(arg, "wifi_ap"))
type = ESP_MAC_WIFI_SOFTAP;
else if (!strcmp(arg, "bluetooth"))
type = ESP_MAC_BT;
else if (!strcmp(arg, "ethernet"))
type = ESP_MAC_ETH;
else {
printf(
"Invalid interface: \"%s\". Must be one of \"wifi_station\", \"wifi_ap\", "
"\"bluetooth\" or \"ethernet\".\r\n",
arg
);
return 1;
}
esp_read_mac(mac, type);
print_mac(mac);
return 0;
}

View File

@@ -0,0 +1,3 @@
#pragma once
void register_system_commands(void);

View File

@@ -0,0 +1,138 @@
#include "tests.h"
#include <stdio.h>
#include <argtable3/argtable3.h>
#include <esp_console.h>
#include "../commands.h"
#include "../epd.h"
#include "fonts.h"
static struct {
struct arg_int* slope;
struct arg_int* width;
struct arg_int* color;
struct arg_end* end;
} render_stairs_args;
static struct {
struct arg_int* gutter;
struct arg_int* color;
struct arg_end* end;
} render_grid_args;
static int render_stairs_cmd(int argc, char* argv[]);
static int render_grid_cmd(int argc, char* argv[]);
void register_tests_commands(void) {
// setup args
render_stairs_args.slope = arg_intn(
NULL, NULL, "<slope>", 0, 1, "angle by which each diagonal line is drawn. default value: 3"
);
render_stairs_args.width = arg_intn(
NULL, NULL, "<width>", 0, 1, "thickness of each diagonal line. default value: 100"
);
render_stairs_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
render_stairs_args.end = arg_end(NARGS(render_stairs_args));
render_grid_args.gutter
= arg_intn(NULL, NULL, "<gutter>", 0, 1, "default value: 75"); // gcd(1200, 825) = 75
render_grid_args.color = arg_intn(NULL, NULL, "<color>", 0, 1, "default value: 0x00");
render_grid_args.end = arg_end(NARGS(render_grid_args));
// register commands
const esp_console_cmd_t commands[] = {
{ .command = "render_stairs",
.help = "Render multiple diagonal lines across the screen.",
.hint = NULL,
.func = &render_stairs_cmd,
.argtable = &render_stairs_args },
{ .command = "render_grid",
.help
= "Renders a grid across the whole screen. At a certain gutter size, cell info will "
"be printed as well.",
.hint = NULL,
.func = &render_grid_cmd,
.argtable = &render_grid_args },
};
for (size_t i = 0; i < ARRAY_SIZE(commands); ++i)
ESP_ERROR_CHECK(esp_console_cmd_register(&commands[i]));
}
static void render_stairs(int slope, int width, uint8_t color) {
for (int y = 0, x = 0; y < epd_rotated_display_height(); y++) {
epd_draw_hline(x, y, width, color, g_framebuffer);
x += slope;
if (x + width > epd_rotated_display_width())
x = 0;
}
}
static int render_stairs_cmd(int argc, char* argv[]) {
HANDLE_ARGUMENTS(render_stairs_args)
uint8_t color = 0x00;
if (!validate_color(&color, render_stairs_args.color))
return 1;
const int slope = GET_INT_ARG(render_stairs_args.slope, 3);
const int width = GET_INT_ARG(render_stairs_args.width, 100);
if (slope < 1 || slope > width) {
printf("Slope %d is too steep: Must be between 1 and width (%d)\r\n", slope, width);
return 1;
}
render_stairs(slope, width, color);
update_screen();
return 0;
}
void render_grid(int gutter, uint8_t color) {
const int width = epd_rotated_display_width();
const int height = epd_rotated_display_height();
// draw lines
for (int row = gutter; row < height; row += gutter)
epd_draw_hline(0, row, width, color, g_framebuffer);
for (int col = gutter; col < width; col += gutter)
epd_draw_vline(col, 0, height, color, g_framebuffer);
// skip printing info if it wouldn't fit
if (gutter < Alexandria.advance_y * 2)
return;
// prepare info
static char label[32];
int col = 0, row;
for (int y = 0; y < height; y += gutter, ++col) {
row = 0;
for (int x = 0; x < width; x += gutter, ++row) {
// print info
snprintf(label, sizeof(label), "(%d,%d)", row, col);
int rx = y + Alexandria.advance_y;
int cx = x + 4; // margin
epd_write_default(&Alexandria, label, &cx, &rx, g_framebuffer);
}
}
}
static int render_grid_cmd(int argc, char* argv[]) {
HANDLE_ARGUMENTS(render_grid_args)
uint8_t color = 0x00;
if (!validate_color(&color, render_grid_args.color))
return 1;
const int gutter = GET_INT_ARG(render_grid_args.gutter, 75);
render_grid(gutter, color);
update_screen();
return 0;
}

View File

@@ -0,0 +1,3 @@
#pragma once
void register_tests_commands(void);

View File

@@ -0,0 +1,57 @@
#include "epd.h"
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
static EpdiyHighlevelState s_state;
uint8_t* g_framebuffer;
static int s_temperature;
// 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
void initialize_screen(void) {
epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K);
// 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(1560);
s_state = epd_hl_init(EPD_BUILTIN_WAVEFORM);
g_framebuffer = epd_hl_get_framebuffer(&s_state);
epd_set_rotation(EPD_ROT_PORTRAIT);
epd_poweron();
s_temperature = (int)epd_ambient_temperature();
epd_poweroff();
}
void update_screen(void) {
enum EpdDrawError err;
epd_poweron();
err = epd_hl_update_screen(&s_state, EPD_MODE_DEFAULT, s_temperature);
taskYIELD();
epd_poweroff();
if (err != EPD_DRAW_SUCCESS) {
ESP_LOGW("screen_diag", "Could not update screen. Reason: %d", err);
}
}
void clear_screen(void) {
epd_hl_set_all_white(&s_state);
update_screen();
}
void full_clear_screen(void) {
epd_poweron();
epd_fullclear(&s_state, s_temperature);
epd_poweroff();
}

View File

@@ -0,0 +1,8 @@
#include <epd_highlevel.h>
extern uint8_t* g_framebuffer;
void initialize_screen(void);
void update_screen(void);
void clear_screen(void);
void full_clear_screen(void);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
#pragma once
#include <epdiy.h>
#ifdef DEFINE_FONTS
#include "alexandria.h"
#include "amiri.h"
#else
extern const EpdFont Alexandria;
extern const EpdFont Amiri;
#endif

View File

@@ -0,0 +1,68 @@
/* Based on the console example app, which is licensed under CC0.
* @link
* https://github.com/espressif/esp-idf/blob/v4.4.3/examples/system/console/basic/main/console_example_main.c
*/
#include <esp_console.h>
#include <esp_log.h>
#include <esp_vfs_fat.h>
#include <nvs.h>
#include <nvs_flash.h>
#include <string.h>
#include "commands/graphics.h"
#include "commands/screen.h"
#include "commands/system.h"
#include "commands/tests.h"
#include "epd.h"
#define DEFINE_FONTS
#include "fonts.h"
static const char* TAG = "screen_diag";
static void initialize_filesystem(void) {
static wl_handle_t wl_handle;
const esp_vfs_fat_mount_config_t mount_config
= { .max_files = 4, .format_if_mount_failed = true };
esp_err_t err
= esp_vfs_fat_spiflash_mount("/screen_diag", "storage", &mount_config, &wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
return;
}
}
static void initialize_nvs(void) {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
}
void app_main(void) {
initialize_nvs();
initialize_filesystem();
initialize_screen();
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
repl_config.prompt = "diag>";
repl_config.history_save_path = "/screen_diag/history.txt";
/* Register commands */
esp_console_register_help_command();
register_system_commands();
register_screen_commands();
register_graphics_commands();
register_tests_commands();
esp_console_repl_t* repl;
esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
ESP_ERROR_CHECK(esp_console_start_repl(repl));
}

View File

@@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, fat, , 1M,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, fat, , 1M,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
# Add newly added components to one of these lines:
# 1. Add here if the component is compatible with IDF >= v4.3
set(EXTRA_COMPONENT_DIRS "../../")
set(TEST_COMPONENTS "epdiy")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(epdiy_testrunner)

View File

@@ -0,0 +1,3 @@
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")

View File

@@ -0,0 +1,15 @@
#include <stdio.h>
#include <unity.h>
#include "unity_test_runner.h"
static void print_banner(const char* text) {
printf("\n#### %s #####\n\n", text);
}
void app_main(void) {
print_banner("Running all the registered tests");
UNITY_BEGIN();
// unity_run_tests_by_tag("lut", false);
unity_run_all_tests();
UNITY_END();
}

View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.16.0)
set(EXTRA_COMPONENT_DIRS "../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(dragon_example)

View File

@@ -0,0 +1,7 @@
A demo showing a Full-Screen Image
==================================
*The image size is chosen to fit a 1200 * 825 display!*
Image by David REVOY / CC BY (https://creativecommons.org/licenses/by/3.0)
https://commons.wikimedia.org/wiki/File:Durian_-_Sintel-wallpaper-dragon.jpg

View File

@@ -0,0 +1,3 @@
set(app_sources "main.c")
idf_component_register(SRCS ${app_sources} REQUIRES epdiy)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
/* Simple firmware for a ESP32 displaying a static image on an EPaper Screen */
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "dragon.h"
#include "epd_highlevel.h"
#include "epdiy.h"
#include "board/tps65185.h"
EpdiyHighlevelState hl;
// 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
int temperature = 25;
void draw_dragon() {
EpdRect dragon_area = { .x = 0, .y = 0, .width = dragon_width, .height = dragon_height };
epd_poweron();
epd_fullclear(&hl, temperature);
epd_copy_to_framebuffer(dragon_area, dragon_data, epd_hl_get_framebuffer(&hl));
enum EpdDrawError _err = epd_hl_update_screen(&hl, MODE_GC16, temperature);
epd_poweroff();
}
void idf_loop() {
// make a full black | white print to force epdiy to send the update
epd_fill_rect(epd_full_screen(), 0, epd_hl_get_framebuffer(&hl));
epd_hl_update_screen(&hl, MODE_DU, temperature);
vTaskDelay(pdMS_TO_TICKS(1));
epd_fill_rect(epd_full_screen(), 255, epd_hl_get_framebuffer(&hl));
epd_hl_update_screen(&hl, MODE_DU, temperature);
}
void idf_setup() {
epd_init(&DEMO_BOARD, &ED097TC2, EPD_LUT_64K);
hl = epd_hl_init(&epdiy_NULL);
// starts the board in kickback more
tps_vcom_kickback();
// display starts to pass BLACK to WHITE but doing nothing+
// dince the NULL waveform is full of 0 "Do nothing for each pixel"
idf_loop();
// start measure and set ACQ bit:
tps_vcom_kickback_start();
int isrdy = 1;
int kickback_volt = 0;
while (kickback_volt == 0) {
idf_loop();
isrdy++;
kickback_volt = tps_vcom_kickback_rdy();
}
ESP_LOGI("vcom", "readings are of %d mV. It was ready in %d refreshes", kickback_volt, isrdy);
vTaskDelay(pdMS_TO_TICKS(2000));
esp_restart();
}
#ifndef ARDUINO_ARCH_ESP32
void app_main() {
idf_setup();
}
#endif

View File

@@ -0,0 +1,30 @@
/**
* This is the Arduino wrapper for the "Demo" example.
* Please go to the main.c for the main example file.
*
* This example was developed for the ESP IoT Development Framework (IDF).
* You can still use this code in the Arduino IDE, but it may not look
* and feel like a classic Arduino sketch.
* If you are looking for an example with Arduino look-and-feel,
* please check the other examples.
*/
// Important: These are C functions, so they must be declared with C linkage!
extern "C" {
void idf_setup();
void idf_loop();
}
void setup() {
if (psramInit()) {
Serial.println("\nThe PSRAM is correctly initialized");
} else {
Serial.println("\nPSRAM does not work");
}
idf_setup();
}
void loop() {
idf_loop();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.13.0)
set(EXTRA_COMPONENT_DIRS "../../")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(weather)

Some files were not shown because too many files have changed in this diff Show More