diff --git a/CMakeLists.txt b/CMakeLists.txt index a64d4e8bf..b81c90bde 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,3 +12,11 @@ add_compile_options(-Wno-missing-field-initializers) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(xiaozhi) +# 项目根目录的 CMakeLists.txt +set(PROJECT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +message(STATUS "项目根目录: ${PROJECT_ROOT_DIR}") + + +# 添加 ESP-IDF 组件路径 +set(EXTRA_COMPONENT_DIRS ${CMAKE_SOURCE_DIR}/components) +list(APPEND EXTRA_COMPONENT_DIRS ${CMAKE_SOURCE_DIR}/managed_components) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 5fdd5726b..30fb49584 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -138,6 +138,8 @@ elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX) set(BOARD_TYPE "doit-s3-aibox") elseif(CONFIG_BOARD_TYPE_ESP32_CGC) set(BOARD_TYPE "esp32-cgc") +elseif(CONFIG_BOARD_TYPE_Freenove_Media_Kit) + set(BOARD_TYPE "freenove-media-kit") endif() file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc @@ -214,3 +216,46 @@ add_custom_command( add_custom_target(lang_header ALL DEPENDS ${LANG_HEADER} ) + + + +message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}") +message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}") +message(STATUS "IDF_PATH: ${IDF_PATH}") +message(STATUS "PROJECT_NAME: ${PROJECT_NAME}") + +# main/CMakeLists.txt + +# 定义按钮组件的路径 +set(BUTTON_COMPONENT_DIR "${CMAKE_SOURCE_DIR}/managed_components/espressif__button") + +# 检查组件是否存在 +if(NOT EXISTS "${BUTTON_COMPONENT_DIR}/idf_component.yml") + message(FATAL_ERROR "Button component missing! Path: ${BUTTON_COMPONENT_DIR}") +endif() + +# 读取版本信息 +file(READ "${BUTTON_COMPONENT_DIR}/idf_component.yml" BUTTON_YML_CONTENT) +string(REGEX MATCH "version:[ \t]*v?([0-9]+\\.[0-9]+\\.[0-9]+)" + BUTTON_VERSION_MATCH "${BUTTON_YML_CONTENT}") + +if(BUTTON_VERSION_MATCH) + set(BUTTON_VERSION "${CMAKE_MATCH_1}") # 提取 4.1.2 + string(REPLACE "." ";" BUTTON_VERSION_LIST "${BUTTON_VERSION}") + list(GET BUTTON_VERSION_LIST 0 BUTTON_VERSION_MAJOR) + list(GET BUTTON_VERSION_LIST 1 BUTTON_VERSION_MINOR) + list(GET BUTTON_VERSION_LIST 2 BUTTON_VERSION_PATCH) + + # 传递宏到代码 + target_compile_definitions(${COMPONENT_LIB} PUBLIC + BUTTON_VERSION_MAJOR=${BUTTON_VERSION_MAJOR} + BUTTON_VERSION_MINOR=${BUTTON_VERSION_MINOR} + BUTTON_VERSION_PATCH=${BUTTON_VERSION_PATCH} + BUTTON_VERSION_STR="\"v${BUTTON_VERSION}\"" + ) + message(STATUS "Button 组件版本: v${BUTTON_VERSION}") +else() + message(WARNING "Button 组件版本解析失败!") +endif() + + diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 5fe196b31..5f7f9227c 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -158,6 +158,8 @@ choice BOARD_TYPE bool "无名科技星智1.54(ML307)" config BOARD_TYPE_SENSECAP_WATCHER bool "SenseCAP Watcher" + config BOARD_TYPE_Freenove_Media_Kit + bool "Freenove Media Kit for ESP32-S3" config BOARD_TYPE_DOIT_S3_AIBOX bool "四博智联AI陪伴盒子" endchoice diff --git a/main/boards/common/button.cc b/main/boards/common/button.cc index feb1ce340..44bc77e76 100644 --- a/main/boards/common/button.cc +++ b/main/boards/common/button.cc @@ -3,6 +3,7 @@ #include static const char* TAG = "Button"; +#if BUTTON_VERSION_MAJOR == 3 #if CONFIG_SOC_ADC_SUPPORTED Button::Button(const button_adc_config_t& adc_cfg) { button_config_t button_config = { @@ -109,3 +110,109 @@ void Button::OnDoubleClick(std::function callback) { } }, this); } + +#elif BUTTON_VERSION_MAJOR >= 4 + +#if CONFIG_SOC_ADC_SUPPORTED +Button::Button(const button_adc_config_t& adc_cfg) { + button_config_t button_config = { + .long_press_time = 1000, + .short_press_time = 50, + }; + esp_err_t err = iot_button_new_adc_device(&button_config, &adc_cfg, &button_handle_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ADC Button create failed"); + return; + } +} +#endif +Button::Button(gpio_num_t gpio_num, bool active_high) : gpio_num_(gpio_num) { + if (gpio_num == GPIO_NUM_NC) { + return; + } + button_gpio_config_t btn_gpio_cfg = { + .gpio_num = gpio_num, + .active_level = static_cast(active_high ? 1 : 0) + }; + button_config_t button_config = { + .long_press_time = 1000, + .short_press_time = 50, + }; + esp_err_t ret = iot_button_new_gpio_device(&button_config, &btn_gpio_cfg, &button_handle_); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "GPIO Button create failed"); + return; + } +} + +Button::~Button() { + if (button_handle_ != NULL) { + iot_button_delete(button_handle_); + } +} + +void Button::OnPressDown(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_press_down_ = callback; + iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, NULL, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_press_down_) { + button->on_press_down_(); + } + }, this); +} + +void Button::OnPressUp(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_press_up_ = callback; + iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, NULL, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_press_up_) { + button->on_press_up_(); + } + }, this); +} + +void Button::OnLongPress(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_long_press_ = callback; + iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, NULL, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_long_press_) { + button->on_long_press_(); + } + }, this); +} + +void Button::OnClick(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_click_ = callback; + iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, NULL, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_click_) { + button->on_click_(); + } + }, this); +} + +void Button::OnDoubleClick(std::function callback) { + if (button_handle_ == nullptr) { + return; + } + on_double_click_ = callback; + iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, NULL, [](void* handle, void* usr_data) { + Button* button = static_cast(usr_data); + if (button->on_double_click_) { + button->on_double_click_(); + } + }, this); +} +#endif \ No newline at end of file diff --git a/main/boards/common/button.h b/main/boards/common/button.h index d2e44fd3a..5424c10c5 100644 --- a/main/boards/common/button.h +++ b/main/boards/common/button.h @@ -1,6 +1,8 @@ #ifndef BUTTON_H_ #define BUTTON_H_ +#include +#include #include #include #include diff --git a/main/boards/freenove-media-kit/ReadMe.md b/main/boards/freenove-media-kit/ReadMe.md new file mode 100644 index 000000000..c6bfcb09d --- /dev/null +++ b/main/boards/freenove-media-kit/ReadMe.md @@ -0,0 +1,59 @@ +# Freenove Media Kit for ESP32-S3 + +> 一基于ESP32-S3的多媒体开发设备 + +media-kit-mini-top + +media-kit-mini-sideview + +Freenove Media Kit 是一个传统的多媒体设备,用于ESP32-S3的开发学习。具有以下外设: + +- 摄像头 + +- TF卡槽 + +- WS2812B彩灯 + +- I2S接口的硅麦 + +- I2S接口的音频解码器+功放 + +- 4Ω 1W扬声器 + +- 3.5mm耳机插座 + +- PH2.0接口的3.7V锂电池插座,带有充电功能 + +- 1.14英寸TFT屏幕 + +- 采用ADC输入的五向按键。 + + + +目前基础示例: + +- Sketch_01_LedPixel_RGBW +- Sketch_02_1_Button_Value +- Sketch_02_2_Button +- Sketch_03_SDMMC_Test +- Sketch_04_Simple_Tone +- Sketch_05_Play_MP3 +- Sketch_06_Video_Web_Server +- Sketch_07_TFT_Clock +- Sketch_08_1_Camera_TFT_Show +- Sketch_08_2_Take_A_Photo +- Sketch_09_1_Record_To_WAV +- Sketch_09_2_Record_And_Play +- Sketch_09_3_Record_And_Play +- Sketch_10_ESP32_SR +- Sketch_11_LVGL +- Sketch_12_LVGL_LedPixel +- Sketch_13_LVGL_Camera +- Sketch_14_Lvgl_Picture +- Sketch_15_Lvgl_Music +- Sketch_16_Lvgl_Sound_Recorder +- Sketch_17_Lvgl_Multifunctionality +- Sketch_18_Lvgl_Multifunctionality + +[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32) 是一个非常优秀的开源项目,因此为Freenove Media Kit进行了适配,在未来的开发中,也许会将自身的项目与小智项目进行合并。 + diff --git a/main/boards/freenove-media-kit/_static/ReadMe/media-kit-mini-sideview.jpg b/main/boards/freenove-media-kit/_static/ReadMe/media-kit-mini-sideview.jpg new file mode 100644 index 000000000..ee0fb07aa Binary files /dev/null and b/main/boards/freenove-media-kit/_static/ReadMe/media-kit-mini-sideview.jpg differ diff --git a/main/boards/freenove-media-kit/_static/ReadMe/media-kit-mini-top.jpg b/main/boards/freenove-media-kit/_static/ReadMe/media-kit-mini-top.jpg new file mode 100644 index 000000000..91be7ad1c Binary files /dev/null and b/main/boards/freenove-media-kit/_static/ReadMe/media-kit-mini-top.jpg differ diff --git a/main/boards/freenove-media-kit/config.h b/main/boards/freenove-media-kit/config.h new file mode 100644 index 000000000..2b92fb210 --- /dev/null +++ b/main/boards/freenove-media-kit/config.h @@ -0,0 +1,40 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_14 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_3 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_46 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_1 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_42 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_41 + +#define BUILTIN_LED_GPIO GPIO_NUM_48 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define TOUCH_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 +#define DISPLAY_MOSI_PIN GPIO_NUM_21 +#define DISPLAY_CLK_PIN GPIO_NUM_47 +#define DISPLAY_DC_PIN GPIO_NUM_45 +#define DISPLAY_RST_PIN GPIO_NUM_NC +#define DISPLAY_CS_PIN GPIO_NUM_NC + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 135 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 40 +#define DISPLAY_OFFSET_Y 53 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/freenove-media-kit/config.json b/main/boards/freenove-media-kit/config.json new file mode 100644 index 000000000..e930c530c --- /dev/null +++ b/main/boards/freenove-media-kit/config.json @@ -0,0 +1,9 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "freenove-media-kit", + "sdkconfig_append": [] + } + ] +} \ No newline at end of file diff --git a/main/boards/freenove-media-kit/freenove-media-kit.cc b/main/boards/freenove-media-kit/freenove-media-kit.cc new file mode 100644 index 000000000..6ecd674c2 --- /dev/null +++ b/main/boards/freenove-media-kit/freenove-media-kit.cc @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include + +#include "application.h" +#include "audio_codecs/no_audio_codec.h" +#include "button.h" +#include "config.h" +#include "display/lcd_display.h" +#include "iot/thing_manager.h" +#include "led/single_led.h" +#include "system_reset.h" +#include "wifi_board.h" + +#define TAG "FreenoveMediaKit" + +LV_FONT_DECLARE(font_puhui_16_4); +LV_FONT_DECLARE(font_awesome_16_4); + +#define LCD_SPI_HOST SPI3_HOST +#define LCD_RST_PIN GPIO_NUM_20 + +button_adc_config_t adc_btn_config0 = { + .unit_id = ADC_UNIT_2, + .adc_channel = ADC_CHANNEL_8, + .button_index = 0, + .min = 0, + .max = 660 * 0 + 100, +}; +button_adc_config_t adc_btn_config1 = { + .unit_id = ADC_UNIT_2, + .adc_channel = ADC_CHANNEL_8, + .button_index = 1, + .min = 660 * 1 - 100, + .max = 660 * 1 + 100, +}; +button_adc_config_t adc_btn_config2 = { + .unit_id = ADC_UNIT_2, + .adc_channel = ADC_CHANNEL_8, + .button_index = 2, + .min = 660 * 2 - 100, + .max = 660 * 2 + 100, +}; +button_adc_config_t adc_btn_config3 = { + .unit_id = ADC_UNIT_2, + .adc_channel = ADC_CHANNEL_8, + .button_index = 3, + .min = 660 * 3 - 100, + .max = 660 * 3 + 100, +}; +button_adc_config_t adc_btn_config4 = { + .unit_id = ADC_UNIT_2, + .adc_channel = ADC_CHANNEL_8, + .button_index = 4, + .min = 660 * 4 - 100, + .max = 660 * 4 + 100, +}; + +class FreenoveMediaKit : public WifiBoard { + private: + Button boot_button_; + LcdDisplay *display_; + Button adcButton0; + Button adcButton1; + Button adcButton2; + Button adcButton3; + Button adcButton4; + + void resetLcd() { + // 配置引脚为输出模式,并设置默认电平为低 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << LCD_RST_PIN); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + gpio_set_level(LCD_RST_PIN, 0); // 设置引脚为低电平 + vTaskDelay(pdMS_TO_TICKS(10)); + // vTaskDelay(50/portTICK_RATE_MS) + gpio_set_level(LCD_RST_PIN, 1); + vTaskDelay(pdMS_TO_TICKS(10)); + // 配置RST引脚为开漏输入模式 + io_conf.mode = GPIO_MODE_INPUT_OUTPUT_OD; + // io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + gpio_config(&io_conf); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + 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(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = 3; + io_config.pclk_hz = 1 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + // io_config.on_color_trans_done. + ESP_ERROR_CHECK( + esp_lcd_new_panel_io_spi(LCD_SPI_HOST, &io_config, &panel_io)); + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + ESP_LOGI(TAG, "Install LCD driver ST7789"); + esp_lcd_panel_reset(panel); + resetLcd(); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + 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_16_4, + .icon_font = &font_awesome_16_4, + .emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() + : font_emoji_32_init(), + }); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto &app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + } + + void InitializeAdcButtons() { + adcButton0.OnClick([this]() { ESP_LOGI(TAG, "adcButton0 Click"); }); + adcButton1.OnClick([this]() { ESP_LOGI(TAG, "adcButton1 Click"); }); + adcButton2.OnClick([this]() { ESP_LOGI(TAG, "adcButton2 Click"); }); + adcButton3.OnClick([this]() { ESP_LOGI(TAG, "adcButton3 Click"); }); + adcButton4.OnClick([this]() { ESP_LOGI(TAG, "adcButton4 Click"); }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeIot() { + auto &thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("Speaker")); + thing_manager.AddThing(iot::CreateThing("Screen")); + } + + public: + FreenoveMediaKit() + : boot_button_(BOOT_BUTTON_GPIO), + adcButton0(adc_btn_config0), + adcButton1(adc_btn_config1), + adcButton2(adc_btn_config2), + adcButton3(adc_btn_config3), + adcButton4(adc_btn_config4) { + InitializeSpi(); + InitializeLcdDisplay(); + InitializeButtons(); + InitializeAdcButtons(); + InitializeIot(); + GetBacklight()->SetBrightness(100); + } + + virtual Led *GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec *GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec( + AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, + AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_RIGHT, AUDIO_I2S_MIC_GPIO_SCK, + AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); + + 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; + } +}; + +DECLARE_BOARD(FreenoveMediaKit); diff --git a/main/idf_component.yml b/main/idf_component.yml index 751e9b12b..d2e17bc17 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -15,10 +15,10 @@ dependencies: espressif/led_strip: "^2.4.1" espressif/esp_codec_dev: "~1.3.2" espressif/esp-sr: "^2.0.3" - espressif/button: "^3.3.1" + espressif/button: "^4.1.2" espressif/knob: "^1.0.0" lvgl/lvgl: "~9.2.2" - esp_lvgl_port: "~2.4.4" + esp_lvgl_port: "~2.5.0" espressif/esp_io_expander_tca95xx_16bit: "^2.0.0" tny-robotics/sh1106-esp-idf: version: "^1.0.0"