Skip to content
juliannojunglePublic

About

Low level GUI system. Includes geometrical primitives, text and image files (png and bmp). Renders on GC9A01 LCDs and desktop simulator. Runs on RP2040 (Raspberry Pi Pico), ESP32 and desktop (Linux/ Windows+WSL).

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

119 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

gui.ll (GUI Low Level)

πŸ¦– Part of dot-ll-collection

⚠️ This project is under active development. The documentation is growing along the project as it's a work-in-progress.

A lightweight, bare-metal GUI library for embedded systems. Draws visual elements directly on a GC9A01 round LCD (240Γ—240) and renders PNG images streamed from an SD card β€” all with minimal memory footprint, no OS, no heap allocations in the critical path.

Currently supports RP2040 (Raspberry Pi Pico), ESP32, and a Simulator (native desktop via SDL2) from a single codebase.


✨ Features

  • 🎨 Direct LCD rendering β€” pixels, lines, circles, rectangles, and text written straight to the display without a framebuffer
  • πŸ–ΌοΈ PNG from SD card β€” PNG files decoded on-the-fly from an SD card and streamed directly to the LCD, row by row
  • π–£― Minimalist QR Code model 2 generation β€” generator written exclusively for displaying short ASCII URLs
  • πŸ”Œ Multi-platform β€” same application code compiles for RP2040, ESP32, and a native desktop Simulator; platform differences are fully encapsulated in the HAL layer
  • πŸ–₯️ Desktop simulator β€” renders the LCD output in an SDL2 window for rapid iteration without hardware
  • ⚑ Bare-metal β€” no RTOS, no dynamic memory allocator required, runs on the metal
  • πŸ“¦ Self-contained β€” zlib, libpng, and FatFS are bundled as submodules; no package manager needed
  • πŸ”§ CMake-native β€” integrates cleanly as a CMake component into any parent project

πŸ–₯️ Supported Platforms

Platform RP2040 ESP32 Simulator
Pinout RP2040 LCD 1.28 ESP32-S3 LCD 1.28 N/A (desktop)
Device RP2040-LCD-1.28 ESP32-S3-LCD-1.28 Any Linux/WSL desktop
Toolchain arm-none-eabi-gcc + Pico SDK xtensa-esp32-elf-gcc + ESP-IDF Host gcc + SDL2
Output .uf2 .bin Native ELF executable

πŸ›’ B.O.M. (Bill Of Materials)

  • One of the target devices:
  • MicroSD to SD card adapter (for easy soldering)
  • MicroSD card
  • Some nice wires (30 AWG recommended)
  • 2 x 10K resistors (for pull-up)
  • 1 x 10uF capacitor (for decoupling)
  • (Optional) 2 x 1.27mm 2x20Pin header (for connecting SD to target device)

πŸ”Œ Wiring

The below table shows connections in a way that you can create a header connector/shield board to swap between the target devices.
⚠️ Connect it to the RP2040 or to the ESP32. You're NOT supposed to wire RP2040 and ESP32 together.

RP2040 ESP32 R1(10K) R2(10K) C1(10uF) SDCARD LCD*
GP0 (MISO) GP46 (MISO) MISO pin7 (DAT0/MISO)
GP1 (CS) GP45 (CS) CS pin1 (DAT3/CS)
GP2 (SCK) GP42 (SCK) pin5 (CLK/SCK)
GP3 (MOSI) GP41 (MOSI) pin2 (CMD/MOSI)
GP5 GP39 pin3 (VSS1/GND)
GND GND GND pin6 (VSS2/GND) GND
3V3 3V3 3V3 3V3 3V3 pin4 (VDD/3V3) VCC (3V3)
GP8 GP8 DC
GP9 GP9 CS
GP10 GP10 CLK
GP11 GP11 DIN (MOSI)
GP12 GP12 RST
GP25 GP40 BL

* The LCD connection is internally made on these devices, GP pins just for reference.


πŸ“š LCD Driver Datasheet (reference)

The GC9A01A LCD driver datasheet is versioned in Documentation/ in two complementary forms:

File Role
Documentation/GC9A01A.md Primary / quick data source. Markdown conversion (mostly used for quick ai insights) β€” search here first for registers, command opcodes, parameter bit fields, defaults, memory layout and timings. Section 6 (commands) is a faithful reproduction with bit-layout tables and verbatim Description/Restriction.
Documentation/GC9A01A.pdf Secondary source for disambiguation. The original 192-page PDF β€” consult it to resolve any doubt, and for graphical waveform/timing diagrams (not extracted into the markdown).

Figures and diagrams are described in text in the markdown, not embedded as images. When precision matters, the PDF is the ground truth.


πŸ“‹ Software Requirements

  • Host: Linux or Windows 10 PC with WSL 2 (Windows Subsystem Linux) - Ubuntu is recommended β€” code edited on Windows, compiled inside WSL
  • IDE: Kiro or VSCode
  • RP2040: arm-none-eabi-gcc, Pico SDK (installed via Setup: RP2040 toolchain)
  • ESP32: ESP-IDF β‰₯ 5.x, xtensa toolchain, espflash (installed via Setup: ESP32 toolchain)
  • Simulator: libsdl2-dev, gdb, dosfstools, mtools (installed via Setup: Simulator toolchain)
  • CMake β‰₯ 3.16

The preferred IDE is Kiro, which provides agent-assisted development with project-aware context via AGENTS.md. The project also works fully with VS Code β€” all tasks and settings are configured in .vscode/.


πŸ—οΈ Architecture

gui.ll/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Sample.c                    # Entry point β€” app_entry()
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ Helper/
β”‚   β”‚   β”‚   └── Trigonometry.c      # Integer Q16.16 cos/sin LUT (used by curved text)
β”‚   β”‚   β”œβ”€β”€ Platform/ESP32/         # ESP-IDF component registration only
β”‚   β”‚   β”œβ”€β”€ Driver/GC9A01/          # LCD driver
β”‚   β”‚   β”œβ”€β”€ LCD/1in28/              # Display layer: LCDSetup (init) + LCDRenderer (blit)
β”‚   β”‚   β”œβ”€β”€ LCD/Simulator/          # SDL2 display layer: same API, renders to window
β”‚   β”‚   β”œβ”€β”€ GUI/                    # Canvas / drawing primitives (Canvas.c/.h)
β”‚   β”‚   └── Fonts/                  # Bitmap font data
β”‚   └── Dependency/
β”‚       β”œβ”€β”€ hal.ll/                 # GPIO, SPI, PWM, timing, board pinout (downloaded)
β”‚       β”œβ”€β”€ fs.ll/                  # SD card file I/O + FatFS (downloaded)
β”‚       β”œβ”€β”€ libpng/                 # libpng
β”‚       └── zlib/                   # zlib (libpng dependency)
β”œβ”€β”€ Documentation/
β”‚   β”œβ”€β”€ GC9A01A.pdf                 # LCD driver datasheet (original PDF)
β”‚   β”œβ”€β”€ GC9A01A.md                  # Datasheet as markdown (primary data source)
β”‚   └── Image/                      # Pinout reference images
└── Toolchain/
    β”œβ”€β”€ RP2040/Setup.sh
    β”œβ”€β”€ ESP32/Setup.sh
    └── Simulator/Setup.sh

Platform abstraction

Hardware access goes through hal.ll, which owns GPIO, SPI, PWM, UART, timing, the RTC and the board pinout. The drawing code calls abstract functions β€” DigitalWrite, SPIWriteByte, Delay, etc. β€” declared in hal.ll's per-platform HAL.h; its contract puts the active platform folder on the include path at build time, so #include "HAL.h" resolves to the correct implementation with no #ifdef scattered through the code.

What stays platform-specific here is the display layer, selected by folder rather than by preprocessor: src/lib/LCD/Simulator on the desktop, src/lib/LCD/1in28 on both hardware targets.

Entry point

void app_entry(void) { /* your application code */ }

#ifdef ESP_PLATFORM
void app_main(void) { app_entry(); }  // ESP-IDF entry
#else
int main(void) { app_entry(); return 0; }  // RP2040 / standard entry
#endif

🎨 Drawing Primitives

The Canvas module writes directly to the LCD without requiring a full framebuffer in RAM.

// Canvas setup
CanvasNewImage(buffer, 240, 240, ROTATE_0);
CanvasClear(BLACK);

// Geometry
CanvasDrawPoint(120, 120, RED, PIXEL_SIZE_2X2, PIXEL_FILL_STYLE_AROUND);
CanvasDrawLine(0, 0, 240, 240, WHITE, PIXEL_SIZE_1X1, LINE_STYLE_SOLID);
CanvasDrawRectangle(10, 10, 100, 80, BLUE, PIXEL_SIZE_1X1, DRAW_FILL_STYLE_EMPTY);
CanvasDrawCircle(120, 120, 60, GREEN, PIXEL_SIZE_1X1, DRAW_FILL_STYLE_FULL);

// Text
CanvasDrawText(20, 20, "Hello!", &Font16, WHITE, BLACK);
CanvasDrawNum(20, 50, 3.14, &Font12, 2, YELLOW, BLACK);
CanvasDrawTime(10, 200, &dateTime, &Font12, WHITE, BLACK);

// Curved text β€” placed on a circle (radius, startAngle in whole degrees, 0Β° = 3 o'clock,
// increasing clockwise) with the glyph kept tangent to the border. A single character or a
// whole string (which advances the angle per glyph automatically):
CanvasDrawCurvedChar('A', 120, 120, 100, 270, TEXT_ORIENTATION_INWARDS, &Font20, WHITE, TRANSPARENT);
CanvasDrawCurvedText("Hello, World!", 120, 120, 100, 250, TEXT_ORIENTATION_OUTWARDS, &Font20, WHITE, TRANSPARENT);

Available colors: WHITE, BLACK, RED, GREEN, BLUE, YELLOW, CYAN, MAGENTA, GRAY, and more. Pixel sizes: PIXEL_SIZE_1X1 through PIXEL_SIZE_8X8. Line styles: LINE_STYLE_SOLID, LINE_STYLE_DOTTED. Fill modes: DRAW_FILL_STYLE_EMPTY, DRAW_FILL_STYLE_FULL. Curved-text orientation: TEXT_ORIENTATION_INWARDS (advances clockwise) or TEXT_ORIENTATION_OUTWARDS (glyph flipped, advances counter-clockwise) β€” ideal for labels following the round display's rim. Pass TRANSPARENT as the background to draw only the glyph pixels over an existing image.


πŸ–ΌοΈ PNG Rendering from SD Card

PNG files are decoded with libpng into the canvas RAM buffer, then blitted to the display in a single transfer β€” so the image appears instantly without visible row-by-row scanning. Memory usage is bounded to a single decoded row at a time; the full image never needs to fit in RAM beyond the canvas texture.

UINT8 texture[240 * 240 * 2]; // RGB565 framebuffer
FIL file;

CanvasNewImage(texture, 240, 240, ROTATE_0);
CanvasSetScale(65);

if (MountSdCard()
    && SelectActiveDrive()
    && OpenFile(&file, "image.png")) {
    CanvasDrawPng(&file);
    CloseFile(&file);
}

UnMountSdCard();

// Optionally draw text/shapes over the decoded image before blitting:
CanvasDrawText(20, 20, "Hello!", &Font16, WHITE, TRANSPARENT);

LCDRenderTexture(texture);

π–£― Minimalist QR Code generation

Minimalist QR Code Model 2 generator, designed exclusively for displaying short ASCII URLs. Supports versions 4 and 5, error correction level M, Byte mode, and automatic selection of the best of the 8 available masks. The output is a 1-bit-per-module bitmap, completely independent of the rendering layer.

Limitations

  • ASCII input only, with a maximum length of 80 bytes.
  • QR Code versions 4 and 5 only.
  • Byte mode only.
  • Error correction level M only.
  • QR mask is selected automatically; manual mask selection is not supported.
  • Maximum QR matrix size: 37 Γ— 37 modules.
  • Maximum output bitmap size: 172 bytes.
  • No dynamic memory allocation (malloc).
  • No external dependencies.
  • No large precomputed lookup tables.

Version selection

URL length QR version Matrix
0–62 bytes Version 4 33 Γ— 33
63–80 bytes Version 5 37 Γ— 37

Memory usage

The output bitmap requires 172 bytes in the worst case (37 Γ— 37 modules, 1 bit per module). QR generation uses approximately 800 bytes of temporary stack memory and does not use dynamic memory allocation.


πŸ“ Using gui.ll in Your Own Project

The CMakeLists.txt at the root is a functional standalone example. To integrate gui.ll as a component in your own CMake project, include the platform-specific fragments directly:

set(PLATFORM_NAME "RP2040")  # or "ESP32"

# RP2040 only β€” include before add_executable
include(path/to/gui.ll/src/lib/Platform/RP2040/PreExecutable.cmake)

add_executable(my_app src/main.c)

# RP2040 only β€” include after add_executable
include(path/to/gui.ll/src/lib/Platform/RP2040/PostExecutable.cmake)

For ESP32, set EXTRA_COMPONENT_DIRS to point at src/lib/Platform/ESP32 before calling project():

set(EXTRA_COMPONENT_DIRS "path/to/gui.ll/src/lib/Platform/ESP32")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(my_app)

Your application entry point mirrors Sample.c β€” implement app_entry() and let the platform macro route main or app_main automatically.


πŸš€ Getting Started

1. Clone with submodules

git clone --recurse-submodules --shallow-submodules https://github.com/juliannojungle/gui.ll.git

This single command clones the repository and all bundled dependencies (FatFS, libpng, zlib) in one step. No separate dependency installation needed.

2. Set up the toolchain

Before building for the first time, install the required toolchain for your target platform. The recommended way is via the built-in tasks.

In Kiro or VS Code β€” open the Command Palette (Ctrl+Shift+P) and select Tasks: Run Task, then choose:

  • Setup: RP2040 toolchain in WSL β€” installs arm-none-eabi-gcc and Pico SDK
  • Setup: ESP32 toolchain in WSL β€” installs ESP-IDF, xtensa toolchain, Rust, and espflash
  • Setup: Simulator toolchain in WSL2 β€” installs libsdl2-dev and gdb

These scripts are idempotent β€” safe to run again if you need to repair an installation.

3. Build

Once the toolchain is ready, build using the tasks:

In Kiro or VS Code β€” open the Command Palette (Ctrl+Shift+P) β†’ Tasks: Run Task:

Task Description
Build: Full RP2040 Clean CMake configure + make for RP2040
Build: Full ESP32 Clean idf.py build for ESP32
Build: Simulator Full Clean CMake configure + make for Simulator
Build: Simulator Incremental Reuses build dir if Simulator; else clean full
Build: Incremental Auto-detects platform, runs make or ninja
Copy UF2 to Windows Copies .uf2 to C:\temp for drag-and-drop flashing

Alternatively, trigger the default build task directly with Ctrl+Shift+B (runs Build: Incremental).


πŸ“„ License

See LICENSE.


@juliannojungle, 2026

About

Low level GUI system. Includes geometrical primitives, text and image files (png and bmp). Renders on GC9A01 LCDs and desktop simulator. Runs on RP2040 (Raspberry Pi Pico), ESP32 and desktop (Linux/ Windows+WSL).

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Contributors

Languages