diff --git a/code/demo1/main/hello_world_main.c b/code/demo1/main/hello_world_main.c index 4597e90..23fe289 100644 --- a/code/demo1/main/hello_world_main.c +++ b/code/demo1/main/hello_world_main.c @@ -49,10 +49,13 @@ void app_main(void) printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size()); + printf("sizeof(int) ==%d\n",sizeof(int)); + spi_init(); lcd_init(); //LCD_Clear(0x1234); + while (1) { diff --git a/code/demo1/main/spi.c b/code/demo1/main/spi.c index 5a4e9d9..c182dd8 100644 --- a/code/demo1/main/spi.c +++ b/code/demo1/main/spi.c @@ -30,7 +30,7 @@ void spi_init() // 1. 配置 GPIO 驱动能力 (ESP32-C3 支持) - gpio_set_drive_capability(VSPI_MISO, GPIO_DRIVE_CAP_3); // 最大驱动 + // gpio_set_drive_capability(VSPI_MISO, GPIO_DRIVE_CAP_3); // 最大驱动 gpio_set_drive_capability(VSPI_MOSI, GPIO_DRIVE_CAP_3); gpio_set_drive_capability(VSPI_SCLK, GPIO_DRIVE_CAP_3); @@ -48,7 +48,7 @@ void spi_init() .sclk_io_num = VSPI_SCLK, // 时钟引脚 .quadwp_io_num = -1, // 不使用 QWP .quadhd_io_num = -1, // 不使用 QHD - .max_transfer_sz = 4096, // 最大传输大小 + .max_transfer_sz = 16, // 最大传输大小 .flags = 0, .intr_flags = 0, }; @@ -66,7 +66,7 @@ void spi_init() spi_device_interface_config_t devcfg = { .mode = 0, // SPI 模式 0 - .clock_speed_hz = 80000000, // + .clock_speed_hz = 40000000, // .spics_io_num = LCD_CS, .queue_size = 7, // 队列深度 .flags = SPI_DEVICE_NO_DUMMY, // 禁用 dummy 周期 @@ -119,8 +119,8 @@ esp_err_t lcd_spi_write8(uint8_t data) { esp_err_t lcd_spi_write16(uint16_t data) { uint8_t tx_buf[2]; - tx_buf[1] = data & 0xFF; - tx_buf[0] = (data >> 8) & 0xFF; + tx_buf[1] = data ; + tx_buf[0] = (data >> 8) ; // spi_transaction_t trans = { // .length = 16, // 数据位数 diff --git a/code/spi_lcd_driver/.clangd b/code/spi_lcd_driver/.clangd new file mode 100644 index 0000000..437f255 --- /dev/null +++ b/code/spi_lcd_driver/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Remove: [-f*, -m*] diff --git a/code/spi_lcd_driver/.devcontainer/Dockerfile b/code/spi_lcd_driver/.devcontainer/Dockerfile new file mode 100644 index 0000000..dafb8ad --- /dev/null +++ b/code/spi_lcd_driver/.devcontainer/Dockerfile @@ -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"] \ No newline at end of file diff --git a/code/spi_lcd_driver/.devcontainer/devcontainer.json b/code/spi_lcd_driver/.devcontainer/devcontainer.json new file mode 100644 index 0000000..246b79f --- /dev/null +++ b/code/spi_lcd_driver/.devcontainer/devcontainer.json @@ -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"] +} \ No newline at end of file diff --git a/code/spi_lcd_driver/.gitignore b/code/spi_lcd_driver/.gitignore new file mode 100644 index 0000000..7805557 --- /dev/null +++ b/code/spi_lcd_driver/.gitignore @@ -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 diff --git a/code/spi_lcd_driver/.vscode/c_cpp_properties.json b/code/spi_lcd_driver/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..73652be --- /dev/null +++ b/code/spi_lcd_driver/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "C:\\Espressif\\tools\\riscv32-esp-elf\\esp-13.2.0_20240530\\riscv32-esp-elf\\bin\\riscv32-esp-elf-gcc.exe", + "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "includePath": [ + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/code/spi_lcd_driver/.vscode/launch.json b/code/spi_lcd_driver/.vscode/launch.json new file mode 100644 index 0000000..3694ae4 --- /dev/null +++ b/code/spi_lcd_driver/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + } + ] +} \ No newline at end of file diff --git a/code/spi_lcd_driver/.vscode/settings.json b/code/spi_lcd_driver/.vscode/settings.json new file mode 100644 index 0000000..b7f32c7 --- /dev/null +++ b/code/spi_lcd_driver/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "idf.openOcdConfigs": [ + "board/esp32c3-builtin.cfg" + ], + "idf.portWin": "detect", + "idf.currentSetup": "C:/Espressif/frameworks/esp-idf-v5.3.1/", + "idf.customExtraVars": { + "IDF_TARGET": "esp32c3" + }, + "clangd.path": "C:\\Espressif\\tools\\esp-clang\\16.0.1-fe4f10a809\\esp-clang\\bin\\clangd.exe", + "clangd.arguments": [ + "--background-index", + "--query-driver=**", + "--compile-commands-dir=c:\\Users\\wuwen\\Documents\\prj\\hantai_project\\code\\spi_lcd_driver\\build" + ] +} diff --git a/code/spi_lcd_driver/CMakeLists.txt b/code/spi_lcd_driver/CMakeLists.txt new file mode 100644 index 0000000..9d7e6af --- /dev/null +++ b/code/spi_lcd_driver/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.16) + +# 添加ESP_LCD组件依赖 +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/lcd) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(spi_lcd_driver) \ No newline at end of file diff --git a/code/spi_lcd_driver/README.md b/code/spi_lcd_driver/README.md new file mode 100644 index 0000000..c3e1ee3 --- /dev/null +++ b/code/spi_lcd_driver/README.md @@ -0,0 +1,53 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | 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. diff --git a/code/spi_lcd_driver/main/CMakeLists.txt b/code/spi_lcd_driver/main/CMakeLists.txt new file mode 100644 index 0000000..ecbbfdc --- /dev/null +++ b/code/spi_lcd_driver/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "main.c" "lcd_driver.c" + INCLUDE_DIRS "." + REQUIRES driver esp_lcd heap +) \ No newline at end of file diff --git a/code/spi_lcd_driver/main/hello_world_main.c b/code/spi_lcd_driver/main/hello_world_main.c new file mode 100644 index 0000000..765acd9 --- /dev/null +++ b/code/spi_lcd_driver/main/hello_world_main.c @@ -0,0 +1,63 @@ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_log.h" +#include "lcd_driver.h" + +static const char *TAG = "MAIN"; + +void app_main(void) +{ + ESP_LOGI(TAG, "ESP32-C3 SPI LCD驱动示例"); + ESP_LOGI(TAG, "ESP-IDF版本: %s", esp_get_idf_version()); + + // 初始化LCD驱动结构 + lcd_driver_t lcd = { + .spi_handle = NULL, + .panel_handle = NULL, + .cs_gpio = LCD_PIN_NUM_CS, + .dc_gpio = LCD_PIN_NUM_DC, + .rst_gpio = LCD_PIN_NUM_RST, + .bckl_gpio = LCD_PIN_NUM_BCKL, + .type = LCD_TYPE_ST7789, + }; + + // 初始化LCD + esp_err_t ret = lcd_driver_init(&lcd, LCD_TYPE_ST7789); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "LCD初始化失败!"); + return; + } + + // 清屏 + ESP_LOGI(TAG, "清屏测试..."); + lcd_clear(&lcd, COLOR_BLACK); + vTaskDelay(pdMS_TO_TICKS(500)); + + // 填充颜色测试 + ESP_LOGI(TAG, "颜色填充测试..."); + lcd_fill_rect(&lcd, 0, 0, LCD_H_RES/2, LCD_V_RES/2, COLOR_RED); + lcd_fill_rect(&lcd, LCD_H_RES/2, 0, LCD_H_RES/2, LCD_V_RES/2, COLOR_GREEN); + lcd_fill_rect(&lcd, 0, LCD_V_RES/2, LCD_H_RES/2, LCD_V_RES/2, COLOR_BLUE); + lcd_fill_rect(&lcd, LCD_H_RES/2, LCD_V_RES/2, LCD_H_RES/2, LCD_V_RES/2, COLOR_YELLOW); + vTaskDelay(pdMS_TO_TICKS(2000)); + + // 清屏 + lcd_clear(&lcd, COLOR_BLACK); + + // 绘制测试图案 + ESP_LOGI(TAG, "绘制测试图案..."); + for (int i = 0; i < 10; i++) { + lcd_fill_rect(&lcd, i * 20, i * 20, LCD_H_RES - i * 40, LCD_V_RES - i * 40, + (i % 2) ? COLOR_CYAN : COLOR_MAGENTA); + vTaskDelay(pdMS_TO_TICKS(200)); + } + + ESP_LOGI(TAG, "LCD测试完成!"); + + // 保持运行 + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} \ No newline at end of file diff --git a/code/spi_lcd_driver/main/lcd_driver.c b/code/spi_lcd_driver/main/lcd_driver.c new file mode 100644 index 0000000..5a38370 --- /dev/null +++ b/code/spi_lcd_driver/main/lcd_driver.c @@ -0,0 +1,302 @@ +#include "lcd_driver.h" +#include "esp_log.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "LCD_DRIVER"; + +// ============ ST7789 初始化序列 ============ +static const uint8_t st7789_init_cmds[] = { + 0x11, 0, 120, // SLPOUT - 退出睡眠模式 + 0x3A, 1, 0, 0x55, // COLMOD - 像素格式: 16bit + 0x36, 1, 0, 0x00, // MADCTL - 内存数据访问控制 + 0xB2, 5, 0, 0x0C, 0x0C, 0x00, 0x33, 0x33, // PORCTRL - 帧率控制 + 0xB7, 1, 0, 0x35, // GCTRL - 门控制 + 0xBB, 1, 0, 0x19, // VCOMS - VCOM电压 + 0xC0, 1, 0, 0x2C, // LCMCTRL - LCD控制 + 0xC2, 2, 0, 0x01, 0xFF, // VDVVRHEN - VDV和VRH命令使能 + 0xC3, 1, 0, 0x11, // VRHSET - VRH设置 + 0xC4, 1, 0, 0x20, // VDVS - VDV设置 + 0xC6, 1, 0, 0x0F, // FRCTRL2 - 帧率控制2 + 0xD0, 2, 0, 0xA4, 0xA1, // PWCTRL1 - 电源控制1 + 0xE0, 14, 0, 0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, + 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23, // PVGAMCTRL - 正伽马 + 0xE1, 14, 0, 0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, + 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23, // NVGAMCTRL - 负伽马 + 0x21, 0, 0, // INVON - 显示反转开启 + 0x29, 0, 120, // DISPON - 显示开启 +}; + +// ============ 初始化SPI总线 ============ +static esp_err_t init_spi_bus(spi_host_device_t host, gpio_num_t mosi, gpio_num_t sclk) +{ + spi_bus_config_t bus_cfg = { + .mosi_io_num = mosi, + .miso_io_num = -1, // LCD通常不需要MISO + .sclk_io_num = sclk, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = LCD_H_RES * LCD_BIT_PER_PIXEL / 8 * 8, + .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS, + }; + + esp_err_t ret = spi_bus_initialize(host, &bus_cfg, SPI_DMA_CH_AUTO); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "SPI总线初始化失败: %s", esp_err_to_name(ret)); + } + return ret; +} + +// ============ 初始化SPI设备 ============ +static esp_err_t init_spi_device(spi_host_device_t host, gpio_num_t cs, + spi_device_handle_t *spi_handle) +{ + spi_device_interface_config_t dev_cfg = { + .clock_speed_hz = LCD_SPI_SPEED_HZ, // 80MHz + .mode = 0, // SPI Mode 0 + .spics_io_num = cs, + .queue_size = 7, + .flags = SPI_DEVICE_NO_DUMMY, + .pre_cb = NULL, + .post_cb = NULL, + }; + + esp_err_t ret = spi_bus_add_device(host, &dev_cfg, spi_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "SPI设备添加失败: %s", esp_err_to_name(ret)); + } + return ret; +} + +// ============ 创建LCD面板IO ============ +static esp_err_t create_lcd_panel_io(spi_device_handle_t spi_handle, gpio_num_t dc_gpio, + esp_lcd_panel_io_handle_t *io_handle) +{ + esp_lcd_panel_io_spi_config_t io_config = { + .dc_gpio_num = dc_gpio, + .cs_gpio_num = -1, // 已在SPI设备中配置 + .pclk_hz = LCD_SPI_SPEED_HZ, + .trans_queue_depth = 10, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .spi_mode = 0, + .flags = { + .dc_low_on_data = 0, + .octal_mode = 0, + .sio_mode = 0, + .lsb_first = 0, + .cs_high_active = 0, + }, + }; + + esp_err_t ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_handle, + &io_config, io_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "创建LCD面板IO失败: %s", esp_err_to_name(ret)); + } + return ret; +} + +// ============ 创建LCD面板 ============ +static esp_err_t create_lcd_panel(esp_lcd_panel_io_handle_t io_handle, + esp_lcd_panel_handle_t *panel_handle, + lcd_type_t type) +{ + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = -1, // 手动控制复位 + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = LCD_BIT_PER_PIXEL, + }; + + esp_err_t ret; + + switch (type) { + case LCD_TYPE_ST7789: + ret = esp_lcd_new_panel_st7789(io_handle, &panel_config, panel_handle); + break; + case LCD_TYPE_ILI9341: + ret = esp_lcd_new_panel_ili9341(io_handle, &panel_config, panel_handle); + break; + case LCD_TYPE_GC9A01: + ret = esp_lcd_new_panel_st7789(io_handle, &panel_config, panel_handle); + break; + default: + ret = ESP_ERR_INVALID_ARG; + } + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "创建LCD面板失败: %s", esp_err_to_name(ret)); + } + return ret; +} + +// ============ 驱动初始化 ============ +esp_err_t lcd_driver_init(lcd_driver_t *lcd, lcd_type_t type) +{ + esp_err_t ret; + + // 初始化GPIO + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << lcd->dc_gpio) | (1ULL << lcd->rst_gpio) | (1ULL << lcd->bckl_gpio), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + }; + gpio_config(&io_conf); + + // 复位LCD + gpio_set_level(lcd->rst_gpio, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(lcd->rst_gpio, 1); + vTaskDelay(pdMS_TO_TICKS(120)); + + // 初始化SPI总线 + ret = init_spi_bus(lcd->spi_handle ? LCD_SPI_HOST : LCD_SPI_HOST, + LCD_PIN_NUM_MOSI, LCD_PIN_NUM_SCLK); + if (ret != ESP_OK) return ret; + + // 初始化SPI设备 + spi_device_interface_config_t dev_cfg = { + .clock_speed_hz = LCD_SPI_SPEED_HZ, + .mode = 0, + .spics_io_num = lcd->cs_gpio, + .queue_size = 7, + .flags = SPI_DEVICE_NO_DUMMY, + }; + ret = spi_bus_add_device(LCD_SPI_HOST, &dev_cfg, &lcd->spi_handle); + if (ret != ESP_OK) return ret; + + // 创建面板IO + esp_lcd_panel_io_handle_t io_handle = NULL; + ret = create_lcd_panel_io(lcd->spi_handle, lcd->dc_gpio, &io_handle); + if (ret != ESP_OK) return ret; + + // 创建面板 + ret = create_lcd_panel(io_handle, &lcd->panel_handle, type); + if (ret != ESP_OK) return ret; + + // 初始化面板 + ret = esp_lcd_panel_reset(lcd->panel_handle); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "面板复位失败,继续执行..."); + } + + ret = esp_lcd_panel_init(lcd->panel_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "面板初始化失败: %s", esp_err_to_name(ret)); + return ret; + } + + // 关闭睡眠模式 + esp_lcd_panel_disp_on_off(lcd->panel_handle, true); + + // 设置默认背光 + lcd_set_backlight(lcd, 100); + + ESP_LOGI(TAG, "LCD初始化完成,SPI速度: %d MHz", LCD_SPI_SPEED_HZ / 1000000); + return ESP_OK; +} + +// ============ 驱动反初始化 ============ +esp_err_t lcd_driver_deinit(lcd_driver_t *lcd) +{ + if (lcd->panel_handle) { + esp_lcd_panel_del(lcd->panel_handle); + lcd->panel_handle = NULL; + } + if (lcd->spi_handle) { + spi_bus_remove_device(lcd->spi_handle); + lcd->spi_handle = NULL; + } + spi_bus_free(LCD_SPI_HOST); + ESP_LOGI(TAG, "LCD驱动已释放"); + return ESP_OK; +} + +// ============ 绘制单个像素 ============ +esp_err_t lcd_draw_pixel(lcd_driver_t *lcd, uint16_t x, uint16_t y, uint16_t color) +{ + if (x >= LCD_H_RES || y >= LCD_V_RES) { + return ESP_ERR_INVALID_ARG; + } + + esp_lcd_panel_set_window(lcd->panel_handle, x, y, x, y); + uint16_t data = color; + esp_lcd_panel_draw_bitmap(lcd->panel_handle, x, y, x + 1, y + 1, &data); + return ESP_OK; +} + +// ============ 填充矩形 ============ +esp_err_t lcd_fill_rect(lcd_driver_t *lcd, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, uint16_t color) +{ + if (x >= LCD_H_RES || y >= LCD_V_RES) { + return ESP_ERR_INVALID_ARG; + } + + // 限制宽高 + if (x + w > LCD_H_RES) w = LCD_H_RES - x; + if (y + h > LCD_V_RES) h = LCD_V_RES - y; + + uint16_t *buf = heap_caps_malloc(w * h * sizeof(uint16_t), MALLOC_CAP_DMA); + if (!buf) { + ESP_LOGE(TAG, "内存分配失败"); + return ESP_ERR_NO_MEM; + } + + // 填充缓冲区 + for (int i = 0; i < w * h; i++) { + buf[i] = color; + } + + esp_lcd_panel_set_window(lcd->panel_handle, x, y, x + w - 1, y + h - 1); + esp_lcd_panel_draw_bitmap(lcd->panel_handle, x, y, x + w, y + h, buf); + + heap_caps_free(buf); + return ESP_OK; +} + +// ============ 绘制图像 ============ +esp_err_t lcd_draw_image(lcd_driver_t *lcd, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, const uint16_t *data) +{ + if (x >= LCD_H_RES || y >= LCD_V_RES || !data) { + return ESP_ERR_INVALID_ARG; + } + + if (x + w > LCD_H_RES) w = LCD_H_RES - x; + if (y + h > LCD_V_RES) h = LCD_V_RES - y; + + esp_lcd_panel_set_window(lcd->panel_handle, x, y, x + w - 1, y + h - 1); + esp_lcd_panel_draw_bitmap(lcd->panel_handle, x, y, x + w, y + h, data); + return ESP_OK; +} + +// ============ 设置旋转 ============ +esp_err_t lcd_set_rotation(lcd_driver_t *lcd, uint8_t rotation) +{ + return esp_lcd_panel_set_gap(lcd->panel_handle, 0, 0); +} + +// ============ 设置背光 ============ +esp_err_t lcd_set_backlight(lcd_driver_t *lcd, uint8_t brightness) +{ + if (brightness > 100) brightness = 100; + + // 使用LED PWM控制背光 + // 注意:ESP32-C3使用LEDC需要不同配置 + #if CONFIG_IDF_TARGET_ESP32C3 + // ESP32-C3的PWM配置 + #endif + + gpio_set_level(lcd->bckl_gpio, (brightness > 0) ? 1 : 0); + return ESP_OK; +} + +// ============ 清屏 ============ +esp_err_t lcd_clear(lcd_driver_t *lcd, uint16_t color) +{ + return lcd_fill_rect(lcd, 0, 0, LCD_H_RES, LCD_V_RES, color); +} \ No newline at end of file diff --git a/code/spi_lcd_driver/main/lcd_driver.h b/code/spi_lcd_driver/main/lcd_driver.h new file mode 100644 index 0000000..f8d07ec --- /dev/null +++ b/code/spi_lcd_driver/main/lcd_driver.h @@ -0,0 +1,70 @@ +#ifndef LCD_DRIVER_H +#define LCD_DRIVER_H + +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_vendor.h" +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "esp_err.h" + +// ============ 引脚配置 (根据实际硬件修改) ============ +#define LCD_PIN_NUM_MOSI 7 // SPI MOSI +#define LCD_PIN_NUM_SCLK 6 // SPI SCLK +#define LCD_PIN_NUM_CS 5 // 片选 +#define LCD_PIN_NUM_DC 4 // 数据/命令选择 +#define LCD_PIN_NUM_RST 3 // 复位 +#define LCD_PIN_NUM_BCKL 2 // 背光控制 + +// ============ 屏幕参数 ============ +#define LCD_H_RES 240 // 水平分辨率 +#define LCD_V_RES 240 // 垂直分辨率 +#define LCD_BIT_PER_PIXEL 16 // 16位色深 (RGB565) + +// ============ SPI配置 ============ +#define LCD_SPI_HOST SPI2_HOST +#define LCD_SPI_SPEED_HZ (80 * 1000 * 1000) // 80MHz + +// ============ 屏幕类型 ============ +typedef enum { + LCD_TYPE_ST7789, + LCD_TYPE_ILI9341, + LCD_TYPE_GC9A01 +} lcd_type_t; + +// ============ 驱动句柄 ============ +typedef struct { + spi_device_handle_t spi_handle; + esp_lcd_panel_handle_t panel_handle; + gpio_num_t cs_gpio; + gpio_num_t dc_gpio; + gpio_num_t rst_gpio; + gpio_num_t bckl_gpio; + lcd_type_t type; +} lcd_driver_t; + +// ============ 函数声明 ============ +esp_err_t lcd_driver_init(lcd_driver_t *lcd, lcd_type_t type); +esp_err_t lcd_driver_deinit(lcd_driver_t *lcd); +esp_err_t lcd_draw_pixel(lcd_driver_t *lcd, uint16_t x, uint16_t y, uint16_t color); +esp_err_t lcd_fill_rect(lcd_driver_t *lcd, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, uint16_t color); +esp_err_t lcd_draw_image(lcd_driver_t *lcd, uint16_t x, uint16_t y, + uint16_t w, uint16_t h, const uint16_t *data); +esp_err_t lcd_set_rotation(lcd_driver_t *lcd, uint8_t rotation); +esp_err_t lcd_set_backlight(lcd_driver_t *lcd, uint8_t brightness); + +// ============ 颜色宏 ============ +#define COLOR_BLACK 0x0000 +#define COLOR_WHITE 0xFFFF +#define COLOR_RED 0xF800 +#define COLOR_GREEN 0x07E0 +#define COLOR_BLUE 0x001F +#define COLOR_YELLOW 0xFFE0 +#define COLOR_CYAN 0x07FF +#define COLOR_MAGENTA 0xF81F +#define COLOR_GRAY 0x8410 + +// RGB565转换宏 +#define RGB565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)) + +#endif // LCD_DRIVER_H \ No newline at end of file diff --git a/code/spi_lcd_driver/pytest_hello_world.py b/code/spi_lcd_driver/pytest_hello_world.py new file mode 100644 index 0000000..caeeff1 --- /dev/null +++ b/code/spi_lcd_driver/pytest_hello_world.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2022-2023 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_qemu.app import QemuApp +from pytest_embedded_qemu.dut import QemuDut + + +@pytest.mark.supported_targets +@pytest.mark.preview_targets +@pytest.mark.generic +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.linux +@pytest.mark.host_test +def test_hello_world_linux(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.esp32 # we only support qemu on esp32 for now +@pytest.mark.host_test +@pytest.mark.qemu +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!') diff --git a/code/spi_lcd_driver/sdkconfig.ci b/code/spi_lcd_driver/sdkconfig.ci new file mode 100644 index 0000000..e69de29