First test

This commit is contained in:
Thomas Peterson 2026-01-14 18:49:19 +01:00
commit 3eb49b31af
15 changed files with 679 additions and 0 deletions

2
.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
Remove: [-f*, -m*]

13
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@ -0,0 +1,19 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

78
.gitignore vendored Normal file
View File

@ -0,0 +1,78 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Directory metadata
.directory
# Temporary files
*~
*.swp
*.swo
*.bak
*.tmp
# Log files
*.log
# Build artifacts and directories
**/build/
build/
*.o
*.a
*.out
*.exe # For any host-side utilities compiled on Windows
# ESP-IDF specific build outputs
*.bin
*.elf
*.map
flasher_args.json # Generated in build directory
sdkconfig.old
sdkconfig
# ESP-IDF dependencies
# For older versions or manual component management
/components/.idf/
**/components/.idf/
# For modern ESP-IDF component manager
managed_components/
# If ESP-IDF tools are installed/referenced locally to the project
.espressif/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
# Python environment files
*.pyc
*.pyo
*.pyd
__pycache__/
*.egg-info/
dist/
# Virtual environment folders
venv/
.venv/
env/
# Language Servers
.clangd/
.ccls-cache/
compile_commands.json
# Windows specific
Thumbs.db
ehthumbs.db
Desktop.ini
# User-specific configuration files
*.user
*.workspace # General workspace files, can be from various tools
*.suo # Visual Studio Solution User Options
*.sln.docstates # Visual Studio

19
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "/home/thomas/.espressif/tools/riscv32-esp-elf/esp-14.2.0_20251107/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
"includePath": [
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
},
{
"type": "espidf",
"name": "Launch",
"request": "launch"
}
]
}

18
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,18 @@
{
"C_Cpp.intelliSenseEngine": "default",
"idf.openOcdConfigs": [
"interface/ftdi/esp32_devkitj_v1.cfg",
"target/esp32.cfg"
],
"idf.port": "/dev/ttyACM0",
"idf.currentSetup": "/home/thomas/.espressif/v5.5.2/esp-idf",
"idf.customExtraVars": {
"IDF_TARGET": "esp32p4"
},
"clangd.path": "/home/thomas/.espressif/tools/esp-clang/esp-19.1.2_20250312/esp-clang/bin/clangd",
"clangd.arguments": [
"--background-index",
"--query-driver=/home/thomas/.espressif/tools/riscv32-esp-elf/esp-14.2.0_20251107/riscv32-esp-elf/bin/riscv32-esp-elf-gcc",
"--compile-commands-dir=/home/thomas/projekte/test1/display/build"
]
}

8
CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
# idf_build_set_property(MINIMAL_BUILD ON)
project(knxdisplay)

53
README.md Normal file
View File

@ -0,0 +1,53 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | Linux |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | ----- |
# Hello World Example
Starts a FreeRTOS task to print "Hello World".
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## How to use example
Follow detailed instructions provided specifically for this example.
Select the instructions depending on Espressif chip installed on your development board:
- [ESP32 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/stable/get-started/index.html)
- [ESP32-S2 Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html)
## Example folder contents
The project **hello_world** contains one source file in C language [hello_world_main.c](main/hello_world_main.c). The file is located in folder [main](main).
ESP-IDF projects are built using CMake. The project build configuration is contained in `CMakeLists.txt` files that provide set of directives and instructions describing the project's source files and targets (executable, library, or both).
Below is short explanation of remaining files in the project folder.
```
├── CMakeLists.txt
├── pytest_hello_world.py Python script used for automated testing
├── main
│ ├── CMakeLists.txt
│ └── hello_world_main.c
└── README.md This is the file you are currently reading
```
For more information on structure and contents of ESP-IDF projects, please refer to Section [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) of the ESP-IDF Programming Guide.
## Troubleshooting
* Program upload failure
* Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
* The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
## Technical support and feedback
Please use the following feedback channels:
* For technical queries, go to the [esp32.com](https://esp32.com/) forum
* For a feature request or bug report, create a [GitHub issue](https://github.com/espressif/esp-idf/issues)
We will get back to you as soon as possible.

110
dependencies.lock Normal file
View File

@ -0,0 +1,110 @@
dependencies:
espressif/cmake_utilities:
component_hash:
351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
dependencies:
- name: idf
require: private
version: '>=4.1'
source:
registry_url: https://components.espressif.com
type: service
version: 0.5.3
espressif/esp_lcd_touch:
component_hash:
3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862
dependencies:
- name: idf
require: private
version: '>=4.4.2'
source:
registry_url: https://components.espressif.com
type: service
version: 1.2.1
espressif/esp_lcd_touch_gt911:
component_hash:
be02e243d18b9a661bc13b0d22c0a5cfa3f708cf04d6eb059772276c8c8a4d76
dependencies:
- name: espressif/esp_lcd_touch
registry_url: https://components.espressif.com
require: public
version: ^1.2.0
- name: idf
require: private
version: '>=4.4.2'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.2.0~1
espressif/esp_lvgl_port:
component_hash:
f872401524cb645ee6ff1c9242d44fb4ddcfd4d37d7be8b9ed3f4e85a404efcd
dependencies:
- name: idf
require: private
version: '>=5.1'
- name: lvgl/lvgl
registry_url: https://components.espressif.com
require: public
version: '>=8,<10'
source:
registry_url: https://components.espressif.com/
type: service
version: 2.7.0
espressif/i2c_bus:
component_hash:
4e990dc11734316186b489b362c61d41f23f79d58bc169795cec215e528cba14
dependencies:
- name: espressif/cmake_utilities
registry_url: https://components.espressif.com
require: private
version: '*'
- name: idf
require: private
version: '>=4.0'
source:
registry_url: https://components.espressif.com
type: service
version: 1.5.0
idf:
source:
type: idf
version: 5.5.2
lvgl/lvgl:
component_hash:
17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f
dependencies: []
source:
registry_url: https://components.espressif.com/
type: service
version: 9.4.0
waveshare/esp_lcd_jd9365_10_1:
component_hash:
6c1336b93a37df2b5be42c49c4c364d0bacdbf96f053a934f881349457fac679
dependencies:
- name: espressif/cmake_utilities
registry_url: https://components.espressif.com
require: private
version: 0.*
- name: espressif/i2c_bus
registry_url: https://components.espressif.com
require: private
version: ^1.3.0
- name: idf
require: private
version: '>=5.3'
source:
registry_url: https://components.espressif.com/
type: service
targets:
- esp32p4
version: 1.0.4
direct_dependencies:
- espressif/esp_lcd_touch_gt911
- espressif/esp_lvgl_port
- idf
- lvgl/lvgl
- waveshare/esp_lcd_jd9365_10_1
manifest_hash: 1b3972520800645b8020e1ad5d7fcfe89a9006c17fff1fcaa83ae8df9e2314ff
target: esp32p4
version: 2.0.0

4
main/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "hello_world_main.c"
PRIV_REQUIRES spi_flash esp_lcd
REQUIRES esp_timer lvgl
INCLUDE_DIRS "")

265
main/hello_world_main.c Normal file
View File

@ -0,0 +1,265 @@
/*
* SPDX-FileCopyrightText: 2026 User
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_ldo_regulator.h"
#include "esp_lcd_touch_gt911.h"
#include "lvgl.h"
#include "esp_lcd_jd9365_10_1.h"
#define TAG "JD9365_LVGL"
// Display config
#define LCD_H_RES 800
#define LCD_V_RES 1280
#define LCD_BIT_PER_PIXEL 24
#define LCD_RST_GPIO 27
#define LCD_BK_GPIO 26
#define LCD_BK_ON_LEVEL 1
// MIPI DSI config
#define MIPI_DSI_LANE_NUM 2
#define MIPI_DSI_PHY_LDO_CHAN 3
#define MIPI_DSI_PHY_LDO_VOLTAGE_MV 2500
// Touch config (GT911)
#define TOUCH_I2C_SDA 7
#define TOUCH_I2C_SCL 8
#define TOUCH_INT_GPIO 3
#define TOUCH_RST_GPIO 2
#define TOUCH_I2C_FREQ_HZ 400000
static esp_lcd_panel_handle_t panel_handle = NULL;
static esp_lcd_touch_handle_t touch_handle = NULL;
static esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
static esp_lcd_panel_io_handle_t mipi_dbi_io = NULL;
static SemaphoreHandle_t refresh_done_sem = NULL;
// ================= Color Transfer Done Callback ===================
static bool on_color_trans_done(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *event_data, void *user_ctx)
{
BaseType_t high_task_awoken = pdFALSE;
if (refresh_done_sem) {
xSemaphoreGiveFromISR(refresh_done_sem, &high_task_awoken);
}
return high_task_awoken == pdTRUE;
}
// ================= Flush Callback ===================
static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *color_p)
{
xSemaphoreTake(refresh_done_sem, portMAX_DELAY);
esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1,
area->x2 + 1, area->y2 + 1, color_p);
lv_display_flush_ready(disp);
}
// ================= MIPI DSI / JD9365 init ===================
static void lcd_init(void)
{
// Backlight GPIO
gpio_config_t bk_gpio = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << LCD_BK_GPIO
};
gpio_config(&bk_gpio);
gpio_set_level(LCD_BK_GPIO, LCD_BK_ON_LEVEL);
// LDO für MIPI PHY
esp_ldo_channel_handle_t ldo_mipi = NULL;
esp_ldo_channel_config_t ldo_cfg = {
.chan_id = MIPI_DSI_PHY_LDO_CHAN,
.voltage_mv = MIPI_DSI_PHY_LDO_VOLTAGE_MV
};
esp_ldo_acquire_channel(&ldo_cfg, &ldo_mipi);
// MIPI DSI Bus
esp_lcd_dsi_bus_config_t bus_cfg = JD9365_PANEL_BUS_DSI_2CH_CONFIG();
esp_lcd_new_dsi_bus(&bus_cfg, &mipi_dsi_bus);
// Panel IO
esp_lcd_dbi_io_config_t io_cfg = JD9365_PANEL_IO_DBI_CONFIG();
esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &io_cfg, &mipi_dbi_io);
// Panel Driver
esp_lcd_dpi_panel_config_t dpi_cfg = JD9365_800_1280_PANEL_60HZ_DPI_CONFIG(LCD_COLOR_PIXEL_FORMAT_RGB888);
jd9365_vendor_config_t vendor_cfg = {
.flags = { .use_mipi_interface = 1 },
.mipi_config = { .dsi_bus = mipi_dsi_bus, .dpi_config = &dpi_cfg, .lane_num = MIPI_DSI_LANE_NUM }
};
esp_lcd_panel_dev_config_t panel_cfg = {
.reset_gpio_num = LCD_RST_GPIO,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_cfg
};
esp_lcd_new_panel_jd9365(mipi_dbi_io, &panel_cfg, &panel_handle);
// Initialize panel first
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_disp_on_off(panel_handle, true);
// Now set up synchronization after panel is ready
refresh_done_sem = xSemaphoreCreateBinary();
xSemaphoreGive(refresh_done_sem); // Allow first draw to proceed
// Register color transfer done callback (signals when draw_bitmap completes)
esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_color_trans_done = on_color_trans_done,
};
esp_lcd_dpi_panel_register_event_callbacks(panel_handle, &cbs, NULL);
}
// ================= Touch init ===================
static void touch_init(void)
{
// I2C Bus initialisieren
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = TOUCH_I2C_SDA,
.scl_io_num = TOUCH_I2C_SCL,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true, // Board hat externe Pullups
};
i2c_master_bus_handle_t i2c_bus = NULL;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus));
// Touch Panel IO
esp_lcd_panel_io_handle_t touch_io = NULL;
esp_lcd_panel_io_i2c_config_t touch_io_cfg = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
touch_io_cfg.scl_speed_hz = TOUCH_I2C_FREQ_HZ;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &touch_io_cfg, &touch_io));
// GT911 Touch Controller
esp_lcd_touch_config_t touch_cfg = {
.x_max = LCD_H_RES,
.y_max = LCD_V_RES,
.rst_gpio_num = TOUCH_RST_GPIO,
.int_gpio_num = TOUCH_INT_GPIO,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(touch_io, &touch_cfg, &touch_handle));
ESP_LOGI(TAG, "Touch GT911 initialized");
}
// ================= LVGL Touch Callback ===================
static void lvgl_touch_cb(lv_indev_t *indev, lv_indev_data_t *data)
{
uint16_t x[1];
uint16_t y[1];
uint8_t touch_cnt = 0;
esp_lcd_touch_read_data(touch_handle);
bool touched = esp_lcd_touch_get_coordinates(touch_handle, x, y, NULL, &touch_cnt, 1);
if (touched && touch_cnt > 0) {
data->point.x = x[0];
data->point.y = y[0];
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
// ================= LVGL init ===================
static void lvgl_init(void)
{
lv_init();
// Display erstellen
lv_display_t* display1 = lv_display_create(LCD_H_RES, LCD_V_RES);
// Set color format to match panel (RGB888 = 24-bit)
lv_display_set_color_format(display1, LV_COLOR_FORMAT_RGB888);
// Allocate buffers in PSRAM with proper alignment for DMA
size_t buf_size = LCD_H_RES * 100 * 3; // 100 lines at a time, RGB888
uint8_t *buf1 = heap_caps_aligned_alloc(64, buf_size,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
uint8_t *buf2 = heap_caps_aligned_alloc(64, buf_size,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
if (!buf1 || !buf2) {
ESP_LOGE(TAG, "Failed to allocate display buffers!");
return;
}
lv_display_set_buffers(display1, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
// Flush-Callback setzen
lv_display_set_flush_cb(display1, lvgl_flush_cb);
// Touch Input Device registrieren
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, lvgl_touch_cb);
lv_indev_set_display(indev, display1);
lv_display_set_default(display1);
}
// ================= Button Callback ===================
static void btn_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "Button clicked!");
}
}
// ================= UI erstellen ===================
static void create_ui(void)
{
// Button erstellen
lv_obj_t *btn = lv_button_create(lv_screen_active());
lv_obj_set_size(btn, 200, 80);
lv_obj_center(btn);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
// Label auf dem Button
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Klick mich!");
lv_obj_center(label);
}
// ================= Main ===================
void app_main(void)
{
ESP_LOGI(TAG, "Starting JD9365 + LVGL 9");
lcd_init();
touch_init();
lvgl_init();
create_ui();
while (1) {
lv_tick_inc(10);
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(10));
}
}

20
main/idf_component.yml Normal file
View File

@ -0,0 +1,20 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
waveshare/esp_lcd_jd9365_10_1: '*'
lvgl/lvgl: ^9.4.0
espressif/esp_lvgl_port: ^2.3.0
espressif/esp_lcd_touch_gt911: '*'

55
pytest_hello_world.py Normal file
View File

@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import hashlib
import logging
from typing import Callable
import pytest
from pytest_embedded_idf.dut import IdfDut
from pytest_embedded_idf.utils import idf_parametrize
from pytest_embedded_qemu.app import QemuApp
from pytest_embedded_qemu.dut import QemuDut
@pytest.mark.generic
@idf_parametrize('target', ['supported_targets', 'preview_targets'], indirect=['target'])
def test_hello_world(dut: IdfDut, log_minimum_free_heap_size: Callable[..., None]) -> None:
dut.expect('Hello world!')
log_minimum_free_heap_size()
@pytest.mark.host_test
@idf_parametrize('target', ['linux'], indirect=['target'])
def test_hello_world_linux(dut: IdfDut) -> None:
dut.expect('Hello world!')
@pytest.mark.host_test
@pytest.mark.macos_shell
@idf_parametrize('target', ['linux'], indirect=['target'])
def test_hello_world_macos(dut: IdfDut) -> None:
dut.expect('Hello world!')
def verify_elf_sha256_embedding(app: QemuApp, sha256_reported: str) -> None:
sha256 = hashlib.sha256()
with open(app.elf_file, 'rb') as f:
sha256.update(f.read())
sha256_expected = sha256.hexdigest()
logging.info(f'ELF file SHA256: {sha256_expected}')
logging.info(f'ELF file SHA256 (reported by the app): {sha256_reported}')
# the app reports only the first several hex characters of the SHA256, check that they match
if not sha256_expected.startswith(sha256_reported):
raise ValueError('ELF file SHA256 mismatch')
@pytest.mark.host_test
@pytest.mark.qemu
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
def test_hello_world_host(app: QemuApp, dut: QemuDut) -> None:
sha256_reported = dut.expect(r'ELF file SHA256:\s+([a-f0-9]+)').group(1).decode('utf-8')
verify_elf_sha256_embedding(app, sha256_reported)
dut.expect('Hello world!')

0
sdkconfig.ci Normal file
View File