Прошло 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,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