User Guide

LVGL Studio Documentation

Everything you need to design, automate, and flash your ESP32 LCD project — from first widget to working firmware.

Getting Started

LVGL Studio is a visual IDE for ESP32 LCD projects. You design the interface, build the logic, generate C code, and flash it — all from your browser.

System Requirements

  • Google Chrome or Microsoft Edge (Web Serial API required)
  • ESP32 or ESP32-S3 board with an LCD panel
  • USB cable (data, not charge-only)
  • No local software needed — fully browser-based

Quick Start (5 steps)

  1. 1Register for a free account at lcdultrapro.com/register
  2. 2Log in and click "Open Editor" from your Dashboard
  3. 3Select your LCD panel from the top dropdown
  4. 4Drag widgets from the left palette onto the canvas
  5. 5Click "Generate Code" → download the ZIP → open in Arduino IDE → Upload

UI Editor

The canvas shows a pixel-accurate preview of your LCD at the selected resolution. Everything you see is exactly what appears on the hardware.

Canvas Controls

Drag widgetMove it anywhere on canvas
Resize handlesDrag corners/edges to resize
Ctrl + clickMulti-select widgets
Ctrl + C / VCopy & paste
Ctrl + Z / YUndo / Redo
Right-clickContext menu (bring to front, etc.)
Delete keyRemove selected widget
Arrow keysNudge 1 px at a time

Props Panel (right sidebar)

Select any widget to open its property panel. You can set position, size, font, colors, border, shadow, opacity, and more. Changes appear on the canvas instantly.

Widget IDs must be unique and contain only letters, numbers, and underscores (no spaces or accented characters). The ID becomes the C variable name.

Multi-screen Support

  1. 1Click the "+" button in the screen tabs bar at the top
  2. 2Give each screen a name (e.g. Screen_Main, Screen_Settings)
  3. 3Switch screens by clicking their tab
  4. 4Use the Navigate action in workflows to switch at runtime

Workflow Engine

The Workflow Editor lets you build logic without writing C code. Connect nodes with wires — the engine compiles everything to valid LVGL 8.4 firmware.

Basic Rules

  • Every workflow must start with a Trigger node (touch, timer, or event)
  • Connect the right side output port of one node to the left side input of another
  • Multiple nodes can chain together in sequence
  • Condition and Switch nodes have multiple output branches
  • One project can have many independent workflows per screen

Palette Categories

Triggers
Start your workflow
Logic
Conditions, loops, switch
Data
Variables, math, string
Embedded
NVS, events, sensors
Actions
GPIO, UI, WiFi
Utility
Delay, comment

Trigger Nodes

Every workflow starts with one of these. They fire when something happens: user touch, timer tick, or an event from another workflow.

Trigger (Touch / Widget Event)

Fires when the user interacts with an LCD widget (tap, swipe, value change). Select the target widget and the event type (CLICKED, VALUE_CHANGED, etc.).

Example: [Trigger: btn_start / CLICKED] → [Action: GPIO Write pin 26 HIGH]
⏱️
Timer Trigger

Fires automatically every N milliseconds. Use for polling sensors, updating the display, or any periodic task. Set Repeat to 0 for infinite.

Example: [Timer: 1000ms] → [Action: Read BME280 → lbl_temp]
👂
Event Listener

Fires when another workflow (on any screen) sends a named event via Emit Event. Perfect for cross-screen communication.

Example: [Event Listener: 'alarm_on'] → [Action: Show MsgBox 'Warning!']

Logic Nodes

⚖️
Condition (If / Else)

Evaluates a condition and routes the workflow to the True or False output. Compare a variable, widget value, or GPIO level against a threshold.

Example: [Condition: temperature > 40] → True: [Action: LED RED] / False: [Action: LED GREEN]
🔁
Loop (For loop)

Repeats the connected Body branch N times. The loop counter is available as a C expression for use inside the loop. Done output fires after the last iteration.

Example: [Loop: 10x] → Body → [Action: Serial send 'ping']
🔀
Switch (Multi-branch)

Routes to different branches based on a variable's integer value. Like C switch/case. Supports 2–8 cases plus a Default branch.

Example: [Switch: mode] → Case 0: Menu / Case 1: Measure / Default: Settings

Data Nodes

📦
Variable (local)

Stores a value in a named variable that lives on the current screen. Lost when switching screens. Sources: fixed number, slider value, ADC pin, C expression.

Example: [Variable: temp = ADC pin 34] → [Condition: temp > 2000]
🌐
Global Variable

Same as Variable but persists across all screens. Any screen's workflow can read or write it. Use for app-wide state like user level, current mode, etc.

Example: [Global Variable: user_level = 1] (Screen1) → read on any screen
🧮
Math

Performs arithmetic on two values and stores the result. Operations: + − × ÷ % min max |A|. Division is automatically guarded against division-by-zero.

Example: [Math: fahrenheit = celsius * 9] → [Math: fahrenheit = fahrenheit / 5 + 32]
📐
Clamp / Map

CLAMP: keeps a value within [min, max]. MAP: re-scales a value from one range to another (like Arduino map()). Essential for ADC → display value conversion.

Example: [Map: adc_raw 0-4095 → percent 0-100] → [Action: Set Bar value = percent]
🎲
Random

Generates a random integer in [min, max] using the ESP32 hardware RNG (true random, not pseudo-random). Stores the result in a variable.

Example: [Random: led_id 0-7] → [Action: GPIO Write led_id]
📝
String Format

Fills a text template with variable values and writes the result to an LCD Label. Template syntax: {varname} is replaced with the variable's current value.

Example: Template: 'T={temp}°C H={hum}%' → label_display

Embedded Nodes

💾
NVS Storage (Flash)

Reads/writes ESP32 Non-Volatile Storage (survives power-off). Uses the Arduino Preferences library. Namespace: group name (max 15 chars). Key: value name (max 15 chars).

Example: [NVS Read: settings/brightness → var: bright] → [Action: Set Backlight = bright]
📡
Emit Event

Sends a named event to the global event bus. Any Event Listener node on any screen can receive it. Optionally attach a numeric payload.

Example: [Condition: temp > 80 TRUE] → [Emit Event: 'overheat']

Code Generation

When your design and workflow are ready, click the Generate Code button in the toolbar.

What is generated?

  • ui_screen_{name}.c — LVGL widget initialization for each screen
  • ui_screen_{name}.h — header file exporting the screen init function
  • ui_workflows.c — all workflow callbacks (timers, events, GPIO)
  • ui_helpers.c — shared helper functions
  • main.ino — the Arduino main file with panel initialization
  • panel driver files — hardware-specific LCD init code

Using in Arduino IDE

  1. 1Download the generated .zip file
  2. 2Extract it anywhere on your computer
  3. 3Opening the main.ino file opens the whole project in Arduino IDE
  4. 4Install required libraries: LVGL 8.4, Arduino_GFX, and any sensor libraries
  5. 5Select your board: ESP32 Dev Module or ESP32-S3
  6. 6Click Upload

All generated code is LVGL 8.4 compatible. Make sure you install LVGL 8.x (not 9.x) from the Library Manager.

Flash to Device

LVGL Studio can flash compiled firmware directly to your ESP32 over USB — no Arduino IDE needed for the flash step.

Requirements

  • Google Chrome or Edge (Firefox is not supported for Web Serial)
  • USB cable connected to your ESP32
  • ESP32 in download mode (usually automatic, or hold BOOT while pressing RESET)

Steps

  1. 1Click the Flash button in the editor toolbar
  2. 2Select your COM port from the browser dialog
  3. 3The editor runs a preflight check (coordinate validation)
  4. 4Firmware uploads automatically — progress bar shown
  5. 5Device resets and your UI appears on the LCD

Mirror Mode

Mirror mode streams a live JPEG snapshot of the canvas to the LCD over USB. Useful for quickly previewing layout changes without a full flash. Click the Mirror button to start/stop.

Tips & Rules

Variable Naming Rules

✅ Correct:  temperature, adc_value, led_state, sensor1
❌ Wrong:    hőmérséklet (accented), adc value (space), 1_data (starts with number)

Variable vs Global Variable

📦 Variable

Lives on one screen only. Lost when you navigate to another screen. Use for temporary calculations.

🌐 Global Variable

Survives screen navigation. Accessible from any screen's workflow. Use for app-wide state.

Common Workflow Recipes

🌡️ Temperature monitor
[Timer: 1000ms]
→ [Action: Read BME280 → temp, hum]
→ [String Format: "🌡️ {temp}°C  💧{hum}%" → lbl_data]
🔆 Brightness save & restore
[Trigger: slider_brightness / VALUE_CHANGED]
→ [Variable: bright = Slider value]
→ [Action: Set Backlight = bright]
→ [NVS Write: settings/brightness = bright]

On startup (Timer 1x):
[Timer: 100ms, once]
→ [NVS Read: settings/brightness → bright]
→ [Action: Set Backlight = bright]
🚨 Cross-screen alarm
Screen1 (sensor screen):
[Timer: 500ms] → [Variable: temp = ADC pin 34]
→ [Condition: temp > 2900] → True → [Emit Event: "alarm"]

Screen2 (dashboard screen):
[Event Listener: "alarm"]
→ [Action: Show MsgBox "WARNING! Overheating!"]
→ [Action: GPIO Write pin 25 HIGH]  ← red LED

Delay Node Warning

Always connect something after the Delay node. The Delay produces a non-blocking lv_timer one-shot — if nothing follows it, the generator may fall back to a blocking delay() which freezes the LCD display.

NVS Key Limits

Namespace and key names must be max 15 characters. Recommended namespaces: settings, calib, state, app.

Event Name Case Sensitivity

Event names are case-sensitive. "Alarm" and "alarm" are different events. Make sure the Emit and Listener nodes use exactly the same name.

Ready to build?

Registration is free. Start your first LCD project now.

Get Started Free