diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 371ed23b..b5faff88 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -195,6 +195,8 @@ elseif(CONFIG_BOARD_TYPE_ELECTRON_BOT) set(BOARD_TYPE "electron-bot") elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM) set(BOARD_TYPE "bread-compact-wifi-s3cam") +elseif(CONFIG_BOARD_TYPE_JIUCHUAN ) + set(BOARD_TYPE "jiuchuan-s3") endif() file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc @@ -313,4 +315,4 @@ spiffs_create_partition_assets( FLASH_IN_PROJECT MMAP_FILE_SUPPORT_FORMAT ".aaf" ) -endif() +endif() \ No newline at end of file diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 191b1fdd..446d5d94 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -269,6 +269,8 @@ choice BOARD_TYPE depends on IDF_TARGET_ESP32S3 select LV_USE_GIF select LV_GIF_CACHE_DECODE_DATA + config BOARD_TYPE_JIUCHUAN + bool "九川智能" endchoice choice ESP_S3_LCD_EV_Board_Version_TYPE diff --git a/main/boards/jiuchuan-s3/README.md b/main/boards/jiuchuan-s3/README.md new file mode 100644 index 00000000..4ae7cbd5 --- /dev/null +++ b/main/boards/jiuchuan-s3/README.md @@ -0,0 +1,25 @@ +# jiuchuan-xiaozhi-sound +九川科技小智AI音箱 + +## 🛠️ 编译指南 +**开发环境**:ESP-IDF v5.4.1 + +### 编译步骤: +> ⚠️ **提示**:若在编译过程中访问在线库失败,可以尝试切换加速器状态,或修改 [idf_component.yml] 文件,替换为国内镜像源。 + +1. 使用 VSCode 打开项目文件夹; +2. 清除工程(Clean Project); +3. 设置 ESP-IDF 版本为 `v5.4.1`; +4. 点击 VSCode 右下角提示,生成 [compile_commands.json] 文件; +5. 设置目标设备为 `[esp32s3] -> [JTAG]`; +6. 打开 **SDK Configuration Editor**; +7. 配置自定义分区表路径为:`partitions/v1/16m.csv`; +8. 设置 **Board Type** 为 **九川科技**; +9. 保存配置并开始编译。 + +## 🔌 烧录步骤 +1. 使用数据线连接电脑与音箱; +2. 关闭设备电源后,长按电源键不松手; +3. 在烧录工具中选择对应的串口(COM Port); +4. 点击烧录按钮,选择 UART 模式; +5. 烧录完成前请勿松开电源键。 \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/config.h b/main/boards/jiuchuan-s3/config.h new file mode 100644 index 00000000..d6895277 --- /dev/null +++ b/main/boards/jiuchuan-s3/config.h @@ -0,0 +1,52 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_13 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_42 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR + + +#define BUILTIN_LED_GPIO GPIO_NUM_10 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define PWR_BUTTON_GPIO GPIO_NUM_3 +#define PWR_EN_GPIO GPIO_NUM_5 +#define PWR_ADC_GPIO GPIO_NUM_4 +#define PWR_BUTTON_TIME 3000000U + +#define WIFI_BUTTON_GPIO GPIO_NUM_6 +#define CMD_BUTTON_GPIO GPIO_NUM_7 + + +#define DISPLAY_SPI_SCK_PIN GPIO_NUM_41 +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_40 +#define DISPLAY_DC_PIN GPIO_NUM_39 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_9 + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/jiuchuan-s3/config.json b/main/boards/jiuchuan-s3/config.json new file mode 100644 index 00000000..d1f610fa --- /dev/null +++ b/main/boards/jiuchuan-s3/config.json @@ -0,0 +1,9 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "jiuchuan-s3", + "sdkconfig_append": [] + } + ] +} \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c new file mode 100644 index 00000000..b8d0e9a8 --- /dev/null +++ b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c @@ -0,0 +1,384 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + #include + #include + #include "sdkconfig.h" + #include + #if CONFIG_LCD_ENABLE_DEBUG_LOG + // The local log level must be defined before including esp_log.h + // Set the maximum log level for this source file + #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG + #endif + + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #include "esp_lcd_panel_interface.h" + #include "esp_lcd_panel_io.h" + #include "esp_lcd_panel_vendor.h" + #include "esp_lcd_panel_ops.h" + #include "esp_lcd_panel_commands.h" + #include "driver/gpio.h" + #include "esp_log.h" + #include "esp_check.h" + #include "esp_compiler.h" + /* GC9309NA LCD controller driver for ESP-IDF + * SPDX-FileCopyrightText: 2024 Your Name + * SPDX-License-Identifier: Apache-2.0 + */ + + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #include "esp_lcd_panel_interface.h" + #include "esp_lcd_panel_io.h" + #include "esp_check.h" + #include "driver/gpio.h" + + + // GC9309NA Command Set + #define GC9309NA_CMD_SLPIN 0x10 + #define GC9309NA_CMD_SLPOUT 0x11 + #define GC9309NA_CMD_INVOFF 0x20 + #define GC9309NA_CMD_INVON 0x21 + #define GC9309NA_CMD_DISPOFF 0x28 + #define GC9309NA_CMD_DISPON 0x29 + #define GC9309NA_CMD_CASET 0x2A + #define GC9309NA_CMD_RASET 0x2B + #define GC9309NA_CMD_RAMWR 0x2C + #define GC9309NA_CMD_MADCTL 0x36 + #define GC9309NA_CMD_COLMOD 0x3A + #define GC9309NA_CMD_TEOFF 0x34 + #define GC9309NA_CMD_TEON 0x35 + #define GC9309NA_CMD_WRDISBV 0x51 + #define GC9309NA_CMD_WRCTRLD 0x53 + + // Manufacturer Commands + #define GC9309NA_CMD_SETGAMMA1 0xF0 + #define GC9309NA_CMD_SETGAMMA2 0xF1 + #define GC9309NA_CMD_PWRCTRL1 0x67 + #define GC9309NA_CMD_PWRCTRL2 0x68 + #define GC9309NA_CMD_PWRCTRL3 0x66 + #define GC9309NA_CMD_PWRCTRL4 0xCA + #define GC9309NA_CMD_PWRCTRL5 0xCB + #define GC9309NA_CMD_DINVCTRL 0xB5 + #define GC9309NA_CMD_REG_ENABLE1 0xFE + #define GC9309NA_CMD_REG_ENABLE2 0xEF + + // 自检模式颜色定义 + + + static const char *TAG = "lcd_panel.gc9309na"; + + typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t madctl_val; + uint8_t colmod_val; + uint16_t te_scanline; + uint8_t fb_bits_per_pixel; + } gc9309na_panel_t; + + static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel); + static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel); + static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel); + static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); + static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); + static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); + static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); + static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); + static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool off); + static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep); + + + esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) + { + esp_err_t ret = ESP_OK; + gc9309na_panel_t *gc9309 = NULL; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid arg"); + + gc9309 = calloc(1, sizeof(gc9309na_panel_t)); + ESP_GOTO_ON_FALSE(gc9309, ESP_ERR_NO_MEM, err, TAG, "no mem"); + + + // Hardware reset GPIO config + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "GPIO config failed"); + } + + gc9309->colmod_val = 0x55; // RGB565 + // Initial register values + + gc9309->fb_bits_per_pixel = 16; + gc9309->io = io; + gc9309->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9309->reset_level = panel_dev_config->flags.reset_active_high; + gc9309->x_gap = 0; + gc9309->y_gap = 0; + + // Function pointers + gc9309->base.del = panel_gc9309na_del; + gc9309->base.reset = panel_gc9309na_reset; + gc9309->base.init = panel_gc9309na_init; + gc9309->base.draw_bitmap = panel_gc9309na_draw_bitmap; + gc9309->base.invert_color = panel_gc9309na_invert_color; + gc9309->base.set_gap = panel_gc9309na_set_gap; + gc9309->base.mirror = panel_gc9309na_mirror; + gc9309->base.swap_xy = panel_gc9309na_swap_xy; + gc9309->base.disp_on_off = panel_gc9309na_disp_on_off; + gc9309->base.disp_sleep = panel_gc9309na_sleep; + + *ret_panel = &(gc9309->base); + ESP_LOGI(TAG, "New GC9309NA panel @%p", gc9309); + return ESP_OK; + + err: + if (gc9309) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9309); + } + return ret; + } + + static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + + if (gc9309->reset_gpio_num >= 0) { + gpio_reset_pin(gc9309->reset_gpio_num); + } + free(gc9309); + ESP_LOGI(TAG, "Del GC9309NA panel"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + + if (gc9309->reset_gpio_num >= 0) { + // Hardware reset + gpio_set_level(gc9309->reset_gpio_num, gc9309->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9309->reset_gpio_num, !gc9309->reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } else { + // Software reset + // uint8_t unlock_cmd[] = {GC9309NA_CMD_REG_ENABLE1, GC9309NA_CMD_REG_ENABLE2}; + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, 0xFE, unlock_cmd, 2), + // TAG, "Unlock failed"); + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, LCD_CMD_SWRESET, NULL, 0), + // TAG, "SW Reset failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + return ESP_OK; + } + static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9309->io; + + // Unlock commands + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xFE, NULL, 0), TAG, "Unlock cmd1 failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEF, NULL, 0), TAG, "Unlock cmd2 failed"); + + // Sleep out command + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed"); + //vTaskDelay(pdMS_TO_TICKS(80)); + + // Timing control commands + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xA0}, 1), TAG, "Timing control failed"); + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xF0}, 1), TAG, "Timing control failed"); + + // Display on command + //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed"); + // vTaskDelay(pdMS_TO_TICKS(10)); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x80, (uint8_t[]){0xC0}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x81, (uint8_t[]){0x01}, 1), TAG, "DINV failed"); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x82, (uint8_t[]){0x07}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x83, (uint8_t[]){0x38}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x88, (uint8_t[]){0x64}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x89, (uint8_t[]){0x86}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8B, (uint8_t[]){0x3C}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8D, (uint8_t[]){0x51}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8E, (uint8_t[]){0x70}, 1), TAG, "DINV failed"); + + //高低位交换 + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB4, (uint8_t[]){0x80}, 1), TAG, "DINV failed"); + + gc9309->colmod_val = 0x05; // RGB565 + gc9309->madctl_val = 0x48; // BGR顺序,设置bit3=1(即0x08) + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_COLMOD, &gc9309->colmod_val, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_MADCTL, &gc9309->madctl_val, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XBF, (uint8_t[]){0X1F}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7d, (uint8_t[]){0x45,0x06}, 2), TAG, "DINV failed"); + // Continue from where you left off + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEE, (uint8_t[]){0x00,0x06}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF4, (uint8_t[]){0x53}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF6, (uint8_t[]){0x17,0x08}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x70, (uint8_t[]){0x4F,0x4F}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x71, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x72, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB5, (uint8_t[]){0x50}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xBA, (uint8_t[]){0x00}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEC, (uint8_t[]){0x71}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7b, (uint8_t[]){0x00,0x0d}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7c, (uint8_t[]){0x0d,0x03}, 2), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF5, (uint8_t[]){0x02,0x10,0x12}, 3), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF0, (uint8_t[]){0x0C,0x11,0x0b,0x0a,0x05,0x32,0x44,0x8e,0x9a,0x29,0x2E,0x5f}, 12), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF1, (uint8_t[]){0x0B,0x11,0x0b,0x07,0x07,0x32,0x45,0xBd,0x8D,0x21,0x28,0xAf}, 12), TAG, "DINV failed"); + + // 240x296 resolution settings + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2a, (uint8_t[]){0x00,0x00,0x00,0xef}, 4), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2b, (uint8_t[]){0x00,0x00,0x01,0x27}, 4), TAG, "DINV failed"); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x66, (uint8_t[]){0x2C}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x67, (uint8_t[]){0x18}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x68, (uint8_t[]){0x3E}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCA, (uint8_t[]){0x0E}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCB, (uint8_t[]){0x06}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB6, (uint8_t[]){0x5C,0x40,0x40}, 3), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCC, (uint8_t[]){0x33}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCD, (uint8_t[]){0x33}, 1), TAG, "DINV failed"); + + // Sleep out command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed"); + vTaskDelay(pdMS_TO_TICKS(80)); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xA0}, 1), TAG, "DINV failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed"); + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xfe, NULL, 0), TAG, "unlock cmd1 failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xee, NULL, 0), TAG, "unlock cmd2 failed"); + + // Display on command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed"); + + // Memory write command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2c, NULL, 0), TAG, "Memory write failed"); + vTaskDelay(pdMS_TO_TICKS(10)); + return ESP_OK; + } + + + static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + + + esp_lcd_panel_io_handle_t io = gc9309->io; + + x_start += gc9309->x_gap; + x_end += gc9309->x_gap; + y_start += gc9309->y_gap; + y_end += gc9309->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "io tx param failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "io tx param failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * gc9309->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "io tx color failed"); + + return ESP_OK; + } + + static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9309->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, + "io tx param failed"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) + { + // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + // esp_lcd_panel_io_handle_t io = gc9309->io; + // if (mirror_x) { + // gc9309->madctl_val |= LCD_CMD_MX_BIT; + // } else { + // gc9309->madctl_val &= ~LCD_CMD_MX_BIT; + // } + // if (mirror_y) { + // gc9309->madctl_val |= LCD_CMD_MY_BIT; + // } else { + // gc9309->madctl_val &= ~LCD_CMD_MY_BIT; + // } + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + // gc9309->madctl_val + // }, 1), TAG, "io tx param failed"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) + { + // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + // esp_lcd_panel_io_handle_t io = gc9309->io; + // if (swap_axes) { + // gc9309->madctl_val |= LCD_CMD_MV_BIT; + // } else { + // gc9309->madctl_val &= ~LCD_CMD_MV_BIT; + // } + // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + // gc9309->madctl_val + // }, 1), TAG, "io tx param failed"); + return ESP_OK; + } + + static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + gc9309->x_gap = x_gap; + gc9309->y_gap = y_gap; + return ESP_OK; + } + + static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool on_off) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + uint8_t cmd = on_off ? GC9309NA_CMD_DISPON : GC9309NA_CMD_DISPOFF; + return esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0); + } + + static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep) + { + gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base); + uint8_t cmd = sleep ? GC9309NA_CMD_SLPIN : GC9309NA_CMD_SLPOUT; + esp_err_t ret = esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0); + vTaskDelay(pdMS_TO_TICKS(120)); + return ret; + } \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h new file mode 100644 index 00000000..0a7065b8 --- /dev/null +++ b/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "esp_lcd_panel_dev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create LCD panel for model ST7789 + * + * @param[in] io LCD panel IO handle + * @param[in] panel_dev_config general panel device configuration + * @param[out] ret_panel Returned LCD panel handle + * @return + * - ESP_ERR_INVALID_ARG if parameter is invalid + * - ESP_ERR_NO_MEM if out of memory + * - ESP_OK on success + */ +esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); + +#ifdef __cplusplus +} +#endif diff --git a/main/boards/jiuchuan-s3/gpio_manager.h b/main/boards/jiuchuan-s3/gpio_manager.h new file mode 100644 index 00000000..f8e83ff6 --- /dev/null +++ b/main/boards/jiuchuan-s3/gpio_manager.h @@ -0,0 +1,62 @@ +#pragma once +#include +#include +#include +#include + +class GpioManager { +public: + enum class GpioMode { + INPUT, + OUTPUT, + INPUT_PULLUP, + INPUT_PULLDOWN + }; + + static void SetLevel(gpio_num_t gpio, uint32_t level) { + std::lock_guard lock(mutex_); + ESP_ERROR_CHECK(gpio_set_level(gpio, level)); + ESP_LOGD("GpioManager", "Set GPIO %d level: %d", static_cast(gpio), static_cast(level)); + } + + static int GetLevel(gpio_num_t gpio) { + std::lock_guard lock(mutex_); + int level = gpio_get_level(gpio); + ESP_LOGD("GpioManager", "Get GPIO %d level: %d", static_cast(gpio), level); + return level; + } + + static void Config(gpio_num_t gpio, GpioMode mode) { + std::lock_guard lock(mutex_); + + gpio_config_t config = {}; + config.pin_bit_mask = (1ULL << gpio); + + switch(mode) { + case GpioMode::INPUT: + config.mode = GPIO_MODE_INPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_DISABLE; + break; + case GpioMode::OUTPUT: + config.mode = GPIO_MODE_OUTPUT; + break; + case GpioMode::INPUT_PULLUP: + config.mode = GPIO_MODE_INPUT; + config.pull_up_en = GPIO_PULLUP_ENABLE; + break; + case GpioMode::INPUT_PULLDOWN: + config.mode = GPIO_MODE_INPUT; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + break; + } + + ESP_ERROR_CHECK(gpio_config(&config)); + ESP_LOGI("GpioManager", "Configured GPIO %d mode: %d", gpio, static_cast(mode)); + } + +private: + static std::mutex mutex_; +}; + +std::mutex GpioManager::mutex_; \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc new file mode 100644 index 00000000..a24364c8 --- /dev/null +++ b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc @@ -0,0 +1,354 @@ +#include "wifi_board.h" +#include "audio_codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "iot/thing_manager.h" + +#include +#include +#include +#include +#include +#include "led/single_led.h" +#include "assets/lang_config.h" +#include "esp_lcd_panel_gc9301.h" + +#include "power_save_timer.h" +#include "power_manager.h" +#include "power_controller.h" +#include "gpio_manager.h" +#include +#include + +#define TAG "JiuchuanDevBoard" +#define __USER_GPIO_PWRDOWN__ + +LV_FONT_DECLARE(font_puhui_20_4); +LV_FONT_DECLARE(font_awesome_20_4); + +class JiuchuanDevBoard : public WifiBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + Button pwr_button_; + Button wifi_button; + Button cmd_button; + LcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + esp_lcd_panel_io_handle_t panel_io = NULL; + esp_lcd_panel_handle_t panel = NULL; + + void InitializePowerManager() { + power_manager_ = new PowerManager(PWR_ADC_GPIO); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + #ifndef __USER_GPIO_PWRDOWN__ + RTC_DATA_ATTR static bool long_press_occurred = false; + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + if (cause == ESP_SLEEP_WAKEUP_EXT0) { + ESP_LOGI(TAG, "Wake up by EXT0"); + const int64_t start = esp_timer_get_time(); + ESP_LOGI(TAG, "esp_sleep_get_wakeup_cause"); + while (gpio_get_level(PWR_BUTTON_GPIO) == 0) { + if (esp_timer_get_time() - start > 3000000) { + long_press_occurred = true; + break; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + } + + if (long_press_occurred) { + ESP_LOGI(TAG, "Long press wakeup"); + long_press_occurred = false; + } else { + ESP_LOGI(TAG, "Short press, return to sleep"); + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); + ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉 + ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO)); + esp_deep_sleep_start(); + } + } + #endif + //一分钟进入浅睡眠,5分钟进入深睡眠关机 + power_save_timer_ = new PowerSaveTimer(-1, (60*10), (60*30)); + // power_save_timer_ = new PowerSaveTimer(-1, 6, 10);//test + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + display_->SetChatMessage("system", ""); + display_->SetEmotion("sleepy"); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + display_->SetChatMessage("system", ""); + display_->SetEmotion("neutral"); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + #ifndef __USER_GPIO_PWRDOWN__ + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0)); + ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 内部上拉 + ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO)); + + esp_lcd_panel_disp_on_off(panel, false); //关闭显示 + esp_deep_sleep_start(); + #else + rtc_gpio_set_level(PWR_EN_GPIO, 0); + rtc_gpio_hold_dis(PWR_EN_GPIO); + #endif + }); + power_save_timer_->SetEnabled(true); + } + + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + } + + void InitializeButtons() { + auto& powerCtrl = PowerController::Instance(); + powerCtrl.SetState(PowerController::PowerState::ACTIVE); // 确保初始状态为ACTIVE + + // 配置GPIO + ESP_LOGI(TAG, "Configuring power button GPIO"); + GpioManager::Config(GPIO_NUM_3, GpioManager::GpioMode::INPUT_PULLDOWN); + GpioManager::Config(PWR_EN_GPIO, GpioManager::GpioMode::OUTPUT); + GpioManager::SetLevel(PWR_EN_GPIO, 1); // 确保电源使能 + + boot_button_.OnClick([this]() { + ESP_LOGI(TAG, "Boot button clicked"); + power_save_timer_->WakeUp(); + }); + + // 检查电源按钮初始状态 + ESP_LOGI(TAG, "Power button initial state: %d", GpioManager::GetLevel(PWR_BUTTON_GPIO)); + + // 高电平有效长按关机逻辑 + pwr_button_.OnLongPress([&powerCtrl]() { + ESP_LOGI(TAG, "Power button long press detected (high-active)"); + + // 高电平有效防抖确认 + for (int i = 0; i < 5; i++) { + int level = GpioManager::GetLevel(PWR_BUTTON_GPIO); + ESP_LOGD(TAG, "Debounce check %d: GPIO%d level=%d", i+1, PWR_BUTTON_GPIO, level); + + if (level == 0) { + ESP_LOGW(TAG, "Power button inactive during confirmation - abort shutdown"); + return; + } + vTaskDelay(100 / portTICK_PERIOD_MS); + } + ESP_LOGI(TAG, "Confirmed power button pressed (level=1)"); + + ESP_LOGI(TAG, "Confirmed power button pressed - initiating shutdown"); + powerCtrl.SetState(PowerController::PowerState::SHUTDOWN); + + // 确保状态变更 + if (powerCtrl.GetState() != PowerController::PowerState::SHUTDOWN) { + ESP_LOGE(TAG, "Failed to set shutdown state!"); + } + }); + + wifi_button.OnClick([this, &powerCtrl]() { + ESP_LOGI(TAG, "Wifi button clicked"); + power_save_timer_->WakeUp(); + + if (powerCtrl.GetState() == PowerController::PowerState::ACTIVE) { + ESP_LOGI(TAG, "Resetting WiFi configuration"); + GpioManager::SetLevel(PWR_EN_GPIO, 1); + ResetWifiConfiguration(); + } + }); + + cmd_button.OnClick([this]() { + ESP_LOGI(TAG, "Command button clicked"); + power_save_timer_->WakeUp(); + Application::GetInstance().ToggleChatState(); + }); + + // 配置电源状态变更回调(优化版) + powerCtrl.OnStateChange([this](PowerController::PowerState newState) { + switch(newState) { + case PowerController::PowerState::SHUTDOWN: { + ESP_LOGI(TAG, "Entering shutdown sequence"); + + // 统一唤醒触发条件 + #ifndef __USER_GPIO_PWRDOWN__ + ESP_LOGD(TAG, "Configuring high-level wakeup on GPIO%d", PWR_BUTTON_GPIO); + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 1)); // 高电平唤醒 + #else + ESP_LOGD(TAG, "Powering down via GPIO control"); + GpioManager::SetLevel(PWR_EN_GPIO, 0); + #endif + + // 确保所有外设已关闭 + vTaskDelay(200 / portTICK_PERIOD_MS); + ESP_LOGI(TAG, "Initiating deep sleep"); + + // 最后状态确认(通过单例访问) + if (PowerController::Instance().GetState() != PowerController::PowerState::SHUTDOWN) { + ESP_LOGE(TAG, "State inconsistency! Forcing shutdown"); + } + esp_deep_sleep_start(); + break; + } + + default: + ESP_LOGD(TAG, "State changed to %d", static_cast(newState)); + break; + } + }); + + } + + void InitializeGC9301isplay() { + // 液晶屏控制IO初始化 + ESP_LOGI(TAG, "test Install panel IO"); + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; + buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + + // 初始化SPI总线 + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 3; + io_config.pclk_hz = 80 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io); + + // 初始化液晶屏驱动芯片9309 + ESP_LOGI(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ENDIAN_BGR; + panel_config.bits_per_pixel = 16; + esp_lcd_new_panel_gc9309na(panel_io, &panel_config, &panel); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, + { + .text_font = &font_puhui_20_4, + .icon_font = &font_awesome_20_4, +#if CONFIG_USE_WECHAT_MESSAGE_STYLE + .emoji_font = font_emoji_32_init(), +#else + .emoji_font = font_emoji_64_init(), +#endif + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeIot() { + auto& thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("Speaker")); + thing_manager.AddThing(iot::CreateThing("Screen")); + thing_manager.AddThing(iot::CreateThing("Battery")); + } +public: + JiuchuanDevBoard() : + boot_button_(BOOT_BUTTON_GPIO), + pwr_button_(PWR_BUTTON_GPIO,true), + wifi_button(WIFI_BUTTON_GPIO), + cmd_button(CMD_BUTTON_GPIO) { + InitializeI2c(); + InitializePowerManager(); + InitializePowerSaveTimer(); + InitializeButtons(); + InitializeGC9301isplay(); + InitializeIot(); + GetBacklight()->RestoreBrightness(); + + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + + static Es8311AudioCodec audio_codec( + codec_i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + static bool last_discharging = false; + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + if (discharging != last_discharging) { + power_save_timer_->SetEnabled(discharging); + last_discharging = discharging; + } + level = power_manager_->GetBatteryLevel(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(JiuchuanDevBoard); \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/power_controller.h b/main/boards/jiuchuan-s3/power_controller.h new file mode 100644 index 00000000..1b26d949 --- /dev/null +++ b/main/boards/jiuchuan-s3/power_controller.h @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include +#include + +class PowerController { +public: + enum class PowerState { + ACTIVE, + LIGHT_SLEEP, + DEEP_SLEEP, + SHUTDOWN + }; + + static PowerController& Instance() { + static PowerController instance; + return instance; + } + + void SetState(PowerState newState) { + std::lock_guard lock(mutex_); + if (currentState_ != newState) { + ESP_LOGI("PowerCtrl", "State change: %d -> %d", + static_cast(currentState_), + static_cast(newState)); + + currentState_ = newState; + if (stateChangeCallback_) { + stateChangeCallback_(newState); + } + } + } + + PowerState GetState() const { + std::lock_guard lock(mutex_); + return currentState_; + } + + void OnStateChange(std::function callback) { + stateChangeCallback_ = callback; + } + +private: + PowerController() = default; + ~PowerController() = default; + + PowerState currentState_ = PowerState::ACTIVE; + std::function stateChangeCallback_; + mutable std::mutex mutex_; +}; \ No newline at end of file diff --git a/main/boards/jiuchuan-s3/power_manager.h b/main/boards/jiuchuan-s3/power_manager.h new file mode 100644 index 00000000..42d8cb88 --- /dev/null +++ b/main/boards/jiuchuan-s3/power_manager.h @@ -0,0 +1,149 @@ +#pragma once +#include +#include + +#include +#include +#include +#include "adc_battery_estimation.h" + +#define JIUCHUAN_ADC_UNIT (ADC_UNIT_1) +#define JIUCHUAN_ADC_BITWIDTH (ADC_BITWIDTH_12) +#define JIUCHUAN_ADC_ATTEN (ADC_ATTEN_DB_12) +#define JIUCHUAN_ADC_CHANNEL (ADC_CHANNEL_3) +#define JIUCHUAN_RESISTOR_UPPER (200000) +#define JIUCHUAN_RESISTOR_LOWER (100000) + +class PowerManager { +private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; + int ticks_ = 0; + const int kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_battery_estimation_handle_t adc_battery_estimation_handle; + + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + float battery_capacity_temp = 0; + adc_battery_estimation_get_capacity(adc_battery_estimation_handle, &battery_capacity_temp); + ESP_LOGI("PowerManager", "Battery level: %.1f%%", battery_capacity_temp); + battery_level_ = battery_capacity_temp; + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + static const battery_point_t battery_ponint_table[]={ + { 4.2 , 100}, + { 4.06 , 80}, + { 3.82 , 60}, + { 3.58 , 40}, + { 3.34 , 20}, + { 3.1 , 0} + }; + + adc_battery_estimation_t config = { + .internal = { + .adc_unit = JIUCHUAN_ADC_UNIT, + .adc_bitwidth = JIUCHUAN_ADC_BITWIDTH, + .adc_atten = JIUCHUAN_ADC_ATTEN, + }, + .adc_channel = JIUCHUAN_ADC_CHANNEL, + .upper_resistor = JIUCHUAN_RESISTOR_UPPER, + .lower_resistor = JIUCHUAN_RESISTOR_LOWER, + .battery_points = battery_ponint_table, + .battery_points_count = sizeof(battery_ponint_table) / sizeof(battery_ponint_table[0]) + }; + + adc_battery_estimation_handle = adc_battery_estimation_create(&config); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + } + + bool IsCharging() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + return !is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; + } +}; \ No newline at end of file diff --git a/main/idf_component.yml b/main/idf_component.yml index 16b2c73b..ad75649c 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -72,3 +72,4 @@ dependencies: ## Required IDF version idf: version: '>=5.4.0' + espressif/adc_battery_estimation: =*