Skip to content

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.

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.

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 build

The 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.

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 memory
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_FREERTOS_HZ=1000
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_USE_MALLOC=y
# Flash
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
# Partition table
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"

The 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.

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.

  1. 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.

  2. 02_RS485_Test — UART echo over RS-485 using the uart_driver API. Pin numbers, baud rate, and port number are configurable through Kconfig rather than hardcoded, so you can change them via idf.py menuconfig without editing source code. The demo configures half-duplex mode with automatic RTS control for TX/RX direction switching.

  3. 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.

  4. 04_Sensor_AD — ADC reading using ESP-IDF’s adc_oneshot driver, which replaced the legacy adc1_get_raw() API in v5.x. Reads and prints calibrated voltage values from the onboard analog input.

  5. 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 and uart_read_bytes() in a loop.

  6. 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.

  7. 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.

  8. 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.

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.

Terminal window
# 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 project
idf.py build
# Flash and open the serial monitor in one command
idf.py -p /dev/ttyACM0 flash monitor

If 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.

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.yml
dependencies:
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.

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 demo
menu "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 16
endmenu

This 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.

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.

Terminal window
# Start OpenOCD (in a separate terminal)
idf.py openocd
# Launch GDB and connect to OpenOCD
idf.py gdb

This 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.

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.