From cebda84b85dc5e20240419a78b39e11a8827ba7a Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Thu, 17 Apr 2025 10:22:39 +0100 Subject: [PATCH 1/8] [nix] Add a nix environment Signed-off-by: Douglas Reis --- flake.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 27 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..1d6cf26 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1744440957, + "narHash": "sha256-FHlSkNqFmPxPJvy+6fNLaNeWnF1lZSgqVCl/eWaJRc4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "26d499fc9f1d567283d5d56fcf367edd815dba1d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..26abad7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,27 @@ +{ + description = "C/C++ environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + flake-utils.url = "github:numtide/flake-utils"; + }; + +outputs = { self, nixpkgs, flake-utils, ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + cmake + clang-tools_18 + gdb + ]; + shellHook = '' + echo "C++ dev environment ready!" + ''; + }; + } + ); +} From 29338fb60439b28c81619be5858db328389014df Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Thu, 17 Apr 2025 10:24:09 +0100 Subject: [PATCH 2/8] Move sources to a src folder Signed-off-by: Douglas Reis --- {core => src/core}/font.h | 0 {core => src/core}/lcd_base.c | 0 {core => src/core}/lcd_base.h | 0 {core => src/core}/lucida_console_10pt.c | 0 {core => src/core}/lucida_console_10pt.h | 0 {core => src/core}/lucida_console_12pt.c | 0 {core => src/core}/lucida_console_12pt.h | 0 {core => src/core}/m3x6_16pt.c | 0 {core => src/core}/m3x6_16pt.h | 0 {st7735 => src/st7735}/lcd_st7735.c | 0 {st7735 => src/st7735}/lcd_st7735.h | 0 {st7735 => src/st7735}/lcd_st7735_cmds.h | 0 {st7735 => src/st7735}/lcd_st7735_init.h | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {core => src/core}/font.h (100%) rename {core => src/core}/lcd_base.c (100%) rename {core => src/core}/lcd_base.h (100%) rename {core => src/core}/lucida_console_10pt.c (100%) rename {core => src/core}/lucida_console_10pt.h (100%) rename {core => src/core}/lucida_console_12pt.c (100%) rename {core => src/core}/lucida_console_12pt.h (100%) rename {core => src/core}/m3x6_16pt.c (100%) rename {core => src/core}/m3x6_16pt.h (100%) rename {st7735 => src/st7735}/lcd_st7735.c (100%) rename {st7735 => src/st7735}/lcd_st7735.h (100%) rename {st7735 => src/st7735}/lcd_st7735_cmds.h (100%) rename {st7735 => src/st7735}/lcd_st7735_init.h (100%) diff --git a/core/font.h b/src/core/font.h similarity index 100% rename from core/font.h rename to src/core/font.h diff --git a/core/lcd_base.c b/src/core/lcd_base.c similarity index 100% rename from core/lcd_base.c rename to src/core/lcd_base.c diff --git a/core/lcd_base.h b/src/core/lcd_base.h similarity index 100% rename from core/lcd_base.h rename to src/core/lcd_base.h diff --git a/core/lucida_console_10pt.c b/src/core/lucida_console_10pt.c similarity index 100% rename from core/lucida_console_10pt.c rename to src/core/lucida_console_10pt.c diff --git a/core/lucida_console_10pt.h b/src/core/lucida_console_10pt.h similarity index 100% rename from core/lucida_console_10pt.h rename to src/core/lucida_console_10pt.h diff --git a/core/lucida_console_12pt.c b/src/core/lucida_console_12pt.c similarity index 100% rename from core/lucida_console_12pt.c rename to src/core/lucida_console_12pt.c diff --git a/core/lucida_console_12pt.h b/src/core/lucida_console_12pt.h similarity index 100% rename from core/lucida_console_12pt.h rename to src/core/lucida_console_12pt.h diff --git a/core/m3x6_16pt.c b/src/core/m3x6_16pt.c similarity index 100% rename from core/m3x6_16pt.c rename to src/core/m3x6_16pt.c diff --git a/core/m3x6_16pt.h b/src/core/m3x6_16pt.h similarity index 100% rename from core/m3x6_16pt.h rename to src/core/m3x6_16pt.h diff --git a/st7735/lcd_st7735.c b/src/st7735/lcd_st7735.c similarity index 100% rename from st7735/lcd_st7735.c rename to src/st7735/lcd_st7735.c diff --git a/st7735/lcd_st7735.h b/src/st7735/lcd_st7735.h similarity index 100% rename from st7735/lcd_st7735.h rename to src/st7735/lcd_st7735.h diff --git a/st7735/lcd_st7735_cmds.h b/src/st7735/lcd_st7735_cmds.h similarity index 100% rename from st7735/lcd_st7735_cmds.h rename to src/st7735/lcd_st7735_cmds.h diff --git a/st7735/lcd_st7735_init.h b/src/st7735/lcd_st7735_init.h similarity index 100% rename from st7735/lcd_st7735_init.h rename to src/st7735/lcd_st7735_init.h From b1dc5290d5d1fa9a6ba129d78379872642ebb5e2 Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Thu, 17 Apr 2025 10:35:15 +0100 Subject: [PATCH 3/8] Add extern C to all headers Signed-off-by: Douglas Reis --- src/core/font.h | 10 +++++++++- src/core/lcd_base.h | 9 +++++++++ src/core/lucida_console_10pt.h | 10 +++++++++- src/core/lucida_console_12pt.h | 10 +++++++++- src/core/m3x6_16pt.h | 8 ++++++++ src/st7735/lcd_st7735.h | 9 +++++++++ src/st7735/lcd_st7735_cmds.h | 8 ++++++++ src/st7735/lcd_st7735_init.h | 8 ++++++++ 8 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/core/font.h b/src/core/font.h index 0cd07b0..6199269 100644 --- a/src/core/font.h +++ b/src/core/font.h @@ -7,6 +7,10 @@ #ifndef COMMON_FONT_H #define COMMON_FONT_H +#ifdef __cplusplus +extern "C" { +#endif + #include typedef struct FontCharInfo_st { @@ -22,4 +26,8 @@ typedef struct Font_st { const unsigned char *bitmap_table; /*< Character bitmap array. */ } Font; -#endif \ No newline at end of file +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/core/lcd_base.h b/src/core/lcd_base.h index 04b4fd2..b16c5b2 100644 --- a/src/core/lcd_base.h +++ b/src/core/lcd_base.h @@ -6,6 +6,11 @@ #ifndef DISPLAY_DRIVERS_COMMON_BASE_H_ #define DISPLAY_DRIVERS_COMMON_BASE_H_ +#ifdef __cplusplus +extern "C" { +#endif + + #include #include #include @@ -192,4 +197,8 @@ static inline uint16_t LCD_rgb565_to_bgr565(const uint8_t rgb[2]) { return ENDIANESS_TO_HALF_WORD(color); } +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/core/lucida_console_10pt.h b/src/core/lucida_console_10pt.h index 35c08c4..9ed0648 100644 --- a/src/core/lucida_console_10pt.h +++ b/src/core/lucida_console_10pt.h @@ -6,6 +6,10 @@ #ifndef LUCIDACONSOLE_10PT_H_ #define LUCIDACONSOLE_10PT_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include #include "font.h" @@ -15,4 +19,8 @@ extern const unsigned char lucidaConsole_10ptBitmaps[]; extern const Font lucidaConsole_10ptFont; extern const FontCharInfo lucidaConsole_10ptDescriptors[]; -#endif /* LUCIDACONSOLE_10PT_H_ */ \ No newline at end of file +#ifdef __cplusplus +} +#endif + +#endif /* LUCIDACONSOLE_10PT_H_ */ diff --git a/src/core/lucida_console_12pt.h b/src/core/lucida_console_12pt.h index 5cd9bf9..b262463 100644 --- a/src/core/lucida_console_12pt.h +++ b/src/core/lucida_console_12pt.h @@ -6,6 +6,10 @@ #ifndef LUCIDACONSOLE_12PT_H_ #define LUCIDACONSOLE_12PT_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include #include "font.h" @@ -15,4 +19,8 @@ extern const unsigned char lucidaConsole_12ptBitmaps[]; extern const Font lucidaConsole_12ptFont; extern const FontCharInfo lucidaConsole_12ptDescriptors[]; -#endif /* LUCIDACONSOLE_12PT_H_ */ \ No newline at end of file +#ifdef __cplusplus +} +#endif + +#endif /* LUCIDACONSOLE_12PT_H_ */ diff --git a/src/core/m3x6_16pt.h b/src/core/m3x6_16pt.h index 29b9eef..9d34aaa 100644 --- a/src/core/m3x6_16pt.h +++ b/src/core/m3x6_16pt.h @@ -5,6 +5,10 @@ #ifndef M3X6_16PT_H_ #define M3X6_16PT_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include #include "font.h" @@ -14,4 +18,8 @@ extern const unsigned char m3x6_16ptBitmaps[]; extern const Font m3x6_16ptFont; extern const FontCharInfo m3x6_16ptDescriptors[]; +#ifdef __cplusplus +} +#endif + #endif /* M3X6_16PT_H_ */ diff --git a/src/st7735/lcd_st7735.h b/src/st7735/lcd_st7735.h index 301be0f..a76373e 100644 --- a/src/st7735/lcd_st7735.h +++ b/src/st7735/lcd_st7735.h @@ -6,6 +6,10 @@ #ifndef DISPLAY_DRIVERS_ST7735_ST7735_H_ #define DISPLAY_DRIVERS_ST7735_ST7735_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include #include "../core/font.h" @@ -257,4 +261,9 @@ void lcd_st7735_set_frame_buffer_resolution(St7735Context *ctx, size_t width, si * @return Result of the operation. */ Result lcd_st7735_check_frame_buffer_resolution(St7735Context *lcd, size_t *width, size_t *height); + +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/st7735/lcd_st7735_cmds.h b/src/st7735/lcd_st7735_cmds.h index 5762fa2..f5144a3 100644 --- a/src/st7735/lcd_st7735_cmds.h +++ b/src/st7735/lcd_st7735_cmds.h @@ -31,6 +31,10 @@ #ifndef DISPLAY_DRIVERS_ST7735_ST7735_CMD_H_ #define DISPLAY_DRIVERS_ST7735_ST7735_CMD_H_ +#ifdef __cplusplus +extern "C" { +#endif + typedef enum { ST7735_NOP = 0x00, ST7735_SWRESET = 0x01, @@ -92,4 +96,8 @@ typedef enum { ST7735_ColorWhite = 0xFFFF, } ST7735_Color; +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/st7735/lcd_st7735_init.h b/src/st7735/lcd_st7735_init.h index 7664f7b..d500667 100644 --- a/src/st7735/lcd_st7735_init.h +++ b/src/st7735/lcd_st7735_init.h @@ -32,6 +32,10 @@ #ifndef DISPLAY_DRIVERS_ST7735_ST7735_INIT_H_ #define DISPLAY_DRIVERS_ST7735_ST7735_INIT_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include "lcd_st7735_cmds.h" #define NEXT_BYTE(addr) (*(const uint8_t *)(addr++)) @@ -160,5 +164,9 @@ static const uint8_t init_script_r3[] = { 100 //100 ms delay }; +#ifdef __cplusplus +} +#endif + // clang-format on #endif From 94c127461ed562019c4dfe49824888d086c0d48c Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Fri, 18 Apr 2025 16:09:18 +0100 Subject: [PATCH 4/8] [tests] Add a framework for unittests Signed-off-by: Douglas Reis --- CMakeLists.txt | 16 +++ README.md | 15 ++ flake.nix | 1 + src/CMakeLists.txt | 8 ++ tests/CMakeLists.txt | 19 +++ tests/golden_files/st7735_startup.txt | 189 ++++++++++++++++++++++++++ tests/main.cc | 107 +++++++++++++++ 7 files changed, 355 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 src/CMakeLists.txt create mode 100644 tests/CMakeLists.txt create mode 100644 tests/golden_files/st7735_startup.txt create mode 100644 tests/main.cc diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d928fe9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.13) + +project(display_drivers LANGUAGES C CXX) + +# Set the C++ standard to C++20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# For lsp +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + + +set(NAME st7735_driver) + +add_subdirectory(src) +add_subdirectory(tests) diff --git a/README.md b/README.md index b2f794f..ee63d1d 100644 --- a/README.md +++ b/README.md @@ -86,3 +86,18 @@ void main(void){ } ``` +## Running unittests +Start nix development environment +```sh +nix develop +``` +Build using Cmake +```sh +cmake -S . -B ./build/. && cmake --build ./build/. +``` +Run the test +```sh +./build/tests/st7735_driver_test +``` + + diff --git a/flake.nix b/flake.nix index 26abad7..6aee2f6 100644 --- a/flake.nix +++ b/flake.nix @@ -17,6 +17,7 @@ outputs = { self, nixpkgs, flake-utils, ... }: cmake clang-tools_18 gdb + python310 ]; shellHook = '' echo "C++ dev environment ready!" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..842eadb --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(${NAME} STATIC + "core/lucida_console_12pt.c" + "core/lcd_base.c" + "core/lucida_console_10pt.c" + "core/m3x6_16pt.c" + "st7735/lcd_st7735.c" +) + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..ab08c6a --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,19 @@ + +# Fetch GoogleTest library. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.12.1.zip +) +FetchContent_MakeAvailable(googletest) + +set(TEST_NAME ${NAME}_test) + +# add_compile_options(-O0 -g3) +add_executable(${TEST_NAME} main.cc ) + +target_include_directories(${TEST_NAME} PRIVATE "../") +target_link_libraries(${TEST_NAME} PRIVATE GTest::gtest_main ${NAME}) + +add_test(NAME Test_0 COMMAND ${TEST_NAME}) + diff --git a/tests/golden_files/st7735_startup.txt b/tests/golden_files/st7735_startup.txt new file mode 100644 index 0000000..a1a443b --- /dev/null +++ b/tests/golden_files/st7735_startup.txt @@ -0,0 +1,189 @@ +gpio_write: cs=false, dc=false +spi_write: 01 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 50 +gpio_write: cs=false, dc=false +spi_write: 11 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 500 +gpio_write: cs=false, dc=false +spi_write: 3a +gpio_write: cs=false, dc=true +spi_write: 05 +gpio_write: cs=true, dc=true +sleep_ms: 10 +gpio_write: cs=false, dc=false +spi_write: b1 +gpio_write: cs=false, dc=true +spi_write: 00 06 03 +gpio_write: cs=true, dc=true +sleep_ms: 10 +gpio_write: cs=false, dc=false +spi_write: 36 +gpio_write: cs=false, dc=true +spi_write: 68 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: b6 +gpio_write: cs=false, dc=true +spi_write: 15 02 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: b4 +gpio_write: cs=false, dc=true +spi_write: 00 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c0 +gpio_write: cs=false, dc=true +spi_write: 02 70 +gpio_write: cs=true, dc=true +sleep_ms: 10 +gpio_write: cs=false, dc=false +spi_write: c1 +gpio_write: cs=false, dc=true +spi_write: 05 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c2 +gpio_write: cs=false, dc=true +spi_write: 01 02 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c5 +gpio_write: cs=false, dc=true +spi_write: 3c 38 +gpio_write: cs=true, dc=true +sleep_ms: 10 +gpio_write: cs=false, dc=false +spi_write: fc +gpio_write: cs=false, dc=true +spi_write: 11 15 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: e0 +gpio_write: cs=false, dc=true +spi_write: 09 16 09 20 21 1b 13 19 17 15 1e 2b 04 05 02 0e +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: e1 +gpio_write: cs=false, dc=true +spi_write: 0b 14 08 1e 22 1d 18 1e 1b 1a 24 2b 06 06 02 0f +gpio_write: cs=true, dc=true +sleep_ms: 10 +gpio_write: cs=false, dc=false +spi_write: 2a +gpio_write: cs=false, dc=true +spi_write: 00 02 00 81 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: 2b +gpio_write: cs=false, dc=true +spi_write: 00 02 00 81 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: 13 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 10 +gpio_write: cs=false, dc=false +spi_write: 29 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 500 +gpio_write: cs=false, dc=false +spi_write: 01 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 150 +gpio_write: cs=false, dc=false +spi_write: 11 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 500 +gpio_write: cs=false, dc=false +spi_write: b1 +gpio_write: cs=false, dc=true +spi_write: 00 02 02 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: b2 +gpio_write: cs=false, dc=true +spi_write: 00 02 02 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: b3 +gpio_write: cs=false, dc=true +spi_write: 00 02 02 00 02 02 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: b4 +gpio_write: cs=false, dc=true +spi_write: 07 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c0 +gpio_write: cs=false, dc=true +spi_write: a2 02 84 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c1 +gpio_write: cs=false, dc=true +spi_write: c5 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c2 +gpio_write: cs=false, dc=true +spi_write: 0a 00 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c3 +gpio_write: cs=false, dc=true +spi_write: 8a 2a +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c4 +gpio_write: cs=false, dc=true +spi_write: 8a ee +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: c5 +gpio_write: cs=false, dc=true +spi_write: 0e +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: 20 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: 36 +gpio_write: cs=false, dc=true +spi_write: 68 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: 3a +gpio_write: cs=false, dc=true +spi_write: 05 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: e0 +gpio_write: cs=false, dc=true +spi_write: 02 1c 07 12 37 32 29 2d 29 25 2b 39 00 01 03 10 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: e1 +gpio_write: cs=false, dc=true +spi_write: 03 1d 07 06 2e 2c 29 2d 2e 2e 37 3f 00 00 02 10 +gpio_write: cs=true, dc=true +gpio_write: cs=false, dc=false +spi_write: 13 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 10 +gpio_write: cs=false, dc=false +spi_write: 29 +gpio_write: cs=false, dc=true +gpio_write: cs=true, dc=true +sleep_ms: 100 diff --git a/tests/main.cc b/tests/main.cc new file mode 100644 index 0000000..f9f60be --- /dev/null +++ b/tests/main.cc @@ -0,0 +1,107 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "../src/st7735/lcd_st7735.h" + +void log_hex(std::ostream &stream, const uint8_t *data, size_t len) { + for (size_t i = 0; i < len; ++i) { + stream << std::format("{:02x} ", static_cast(data[i])); + } + stream << std::endl; +} + +struct MockInterfaceFile { + std::string filename_; + + MockInterfaceFile() { + char out_filename[1024]; + filename_ = std::string(std::tmpnam(out_filename)); + }; + + static uint32_t spi_write(void *handle, uint8_t *data, size_t len) { + MockInterfaceFile *self = (MockInterfaceFile *)handle; + std::ofstream file(self->filename_, std::ios::app); + file << std::format("{}: ", __func__); + log_hex(file, data, len); + return len; + } + + static uint32_t gpio_write(void *handle, bool cs, bool dc) { + MockInterfaceFile *self = (MockInterfaceFile *)handle; + std::ofstream file(self->filename_, std::ios::app); + file << std::format("{}: cs={}, dc={}", __func__, cs, dc) << std::endl; + return 0; + } + + static uint32_t reset(void *handle) { + MockInterfaceFile *self = (MockInterfaceFile *)handle; + std::ofstream file(self->filename_, std::ios::app); + file << std::format("{}", __func__) << std::endl; + return 0; + } + + static void set_pwm(void *handle, uint8_t pwm) { + MockInterfaceFile *self = (MockInterfaceFile *)handle; + std::ofstream file(self->filename_, std::ios::app); + file << std::format("{}: {}", __func__, pwm) << std::endl; + } + + static void sleep_ms(void *handle, uint32_t ms) { + MockInterfaceFile *self = (MockInterfaceFile *)handle; + std::ofstream file(self->filename_, std::ios::app); + file << std::format("{}: {}", __func__, ms) << std::endl; + } +}; + +class DisplayTest : public testing::Test { + public: + void compare_files(const std::string &result_file, const std::string &expected_file) { + std::ifstream f1(result_file), f2(expected_file); + std::string result, expected; + int lineNum = 1; + + while (std::getline(f1, result) && std::getline(f2, expected)) { + EXPECT_EQ(result, expected) << std::format("File mismatch {} != {} at line{}", result_file, expected_file, + lineNum); + ++lineNum; + } + } +}; + + +class st7735Test : public DisplayTest { + public: + MockInterfaceFile mock_ = MockInterfaceFile(); + St7735Context ctx_; + LCD_Interface interface_; + + st7735Test() { + interface_ = { + .handle = &mock_, + .spi_write = MockInterfaceFile::spi_write, + .spi_read = NULL, + .gpio_write = MockInterfaceFile::gpio_write, + .reset = MockInterfaceFile::reset, + .set_backlight_pwm = MockInterfaceFile::set_pwm, + .timer_delay = MockInterfaceFile::sleep_ms, + }; + lcd_st7735_init(&ctx_, &interface_); + } +}; + +TEST_F(st7735Test, startup) { + Result res = lcd_st7735_startup(&ctx_); + EXPECT_EQ(res.code, 0); + compare_files(mock_.filename_, "./golden_files/st7735_startup.txt"); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 2b520eafc8e670a6a6d4255bfc7420a14832cafd Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Sat, 19 Apr 2025 18:56:00 +0100 Subject: [PATCH 5/8] [doc] Fix fill_rectangle function in main page Signed-off-by: Douglas Reis --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee63d1d..e9923a5 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ void main(void){ lcd_st7735_init(&ctx, &interface); lcd_st7735_startup(&ctx); - lcd_st7735_fill_rectangle(&ctx, (LCD_rectangle){.origin = {.x = 0, .y = 0}, - .end = {.x = 160, .y = 128}}, 0x00FF00); + lcd_st7735_fill_rectangle( + &ctx_, (LCD_rectangle){.origin = {.x = 0, .y = 0}, .width = 160, .height = 128}, 0x00FF00); } ``` From 43b8c11841bfb81868d78ae05f38747b612b2342 Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Sat, 19 Apr 2025 18:57:09 +0100 Subject: [PATCH 6/8] [sim] Add a emulator for the st7735 controller Signed-off-by: Douglas Reis --- simulator/st7735/controller.hh | 238 ++++++++++++++++++++ tests/golden_files/test_draw_rectangles.png | Bin 0 -> 1059 bytes tests/golden_files/test_draw_text.png | Bin 0 -> 4067 bytes 3 files changed, 238 insertions(+) create mode 100644 simulator/st7735/controller.hh create mode 100644 tests/golden_files/test_draw_rectangles.png create mode 100644 tests/golden_files/test_draw_text.png diff --git a/simulator/st7735/controller.hh b/simulator/st7735/controller.hh new file mode 100644 index 0000000..5c44815 --- /dev/null +++ b/simulator/st7735/controller.hh @@ -0,0 +1,238 @@ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +#ifdef SIMULATOR_LOGGING +#include +#define LOG(msg) std::cout << msg +#else +#define LOG(msg) \ + do { \ + } while (0) +#endif + +namespace Simulator { + +// Forwward declaration +template +class St7735; + +template +class State { + public: + virtual ~State() = default; + virtual void handle(St7735& sim, std::vector& buffer) = 0; +}; + +template +class CommandState : public State { + public: + void handle(St7735& sim, std::vector& buffer) override { sim.parse_commands(buffer); } +}; + +template +class CasetState : public State { + public: + void handle(St7735& sim, std::vector& buffer) override { sim.parse_caset(buffer); } +}; + +template +class RasetState : public State { + public: + void handle(St7735& sim, std::vector& buffer) override { sim.parse_raset(buffer); } +}; + +template +class RamWriteState : public State { + public: + void handle(St7735& sim, std::vector& buffer) override { sim.ram_write(buffer); } +}; + +enum class PinLevel { + Low = 0, + High = 1, +}; + +union __attribute__((packed)) Pixel { + struct { + uint8_t r; + uint8_t g; + uint8_t b; + } rgb; + uint8_t buffer[3]; +}; + +struct Cursor { + size_t col_start, col_end, row_start, row_end, row, col; + void operator++(int) { + if (++col > col_end) { + col = col_start; + if (row++ > row_end) { + row = row_start; + } + } + } +}; + +template +class St7735 { + State* state = new CommandState(); + + std::array, height> frame_buffer; + Cursor cursor; + + PinLevel dc_pin_ = PinLevel::High; + PinLevel cs_pin_ = PinLevel::High; + LCD_Orientation orientation_; + + public: + St7735() {} + + void set_state(State* new_state) { state = new_state; } + void update(std::vector& data) { state->handle(*this, data); } + + void spi_write(uint8_t* data, size_t len) { + std::vector vec(data, data + len); + update(vec); + } + + void parse_commands(std::vector& buffer) { + LOG(std::format("{}: ", __func__)); + uint8_t cmd = buffer[0]; + switch (cmd) { + break; + case ST7735_CASET: + LOG(std::format("CASET: ")); + this->set_state(new CasetState()); + break; + case ST7735_RASET: + LOG(std::format("RASET: ")); + this->set_state(new RasetState()); + break; + case ST7735_RAMWR: + LOG(std::format("RAMWR:\n")); + this->set_state(new RamWriteState()); + break; + case ST7735_NOP: + case ST7735_SWRESET: + case ST7735_RDDID: + case ST7735_RDDST: + case ST7735_SLPIN: + case ST7735_SLPOUT: + case ST7735_PTLON: + case ST7735_NORON: + case ST7735_INVOFF: + case ST7735_INVON: + case ST7735_DISPOFF: + case ST7735_DISPON: + case ST7735_PTLAR: + case ST7735_COLMOD: + case ST7735_MADCTL: + case ST7735_FRMCTR1: + case ST7735_FRMCTR2: + case ST7735_FRMCTR3: + case ST7735_INVCTR: + case ST7735_DISSET5: + case ST7735_PWCTR1: + case ST7735_PWCTR2: + case ST7735_PWCTR3: + case ST7735_PWCTR4: + case ST7735_PWCTR5: + case ST7735_VMCTR1: + case ST7735_RDID1: + case ST7735_RDID2: + case ST7735_RDID3: + case ST7735_RDID4: + case ST7735_PWCTR6: + case ST7735_GMCTRP1: + case ST7735_GMCTRN1: + case ST7735_RAMRD: + default: + LOG(std::format("cmd[{:#02x}] unimp: \n", cmd)); + break; + } + } + + void parse_caset(std::vector& buffer) { + cursor.col = cursor.col_start = buffer[0] << 8 | buffer[1]; + cursor.col_end = buffer[2] << 8 | buffer[3]; + LOG(std::format("x: {},y:{} \n", col_addr_s_, col_addr_e_)); + } + + void parse_raset(std::vector& buffer) { + cursor.row = cursor.row_start = buffer[0] << 8 | buffer[1]; + cursor.row_end = buffer[2] << 8 | buffer[3]; + LOG(std::format("x: {},y:{} \n", row_addr_s_, row_addr_e_)); + } + + void ram_write(std::vector& buffer) { + for (size_t i = 0; i < buffer.size() - 1; i += 2) { + uint16_t bgr565 = buffer[i] << 8 | buffer[i + 1]; + Pixel pixel = {.rgb = {.r = static_cast((((bgr565 >> 0) & 0x1f) << 3) | 0x7), + .g = static_cast((((bgr565 >> 5) & 0x3f) << 2) | 0x3), + .b = static_cast((((bgr565 >> 11) & 0x1f) << 3) | 0x7)}}; + + frame_buffer[cursor.row][cursor.col] = pixel; + cursor++; + } + } + + void render() { + for (auto& row : frame_buffer) { + LOG(std::format("{{")); + for (auto& pixel : row) { + std::cout << std::format("{:02x}{:02x}{:02x},", pixel.rgb.r, pixel.rgb.g, pixel.rgb.b); + } + std::cout << std::format("}}\n"); + } + } + + void bmp(std::string filename) { + unsigned char bpm[width * height * 3]; + + size_t i = 0; + for (auto& row : frame_buffer) { + for (auto& pixel : row) { + bpm[i++] = pixel.rgb.r; + bpm[i++] = pixel.rgb.g; + bpm[i++] = pixel.rgb.b; + } + } + stbi_write_bmp(filename.c_str(), width, height, 3, bpm); + } + + void png(std::string filename) { + unsigned char png[width * height * 3]; + + size_t i = 0; + for (auto& row : frame_buffer) { + for (auto& pixel : row) { + png[i++] = pixel.rgb.r; + png[i++] = pixel.rgb.g; + png[i++] = pixel.rgb.b; + } + } + stbi_write_png(filename.c_str(), width, height, 3, png, 3 * width); + } + + void dc_pin(PinLevel level) { + if (level == PinLevel::Low) { + this->set_state(new CommandState()); + } + dc_pin_ = level; + } + + void cs_pin(PinLevel level) { cs_pin_ = level; } +}; + +} // namespakce Simulator diff --git a/tests/golden_files/test_draw_rectangles.png b/tests/golden_files/test_draw_rectangles.png new file mode 100644 index 0000000000000000000000000000000000000000..e9957180c0a0cc7b4b255c0c6245b57f1cd77891 GIT binary patch literal 1059 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Rg9%77Gk)~}lCL~n978JNlK=ewZ_nJzz|C&g z{Qu7Z<Uo{3o|V){g5GR zGwZwC>jxK245HZkmo;9rsxaZbpuplL9l5x7&BY$mTPLKzCP>`;zxncsLi>mhl4lyr zb48fr_VTcNc@%5;wLN*qPx1BjAVaJww)~M@+w6Y%zyjVzpoKt-&dyux`Xxh3LU$2R z^gzo2poKt-p6y*I!=%Gfb|&zN`KR(%ftybA|Mw8B`yaGr_k_fRKi&3S5Elatc&paC ztkF@L31p0*?JQe&pjUv7bpblIkrU+JEqroktDP-bA~@!}bFtpPF<^sPSL=lgQMq(s zi?dwo+cRIJMcuRpY5@A(^$XE~qr+3-@4x&+pvatta}E>Z_N-HT5dX=4Wx9mM|3_cn zWhiHFnP?idqt9%U+vNj5o(V6H4@b|ngP<_ICk+k*P}s78!`7i$AR#4v*-gvHin*s9 z55M^1xbLjUoO@rB5B%vq|L9`amkjNyEl*@lDH_0H?d(BdJY8rz0Q54@ua#v zdce*^3Vv`rgPaRY4WKk)!oeu^=ds9lr{~h`dIaY zUA3BDQ&Rc2bDa&yH3+xy+!6q#4q&vtu>?me*ssW;c}nrXiGTCco~JyN?R~T$A*7?R zA#Ts=ga0nPYFQ`G{4{>g%Y0x$ftUey(-uWg0s)5B&0=s`ha^GSlfa~G!qIrq?V5pq zn2x004jp0ssI20|E6+000lFNkl< zXa3JHC;$fo!w-g0K)s9Ey>}397+V02F};!Wj^IAY82K;SRy223Y|yUPQJU z%^3_t_4r&+Ucw!MbnvnwuEq|kN2VG)d8al}nPwa9~;0rv-3#$}7;vuCD!=P+BAi|0& zMKosl1`lv}(4#TnS}+S|h+cT`;R!(^)!>SKV#))o&LhTNjzJRfw624Z(>pMrIdBxw zShFB@X>aQpBiYIr)p8fds;_Xjw+panGD!u8v5PgYlbU_;6c}(v(WpoO8p}aeawJ(J%OGedz`cqbnrI=592t;=LX|kip6X#? z!oe_<0QR&vI@th;MaVzPyzM?#V(;);&vI0#p(1Qe%_+X1~uxLr%dE6G$zGh;bE>5k{orN%A9ds8X{Zj4QT&;Ap<# zQV$Or{1qXxc05*NjXZc9Q(nc8o%0~ULZkzTcQ9H|&|{zje>@QrKoIYtg#|Jfs{%YG z!$Scxl+lcWr%q(k@W%?lIKvvXSSt&x{R=n(SMbr_yv7FRie8BipZ0hH!o`a}$-Eoj*eQvvY~Bf|@j$l$;|Yl=hm81dQe2j0&HvAa_&178J24X3iQ9!$p{fnj-t1mFi zU>0Qm!1Z8y5+Z_WD6%8qo=1xYOm}0t11N%NIQ~)uzbvjGBbXQQgej)8v8D#NGOWQx zc42_uG&ET_mkAz21YJX3p^c^sk0C%Q{NY7v&=7Am0}TKfv5VjPc)|y3-hh}!S~rPc z93b07h7YL~&I4#QW-&&jr+(0;4TNPZ8Z#Z?4=;|<<~LFtLPCSQ(vDzKfMhU|3}+#u z+(V1+A2e%zqa`&okKvbPzzB4BWdvkkCSqz89u!tIR4;qbBN%J;B|b#RNMd+G7JpJD zJ|RO?;wfpU8{cFH0z@PJI40F_CU}ZNWkeiavTK*EE566IdEqvT)Hrw zhu<7D^`j9?lN63MB7uT<;s#j@T4Yn=L~u9h5BdQpnC?Ti5|2~xYlJuyq6Uv2;0n-8 zgNF{DV1gKnE2B^-3yC4D!OcOhx{lPiXF(2hxYN->5I+|!zVUE@=EL2GUm5;13khh< zFon1ie-Pl;3vnSXlYz2WBa-+)$7MT7d6XnW2zQe(vkRVtOwgemv`^{C&O;7*xJNME zkL*YMvJ}>c6xIi%W?!InH17wK;U;22f%s^^>HxGvz=5~zL$)kdk5H05a1|kVLX3%u zNfK*bhxms^(M(B(fIAE`{@`(d=_6zt@yjyM5)hwCpmi{E1dv*a!u8_P2oZ-U1Op4LJP!;U=DfV$v_n7ARv7Knj(Ov9>xpvj~T4_+2kMC+K*VR z#_tzka1m619Hc*pYqvs_K}?2(12P+8DY7_F1W!04OA$Geg>*g(hKrH?@B^cbhTn%I z=|xry_bslX62HmFL59YL8~6i9dy}97NCczB5W$QDcQ9HsqgjW{MfN+If8kP8D58$Am;}YKlAkWRj*1<%UOVn+#a16mw7ql{Lz?*PP#MccBqky;&fW+~DlzEsA zB=HayYy~y4PcSP#JpRP2u#w$INmjsbG93~s&;j(!%6OVSgo|io&G58|TzH}>Knp%h zf1(w<@DzYmC0hC*snEt_GJOgoh>tkvQqkawg^_amC@q@JKZe5G%!nAt`Z0oM$%gIb zGvu`!v7$2g)Mq~x>@&s!Yf|fzi zG(tibtJP@56fzf03LZspk8t3=lMHez7FG}9l7c%D?M5x+q`~oLke+74g6w+C;3h^E zO$~Bv;fe`}7Lu<1LJLhyld;-`RS+{G;mYtwKhO=hQU=3d1x(0c4QJztB&{#z3(OycS}@dUMI*;L8XMvQT9;sC1T;L3(cFe7e`5wN z)(nj`qmz`%(cFgYJ5s}w=G`Pn5Kujuah0XW;Rp{GGNPZLUR-9v{Yy!j#^Ye*@PwqK zK{3!l6>sCp1Z2b?&?r1n2+u&&wUL2bfSFc^Pd(5&7`Z4%3okU^{vf5>`h!Y+T&(8c zi3Ut5G*2OOv6=%B#F})-OHT{~1fWGUvL~^ojvuJqB;s9*95+}^MhjoK8Z;;2N-KDR z8>>;sc^B>rh+P~+baf!kz;q19VC^P>CL2gil0z#Albv%Y@g{z~1WhK%>e0yta-x{R zL`<+m#OkIWqmvD^uE?ku#CQsATvD_s=gBii32kfzb(%Q3xBCA*32k^}+)! zcCq>wk07QLL$e zLwVRzGY7+i2Eu@+K}e=Sb1Sk>u*NR3c4GWVYA&QtL}GP0@u``-2u3!aniKwzAcm9h zGzbrRv@nGzzzk@(2ri9ODKn^*MHmJSU?vox2$~?(axk9k3#m5X*%~ve0Yx~FsvP8a z{XwJCY2g=Tz!=Es3^Rq{%7~c762a)kj1|hWDcl4|aKQr;C{8$ni40;$B*96nj)q4h zTo&S6xSw(5<{z~0>X4DDehgd(41X#?4riu8Rl-0VLu3X*b2$f%8%IF%iMIlmVyqE^ z93RBwZ(`KI1BZnUMG8DzkX?q%MlPa(?jb2CA@0X48<71)Qi#wl#p8DdTIxaO!r4Et zRVerc>32CL9YPta6;!mBLZb#Goej6!M>$}PHAqPSB&k(X4#n67dVlnKS%%>aSVk|W z9Oe;>>^;nLsPUZF4wr(THU8rd&MWe8nS*J_00uXn6Pxjb!yr!|h*tCu9Ni48ht*>} zk{nGX+(0zNa4xbqRs-oDcF0~pb|_PxSjX| zgI=WE9Q|Nk?Lg5etiqsJ%>^VB^W!J`p8T7VDb5E#wDqd9mq2M^^C006u$ ViA&k_LxKPR002ovPDHLkV1f_Y>o@=a literal 0 HcmV?d00001 From 6b71ca91b3aa98e2730fac90e96d1f92751c285c Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Sat, 19 Apr 2025 18:58:18 +0100 Subject: [PATCH 7/8] [test] Use the emulator to test the driver - Test rectangles - Test text Signed-off-by: Douglas Reis --- tests/CMakeLists.txt | 10 ++- tests/main.cc | 187 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 192 insertions(+), 5 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ab08c6a..19b610c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,13 +7,19 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(googletest) +FetchContent_Declare(FTB + GIT_REPOSITORY https://github.com/nothings/stb + GIT_TAG f0569113c93ad095470c54bf34a17b36646bbbb5 +) +FetchContent_Populate(FTB) + set(TEST_NAME ${NAME}_test) # add_compile_options(-O0 -g3) add_executable(${TEST_NAME} main.cc ) -target_include_directories(${TEST_NAME} PRIVATE "../") -target_link_libraries(${TEST_NAME} PRIVATE GTest::gtest_main ${NAME}) +target_include_directories(${TEST_NAME} PRIVATE "../" "${ftb_SOURCE_DIR}") +target_link_libraries(${TEST_NAME} PRIVATE GTest::gtest_main ${NAME} ) add_test(NAME Test_0 COMMAND ${TEST_NAME}) diff --git a/tests/main.cc b/tests/main.cc index f9f60be..2d1c43e 100644 --- a/tests/main.cc +++ b/tests/main.cc @@ -8,6 +8,11 @@ #include #include "../src/st7735/lcd_st7735.h" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +constexpr size_t DisplayWidth = 160; +constexpr size_t DisplayHeight = 128; void log_hex(std::ostream &stream, const uint8_t *data, size_t len) { for (size_t i = 0; i < len; ++i) { @@ -20,8 +25,8 @@ struct MockInterfaceFile { std::string filename_; MockInterfaceFile() { - char out_filename[1024]; - filename_ = std::string(std::tmpnam(out_filename)); + char buffer[1024]; + filename_ = std::string(std::tmpnam(buffer)); }; static uint32_t spi_write(void *handle, uint8_t *data, size_t len) { @@ -61,6 +66,38 @@ struct MockInterfaceFile { class DisplayTest : public testing::Test { public: +#define GET_GOLDEN_FILE() \ + std::format("./tests/golden_files/test_{}.png", testing::UnitTest::GetInstance()->current_test_info()->name()) + + template + T rotate_left(T& v) { + v = v << 8 | v >> (32 - 8); + return v; + } + std::string make_temp_filename() { + char buffer[1024]; + return std::string(std::tmpnam(buffer)); + } + + void compare_img(const std::string &result_img, const std::string &expected_img) { + int w1, h1, c1, w2, h2, c2; + + unsigned char *result = stbi_load(result_img.c_str(), &w1, &h1, &c1, 0); + unsigned char *expected = stbi_load(expected_img.c_str(), &w2, &h2, &c2, 0); + + ASSERT_NE(expected, nullptr) << std::format("File {} not found, to compare with {}", expected_img, result_img); + ASSERT_NE(result, nullptr) << std::format("File {} not found, to compare with {}", result_img, expected_img); + ASSERT_TRUE(w1 == w2 && h1 == h2 && c1 == c2); + + size_t size = w1 * h1 * c1; + ASSERT_TRUE(std::memcmp(result, expected, size) == 0) + << std::format("Mismatch {} != {}\n", result_img, expected_img) + << std::format("Run: compare {} {} /tmp/diff.png", result_img, expected_img); + + stbi_image_free(result); + stbi_image_free(expected); + } + void compare_files(const std::string &result_file, const std::string &expected_file) { std::ifstream f1(result_file), f2(expected_file); std::string result, expected; @@ -74,7 +111,6 @@ class DisplayTest : public testing::Test { } }; - class st7735Test : public DisplayTest { public: MockInterfaceFile mock_ = MockInterfaceFile(); @@ -101,6 +137,151 @@ TEST_F(st7735Test, startup) { compare_files(mock_.filename_, "./golden_files/st7735_startup.txt"); } +#include +struct MockInterfaceSimulator { + Simulator::St7735 simulator; + + MockInterfaceSimulator() {}; + + static uint32_t spi_write(void *handle, uint8_t *data, size_t len) { + MockInterfaceSimulator *self = (MockInterfaceSimulator *)handle; + self->simulator.spi_write(data, len); + return len; + } + + static uint32_t gpio_write(void *handle, bool cs, bool dc) { + MockInterfaceSimulator *self = (MockInterfaceSimulator *)handle; + self->simulator.dc_pin(dc ? Simulator::PinLevel::High : Simulator::PinLevel::Low); + self->simulator.cs_pin(cs ? Simulator::PinLevel::High : Simulator::PinLevel::Low); + return 0; + } + + static uint32_t reset(void *handle) { + MockInterfaceSimulator *self = (MockInterfaceSimulator *)handle; + return 0; + } + + static void set_pwm(void *handle, uint8_t pwm) { MockInterfaceSimulator *self = (MockInterfaceSimulator *)handle; } + + static void sleep_ms(void *handle, uint32_t ms) { MockInterfaceSimulator *self = (MockInterfaceSimulator *)handle; } +}; + +class st7735SimTest : public DisplayTest { + public: + MockInterfaceSimulator mock_ = MockInterfaceSimulator(); + St7735Context ctx_; + LCD_Interface interface_; + + st7735SimTest() { + interface_ = { + .handle = &mock_, + .spi_write = MockInterfaceSimulator::spi_write, + .spi_read = NULL, + .gpio_write = MockInterfaceSimulator::gpio_write, + .reset = MockInterfaceSimulator::reset, + .set_backlight_pwm = MockInterfaceSimulator::set_pwm, + .timer_delay = MockInterfaceSimulator::sleep_ms, + }; + lcd_st7735_init(&ctx_, &interface_); + lcd_st7735_startup(&ctx_); + } +}; + +TEST_F(st7735SimTest, draw_rectangles) { + Result res = lcd_st7735_clean(&ctx_); + EXPECT_EQ(res.code, 0); + size_t increment = 20; + uint32_t rgb = 0x0000FF; + + { + LCD_rectangle rec{.origin = {.x = 0, .y = 0}, .width = DisplayWidth, .height = 10}; + auto draw_horizontal = [&](uint32_t color) { + rec.origin.y += increment; + res = lcd_st7735_fill_rectangle(&ctx_, rec, color); + EXPECT_EQ(res.code, 0); + }; + + draw_horizontal(rotate_left(rgb)); + draw_horizontal(rotate_left(rgb)); + draw_horizontal(rotate_left(rgb)); + draw_horizontal(rotate_left(rgb)); + draw_horizontal(rotate_left(rgb)); + } + { + LCD_rectangle rec{.origin = {.x = 10, .y = 0}, .width = 10, .height = DisplayHeight}; + auto draw_vertical = [&](uint32_t color) { + rec.origin.x += increment; + res = lcd_st7735_fill_rectangle(&ctx_, rec, color); + EXPECT_EQ(res.code, 0); + }; + + draw_vertical(rotate_left(rgb)); + draw_vertical(rotate_left(rgb)); + draw_vertical(rotate_left(rgb)); + draw_vertical(rotate_left(rgb)); + draw_vertical(rotate_left(rgb)); + draw_vertical(rotate_left(rgb)); + + EXPECT_EQ(res.code, 0); + } + + std::string filename = make_temp_filename(); + mock_.simulator.png(filename); + compare_img(filename, GET_GOLDEN_FILE()); +} + +#include +#include +TEST_F(st7735SimTest, draw_text) { + Result res = lcd_st7735_clean(&ctx_); + EXPECT_EQ(res.code, 0); + + std::string ascii = ""; + for (int i = 32; i <= 126; ++i) { + if (std::isprint(static_cast(i))) { + ascii += static_cast(i); + } + } + size_t font_h(0), rows(0), columns(0), ascii_index(0); + uint32_t bg(0xff), fg(0x00); + LCD_Point pos{.x = 0, .y = 0}; + + auto set_font = [&](const Font *font) { + res = lcd_st7735_set_font(&ctx_, font); + EXPECT_EQ(res.code, 0); + font_h = ctx_.parent.font->height; + rows = DisplayHeight / ctx_.parent.font->height; + columns = DisplayWidth / ctx_.parent.font->descriptor_table->width; + ascii_index = 0; + }; + + auto draw_text = [&]() { + res = lcd_st7735_set_font_colors(&ctx_, bg, fg); + EXPECT_EQ(res.code, 0); + std::string print = ascii.substr(ascii_index, columns); + res = lcd_st7735_puts(&ctx_, pos, print.c_str()); + EXPECT_EQ(res.code, print.size()); + ascii_index += columns; + pos.y += font_h; + bg = bg << 8 | bg >> (32 - 8); // Rotate left + fg = bg ^ 0xffffff; + }; + + set_font(&lucidaConsole_12ptFont); + do { + draw_text(); + } while (ascii_index < ascii.size()); + + set_font(&lucidaConsole_10ptFont); + do { + draw_text(); + } while (pos.y < DisplayHeight - font_h); + + std::string filename = make_temp_filename(); + mock_.simulator.png(filename); + compare_img(filename, GET_GOLDEN_FILE()); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); From 7a2a48d033174b6f888dc28f09b3dae79540ee08 Mon Sep 17 00:00:00 2001 From: Douglas Reis Date: Mon, 28 Apr 2025 17:58:49 +0100 Subject: [PATCH 8/8] [CI] introduce a CI job using github actions Signed-off-by: Douglas Reis --- .github/workflows/cmake.yml | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/cmake.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..1852c6f --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,41 @@ +name: CMake + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + BUILD_TYPE: Release + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt install software-properties-common + sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt install cmake gcc-13 g++-13 + + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 60 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 60 + sudo update-alternatives --config gcc + sudo update-alternatives --config g++ + + - name: Linting + run: find -name "*.cc" -o -name "*.c" -o -name "*.hh" -o -name "*.h" | xargs clang-format -n + + - name: Build + run: | + cmake -B build -S ./ && cmake --build build + + - name: Run st7735_driver_test + run: | + ./build/tests/st7735_driver_test +