ESP-IDF Development
ESP-IDF (Espressif IoT Development Framework) is the official SDK for ESP32-S3 development. It gives direct access to FreeRTOS, the hardware abstraction layer, and all peripheral drivers without the abstractions that Arduino layers on top. For production firmware, custom peripheral drivers, or projects requiring fine-grained control over memory allocation, interrupt priorities, and task scheduling, ESP-IDF is the recommended development path. The trade-off is a steeper learning curve and more explicit initialization code, but the payoff is deterministic behavior and full visibility into what every line of code does to the hardware.
Recommended Version
Section titled “Recommended Version”ESP-IDF v5.1.4 or later is required for the ESP32-S3-Touch-LCD-4.3. Version 5.5.2 is specifically recommended because it includes fixes for RS-485 half-duplex UART timing edge cases and improved TWAI (CAN) driver stability under sustained bus load. The Waveshare demo code targets v5.1.x as a baseline, but all examples compile and run correctly on v5.5.x with no modifications.
Install ESP-IDF following the official Espressif documentation, or use the VS Code ESP-IDF Extension which bundles the toolchain, CMake, and Ninja in a self-contained environment.
Project Structure
Section titled “Project Structure”Each Waveshare demo follows the standard ESP-IDF component-based project layout. Understanding this structure is important because it determines how the build system resolves dependencies, applies Kconfig options, and links components together.
project/├── main/│ ├── CMakeLists.txt # Registers main as a component│ ├── main.c # Application entry point (app_main)│ ├── Kconfig.projbuild # Project-level menuconfig options│ └── idf_component.yml # Component Manager dependencies├── components/ # Optional local components├── CMakeLists.txt # Top-level project CMake file├── sdkconfig # Generated by menuconfig└── sdkconfig.defaults # Default overrides applied on clean buildThe idf_component.yml file is how ESP-IDF v5.x declares external dependencies. When you run idf.py build for the first time, the IDF Component Manager reads this manifest and downloads the specified component versions into a managed_components/ directory. This is analogous to a package manager lock file and ensures reproducible builds across machines.
Critical sdkconfig Settings
Section titled “Critical sdkconfig Settings”The ESP32-S3-Touch-LCD-4.3 requires several non-default configuration options to enable its hardware features. These settings belong in sdkconfig.defaults so they are applied automatically on a fresh build, or can be set interactively through idf.py menuconfig.
# CPU and memoryCONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=yCONFIG_FREERTOS_HZ=1000CONFIG_SPIRAM=yCONFIG_SPIRAM_MODE_OCT=yCONFIG_SPIRAM_SPEED_80M=yCONFIG_SPIRAM_USE_MALLOC=y
# FlashCONFIG_ESPTOOLPY_FLASHMODE_QIO=yCONFIG_ESPTOOLPY_FLASHFREQ_80M=yCONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
# Partition tableCONFIG_PARTITION_TABLE_CUSTOM=yCONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"# Requires CONFIG_IDF_EXPERIMENTAL_FEATURES=yCONFIG_IDF_EXPERIMENTAL_FEATURES=y
# 120 MHz PSRAM clock -- measurably faster display renderingCONFIG_SPIRAM_SPEED_120M=y
# Move instructions and read-only data to PSRAM# Frees internal SRAM for DMA buffers and stack spaceCONFIG_SPIRAM_FETCH_INSTRUCTIONS=yCONFIG_SPIRAM_RODATA=y
# 64-byte cache line improves PSRAM throughputCONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
# Compiler optimization for speed over sizeCONFIG_COMPILER_OPTIMIZATION_PERF=yThe CONFIG_SPIRAM_USE_MALLOC=y setting is essential. It allows standard malloc() calls to fall through to PSRAM when internal SRAM is exhausted, which is how LVGL’s frame buffers get allocated into the larger external memory without requiring explicit PSRAM allocation calls throughout the codebase.
Demo Projects
Section titled “Demo Projects”Waveshare provides eight ESP-IDF demo projects that exercise each subsystem. They parallel the Arduino demos in functionality but expose the underlying ESP-IDF APIs directly.
-
01_I2C_Test — I2C bus scanner with a console REPL interface. Reports all connected devices and their addresses. The REPL allows sending raw I2C transactions for debugging, which is useful when bringing up custom I2C peripherals on the expansion header.
-
02_RS485_Test — UART echo over RS-485 using the
uart_driverAPI. Pin numbers, baud rate, and port number are configurable through Kconfig rather than hardcoded, so you can change them viaidf.py menuconfigwithout editing source code. The demo configures half-duplex mode with automatic RTS control for TX/RX direction switching. -
03_SD_Test — SD card mount and file I/O using the VFS FAT filesystem layer. Because the SD card chip select pin routes through the CH422G IO expander, this demo initializes the expander via raw I2C writes before attempting SPI communication with the card. The VFS integration means standard
fopen()/fwrite()/fread()calls work against the mounted card. -
04_Sensor_AD — ADC reading using ESP-IDF’s
adc_oneshotdriver, which replaced the legacyadc1_get_raw()API in v5.x. Reads and prints calibrated voltage values from the onboard analog input. -
05_UART_Test — Basic UART echo task running on a dedicated FreeRTOS thread. Demonstrates the event-driven UART pattern using
uart_driver_install()with a ring buffer anduart_read_bytes()in a loop. -
06_TWAItransmit — CAN bus transmitter using the TWAI driver. Configures the peripheral for 50 kbit/s with alert monitoring for bus errors, arbitration loss, and TX completion. The alert mechanism uses FreeRTOS event groups, which is the idiomatic ESP-IDF pattern for responding to peripheral events without polling.
-
07_TWAIreceive — CAN bus receiver with the TWAI driver. Installs the driver, starts the peripheral, and reads incoming frames in a loop with configurable timeout. Prints message ID, DLC, and data bytes for each received frame.
-
08_lvgl_Porting — Complete LVGL integration with the RGB LCD panel. This is the reference implementation for display applications and includes multiple rendering modes (normal, full refresh, direct mode) selectable via preprocessor defines. Supports display rotation at 0, 90, 180, and 270 degrees with software pixel remapping and touch coordinate transformation. The initialization sequence demonstrates the full hardware bring-up: IO expander, backlight, RGB panel, touch controller, and LVGL driver registration.
Build and Flash
Section titled “Build and Flash”The ESP-IDF build system uses CMake and Ninja under the hood, but the idf.py wrapper script provides a consistent interface for all operations.
# Set the target chip (only needed once per project directory)idf.py set-target esp32s3
# Open the interactive configuration menu (optional)idf.py menuconfig
# Build the projectidf.py build
# Flash and open the serial monitor in one commandidf.py -p /dev/ttyACM0 flash monitorIf you are using the USB-UART bridge (CH343P), the device typically appears as /dev/ttyACM0 on Linux or /dev/cu.usbmodem* on macOS. For native USB CDC (programming through the ESP32-S3’s built-in USB peripheral), the device path may differ — check dmesg or your system’s device manager.
Component Manager
Section titled “Component Manager”ESP-IDF v5.x uses the IDF Component Manager to resolve and fetch external dependencies. Each project’s main/idf_component.yml declares the components it needs, and the build system downloads them automatically on the first build.
# Example idf_component.ymldependencies: espressif/esp_lcd_touch_gt911: "^1.0.7" lvgl/lvgl: "~8.4.0" espressif/esp_lvgl_port: "^1.4.0"Components are cached in ~/.espressif/components/ and versioned, so subsequent builds resolve instantly. For air-gapped or reproducible builds, you can vendor dependencies by copying the managed_components/ directory into version control and setting IDF_COMPONENT_OVERWRITE_MANAGED_COMPONENTS=0.
Kconfig Customization
Section titled “Kconfig Customization”Several Waveshare demos expose project-specific configuration options through Kconfig, accessible via idf.py menuconfig. Look for Kconfig.projbuild in each demo’s main/ directory — this file defines the menu entries that appear under the project’s name in the configuration tree.
# Example Kconfig.projbuild for RS-485 demomenu "RS485 Configuration" config RS485_UART_PORT_NUM int "UART port number" default 1 config RS485_BAUD_RATE int "Baud rate" default 115200 config RS485_RXD int "RXD GPIO number" default 15 config RS485_TXD int "TXD GPIO number" default 16endmenuThis pattern keeps pin assignments and protocol parameters out of source code, making it straightforward to adapt demos for different wiring configurations or baud rates without touching C files. The selected values are written to sdkconfig and exposed as CONFIG_* preprocessor macros during compilation.
Debugging with OpenOCD
Section titled “Debugging with OpenOCD”For hardware-level debugging, the ESP32-S3 supports JTAG through its USB peripheral. ESP-IDF integrates with OpenOCD for GDB-based debugging, breakpoints, and memory inspection.
# Start OpenOCD (in a separate terminal)idf.py openocd
# Launch GDB and connect to OpenOCDidf.py gdbThis requires the board’s native USB port (not the CH343P UART bridge) and the USB-JTAG/Serial mode enabled in eFuse. The VS Code ESP-IDF Extension provides a graphical debugging interface that wraps these commands, including variable inspection, call stack visualization, and peripheral register views.
Next Steps
Section titled “Next Steps”With the build environment working and demo code running, the LVGL UI Development guide covers display-specific optimization for frame buffer allocation, anti-tearing modes, and rendering performance. The Pin Reference page maps every GPIO to its function on this board, which is essential when adding custom peripherals or rerouting signals. For projects that communicate over the CAN bus or RS-485, the interface guides provide protocol-level detail and wiring diagrams beyond what the demo code covers.