Compare commits

...

5 Commits

Author SHA1 Message Date
Terrence
737333e1a6 Add bed-operator board 2026-01-06 20:42:52 +08:00
Terrence
89a06eda81 fix: esp_psram_get_size not found in c3 2026-01-06 15:11:31 +08:00
Terrence
846d7afb80 use MAIN_EVENT_CLOCK_TICK to avoid audio glitches 2026-01-06 15:11:31 +08:00
Terrence
855e8c09b4 Add image cache 2026-01-06 15:11:31 +08:00
laride
be88719932 feat: Add ESP-SensairShuttle (#1620)
* feat: Add ESP-SensairShuttle

* fix: fix board name
2026-01-02 12:19:46 +08:00
11 changed files with 1216 additions and 2 deletions

View File

@@ -126,6 +126,11 @@ elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_S3)
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
set(BUILTIN_ICON_FONT font_awesome_20_4)
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_S3_BED_OPERATOR)
set(BOARD_TYPE "lichuang-dev-bed-operator")
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
set(BUILTIN_ICON_FONT font_awesome_20_4)
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_C3)
set(BOARD_TYPE "lichuang-c3-dev")
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
@@ -212,6 +217,11 @@ elseif(CONFIG_BOARD_TYPE_ECHOEAR)
set(BUILTIN_TEXT_FONT font_puhui_20_4)
set(BUILTIN_ICON_FONT font_awesome_20_4)
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ESP_SENSAIRSHUTTLE)
set(BOARD_TYPE "esp-sensairshuttle")
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
set(BUILTIN_ICON_FONT font_awesome_16_4)
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_AUDIO_BOARD)
set(BOARD_TYPE "waveshare-s3-audio-board")
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)

View File

@@ -150,6 +150,9 @@ choice BOARD_TYPE
config BOARD_TYPE_ESP_SPARKBOT
bool "Espressif SparkBot"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ESP_SENSAIRSHUTTLE
bool "Espressif ESP-SensairShuttle"
depends on IDF_TARGET_ESP32C5
config BOARD_TYPE_ESP_SPOT_S3
bool "Espressif Spot-S3"
depends on IDF_TARGET_ESP32S3
@@ -198,6 +201,9 @@ choice BOARD_TYPE
config BOARD_TYPE_LICHUANG_DEV_S3
bool "立创·实战派 ESP32-S3"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_LICHUANG_DEV_S3_BED_OPERATOR
bool "立创·实战派ESP32-S3开发板(床控)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_LICHUANG_DEV_C3
bool "立创·实战派 ESP32-C3"
depends on IDF_TARGET_ESP32C3
@@ -582,7 +588,7 @@ choice DISPLAY_STYLE
config USE_EMOTE_MESSAGE_STYLE
bool "Emote animation style"
depends on BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3
depends on BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_SENSAIRSHUTTLE
endchoice
choice WAKE_WORD_TYPE
@@ -657,7 +663,7 @@ config USE_DEVICE_AEC
bool "Enable Device-Side AEC"
default n
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE \
|| BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75 || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_83\
|| BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_LICHUANG_DEV_S3_BED_OPERATOR || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75 || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_83\
|| BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_2_06 || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_7B \
|| BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3 \
|| BOARD_TYPE_ECHOEAR || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_49)

View File

@@ -0,0 +1,39 @@
# ESP-SensairShuttle
## 简介
<div align="center">
<a href="https://docs.espressif.com/projects/esp-dev-kits/zh_CN/latest/esp32c5/esp-sensairshuttle/index.html">
<b> 开发版文档 </b>
</a>
|
<a href="#传感器--shuttleboard-子板支持">
<b> 传感器 & <i>ShuttleBoard</i> 文档 </b>
</a>
</div>
ESP-SensairShuttle 是乐鑫携手 Bosch Sensortec 面向**动作感知**与**大模型人机交互**场景联合推出的开发板。
ESP-SensairShuttle 主控采用乐鑫 ESP32-C5-WROOM-1-N16R8 模组,具有 2.4 & 5 GHz 双频 Wi-Fi 6 (802.11ax)、Bluetooth® 5 (LE)、Zigbee 及 Thread (802.15.4) 无线通信能力。
## 传感器 & _ShuttleBoard_ 子板支持
即将推出,敬请期待。
## 配置、编译命令
由于 ESP-SensairShuttle 需要配置较多的 sdkconfig 选项,推荐使用编译脚本编译。
**编译**
```bash
python ./scripts/release.py esp-sensairshuttle
```
如需手动编译,请参考 `main/boards/esp-sensairshuttle/config.json` 修改 menuconfig 对应选项。
**烧录**
```bash
idf.py flash
```

View File

@@ -0,0 +1,249 @@
#include "adc_pdm_audio_codec.h"
#include <esp_log.h>
#include <esp_timer.h>
#include <driver/i2c.h>
#include <driver/i2c_master.h>
#include <driver/i2s_tdm.h>
#include "adc_mic.h"
#include "driver/i2s_pdm.h"
#include "soc/gpio_sig_map.h"
#include "soc/io_mux_reg.h"
#include "hal/rtc_io_hal.h"
#include "hal/gpio_ll.h"
#include "settings.h"
#include "config.h"
static const char TAG[] = "AdcPdmAudioCodec";
#define BSP_I2S_GPIO_CFG(_dout) \
{ \
.clk = GPIO_NUM_NC, \
.dout = _dout, \
.invert_flags = { \
.clk_inv = false, \
}, \
}
/**
* @brief Mono Duplex I2S configuration structure
*
* This configuration is used by default in bsp_audio_init()
*/
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \
{ \
.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
.gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \
}
AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
input_reference_ = false;
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
uint8_t adc_channel[1] = {0};
adc_channel[0] = adc_mic_channel;
audio_codec_adc_cfg_t cfg = {
.handle = NULL,
.max_store_buf_size = 1024 * 2,
.conv_frame_size = 1024,
.unit_id = ADC_UNIT_1,
.adc_channel_list = adc_channel,
.adc_channel_num = sizeof(adc_channel) / sizeof(adc_channel[0]),
.sample_rate_hz = (uint32_t)input_sample_rate,
};
const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
esp_codec_dev_cfg_t codec_dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN,
.data_if = adc_if,
};
input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
if (!input_dev_) {
ESP_LOGE(TAG, "Failed to create codec device");
return;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
.rx_handle = NULL,
.tx_handle = tx_handle_,
};
const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
codec_dev_cfg.codec_if = NULL;
codec_dev_cfg.data_if = i2s_data_if;
output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
output_volume_ = 100;
if(pa_ctl != GPIO_NUM_NC) {
pa_ctrl_pin_ = pa_ctl;
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
}
gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
if(pdm_speak_n != GPIO_NUM_NC){
PIN_FUNC_SELECT(IO_MUX_GPIO10_REG, PIN_FUNC_GPIO);
gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号
gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
}
// 初始化输出定时器
esp_timer_create_args_t output_timer_args = {
.callback = &AdcPdmAudioCodec::OutputTimerCallback,
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "output_timer"
};
ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_));
ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
}
AdcPdmAudioCodec::~AdcPdmAudioCodec() {
// 删除定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
esp_timer_delete(output_timer_);
output_timer_ = nullptr;
}
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
esp_codec_dev_delete(output_dev_);
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
esp_codec_dev_delete(input_dev_);
}
void AdcPdmAudioCodec::SetOutputVolume(int volume) {
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
AudioCodec::SetOutputVolume(volume);
}
void AdcPdmAudioCodec::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
.sample_rate = (uint32_t)input_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioCodec::EnableInput(enable);
}
void AdcPdmAudioCodec::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
// 强制按板卡配置重配PDM TX时钟覆盖第三方库在set_fmt中的默认up_sample_fs
// 若通道已启用,先禁用再重配,最后再启用
ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_));
i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_);
clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 1);
}
// 启用输出时启动定时器
if (output_timer_) {
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
}
} else {
// 禁用输出时停止定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
}
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 0);
}
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
}
AudioCodec::EnableOutput(enable);
}
int AdcPdmAudioCodec::Read(int16_t* dest, int samples) {
if (input_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
}
return samples;
}
int AdcPdmAudioCodec::Write(const int16_t* data, int samples) {
if (output_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
// 重置输出定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
}
}
return samples;
}
void AdcPdmAudioCodec::Start() {
Settings settings("audio", false);
output_volume_ = settings.GetInt("output_volume", output_volume_);
if (output_volume_ <= 0) {
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
output_volume_ = 10;
}
EnableInput(true);
EnableOutput(true);
ESP_LOGI(TAG, "Audio codec started");
}
// 定时器回调函数实现
void AdcPdmAudioCodec::OutputTimerCallback(void* arg) {
AdcPdmAudioCodec* codec = static_cast<AdcPdmAudioCodec*>(arg);
if (codec && codec->output_enabled_) {
codec->EnableOutput(false);
}
}

View File

@@ -0,0 +1,37 @@
#ifndef _BOX_AUDIO_CODEC_H
#define _BOX_AUDIO_CODEC_H
#include "audio_codec.h"
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
#include <esp_timer.h>
class AdcPdmAudioCodec : public AudioCodec {
private:
esp_codec_dev_handle_t output_dev_ = nullptr;
esp_codec_dev_handle_t input_dev_ = nullptr;
gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC;
// 定时器相关成员变量
esp_timer_handle_t output_timer_ = nullptr;
static constexpr uint64_t TIMER_TIMEOUT_US = 120000; // 120ms = 120000us
// 定时器回调函数
static void OutputTimerCallback(void* arg);
virtual int Read(int16_t* dest, int samples) override;
virtual int Write(const int16_t* data, int samples) override;
public:
AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl);
virtual ~AdcPdmAudioCodec();
virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
void Start();
};
#endif // _BOX_AUDIO_CODEC_H

View File

@@ -0,0 +1,40 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_PDM_UPSAMPLE_FS 480
#define AUDIO_ADC_MIC_CHANNEL 5
#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_7
#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_8
#define AUDIO_PA_CTL_GPIO GPIO_NUM_1
#define BOOT_BUTTON_GPIO GPIO_NUM_28
#define DISPLAY_MOSI_PIN GPIO_NUM_23
#define DISPLAY_CLK_PIN GPIO_NUM_24
#define DISPLAY_DC_PIN GPIO_NUM_26
#define DISPLAY_RST_PIN GPIO_NUM_NC
#define DISPLAY_CS_PIN GPIO_NUM_25
#define LCD_TP_SCL GPIO_NUM_3
#define LCD_TP_SDA GPIO_NUM_2
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 284
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY true
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 36
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,28 @@
{
"target": "esp32c5",
"builds": [
{
"name": "esp-sensairshuttle",
"sdkconfig_append": [
"CONFIG_IDF_TARGET=\"esp32c5\"",
"CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6",
"CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n",
"CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n",
"CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0",
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
"CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768",
"CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048",
"CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y",
"CONFIG_SPIRAM=y",
"CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=3072",
"CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y",
"CONFIG_LWIP_IPV6=n",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_SR_WN_WN9S_HIESP=y",
"CONFIG_USE_EMOTE_MESSAGE_STYLE=y",
"CONFIG_FLASH_CUSTOM_ASSETS=y",
"CONFIG_CUSTOM_ASSETS_FILE=\"https://dl.espressif.com/AE/wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-echoear.bin\""
]
}
]
}

View File

@@ -0,0 +1,315 @@
#include "wifi_board.h"
#include "adc_pdm_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include "display/lcd_display.h"
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include "esp_lcd_ili9341.h"
#include "display/emote_display.h"
#include "assets/lang_config.h"
#include "anim_player.h"
#include "led_strip.h"
#include "driver/rmt_tx.h"
#include "i2c_device.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "sdkconfig.h"
constexpr char TAG[] = "ESP_SensairShuttle";
static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
// {cmd, { data }, data_size, delay_ms}
{0x11, NULL, 0, 120}, // Sleep Out
{0x36, (uint8_t []){0x00}, 1, 0}, // Memory Data Access Control
{0x3A, (uint8_t []){0x05}, 1, 0}, // Interface Pixel Format (16-bit)
{0xB2, (uint8_t []){0x0C, 0x0C, 0x00, 0x33, 0x33}, 5, 0}, // Porch Setting
{0xB7, (uint8_t []){0x05}, 1, 0}, // Gate Control
{0xBB, (uint8_t []){0x21}, 1, 0}, // VCOM Setting
{0xC0, (uint8_t []){0x2C}, 1, 0}, // LCM Control
{0xC2, (uint8_t []){0x01}, 1, 0}, // VDV and VRH Command Enable
{0xC3, (uint8_t []){0x15}, 1, 0}, // VRH Set
{0xC6, (uint8_t []){0x0F}, 1, 0}, // Frame Rate Control
{0xD0, (uint8_t []){0xA7}, 1, 0}, // Power Control 1
{0xD0, (uint8_t []){0xA4, 0xA1}, 2, 0}, // Power Control 1
{0xD6, (uint8_t []){0xA1}, 1, 0}, // Gate output GND in sleep mode
{
0xE0, (uint8_t [])
{
0xF0, 0x05, 0x0E, 0x08, 0x0A, 0x17, 0x39, 0x54,
0x4E, 0x37, 0x12, 0x12, 0x31, 0x37
}, 14, 0
}, // Positive Gamma Control
{
0xE1, (uint8_t [])
{
0xF0, 0x10, 0x14, 0x0D, 0x0B, 0x05, 0x39, 0x44,
0x4D, 0x38, 0x14, 0x14, 0x2E, 0x35
}, 14, 0
}, // Negative Gamma Control
{0xE4, (uint8_t []){0x23, 0x00, 0x00}, 3, 0}, // Gate position control
{0x21, NULL, 0, 0}, // Display Inversion On
{0x29, NULL, 0, 0}, // Display On
{0x2C, NULL, 0, 0}, // Memory Write
};
class Cst816d : public I2cDevice {
public:
struct TouchPoint_t {
int num = 0;
int x = -1;
int y = -1;
};
enum TouchEvent {
TOUCH_NONE,
TOUCH_PRESS,
TOUCH_RELEASE,
TOUCH_HOLD
};
Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr)
{
read_buffer_ = new uint8_t[6];
was_touched_ = false;
press_count_ = 0;
}
~Cst816d()
{
delete[] read_buffer_;
}
void UpdateTouchPoint()
{
ReadRegs(0x02, read_buffer_, 6);
tp_.num = read_buffer_[0] & 0x0F;
tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
}
const TouchPoint_t &GetTouchPoint()
{
return tp_;
}
TouchEvent CheckTouchEvent()
{
bool is_touched = (tp_.num > 0);
TouchEvent event = TOUCH_NONE;
if (is_touched && !was_touched_) {
// Press event (transition from not touched to touched)
press_count_++;
event = TOUCH_PRESS;
ESP_LOGI(TAG, "TOUCH PRESS - count: %d, x: %d, y: %d", press_count_, tp_.x, tp_.y);
} else if (!is_touched && was_touched_) {
// Release event (transition from touched to not touched)
event = TOUCH_RELEASE;
ESP_LOGI(TAG, "TOUCH RELEASE - total presses: %d", press_count_);
} else if (is_touched && was_touched_) {
// Continuous touch (hold)
event = TOUCH_HOLD;
ESP_LOGD(TAG, "TOUCH HOLD - x: %d, y: %d", tp_.x, tp_.y);
}
// Update previous state
was_touched_ = is_touched;
return event;
}
int GetPressCount() const
{
return press_count_;
}
void ResetPressCount()
{
press_count_ = 0;
}
private:
uint8_t* read_buffer_ = nullptr;
TouchPoint_t tp_;
// Touch state tracking
bool was_touched_;
int press_count_;
};
class EspSensairShuttle : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Cst816d* cst816d_;
Display* display_ = nullptr;
Button boot_button_;
void InitializeI2c()
{
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = LCD_TP_SDA,
.scl_io_num = LCD_TP_SCL,
.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, &i2c_bus_));
}
static void touch_event_task(void* arg)
{
Cst816d* touchpad = static_cast<Cst816d*>(arg);
if (touchpad == nullptr) {
ESP_LOGE(TAG, "Invalid touchpad pointer in touch_event_task");
vTaskDelete(NULL);
return;
}
while (true) {
touchpad->UpdateTouchPoint();
auto touch_event = touchpad->CheckTouchEvent();
if (touch_event == Cst816d::TOUCH_RELEASE) {
auto &app = Application::GetInstance();
auto &board = (EspSensairShuttle &)Board::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting) {
board.EnterWifiConfigMode();
} else {
app.ToggleChatState();
}
}
vTaskDelay(pdMS_TO_TICKS(50)); // Poll every 50ms
}
}
void InitializeCst816dTouchPad()
{
cst816d_ = new Cst816d(i2c_bus_, 0x15);
xTaskCreate(touch_event_task, "touch_task", 2 * 1024, cst816d_, 5, NULL);
}
void InitializeButtons()
{
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting) {
ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode");
EnterWifiConfigMode();
return;
}
app.ToggleChatState();
});
}
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 * 10 * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeLcdDisplay()
{
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
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 = DISPLAY_SPI_MODE;
io_config.pclk_hz = 40 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
ESP_LOGD(TAG, "Install LCD driver");
const ili9341_vendor_config_t vendor_config = {
.init_cmds = &vendor_specific_init[0],
.init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
};
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;
panel_config.vendor_config = (void *) &vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
esp_lcd_panel_set_gap(panel, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
ESP_LOGI(TAG, "LCD panel create success, %p", panel);
#ifdef CONFIG_USE_EMOTE_MESSAGE_STYLE
display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
#else
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, 0, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
#endif
}
public:
EspSensairShuttle() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeCst816dTouchPad();
InitializeButtons();
InitializeSpi();
InitializeLcdDisplay();
}
virtual AudioCodec* GetAudioCodec() override
{
static AdcPdmAudioCodec audio_codec(
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_ADC_MIC_CHANNEL,
AUDIO_PDM_SPEAK_P_GPIO,
AUDIO_PDM_SPEAK_N_GPIO,
AUDIO_PA_CTL_GPIO);
return &audio_codec;
}
virtual Display* GetDisplay() override
{
return display_;
}
Cst816d* GetTouchpad()
{
return cst816d_;
}
};
DECLARE_BOARD(EspSensairShuttle);

View File

@@ -0,0 +1,62 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#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_12
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
#define AUDIO_CODEC_USE_PCA9557
#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 AUDIO_CODEC_ES7210_ADDR 0x82
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
/* Camera pins */
#define CAMERA_PIN_PWDN GPIO_NUM_NC
#define CAMERA_PIN_RESET GPIO_NUM_NC
#define CAMERA_PIN_XCLK GPIO_NUM_5
#define CAMERA_PIN_SIOD GPIO_NUM_1
#define CAMERA_PIN_SIOC GPIO_NUM_2
#define CAMERA_PIN_D7 GPIO_NUM_9
#define CAMERA_PIN_D6 GPIO_NUM_4
#define CAMERA_PIN_D5 GPIO_NUM_6
#define CAMERA_PIN_D4 GPIO_NUM_15
#define CAMERA_PIN_D3 GPIO_NUM_17
#define CAMERA_PIN_D2 GPIO_NUM_8
#define CAMERA_PIN_D1 GPIO_NUM_18
#define CAMERA_PIN_D0 GPIO_NUM_16
#define CAMERA_PIN_VSYNC GPIO_NUM_3
#define CAMERA_PIN_HREF GPIO_NUM_46
#define CAMERA_PIN_PCLK GPIO_NUM_7
#define XCLK_FREQ_HZ 20000000
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,14 @@
{
"target": "esp32s3",
"builds": [
{
"name": "lichuang-dev-bed-operator",
"sdkconfig_append": [
"CONFIG_USE_DEVICE_AEC=y",
"CONFIG_CAMERA_GC0308=y",
"CONFIG_CAMERA_GC0308_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
"CONFIG_CAMERA_GC0308_DVP_YUV422_640X480_16FPS=y"
]
}
]
}

View File

@@ -0,0 +1,414 @@
#include "wifi_board.h"
#include "codecs/box_audio_codec.h"
#include "display/lcd_display.h"
#include "display/emote_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "esp32_camera.h"
#include "assets/lang_config.h"
#include "mcp_server.h"
#include <esp_log.h>
#include <esp_system.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <esp_lcd_touch_ft5x06.h>
#include <esp_lvgl_port.h>
#include <lvgl.h>
#include <thread>
#include <atomic>
#define TAG "LichuangDevBoard"
class Pca9557 : public I2cDevice {
public:
Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(0x01, 0x03);
WriteReg(0x03, 0xf8);
}
void SetOutputState(uint8_t bit, uint8_t level) {
uint8_t data = ReadReg(0x01);
data = (data & ~(1 << bit)) | (level << bit);
WriteReg(0x01, data);
}
};
class CustomAudioCodec : public BoxAudioCodec {
private:
Pca9557* pca9557_;
public:
CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557)
: BoxAudioCodec(i2c_bus,
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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8311_ADDR,
AUDIO_CODEC_ES7210_ADDR,
AUDIO_INPUT_REFERENCE),
pca9557_(pca9557) {
}
virtual void EnableOutput(bool enable) override {
BoxAudioCodec::EnableOutput(enable);
if (enable) {
pca9557_->SetOutputState(1, 1);
} else {
pca9557_->SetOutputState(1, 0);
}
}
};
/**
* @brief PCF8575 I/O 扩展芯片,地址为 0x20。
*/
class Pcf8575 : public I2cDevice {
private:
uint16_t data_ = 0x0000;
bool initialized_ = false;
public:
Pcf8575(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
// 复位所有位
if (i2c_master_transmit(i2c_device_, (uint8_t*)&data_, 2, 100) != ESP_OK) {
initialized_ = false;
} else {
initialized_ = true;
}
}
void SetBit(uint8_t bit, uint8_t level) {
if (initialized_) {
data_ = (data_ & ~(1 << bit)) | (level << bit);
ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_transmit(i2c_device_, (uint8_t*)&data_, 2, 100));
}
}
bool IsInitialized() const {
return initialized_;
}
};
class LichuangDevBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
i2c_master_dev_handle_t pca9557_handle_;
Button boot_button_;
LcdDisplay* display_;
Pca9557* pca9557_ = nullptr;
Pcf8575* pcf8575_ = nullptr;
Esp32Camera* camera_;
std::atomic<bool> bed_operating_{false};
/**
* @brief 控制床的某个功能
* @param bit 要控制的位
* @param duration_ms 持续时间,单位毫秒
* @return 返回值
*/
ReturnValue ControlBed(int bit, int duration_ms = 12000) {
if (bed_operating_) {
throw std::runtime_error("Bed is already operating");
}
ESP_LOGI(TAG, "ControlBed(%d, %d)", bit, duration_ms);
bed_operating_ = true;
std::thread([this, bit, duration_ms]() {
// High level to trigger
pcf8575_->SetBit(bit, 1);
// Duration in milliseconds
int count = duration_ms / 100;
for (int i = 0; i < count && bed_operating_; i++) {
vTaskDelay(pdMS_TO_TICKS(100));
}
// Low level to stop
pcf8575_->SetBit(bit, 0);
bed_operating_ = false;
}).detach();
return "{\"success\": true, \"message\": \"Bed is operating now\"}";
}
void InitializeTools() {
// 初始化工具
auto& mcp_server = McpServer::GetInstance();
mcp_server.AddTool("bed.adjust", "床位调整\n"
"Args: \n"
" action: 动作支持以下动作raise_back升高靠背lower_back降低靠背raise_leg升高腿部lower_leg降低腿部lean_left靠左倾斜lean_right靠右倾斜\n"
" full_adjust: 是否为完整调整持续12秒否则为单次调整\n",
PropertyList({
Property("action", kPropertyTypeString),
Property("full_adjust", kPropertyTypeBoolean, false),
}), [this](const PropertyList& properties) -> ReturnValue {
auto action = properties["action"].value<std::string>();
auto full_adjust = properties["full_adjust"].value<bool>();
int duration_ms = 2000;
if (full_adjust) {
duration_ms = 12000;
}
if (action == "raise_back") {
return ControlBed(0, duration_ms);
} else if (action == "lower_back") {
return ControlBed(1, duration_ms);
} else if (action == "raise_leg") {
return ControlBed(2, duration_ms);
} else if (action == "lower_leg") {
return ControlBed(3, duration_ms);
} else if (action == "lean_left") {
return ControlBed(4, duration_ms);
} else if (action == "lean_right") {
return ControlBed(5, duration_ms);
} else {
throw std::runtime_error("Invalid action: " + action);
}
});
mcp_server.AddTool("bed.open_toilet", "便盆打开", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return ControlBed(6);
});
mcp_server.AddTool("bed.close_toilet", "便盆关闭", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return ControlBed(7);
});
mcp_server.AddTool("bed.auto_flip_a", "自动翻身A", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return ControlBed(8, 1000);
});
mcp_server.AddTool("bed.auto_flip_b", "自动翻身B", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return ControlBed(9, 1000);
});
mcp_server.AddTool("bed.stop", "停止操作。如用户要求停下来或取消当前操作,必须先调用后回答", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
if (!bed_operating_) {
return "{\"success\": false, \"message\": \"No operation is in progress\"}";
}
bed_operating_ = false;
return "{\"success\": true, \"message\": \"Operation cancelled\"}";
});
}
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, &i2c_bus_));
// Initialize PCA9557
pca9557_ = new Pca9557(i2c_bus_, 0x19);
pcf8575_ = new Pcf8575(i2c_bus_, 0x20);
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = GPIO_NUM_40;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = GPIO_NUM_41;
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));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
// During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
if (app.GetDeviceState() == kDeviceStateStarting) {
EnterWifiConfigMode();
return;
}
app.ToggleChatState();
});
#if CONFIG_USE_DEVICE_AEC
boot_button_.OnDoubleClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateIdle) {
app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
}
});
#endif
}
void InitializeSt7789Display() {
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 = GPIO_NUM_NC;
io_config.dc_gpio_num = GPIO_NUM_39;
io_config.spi_mode = 2;
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_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片ST7789
ESP_LOGD(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_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
pca9557_->SetOutputState(0, 0);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_disp_on_off(panel, true);
#if CONFIG_USE_EMOTE_MESSAGE_STYLE
display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
#else
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);
#endif
}
void InitializeTouch()
{
esp_lcd_touch_handle_t tp;
esp_lcd_touch_config_t tp_cfg = {
.x_max = DISPLAY_HEIGHT,
.y_max = DISPLAY_WIDTH,
.rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset
.int_gpio_num = GPIO_NUM_NC,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 1,
.mirror_x = 1,
.mirror_y = 0,
},
};
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG();
tp_io_config.scl_speed_hz = 400000;
esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle);
esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp);
assert(tp);
/* Add touch input (for selected screen) */
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = lv_display_get_default(),
.handle = tp,
};
if(touch_cfg.disp) {
lvgl_port_add_touch(&touch_cfg);
} else {
ESP_LOGE(TAG, "Touch display is not initialized");
}
}
void InitializeCamera() {
// Open camera power
pca9557_->SetOutputState(2, 0);
static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
.data_width = CAM_CTLR_DATA_WIDTH_8,
.data_io = {
[0] = CAMERA_PIN_D0,
[1] = CAMERA_PIN_D1,
[2] = CAMERA_PIN_D2,
[3] = CAMERA_PIN_D3,
[4] = CAMERA_PIN_D4,
[5] = CAMERA_PIN_D5,
[6] = CAMERA_PIN_D6,
[7] = CAMERA_PIN_D7,
},
.vsync_io = CAMERA_PIN_VSYNC,
.de_io = CAMERA_PIN_HREF,
.pclk_io = CAMERA_PIN_PCLK,
.xclk_io = CAMERA_PIN_XCLK,
};
esp_video_init_sccb_config_t sccb_config = {
.init_sccb = false,
.i2c_handle = i2c_bus_,
.freq = 100000,
};
esp_video_init_dvp_config_t dvp_config = {
.sccb_config = sccb_config,
.reset_pin = CAMERA_PIN_RESET,
.pwdn_pin = CAMERA_PIN_PWDN,
.dvp_pin = dvp_pin_config,
.xclk_freq = XCLK_FREQ_HZ,
};
esp_video_init_config_t video_config = {
.dvp = &dvp_config,
};
camera_ = new Esp32Camera(video_config);
}
public:
LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeSpi();
InitializeSt7789Display();
InitializeTouch();
InitializeButtons();
InitializeCamera();
GetBacklight()->RestoreBrightness();
if (pcf8575_->IsInitialized()) {
InitializeTools();
} else {
// PCF8575 initialization failed, show error and reboot after 30 seconds
ESP_LOGE(TAG, "PCF8575 initialization failed, will reboot in 30 seconds");
display_->SetStatus(Lang::Strings::ERROR);
display_->SetEmotion("triangle_exclamation");
display_->SetChatMessage("system", "PCF8575 not connected\nReboot in 30s...");
vTaskDelay(pdMS_TO_TICKS(30000));
esp_restart();
}
}
virtual AudioCodec* GetAudioCodec() override {
static CustomAudioCodec audio_codec(
i2c_bus_,
pca9557_);
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 Camera* GetCamera() override {
return camera_;
}
};
DECLARE_BOARD(LichuangDevBoard);