Skip to content

Factory Graphics Module

The factory_graphics module is a minimal, self-contained 2D graphics library used exclusively by the Factory Application. It provides raw pixel-level drawing primitives that write directly to the LCD controller — fully bypassing LVGL and the main firmware’s UI stack — making it suitable for pre-boot factory testing, firmware flashing UIs, and snapshot capture during production verification.

Each supported board has its own copy of gfx.c that adapts the unified public API to the board-specific LCD driver and font size. The API contract is identical across all variants.

Related modules:


Snapshot Output

Fonts (compile-time, per board)

factory_lcd_drivers

factory_graphics (gfx.c)

Factory Application

calls

board-specific

board-specific

board-specific

board-specific

board-specific

board-specific

#ifdef ENABLE_SNAPSHOT

glyph lookup

glyph lookup

glyph lookup

factory_core

(main.c)

draw_header / draw_menu

draw_progress / ...

Public API

gfx_init · gfx_clear

gfx_draw_char · gfx_draw_string

gfx_hline · gfx_vline

gfx_rect · gfx_fill_rect

gfx_flush() wrapper

(internal)

Snapshot Subsystem

gfx_snapshot_begin/end

gfx_snapshot_is_capturing

snap_write() (internal)

Static line_buf[SCREEN_WIDTH]

uint16_t - no heap allocation

st7796_flush()

(SPI)

ili9341_flush()

(SPI)

ili9485_flush()

(RGB)

st7262_flush()

(RGB parallel)

rm68120_flush()

(I80)

st7796_i80_flush()

(I80)

font8x16

(480x272 boards)

font11x21

(320x480 boards)

font12x24

(800x480 boards)

Raw .snap file

on SD card

(RGB565, row-major)

snap2png.py

(factory_tools)

PNG preview image


Each board has its own gfx.c under boards/<board>/Factory/main/. The implementation is identical in structure; only the included LCD driver header and font header differ.

BoardLCD Driver (flush fn)BusFontScreen Size
esp32_3248s035cst7796_flush()SPIfont11x21320 x 480
esp32_3248s035rst7796_flush()SPIfont11x21320 x 480
esp32s3_4827s043cili9485_flush()RGBfont8x16480 x 272
esp32s3_8048_touch_lcd_7st7262_flush()RGB parallelfont12x24800 x 480
esp32s3_8048s043cst7262_flush()RGB parallelfont12x24800 x 480
esp32s3_8048s050cst7262_flush()RGB parallelfont12x24800 x 480
esp32s3_8048s070cst7262_flush()RGB parallelfont12x24800 x 480
esp32s3_bzm_tft35_gt911st7796_flush()SPIfont11x21320 x 480
esp32s3_hmi43v3rm68120_flush()I80board-specificboard-specific
esp32s3_zx3d50ce02s_usrc_4832st7796_i80_flush()I80board-specificboard-specific
pibot_pendant_v1_0ili9341_flush()SPIboard-specific240 x 320

Note: SCREEN_WIDTH and SCREEN_HEIGHT are defined in the board-local hw_config.h. gfx.c never hard-codes display dimensions — all size references go through these macros.


Per-board boards/board/Factory/main/

includes

includes

includes

calls gfx_*()

SPI / RGB / I80 transfer

gfx.c / gfx.h

LCD driver .c/.h

(st7796 / ili9341 / st7262 / ...)

font bitmap .c/.h

(font11x21 / font8x16 / font12x24)

hw_config.h

SCREEN_WIDTH / SCREEN_HEIGHT

FONT_WIDTH / FONT_HEIGHT

FONT_BYTES_PER_ROW

main.c

(factory_core)

Physical LCD


All functions below are declared in gfx.h and have identical signatures across every board variant.

Initializes the graphics subsystem. Currently a no-op placeholder — snapshot capture is opened on-demand via gfx_snapshot_begin(). Must be called once before any other gfx_* function.


All colors are 16-bit RGB565 in host (native) byte order. The module internally handles byte-swapping for SPI displays before sending to the hardware.

Fills the entire screen with a solid color. Uses the static line_buf and flushes one row at a time to avoid large stack allocations.

ParameterDescription
colorRGB565 fill color (host byte order)

void gfx_draw_char(int x, int y, char c, uint16_t fg, uint16_t bg)

Section titled “void gfx_draw_char(int x, int y, char c, uint16_t fg, uint16_t bg)”

Renders a single ASCII character (0x20-0x7E) at pixel position (x, y). Non-printable characters are replaced with ?. The glyph is rendered into a temporary stack buffer (FONT_WIDTH x FONT_HEIGHT pixels) then flushed in a single LCD write call.

ParameterDescription
x, yTop-left corner of the character cell, in pixels
cASCII character to render
fgForeground (ink) color, RGB565
bgBackground color, RGB565

Clipping: Characters that would extend past the right or bottom edge are clipped to the screen boundary. If x < 0 or y < 0 the character is skipped entirely.


void gfx_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg)

Section titled “void gfx_draw_string(int x, int y, const char *str, uint16_t fg, uint16_t bg)”

Renders a null-terminated string by calling gfx_draw_char() for each character, advancing x by FONT_WIDTH per character. Stops at the right screen edge without wrapping.

ParameterDescription
x, yBaseline position of the first character, in pixels
strNull-terminated ASCII string
fg, bgForeground and background colors, RGB565

void gfx_hline(int x, int y, int w, uint16_t color)

Section titled “void gfx_hline(int x, int y, int w, uint16_t color)”

Draws a horizontal line of width w starting at (x, y). Fills line_buf with the color and issues a single LCD flush. Clipped to screen bounds.

ParameterDescription
x, yStart position
wWidth in pixels
colorRGB565 line color

void gfx_vline(int x, int y, int h, uint16_t color)

Section titled “void gfx_vline(int x, int y, int h, uint16_t color)”

Draws a vertical line of height h starting at (x, y). Issues one single-pixel flush per row. Clipped to screen bounds.

ParameterDescription
x, yStart position
hHeight in pixels
colorRGB565 line color

void gfx_rect(int x, int y, int w, int h, uint16_t color)

Section titled “void gfx_rect(int x, int y, int w, int h, uint16_t color)”

Draws a hollow rectangle outline. Composed internally from four gfx_hline() and gfx_vline() calls: top edge, bottom edge, left edge, right edge.

ParameterDescription
x, yTop-left corner
w, hWidth and height in pixels
colorRGB565 outline color

void gfx_fill_rect(int x, int y, int w, int h, uint16_t color)

Section titled “void gfx_fill_rect(int x, int y, int w, int h, uint16_t color)”

Draws a solid filled rectangle. Fills line_buf with the color once, then flushes row-by-row. Clipped to screen bounds.

ParameterDescription
x, yTop-left corner
w, hWidth and height in pixels
colorRGB565 fill color

The snapshot subsystem is compiled in only when ENABLE_SNAPSHOT is defined at build time. When the macro is absent, gfx_snapshot_begin() and gfx_snapshot_end() return false and gfx_snapshot_is_capturing() returns false — all other drawing functions behave normally.

bool gfx_snapshot_begin(const char *filepath)

Section titled “bool gfx_snapshot_begin(const char *filepath)”

Opens a raw binary file at filepath (must be a path on a mounted filesystem, typically the SD card) and begins intercepting all gfx_flush() calls to mirror pixel data into the file simultaneously with display output.

The file is immediately pre-allocated and zeroed on open so that any region not explicitly redrawn stays black rather than containing garbage data.

ParameterDescription
filepathFull path to the output file, e.g. /sdcard/snap0001.snap

Returns: true on success; false if a capture is already in progress or if the file cannot be created (error logged via ESP_LOGE).

File format written:

Offset 0 : uint32_t width (little-endian)
Offset 4 : uint32_t height (little-endian)
Offset 8 : uint16_t pixels[height][width] native RGB565, row-major

Total file size: 8 + SCREEN_WIDTH x SCREEN_HEIGHT x 2 bytes.

Caller responsibility: After calling gfx_snapshot_begin(), the caller must trigger a complete screen redraw before calling gfx_snapshot_end(). Only draw operations performed in that window are captured.


Closes the snapshot file and stops pixel interception.

Returns: true on success; false if no capture was in progress.


Returns true if a snapshot file is currently open and pixel data is being captured.


Physical DisplayLCD Driver (st7796 / st7262 / ...)gfx.cfactory_core (main.c)Physical DisplayLCD Driver (st7796 / st7262 / ...)gfx.cfactory_core (main.c)loop[per character]gfx_draw_string(x, y, "Hello", fg, bg)gfx_draw_char() - build char_buf on stackgfx_flush(x, y, x1, y1, char_buf, count)st7796_flush() / st7262_flush() / ...SPI / RGB / I80 pixel transfer SD Card .snap fileLCD Drivergfx.cfactory_core (main.c)SD Card .snap fileLCD Drivergfx.cfactory_core (main.c)Caller triggers a full screen redraw.snap file complete on SD cardfactory_tools snap2png.py converts to PNGgfx_snapshot_begin("/sdcard/snap0001.snap")fopen() - write 8-byte header, pre-fill zerosgfx_clear(BLACK)gfx_flush() called per rowst7262_flush(...)snap_write() - fseek to row offset, fwrite native RGB565gfx_draw_string(...)gfx_flush() called per characterst7262_flush(...)snap_write() - fseek to character region, fwritegfx_snapshot_end()fclose()

API call

color = native RGB565

(host byte order)

Byte-swap for SPI wire

swapped = (color >> 8) | (color << 8)

Stored in line_buf / char_buf

gfx_flush() to LCD driver

(big-endian on SPI wire)

snap_write()

un-swap back to native

native = (sw >> 8) | (sw << 8)

Written to .snap file

(native RGB565 LE)

Why two byte-swaps? SPI LCD controllers expect big-endian RGB565 on the wire. The rendering buffers hold pre-swapped data ready for the LCD driver. The snapshot writer receives these same pre-swapped buffers and reverses the swap before storing to disk, producing native little-endian RGB565 that snap2png.py can read directly without any additional transformation.


A single static uint16_t line_buf[SCREEN_WIDTH] is shared across gfx_clear(), gfx_hline(), and gfx_fill_rect(). This eliminates all heap allocation from the hot rendering paths and guarantees predictable memory usage in the constrained factory app environment.

The character draw path uses a local stack buffer of FONT_WIDTH x FONT_HEIGHT pixels (288 bytes maximum for font12x24). All buffers stay within the safe allocation limits for the ESP32 factory environment.

gfx.c has zero dependency on LVGL. This is intentional: the factory app starts before the main application, does not initialize the LVGL scheduler or task, and needs simple, deterministic pixel output with minimal initialization overhead. For the main firmware’s UI system, see display_core.md.

Every drawing primitive validates and clips coordinates before calling gfx_flush(). Coordinates that fall entirely outside [0, SCREEN_WIDTH) x [0, SCREEN_HEIGHT) produce no output without crashing.

long offset = SNAP_HEADER_SIZE + (long)(row * SCREEN_WIDTH + x0) * 2;
fseek(s_snap_file, offset, SEEK_SET);

fseek() is called once per updated row within the drawn region. This random-access write model allows individual draw calls (characters, rectangles) to update only their own region of the file without needing a full framebuffer in RAM.

gfx_snapshot_begin() writes the full SCREEN_WIDTH x SCREEN_HEIGHT x 2 pixel area as zeros using 512-byte chunk writes. This guarantees a complete, valid image even if the caller does not redraw every pixel, and avoids undefined data in unused screen regions.


boards/
├── esp32_3248s035c/Factory/main/gfx.c ST7796 / font11x21
├── esp32_3248s035r/Factory/main/gfx.c ST7796 / font11x21
├── esp32s3_4827s043c/Factory/main/gfx.c ILI9485 / font8x16
├── esp32s3_8048_touch_lcd_7/Factory/main/gfx.c ST7262 / font12x24
├── esp32s3_8048s043c/Factory/main/gfx.c ST7262 / font12x24
├── esp32s3_8048s050c/Factory/main/gfx.c ST7262 / font12x24
├── esp32s3_8048s070c/Factory/main/gfx.c ST7262 / font12x24
├── esp32s3_bzm_tft35_gt911/Factory/main/gfx.c ST7796 / font11x21
├── esp32s3_hmi43v3/Factory/main/gfx.c RM68120 / board-specific font
├── esp32s3_zx3d50ce02s_usrc_4832/Factory/main/gfx.c ST7796 I80 / board-specific font
└── pibot_pendant_v1_0/Factory/main/gfx.c ILI9341 / board-specific font

ENABLE_SNAPSHOT only

gfx.c

gfx.h

public API declarations

hw_config.h

SCREEN_WIDTH, SCREEN_HEIGHT

FONT_WIDTH, FONT_HEIGHT

FONT_BYTES_PER_ROW

Board LCD driver header

st7796.h / ili9341.h / st7262.h / ili9485.h / ...

Font header

font11x21.h / font8x16.h / font12x24.h

provides font_get_glyph()

stdio.h errno.h esp_log.h

string.h - memset for snapshot pre-fill

Header / LibraryRole
gfx.hPublic API declarations (included by main.c and other callers)
hw_config.hScreen and font dimension macros (board-specific values)
st7796.h / ili9341.h / st7262.h / ili9485.hLCD driver flush function declaration
font11x21.h / font8x16.h / font12x24.hGlyph bitmap tables and font*_get_glyph() lookup
<string.h>memset() for snapshot file pre-fill
<stdio.h>, <errno.h>Snapshot file I/O (fopen, fwrite, fseek, fclose)
esp_log.hESP_LOGI / ESP_LOGE / ESP_LOGW snapshot status messages