Upgrade Playlist Features

This commit is contained in:
2025-12-09 17:20:01 +08:00
parent 577990de69
commit 8bd2780688
683 changed files with 91812 additions and 81260 deletions

View File

@@ -1,326 +1,326 @@
# 自定义开发板指南
本指南介绍如何为小智AI语音聊天机器人项目定制一个新的开发板初始化程序。小智AI支持70多种ESP32系列开发板每个开发板的初始化代码都放在对应的目录下。
## 重要提示
> **警告**: 对于自定义开发板当IO配置与原有开发板不同时切勿直接覆盖原有开发板的配置编译固件。必须创建新的开发板类型或者通过config.json文件中的builds配置不同的name和sdkconfig宏定义来区分。使用 `python scripts/release.py [开发板目录名字]` 来编译打包固件。
>
> 如果直接覆盖原有配置将来OTA升级时您的自定义固件可能会被原有开发板的标准固件覆盖导致您的设备无法正常工作。每个开发板有唯一的标识和对应的固件升级通道保持开发板标识的唯一性非常重要。
## 目录结构
每个开发板的目录结构通常包含以下文件:
- `xxx_board.cc` - 主要的板级初始化代码,实现了板子相关的初始化和功能
- `config.h` - 板级配置文件,定义了硬件管脚映射和其他配置项
- `config.json` - 编译配置,指定目标芯片和特殊的编译选项
- `README.md` - 开发板相关的说明文档
## 定制开发板步骤
### 1. 创建新的开发板目录
首先在`boards/`目录下创建一个新的目录,例如`my-custom-board/`
```bash
mkdir main/boards/my-custom-board
```
### 2. 创建配置文件
#### config.h
`config.h`中定义所有的硬件配置,包括:
- 音频采样率和I2S引脚配置
- 音频编解码芯片地址和I2C引脚配置
- 按钮和LED引脚配置
- 显示屏参数和引脚配置
参考示例来自lichuang-c3-dev
```c
#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_I2S_GPIO_MCLK GPIO_NUM_10
#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
#define AUDIO_CODEC_PA_PIN GPIO_NUM_13
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
// 按钮配置
#define BOOT_BUTTON_GPIO GPIO_NUM_9
// 显示屏配置
#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3
#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5
#define DISPLAY_DC_PIN GPIO_NUM_6
#define DISPLAY_SPI_CS_PIN GPIO_NUM_4
#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_2
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_
```
#### config.json
`config.json`中定义编译配置:
```json
{
"target": "esp32s3", // 目标芯片型号: esp32, esp32s3, esp32c3等
"builds": [
{
"name": "my-custom-board", // 开发板名称
"sdkconfig_append": [
// 额外需要的编译配置
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}
```
### 3. 编写板级初始化代码
创建一个`my_custom_board.cc`文件,实现开发板的所有初始化逻辑。
一个基本的开发板类定义包含以下几个部分:
1. **类定义**:继承自`WifiBoard``Ml307Board`
2. **初始化函数**包括I2C、显示屏、按钮、IoT等组件的初始化
3. **虚函数重写**:如`GetAudioCodec()``GetDisplay()``GetBacklight()`
4. **注册开发板**:使用`DECLARE_BOARD`宏注册开发板
```cpp
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#define TAG "MyCustomBoard"
class MyCustomBoard : public WifiBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
LcdDisplay* display_;
// I2C初始化
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.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_));
}
// SPI初始化用于显示屏
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SPI_SCK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
// 按钮初始化
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
// 显示屏初始化以ST7789为例
void InitializeDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
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 = 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(SPI2_HOST, &io_config, &panel_io));
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);
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);
// 创建显示屏对象
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);
}
// MCP Tools 初始化
void InitializeTools() {
// 参考 MCP 文档
}
public:
// 构造函数
MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeSpi();
InitializeDisplay();
InitializeButtons();
InitializeTools();
GetBacklight()->SetBrightness(100);
}
// 获取音频编解码器
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;
}
};
// 注册开发板
DECLARE_BOARD(MyCustomBoard);
```
### 4. 创建README.md
在README.md中说明开发板的特性、硬件要求、编译和烧录步骤
## 常见开发板组件
### 1. 显示屏
项目支持多种显示屏驱动,包括:
- ST7789 (SPI)
- ILI9341 (SPI)
- SH8601 (QSPI)
- 等...
### 2. 音频编解码器
支持的编解码器包括:
- ES8311 (常用)
- ES7210 (麦克风阵列)
- AW88298 (功放)
- 等...
### 3. 电源管理
一些开发板使用电源管理芯片:
- AXP2101
- 其他可用的PMIC
### 4. MCP设备控制
可以添加各种MCP工具让AI能够使用:
- Speaker (扬声器控制)
- Screen (屏幕亮度调节)
- Battery (电池电量读取)
- Light (灯光控制)
- 等...
## 开发板类继承关系
- `Board` - 基础板级类
- `WifiBoard` - Wi-Fi连接的开发板
- `Ml307Board` - 使用4G模块的开发板
- `DualNetworkBoard` - 支持Wi-Fi与4G网络切换的开发板
## 开发技巧
1. **参考相似的开发板**:如果您的新开发板与现有开发板有相似之处,可以参考现有实现
2. **分步调试**:先实现基础功能(如显示),再添加更复杂的功能(如音频)
3. **管脚映射**确保在config.h中正确配置所有管脚映射
4. **检查硬件兼容性**:确认所有芯片和驱动程序的兼容性
## 可能遇到的问题
1. **显示屏不正常**检查SPI配置、镜像设置和颜色反转设置
2. **音频无输出**检查I2S配置、PA使能引脚和编解码器地址
3. **无法连接网络**检查Wi-Fi凭据和网络配置
4. **无法与服务器通信**检查MQTT或WebSocket配置
## 参考资料
- ESP-IDF 文档: https://docs.espressif.com/projects/esp-idf/
- LVGL 文档: https://docs.lvgl.io/
# 自定义开发板指南
本指南介绍如何为小智AI语音聊天机器人项目定制一个新的开发板初始化程序。小智AI支持70多种ESP32系列开发板每个开发板的初始化代码都放在对应的目录下。
## 重要提示
> **警告**: 对于自定义开发板当IO配置与原有开发板不同时切勿直接覆盖原有开发板的配置编译固件。必须创建新的开发板类型或者通过config.json文件中的builds配置不同的name和sdkconfig宏定义来区分。使用 `python scripts/release.py [开发板目录名字]` 来编译打包固件。
>
> 如果直接覆盖原有配置将来OTA升级时您的自定义固件可能会被原有开发板的标准固件覆盖导致您的设备无法正常工作。每个开发板有唯一的标识和对应的固件升级通道保持开发板标识的唯一性非常重要。
## 目录结构
每个开发板的目录结构通常包含以下文件:
- `xxx_board.cc` - 主要的板级初始化代码,实现了板子相关的初始化和功能
- `config.h` - 板级配置文件,定义了硬件管脚映射和其他配置项
- `config.json` - 编译配置,指定目标芯片和特殊的编译选项
- `README.md` - 开发板相关的说明文档
## 定制开发板步骤
### 1. 创建新的开发板目录
首先在`boards/`目录下创建一个新的目录,例如`my-custom-board/`
```bash
mkdir main/boards/my-custom-board
```
### 2. 创建配置文件
#### config.h
`config.h`中定义所有的硬件配置,包括:
- 音频采样率和I2S引脚配置
- 音频编解码芯片地址和I2C引脚配置
- 按钮和LED引脚配置
- 显示屏参数和引脚配置
参考示例来自lichuang-c3-dev
```c
#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_I2S_GPIO_MCLK GPIO_NUM_10
#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
#define AUDIO_CODEC_PA_PIN GPIO_NUM_13
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
// 按钮配置
#define BOOT_BUTTON_GPIO GPIO_NUM_9
// 显示屏配置
#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3
#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5
#define DISPLAY_DC_PIN GPIO_NUM_6
#define DISPLAY_SPI_CS_PIN GPIO_NUM_4
#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_2
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_
```
#### config.json
`config.json`中定义编译配置:
```json
{
"target": "esp32s3", // 目标芯片型号: esp32, esp32s3, esp32c3等
"builds": [
{
"name": "my-custom-board", // 开发板名称
"sdkconfig_append": [
// 额外需要的编译配置
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}
```
### 3. 编写板级初始化代码
创建一个`my_custom_board.cc`文件,实现开发板的所有初始化逻辑。
一个基本的开发板类定义包含以下几个部分:
1. **类定义**:继承自`WifiBoard``Ml307Board`
2. **初始化函数**包括I2C、显示屏、按钮、IoT等组件的初始化
3. **虚函数重写**:如`GetAudioCodec()``GetDisplay()``GetBacklight()`
4. **注册开发板**:使用`DECLARE_BOARD`宏注册开发板
```cpp
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#define TAG "MyCustomBoard"
class MyCustomBoard : public WifiBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
LcdDisplay* display_;
// I2C初始化
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.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_));
}
// SPI初始化用于显示屏
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SPI_SCK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
// 按钮初始化
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
// 显示屏初始化以ST7789为例
void InitializeDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
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 = 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(SPI2_HOST, &io_config, &panel_io));
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);
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);
// 创建显示屏对象
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);
}
// MCP Tools 初始化
void InitializeTools() {
// 参考 MCP 文档
}
public:
// 构造函数
MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeSpi();
InitializeDisplay();
InitializeButtons();
InitializeTools();
GetBacklight()->SetBrightness(100);
}
// 获取音频编解码器
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;
}
};
// 注册开发板
DECLARE_BOARD(MyCustomBoard);
```
### 4. 创建README.md
在README.md中说明开发板的特性、硬件要求、编译和烧录步骤
## 常见开发板组件
### 1. 显示屏
项目支持多种显示屏驱动,包括:
- ST7789 (SPI)
- ILI9341 (SPI)
- SH8601 (QSPI)
- 等...
### 2. 音频编解码器
支持的编解码器包括:
- ES8311 (常用)
- ES7210 (麦克风阵列)
- AW88298 (功放)
- 等...
### 3. 电源管理
一些开发板使用电源管理芯片:
- AXP2101
- 其他可用的PMIC
### 4. MCP设备控制
可以添加各种MCP工具让AI能够使用:
- Speaker (扬声器控制)
- Screen (屏幕亮度调节)
- Battery (电池电量读取)
- Light (灯光控制)
- 等...
## 开发板类继承关系
- `Board` - 基础板级类
- `WifiBoard` - Wi-Fi连接的开发板
- `Ml307Board` - 使用4G模块的开发板
- `DualNetworkBoard` - 支持Wi-Fi与4G网络切换的开发板
## 开发技巧
1. **参考相似的开发板**:如果您的新开发板与现有开发板有相似之处,可以参考现有实现
2. **分步调试**:先实现基础功能(如显示),再添加更复杂的功能(如音频)
3. **管脚映射**确保在config.h中正确配置所有管脚映射
4. **检查硬件兼容性**:确认所有芯片和驱动程序的兼容性
## 可能遇到的问题
1. **显示屏不正常**检查SPI配置、镜像设置和颜色反转设置
2. **音频无输出**检查I2S配置、PA使能引脚和编解码器地址
3. **无法连接网络**检查Wi-Fi凭据和网络配置
4. **无法与服务器通信**检查MQTT或WebSocket配置
## 参考资料
- ESP-IDF 文档: https://docs.espressif.com/projects/esp-idf/
- LVGL 文档: https://docs.lvgl.io/
- ESP-SR 文档: https://github.com/espressif/esp-sr

View File

@@ -1,299 +1,299 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "led/single_led.h"
#include "i2c_device.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/timers.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_ops.h>
#define TAG "atk_dnesp32s3_box"
class ATK_NoAudioCodecDuplex : public NoAudioCodec {
public:
ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
duplex_ = true;
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,
};
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
i2s_std_config_t std_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)output_sample_rate_,
.clk_src = I2S_CLK_SRC_DEFAULT,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
#ifdef I2S_HW_VERSION_2
.ext_clk_freq_hz = 0,
#endif
},
.slot_cfg = {
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_STEREO,
.slot_mask = I2S_STD_SLOT_BOTH,
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
.ws_pol = false,
.bit_shift = true,
#ifdef I2S_HW_VERSION_2
.left_align = true,
.big_endian = false,
.bit_order_lsb = false
#endif
},
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = bclk,
.ws = ws,
.dout = dout,
.din = din,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
ESP_LOGI(TAG, "Duplex channels created");
}
};
class XL9555_IN : public I2cDevice {
public:
XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(0x06, 0x3B);
WriteReg(0x07, 0xFE);
}
void xl9555_cfg(void) {
WriteReg(0x06, 0x1B);
WriteReg(0x07, 0xFE);
}
void SetOutputState(uint8_t bit, uint8_t level) {
uint16_t data;
int index = bit;
if (bit < 8) {
data = ReadReg(0x02);
} else {
data = ReadReg(0x03);
index -= 8;
}
data = (data & ~(1 << index)) | (level << index);
if (bit < 8) {
WriteReg(0x02, data);
} else {
WriteReg(0x03, data);
}
}
int GetPingState(uint16_t pin) {
uint8_t data;
if (pin <= 0x0080) {
data = ReadReg(0x00);
return (data & (uint8_t)(pin & 0xFF)) ? 1 : 0;
} else {
data = ReadReg(0x01);
return (data & (uint8_t)((pin >> 8) & 0xFF )) ? 1 : 0;
}
return 0;
}
};
class atk_dnesp32s3_box : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
i2c_master_dev_handle_t xl9555_handle_;
Button boot_button_;
LcdDisplay* display_;
XL9555_IN* xl9555_in_;
bool es8311_detected_ = false;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = GPIO_NUM_48,
.scl_io_num = GPIO_NUM_45,
.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 XL9555
xl9555_in_ = new XL9555_IN(i2c_bus_, 0x20);
if (xl9555_in_->GetPingState(0x0020) == 1) {
es8311_detected_ = true; /* 音频设备标志位SPK_CTRL_IO为高电平时该标志位置1且判定为ES8311 */
} else {
es8311_detected_ = false; /* 音频设备标志位SPK_CTRL_IO为低电平时该标志位置0且判定为NS4168 */
}
xl9555_in_->xl9555_cfg();
}
void InitializeATK_ST7789_80_Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
/* 配置RD引脚 */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_NUM_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_NUM_RD, 1);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_NUM_DC,
.wr_gpio_num = LCD_NUM_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
GPIO_LCD_D0,
GPIO_LCD_D1,
GPIO_LCD_D2,
GPIO_LCD_D3,
GPIO_LCD_D4,
GPIO_LCD_D5,
GPIO_LCD_D6,
GPIO_LCD_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_NUM_CS,
.pclk_hz = (10 * 1000 * 1000),
.trans_queue_depth = 10,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.swap_color_bytes = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
esp_lcd_panel_set_gap(panel, 0, 0);
uint8_t data0[] = {0x00};
uint8_t data1[] = {0x65};
esp_lcd_panel_io_tx_param(panel_io, 0x36, data0, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, data1, 1);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
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);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeATK_ST7789_80_Display();
xl9555_in_->SetOutputState(5, 1);
xl9555_in_->SetOutputState(7, 1);
InitializeButtons();
}
virtual AudioCodec* GetAudioCodec() override {
/* 根据探测结果初始化编解码器 */
if (es8311_detected_) {
/* 使用ES8311 驱动 */
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
GPIO_NUM_NC,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
GPIO_NUM_NC,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
} else {
static ATK_NoAudioCodecDuplex audio_codec(
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN);
return &audio_codec;
}
return NULL;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(atk_dnesp32s3_box);
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "led/single_led.h"
#include "i2c_device.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/timers.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_ops.h>
#define TAG "atk_dnesp32s3_box"
class ATK_NoAudioCodecDuplex : public NoAudioCodec {
public:
ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
duplex_ = true;
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,
};
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
i2s_std_config_t std_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)output_sample_rate_,
.clk_src = I2S_CLK_SRC_DEFAULT,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
#ifdef I2S_HW_VERSION_2
.ext_clk_freq_hz = 0,
#endif
},
.slot_cfg = {
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_STEREO,
.slot_mask = I2S_STD_SLOT_BOTH,
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
.ws_pol = false,
.bit_shift = true,
#ifdef I2S_HW_VERSION_2
.left_align = true,
.big_endian = false,
.bit_order_lsb = false
#endif
},
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = bclk,
.ws = ws,
.dout = dout,
.din = din,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
ESP_LOGI(TAG, "Duplex channels created");
}
};
class XL9555_IN : public I2cDevice {
public:
XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(0x06, 0x3B);
WriteReg(0x07, 0xFE);
}
void xl9555_cfg(void) {
WriteReg(0x06, 0x1B);
WriteReg(0x07, 0xFE);
}
void SetOutputState(uint8_t bit, uint8_t level) {
uint16_t data;
int index = bit;
if (bit < 8) {
data = ReadReg(0x02);
} else {
data = ReadReg(0x03);
index -= 8;
}
data = (data & ~(1 << index)) | (level << index);
if (bit < 8) {
WriteReg(0x02, data);
} else {
WriteReg(0x03, data);
}
}
int GetPingState(uint16_t pin) {
uint8_t data;
if (pin <= 0x0080) {
data = ReadReg(0x00);
return (data & (uint8_t)(pin & 0xFF)) ? 1 : 0;
} else {
data = ReadReg(0x01);
return (data & (uint8_t)((pin >> 8) & 0xFF )) ? 1 : 0;
}
return 0;
}
};
class atk_dnesp32s3_box : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
i2c_master_dev_handle_t xl9555_handle_;
Button boot_button_;
LcdDisplay* display_;
XL9555_IN* xl9555_in_;
bool es8311_detected_ = false;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = GPIO_NUM_48,
.scl_io_num = GPIO_NUM_45,
.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 XL9555
xl9555_in_ = new XL9555_IN(i2c_bus_, 0x20);
if (xl9555_in_->GetPingState(0x0020) == 1) {
es8311_detected_ = true; /* 音频设备标志位SPK_CTRL_IO为高电平时该标志位置1且判定为ES8311 */
} else {
es8311_detected_ = false; /* 音频设备标志位SPK_CTRL_IO为低电平时该标志位置0且判定为NS4168 */
}
xl9555_in_->xl9555_cfg();
}
void InitializeATK_ST7789_80_Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
/* 配置RD引脚 */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_NUM_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_NUM_RD, 1);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_NUM_DC,
.wr_gpio_num = LCD_NUM_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
GPIO_LCD_D0,
GPIO_LCD_D1,
GPIO_LCD_D2,
GPIO_LCD_D3,
GPIO_LCD_D4,
GPIO_LCD_D5,
GPIO_LCD_D6,
GPIO_LCD_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_NUM_CS,
.pclk_hz = (10 * 1000 * 1000),
.trans_queue_depth = 10,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.swap_color_bytes = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
esp_lcd_panel_set_gap(panel, 0, 0);
uint8_t data0[] = {0x00};
uint8_t data1[] = {0x65};
esp_lcd_panel_io_tx_param(panel_io, 0x36, data0, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, data1, 1);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
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);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeATK_ST7789_80_Display();
xl9555_in_->SetOutputState(5, 1);
xl9555_in_->SetOutputState(7, 1);
InitializeButtons();
}
virtual AudioCodec* GetAudioCodec() override {
/* 根据探测结果初始化编解码器 */
if (es8311_detected_) {
/* 使用ES8311 驱动 */
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
GPIO_NUM_NC,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
GPIO_NUM_NC,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
} else {
static ATK_NoAudioCodecDuplex audio_codec(
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN);
return &audio_codec;
}
return NULL;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(atk_dnesp32s3_box);

View File

@@ -1,46 +1,46 @@
#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_I2S_GPIO_WS GPIO_NUM_13
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_47
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_4
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY true
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
// Pin Definitions
#define LCD_NUM_CS GPIO_NUM_1
#define LCD_NUM_DC GPIO_NUM_2
#define LCD_NUM_RD GPIO_NUM_41
#define LCD_NUM_WR GPIO_NUM_42
#define LCD_NUM_RST GPIO_NUM_NC
#define GPIO_LCD_D0 GPIO_NUM_40
#define GPIO_LCD_D1 GPIO_NUM_39
#define GPIO_LCD_D2 GPIO_NUM_38
#define GPIO_LCD_D3 GPIO_NUM_12
#define GPIO_LCD_D4 GPIO_NUM_11
#define GPIO_LCD_D5 GPIO_NUM_10
#define GPIO_LCD_D6 GPIO_NUM_9
#define GPIO_LCD_D7 GPIO_NUM_46
#endif // _BOARD_CONFIG_H_
#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_I2S_GPIO_WS GPIO_NUM_13
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_47
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_4
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY true
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
// Pin Definitions
#define LCD_NUM_CS GPIO_NUM_1
#define LCD_NUM_DC GPIO_NUM_2
#define LCD_NUM_RD GPIO_NUM_41
#define LCD_NUM_WR GPIO_NUM_42
#define LCD_NUM_RST GPIO_NUM_NC
#define GPIO_LCD_D0 GPIO_NUM_40
#define GPIO_LCD_D1 GPIO_NUM_39
#define GPIO_LCD_D2 GPIO_NUM_38
#define GPIO_LCD_D3 GPIO_NUM_12
#define GPIO_LCD_D4 GPIO_NUM_11
#define GPIO_LCD_D5 GPIO_NUM_10
#define GPIO_LCD_D6 GPIO_NUM_9
#define GPIO_LCD_D7 GPIO_NUM_46
#endif // _BOARD_CONFIG_H_

View File

@@ -1,11 +1,11 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box",
"sdkconfig_append": [
"CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
]
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box",
"sdkconfig_append": [
"CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
]
}
]
}

View File

@@ -1,388 +1,388 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#define TAG "atk_dnesp32s3_box0"
class atk_dnesp32s3_box0 : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button right_button_;
Button left_button_;
Button middle_button_;
LcdDisplay* display_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
LcdStatus LcdStatus_ = kDevicelcdbacklightOn;
PowerSleep power_sleep_ = kDeviceNoSleep;
WakeStatus wake_status_ = kDeviceAwakened;
XiaozhiStatus XiaozhiStatus_ = kDevice_Exit_Distributionnetwork;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
gpio_config_t gpio_init_struct = {0};
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pin_bit_mask = (1ull << CODEC_PWR_PIN) | (1ull << SYS_POW_PIN);
gpio_config(&gpio_init_struct);
gpio_set_level(CODEC_PWR_PIN, 1);
gpio_set_level(SYS_POW_PIN, 1);
gpio_config_t chg_init_struct = {0};
chg_init_struct.intr_type = GPIO_INTR_DISABLE;
chg_init_struct.mode = GPIO_MODE_INPUT;
chg_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
chg_init_struct.pin_bit_mask = 1ull << CHRG_PIN;
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
chg_init_struct.mode = GPIO_MODE_OUTPUT;
chg_init_struct.pull_up_en = GPIO_PULLUP_DISABLE;
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
chg_init_struct.pin_bit_mask = 1ull << CHG_CTRL_PIN;
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
gpio_set_level(CHG_CTRL_PIN, 1);
if (gpio_get_level(CHRG_PIN) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box0* self = static_cast<atk_dnesp32s3_box0*>(arg);
if (self->LcdStatus_ == kDevicelcdbacklightOff && Application::GetInstance().GetDeviceState() == kDeviceStateListening
&& self->wake_status_ == kDeviceWaitWake) {
if (self->power_sleep_ == kDeviceNeutralSleep) {
self->power_save_timer_->WakeUp();
}
self->GetBacklight()->RestoreBrightness();
self->wake_status_ = kDeviceAwakened;
self->LcdStatus_ = kDevicelcdbacklightOn;
} else if (self->power_sleep_ == kDeviceNeutralSleep && Application::GetInstance().GetDeviceState() == kDeviceStateListening
&& self->LcdStatus_ != kDevicelcdbacklightOff && self->wake_status_ == kDeviceAwakened) {
self->power_save_timer_->WakeUp();
self->power_sleep_ = kDeviceNoSleep;
} else {
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (gpio_get_level(CHRG_PIN) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
if (self->power_manager_->low_voltage_ < 2877 && self->power_status_ != kDeviceTypecSupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(SYS_POW_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 300000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(CHRG_PIN);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
power_sleep_ = kDeviceNeutralSleep;
XiaozhiStatus_ = kDevice_join_Sleep;
GetDisplay()->SetPowerSaveMode(true);
if (LcdStatus_ != kDevicelcdbacklightOff) {
GetBacklight()->SetBrightness(1);
}
});
power_save_timer_->OnExitSleepMode([this]() {
power_sleep_ = kDeviceNoSleep;
GetDisplay()->SetPowerSaveMode(false);
if (XiaozhiStatus_ != kDevice_Exit_Sleep) {
GetBacklight()->RestoreBrightness();
}
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
esp_timer_stop(power_manager_->timer_handle_);
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(SYS_POW_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
});
power_save_timer_->SetEnabled(true);
}
// Initialize I2C peripheral
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
middle_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (LcdStatus_ != kDevicelcdbacklightOff) {
if (power_sleep_ == kDeviceNeutralSleep) {
power_save_timer_->WakeUp();
power_sleep_ = kDeviceNoSleep;
}
app.ToggleChatState();
}
});
middle_button_.OnPressUp([this]() {
if (LcdStatus_ == kDevicelcdbacklightOff) {
Application::GetInstance().StopListening();
Application::GetInstance().SetDeviceState(kDeviceStateIdle);
wake_status_ = kDeviceWaitWake;
}
if (XiaozhiStatus_ == kDevice_Distributionnetwork || XiaozhiStatus_ == kDevice_Exit_Sleep) {
esp_timer_stop(power_manager_->timer_handle_);
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(SYS_POW_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
} else if (XiaozhiStatus_ == kDevice_join_Sleep) {
GetBacklight()->RestoreBrightness();
XiaozhiStatus_ = kDevice_null;
}
});
middle_button_.OnLongPress([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
if (app.GetDeviceState() == kDeviceStateWifiConfiguring && power_status_ != kDeviceTypecSupply) {
GetBacklight()->SetBrightness(0);
XiaozhiStatus_ = kDevice_Distributionnetwork;
} else if (power_status_ == kDeviceBatterySupply && LcdStatus_ != kDevicelcdbacklightOff) {
Application::GetInstance().StartListening();
GetBacklight()->SetBrightness(0);
XiaozhiStatus_ = kDevice_Exit_Sleep;
} else if (power_status_ == kDeviceTypecSupply && LcdStatus_ == kDevicelcdbacklightOn && Application::GetInstance().GetDeviceState() != kDeviceStateStarting) {
Application::GetInstance().StartListening();
GetBacklight()->SetBrightness(0);
LcdStatus_ = kDevicelcdbacklightOff;
} else if (LcdStatus_ == kDevicelcdbacklightOff && (power_status_ == kDeviceTypecSupply || power_status_ == kDeviceBatterySupply)) {
GetDisplay()->SetChatMessage("system", "");
GetBacklight()->RestoreBrightness();
wake_status_ = kDeviceAwakened;
LcdStatus_ = kDevicelcdbacklightOn;
}
}
});
left_button_.OnClick([this]() {
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
power_save_timer_->WakeUp();
power_sleep_ = kDeviceNoSleep;
}
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
left_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
right_button_.OnClick([this]() {
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
power_save_timer_->WakeUp();
power_sleep_ = kDeviceNoSleep;
}
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
right_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 7;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
ESP_LOGI(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = LCD_RST_PIN;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
esp_lcd_panel_reset(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_init(panel);
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);
}
public:
atk_dnesp32s3_box0() :
right_button_(R_BUTTON_GPIO, false),
left_button_(L_BUTTON_GPIO, false),
middle_button_(M_BUTTON_GPIO, true) {
InitializeBoardPowerManager();
InitializePowerManager();
InitializePowerSaveTimer();
InitializeI2c();
InitializeSpi();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
GPIO_NUM_NC,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
GPIO_NUM_NC,
AUDIO_CODEC_ES8311_ADDR,
false);
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(atk_dnesp32s3_box0);
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#define TAG "atk_dnesp32s3_box0"
class atk_dnesp32s3_box0 : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button right_button_;
Button left_button_;
Button middle_button_;
LcdDisplay* display_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
LcdStatus LcdStatus_ = kDevicelcdbacklightOn;
PowerSleep power_sleep_ = kDeviceNoSleep;
WakeStatus wake_status_ = kDeviceAwakened;
XiaozhiStatus XiaozhiStatus_ = kDevice_Exit_Distributionnetwork;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
gpio_config_t gpio_init_struct = {0};
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pin_bit_mask = (1ull << CODEC_PWR_PIN) | (1ull << SYS_POW_PIN);
gpio_config(&gpio_init_struct);
gpio_set_level(CODEC_PWR_PIN, 1);
gpio_set_level(SYS_POW_PIN, 1);
gpio_config_t chg_init_struct = {0};
chg_init_struct.intr_type = GPIO_INTR_DISABLE;
chg_init_struct.mode = GPIO_MODE_INPUT;
chg_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
chg_init_struct.pin_bit_mask = 1ull << CHRG_PIN;
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
chg_init_struct.mode = GPIO_MODE_OUTPUT;
chg_init_struct.pull_up_en = GPIO_PULLUP_DISABLE;
chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
chg_init_struct.pin_bit_mask = 1ull << CHG_CTRL_PIN;
ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
gpio_set_level(CHG_CTRL_PIN, 1);
if (gpio_get_level(CHRG_PIN) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box0* self = static_cast<atk_dnesp32s3_box0*>(arg);
if (self->LcdStatus_ == kDevicelcdbacklightOff && Application::GetInstance().GetDeviceState() == kDeviceStateListening
&& self->wake_status_ == kDeviceWaitWake) {
if (self->power_sleep_ == kDeviceNeutralSleep) {
self->power_save_timer_->WakeUp();
}
self->GetBacklight()->RestoreBrightness();
self->wake_status_ = kDeviceAwakened;
self->LcdStatus_ = kDevicelcdbacklightOn;
} else if (self->power_sleep_ == kDeviceNeutralSleep && Application::GetInstance().GetDeviceState() == kDeviceStateListening
&& self->LcdStatus_ != kDevicelcdbacklightOff && self->wake_status_ == kDeviceAwakened) {
self->power_save_timer_->WakeUp();
self->power_sleep_ = kDeviceNoSleep;
} else {
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (gpio_get_level(CHRG_PIN) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
if (self->power_manager_->low_voltage_ < 2877 && self->power_status_ != kDeviceTypecSupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(SYS_POW_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 300000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(CHRG_PIN);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
power_sleep_ = kDeviceNeutralSleep;
XiaozhiStatus_ = kDevice_join_Sleep;
GetDisplay()->SetPowerSaveMode(true);
if (LcdStatus_ != kDevicelcdbacklightOff) {
GetBacklight()->SetBrightness(1);
}
});
power_save_timer_->OnExitSleepMode([this]() {
power_sleep_ = kDeviceNoSleep;
GetDisplay()->SetPowerSaveMode(false);
if (XiaozhiStatus_ != kDevice_Exit_Sleep) {
GetBacklight()->RestoreBrightness();
}
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
esp_timer_stop(power_manager_->timer_handle_);
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(SYS_POW_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
});
power_save_timer_->SetEnabled(true);
}
// Initialize I2C peripheral
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
middle_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (LcdStatus_ != kDevicelcdbacklightOff) {
if (power_sleep_ == kDeviceNeutralSleep) {
power_save_timer_->WakeUp();
power_sleep_ = kDeviceNoSleep;
}
app.ToggleChatState();
}
});
middle_button_.OnPressUp([this]() {
if (LcdStatus_ == kDevicelcdbacklightOff) {
Application::GetInstance().StopListening();
Application::GetInstance().SetDeviceState(kDeviceStateIdle);
wake_status_ = kDeviceWaitWake;
}
if (XiaozhiStatus_ == kDevice_Distributionnetwork || XiaozhiStatus_ == kDevice_Exit_Sleep) {
esp_timer_stop(power_manager_->timer_handle_);
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(SYS_POW_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
} else if (XiaozhiStatus_ == kDevice_join_Sleep) {
GetBacklight()->RestoreBrightness();
XiaozhiStatus_ = kDevice_null;
}
});
middle_button_.OnLongPress([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
if (app.GetDeviceState() == kDeviceStateWifiConfiguring && power_status_ != kDeviceTypecSupply) {
GetBacklight()->SetBrightness(0);
XiaozhiStatus_ = kDevice_Distributionnetwork;
} else if (power_status_ == kDeviceBatterySupply && LcdStatus_ != kDevicelcdbacklightOff) {
Application::GetInstance().StartListening();
GetBacklight()->SetBrightness(0);
XiaozhiStatus_ = kDevice_Exit_Sleep;
} else if (power_status_ == kDeviceTypecSupply && LcdStatus_ == kDevicelcdbacklightOn && Application::GetInstance().GetDeviceState() != kDeviceStateStarting) {
Application::GetInstance().StartListening();
GetBacklight()->SetBrightness(0);
LcdStatus_ = kDevicelcdbacklightOff;
} else if (LcdStatus_ == kDevicelcdbacklightOff && (power_status_ == kDeviceTypecSupply || power_status_ == kDeviceBatterySupply)) {
GetDisplay()->SetChatMessage("system", "");
GetBacklight()->RestoreBrightness();
wake_status_ = kDeviceAwakened;
LcdStatus_ = kDevicelcdbacklightOn;
}
}
});
left_button_.OnClick([this]() {
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
power_save_timer_->WakeUp();
power_sleep_ = kDeviceNoSleep;
}
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
left_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
right_button_.OnClick([this]() {
if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
power_save_timer_->WakeUp();
power_sleep_ = kDeviceNoSleep;
}
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
right_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 7;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
ESP_LOGI(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = LCD_RST_PIN;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
esp_lcd_panel_reset(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_init(panel);
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);
}
public:
atk_dnesp32s3_box0() :
right_button_(R_BUTTON_GPIO, false),
left_button_(L_BUTTON_GPIO, false),
middle_button_(M_BUTTON_GPIO, true) {
InitializeBoardPowerManager();
InitializePowerManager();
InitializePowerSaveTimer();
InitializeI2c();
InitializeSpi();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
GPIO_NUM_NC,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
GPIO_NUM_NC,
AUDIO_CODEC_ES8311_ADDR,
false);
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(atk_dnesp32s3_box0);

View File

@@ -1,84 +1,84 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum XiaozhiStatus {
kDevice_null,
kDevice_join_Sleep,
kDevice_Exit_Sleep,
kDevice_Distributionnetwork,
kDevice_Exit_Distributionnetwork,
};
enum LcdStatus {
kDevicelcdbacklightOn,
kDevicelcdbacklightOff,
};
enum WakeStatus {
kDeviceAwakened,
kDeviceWaitWake,
kDeviceSleeped,
};
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
enum PowerSleep {
kDeviceNoSleep,
kDeviceDeepSleep,
kDeviceNeutralSleep,
};
#define SYS_POW_PIN GPIO_NUM_2
#define CHG_CTRL_PIN GPIO_NUM_47
#define CODEC_PWR_PIN GPIO_NUM_14
#define CHRG_PIN GPIO_NUM_48
#define BAT_VSEN_PIN GPIO_NUM_1
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_11
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_SPK_GPIO_PIN GPIO_NUM_21
#define R_BUTTON_GPIO GPIO_NUM_0
#define M_BUTTON_GPIO GPIO_NUM_4
#define L_BUTTON_GPIO GPIO_NUM_3
#define BUILTIN_LED_GPIO GPIO_NUM_13
#define LCD_SCLK_PIN GPIO_NUM_39
#define LCD_MOSI_PIN GPIO_NUM_40
#define LCD_MISO_PIN GPIO_NUM_NC
#define LCD_DC_PIN GPIO_NUM_38
#define LCD_CS_PIN GPIO_NUM_41
#define LCD_RST_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#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_42
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum XiaozhiStatus {
kDevice_null,
kDevice_join_Sleep,
kDevice_Exit_Sleep,
kDevice_Distributionnetwork,
kDevice_Exit_Distributionnetwork,
};
enum LcdStatus {
kDevicelcdbacklightOn,
kDevicelcdbacklightOff,
};
enum WakeStatus {
kDeviceAwakened,
kDeviceWaitWake,
kDeviceSleeped,
};
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
enum PowerSleep {
kDeviceNoSleep,
kDeviceDeepSleep,
kDeviceNeutralSleep,
};
#define SYS_POW_PIN GPIO_NUM_2
#define CHG_CTRL_PIN GPIO_NUM_47
#define CODEC_PWR_PIN GPIO_NUM_14
#define CHRG_PIN GPIO_NUM_48
#define BAT_VSEN_PIN GPIO_NUM_1
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_11
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_SPK_GPIO_PIN GPIO_NUM_21
#define R_BUTTON_GPIO GPIO_NUM_0
#define M_BUTTON_GPIO GPIO_NUM_4
#define L_BUTTON_GPIO GPIO_NUM_3
#define BUILTIN_LED_GPIO GPIO_NUM_13
#define LCD_SCLK_PIN GPIO_NUM_39
#define LCD_MOSI_PIN GPIO_NUM_40
#define LCD_MISO_PIN GPIO_NUM_NC
#define LCD_DC_PIN GPIO_NUM_38
#define LCD_CS_PIN GPIO_NUM_41
#define LCD_RST_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#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_42
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -1,9 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box0",
"sdkconfig_append": []
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box0",
"sdkconfig_append": []
}
]
}

View File

@@ -1,193 +1,193 @@
#pragma once
#include <vector>
#include <functional>
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> 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_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
bool new_charging_status = gpio_get_level(charging_pin_) == 0;
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() {
int adc_value;
uint32_t temp_val = 0;
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
gpio_set_level(CHG_CTRL_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(100));
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2951, 0}, /* 3.80V */
{3019, 20},
{3037, 40},
{3091, 60}, /* 3.88 */
{3124, 80},
{3231, 100}
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2877;
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(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
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_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<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};
#pragma once
#include <vector>
#include <functional>
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> 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_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
bool new_charging_status = gpio_get_level(charging_pin_) == 0;
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() {
int adc_value;
uint32_t temp_val = 0;
gpio_set_level(CHG_CTRL_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
gpio_set_level(CHG_CTRL_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(100));
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2951, 0}, /* 3.80V */
{3019, 20},
{3037, 40},
{3091, 60}, /* 3.88 */
{3124, 80},
{3231, 100}
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2877;
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(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
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_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<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};

View File

@@ -1,477 +1,477 @@
#include "dual_network_board.h"
#include "codecs/es8389_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#include "esp_io_expander_tca95xx_16bit.h"
#define TAG "atk_dnesp32s3_box2_4g"
class atk_dnesp32s3_box2_4g : public DualNetworkBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
LcdDisplay* display_;
esp_io_expander_handle_t io_exp_handle;
button_handle_t btns;
button_driver_t* btn_driver_ = nullptr;
static atk_dnesp32s3_box2_4g* instance_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
instance_ = this;
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box2_4g* self = static_cast<atk_dnesp32s3_box2_4g*>(arg);
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
/* 低于某个电量,会自动关机 */
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(io_exp_handle);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
GetBacklight()->SetBrightness(0);
esp_timer_stop(power_manager_->timer_handle_);
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
}
});
power_save_timer_->SetEnabled(true);
}
void audio_volume_change(bool direction) {
auto codec = GetAudioCodec();
auto volume = codec->output_volume();
if (direction) {
volume += 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
} else {
volume -= 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
}
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
}
void audio_volume_minimum(){
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
}
void audio_volume_maxmum(){
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
}
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
}
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
uint32_t pin_val = 0;
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
pin_mask &= DRV_IO_EXP_INPUT_MASK;
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
}
void InitializeIoExpander() {
esp_err_t ret = ESP_OK;
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
assert(ret == ESP_OK);
}
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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_));
}
void InitializeButtons() {
instance_ = this;
button_config_t l_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t m_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t r_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_driver_t* xio_l_btn_driver_ = nullptr;
button_driver_t* xio_m_btn_driver_ = nullptr;
button_handle_t l_btn_handle = NULL;
button_handle_t m_btn_handle = NULL;
button_handle_t r_btn_handle = NULL;
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_l_btn_driver_->enable_power_save = false;
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
};
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_m_btn_driver_->enable_power_save = false;
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return instance_->IoExpanderGetLevel(XIO_KEY_M);
};
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
button_gpio_config_t r_cfg = {
.gpio_num = R_BUTTON_GPIO,
.active_level = BUTTON_INACTIVE,
.enable_power_save = false,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(false);
}, this);
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_minimum();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (self->GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
}
else {
app.ToggleChatState();
}
} else {
app.ToggleChatState();
}
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
self->SwitchNetworkType();
}
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
auto& app = Application::GetInstance();
if (self->GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
}
if (self->power_status_ == kDeviceBatterySupply) {
auto backlight = Board::GetInstance().GetBacklight();
backlight->SetBrightness(0);
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(true);
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_maxmum();
}, this);
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
/*RD PIN */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_PIN_RD, 1);
/* BL PIN */
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_PIN_DC,
.wr_gpio_num = LCD_PIN_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
LCD_PIN_D0,
LCD_PIN_D1,
LCD_PIN_D2,
LCD_PIN_D3,
LCD_PIN_D4,
LCD_PIN_D5,
LCD_PIN_D6,
LCD_PIN_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_PIN_CS,
.pclk_hz = (20 * 1000 * 1000),
.trans_queue_depth = 7,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 1,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.cs_active_high = 0,
.pclk_active_neg = 0,
.pclk_idle_low = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_PIN_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_set_gap(panel, 0, 0);
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
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);
}
public:
atk_dnesp32s3_box2_4g() :
DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) {
InitializeI2c();
InitializeIoExpander();
InitializePowerSaveTimer();
InitializePowerManager();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
InitializeBoardPowerManager();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8389AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8389_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();
}
DualNetworkBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(atk_dnesp32s3_box2_4g);
// 定义静态成员变量
atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr;
#include "dual_network_board.h"
#include "codecs/es8389_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#include "esp_io_expander_tca95xx_16bit.h"
#define TAG "atk_dnesp32s3_box2_4g"
class atk_dnesp32s3_box2_4g : public DualNetworkBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
LcdDisplay* display_;
esp_io_expander_handle_t io_exp_handle;
button_handle_t btns;
button_driver_t* btn_driver_ = nullptr;
static atk_dnesp32s3_box2_4g* instance_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
instance_ = this;
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box2_4g* self = static_cast<atk_dnesp32s3_box2_4g*>(arg);
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
/* 低于某个电量,会自动关机 */
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(io_exp_handle);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
GetBacklight()->SetBrightness(0);
esp_timer_stop(power_manager_->timer_handle_);
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
}
});
power_save_timer_->SetEnabled(true);
}
void audio_volume_change(bool direction) {
auto codec = GetAudioCodec();
auto volume = codec->output_volume();
if (direction) {
volume += 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
} else {
volume -= 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
}
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
}
void audio_volume_minimum(){
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
}
void audio_volume_maxmum(){
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
}
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
}
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
uint32_t pin_val = 0;
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
pin_mask &= DRV_IO_EXP_INPUT_MASK;
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
}
void InitializeIoExpander() {
esp_err_t ret = ESP_OK;
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
assert(ret == ESP_OK);
}
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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_));
}
void InitializeButtons() {
instance_ = this;
button_config_t l_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t m_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t r_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_driver_t* xio_l_btn_driver_ = nullptr;
button_driver_t* xio_m_btn_driver_ = nullptr;
button_handle_t l_btn_handle = NULL;
button_handle_t m_btn_handle = NULL;
button_handle_t r_btn_handle = NULL;
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_l_btn_driver_->enable_power_save = false;
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
};
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_m_btn_driver_->enable_power_save = false;
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return instance_->IoExpanderGetLevel(XIO_KEY_M);
};
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
button_gpio_config_t r_cfg = {
.gpio_num = R_BUTTON_GPIO,
.active_level = BUTTON_INACTIVE,
.enable_power_save = false,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(false);
}, this);
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_minimum();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (self->GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
}
else {
app.ToggleChatState();
}
} else {
app.ToggleChatState();
}
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
self->SwitchNetworkType();
}
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
auto& app = Application::GetInstance();
if (self->GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
}
if (self->power_status_ == kDeviceBatterySupply) {
auto backlight = Board::GetInstance().GetBacklight();
backlight->SetBrightness(0);
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(true);
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_maxmum();
}, this);
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
/*RD PIN */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_PIN_RD, 1);
/* BL PIN */
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_PIN_DC,
.wr_gpio_num = LCD_PIN_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
LCD_PIN_D0,
LCD_PIN_D1,
LCD_PIN_D2,
LCD_PIN_D3,
LCD_PIN_D4,
LCD_PIN_D5,
LCD_PIN_D6,
LCD_PIN_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_PIN_CS,
.pclk_hz = (20 * 1000 * 1000),
.trans_queue_depth = 7,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 1,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.cs_active_high = 0,
.pclk_active_neg = 0,
.pclk_idle_low = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_PIN_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_set_gap(panel, 0, 0);
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
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);
}
public:
atk_dnesp32s3_box2_4g() :
DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) {
InitializeI2c();
InitializeIoExpander();
InitializePowerSaveTimer();
InitializePowerManager();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
InitializeBoardPowerManager();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8389AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8389_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();
}
DualNetworkBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(atk_dnesp32s3_box2_4g);
// 定义静态成员变量
atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr;

View File

@@ -1,80 +1,80 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
#define SPISD_PIN_MOSI GPIO_NUM_16
#define SPISD_PIN_MISO GPIO_NUM_18
#define SPISD_PIN_CLK GPIO_NUM_17
#define SPISD_PIN_TS GPIO_NUM_15
#define R_BUTTON_GPIO GPIO_NUM_0
#define XL9555_INT_GPIO GPIO_NUM_2
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
#define LCD_PIN_CS GPIO_NUM_14
#define LCD_PIN_DC GPIO_NUM_12
#define LCD_PIN_RD GPIO_NUM_10
#define LCD_PIN_WR GPIO_NUM_11
#define LCD_PIN_RST GPIO_NUM_NC
#define LCD_PIN_D0 GPIO_NUM_13
#define LCD_PIN_D1 GPIO_NUM_9
#define LCD_PIN_D2 GPIO_NUM_8
#define LCD_PIN_D3 GPIO_NUM_7
#define LCD_PIN_D4 GPIO_NUM_6
#define LCD_PIN_D5 GPIO_NUM_5
#define LCD_PIN_D6 GPIO_NUM_4
#define LCD_PIN_D7 GPIO_NUM_3
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#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_21
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define Module_4G_RX_PIN GPIO_NUM_44
#define Module_4G_TX_PIN GPIO_NUM_43
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
#define SPISD_PIN_MOSI GPIO_NUM_16
#define SPISD_PIN_MISO GPIO_NUM_18
#define SPISD_PIN_CLK GPIO_NUM_17
#define SPISD_PIN_TS GPIO_NUM_15
#define R_BUTTON_GPIO GPIO_NUM_0
#define XL9555_INT_GPIO GPIO_NUM_2
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
#define LCD_PIN_CS GPIO_NUM_14
#define LCD_PIN_DC GPIO_NUM_12
#define LCD_PIN_RD GPIO_NUM_10
#define LCD_PIN_WR GPIO_NUM_11
#define LCD_PIN_RST GPIO_NUM_NC
#define LCD_PIN_D0 GPIO_NUM_13
#define LCD_PIN_D1 GPIO_NUM_9
#define LCD_PIN_D2 GPIO_NUM_8
#define LCD_PIN_D3 GPIO_NUM_7
#define LCD_PIN_D4 GPIO_NUM_6
#define LCD_PIN_D5 GPIO_NUM_5
#define LCD_PIN_D6 GPIO_NUM_4
#define LCD_PIN_D7 GPIO_NUM_3
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#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_21
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define Module_4G_RX_PIN GPIO_NUM_44
#define Module_4G_TX_PIN GPIO_NUM_43
#endif // _BOARD_CONFIG_H_

View File

@@ -1,9 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box2-4g",
"sdkconfig_append": []
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box2-4g",
"sdkconfig_append": []
}
]
}

View File

@@ -1,195 +1,195 @@
#pragma once
#include <vector>
#include <functional>
#include "esp_io_expander_tca95xx_16bit.h"
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
esp_io_expander_handle_t xl9555_;
uint32_t pin_val = 0;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> 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_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
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() {
int adc_value;
uint32_t temp_val = 0;
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(500));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2696, 0}, /* 3.48V -屏幕闪屏 */
{2724, 20}, /* 3.53V */
{2861, 40}, /* 3.7V */
{3038, 60}, /* 3.90V */
{3150, 80}, /* 4.02V */
{3280, 100} /* 4.14V */
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2630;
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(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
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_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<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};
#pragma once
#include <vector>
#include <functional>
#include "esp_io_expander_tca95xx_16bit.h"
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
esp_io_expander_handle_t xl9555_;
uint32_t pin_val = 0;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> 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_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
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() {
int adc_value;
uint32_t temp_val = 0;
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(500));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2696, 0}, /* 3.48V -屏幕闪屏 */
{2724, 20}, /* 3.53V */
{2861, 40}, /* 3.7V */
{3038, 60}, /* 3.90V */
{3150, 80}, /* 4.02V */
{3280, 100} /* 4.14V */
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2630;
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(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
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_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<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};

View File

@@ -1,456 +1,456 @@
#include "wifi_board.h"
#include "codecs/es8389_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#include "esp_io_expander_tca95xx_16bit.h"
#define TAG "atk_dnesp32s3_box2_wifi"
class atk_dnesp32s3_box2_wifi : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
LcdDisplay* display_;
esp_io_expander_handle_t io_exp_handle;
button_handle_t btns;
button_driver_t* btn_driver_ = nullptr;
static atk_dnesp32s3_box2_wifi* instance_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
instance_ = this;
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box2_wifi* self = static_cast<atk_dnesp32s3_box2_wifi*>(arg);
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
/* 低于某个电量,会自动关机 */
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(io_exp_handle);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
GetBacklight()->SetBrightness(0);
esp_timer_stop(power_manager_->timer_handle_);
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
}
});
power_save_timer_->SetEnabled(true);
}
void audio_volume_change(bool direction) {
auto codec = GetAudioCodec();
auto volume = codec->output_volume();
if (direction) {
volume += 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
} else {
volume -= 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
}
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
}
void audio_volume_minimum(){
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
}
void audio_volume_maxmum(){
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
}
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
}
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
uint32_t pin_val = 0;
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
pin_mask &= DRV_IO_EXP_INPUT_MASK;
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
}
void InitializeIoExpander() {
esp_err_t ret = ESP_OK;
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
assert(ret == ESP_OK);
}
// Initialize I2C peripheral
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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_));
}
void InitializeButtons() {
instance_ = this;
button_config_t l_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t m_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t r_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_driver_t* xio_l_btn_driver_ = nullptr;
button_driver_t* xio_m_btn_driver_ = nullptr;
button_handle_t l_btn_handle = NULL;
button_handle_t m_btn_handle = NULL;
button_handle_t r_btn_handle = NULL;
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_l_btn_driver_->enable_power_save = false;
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
};
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_m_btn_driver_->enable_power_save = false;
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return instance_->IoExpanderGetLevel(XIO_KEY_M);
};
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
button_gpio_config_t r_cfg = {
.gpio_num = R_BUTTON_GPIO,
.active_level = BUTTON_INACTIVE,
.enable_power_save = false,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(false);
}, this);
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_minimum();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
app.ToggleChatState();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
self->ResetWifiConfiguration();
}
if (self->power_status_ == kDeviceBatterySupply) {
auto backlight = Board::GetInstance().GetBacklight();
backlight->SetBrightness(0);
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(true);
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_maxmum();
}, this);
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
/* RD PIN */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_PIN_RD, 1);
/* BL PIN */
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_PIN_DC,
.wr_gpio_num = LCD_PIN_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
LCD_PIN_D0,
LCD_PIN_D1,
LCD_PIN_D2,
LCD_PIN_D3,
LCD_PIN_D4,
LCD_PIN_D5,
LCD_PIN_D6,
LCD_PIN_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_PIN_CS,
.pclk_hz = (20 * 1000 * 1000),
.trans_queue_depth = 7,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 1,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.cs_active_high = 0,
.pclk_active_neg = 0,
.pclk_idle_low = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_PIN_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_set_gap(panel, 0, 0);
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
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);
}
public:
atk_dnesp32s3_box2_wifi() {
InitializeI2c();
InitializeIoExpander();
InitializePowerSaveTimer();
InitializePowerManager();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
InitializeBoardPowerManager();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8389AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8389_ADDR,
false);
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(atk_dnesp32s3_box2_wifi);
// 定义静态成员变量
atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;
#include "wifi_board.h"
#include "codecs/es8389_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#include "esp_io_expander_tca95xx_16bit.h"
#define TAG "atk_dnesp32s3_box2_wifi"
class atk_dnesp32s3_box2_wifi : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
LcdDisplay* display_;
esp_io_expander_handle_t io_exp_handle;
button_handle_t btns;
button_driver_t* btn_driver_ = nullptr;
static atk_dnesp32s3_box2_wifi* instance_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
instance_ = this;
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box2_wifi* self = static_cast<atk_dnesp32s3_box2_wifi*>(arg);
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
/* 低于某个电量,会自动关机 */
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(io_exp_handle);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
GetBacklight()->SetBrightness(0);
esp_timer_stop(power_manager_->timer_handle_);
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
}
});
power_save_timer_->SetEnabled(true);
}
void audio_volume_change(bool direction) {
auto codec = GetAudioCodec();
auto volume = codec->output_volume();
if (direction) {
volume += 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
} else {
volume -= 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
}
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
}
void audio_volume_minimum(){
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
}
void audio_volume_maxmum(){
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
}
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
}
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
uint32_t pin_val = 0;
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
pin_mask &= DRV_IO_EXP_INPUT_MASK;
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
}
void InitializeIoExpander() {
esp_err_t ret = ESP_OK;
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
assert(ret == ESP_OK);
}
// Initialize I2C peripheral
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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_));
}
void InitializeButtons() {
instance_ = this;
button_config_t l_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t m_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t r_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_driver_t* xio_l_btn_driver_ = nullptr;
button_driver_t* xio_m_btn_driver_ = nullptr;
button_handle_t l_btn_handle = NULL;
button_handle_t m_btn_handle = NULL;
button_handle_t r_btn_handle = NULL;
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_l_btn_driver_->enable_power_save = false;
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
};
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_m_btn_driver_->enable_power_save = false;
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return instance_->IoExpanderGetLevel(XIO_KEY_M);
};
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
button_gpio_config_t r_cfg = {
.gpio_num = R_BUTTON_GPIO,
.active_level = BUTTON_INACTIVE,
.enable_power_save = false,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(false);
}, this);
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_minimum();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
app.ToggleChatState();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
self->ResetWifiConfiguration();
}
if (self->power_status_ == kDeviceBatterySupply) {
auto backlight = Board::GetInstance().GetBacklight();
backlight->SetBrightness(0);
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(true);
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_maxmum();
}, this);
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
/* RD PIN */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_PIN_RD, 1);
/* BL PIN */
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_PIN_DC,
.wr_gpio_num = LCD_PIN_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
LCD_PIN_D0,
LCD_PIN_D1,
LCD_PIN_D2,
LCD_PIN_D3,
LCD_PIN_D4,
LCD_PIN_D5,
LCD_PIN_D6,
LCD_PIN_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_PIN_CS,
.pclk_hz = (20 * 1000 * 1000),
.trans_queue_depth = 7,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 1,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.cs_active_high = 0,
.pclk_active_neg = 0,
.pclk_idle_low = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_PIN_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_set_gap(panel, 0, 0);
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
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);
}
public:
atk_dnesp32s3_box2_wifi() {
InitializeI2c();
InitializeIoExpander();
InitializePowerSaveTimer();
InitializePowerManager();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
InitializeBoardPowerManager();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8389AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8389_ADDR,
false);
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(atk_dnesp32s3_box2_wifi);
// 定义静态成员变量
atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;

View File

@@ -1,71 +1,71 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
#define R_BUTTON_GPIO GPIO_NUM_0
#define XL9555_INT_GPIO GPIO_NUM_2
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
#define LCD_PIN_CS GPIO_NUM_14
#define LCD_PIN_DC GPIO_NUM_12
#define LCD_PIN_RD GPIO_NUM_10
#define LCD_PIN_WR GPIO_NUM_11
#define LCD_PIN_RST GPIO_NUM_NC
#define LCD_PIN_D0 GPIO_NUM_13
#define LCD_PIN_D1 GPIO_NUM_9
#define LCD_PIN_D2 GPIO_NUM_8
#define LCD_PIN_D3 GPIO_NUM_7
#define LCD_PIN_D4 GPIO_NUM_6
#define LCD_PIN_D5 GPIO_NUM_5
#define LCD_PIN_D6 GPIO_NUM_4
#define LCD_PIN_D7 GPIO_NUM_3
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#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_21
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
#define R_BUTTON_GPIO GPIO_NUM_0
#define XL9555_INT_GPIO GPIO_NUM_2
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
#define LCD_PIN_CS GPIO_NUM_14
#define LCD_PIN_DC GPIO_NUM_12
#define LCD_PIN_RD GPIO_NUM_10
#define LCD_PIN_WR GPIO_NUM_11
#define LCD_PIN_RST GPIO_NUM_NC
#define LCD_PIN_D0 GPIO_NUM_13
#define LCD_PIN_D1 GPIO_NUM_9
#define LCD_PIN_D2 GPIO_NUM_8
#define LCD_PIN_D3 GPIO_NUM_7
#define LCD_PIN_D4 GPIO_NUM_6
#define LCD_PIN_D5 GPIO_NUM_5
#define LCD_PIN_D6 GPIO_NUM_4
#define LCD_PIN_D7 GPIO_NUM_3
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#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_21
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -1,9 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box2-wifi",
"sdkconfig_append": []
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box2-wifi",
"sdkconfig_append": []
}
]
}

View File

@@ -1,195 +1,195 @@
#pragma once
#include <vector>
#include <functional>
#include "esp_io_expander_tca95xx_16bit.h"
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
esp_io_expander_handle_t xl9555_;
uint32_t pin_val = 0;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> 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_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
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() {
int adc_value;
uint32_t temp_val = 0;
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(500));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2696, 0}, /* 3.48V -屏幕闪屏 */
{2724, 20}, /* 3.53V */
{2861, 40}, /* 3.7V */
{3038, 60}, /* 3.90V */
{3150, 80}, /* 4.02V */
{3280, 100} /* 4.14V */
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2630;
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(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
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_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<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};
#pragma once
#include <vector>
#include <functional>
#include "esp_io_expander_tca95xx_16bit.h"
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
esp_io_expander_handle_t xl9555_;
uint32_t pin_val = 0;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> 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_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
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() {
int adc_value;
uint32_t temp_val = 0;
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(500));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2696, 0}, /* 3.48V -屏幕闪屏 */
{2724, 20}, /* 3.53V */
{2861, 40}, /* 3.7V */
{3038, 60}, /* 3.90V */
{3150, 80}, /* 4.02V */
{3280, 100} /* 4.14V */
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2630;
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(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
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_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<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};

View File

@@ -1,230 +1,230 @@
#include "wifi_board.h"
#include "codecs/es8388_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "led/single_led.h"
#include "esp32_camera.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <wifi_station.h>
#define TAG "atk_dnesp32s3"
class XL9555 : public I2cDevice {
public:
XL9555(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(0x06, 0x03);
WriteReg(0x07, 0xF0);
}
void SetOutputState(uint8_t bit, uint8_t level) {
uint16_t data;
int index = bit;
if (bit < 8) {
data = ReadReg(0x02);
} else {
data = ReadReg(0x03);
index -= 8;
}
data = (data & ~(1 << index)) | (level << index);
if (bit < 8) {
WriteReg(0x02, data);
} else {
WriteReg(0x03, data);
}
}
};
class atk_dnesp32s3 : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
LcdDisplay* display_;
XL9555* xl9555_;
Esp32Camera* camera_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 XL9555
xl9555_ = new XL9555(i2c_bus_, 0x20);
}
// Initialize spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
void InitializeSt7789Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
ESP_LOGD(TAG, "Install panel IO");
// 液晶屏控制IO初始化
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 20 * 1000 * 1000;
io_config.trans_queue_depth = 7;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI2_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;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
esp_lcd_panel_reset(panel);
xl9555_->SetOutputState(8, 1);
xl9555_->SetOutputState(2, 0);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
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);
}
// 初始化摄像头ov2640
// 根据正点原子官方示例参数
void InitializeCamera() {
xl9555_->SetOutputState(OV_PWDN_IO, 0); // PWDN=低 (上电)
xl9555_->SetOutputState(OV_RESET_IO, 0); // 确保复位
vTaskDelay(pdMS_TO_TICKS(50)); // 延长复位保持时间
xl9555_->SetOutputState(OV_RESET_IO, 1); // 释放复位
vTaskDelay(pdMS_TO_TICKS(50)); // 延长 50ms
camera_config_t config = {};
config.pin_pwdn = CAM_PIN_PWDN; // 实际由 XL9555 控制
config.pin_reset = CAM_PIN_RESET;// 实际由 XL9555 控制
config.pin_xclk = CAM_PIN_XCLK;
config.pin_sccb_sda = CAM_PIN_SIOD;
config.pin_sccb_scl = CAM_PIN_SIOC;
config.pin_d7 = CAM_PIN_D7;
config.pin_d6 = CAM_PIN_D6;
config.pin_d5 = CAM_PIN_D5;
config.pin_d4 = CAM_PIN_D4;
config.pin_d3 = CAM_PIN_D3;
config.pin_d2 = CAM_PIN_D2;
config.pin_d1 = CAM_PIN_D1;
config.pin_d0 = CAM_PIN_D0;
config.pin_vsync = CAM_PIN_VSYNC;
config.pin_href = CAM_PIN_HREF;
config.pin_pclk = CAM_PIN_PCLK;
/* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */
config.xclk_freq_hz = 24000000;
config.ledc_timer = LEDC_TIMER_0;
config.ledc_channel = LEDC_CHANNEL_0;
config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */
config.frame_size = FRAMESIZE_QVGA; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */
config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */
config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
esp_err_t err = esp_camera_init(&config); // 测试相机是否存在
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err));
// 如果摄像头初始化失败,设置 camera_ 为 nullptr
camera_ = nullptr;
return;
}else
{
esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备
camera_ = new Esp32Camera(config);
}
}
public:
atk_dnesp32s3() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeSpi();
InitializeSt7789Display();
InitializeButtons();
InitializeCamera();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8388_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Camera* GetCamera() override {
return camera_;
}
};
DECLARE_BOARD(atk_dnesp32s3);
#include "wifi_board.h"
#include "codecs/es8388_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "led/single_led.h"
#include "esp32_camera.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include <wifi_station.h>
#define TAG "atk_dnesp32s3"
class XL9555 : public I2cDevice {
public:
XL9555(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(0x06, 0x03);
WriteReg(0x07, 0xF0);
}
void SetOutputState(uint8_t bit, uint8_t level) {
uint16_t data;
int index = bit;
if (bit < 8) {
data = ReadReg(0x02);
} else {
data = ReadReg(0x03);
index -= 8;
}
data = (data & ~(1 << index)) | (level << index);
if (bit < 8) {
WriteReg(0x02, data);
} else {
WriteReg(0x03, data);
}
}
};
class atk_dnesp32s3 : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
LcdDisplay* display_;
XL9555* xl9555_;
Esp32Camera* camera_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 XL9555
xl9555_ = new XL9555(i2c_bus_, 0x20);
}
// Initialize spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
void InitializeSt7789Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
ESP_LOGD(TAG, "Install panel IO");
// 液晶屏控制IO初始化
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 20 * 1000 * 1000;
io_config.trans_queue_depth = 7;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI2_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;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
esp_lcd_panel_reset(panel);
xl9555_->SetOutputState(8, 1);
xl9555_->SetOutputState(2, 0);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
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);
}
// 初始化摄像头ov2640
// 根据正点原子官方示例参数
void InitializeCamera() {
xl9555_->SetOutputState(OV_PWDN_IO, 0); // PWDN=低 (上电)
xl9555_->SetOutputState(OV_RESET_IO, 0); // 确保复位
vTaskDelay(pdMS_TO_TICKS(50)); // 延长复位保持时间
xl9555_->SetOutputState(OV_RESET_IO, 1); // 释放复位
vTaskDelay(pdMS_TO_TICKS(50)); // 延长 50ms
camera_config_t config = {};
config.pin_pwdn = CAM_PIN_PWDN; // 实际由 XL9555 控制
config.pin_reset = CAM_PIN_RESET;// 实际由 XL9555 控制
config.pin_xclk = CAM_PIN_XCLK;
config.pin_sccb_sda = CAM_PIN_SIOD;
config.pin_sccb_scl = CAM_PIN_SIOC;
config.pin_d7 = CAM_PIN_D7;
config.pin_d6 = CAM_PIN_D6;
config.pin_d5 = CAM_PIN_D5;
config.pin_d4 = CAM_PIN_D4;
config.pin_d3 = CAM_PIN_D3;
config.pin_d2 = CAM_PIN_D2;
config.pin_d1 = CAM_PIN_D1;
config.pin_d0 = CAM_PIN_D0;
config.pin_vsync = CAM_PIN_VSYNC;
config.pin_href = CAM_PIN_HREF;
config.pin_pclk = CAM_PIN_PCLK;
/* XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental) */
config.xclk_freq_hz = 24000000;
config.ledc_timer = LEDC_TIMER_0;
config.ledc_channel = LEDC_CHANNEL_0;
config.pixel_format = PIXFORMAT_RGB565; /* YUV422,GRAYSCALE,RGB565,JPEG */
config.frame_size = FRAMESIZE_QVGA; /* QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates */
config.jpeg_quality = 12; /* 0-63, for OV series camera sensors, lower number means higher quality */
config.fb_count = 2; /* When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode */
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
esp_err_t err = esp_camera_init(&config); // 测试相机是否存在
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera is not plugged in or not supported, error: %s", esp_err_to_name(err));
// 如果摄像头初始化失败,设置 camera_ 为 nullptr
camera_ = nullptr;
return;
}else
{
esp_camera_deinit();// 释放之前的摄像头资源,为正确初始化做准备
camera_ = new Esp32Camera(config);
}
}
public:
atk_dnesp32s3() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeSpi();
InitializeSt7789Display();
InitializeButtons();
InitializeCamera();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8388_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Camera* GetCamera() override {
return camera_;
}
};
DECLARE_BOARD(atk_dnesp32s3);

View File

@@ -1,64 +1,64 @@
#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_I2S_GPIO_MCLK GPIO_NUM_3
#define AUDIO_I2S_GPIO_WS GPIO_NUM_9
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define LCD_SCLK_PIN GPIO_NUM_12
#define LCD_MOSI_PIN GPIO_NUM_11
#define LCD_MISO_PIN GPIO_NUM_13
#define LCD_DC_PIN GPIO_NUM_40
#define LCD_CS_PIN GPIO_NUM_21
#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_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
/* 相机引脚配置 */
#define CAM_PIN_PWDN GPIO_NUM_NC
#define CAM_PIN_RESET GPIO_NUM_NC
#define CAM_PIN_VSYNC GPIO_NUM_47
#define CAM_PIN_HREF GPIO_NUM_48
#define CAM_PIN_PCLK GPIO_NUM_45
#define CAM_PIN_XCLK GPIO_NUM_NC
#define CAM_PIN_SIOD GPIO_NUM_39
#define CAM_PIN_SIOC GPIO_NUM_38
#define CAM_PIN_D0 GPIO_NUM_4
#define CAM_PIN_D1 GPIO_NUM_5
#define CAM_PIN_D2 GPIO_NUM_6
#define CAM_PIN_D3 GPIO_NUM_7
#define CAM_PIN_D4 GPIO_NUM_15
#define CAM_PIN_D5 GPIO_NUM_16
#define CAM_PIN_D6 GPIO_NUM_17
#define CAM_PIN_D7 GPIO_NUM_18
#define OV_PWDN_IO 4
#define OV_RESET_IO 5
#endif // _BOARD_CONFIG_H_
#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_I2S_GPIO_MCLK GPIO_NUM_3
#define AUDIO_I2S_GPIO_WS GPIO_NUM_9
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define LCD_SCLK_PIN GPIO_NUM_12
#define LCD_MOSI_PIN GPIO_NUM_11
#define LCD_MISO_PIN GPIO_NUM_13
#define LCD_DC_PIN GPIO_NUM_40
#define LCD_CS_PIN GPIO_NUM_21
#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_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
/* 相机引脚配置 */
#define CAM_PIN_PWDN GPIO_NUM_NC
#define CAM_PIN_RESET GPIO_NUM_NC
#define CAM_PIN_VSYNC GPIO_NUM_47
#define CAM_PIN_HREF GPIO_NUM_48
#define CAM_PIN_PCLK GPIO_NUM_45
#define CAM_PIN_XCLK GPIO_NUM_NC
#define CAM_PIN_SIOD GPIO_NUM_39
#define CAM_PIN_SIOC GPIO_NUM_38
#define CAM_PIN_D0 GPIO_NUM_4
#define CAM_PIN_D1 GPIO_NUM_5
#define CAM_PIN_D2 GPIO_NUM_6
#define CAM_PIN_D3 GPIO_NUM_7
#define CAM_PIN_D4 GPIO_NUM_15
#define CAM_PIN_D5 GPIO_NUM_16
#define CAM_PIN_D6 GPIO_NUM_17
#define CAM_PIN_D7 GPIO_NUM_18
#define OV_PWDN_IO 4
#define OV_RESET_IO 5
#endif // _BOARD_CONFIG_H_

View File

@@ -1,9 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3",
"sdkconfig_append": []
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3",
"sdkconfig_append": []
}
]
}

View File

@@ -1,220 +1,220 @@
#include "ml307_board.h"
#include "codecs/es8388_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "led/single_led.h"
#include "driver/gpio.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#define TAG "atk_dnesp32s3m_4g"
class atk_dnesp32s3m_4g : public Ml307Board {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
Button phone_button_;
LcdDisplay* display_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
Application::GetInstance().ToggleChatState();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
//不插耳机
phone_button_.OnPressDown([this]() {
gpio_set_level(SPK_EN_PIN, 1);
});
//插入耳机
phone_button_.OnPressUp([this]() {
gpio_set_level(SPK_EN_PIN, 0);
});
}
void InitializeSt7735Display() {
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 = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 60 * 1000 * 1000;
io_config.trans_queue_depth = 7;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
// 初始化液晶屏驱动芯片ST7735
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = LCD_RST_PIN;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
panel_config.bits_per_pixel = 16;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
//使能功放引脚
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
gpio_set_level(SPK_EN_PIN, 0);
//检测耳机是否插入,插入时为高电平
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
//耳机插入
if (gpio_get_level(PHONE_CK_PIN)) {
gpio_set_level(SPK_EN_PIN, 1);
}
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
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);
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);
}
public:
atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN),
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
phone_button_(PHONE_CK_PIN, true) {
InitializeI2c();
InitializeSpi();
InitializeSt7735Display();
InitializeButtons();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8388_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(atk_dnesp32s3m_4g);
#include "ml307_board.h"
#include "codecs/es8388_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "led/single_led.h"
#include "driver/gpio.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#define TAG "atk_dnesp32s3m_4g"
class atk_dnesp32s3m_4g : public Ml307Board {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
Button phone_button_;
LcdDisplay* display_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
Application::GetInstance().ToggleChatState();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
//不插耳机
phone_button_.OnPressDown([this]() {
gpio_set_level(SPK_EN_PIN, 1);
});
//插入耳机
phone_button_.OnPressUp([this]() {
gpio_set_level(SPK_EN_PIN, 0);
});
}
void InitializeSt7735Display() {
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 = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 60 * 1000 * 1000;
io_config.trans_queue_depth = 7;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
// 初始化液晶屏驱动芯片ST7735
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = LCD_RST_PIN;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
panel_config.bits_per_pixel = 16;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
//使能功放引脚
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
gpio_set_level(SPK_EN_PIN, 0);
//检测耳机是否插入,插入时为高电平
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
//耳机插入
if (gpio_get_level(PHONE_CK_PIN)) {
gpio_set_level(SPK_EN_PIN, 1);
}
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
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);
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);
}
public:
atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN),
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
phone_button_(PHONE_CK_PIN, true) {
InitializeI2c();
InitializeSpi();
InitializeSt7735Display();
InitializeButtons();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8388_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(atk_dnesp32s3m_4g);

View File

@@ -1,53 +1,53 @@
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
#define Module_4G_RX_PIN GPIO_NUM_21
#define Module_4G_TX_PIN GPIO_NUM_45
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define LCD_SCLK_PIN GPIO_NUM_12
#define LCD_MOSI_PIN GPIO_NUM_11
#define LCD_MISO_PIN GPIO_NUM_13
#define LCD_DC_PIN GPIO_NUM_40
#define LCD_CS_PIN GPIO_NUM_39
#define LCD_RST_PIN GPIO_NUM_38
#define SPK_EN_PIN GPIO_NUM_42
#define PHONE_CK_PIN GPIO_NUM_3
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 80
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X 1
#define DISPLAY_OFFSET_Y 26
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
#define Module_4G_RX_PIN GPIO_NUM_21
#define Module_4G_TX_PIN GPIO_NUM_45
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define LCD_SCLK_PIN GPIO_NUM_12
#define LCD_MOSI_PIN GPIO_NUM_11
#define LCD_MISO_PIN GPIO_NUM_13
#define LCD_DC_PIN GPIO_NUM_40
#define LCD_CS_PIN GPIO_NUM_39
#define LCD_RST_PIN GPIO_NUM_38
#define SPK_EN_PIN GPIO_NUM_42
#define PHONE_CK_PIN GPIO_NUM_3
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 80
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X 1
#define DISPLAY_OFFSET_Y 26
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -1,230 +1,230 @@
#include "wifi_board.h"
#include "codecs/es8388_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "led/single_led.h"
#include "driver/gpio.h"
#include "assets/lang_config.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#define TAG "atk_dnesp32s3m_wifi"
class atk_dnesp32s3m_wifi : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
Button phone_button_;
LcdDisplay* display_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
boot_button_.OnPressDown([this]() {
Application::GetInstance().StartListening();
});
boot_button_.OnPressUp([this]() {
Application::GetInstance().StopListening();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
//不插耳机
phone_button_.OnPressDown([this]() {
gpio_set_level(SPK_EN_PIN, 1);
});
//插入耳机
phone_button_.OnPressUp([this]() {
gpio_set_level(SPK_EN_PIN, 0);
});
}
void InitializeSt7735Display() {
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 = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 60 * 1000 * 1000;
io_config.trans_queue_depth = 7;
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");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = LCD_RST_PIN;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
panel_config.bits_per_pixel = 16;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
//使能功放引脚
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
gpio_set_level(SPK_EN_PIN, 0);
//检测耳机是否插入,插入时为高电平
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
//耳机插入
if (gpio_get_level(PHONE_CK_PIN)) {
gpio_set_level(SPK_EN_PIN, 1);
}
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
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);
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);
}
public:
atk_dnesp32s3m_wifi() :
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
phone_button_(PHONE_CK_PIN, true) {
InitializeI2c();
InitializeSpi();
InitializeSt7735Display();
InitializeButtons();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8388_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(atk_dnesp32s3m_wifi);
#include "wifi_board.h"
#include "codecs/es8388_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "led/single_led.h"
#include "driver/gpio.h"
#include "assets/lang_config.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#define TAG "atk_dnesp32s3m_wifi"
class atk_dnesp32s3m_wifi : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
Button phone_button_;
LcdDisplay* display_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.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 spi peripheral
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = LCD_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = LCD_SCLK_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(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
boot_button_.OnPressDown([this]() {
Application::GetInstance().StartListening();
});
boot_button_.OnPressUp([this]() {
Application::GetInstance().StopListening();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
//不插耳机
phone_button_.OnPressDown([this]() {
gpio_set_level(SPK_EN_PIN, 1);
});
//插入耳机
phone_button_.OnPressUp([this]() {
gpio_set_level(SPK_EN_PIN, 0);
});
}
void InitializeSt7735Display() {
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 = LCD_CS_PIN;
io_config.dc_gpio_num = LCD_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 60 * 1000 * 1000;
io_config.trans_queue_depth = 7;
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");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = LCD_RST_PIN;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
panel_config.bits_per_pixel = 16;
panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
//使能功放引脚
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
gpio_set_level(SPK_EN_PIN, 0);
//检测耳机是否插入,插入时为高电平
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
//耳机插入
if (gpio_get_level(PHONE_CK_PIN)) {
gpio_set_level(SPK_EN_PIN, 1);
}
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
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);
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);
}
public:
atk_dnesp32s3m_wifi() :
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
phone_button_(PHONE_CK_PIN, true) {
InitializeI2c();
InitializeSpi();
InitializeSt7735Display();
InitializeButtons();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8388_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(atk_dnesp32s3m_wifi);

View File

@@ -1,52 +1,52 @@
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define LCD_SCLK_PIN GPIO_NUM_12
#define LCD_MOSI_PIN GPIO_NUM_11
#define LCD_MISO_PIN GPIO_NUM_13
#define LCD_DC_PIN GPIO_NUM_40
#define LCD_CS_PIN GPIO_NUM_39
#define LCD_RST_PIN GPIO_NUM_38
#define SPK_EN_PIN GPIO_NUM_42
#define PHONE_CK_PIN GPIO_NUM_3
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 80
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X 1
#define DISPLAY_OFFSET_Y 26
// #define DISPLAY_OFFSET_X 0
// #define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_
#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define LCD_SCLK_PIN GPIO_NUM_12
#define LCD_MOSI_PIN GPIO_NUM_11
#define LCD_MISO_PIN GPIO_NUM_13
#define LCD_DC_PIN GPIO_NUM_40
#define LCD_CS_PIN GPIO_NUM_39
#define LCD_RST_PIN GPIO_NUM_38
#define SPK_EN_PIN GPIO_NUM_42
#define PHONE_CK_PIN GPIO_NUM_3
#define DISPLAY_WIDTH 160
#define DISPLAY_HEIGHT 80
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X 1
#define DISPLAY_OFFSET_Y 26
// #define DISPLAY_OFFSET_X 0
// #define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -1,45 +1,45 @@
# AtomEchoS3R
## 简介
AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
## 配置、编译命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig 并配置**
```bash
idf.py menuconfig
```
分别配置如下选项:
- `Xiaozhi Assistant``Board Type` → 选择 `AtomEchoS3R`
- `Partition Table``Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
- `Serial flasher config``Flash size` → 选择 `8 MB`
- `Component config``ESP PSRAM``Support for external, SPI-connected RAM``SPI RAM config` → 选择 `Octal Mode PSRAM`
`S` 保存,按 `Q` 退出。
**编译**
```bash
idf.py build
```
**烧录**
将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
```bash
idf.py flash
```
烧录完毕后,按一下 RESET 按钮重启设备。
# AtomEchoS3R
## 简介
AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
## 配置、编译命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig 并配置**
```bash
idf.py menuconfig
```
分别配置如下选项:
- `Xiaozhi Assistant``Board Type` → 选择 `AtomEchoS3R`
- `Partition Table``Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
- `Serial flasher config``Flash size` → 选择 `8 MB`
- `Component config``ESP PSRAM``Support for external, SPI-connected RAM``SPI RAM config` → 选择 `Octal Mode PSRAM`
`S` 保存,按 `Q` 退出。
**编译**
```bash
idf.py build
```
**烧录**
将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
```bash
idf.py flash
```
烧录完毕后,按一下 RESET 按钮重启设备。

View File

@@ -1,91 +1,91 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#define TAG "AtomEchoS3R"
class AtomEchoS3rBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.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_));
}
void I2cDetect() {
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
InitializeButtons();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomEchoS3rBaseBoard);
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#define TAG "AtomEchoS3R"
class AtomEchoS3rBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.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_));
}
void I2cDetect() {
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
InitializeButtons();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomEchoS3rBaseBoard);

View File

@@ -1,29 +1,29 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomEchoS3R Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
#define AUDIO_I2S_GPIO_WS GPIO_NUM_3
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define USER_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomEchoS3R Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
#define AUDIO_I2S_GPIO_WS GPIO_NUM_3
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define USER_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#endif // _BOARD_CONFIG_H_

View File

@@ -1,12 +1,12 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atom-echos3r",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}
{
"target": "esp32s3",
"builds": [
{
"name": "atom-echos3r",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}

View File

@@ -1,37 +1,25 @@
# 编译配置命令
**配置编译目标为 ESP32**
```bash
idf.py set-target esp32
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base
```
**修改 flash 大小**
```
Serial flasher config -> Flash size -> 4 MB
```
**修改分区表:**
```
Partition Table -> Custom partition CSV file -> partitions/v1/4m.csv
```
**编译:**
```bash
idf.py build
# 编译配置命令
**配置编译目标为 ESP32**
```bash
idf.py set-target esp32
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base
```
**编译**
```bash
idf.py build
```

View File

@@ -1,133 +1,133 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include "led/circular_strip.h"
#define TAG "XX+EchoBase"
#define PI4IOE_ADDR 0x43
#define PI4IOE_REG_CTRL 0x00
#define PI4IOE_REG_IO_PP 0x07
#define PI4IOE_REG_IO_DIR 0x03
#define PI4IOE_REG_IO_OUT 0x05
#define PI4IOE_REG_IO_PULLUP 0x0D
class Pi4ioe : public I2cDevice {
public:
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
}
void SetSpeakerMute(bool mute) {
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
}
};
class AtomMatrixEchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Pi4ioe* pi4ioe_;
Button face_button_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_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_));
}
void I2cDetect() {
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
}
void InitializePi4ioe() {
ESP_LOGI(TAG, "Init PI4IOE");
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
pi4ioe_->SetSpeakerMute(false);
}
void InitializeButtons() {
face_button_.OnClick([this]() {
ESP_LOGI(TAG, " ===>>> face_button_.OnClick ");
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
InitializePi4ioe();
InitializeButtons();
}
virtual Led* GetLed() override {
static CircularStrip led(BUILTIN_LED_GPIO, 25);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomMatrixEchoBaseBoard);
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include "led/circular_strip.h"
#define TAG "XX+EchoBase"
#define PI4IOE_ADDR 0x43
#define PI4IOE_REG_CTRL 0x00
#define PI4IOE_REG_IO_PP 0x07
#define PI4IOE_REG_IO_DIR 0x03
#define PI4IOE_REG_IO_OUT 0x05
#define PI4IOE_REG_IO_PULLUP 0x0D
class Pi4ioe : public I2cDevice {
public:
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
}
void SetSpeakerMute(bool mute) {
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
}
};
class AtomMatrixEchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Pi4ioe* pi4ioe_;
Button face_button_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_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_));
}
void I2cDetect() {
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
}
void InitializePi4ioe() {
ESP_LOGI(TAG, "Init PI4IOE");
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
pi4ioe_->SetSpeakerMute(false);
}
void InitializeButtons() {
face_button_.OnClick([this]() {
ESP_LOGI(TAG, " ===>>> face_button_.OnClick ");
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
InitializePi4ioe();
InitializeButtons();
}
virtual Led* GetLed() override {
static CircularStrip led(BUILTIN_LED_GPIO, 25);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomMatrixEchoBaseBoard);

View File

@@ -1,29 +1,29 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomMatrix+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_19
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_33
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_23
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_22
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_27
#define BOOT_BUTTON_GPIO GPIO_NUM_39
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomMatrix+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_19
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_33
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_23
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_22
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_27
#define BOOT_BUTTON_GPIO GPIO_NUM_39
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#endif // _BOARD_CONFIG_H_

View File

@@ -1,12 +1,10 @@
{
"target": "esp32",
"builds": [
{
"name": "atommatrix-echo-base",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\""
]
}
]
{
"target": "esp32",
"builds": [
{
"name": "atommatrix-echo-base",
"sdkconfig_append": [
]
}
]
}

View File

@@ -1,49 +1,49 @@
# 编译配置命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base
```
**关闭语音唤醒:**
```
Xiaozhi Assistant -> [ ] 启用语音唤醒与音频处理 -> Unselect
```
**修改 flash 大小:**
```
Serial flasher config -> Flash size -> 8 MB
```
**修改分区表:**
```
Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
```
**关闭片外 PSRAM**
```
Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect
```
**编译:**
```bash
idf.py build
# 编译配置命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base
```
**关闭语音唤醒:**
```
Xiaozhi Assistant -> [ ] 启用语音唤醒与音频处理 -> Unselect
```
**修改 flash 大小:**
```
Serial flasher config -> Flash size -> 8 MB
```
**修改分区表:**
```
Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
```
**关闭片外 PSRAM**
```
Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect
```
**编译:**
```bash
idf.py build
```

View File

@@ -1,228 +1,228 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_gc9a01.h>
#define TAG "AtomS3+EchoBase"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb2, (uint8_t[]){0x2f}, 1, 0},
{0xb3, (uint8_t[]){0x03}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x01}, 1, 0},
{0xac, (uint8_t[]){0xcb}, 1, 0},
{0xab, (uint8_t[]){0x0e}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x19}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xe8, (uint8_t[]){0x24}, 1, 0},
{0xe9, (uint8_t[]){0x48}, 1, 0},
{0xea, (uint8_t[]){0x22}, 1, 0},
{0xc6, (uint8_t[]){0x30}, 1, 0},
{0xc7, (uint8_t[]){0x18}, 1, 0},
{0xf0,
(uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
0x00, 0x1c, 0x1f, 0x0f},
14, 0},
{0xf1,
(uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
0x07, 0x0d, 0x11, 0x0f},
14, 0},
};
class AtomS3EchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Display* display_;
Button boot_button_;
bool is_echo_base_connected_ = false;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_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_));
}
void I2cDetect() {
is_echo_base_connected_ = false;
uint8_t echo_base_connected_flag = 0x00;
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
if (address == 0x18) {
echo_base_connected_flag |= 0xF0;
} else if (address == 0x43) {
echo_base_connected_flag |= 0x0F;
}
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
}
void CheckEchoBaseConnection() {
if (is_echo_base_connected_) {
return;
}
// Pop error page
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->SetBrightness(100);
display_->SetStatus(Lang::Strings::ERROR);
display_->SetEmotion("triangle_exclamation");
display_->SetChatMessage("system", "Echo Base\nnot connected");
while (1) {
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
vTaskDelay(pdMS_TO_TICKS(1000));
// Rerun detection
I2cDetect();
if (is_echo_base_connected_) {
vTaskDelay(pdMS_TO_TICKS(500));
I2cDetect();
if (is_echo_base_connected_) {
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
vTaskDelay(pdMS_TO_TICKS(200));
esp_restart();
}
}
}
}
void InitializeSpi() {
ESP_LOGI(TAG, "Initialize SPI bus");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = GPIO_NUM_21;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = GPIO_NUM_17;
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 InitializeGc9107Display() {
ESP_LOGI(TAG, "Init GC9107 display");
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = GPIO_NUM_15;
io_config.dc_gpio_num = GPIO_NUM_33;
io_config.spi_mode = 0;
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(SPI3_HOST, &io_config, &io_handle));
ESP_LOGI(TAG, "Install GC9A01 panel driver");
esp_lcd_panel_handle_t panel_handle = NULL;
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use
panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
panel_config.vendor_config = &gc9107_vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
display_ = new SpiLcdDisplay(io_handle, panel_handle,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
CheckEchoBaseConnection();
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256);
return &backlight;
}
};
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_gc9a01.h>
#define TAG "AtomS3+EchoBase"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb2, (uint8_t[]){0x2f}, 1, 0},
{0xb3, (uint8_t[]){0x03}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x01}, 1, 0},
{0xac, (uint8_t[]){0xcb}, 1, 0},
{0xab, (uint8_t[]){0x0e}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x19}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xe8, (uint8_t[]){0x24}, 1, 0},
{0xe9, (uint8_t[]){0x48}, 1, 0},
{0xea, (uint8_t[]){0x22}, 1, 0},
{0xc6, (uint8_t[]){0x30}, 1, 0},
{0xc7, (uint8_t[]){0x18}, 1, 0},
{0xf0,
(uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
0x00, 0x1c, 0x1f, 0x0f},
14, 0},
{0xf1,
(uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
0x07, 0x0d, 0x11, 0x0f},
14, 0},
};
class AtomS3EchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Display* display_;
Button boot_button_;
bool is_echo_base_connected_ = false;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_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_));
}
void I2cDetect() {
is_echo_base_connected_ = false;
uint8_t echo_base_connected_flag = 0x00;
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
if (address == 0x18) {
echo_base_connected_flag |= 0xF0;
} else if (address == 0x43) {
echo_base_connected_flag |= 0x0F;
}
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
}
void CheckEchoBaseConnection() {
if (is_echo_base_connected_) {
return;
}
// Pop error page
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->SetBrightness(100);
display_->SetStatus(Lang::Strings::ERROR);
display_->SetEmotion("triangle_exclamation");
display_->SetChatMessage("system", "Echo Base\nnot connected");
while (1) {
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
vTaskDelay(pdMS_TO_TICKS(1000));
// Rerun detection
I2cDetect();
if (is_echo_base_connected_) {
vTaskDelay(pdMS_TO_TICKS(500));
I2cDetect();
if (is_echo_base_connected_) {
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
vTaskDelay(pdMS_TO_TICKS(200));
esp_restart();
}
}
}
}
void InitializeSpi() {
ESP_LOGI(TAG, "Initialize SPI bus");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = GPIO_NUM_21;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = GPIO_NUM_17;
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 InitializeGc9107Display() {
ESP_LOGI(TAG, "Init GC9107 display");
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = GPIO_NUM_15;
io_config.dc_gpio_num = GPIO_NUM_33;
io_config.spi_mode = 0;
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(SPI3_HOST, &io_config, &io_handle));
ESP_LOGI(TAG, "Install GC9A01 panel driver");
esp_lcd_panel_handle_t panel_handle = NULL;
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use
panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
panel_config.vendor_config = &gc9107_vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
display_ = new SpiLcdDisplay(io_handle, panel_handle,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
CheckEchoBaseConnection();
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256);
return &backlight;
}
};
DECLARE_BOARD(AtomS3EchoBaseBoard);

View File

@@ -1,43 +1,43 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomS3+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomS3+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -1,14 +1,13 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atoms3-echo-base",
"sdkconfig_append": [
"CONFIG_SPIRAM=n",
"CONFIG_USE_AFE=n",
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atoms3-echo-base",
"sdkconfig_append": [
"CONFIG_SPIRAM=n",
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}

View File

@@ -1,54 +1,54 @@
# AtomS3R CAM/M12 + Echo Base
## 简介
<div align="center">
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam"><b> AtomS3R CAM 产品主页 </b></a>
|
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R-M12"><b> AtomS3R M12 产品主页 </b></a>
|
<a href="https://docs.m5stack.com/zh_CN/atom/Atomic%20Echo%20Base"><b> Echo Base 产品主页 </b></a>
</div>
AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
## 配置、编译命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig 并配置**
```bash
idf.py menuconfig
```
分别配置如下选项:
- `Xiaozhi Assistant``Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base`
- `Xiaozhi Assistant``IoT Protocol` → 选择 `MCP协议` 可开启摄像头识别功能
- `Partition Table``Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
- `Serial flasher config``Flash size` → 选择 `8 MB`
`S` 保存,按 `Q` 退出。
**编译**
```bash
idf.py build
```
**烧录**
将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
```bash
idf.py flash
```
烧录完毕后,按一下 RESET 按钮重启。
# AtomS3R CAM/M12 + Echo Base
## 简介
<div align="center">
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam"><b> AtomS3R CAM 产品主页 </b></a>
|
<a href="https://docs.m5stack.com/zh_CN/core/AtomS3R-M12"><b> AtomS3R M12 产品主页 </b></a>
|
<a href="https://docs.m5stack.com/zh_CN/atom/Atomic%20Echo%20Base"><b> Echo Base 产品主页 </b></a>
</div>
AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器搭载了摄像头。Atomic Echo Base 是一款专为 M5 Atom 系列主机设计的语音识别底座,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
## 配置、编译命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig 并配置**
```bash
idf.py menuconfig
```
分别配置如下选项:
- `Xiaozhi Assistant``Board Type` → 选择 `AtomS3R CAM/M12 + Echo Base`
- `Xiaozhi Assistant``IoT Protocol` → 选择 `MCP协议` 可开启摄像头识别功能
- `Partition Table``Custom partition CSV file` → 删除原有内容,输入 `partitions/v2/8m.csv`
- `Serial flasher config``Flash size` → 选择 `8 MB`
`S` 保存,按 `Q` 退出。
**编译**
```bash
idf.py build
```
**烧录**
将 AtomS3R CAM/M12 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
```bash
idf.py flash
```
烧录完毕后,按一下 RESET 按钮重启。

View File

@@ -1,190 +1,190 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include "esp32_camera.h"
#define TAG "AtomS3R CAM/M12 + EchoBase"
#define PI4IOE_ADDR 0x43
#define PI4IOE_REG_CTRL 0x00
#define PI4IOE_REG_IO_PP 0x07
#define PI4IOE_REG_IO_DIR 0x03
#define PI4IOE_REG_IO_OUT 0x05
#define PI4IOE_REG_IO_PULLUP 0x0D
class Pi4ioe : public I2cDevice {
public:
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
}
void SetSpeakerMute(bool mute) {
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
}
};
class AtomS3rCamM12EchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Pi4ioe* pi4ioe_ = nullptr;
bool is_echo_base_connected_ = false;
Esp32Camera* camera_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.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_));
}
void I2cDetect() {
is_echo_base_connected_ = false;
uint8_t echo_base_connected_flag = 0x00;
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
if (address == 0x18) {
echo_base_connected_flag |= 0xF0;
} else if (address == 0x43) {
echo_base_connected_flag |= 0x0F;
}
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
}
void CheckEchoBaseConnection() {
if (is_echo_base_connected_) {
return;
}
while (1) {
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
vTaskDelay(pdMS_TO_TICKS(1000));
// Rerun detection
I2cDetect();
if (is_echo_base_connected_) {
vTaskDelay(pdMS_TO_TICKS(500));
I2cDetect();
if (is_echo_base_connected_) {
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
vTaskDelay(pdMS_TO_TICKS(200));
esp_restart();
}
}
}
}
void InitializePi4ioe() {
ESP_LOGI(TAG, "Init PI4IOE");
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
pi4ioe_->SetSpeakerMute(false);
}
void EnableCameraPower() {
gpio_reset_pin((gpio_num_t)18);
gpio_set_direction((gpio_num_t)18, GPIO_MODE_OUTPUT);
gpio_set_pull_mode((gpio_num_t)18, GPIO_PULLDOWN_ONLY);
ESP_LOGI(TAG, "Camera Power Enabled");
vTaskDelay(pdMS_TO_TICKS(300));
}
void InitializeCamera() {
camera_config_t config = {};
config.pin_d0 = CAMERA_PIN_D0;
config.pin_d1 = CAMERA_PIN_D1;
config.pin_d2 = CAMERA_PIN_D2;
config.pin_d3 = CAMERA_PIN_D3;
config.pin_d4 = CAMERA_PIN_D4;
config.pin_d5 = CAMERA_PIN_D5;
config.pin_d6 = CAMERA_PIN_D6;
config.pin_d7 = CAMERA_PIN_D7;
config.pin_xclk = CAMERA_PIN_XCLK;
config.pin_pclk = CAMERA_PIN_PCLK;
config.pin_vsync = CAMERA_PIN_VSYNC;
config.pin_href = CAMERA_PIN_HREF;
config.pin_sccb_sda = CAMERA_PIN_SIOD;
config.pin_sccb_scl = CAMERA_PIN_SIOC;
config.sccb_i2c_port = 1;
config.pin_pwdn = CAMERA_PIN_PWDN;
config.pin_reset = CAMERA_PIN_RESET;
config.xclk_freq_hz = XCLK_FREQ_HZ;
config.pixel_format = PIXFORMAT_RGB565;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
camera_ = new Esp32Camera(config);
camera_->SetHMirror(false);
}
virtual Camera* GetCamera() override {
return camera_;
}
public:
AtomS3rCamM12EchoBaseBoard() {
EnableCameraPower(); // IO18 还会控制指示灯
InitializeCamera();
InitializeI2c();
I2cDetect();
CheckEchoBaseConnection();
InitializePi4ioe();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomS3rCamM12EchoBaseBoard);
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include "esp32_camera.h"
#define TAG "AtomS3R CAM/M12 + EchoBase"
#define PI4IOE_ADDR 0x43
#define PI4IOE_REG_CTRL 0x00
#define PI4IOE_REG_IO_PP 0x07
#define PI4IOE_REG_IO_DIR 0x03
#define PI4IOE_REG_IO_OUT 0x05
#define PI4IOE_REG_IO_PULLUP 0x0D
class Pi4ioe : public I2cDevice {
public:
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
}
void SetSpeakerMute(bool mute) {
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
}
};
class AtomS3rCamM12EchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Pi4ioe* pi4ioe_ = nullptr;
bool is_echo_base_connected_ = false;
Esp32Camera* camera_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.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_));
}
void I2cDetect() {
is_echo_base_connected_ = false;
uint8_t echo_base_connected_flag = 0x00;
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
if (address == 0x18) {
echo_base_connected_flag |= 0xF0;
} else if (address == 0x43) {
echo_base_connected_flag |= 0x0F;
}
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
}
void CheckEchoBaseConnection() {
if (is_echo_base_connected_) {
return;
}
while (1) {
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
vTaskDelay(pdMS_TO_TICKS(1000));
// Rerun detection
I2cDetect();
if (is_echo_base_connected_) {
vTaskDelay(pdMS_TO_TICKS(500));
I2cDetect();
if (is_echo_base_connected_) {
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
vTaskDelay(pdMS_TO_TICKS(200));
esp_restart();
}
}
}
}
void InitializePi4ioe() {
ESP_LOGI(TAG, "Init PI4IOE");
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
pi4ioe_->SetSpeakerMute(false);
}
void EnableCameraPower() {
gpio_reset_pin((gpio_num_t)18);
gpio_set_direction((gpio_num_t)18, GPIO_MODE_OUTPUT);
gpio_set_pull_mode((gpio_num_t)18, GPIO_PULLDOWN_ONLY);
ESP_LOGI(TAG, "Camera Power Enabled");
vTaskDelay(pdMS_TO_TICKS(300));
}
void InitializeCamera() {
camera_config_t config = {};
config.pin_d0 = CAMERA_PIN_D0;
config.pin_d1 = CAMERA_PIN_D1;
config.pin_d2 = CAMERA_PIN_D2;
config.pin_d3 = CAMERA_PIN_D3;
config.pin_d4 = CAMERA_PIN_D4;
config.pin_d5 = CAMERA_PIN_D5;
config.pin_d6 = CAMERA_PIN_D6;
config.pin_d7 = CAMERA_PIN_D7;
config.pin_xclk = CAMERA_PIN_XCLK;
config.pin_pclk = CAMERA_PIN_PCLK;
config.pin_vsync = CAMERA_PIN_VSYNC;
config.pin_href = CAMERA_PIN_HREF;
config.pin_sccb_sda = CAMERA_PIN_SIOD;
config.pin_sccb_scl = CAMERA_PIN_SIOC;
config.sccb_i2c_port = 1;
config.pin_pwdn = CAMERA_PIN_PWDN;
config.pin_reset = CAMERA_PIN_RESET;
config.xclk_freq_hz = XCLK_FREQ_HZ;
config.pixel_format = PIXFORMAT_RGB565;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
camera_ = new Esp32Camera(config);
camera_->SetHMirror(false);
}
virtual Camera* GetCamera() override {
return camera_;
}
public:
AtomS3rCamM12EchoBaseBoard() {
EnableCameraPower(); // IO18 还会控制指示灯
InitializeCamera();
InitializeI2c();
I2cDetect();
CheckEchoBaseConnection();
InitializePi4ioe();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomS3rCamM12EchoBaseBoard);

View File

@@ -1,49 +1,49 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomS3R M12+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define CAMERA_PIN_PWDN GPIO_NUM_NC
#define CAMERA_PIN_RESET GPIO_NUM_NC
#define CAMERA_PIN_VSYNC GPIO_NUM_10
#define CAMERA_PIN_HREF GPIO_NUM_14
#define CAMERA_PIN_PCLK GPIO_NUM_40
#define CAMERA_PIN_XCLK GPIO_NUM_21
#define CAMERA_PIN_SIOD GPIO_NUM_12
#define CAMERA_PIN_SIOC GPIO_NUM_9
#define CAMERA_PIN_D0 GPIO_NUM_3
#define CAMERA_PIN_D1 GPIO_NUM_42
#define CAMERA_PIN_D2 GPIO_NUM_46
#define CAMERA_PIN_D3 GPIO_NUM_48
#define CAMERA_PIN_D4 GPIO_NUM_4
#define CAMERA_PIN_D5 GPIO_NUM_17
#define CAMERA_PIN_D6 GPIO_NUM_11
#define CAMERA_PIN_D7 GPIO_NUM_13
#define CAMERA_XCLK_FREQ (20000000)
#define XCLK_FREQ_HZ CAMERA_XCLK_FREQ
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomS3R M12+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define CAMERA_PIN_PWDN GPIO_NUM_NC
#define CAMERA_PIN_RESET GPIO_NUM_NC
#define CAMERA_PIN_VSYNC GPIO_NUM_10
#define CAMERA_PIN_HREF GPIO_NUM_14
#define CAMERA_PIN_PCLK GPIO_NUM_40
#define CAMERA_PIN_XCLK GPIO_NUM_21
#define CAMERA_PIN_SIOD GPIO_NUM_12
#define CAMERA_PIN_SIOC GPIO_NUM_9
#define CAMERA_PIN_D0 GPIO_NUM_3
#define CAMERA_PIN_D1 GPIO_NUM_42
#define CAMERA_PIN_D2 GPIO_NUM_46
#define CAMERA_PIN_D3 GPIO_NUM_48
#define CAMERA_PIN_D4 GPIO_NUM_4
#define CAMERA_PIN_D5 GPIO_NUM_17
#define CAMERA_PIN_D6 GPIO_NUM_11
#define CAMERA_PIN_D7 GPIO_NUM_13
#define CAMERA_XCLK_FREQ (20000000)
#define XCLK_FREQ_HZ CAMERA_XCLK_FREQ
#endif // _BOARD_CONFIG_H_

View File

@@ -1,12 +1,12 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atoms3r-cam-m12-echo-base",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atoms3r-cam-m12-echo-base",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}

View File

@@ -1,308 +1,308 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_gc9a01.h>
#define TAG "AtomS3R+EchoBase"
#define PI4IOE_ADDR 0x43
#define PI4IOE_REG_CTRL 0x00
#define PI4IOE_REG_IO_PP 0x07
#define PI4IOE_REG_IO_DIR 0x03
#define PI4IOE_REG_IO_OUT 0x05
#define PI4IOE_REG_IO_PULLUP 0x0D
class Pi4ioe : public I2cDevice {
public:
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
}
void SetSpeakerMute(bool mute) {
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
}
};
class Lp5562 : public I2cDevice {
public:
Lp5562(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(0x00, 0B01000000); // Set chip_en to 1
WriteReg(0x08, 0B00000001); // Enable internal clock
WriteReg(0x70, 0B00000000); // Configure all LED outputs to be controlled from I2C registers
// PWM clock frequency 558 Hz
auto data = ReadReg(0x08);
data = data | 0B01000000;
WriteReg(0x08, data);
}
void SetBrightness(uint8_t brightness) {
// Map 0~100 to 0~255
brightness = brightness * 255 / 100;
WriteReg(0x0E, brightness);
}
};
class CustomBacklight : public Backlight {
public:
CustomBacklight(Lp5562* lp5562) : lp5562_(lp5562) {}
void SetBrightnessImpl(uint8_t brightness) override {
if (lp5562_) {
lp5562_->SetBrightness(brightness);
} else {
ESP_LOGE(TAG, "LP5562 not available");
}
}
private:
Lp5562* lp5562_ = nullptr;
};
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb2, (uint8_t[]){0x2f}, 1, 0},
{0xb3, (uint8_t[]){0x03}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x01}, 1, 0},
{0xac, (uint8_t[]){0xcb}, 1, 0},
{0xab, (uint8_t[]){0x0e}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x19}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xe8, (uint8_t[]){0x24}, 1, 0},
{0xe9, (uint8_t[]){0x48}, 1, 0},
{0xea, (uint8_t[]){0x22}, 1, 0},
{0xc6, (uint8_t[]){0x30}, 1, 0},
{0xc7, (uint8_t[]){0x18}, 1, 0},
{0xf0,
(uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
0x00, 0x1c, 0x1f, 0x0f},
14, 0},
{0xf1,
(uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
0x07, 0x0d, 0x11, 0x0f},
14, 0},
};
class AtomS3rEchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
i2c_master_bus_handle_t i2c_bus_internal_;
Pi4ioe* pi4ioe_ = nullptr;
Lp5562* lp5562_ = nullptr;
Display* display_ = nullptr;
Button boot_button_;
bool is_echo_base_connected_ = false;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_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_));
i2c_bus_cfg.i2c_port = I2C_NUM_0;
i2c_bus_cfg.sda_io_num = GPIO_NUM_45;
i2c_bus_cfg.scl_io_num = GPIO_NUM_0;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_internal_));
}
void I2cDetect() {
is_echo_base_connected_ = false;
uint8_t echo_base_connected_flag = 0x00;
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
if (address == 0x18) {
echo_base_connected_flag |= 0xF0;
} else if (address == 0x43) {
echo_base_connected_flag |= 0x0F;
}
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
}
void CheckEchoBaseConnection() {
if (is_echo_base_connected_) {
return;
}
// Pop error page
InitializeLp5562();
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->SetBrightness(100);
display_->SetStatus(Lang::Strings::ERROR);
display_->SetEmotion("triangle_exclamation");
display_->SetChatMessage("system", "Echo Base\nnot connected");
while (1) {
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
vTaskDelay(pdMS_TO_TICKS(1000));
// Rerun detection
I2cDetect();
if (is_echo_base_connected_) {
vTaskDelay(pdMS_TO_TICKS(500));
I2cDetect();
if (is_echo_base_connected_) {
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
vTaskDelay(pdMS_TO_TICKS(200));
esp_restart();
}
}
}
}
void InitializePi4ioe() {
ESP_LOGI(TAG, "Init PI4IOE");
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
pi4ioe_->SetSpeakerMute(false);
}
void InitializeLp5562() {
ESP_LOGI(TAG, "Init LP5562");
lp5562_ = new Lp5562(i2c_bus_internal_, 0x30);
}
void InitializeSpi() {
ESP_LOGI(TAG, "Initialize SPI bus");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = GPIO_NUM_21;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = GPIO_NUM_15;
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 InitializeGc9107Display() {
ESP_LOGI(TAG, "Init GC9107 display");
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = GPIO_NUM_14;
io_config.dc_gpio_num = GPIO_NUM_42;
io_config.spi_mode = 0;
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(SPI3_HOST, &io_config, &io_handle));
ESP_LOGI(TAG, "Install GC9A01 panel driver");
esp_lcd_panel_handle_t panel_handle = NULL;
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_48; // Set to -1 if not use
panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
panel_config.vendor_config = &gc9107_vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
display_ = new SpiLcdDisplay(io_handle, panel_handle,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomS3rEchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
CheckEchoBaseConnection();
InitializePi4ioe();
InitializeLp5562();
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight *GetBacklight() override {
static CustomBacklight backlight(lp5562_);
return &backlight;
}
};
DECLARE_BOARD(AtomS3rEchoBaseBoard);
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_gc9a01.h>
#define TAG "AtomS3R+EchoBase"
#define PI4IOE_ADDR 0x43
#define PI4IOE_REG_CTRL 0x00
#define PI4IOE_REG_IO_PP 0x07
#define PI4IOE_REG_IO_DIR 0x03
#define PI4IOE_REG_IO_OUT 0x05
#define PI4IOE_REG_IO_PULLUP 0x0D
class Pi4ioe : public I2cDevice {
public:
Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
}
void SetSpeakerMute(bool mute) {
WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
}
};
class Lp5562 : public I2cDevice {
public:
Lp5562(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
WriteReg(0x00, 0B01000000); // Set chip_en to 1
WriteReg(0x08, 0B00000001); // Enable internal clock
WriteReg(0x70, 0B00000000); // Configure all LED outputs to be controlled from I2C registers
// PWM clock frequency 558 Hz
auto data = ReadReg(0x08);
data = data | 0B01000000;
WriteReg(0x08, data);
}
void SetBrightness(uint8_t brightness) {
// Map 0~100 to 0~255
brightness = brightness * 255 / 100;
WriteReg(0x0E, brightness);
}
};
class CustomBacklight : public Backlight {
public:
CustomBacklight(Lp5562* lp5562) : lp5562_(lp5562) {}
void SetBrightnessImpl(uint8_t brightness) override {
if (lp5562_) {
lp5562_->SetBrightness(brightness);
} else {
ESP_LOGE(TAG, "LP5562 not available");
}
}
private:
Lp5562* lp5562_ = nullptr;
};
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb2, (uint8_t[]){0x2f}, 1, 0},
{0xb3, (uint8_t[]){0x03}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x01}, 1, 0},
{0xac, (uint8_t[]){0xcb}, 1, 0},
{0xab, (uint8_t[]){0x0e}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x19}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xe8, (uint8_t[]){0x24}, 1, 0},
{0xe9, (uint8_t[]){0x48}, 1, 0},
{0xea, (uint8_t[]){0x22}, 1, 0},
{0xc6, (uint8_t[]){0x30}, 1, 0},
{0xc7, (uint8_t[]){0x18}, 1, 0},
{0xf0,
(uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
0x00, 0x1c, 0x1f, 0x0f},
14, 0},
{0xf1,
(uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
0x07, 0x0d, 0x11, 0x0f},
14, 0},
};
class AtomS3rEchoBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
i2c_master_bus_handle_t i2c_bus_internal_;
Pi4ioe* pi4ioe_ = nullptr;
Lp5562* lp5562_ = nullptr;
Display* display_ = nullptr;
Button boot_button_;
bool is_echo_base_connected_ = false;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_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_));
i2c_bus_cfg.i2c_port = I2C_NUM_0;
i2c_bus_cfg.sda_io_num = GPIO_NUM_45;
i2c_bus_cfg.scl_io_num = GPIO_NUM_0;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_internal_));
}
void I2cDetect() {
is_echo_base_connected_ = false;
uint8_t echo_base_connected_flag = 0x00;
uint8_t address;
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
for (int i = 0; i < 128; i += 16) {
printf("%02x: ", i);
for (int j = 0; j < 16; j++) {
fflush(stdout);
address = i + j;
esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
if (ret == ESP_OK) {
printf("%02x ", address);
if (address == 0x18) {
echo_base_connected_flag |= 0xF0;
} else if (address == 0x43) {
echo_base_connected_flag |= 0x0F;
}
} else if (ret == ESP_ERR_TIMEOUT) {
printf("UU ");
} else {
printf("-- ");
}
}
printf("\r\n");
}
is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
}
void CheckEchoBaseConnection() {
if (is_echo_base_connected_) {
return;
}
// Pop error page
InitializeLp5562();
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->SetBrightness(100);
display_->SetStatus(Lang::Strings::ERROR);
display_->SetEmotion("triangle_exclamation");
display_->SetChatMessage("system", "Echo Base\nnot connected");
while (1) {
ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
vTaskDelay(pdMS_TO_TICKS(1000));
// Rerun detection
I2cDetect();
if (is_echo_base_connected_) {
vTaskDelay(pdMS_TO_TICKS(500));
I2cDetect();
if (is_echo_base_connected_) {
ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
vTaskDelay(pdMS_TO_TICKS(200));
esp_restart();
}
}
}
}
void InitializePi4ioe() {
ESP_LOGI(TAG, "Init PI4IOE");
pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
pi4ioe_->SetSpeakerMute(false);
}
void InitializeLp5562() {
ESP_LOGI(TAG, "Init LP5562");
lp5562_ = new Lp5562(i2c_bus_internal_, 0x30);
}
void InitializeSpi() {
ESP_LOGI(TAG, "Initialize SPI bus");
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = GPIO_NUM_21;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = GPIO_NUM_15;
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 InitializeGc9107Display() {
ESP_LOGI(TAG, "Init GC9107 display");
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = GPIO_NUM_14;
io_config.dc_gpio_num = GPIO_NUM_42;
io_config.spi_mode = 0;
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(SPI3_HOST, &io_config, &io_handle));
ESP_LOGI(TAG, "Install GC9A01 panel driver");
esp_lcd_panel_handle_t panel_handle = NULL;
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_48; // Set to -1 if not use
panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
panel_config.vendor_config = &gc9107_vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
display_ = new SpiLcdDisplay(io_handle, panel_handle,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomS3rEchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
CheckEchoBaseConnection();
InitializePi4ioe();
InitializeLp5562();
InitializeSpi();
InitializeGc9107Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight *GetBacklight() override {
static CustomBacklight backlight(lp5562_);
return &backlight;
}
};
DECLARE_BOARD(AtomS3rEchoBaseBoard);

View File

@@ -1,43 +1,43 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomS3R+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomS3R+EchoBase Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define BOOT_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_

View File

@@ -1,12 +1,12 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atoms3r-echo-base",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "atoms3r-echo-base",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}

View File

@@ -1,276 +1,278 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_5
#define ASR_BUTTON_GPIO GPIO_NUM_19
#define BUILTIN_LED_GPIO GPIO_NUM_2
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22
#define DISPLAY_CS_PIN GPIO_NUM_NC
#else
#define DISPLAY_CS_PIN GPIO_NUM_22
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_23
#endif
#define DISPLAY_MOSI_PIN GPIO_NUM_4
#define DISPLAY_CLK_PIN GPIO_NUM_15
#define DISPLAY_DC_PIN GPIO_NUM_21
#define DISPLAY_RST_PIN GPIO_NUM_18
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 2
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#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
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_5
#define ASR_BUTTON_GPIO GPIO_NUM_19
#define BUILTIN_LED_GPIO GPIO_NUM_2
#define ML307_RX_PIN GPIO_NUM_16
#define ML307_TX_PIN GPIO_NUM_17
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22
#define DISPLAY_CS_PIN GPIO_NUM_NC
#else
#define DISPLAY_CS_PIN GPIO_NUM_22
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_23
#endif
#define DISPLAY_MOSI_PIN GPIO_NUM_4
#define DISPLAY_CLK_PIN GPIO_NUM_15
#define DISPLAY_DC_PIN GPIO_NUM_21
#define DISPLAY_RST_PIN GPIO_NUM_18
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 2
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#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
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#endif // _BOARD_CONFIG_H_

View File

@@ -1,13 +1,11 @@
{
"target": "esp32",
"builds": [
{
"name": "bread-compact-esp32-lcd",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"",
"LCD_ST7789_240X240_7PIN=y"
]
}
]
{
"target": "esp32",
"builds": [
{
"name": "bread-compact-esp32-lcd",
"sdkconfig_append": [
"LCD_ST7789_240X240_7PIN=y"
]
}
]
}

View File

@@ -1,200 +1,213 @@
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "led/single_led.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include "esp_lcd_ili9341.h"
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include "esp_lcd_gc9a01.h"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "ESP32-LCD-MarsbearSupport"
class CompactWifiBoardLCD : public WifiBoard {
private:
Button boot_button_;
Button touch_button_;
Button asr_button_;
LcdDisplay* display_;
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(SPI3_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 = 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(SPI3_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;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
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);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
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);
}
void InitializeButtons() {
// 配置 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
.intr_type = GPIO_INTR_DISABLE // 禁用中断
};
gpio_config(&io_conf); // 应用配置
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
gpio_set_level(BUILTIN_LED_GPIO, 1);
app.ToggleChatState();
});
asr_button_.OnClick([this]() {
std::string wake_word="你好小智";
Application::GetInstance().WakeWordInvoke(wake_word);
});
touch_button_.OnPressDown([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 1);
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 0);
Application::GetInstance().StopListening();
});
}
public:
CompactWifiBoardLCD() :
boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(CompactWifiBoardLCD);
#include "dual_network_board.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "led/single_led.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include "esp_lcd_ili9341.h"
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include "esp_lcd_gc9a01.h"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "ESP32-LCD-MarsbearSupport"
class CompactWifiBoardLCD : public DualNetworkBoard {
private:
Button boot_button_;
Button touch_button_;
Button asr_button_;
LcdDisplay* display_;
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(SPI3_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 = 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(SPI3_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;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
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);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
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);
}
void InitializeButtons() {
// 配置 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
.intr_type = GPIO_INTR_DISABLE // 禁用中断
};
gpio_config(&io_conf); // 应用配置
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
// cast to WifiBoard
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
}
gpio_set_level(BUILTIN_LED_GPIO, 1);
app.ToggleChatState();
});
boot_button_.OnDoubleClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
SwitchNetworkType();
}
});
asr_button_.OnClick([this]() {
std::string wake_word="你好小智";
Application::GetInstance().WakeWordInvoke(wake_word);
});
touch_button_.OnPressDown([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 1);
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 0);
Application::GetInstance().StopListening();
});
}
public:
CompactWifiBoardLCD() :
DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(CompactWifiBoardLCD);

View File

@@ -1,37 +1,25 @@
# 编译配置命令
**配置编译目标为 ESP32**
```bash
idf.py set-target esp32
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type -> 面包板 ESP32 DevKit
```
**修改 flash 大小**
```
Serial flasher config -> Flash size -> 4 MB
```
**修改分区表:**
```
Partition Table -> Custom partition CSV file -> partitions/v1/4m.csv
```
**编译:**
```bash
idf.py build
# 编译配置命令
**配置编译目标为 ESP32**
```bash
idf.py set-target esp32
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type -> 面包板 ESP32 DevKit
```
**编译**
```bash
idf.py build
```

View File

@@ -1,55 +1,58 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_5
#define ASR_BUTTON_GPIO GPIO_NUM_19
#define BUILTIN_LED_GPIO GPIO_NUM_2
#define DISPLAY_SDA_PIN GPIO_NUM_4
#define DISPLAY_SCL_PIN GPIO_NUM_15
#define DISPLAY_WIDTH 128
#if CONFIG_OLED_SSD1306_128X32
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#else
#error "未选择 OLED 屏幕类型"
#endif
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_5
#define ASR_BUTTON_GPIO GPIO_NUM_19
#define BUILTIN_LED_GPIO GPIO_NUM_2
#define ML307_RX_PIN GPIO_NUM_16
#define ML307_TX_PIN GPIO_NUM_17
#define DISPLAY_SDA_PIN GPIO_NUM_4
#define DISPLAY_SCL_PIN GPIO_NUM_15
#define DISPLAY_WIDTH 128
#if CONFIG_OLED_SSD1306_128X32
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#else
#error "未选择 OLED 屏幕类型"
#endif
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_

View File

@@ -1,21 +1,17 @@
{
"target": "esp32",
"builds": [
{
"name": "bread-compact-esp32",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"",
"CONFIG_OLED_SSD1306_128X64=y"
]
},
{
"name": "bread-compact-esp32-128x32",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/4m.csv\"",
"CONFIG_OLED_SSD1306_128X32=y"
]
}
]
{
"target": "esp32",
"builds": [
{
"name": "bread-compact-esp32",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X64=y"
]
},
{
"name": "bread-compact-esp32-128x32",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X32=y"
]
}
]
}

View File

@@ -1,162 +1,174 @@
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "display/oled_display.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#define TAG "ESP32-MarsbearSupport"
class CompactWifiBoard : public WifiBoard {
private:
Button boot_button_;
Button touch_button_;
Button asr_button_;
i2c_master_bus_handle_t display_i2c_bus_;
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
Display* display_ = nullptr;
void InitializeDisplayI2c() {
i2c_master_bus_config_t bus_config = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = DISPLAY_SDA_PIN,
.scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
}
void InitializeSsd1306Display() {
// SSD1306 config
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = 0x3C,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
ESP_LOGI(TAG, "Install SSD1306 driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = -1;
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
};
panel_config.vendor_config = &ssd1306_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
if (esp_lcd_panel_init(panel_) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize display");
display_ = new NoDisplay();
return;
}
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
}
void InitializeButtons() {
// 配置 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
.intr_type = GPIO_INTR_DISABLE // 禁用中断
};
gpio_config(&io_conf); // 应用配置
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
gpio_set_level(BUILTIN_LED_GPIO, 1);
app.ToggleChatState();
});
asr_button_.OnClick([this]() {
std::string wake_word="你好小智";
Application::GetInstance().WakeWordInvoke(wake_word);
});
touch_button_.OnPressDown([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 1);
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 0);
Application::GetInstance().StopListening();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactWifiBoard() : boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO)
{
InitializeDisplayI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializeTools();
}
virtual AudioCodec* GetAudioCodec() override
{
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(CompactWifiBoard);
#include "dual_network_board.h"
#include "codecs/no_audio_codec.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "display/oled_display.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#define TAG "ESP32-MarsbearSupport"
class CompactWifiBoard : public DualNetworkBoard {
private:
Button boot_button_;
Button touch_button_;
Button asr_button_;
i2c_master_bus_handle_t display_i2c_bus_;
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
Display* display_ = nullptr;
void InitializeDisplayI2c() {
i2c_master_bus_config_t bus_config = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = DISPLAY_SDA_PIN,
.scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
}
void InitializeSsd1306Display() {
// SSD1306 config
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = 0x3C,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
ESP_LOGI(TAG, "Install SSD1306 driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = -1;
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
};
panel_config.vendor_config = &ssd1306_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
if (esp_lcd_panel_init(panel_) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize display");
display_ = new NoDisplay();
return;
}
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
}
void InitializeButtons() {
// 配置 GPIO
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 设置需要配置的 GPIO 引脚
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
.intr_type = GPIO_INTR_DISABLE // 禁用中断
};
gpio_config(&io_conf); // 应用配置
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
// cast to WifiBoard
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
}
gpio_set_level(BUILTIN_LED_GPIO, 1);
app.ToggleChatState();
});
boot_button_.OnDoubleClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
SwitchNetworkType();
}
});
asr_button_.OnClick([this]() {
std::string wake_word="你好小智";
Application::GetInstance().WakeWordInvoke(wake_word);
});
touch_button_.OnPressDown([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 1);
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
gpio_set_level(BUILTIN_LED_GPIO, 0);
Application::GetInstance().StopListening();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactWifiBoard() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO)
{
InitializeDisplayI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializeTools();
}
virtual AudioCodec* GetAudioCodec() override
{
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(CompactWifiBoard);

View File

@@ -1,191 +1,191 @@
#include "dual_network_board.h"
#include "codecs/no_audio_codec.h"
#include "display/oled_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#define TAG "CompactMl307Board"
class CompactMl307Board : public DualNetworkBoard {
private:
i2c_master_bus_handle_t display_i2c_bus_;
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
Display* display_ = nullptr;
Button boot_button_;
Button touch_button_;
Button volume_up_button_;
Button volume_down_button_;
void InitializeDisplayI2c() {
i2c_master_bus_config_t bus_config = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = DISPLAY_SDA_PIN,
.scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
}
void InitializeSsd1306Display() {
// SSD1306 config
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = 0x3C,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
ESP_LOGI(TAG, "Install SSD1306 driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = -1;
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
};
panel_config.vendor_config = &ssd1306_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
if (esp_lcd_panel_init(panel_) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize display");
display_ = new NoDisplay();
return;
}
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
// cast to WifiBoard
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
}
app.ToggleChatState();
});
boot_button_.OnDoubleClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
SwitchNetworkType();
}
});
touch_button_.OnPressDown([this]() {
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
Application::GetInstance().StopListening();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactMl307Board() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC),
boot_button_(BOOT_BUTTON_GPIO),
touch_button_(TOUCH_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
InitializeDisplayI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializeTools();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(CompactMl307Board);
#include "dual_network_board.h"
#include "codecs/no_audio_codec.h"
#include "display/oled_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#define TAG "CompactMl307Board"
class CompactMl307Board : public DualNetworkBoard {
private:
i2c_master_bus_handle_t display_i2c_bus_;
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
Display* display_ = nullptr;
Button boot_button_;
Button touch_button_;
Button volume_up_button_;
Button volume_down_button_;
void InitializeDisplayI2c() {
i2c_master_bus_config_t bus_config = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = DISPLAY_SDA_PIN,
.scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
}
void InitializeSsd1306Display() {
// SSD1306 config
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = 0x3C,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
ESP_LOGI(TAG, "Install SSD1306 driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = -1;
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
};
panel_config.vendor_config = &ssd1306_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
if (esp_lcd_panel_init(panel_) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize display");
display_ = new NoDisplay();
return;
}
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
// cast to WifiBoard
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
}
app.ToggleChatState();
});
boot_button_.OnDoubleClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
SwitchNetworkType();
}
});
touch_button_.OnPressDown([this]() {
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
Application::GetInstance().StopListening();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactMl307Board() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC),
boot_button_(BOOT_BUTTON_GPIO),
touch_button_(TOUCH_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
InitializeDisplayI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializeTools();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(CompactMl307Board);

View File

@@ -1,59 +1,59 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_47
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
#define DISPLAY_SDA_PIN GPIO_NUM_41
#define DISPLAY_SCL_PIN GPIO_NUM_42
#define DISPLAY_WIDTH 128
#if CONFIG_OLED_SSD1306_128X32
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#else
#error "未选择 OLED 屏幕类型"
#endif
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define ML307_RX_PIN GPIO_NUM_11
#define ML307_TX_PIN GPIO_NUM_12
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_47
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
#define DISPLAY_SDA_PIN GPIO_NUM_41
#define DISPLAY_SCL_PIN GPIO_NUM_42
#define DISPLAY_WIDTH 128
#if CONFIG_OLED_SSD1306_128X32
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#else
#error "未选择 OLED 屏幕类型"
#endif
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define ML307_RX_PIN GPIO_NUM_11
#define ML307_TX_PIN GPIO_NUM_12
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_

View File

@@ -1,17 +1,17 @@
{
"target": "esp32s3",
"builds": [
{
"name": "bread-compact-ml307",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X32=y"
]
},
{
"name": "bread-compact-ml307-128x64",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X64=y"
]
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "bread-compact-ml307",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X32=y"
]
},
{
"name": "bread-compact-ml307-128x64",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X64=y"
]
}
]
}

View File

@@ -1,183 +1,183 @@
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include "esp_lcd_ili9341.h"
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include "esp_lcd_gc9a01.h"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "CompactWifiBoardLCD"
class CompactWifiBoardLCD : public WifiBoard {
private:
Button boot_button_;
LcdDisplay* display_;
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(SPI3_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 = 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(SPI3_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;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
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);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
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);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactWifiBoardLCD() :
boot_button_(BOOT_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeTools();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(CompactWifiBoardLCD);
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include "esp_lcd_ili9341.h"
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include "esp_lcd_gc9a01.h"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "CompactWifiBoardLCD"
class CompactWifiBoardLCD : public WifiBoard {
private:
Button boot_button_;
LcdDisplay* display_;
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(SPI3_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 = 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(SPI3_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;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
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);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
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);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactWifiBoardLCD() :
boot_button_(BOOT_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeTools();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
};
DECLARE_BOARD(CompactWifiBoardLCD);

View File

@@ -1,289 +1,289 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#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_42
#define DISPLAY_MOSI_PIN GPIO_NUM_47
#define DISPLAY_CLK_PIN GPIO_NUM_21
#define DISPLAY_DC_PIN GPIO_NUM_40
#define DISPLAY_RST_PIN GPIO_NUM_45
#define DISPLAY_CS_PIN GPIO_NUM_41
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 3
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#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
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#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_42
#define DISPLAY_MOSI_PIN GPIO_NUM_47
#define DISPLAY_CLK_PIN GPIO_NUM_21
#define DISPLAY_DC_PIN GPIO_NUM_40
#define DISPLAY_RST_PIN GPIO_NUM_45
#define DISPLAY_CS_PIN GPIO_NUM_41
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 3
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#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
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_

View File

@@ -1,31 +1,31 @@
硬件基于基于ESP32S3CAM开发板代码基于bread-compact-wifi-lcd修改
使用的摄像头是OV2640
注意因为摄像头占用IO较多所以占用了ESP32S3的USB 19 20两个引脚
连线方式参考config.h文件中对引脚的定义
# 编译配置命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type ->面包板新版接线WiFi+ LCD + Camera
```
**编译烧入:**
```bash
idf.py build flash
硬件基于基于ESP32S3CAM开发板代码基于bread-compact-wifi-lcd修改
使用的摄像头是OV2640
注意因为摄像头占用IO较多所以占用了ESP32S3的USB 19 20两个引脚
连线方式参考config.h文件中对引脚的定义
# 编译配置命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子:**
```
Xiaozhi Assistant -> Board Type ->面包板新版接线WiFi+ LCD + Camera
```
**编译烧入:**
```bash
idf.py build flash
```

View File

@@ -1,214 +1,214 @@
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "esp32_camera.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include "esp_lcd_ili9341.h"
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include "esp_lcd_gc9a01.h"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "CompactWifiBoardS3Cam"
class CompactWifiBoardS3Cam : public WifiBoard {
private:
Button boot_button_;
LcdDisplay* display_;
Esp32Camera* camera_;
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(SPI3_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 = 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(SPI3_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;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
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);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
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);
}
void InitializeCamera() {
camera_config_t config = {};
config.pin_d0 = CAMERA_PIN_D0;
config.pin_d1 = CAMERA_PIN_D1;
config.pin_d2 = CAMERA_PIN_D2;
config.pin_d3 = CAMERA_PIN_D3;
config.pin_d4 = CAMERA_PIN_D4;
config.pin_d5 = CAMERA_PIN_D5;
config.pin_d6 = CAMERA_PIN_D6;
config.pin_d7 = CAMERA_PIN_D7;
config.pin_xclk = CAMERA_PIN_XCLK;
config.pin_pclk = CAMERA_PIN_PCLK;
config.pin_vsync = CAMERA_PIN_VSYNC;
config.pin_href = CAMERA_PIN_HREF;
config.pin_sccb_sda = CAMERA_PIN_SIOD;
config.pin_sccb_scl = CAMERA_PIN_SIOC;
config.sccb_i2c_port = 0;
config.pin_pwdn = CAMERA_PIN_PWDN;
config.pin_reset = CAMERA_PIN_RESET;
config.xclk_freq_hz = XCLK_FREQ_HZ;
config.pixel_format = PIXFORMAT_RGB565;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
camera_ = new Esp32Camera(config);
camera_->SetHMirror(false);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
CompactWifiBoardS3Cam() :
boot_button_(BOOT_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeCamera();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
virtual Camera* GetCamera() override {
return camera_;
}
};
DECLARE_BOARD(CompactWifiBoardS3Cam);
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "esp32_camera.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <driver/spi_common.h>
#if defined(LCD_TYPE_ILI9341_SERIAL)
#include "esp_lcd_ili9341.h"
#endif
#if defined(LCD_TYPE_GC9A01_SERIAL)
#include "esp_lcd_gc9a01.h"
static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
// {cmd, { data }, data_size, delay_ms}
{0xfe, (uint8_t[]){0x00}, 0, 0},
{0xef, (uint8_t[]){0x00}, 0, 0},
{0xb0, (uint8_t[]){0xc0}, 1, 0},
{0xb1, (uint8_t[]){0x80}, 1, 0},
{0xb2, (uint8_t[]){0x27}, 1, 0},
{0xb3, (uint8_t[]){0x13}, 1, 0},
{0xb6, (uint8_t[]){0x19}, 1, 0},
{0xb7, (uint8_t[]){0x05}, 1, 0},
{0xac, (uint8_t[]){0xc8}, 1, 0},
{0xab, (uint8_t[]){0x0f}, 1, 0},
{0x3a, (uint8_t[]){0x05}, 1, 0},
{0xb4, (uint8_t[]){0x04}, 1, 0},
{0xa8, (uint8_t[]){0x08}, 1, 0},
{0xb8, (uint8_t[]){0x08}, 1, 0},
{0xea, (uint8_t[]){0x02}, 1, 0},
{0xe8, (uint8_t[]){0x2A}, 1, 0},
{0xe9, (uint8_t[]){0x47}, 1, 0},
{0xe7, (uint8_t[]){0x5f}, 1, 0},
{0xc6, (uint8_t[]){0x21}, 1, 0},
{0xc7, (uint8_t[]){0x15}, 1, 0},
{0xf0,
(uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
0x04, 0x12, 0x14, 0x1f},
14, 0},
{0xf1,
(uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
0x0C, 0x1A, 0x14, 0x1E},
14, 0},
{0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
{0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
};
#endif
#define TAG "CompactWifiBoardS3Cam"
class CompactWifiBoardS3Cam : public WifiBoard {
private:
Button boot_button_;
LcdDisplay* display_;
Esp32Camera* camera_;
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(SPI3_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 = 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(SPI3_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;
#if defined(LCD_TYPE_ILI9341_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#elif defined(LCD_TYPE_GC9A01_SERIAL)
ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
gc9a01_vendor_config_t gc9107_vendor_config = {
.init_cmds = gc9107_lcd_init_cmds,
.init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
};
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
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);
#ifdef LCD_TYPE_GC9A01_SERIAL
panel_config.vendor_config = &gc9107_vendor_config;
#endif
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);
}
void InitializeCamera() {
camera_config_t config = {};
config.pin_d0 = CAMERA_PIN_D0;
config.pin_d1 = CAMERA_PIN_D1;
config.pin_d2 = CAMERA_PIN_D2;
config.pin_d3 = CAMERA_PIN_D3;
config.pin_d4 = CAMERA_PIN_D4;
config.pin_d5 = CAMERA_PIN_D5;
config.pin_d6 = CAMERA_PIN_D6;
config.pin_d7 = CAMERA_PIN_D7;
config.pin_xclk = CAMERA_PIN_XCLK;
config.pin_pclk = CAMERA_PIN_PCLK;
config.pin_vsync = CAMERA_PIN_VSYNC;
config.pin_href = CAMERA_PIN_HREF;
config.pin_sccb_sda = CAMERA_PIN_SIOD;
config.pin_sccb_scl = CAMERA_PIN_SIOC;
config.sccb_i2c_port = 0;
config.pin_pwdn = CAMERA_PIN_PWDN;
config.pin_reset = CAMERA_PIN_RESET;
config.xclk_freq_hz = XCLK_FREQ_HZ;
config.pixel_format = PIXFORMAT_RGB565;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
camera_ = new Esp32Camera(config);
camera_->SetHMirror(false);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
CompactWifiBoardS3Cam() :
boot_button_(BOOT_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeCamera();
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
GetBacklight()->RestoreBrightness();
}
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
return nullptr;
}
virtual Camera* GetCamera() override {
return camera_;
}
};
DECLARE_BOARD(CompactWifiBoardS3Cam);

View File

@@ -1,308 +1,308 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_1
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_2
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_39
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_41
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#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
//Camera Config
#define CAMERA_PIN_D0 GPIO_NUM_11
#define CAMERA_PIN_D1 GPIO_NUM_9
#define CAMERA_PIN_D2 GPIO_NUM_8
#define CAMERA_PIN_D3 GPIO_NUM_10
#define CAMERA_PIN_D4 GPIO_NUM_12
#define CAMERA_PIN_D5 GPIO_NUM_18
#define CAMERA_PIN_D6 GPIO_NUM_17
#define CAMERA_PIN_D7 GPIO_NUM_16
#define CAMERA_PIN_XCLK GPIO_NUM_15
#define CAMERA_PIN_PCLK GPIO_NUM_13
#define CAMERA_PIN_VSYNC GPIO_NUM_6
#define CAMERA_PIN_HREF GPIO_NUM_7
#define CAMERA_PIN_SIOC GPIO_NUM_5
#define CAMERA_PIN_SIOD GPIO_NUM_4
#define CAMERA_PIN_PWDN GPIO_NUM_NC
#define CAMERA_PIN_RESET GPIO_NUM_NC
#define XCLK_FREQ_HZ 20000000
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38
#define DISPLAY_MOSI_PIN GPIO_NUM_20
#define DISPLAY_CLK_PIN GPIO_NUM_19
#define DISPLAY_DC_PIN GPIO_NUM_47
#define DISPLAY_RST_PIN GPIO_NUM_21
#define DISPLAY_CS_PIN GPIO_NUM_45
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 3
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#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
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_14
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_1
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_2
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_39
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_41
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#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
//Camera Config
#define CAMERA_PIN_D0 GPIO_NUM_11
#define CAMERA_PIN_D1 GPIO_NUM_9
#define CAMERA_PIN_D2 GPIO_NUM_8
#define CAMERA_PIN_D3 GPIO_NUM_10
#define CAMERA_PIN_D4 GPIO_NUM_12
#define CAMERA_PIN_D5 GPIO_NUM_18
#define CAMERA_PIN_D6 GPIO_NUM_17
#define CAMERA_PIN_D7 GPIO_NUM_16
#define CAMERA_PIN_XCLK GPIO_NUM_15
#define CAMERA_PIN_PCLK GPIO_NUM_13
#define CAMERA_PIN_VSYNC GPIO_NUM_6
#define CAMERA_PIN_HREF GPIO_NUM_7
#define CAMERA_PIN_SIOC GPIO_NUM_5
#define CAMERA_PIN_SIOD GPIO_NUM_4
#define CAMERA_PIN_PWDN GPIO_NUM_NC
#define CAMERA_PIN_RESET GPIO_NUM_NC
#define XCLK_FREQ_HZ 20000000
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38
#define DISPLAY_MOSI_PIN GPIO_NUM_20
#define DISPLAY_CLK_PIN GPIO_NUM_19
#define DISPLAY_DC_PIN GPIO_NUM_47
#define DISPLAY_RST_PIN GPIO_NUM_21
#define DISPLAY_CS_PIN GPIO_NUM_45
#ifdef CONFIG_LCD_ST7789_240X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_170X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 170
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 35
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_172X320
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 172
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 34
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X280
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 280
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 20
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7789_240X240_7PIN
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 3
#endif
#ifdef CONFIG_LCD_ST7789_240X135
#define LCD_TYPE_ST7789_SERIAL
#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
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X160
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7735_128X128
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 128
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 32
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 480
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
#define LCD_TYPE_ILI9341_SERIAL
#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_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_GC9A01_240X240
#define LCD_TYPE_GC9A01_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
#ifdef CONFIG_LCD_CUSTOM
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_SPI_MODE 0
#endif
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_14
#endif // _BOARD_CONFIG_H_

View File

@@ -1,188 +1,188 @@
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/oled_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#ifdef SH1106
#include <esp_lcd_panel_sh1106.h>
#endif
#define TAG "CompactWifiBoard"
class CompactWifiBoard : public WifiBoard {
private:
i2c_master_bus_handle_t display_i2c_bus_;
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
Display* display_ = nullptr;
Button boot_button_;
Button touch_button_;
Button volume_up_button_;
Button volume_down_button_;
void InitializeDisplayI2c() {
i2c_master_bus_config_t bus_config = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = DISPLAY_SDA_PIN,
.scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
}
void InitializeSsd1306Display() {
// SSD1306 config
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = 0x3C,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
ESP_LOGI(TAG, "Install SSD1306 driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = -1;
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
};
panel_config.vendor_config = &ssd1306_config;
#ifdef SH1106
ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(panel_io_, &panel_config, &panel_));
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
#endif
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
if (esp_lcd_panel_init(panel_) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize display");
display_ = new NoDisplay();
return;
}
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false));
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
touch_button_.OnPressDown([this]() {
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
Application::GetInstance().StopListening();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
}
// 物联网初始化,逐步迁移到 MCP 协议
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactWifiBoard() :
boot_button_(BOOT_BUTTON_GPIO),
touch_button_(TOUCH_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
InitializeDisplayI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializeTools();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(CompactWifiBoard);
#include "wifi_board.h"
#include "codecs/no_audio_codec.h"
#include "display/oled_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#ifdef SH1106
#include <esp_lcd_panel_sh1106.h>
#endif
#define TAG "CompactWifiBoard"
class CompactWifiBoard : public WifiBoard {
private:
i2c_master_bus_handle_t display_i2c_bus_;
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
Display* display_ = nullptr;
Button boot_button_;
Button touch_button_;
Button volume_up_button_;
Button volume_down_button_;
void InitializeDisplayI2c() {
i2c_master_bus_config_t bus_config = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = DISPLAY_SDA_PIN,
.scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
}
void InitializeSsd1306Display() {
// SSD1306 config
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = 0x3C,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.flags = {
.dc_low_on_data = 0,
.disable_control_phase = 0,
},
.scl_speed_hz = 400 * 1000,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
ESP_LOGI(TAG, "Install SSD1306 driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = -1;
panel_config.bits_per_pixel = 1;
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
};
panel_config.vendor_config = &ssd1306_config;
#ifdef SH1106
ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(panel_io_, &panel_config, &panel_));
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
#endif
ESP_LOGI(TAG, "SSD1306 driver installed");
// Reset the display
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
if (esp_lcd_panel_init(panel_) != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize display");
display_ = new NoDisplay();
return;
}
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false));
// Set the display to on
ESP_LOGI(TAG, "Turning display on");
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
touch_button_.OnPressDown([this]() {
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([this]() {
Application::GetInstance().StopListening();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
}
// 物联网初始化,逐步迁移到 MCP 协议
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactWifiBoard() :
boot_button_(BOOT_BUTTON_GPIO),
touch_button_(TOUCH_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
InitializeDisplayI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializeTools();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(CompactWifiBoard);

View File

@@ -1,59 +1,59 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_47
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
#define DISPLAY_SDA_PIN GPIO_NUM_41
#define DISPLAY_SCL_PIN GPIO_NUM_42
#define DISPLAY_WIDTH 128
#if CONFIG_OLED_SSD1306_128X32
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#elif CONFIG_OLED_SH1106_128X64
#define DISPLAY_HEIGHT 64
#define SH1106
#else
#error "未选择 OLED 屏幕类型"
#endif
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_47
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
#define DISPLAY_SDA_PIN GPIO_NUM_41
#define DISPLAY_SCL_PIN GPIO_NUM_42
#define DISPLAY_WIDTH 128
#if CONFIG_OLED_SSD1306_128X32
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#elif CONFIG_OLED_SH1106_128X64
#define DISPLAY_HEIGHT 64
#define SH1106
#else
#error "未选择 OLED 屏幕类型"
#endif
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_

View File

@@ -1,17 +1,17 @@
{
"target": "esp32s3",
"builds": [
{
"name": "bread-compact-wifi",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X32=y"
]
},
{
"name": "bread-compact-wifi-128x64",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X64=y"
]
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "bread-compact-wifi",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X32=y"
]
},
{
"name": "bread-compact-wifi-128x64",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X64=y"
]
}
]
}

View File

@@ -1,81 +1,81 @@
#include "adc_battery_monitor.h"
AdcBatteryMonitor::AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin)
: charging_pin_(charging_pin) {
// Initialize charging pin
gpio_config_t gpio_cfg = {
.pin_bit_mask = 1ULL << charging_pin,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
// Initialize ADC battery estimation
adc_battery_estimation_t adc_cfg = {
.internal = {
.adc_unit = adc_unit,
.adc_bitwidth = ADC_BITWIDTH_12,
.adc_atten = ADC_ATTEN_DB_12,
},
.adc_channel = adc_channel,
.upper_resistor = upper_resistor,
.lower_resistor = lower_resistor
};
adc_cfg.charging_detect_cb = [](void *user_data) -> bool {
AdcBatteryMonitor *self = (AdcBatteryMonitor *)user_data;
return gpio_get_level(self->charging_pin_) == 1;
};
adc_cfg.charging_detect_user_data = this;
adc_battery_estimation_handle_ = adc_battery_estimation_create(&adc_cfg);
// Initialize timer
esp_timer_create_args_t timer_cfg = {
.callback = [](void *arg) {
AdcBatteryMonitor *self = (AdcBatteryMonitor *)arg;
self->CheckBatteryStatus();
},
.arg = this,
.name = "adc_battery_monitor",
};
ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
}
AdcBatteryMonitor::~AdcBatteryMonitor() {
if (adc_battery_estimation_handle_) {
ESP_ERROR_CHECK(adc_battery_estimation_destroy(adc_battery_estimation_handle_));
}
}
bool AdcBatteryMonitor::IsCharging() {
bool is_charging = false;
ESP_ERROR_CHECK(adc_battery_estimation_get_charging_state(adc_battery_estimation_handle_, &is_charging));
return is_charging;
}
bool AdcBatteryMonitor::IsDischarging() {
return !IsCharging();
}
uint8_t AdcBatteryMonitor::GetBatteryLevel() {
float capacity = 0;
ESP_ERROR_CHECK(adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity));
return capacity;
}
void AdcBatteryMonitor::OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
void AdcBatteryMonitor::CheckBatteryStatus() {
bool new_charging_status = IsCharging();
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (on_charging_status_changed_) {
on_charging_status_changed_(is_charging_);
}
}
#include "adc_battery_monitor.h"
AdcBatteryMonitor::AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin)
: charging_pin_(charging_pin) {
// Initialize charging pin
gpio_config_t gpio_cfg = {
.pin_bit_mask = 1ULL << charging_pin,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
// Initialize ADC battery estimation
adc_battery_estimation_t adc_cfg = {
.internal = {
.adc_unit = adc_unit,
.adc_bitwidth = ADC_BITWIDTH_12,
.adc_atten = ADC_ATTEN_DB_12,
},
.adc_channel = adc_channel,
.upper_resistor = upper_resistor,
.lower_resistor = lower_resistor
};
adc_cfg.charging_detect_cb = [](void *user_data) -> bool {
AdcBatteryMonitor *self = (AdcBatteryMonitor *)user_data;
return gpio_get_level(self->charging_pin_) == 1;
};
adc_cfg.charging_detect_user_data = this;
adc_battery_estimation_handle_ = adc_battery_estimation_create(&adc_cfg);
// Initialize timer
esp_timer_create_args_t timer_cfg = {
.callback = [](void *arg) {
AdcBatteryMonitor *self = (AdcBatteryMonitor *)arg;
self->CheckBatteryStatus();
},
.arg = this,
.name = "adc_battery_monitor",
};
ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
}
AdcBatteryMonitor::~AdcBatteryMonitor() {
if (adc_battery_estimation_handle_) {
ESP_ERROR_CHECK(adc_battery_estimation_destroy(adc_battery_estimation_handle_));
}
}
bool AdcBatteryMonitor::IsCharging() {
bool is_charging = false;
ESP_ERROR_CHECK(adc_battery_estimation_get_charging_state(adc_battery_estimation_handle_, &is_charging));
return is_charging;
}
bool AdcBatteryMonitor::IsDischarging() {
return !IsCharging();
}
uint8_t AdcBatteryMonitor::GetBatteryLevel() {
float capacity = 0;
ESP_ERROR_CHECK(adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity));
return capacity;
}
void AdcBatteryMonitor::OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
void AdcBatteryMonitor::CheckBatteryStatus() {
bool new_charging_status = IsCharging();
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (on_charging_status_changed_) {
on_charging_status_changed_(is_charging_);
}
}
}

View File

@@ -1,30 +1,30 @@
#ifndef ADC_BATTERY_MONITOR_H
#define ADC_BATTERY_MONITOR_H
#include <functional>
#include <driver/gpio.h>
#include <adc_battery_estimation.h>
#include <esp_timer.h>
class AdcBatteryMonitor {
public:
AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin = GPIO_NUM_NC);
~AdcBatteryMonitor();
bool IsCharging();
bool IsDischarging();
uint8_t GetBatteryLevel();
void OnChargingStatusChanged(std::function<void(bool)> callback);
private:
gpio_num_t charging_pin_;
adc_battery_estimation_handle_t adc_battery_estimation_handle_ = nullptr;
esp_timer_handle_t timer_handle_ = nullptr;
bool is_charging_ = false;
std::function<void(bool)> on_charging_status_changed_;
void CheckBatteryStatus();
};
#endif // ADC_BATTERY_MONITOR_H
#ifndef ADC_BATTERY_MONITOR_H
#define ADC_BATTERY_MONITOR_H
#include <functional>
#include <driver/gpio.h>
#include <adc_battery_estimation.h>
#include <esp_timer.h>
class AdcBatteryMonitor {
public:
AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin = GPIO_NUM_NC);
~AdcBatteryMonitor();
bool IsCharging();
bool IsDischarging();
uint8_t GetBatteryLevel();
void OnChargingStatusChanged(std::function<void(bool)> callback);
private:
gpio_num_t charging_pin_;
adc_battery_estimation_handle_t adc_battery_estimation_handle_ = nullptr;
esp_timer_handle_t timer_handle_ = nullptr;
bool is_charging_ = false;
std::function<void(bool)> on_charging_status_changed_;
void CheckBatteryStatus();
};
#endif // ADC_BATTERY_MONITOR_H

View File

@@ -1,371 +1,371 @@
#include "afsk_demod.h"
#include <cstring>
#include <algorithm>
#include "esp_log.h"
#include "display.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace audio_wifi_config
{
static const char *kLogTag = "AUDIO_WIFI_CONFIG";
void ReceiveWifiCredentialsFromAudio(Application *app,
WifiConfigurationAp *wifi_ap,
Display *display,
size_t input_channels
)
{
const int kInputSampleRate = 16000; // Input sampling rate
const float kDownsampleStep = static_cast<float>(kInputSampleRate) / static_cast<float>(kAudioSampleRate); // Downsampling step
std::vector<int16_t> audio_data;
AudioSignalProcessor signal_processor(kAudioSampleRate, kMarkFrequency, kSpaceFrequency, kBitRate, kWindowSize);
AudioDataBuffer data_buffer;
while (true)
{
// 检查Application状态只有在WiFi配置模式下才处理音频
if (app->GetDeviceState() != kDeviceStateWifiConfiguring) {
// 不在WiFi配置状态休眠100ms后再检查
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
if (!app->GetAudioService().ReadAudioData(audio_data, 16000, 480)) { // 16kHz, 480 samples corresponds to 30ms data
// 读取音频失败,短暂延迟后重试
ESP_LOGI(kLogTag, "Failed to read audio data, retrying.");
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
if (input_channels == 2) { // 如果是双声道输入,转换为单声道
auto mono_data = std::vector<int16_t>(audio_data.size() / 2);
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
mono_data[i] = audio_data[j];
}
audio_data = std::move(mono_data);
}
// Downsample the audio data
std::vector<float> downsampled_data;
size_t last_index = 0;
if (kDownsampleStep > 1.0f) {
downsampled_data.reserve(audio_data.size() / static_cast<size_t>(kDownsampleStep));
for (size_t i = 0; i < audio_data.size(); ++i) {
size_t sample_index = static_cast<size_t>(i / kDownsampleStep);
if ((sample_index + 1) > last_index) {
downsampled_data.push_back(static_cast<float>(audio_data[i]));
last_index = sample_index + 1;
}
}
} else {
downsampled_data.reserve(audio_data.size());
for (int16_t sample : audio_data) {
downsampled_data.push_back(static_cast<float>(sample));
}
}
// Process audio samples to get probability data
auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data);
// Feed probability data to the data buffer
if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) {
// If complete data was received, extract WiFi credentials
if (data_buffer.decoded_text.has_value()) {
ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str());
display->SetChatMessage("system", data_buffer.decoded_text->c_str());
// Split SSID and password by newline character
std::string wifi_ssid, wifi_password;
size_t newline_position = data_buffer.decoded_text->find('\n');
if (newline_position != std::string::npos) {
wifi_ssid = data_buffer.decoded_text->substr(0, newline_position);
wifi_password = data_buffer.decoded_text->substr(newline_position + 1);
ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str());
} else {
ESP_LOGE(kLogTag, "Invalid data format, no newline character found");
continue;
}
if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) {
wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials
esp_restart(); // Restart device to apply new WiFi configuration
} else {
ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials");
}
data_buffer.decoded_text.reset(); // Clear processed data
}
}
vTaskDelay(pdMS_TO_TICKS(1)); // 1ms delay
}
}
// Default start and end transmission identifiers
// \x01\x02 = 00000001 00000010
const std::vector<uint8_t> kDefaultStartTransmissionPattern = {
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0};
// \x03\x04 = 00000011 00000100
const std::vector<uint8_t> kDefaultEndTransmissionPattern = {
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0};
// FrequencyDetector implementation
FrequencyDetector::FrequencyDetector(float frequency, size_t window_size)
: frequency_(frequency), window_size_(window_size) {
frequency_bin_ = std::floor(frequency_ * static_cast<float>(window_size_));
angular_frequency_ = 2.0f * M_PI * frequency_;
cos_coefficient_ = std::cos(angular_frequency_);
sin_coefficient_ = std::sin(angular_frequency_);
filter_coefficient_ = 2.0f * cos_coefficient_;
// Initialize state buffer
state_buffer_.push_back(0.0f);
state_buffer_.push_back(0.0f);
}
void FrequencyDetector::Reset() {
state_buffer_.clear();
state_buffer_.push_back(0.0f);
state_buffer_.push_back(0.0f);
}
void FrequencyDetector::ProcessSample(float sample) {
if (state_buffer_.size() < 2) {
return;
}
float s_minus_2 = state_buffer_.front(); // S[-2]
state_buffer_.pop_front();
float s_minus_1 = state_buffer_.front(); // S[-1]
state_buffer_.pop_front();
float s_current = sample + filter_coefficient_ * s_minus_1 - s_minus_2;
state_buffer_.push_back(s_minus_1); // Put S[-1] back
state_buffer_.push_back(s_current); // Add new S[0]
}
float FrequencyDetector::GetAmplitude() const {
if (state_buffer_.size() < 2) {
return 0.0f;
}
float s_minus_1 = state_buffer_[1]; // S[-1]
float s_minus_2 = state_buffer_[0]; // S[-2]
float real_part = cos_coefficient_ * s_minus_1 - s_minus_2; // Real part
float imaginary_part = sin_coefficient_ * s_minus_1; // Imaginary part
return std::sqrt(real_part * real_part + imaginary_part * imaginary_part) /
(static_cast<float>(window_size_) / 2.0f);
}
// AudioSignalProcessor implementation
AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
size_t bit_rate, size_t window_size)
: input_buffer_size_(window_size), output_sample_count_(0) {
if (sample_rate % bit_rate != 0) {
// On ESP32 we can continue execution, but log the error
ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate);
}
float normalized_mark_freq = static_cast<float>(mark_frequency) / static_cast<float>(sample_rate);
float normalized_space_freq = static_cast<float>(space_frequency) / static_cast<float>(sample_rate);
mark_detector_ = std::make_unique<FrequencyDetector>(normalized_mark_freq, window_size);
space_detector_ = std::make_unique<FrequencyDetector>(normalized_space_freq, window_size);
samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit
}
std::vector<float> AudioSignalProcessor::ProcessAudioSamples(const std::vector<float> &samples) {
std::vector<float> result;
for (float sample : samples) {
if (input_buffer_.size() < input_buffer_size_) {
input_buffer_.push_back(sample); // Just add, don't process yet
} else {
// Input buffer is full, process the data
input_buffer_.pop_front(); // Remove oldest sample
input_buffer_.push_back(sample); // Add new sample
output_sample_count_++;
if (output_sample_count_ >= samples_per_bit_) {
// Process all samples in the window using Goertzel algorithm
for (float window_sample : input_buffer_) {
mark_detector_->ProcessSample(window_sample);
space_detector_->ProcessSample(window_sample);
}
float mark_amplitude = mark_detector_->GetAmplitude(); // Mark amplitude
float space_amplitude = space_detector_->GetAmplitude(); // Space amplitude
// Avoid division by zero
float mark_probability = mark_amplitude /
(space_amplitude + mark_amplitude + std::numeric_limits<float>::epsilon());
result.push_back(mark_probability);
// Reset detector windows
mark_detector_->Reset();
space_detector_->Reset();
output_sample_count_ = 0; // Reset output counter
}
}
}
return result;
}
// AudioDataBuffer implementation
AudioDataBuffer::AudioDataBuffer()
: current_state_(DataReceptionState::kInactive),
start_of_transmission_(kDefaultStartTransmissionPattern),
end_of_transmission_(kDefaultEndTransmissionPattern),
enable_checksum_validation_(true) {
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776
bit_buffer_.reserve(max_bit_buffer_size_);
}
AudioDataBuffer::AudioDataBuffer(size_t max_byte_size, const std::vector<uint8_t> &start_identifier,
const std::vector<uint8_t> &end_identifier, bool enable_checksum)
: current_state_(DataReceptionState::kInactive),
start_of_transmission_(start_identifier),
end_of_transmission_(end_identifier),
enable_checksum_validation_(enable_checksum) {
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes
bit_buffer_.reserve(max_bit_buffer_size_);
}
uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) {
uint8_t checksum = 0;
for (char character : text) {
checksum += static_cast<uint8_t>(character);
}
return checksum;
}
void AudioDataBuffer::ClearBuffers() {
identifier_buffer_.clear();
bit_buffer_.clear();
}
bool AudioDataBuffer::ProcessProbabilityData(const std::vector<float> &probabilities, float threshold) {
for (float probability : probabilities) {
uint8_t bit = (probability > threshold) ? 1 : 0;
if (identifier_buffer_.size() >= identifier_buffer_size_) {
identifier_buffer_.pop_front(); // Maintain buffer size
}
identifier_buffer_.push_back(bit);
// Process received bit based on state machine
switch (current_state_) {
case DataReceptionState::kInactive:
if (identifier_buffer_.size() >= start_of_transmission_.size()) {
current_state_ = DataReceptionState::kWaiting; // Enter waiting state
ESP_LOGI(kLogTag, "Entering Waiting state");
}
break;
case DataReceptionState::kWaiting:
// Waiting state, possibly waiting for transmission end
if (identifier_buffer_.size() >= start_of_transmission_.size()) {
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == start_of_transmission_)
{
ClearBuffers(); // Clear buffers
current_state_ = DataReceptionState::kReceiving; // Enter receiving state
ESP_LOGI(kLogTag, "Entering Receiving state");
}
}
break;
case DataReceptionState::kReceiving:
bit_buffer_.push_back(bit);
if (identifier_buffer_.size() >= end_of_transmission_.size()) {
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == end_of_transmission_) {
current_state_ = DataReceptionState::kInactive; // Enter inactive state
// Convert bits to bytes
std::vector<uint8_t> bytes = ConvertBitsToBytes(bit_buffer_);
uint8_t received_checksum = 0;
size_t minimum_length = 0;
if (enable_checksum_validation_) {
// If checksum is required, last byte is checksum
minimum_length = 1 + start_of_transmission_.size() / 8;
if (bytes.size() >= minimum_length)
{
received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1];
}
} else {
minimum_length = start_of_transmission_.size() / 8;
}
if (bytes.size() < minimum_length) {
ClearBuffers();
ESP_LOGW(kLogTag, "Data too short, clearing buffer");
return false; // Data too short, return failure
}
// Extract text data (remove trailing identifier part)
std::vector<uint8_t> text_bytes(
bytes.begin(), bytes.begin() + bytes.size() - minimum_length);
std::string result(text_bytes.begin(), text_bytes.end());
// Validate checksum if required
if (enable_checksum_validation_) {
uint8_t calculated_checksum = CalculateChecksum(result);
if (calculated_checksum != received_checksum) {
// Checksum mismatch
ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d",
received_checksum, calculated_checksum);
ClearBuffers();
return false;
}
}
ClearBuffers();
decoded_text = result;
return true; // Return success
} else if (bit_buffer_.size() >= max_bit_buffer_size_) {
// If not end identifier and bit buffer is full, reset
ClearBuffers();
ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer");
current_state_ = DataReceptionState::kInactive; // Reset state machine
}
}
break;
}
}
return false;
}
std::vector<uint8_t> AudioDataBuffer::ConvertBitsToBytes(const std::vector<uint8_t> &bits) const {
std::vector<uint8_t> bytes;
// Ensure number of bits is a multiple of 8
size_t complete_bytes_count = bits.size() / 8;
bytes.reserve(complete_bytes_count);
for (size_t i = 0; i < complete_bytes_count; ++i) {
uint8_t byte_value = 0;
for (size_t j = 0; j < 8; ++j) {
byte_value |= bits[i * 8 + j] << (7 - j);
}
bytes.push_back(byte_value);
}
return bytes;
}
}
#include "afsk_demod.h"
#include <cstring>
#include <algorithm>
#include "esp_log.h"
#include "display.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
namespace audio_wifi_config
{
static const char *kLogTag = "AUDIO_WIFI_CONFIG";
void ReceiveWifiCredentialsFromAudio(Application *app,
WifiConfigurationAp *wifi_ap,
Display *display,
size_t input_channels
)
{
const int kInputSampleRate = 16000; // Input sampling rate
const float kDownsampleStep = static_cast<float>(kInputSampleRate) / static_cast<float>(kAudioSampleRate); // Downsampling step
std::vector<int16_t> audio_data;
AudioSignalProcessor signal_processor(kAudioSampleRate, kMarkFrequency, kSpaceFrequency, kBitRate, kWindowSize);
AudioDataBuffer data_buffer;
while (true)
{
// 检查Application状态只有在WiFi配置模式下才处理音频
if (app->GetDeviceState() != kDeviceStateWifiConfiguring) {
// 不在WiFi配置状态休眠100ms后再检查
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
if (!app->GetAudioService().ReadAudioData(audio_data, 16000, 480)) { // 16kHz, 480 samples corresponds to 30ms data
// 读取音频失败,短暂延迟后重试
ESP_LOGI(kLogTag, "Failed to read audio data, retrying.");
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
if (input_channels == 2) { // 如果是双声道输入,转换为单声道
auto mono_data = std::vector<int16_t>(audio_data.size() / 2);
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
mono_data[i] = audio_data[j];
}
audio_data = std::move(mono_data);
}
// Downsample the audio data
std::vector<float> downsampled_data;
size_t last_index = 0;
if (kDownsampleStep > 1.0f) {
downsampled_data.reserve(audio_data.size() / static_cast<size_t>(kDownsampleStep));
for (size_t i = 0; i < audio_data.size(); ++i) {
size_t sample_index = static_cast<size_t>(i / kDownsampleStep);
if ((sample_index + 1) > last_index) {
downsampled_data.push_back(static_cast<float>(audio_data[i]));
last_index = sample_index + 1;
}
}
} else {
downsampled_data.reserve(audio_data.size());
for (int16_t sample : audio_data) {
downsampled_data.push_back(static_cast<float>(sample));
}
}
// Process audio samples to get probability data
auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data);
// Feed probability data to the data buffer
if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) {
// If complete data was received, extract WiFi credentials
if (data_buffer.decoded_text.has_value()) {
ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str());
display->SetChatMessage("system", data_buffer.decoded_text->c_str());
// Split SSID and password by newline character
std::string wifi_ssid, wifi_password;
size_t newline_position = data_buffer.decoded_text->find('\n');
if (newline_position != std::string::npos) {
wifi_ssid = data_buffer.decoded_text->substr(0, newline_position);
wifi_password = data_buffer.decoded_text->substr(newline_position + 1);
ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str());
} else {
ESP_LOGE(kLogTag, "Invalid data format, no newline character found");
continue;
}
if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) {
wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials
esp_restart(); // Restart device to apply new WiFi configuration
} else {
ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials");
}
data_buffer.decoded_text.reset(); // Clear processed data
}
}
vTaskDelay(pdMS_TO_TICKS(1)); // 1ms delay
}
}
// Default start and end transmission identifiers
// \x01\x02 = 00000001 00000010
const std::vector<uint8_t> kDefaultStartTransmissionPattern = {
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0};
// \x03\x04 = 00000011 00000100
const std::vector<uint8_t> kDefaultEndTransmissionPattern = {
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0};
// FrequencyDetector implementation
FrequencyDetector::FrequencyDetector(float frequency, size_t window_size)
: frequency_(frequency), window_size_(window_size) {
frequency_bin_ = std::floor(frequency_ * static_cast<float>(window_size_));
angular_frequency_ = 2.0f * M_PI * frequency_;
cos_coefficient_ = std::cos(angular_frequency_);
sin_coefficient_ = std::sin(angular_frequency_);
filter_coefficient_ = 2.0f * cos_coefficient_;
// Initialize state buffer
state_buffer_.push_back(0.0f);
state_buffer_.push_back(0.0f);
}
void FrequencyDetector::Reset() {
state_buffer_.clear();
state_buffer_.push_back(0.0f);
state_buffer_.push_back(0.0f);
}
void FrequencyDetector::ProcessSample(float sample) {
if (state_buffer_.size() < 2) {
return;
}
float s_minus_2 = state_buffer_.front(); // S[-2]
state_buffer_.pop_front();
float s_minus_1 = state_buffer_.front(); // S[-1]
state_buffer_.pop_front();
float s_current = sample + filter_coefficient_ * s_minus_1 - s_minus_2;
state_buffer_.push_back(s_minus_1); // Put S[-1] back
state_buffer_.push_back(s_current); // Add new S[0]
}
float FrequencyDetector::GetAmplitude() const {
if (state_buffer_.size() < 2) {
return 0.0f;
}
float s_minus_1 = state_buffer_[1]; // S[-1]
float s_minus_2 = state_buffer_[0]; // S[-2]
float real_part = cos_coefficient_ * s_minus_1 - s_minus_2; // Real part
float imaginary_part = sin_coefficient_ * s_minus_1; // Imaginary part
return std::sqrt(real_part * real_part + imaginary_part * imaginary_part) /
(static_cast<float>(window_size_) / 2.0f);
}
// AudioSignalProcessor implementation
AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
size_t bit_rate, size_t window_size)
: input_buffer_size_(window_size), output_sample_count_(0) {
if (sample_rate % bit_rate != 0) {
// On ESP32 we can continue execution, but log the error
ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate);
}
float normalized_mark_freq = static_cast<float>(mark_frequency) / static_cast<float>(sample_rate);
float normalized_space_freq = static_cast<float>(space_frequency) / static_cast<float>(sample_rate);
mark_detector_ = std::make_unique<FrequencyDetector>(normalized_mark_freq, window_size);
space_detector_ = std::make_unique<FrequencyDetector>(normalized_space_freq, window_size);
samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit
}
std::vector<float> AudioSignalProcessor::ProcessAudioSamples(const std::vector<float> &samples) {
std::vector<float> result;
for (float sample : samples) {
if (input_buffer_.size() < input_buffer_size_) {
input_buffer_.push_back(sample); // Just add, don't process yet
} else {
// Input buffer is full, process the data
input_buffer_.pop_front(); // Remove oldest sample
input_buffer_.push_back(sample); // Add new sample
output_sample_count_++;
if (output_sample_count_ >= samples_per_bit_) {
// Process all samples in the window using Goertzel algorithm
for (float window_sample : input_buffer_) {
mark_detector_->ProcessSample(window_sample);
space_detector_->ProcessSample(window_sample);
}
float mark_amplitude = mark_detector_->GetAmplitude(); // Mark amplitude
float space_amplitude = space_detector_->GetAmplitude(); // Space amplitude
// Avoid division by zero
float mark_probability = mark_amplitude /
(space_amplitude + mark_amplitude + std::numeric_limits<float>::epsilon());
result.push_back(mark_probability);
// Reset detector windows
mark_detector_->Reset();
space_detector_->Reset();
output_sample_count_ = 0; // Reset output counter
}
}
}
return result;
}
// AudioDataBuffer implementation
AudioDataBuffer::AudioDataBuffer()
: current_state_(DataReceptionState::kInactive),
start_of_transmission_(kDefaultStartTransmissionPattern),
end_of_transmission_(kDefaultEndTransmissionPattern),
enable_checksum_validation_(true) {
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776
bit_buffer_.reserve(max_bit_buffer_size_);
}
AudioDataBuffer::AudioDataBuffer(size_t max_byte_size, const std::vector<uint8_t> &start_identifier,
const std::vector<uint8_t> &end_identifier, bool enable_checksum)
: current_state_(DataReceptionState::kInactive),
start_of_transmission_(start_identifier),
end_of_transmission_(end_identifier),
enable_checksum_validation_(enable_checksum) {
identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes
bit_buffer_.reserve(max_bit_buffer_size_);
}
uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) {
uint8_t checksum = 0;
for (char character : text) {
checksum += static_cast<uint8_t>(character);
}
return checksum;
}
void AudioDataBuffer::ClearBuffers() {
identifier_buffer_.clear();
bit_buffer_.clear();
}
bool AudioDataBuffer::ProcessProbabilityData(const std::vector<float> &probabilities, float threshold) {
for (float probability : probabilities) {
uint8_t bit = (probability > threshold) ? 1 : 0;
if (identifier_buffer_.size() >= identifier_buffer_size_) {
identifier_buffer_.pop_front(); // Maintain buffer size
}
identifier_buffer_.push_back(bit);
// Process received bit based on state machine
switch (current_state_) {
case DataReceptionState::kInactive:
if (identifier_buffer_.size() >= start_of_transmission_.size()) {
current_state_ = DataReceptionState::kWaiting; // Enter waiting state
ESP_LOGI(kLogTag, "Entering Waiting state");
}
break;
case DataReceptionState::kWaiting:
// Waiting state, possibly waiting for transmission end
if (identifier_buffer_.size() >= start_of_transmission_.size()) {
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == start_of_transmission_)
{
ClearBuffers(); // Clear buffers
current_state_ = DataReceptionState::kReceiving; // Enter receiving state
ESP_LOGI(kLogTag, "Entering Receiving state");
}
}
break;
case DataReceptionState::kReceiving:
bit_buffer_.push_back(bit);
if (identifier_buffer_.size() >= end_of_transmission_.size()) {
std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
if (identifier_snapshot == end_of_transmission_) {
current_state_ = DataReceptionState::kInactive; // Enter inactive state
// Convert bits to bytes
std::vector<uint8_t> bytes = ConvertBitsToBytes(bit_buffer_);
uint8_t received_checksum = 0;
size_t minimum_length = 0;
if (enable_checksum_validation_) {
// If checksum is required, last byte is checksum
minimum_length = 1 + start_of_transmission_.size() / 8;
if (bytes.size() >= minimum_length)
{
received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1];
}
} else {
minimum_length = start_of_transmission_.size() / 8;
}
if (bytes.size() < minimum_length) {
ClearBuffers();
ESP_LOGW(kLogTag, "Data too short, clearing buffer");
return false; // Data too short, return failure
}
// Extract text data (remove trailing identifier part)
std::vector<uint8_t> text_bytes(
bytes.begin(), bytes.begin() + bytes.size() - minimum_length);
std::string result(text_bytes.begin(), text_bytes.end());
// Validate checksum if required
if (enable_checksum_validation_) {
uint8_t calculated_checksum = CalculateChecksum(result);
if (calculated_checksum != received_checksum) {
// Checksum mismatch
ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d",
received_checksum, calculated_checksum);
ClearBuffers();
return false;
}
}
ClearBuffers();
decoded_text = result;
return true; // Return success
} else if (bit_buffer_.size() >= max_bit_buffer_size_) {
// If not end identifier and bit buffer is full, reset
ClearBuffers();
ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer");
current_state_ = DataReceptionState::kInactive; // Reset state machine
}
}
break;
}
}
return false;
}
std::vector<uint8_t> AudioDataBuffer::ConvertBitsToBytes(const std::vector<uint8_t> &bits) const {
std::vector<uint8_t> bytes;
// Ensure number of bits is a multiple of 8
size_t complete_bytes_count = bits.size() / 8;
bytes.reserve(complete_bytes_count);
for (size_t i = 0; i < complete_bytes_count; ++i) {
uint8_t byte_value = 0;
for (size_t j = 0; j < 8; ++j) {
byte_value |= bits[i * 8 + j] << (7 - j);
}
bytes.push_back(byte_value);
}
return bytes;
}
}

View File

@@ -1,177 +1,177 @@
#pragma once
#include <vector>
#include <deque>
#include <string>
#include <memory>
#include <optional>
#include <cmath>
#include "wifi_configuration_ap.h"
#include "application.h"
// Audio signal processing constants for WiFi configuration via audio
const size_t kAudioSampleRate = 6400;
const size_t kMarkFrequency = 1800;
const size_t kSpaceFrequency = 1500;
const size_t kBitRate = 100;
const size_t kWindowSize = 64;
namespace audio_wifi_config
{
// Main function to receive WiFi credentials through audio signal
void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display,
size_t input_channels = 1);
/**
* Goertzel algorithm implementation for single frequency detection
* Used to detect specific audio frequencies in the AFSK demodulation process
*/
class FrequencyDetector
{
private:
float frequency_; // Target frequency (normalized, i.e., f / fs)
size_t window_size_; // Window size for analysis
float frequency_bin_; // Frequency bin
float angular_frequency_; // Angular frequency
float cos_coefficient_; // cos(w)
float sin_coefficient_; // sin(w)
float filter_coefficient_; // 2 * cos(w)
std::deque<float> state_buffer_; // Circular buffer for storing S[-1] and S[-2]
public:
/**
* Constructor
* @param frequency Normalized frequency (f / fs)
* @param window_size Window size for analysis
*/
FrequencyDetector(float frequency, size_t window_size);
/**
* Reset the detector state
*/
void Reset();
/**
* Process one audio sample
* @param sample Input audio sample
*/
void ProcessSample(float sample);
/**
* Calculate current amplitude
* @return Amplitude value
*/
float GetAmplitude() const;
};
/**
* Audio signal processor for Mark/Space frequency pair detection
* Processes audio signals to extract digital data using AFSK demodulation
*/
class AudioSignalProcessor
{
private:
std::deque<float> input_buffer_; // Input sample buffer
size_t input_buffer_size_; // Input buffer size = window size
size_t output_sample_count_; // Output sample counter
size_t samples_per_bit_; // Samples per bit threshold
std::unique_ptr<FrequencyDetector> mark_detector_; // Mark frequency detector
std::unique_ptr<FrequencyDetector> space_detector_; // Space frequency detector
public:
/**
* Constructor
* @param sample_rate Audio sampling rate
* @param mark_frequency Mark frequency for digital '1'
* @param space_frequency Space frequency for digital '0'
* @param bit_rate Data transmission bit rate
* @param window_size Analysis window size
*/
AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
size_t bit_rate, size_t window_size);
/**
* Process input audio samples
* @param samples Input audio sample vector
* @return Vector of Mark probability values (0.0 to 1.0)
*/
std::vector<float> ProcessAudioSamples(const std::vector<float> &samples);
};
/**
* Data reception state machine states
*/
enum class DataReceptionState
{
kInactive, // Waiting for start signal
kWaiting, // Detected potential start, waiting for confirmation
kReceiving // Actively receiving data
};
/**
* Data buffer for managing audio-to-digital data conversion
* Handles the complete process from audio signal to decoded text data
*/
class AudioDataBuffer
{
private:
DataReceptionState current_state_; // Current reception state
std::deque<uint8_t> identifier_buffer_; // Buffer for start/end identifier detection
size_t identifier_buffer_size_; // Identifier buffer size
std::vector<uint8_t> bit_buffer_; // Buffer for storing bit stream
size_t max_bit_buffer_size_; // Maximum bit buffer size
const std::vector<uint8_t> start_of_transmission_; // Start-of-transmission identifier
const std::vector<uint8_t> end_of_transmission_; // End-of-transmission identifier
bool enable_checksum_validation_; // Whether to validate checksum
public:
std::optional<std::string> decoded_text; // Successfully decoded text data
/**
* Default constructor using predefined start and end identifiers
*/
AudioDataBuffer();
/**
* Constructor with custom parameters
* @param max_byte_size Expected maximum data size in bytes
* @param start_identifier Start-of-transmission identifier
* @param end_identifier End-of-transmission identifier
* @param enable_checksum Whether to enable checksum validation
*/
AudioDataBuffer(size_t max_byte_size, const std::vector<uint8_t> &start_identifier,
const std::vector<uint8_t> &end_identifier, bool enable_checksum = false);
/**
* Process probability data and attempt to decode
* @param probabilities Vector of Mark probabilities
* @param threshold Decision threshold for bit detection
* @return true if complete data was successfully received and decoded
*/
bool ProcessProbabilityData(const std::vector<float> &probabilities, float threshold = 0.5f);
/**
* Calculate checksum for ASCII text
* @param text Input text string
* @return Checksum value (0-255)
*/
static uint8_t CalculateChecksum(const std::string &text);
private:
/**
* Convert bit vector to byte vector
* @param bits Input bit vector
* @return Converted byte vector
*/
std::vector<uint8_t> ConvertBitsToBytes(const std::vector<uint8_t> &bits) const;
/**
* Clear all buffers and reset state
*/
void ClearBuffers();
};
// Default start and end transmission identifiers
extern const std::vector<uint8_t> kDefaultStartTransmissionPattern;
extern const std::vector<uint8_t> kDefaultEndTransmissionPattern;
#pragma once
#include <vector>
#include <deque>
#include <string>
#include <memory>
#include <optional>
#include <cmath>
#include "wifi_configuration_ap.h"
#include "application.h"
// Audio signal processing constants for WiFi configuration via audio
const size_t kAudioSampleRate = 6400;
const size_t kMarkFrequency = 1800;
const size_t kSpaceFrequency = 1500;
const size_t kBitRate = 100;
const size_t kWindowSize = 64;
namespace audio_wifi_config
{
// Main function to receive WiFi credentials through audio signal
void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display,
size_t input_channels = 1);
/**
* Goertzel algorithm implementation for single frequency detection
* Used to detect specific audio frequencies in the AFSK demodulation process
*/
class FrequencyDetector
{
private:
float frequency_; // Target frequency (normalized, i.e., f / fs)
size_t window_size_; // Window size for analysis
float frequency_bin_; // Frequency bin
float angular_frequency_; // Angular frequency
float cos_coefficient_; // cos(w)
float sin_coefficient_; // sin(w)
float filter_coefficient_; // 2 * cos(w)
std::deque<float> state_buffer_; // Circular buffer for storing S[-1] and S[-2]
public:
/**
* Constructor
* @param frequency Normalized frequency (f / fs)
* @param window_size Window size for analysis
*/
FrequencyDetector(float frequency, size_t window_size);
/**
* Reset the detector state
*/
void Reset();
/**
* Process one audio sample
* @param sample Input audio sample
*/
void ProcessSample(float sample);
/**
* Calculate current amplitude
* @return Amplitude value
*/
float GetAmplitude() const;
};
/**
* Audio signal processor for Mark/Space frequency pair detection
* Processes audio signals to extract digital data using AFSK demodulation
*/
class AudioSignalProcessor
{
private:
std::deque<float> input_buffer_; // Input sample buffer
size_t input_buffer_size_; // Input buffer size = window size
size_t output_sample_count_; // Output sample counter
size_t samples_per_bit_; // Samples per bit threshold
std::unique_ptr<FrequencyDetector> mark_detector_; // Mark frequency detector
std::unique_ptr<FrequencyDetector> space_detector_; // Space frequency detector
public:
/**
* Constructor
* @param sample_rate Audio sampling rate
* @param mark_frequency Mark frequency for digital '1'
* @param space_frequency Space frequency for digital '0'
* @param bit_rate Data transmission bit rate
* @param window_size Analysis window size
*/
AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
size_t bit_rate, size_t window_size);
/**
* Process input audio samples
* @param samples Input audio sample vector
* @return Vector of Mark probability values (0.0 to 1.0)
*/
std::vector<float> ProcessAudioSamples(const std::vector<float> &samples);
};
/**
* Data reception state machine states
*/
enum class DataReceptionState
{
kInactive, // Waiting for start signal
kWaiting, // Detected potential start, waiting for confirmation
kReceiving // Actively receiving data
};
/**
* Data buffer for managing audio-to-digital data conversion
* Handles the complete process from audio signal to decoded text data
*/
class AudioDataBuffer
{
private:
DataReceptionState current_state_; // Current reception state
std::deque<uint8_t> identifier_buffer_; // Buffer for start/end identifier detection
size_t identifier_buffer_size_; // Identifier buffer size
std::vector<uint8_t> bit_buffer_; // Buffer for storing bit stream
size_t max_bit_buffer_size_; // Maximum bit buffer size
const std::vector<uint8_t> start_of_transmission_; // Start-of-transmission identifier
const std::vector<uint8_t> end_of_transmission_; // End-of-transmission identifier
bool enable_checksum_validation_; // Whether to validate checksum
public:
std::optional<std::string> decoded_text; // Successfully decoded text data
/**
* Default constructor using predefined start and end identifiers
*/
AudioDataBuffer();
/**
* Constructor with custom parameters
* @param max_byte_size Expected maximum data size in bytes
* @param start_identifier Start-of-transmission identifier
* @param end_identifier End-of-transmission identifier
* @param enable_checksum Whether to enable checksum validation
*/
AudioDataBuffer(size_t max_byte_size, const std::vector<uint8_t> &start_identifier,
const std::vector<uint8_t> &end_identifier, bool enable_checksum = false);
/**
* Process probability data and attempt to decode
* @param probabilities Vector of Mark probabilities
* @param threshold Decision threshold for bit detection
* @return true if complete data was successfully received and decoded
*/
bool ProcessProbabilityData(const std::vector<float> &probabilities, float threshold = 0.5f);
/**
* Calculate checksum for ASCII text
* @param text Input text string
* @return Checksum value (0-255)
*/
static uint8_t CalculateChecksum(const std::string &text);
private:
/**
* Convert bit vector to byte vector
* @param bits Input bit vector
* @return Converted byte vector
*/
std::vector<uint8_t> ConvertBitsToBytes(const std::vector<uint8_t> &bits) const;
/**
* Clear all buffers and reset state
*/
void ClearBuffers();
};
// Default start and end transmission identifiers
extern const std::vector<uint8_t> kDefaultStartTransmissionPattern;
extern const std::vector<uint8_t> kDefaultEndTransmissionPattern;
}

View File

@@ -1,41 +1,41 @@
#include "axp2101.h"
#include "board.h"
#include "display.h"
#include <esp_log.h>
#define TAG "Axp2101"
Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
}
int Axp2101::GetBatteryCurrentDirection() {
return (ReadReg(0x01) & 0b01100000) >> 5;
}
bool Axp2101::IsCharging() {
return GetBatteryCurrentDirection() == 1;
}
bool Axp2101::IsDischarging() {
return GetBatteryCurrentDirection() == 2;
}
bool Axp2101::IsChargingDone() {
uint8_t value = ReadReg(0x01);
return (value & 0b00000111) == 0b00000100;
}
int Axp2101::GetBatteryLevel() {
return ReadReg(0xA4);
}
float Axp2101::GetTemperature() {
return ReadReg(0xA5);
}
void Axp2101::PowerOff() {
uint8_t value = ReadReg(0x10);
value = value | 0x01;
WriteReg(0x10, value);
}
#include "axp2101.h"
#include "board.h"
#include "display.h"
#include <esp_log.h>
#define TAG "Axp2101"
Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
}
int Axp2101::GetBatteryCurrentDirection() {
return (ReadReg(0x01) & 0b01100000) >> 5;
}
bool Axp2101::IsCharging() {
return GetBatteryCurrentDirection() == 1;
}
bool Axp2101::IsDischarging() {
return GetBatteryCurrentDirection() == 2;
}
bool Axp2101::IsChargingDone() {
uint8_t value = ReadReg(0x01);
return (value & 0b00000111) == 0b00000100;
}
int Axp2101::GetBatteryLevel() {
return ReadReg(0xA4);
}
float Axp2101::GetTemperature() {
return ReadReg(0xA5);
}
void Axp2101::PowerOff() {
uint8_t value = ReadReg(0x10);
value = value | 0x01;
WriteReg(0x10, value);
}

View File

@@ -1,20 +1,20 @@
#ifndef __AXP2101_H__
#define __AXP2101_H__
#include "i2c_device.h"
class Axp2101 : public I2cDevice {
public:
Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
bool IsCharging();
bool IsDischarging();
bool IsChargingDone();
int GetBatteryLevel();
float GetTemperature();
void PowerOff();
private:
int GetBatteryCurrentDirection();
};
#endif
#ifndef __AXP2101_H__
#define __AXP2101_H__
#include "i2c_device.h"
class Axp2101 : public I2cDevice {
public:
Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
bool IsCharging();
bool IsDischarging();
bool IsChargingDone();
int GetBatteryLevel();
float GetTemperature();
void PowerOff();
private:
int GetBatteryCurrentDirection();
};
#endif

View File

@@ -1,121 +1,121 @@
#include "backlight.h"
#include "settings.h"
#include <esp_log.h>
#include <driver/ledc.h>
#define TAG "Backlight"
Backlight::Backlight() {
// 创建背光渐变定时器
const esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto self = static_cast<Backlight*>(arg);
self->OnTransitionTimer();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "backlight_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &transition_timer_));
}
Backlight::~Backlight() {
if (transition_timer_ != nullptr) {
esp_timer_stop(transition_timer_);
esp_timer_delete(transition_timer_);
}
}
void Backlight::RestoreBrightness() {
// Load brightness from settings
Settings settings("display");
int saved_brightness = settings.GetInt("brightness", 75);
// 检查亮度值是否为0或过小设置默认值
if (saved_brightness <= 0) {
ESP_LOGW(TAG, "Brightness value (%d) is too small, setting to default (10)", saved_brightness);
saved_brightness = 10; // 设置一个较低的默认值
}
SetBrightness(saved_brightness);
}
void Backlight::SetBrightness(uint8_t brightness, bool permanent) {
if (brightness > 100) {
brightness = 100;
}
if (brightness_ == brightness) {
return;
}
if (permanent) {
Settings settings("display", true);
settings.SetInt("brightness", brightness);
}
target_brightness_ = brightness;
step_ = (target_brightness_ > brightness_) ? 1 : -1;
if (transition_timer_ != nullptr) {
// 启动定时器,每 5ms 更新一次
esp_timer_start_periodic(transition_timer_, 5 * 1000);
}
ESP_LOGI(TAG, "Set brightness to %d", brightness);
}
void Backlight::OnTransitionTimer() {
if (brightness_ == target_brightness_) {
esp_timer_stop(transition_timer_);
return;
}
brightness_ += step_;
SetBrightnessImpl(brightness_);
if (brightness_ == target_brightness_) {
esp_timer_stop(transition_timer_);
}
}
PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert, uint32_t freq_hz) : Backlight() {
const ledc_timer_config_t backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = freq_hz, //背光pwm频率需要高一点防止电感啸叫
.clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false
};
ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer));
// Setup LEDC peripheral for PWM backlight control
const ledc_channel_config_t backlight_channel = {
.gpio_num = pin,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0,
.flags = {
.output_invert = output_invert,
}
};
ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel));
}
PwmBacklight::~PwmBacklight() {
ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
}
void PwmBacklight::SetBrightnessImpl(uint8_t brightness) {
// LEDC resolution set to 10bits, thus: 100% = 1023
uint32_t duty_cycle = (1023 * brightness) / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}
#include "backlight.h"
#include "settings.h"
#include <esp_log.h>
#include <driver/ledc.h>
#define TAG "Backlight"
Backlight::Backlight() {
// 创建背光渐变定时器
const esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto self = static_cast<Backlight*>(arg);
self->OnTransitionTimer();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "backlight_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &transition_timer_));
}
Backlight::~Backlight() {
if (transition_timer_ != nullptr) {
esp_timer_stop(transition_timer_);
esp_timer_delete(transition_timer_);
}
}
void Backlight::RestoreBrightness() {
// Load brightness from settings
Settings settings("display");
int saved_brightness = settings.GetInt("brightness", 75);
// 检查亮度值是否为0或过小设置默认值
if (saved_brightness <= 0) {
ESP_LOGW(TAG, "Brightness value (%d) is too small, setting to default (10)", saved_brightness);
saved_brightness = 10; // 设置一个较低的默认值
}
SetBrightness(saved_brightness);
}
void Backlight::SetBrightness(uint8_t brightness, bool permanent) {
if (brightness > 100) {
brightness = 100;
}
if (brightness_ == brightness) {
return;
}
if (permanent) {
Settings settings("display", true);
settings.SetInt("brightness", brightness);
}
target_brightness_ = brightness;
step_ = (target_brightness_ > brightness_) ? 1 : -1;
if (transition_timer_ != nullptr) {
// 启动定时器,每 5ms 更新一次
esp_timer_start_periodic(transition_timer_, 5 * 1000);
}
ESP_LOGI(TAG, "Set brightness to %d", brightness);
}
void Backlight::OnTransitionTimer() {
if (brightness_ == target_brightness_) {
esp_timer_stop(transition_timer_);
return;
}
brightness_ += step_;
SetBrightnessImpl(brightness_);
if (brightness_ == target_brightness_) {
esp_timer_stop(transition_timer_);
}
}
PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert, uint32_t freq_hz) : Backlight() {
const ledc_timer_config_t backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = freq_hz, //背光pwm频率需要高一点防止电感啸叫
.clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false
};
ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer));
// Setup LEDC peripheral for PWM backlight control
const ledc_channel_config_t backlight_channel = {
.gpio_num = pin,
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0,
.flags = {
.output_invert = output_invert,
}
};
ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel));
}
PwmBacklight::~PwmBacklight() {
ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
}
void PwmBacklight::SetBrightnessImpl(uint8_t brightness) {
// LEDC resolution set to 10bits, thus: 100% = 1023
uint32_t duty_cycle = (1023 * brightness) / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}

View File

@@ -1,36 +1,36 @@
#pragma once
#include <cstdint>
#include <functional>
#include <driver/gpio.h>
#include <esp_timer.h>
class Backlight {
public:
Backlight();
~Backlight();
void RestoreBrightness();
void SetBrightness(uint8_t brightness, bool permanent = false);
inline uint8_t brightness() const { return brightness_; }
protected:
void OnTransitionTimer();
virtual void SetBrightnessImpl(uint8_t brightness) = 0;
esp_timer_handle_t transition_timer_ = nullptr;
uint8_t brightness_ = 0;
uint8_t target_brightness_ = 0;
uint8_t step_ = 1;
};
class PwmBacklight : public Backlight {
public:
PwmBacklight(gpio_num_t pin, bool output_invert = false, uint32_t freq_hz = 25000);
~PwmBacklight();
void SetBrightnessImpl(uint8_t brightness) override;
};
#pragma once
#include <cstdint>
#include <functional>
#include <driver/gpio.h>
#include <esp_timer.h>
class Backlight {
public:
Backlight();
~Backlight();
void RestoreBrightness();
void SetBrightness(uint8_t brightness, bool permanent = false);
inline uint8_t brightness() const { return brightness_; }
protected:
void OnTransitionTimer();
virtual void SetBrightnessImpl(uint8_t brightness) = 0;
esp_timer_handle_t transition_timer_ = nullptr;
uint8_t brightness_ = 0;
uint8_t target_brightness_ = 0;
uint8_t step_ = 1;
};
class PwmBacklight : public Backlight {
public:
PwmBacklight(gpio_num_t pin, bool output_invert = false, uint32_t freq_hz = 25000);
~PwmBacklight();
void SetBrightnessImpl(uint8_t brightness) override;
};

View File

@@ -1,207 +1,192 @@
#include "board.h"
#include "system_info.h"
#include "settings.h"
#include "display/display.h"
#include "display/oled_display.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_ota_ops.h>
#include <esp_chip_info.h>
#include <esp_random.h>
#include "esp32_music.h"
#define TAG "Board"
Board::Board() {
music_ = nullptr; // 先初始化为空指针
Settings settings("board", true);
uuid_ = settings.GetString("uuid");
if (uuid_.empty()) {
uuid_ = GenerateUuid();
settings.SetString("uuid", uuid_);
}
ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME);
// 初始化音乐播放器
music_ = new Esp32Music();
ESP_LOGI(TAG, "Music player initialized for all boards");
}
Board::~Board() {
if (music_) {
delete music_;
music_ = nullptr;
ESP_LOGI(TAG, "Music player destroyed");
}
}
std::string Board::GenerateUuid() {
// UUID v4 需要 16 字节的随机数据
uint8_t uuid[16];
// 使用 ESP32 的硬件随机数生成器
esp_fill_random(uuid, sizeof(uuid));
// 设置版本 (版本 4) 和变体位
uuid[6] = (uuid[6] & 0x0F) | 0x40; // 版本 4
uuid[8] = (uuid[8] & 0x3F) | 0x80; // 变体 1
// 将字节转换为标准的 UUID 字符串格式
char uuid_str[37];
snprintf(uuid_str, sizeof(uuid_str),
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11],
uuid[12], uuid[13], uuid[14], uuid[15]);
return std::string(uuid_str);
}
bool Board::GetBatteryLevel(int &level, bool& charging, bool& discharging) {
return false;
}
bool Board::GetTemperature(float& esp32temp){
return false;
}
Display* Board::GetDisplay() {
static NoDisplay display;
return &display;
}
Camera* Board::GetCamera() {
return nullptr;
}
Music* Board::GetMusic() {
return music_;
}
Led* Board::GetLed() {
static NoLed led;
return &led;
}
std::string Board::GetSystemInfoJson() {
/*
{
"version": 2,
"flash_size": 4194304,
"psram_size": 0,
"minimum_free_heap_size": 123456,
"mac_address": "00:00:00:00:00:00",
"uuid": "00000000-0000-0000-0000-000000000000",
"chip_model_name": "esp32s3",
"chip_info": {
"model": 1,
"cores": 2,
"revision": 0,
"features": 0
},
"application": {
"name": "my-app",
"version": "1.0.0",
"compile_time": "2021-01-01T00:00:00Z"
"idf_version": "4.2-dev"
"elf_sha256": ""
},
"partition_table": [
"app": {
"label": "app",
"type": 1,
"subtype": 2,
"address": 0x10000,
"size": 0x100000
}
],
"ota": {
"label": "ota_0"
},
"board": {
...
}
}
*/
std::string json = R"({"version":2,"language":")" + std::string(Lang::CODE) + R"(",)";
json += R"("flash_size":)" + std::to_string(SystemInfo::GetFlashSize()) + R"(,)";
json += R"("minimum_free_heap_size":")" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + R"(",)";
json += R"("mac_address":")" + SystemInfo::GetMacAddress() + R"(",)";
json += R"("uuid":")" + uuid_ + R"(",)";
json += R"("chip_model_name":")" + SystemInfo::GetChipModelName() + R"(",)";
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
json += R"("chip_info":{)";
json += R"("model":)" + std::to_string(chip_info.model) + R"(,)";
json += R"("cores":)" + std::to_string(chip_info.cores) + R"(,)";
json += R"("revision":)" + std::to_string(chip_info.revision) + R"(,)";
json += R"("features":)" + std::to_string(chip_info.features) + R"(},)";
auto app_desc = esp_app_get_description();
json += R"("application":{)";
json += R"("name":")" + std::string(app_desc->project_name) + R"(",)";
json += R"("version":")" + std::string(app_desc->version) + R"(",)";
json += R"("compile_time":")" + std::string(app_desc->date) + R"(T)" + std::string(app_desc->time) + R"(Z",)";
json += R"("idf_version":")" + std::string(app_desc->idf_ver) + R"(",)";
char sha256_str[65];
for (int i = 0; i < 32; i++) {
snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
}
json += R"("elf_sha256":")" + std::string(sha256_str) + R"(")";
json += R"(},)";
json += R"("partition_table": [)";
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
while (it) {
const esp_partition_t *partition = esp_partition_get(it);
json += R"({)";
json += R"("label":")" + std::string(partition->label) + R"(",)";
json += R"("type":)" + std::to_string(partition->type) + R"(,)";
json += R"("subtype":)" + std::to_string(partition->subtype) + R"(,)";
json += R"("address":)" + std::to_string(partition->address) + R"(,)";
json += R"("size":)" + std::to_string(partition->size) + R"(},)";;
it = esp_partition_next(it);
}
json.pop_back(); // Remove the last comma
json += R"(],)";
json += R"("ota":{)";
auto ota_partition = esp_ota_get_running_partition();
json += R"("label":")" + std::string(ota_partition->label) + R"(")";
json += R"(},)";
// Append display info
auto display = GetDisplay();
if (display) {
json += R"("display":{)";
if (dynamic_cast<OledDisplay*>(display)) {
json += R"("monochrome":)" + std::string("true") + R"(,)";
} else {
json += R"("monochrome":)" + std::string("false") + R"(,)";
}
json += R"("width":)" + std::to_string(display->width()) + R"(,)";
json += R"("height":)" + std::to_string(display->height()) + R"(,)";
json.pop_back(); // Remove the last comma
}
json += R"(},)";
json += R"("board":)" + GetBoardJson();
// Close the JSON object
json += R"(})";
return json;
}
Assets* Board::GetAssets() {
#ifdef DEFAULT_ASSETS
static Assets assets(DEFAULT_ASSETS);
return &assets;
#else
return nullptr;
#endif
}
#include "board.h"
#include "system_info.h"
#include "settings.h"
#include "display/display.h"
#include "display/oled_display.h"
#include "assets/lang_config.h"
#include "esp32_music.h"
#include <esp_log.h>
#include <esp_ota_ops.h>
#include <esp_chip_info.h>
#include <esp_random.h>
#define TAG "Board"
Board::Board() {
music_ = nullptr; // 先初始化为空指针
Settings settings("board", true);
uuid_ = settings.GetString("uuid");
if (uuid_.empty()) {
uuid_ = GenerateUuid();
settings.SetString("uuid", uuid_);
}
ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME);
// 初始化音乐播放器
music_ = new Esp32Music();
ESP_LOGI(TAG, "Music player initialized for all boards");
}
Board::~Board() {
if (music_) {
delete music_;
music_ = nullptr;
ESP_LOGI(TAG, "Music player destroyed");
}
}
std::string Board::GenerateUuid() {
// UUID v4 需要 16 字节的随机数据
uint8_t uuid[16];
// 使用 ESP32 的硬件随机数生成器
esp_fill_random(uuid, sizeof(uuid));
// 设置版本 (版本 4) 和变体位
uuid[6] = (uuid[6] & 0x0F) | 0x40; // 版本 4
uuid[8] = (uuid[8] & 0x3F) | 0x80; // 变体 1
// 将字节转换为标准的 UUID 字符串格式
char uuid_str[37];
snprintf(uuid_str, sizeof(uuid_str),
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11],
uuid[12], uuid[13], uuid[14], uuid[15]);
return std::string(uuid_str);
}
bool Board::GetBatteryLevel(int &level, bool& charging, bool& discharging) {
return false;
}
bool Board::GetTemperature(float& esp32temp){
return false;
}
Display* Board::GetDisplay() {
static NoDisplay display;
return &display;
}
Camera* Board::GetCamera() {
return nullptr;
}
Led* Board::GetLed() {
static NoLed led;
return &led;
}
Music* Board::GetMusic() {
return music_;
}
std::string Board::GetSystemInfoJson() {
/*
{
"version": 2,
"flash_size": 4194304,
"psram_size": 0,
"minimum_free_heap_size": 123456,
"mac_address": "00:00:00:00:00:00",
"uuid": "00000000-0000-0000-0000-000000000000",
"chip_model_name": "esp32s3",
"chip_info": {
"model": 1,
"cores": 2,
"revision": 0,
"features": 0
},
"application": {
"name": "my-app",
"version": "1.0.0",
"compile_time": "2021-01-01T00:00:00Z"
"idf_version": "4.2-dev"
"elf_sha256": ""
},
"partition_table": [
"app": {
"label": "app",
"type": 1,
"subtype": 2,
"address": 0x10000,
"size": 0x100000
}
],
"ota": {
"label": "ota_0"
},
"board": {
...
}
}
*/
std::string json = R"({"version":2,"language":")" + std::string(Lang::CODE) + R"(",)";
json += R"("flash_size":)" + std::to_string(SystemInfo::GetFlashSize()) + R"(,)";
json += R"("minimum_free_heap_size":")" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + R"(",)";
json += R"("mac_address":")" + SystemInfo::GetMacAddress() + R"(",)";
json += R"("uuid":")" + uuid_ + R"(",)";
json += R"("chip_model_name":")" + SystemInfo::GetChipModelName() + R"(",)";
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
json += R"("chip_info":{)";
json += R"("model":)" + std::to_string(chip_info.model) + R"(,)";
json += R"("cores":)" + std::to_string(chip_info.cores) + R"(,)";
json += R"("revision":)" + std::to_string(chip_info.revision) + R"(,)";
json += R"("features":)" + std::to_string(chip_info.features) + R"(},)";
auto app_desc = esp_app_get_description();
json += R"("application":{)";
json += R"("name":")" + std::string(app_desc->project_name) + R"(",)";
json += R"("version":")" + std::string(app_desc->version) + R"(",)";
json += R"("compile_time":")" + std::string(app_desc->date) + R"(T)" + std::string(app_desc->time) + R"(Z",)";
json += R"("idf_version":")" + std::string(app_desc->idf_ver) + R"(",)";
char sha256_str[65];
for (int i = 0; i < 32; i++) {
snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
}
json += R"("elf_sha256":")" + std::string(sha256_str) + R"(")";
json += R"(},)";
json += R"("partition_table": [)";
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
while (it) {
const esp_partition_t *partition = esp_partition_get(it);
json += R"({)";
json += R"("label":")" + std::string(partition->label) + R"(",)";
json += R"("type":)" + std::to_string(partition->type) + R"(,)";
json += R"("subtype":)" + std::to_string(partition->subtype) + R"(,)";
json += R"("address":)" + std::to_string(partition->address) + R"(,)";
json += R"("size":)" + std::to_string(partition->size) + R"(},)";;
it = esp_partition_next(it);
}
json.pop_back(); // Remove the last comma
json += R"(],)";
json += R"("ota":{)";
auto ota_partition = esp_ota_get_running_partition();
json += R"("label":")" + std::string(ota_partition->label) + R"(")";
json += R"(},)";
// Append display info
auto display = GetDisplay();
if (display) {
json += R"("display":{)";
if (dynamic_cast<OledDisplay*>(display)) {
json += R"("monochrome":)" + std::string("true") + R"(,)";
} else {
json += R"("monochrome":)" + std::string("false") + R"(,)";
}
json += R"("width":)" + std::to_string(display->width()) + R"(,)";
json += R"("height":)" + std::to_string(display->height()) + R"(,)";
json.pop_back(); // Remove the last comma
}
json += R"(},)";
json += R"("board":)" + GetBoardJson();
// Close the JSON object
json += R"(})";
return json;
}

View File

@@ -1,69 +1,66 @@
#ifndef BOARD_H
#define BOARD_H
#include <http.h>
#include <web_socket.h>
#include <mqtt.h>
#include <udp.h>
#include <string>
#include <network_interface.h>
#include "led/led.h"
#include "backlight.h"
#include "camera.h"
#include "assets.h"
#include "music.h"
void* create_board();
class AudioCodec;
class Display;
class Board {
private:
Board(const Board&) = delete; // 禁用拷贝构造函数
Board& operator=(const Board&) = delete; // 禁用赋值操作
protected:
Board();
std::string GenerateUuid();
// 软件生成的设备唯一标识
std::string uuid_;
// 音乐播放器实例
Music* music_;
public:
static Board& GetInstance() {
static Board* instance = static_cast<Board*>(create_board());
return *instance;
}
virtual ~Board(); // 改为非默认析构函数,用于清理 music_
virtual std::string GetBoardType() = 0;
virtual std::string GetUuid() { return uuid_; }
virtual Backlight* GetBacklight() { return nullptr; }
virtual Led* GetLed();
virtual AudioCodec* GetAudioCodec() = 0;
virtual bool GetTemperature(float& esp32temp);
virtual Display* GetDisplay();
virtual Camera* GetCamera();
virtual Music* GetMusic();
virtual NetworkInterface* GetNetwork() = 0;
virtual void StartNetwork() = 0;
virtual const char* GetNetworkStateIcon() = 0;
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging);
virtual std::string GetSystemInfoJson();
virtual void SetPowerSaveMode(bool enabled) = 0;
virtual std::string GetBoardJson() = 0;
virtual std::string GetDeviceStatusJson() = 0;
virtual Assets* GetAssets();
};
#define DECLARE_BOARD(BOARD_CLASS_NAME) \
void* create_board() { \
return new BOARD_CLASS_NAME(); \
}
#endif // BOARD_H
#ifndef BOARD_H
#define BOARD_H
#include <http.h>
#include <web_socket.h>
#include <mqtt.h>
#include <udp.h>
#include <string>
#include <network_interface.h>
#include "led/led.h"
#include "backlight.h"
#include "camera.h"
#include "assets.h"
#include "music.h"
void* create_board();
class AudioCodec;
class Display;
class Board {
private:
Board(const Board&) = delete; // 禁用拷贝构造函数
Board& operator=(const Board&) = delete; // 禁用赋值操作
protected:
Board();
std::string GenerateUuid();
// 软件生成的设备唯一标识
std::string uuid_;
// 音乐播放器实例
Music* music_;
public:
static Board& GetInstance() {
static Board* instance = static_cast<Board*>(create_board());
return *instance;
}
virtual ~Board(); // 改为非默认析构函数,用于清理 music_
virtual std::string GetBoardType() = 0;
virtual std::string GetUuid() { return uuid_; }
virtual Backlight* GetBacklight() { return nullptr; }
virtual Led* GetLed();
virtual Music* GetMusic();
virtual AudioCodec* GetAudioCodec() = 0;
virtual bool GetTemperature(float& esp32temp);
virtual Display* GetDisplay();
virtual Camera* GetCamera();
virtual NetworkInterface* GetNetwork() = 0;
virtual void StartNetwork() = 0;
virtual const char* GetNetworkStateIcon() = 0;
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging);
virtual std::string GetSystemInfoJson();
virtual void SetPowerSaveMode(bool enabled) = 0;
virtual std::string GetBoardJson() = 0;
virtual std::string GetDeviceStatusJson() = 0;
};
#define DECLARE_BOARD(BOARD_CLASS_NAME) \
void* create_board() { \
return new BOARD_CLASS_NAME(); \
}
#endif // BOARD_H

View File

@@ -1,125 +1,125 @@
#include "button.h"
#include <button_gpio.h>
#include <esp_log.h>
#define TAG "Button"
#if CONFIG_SOC_ADC_SUPPORTED
AdcButton::AdcButton(const button_adc_config_t& adc_config) : Button(nullptr) {
button_config_t btn_config = {
.long_press_time = 2000,
.short_press_time = 0,
};
ESP_ERROR_CHECK(iot_button_new_adc_device(&btn_config, &adc_config, &button_handle_));
}
#endif
Button::Button(button_handle_t button_handle) : button_handle_(button_handle) {
}
Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, uint16_t short_press_time, bool enable_power_save) : gpio_num_(gpio_num) {
if (gpio_num == GPIO_NUM_NC) {
return;
}
button_config_t button_config = {
.long_press_time = long_press_time,
.short_press_time = short_press_time
};
button_gpio_config_t gpio_config = {
.gpio_num = gpio_num,
.active_level = static_cast<uint8_t>(active_high ? 1 : 0),
.enable_power_save = enable_power_save,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&button_config, &gpio_config, &button_handle_));
}
Button::~Button() {
if (button_handle_ != NULL) {
iot_button_delete(button_handle_);
}
}
void Button::OnPressDown(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_press_down_ = callback;
iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_press_down_) {
button->on_press_down_();
}
}, this);
}
void Button::OnPressUp(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_press_up_ = callback;
iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_press_up_) {
button->on_press_up_();
}
}, this);
}
void Button::OnLongPress(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_long_press_ = callback;
iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_long_press_) {
button->on_long_press_();
}
}, this);
}
void Button::OnClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_click_) {
button->on_click_();
}
}, this);
}
void Button::OnDoubleClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_double_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_double_click_) {
button->on_double_click_();
}
}, this);
}
void Button::OnMultipleClick(std::function<void()> callback, uint8_t click_count) {
if (button_handle_ == nullptr) {
return;
}
on_multiple_click_ = callback;
button_event_args_t event_args = {
.multiple_clicks = {
.clicks = click_count
}
};
iot_button_register_cb(button_handle_, BUTTON_MULTIPLE_CLICK, &event_args, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_multiple_click_) {
button->on_multiple_click_();
}
}, this);
#include "button.h"
#include <button_gpio.h>
#include <esp_log.h>
#define TAG "Button"
#if CONFIG_SOC_ADC_SUPPORTED
AdcButton::AdcButton(const button_adc_config_t& adc_config) : Button(nullptr) {
button_config_t btn_config = {
.long_press_time = 2000,
.short_press_time = 0,
};
ESP_ERROR_CHECK(iot_button_new_adc_device(&btn_config, &adc_config, &button_handle_));
}
#endif
Button::Button(button_handle_t button_handle) : button_handle_(button_handle) {
}
Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, uint16_t short_press_time, bool enable_power_save) : gpio_num_(gpio_num) {
if (gpio_num == GPIO_NUM_NC) {
return;
}
button_config_t button_config = {
.long_press_time = long_press_time,
.short_press_time = short_press_time
};
button_gpio_config_t gpio_config = {
.gpio_num = gpio_num,
.active_level = static_cast<uint8_t>(active_high ? 1 : 0),
.enable_power_save = enable_power_save,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&button_config, &gpio_config, &button_handle_));
}
Button::~Button() {
if (button_handle_ != NULL) {
iot_button_delete(button_handle_);
}
}
void Button::OnPressDown(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_press_down_ = callback;
iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_press_down_) {
button->on_press_down_();
}
}, this);
}
void Button::OnPressUp(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_press_up_ = callback;
iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_press_up_) {
button->on_press_up_();
}
}, this);
}
void Button::OnLongPress(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_long_press_ = callback;
iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_long_press_) {
button->on_long_press_();
}
}, this);
}
void Button::OnClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_click_) {
button->on_click_();
}
}, this);
}
void Button::OnDoubleClick(std::function<void()> callback) {
if (button_handle_ == nullptr) {
return;
}
on_double_click_ = callback;
iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, nullptr, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_double_click_) {
button->on_double_click_();
}
}, this);
}
void Button::OnMultipleClick(std::function<void()> callback, uint8_t click_count) {
if (button_handle_ == nullptr) {
return;
}
on_multiple_click_ = callback;
button_event_args_t event_args = {
.multiple_clicks = {
.clicks = click_count
}
};
iot_button_register_cb(button_handle_, BUTTON_MULTIPLE_CLICK, &event_args, [](void* handle, void* usr_data) {
Button* button = static_cast<Button*>(usr_data);
if (button->on_multiple_click_) {
button->on_multiple_click_();
}
}, this);
}

View File

@@ -1,49 +1,49 @@
#ifndef BUTTON_H_
#define BUTTON_H_
#include <driver/gpio.h>
#include <iot_button.h>
#include <button_types.h>
#include <button_adc.h>
#include <button_gpio.h>
#include <functional>
class Button {
public:
Button(button_handle_t button_handle);
Button(gpio_num_t gpio_num, bool active_high = false, uint16_t long_press_time = 0, uint16_t short_press_time = 0, bool enable_power_save = false);
~Button();
void OnPressDown(std::function<void()> callback);
void OnPressUp(std::function<void()> callback);
void OnLongPress(std::function<void()> callback);
void OnClick(std::function<void()> callback);
void OnDoubleClick(std::function<void()> callback);
void OnMultipleClick(std::function<void()> callback, uint8_t click_count = 3);
protected:
gpio_num_t gpio_num_;
button_handle_t button_handle_ = nullptr;
std::function<void()> on_press_down_;
std::function<void()> on_press_up_;
std::function<void()> on_long_press_;
std::function<void()> on_click_;
std::function<void()> on_double_click_;
std::function<void()> on_multiple_click_;
};
#if CONFIG_SOC_ADC_SUPPORTED
class AdcButton : public Button {
public:
AdcButton(const button_adc_config_t& adc_config);
};
#endif
class PowerSaveButton : public Button {
public:
PowerSaveButton(gpio_num_t gpio_num) : Button(gpio_num, false, 0, 0, true) {
}
};
#endif // BUTTON_H_
#ifndef BUTTON_H_
#define BUTTON_H_
#include <driver/gpio.h>
#include <iot_button.h>
#include <button_types.h>
#include <button_adc.h>
#include <button_gpio.h>
#include <functional>
class Button {
public:
Button(button_handle_t button_handle);
Button(gpio_num_t gpio_num, bool active_high = false, uint16_t long_press_time = 0, uint16_t short_press_time = 0, bool enable_power_save = false);
~Button();
void OnPressDown(std::function<void()> callback);
void OnPressUp(std::function<void()> callback);
void OnLongPress(std::function<void()> callback);
void OnClick(std::function<void()> callback);
void OnDoubleClick(std::function<void()> callback);
void OnMultipleClick(std::function<void()> callback, uint8_t click_count = 3);
protected:
gpio_num_t gpio_num_;
button_handle_t button_handle_ = nullptr;
std::function<void()> on_press_down_;
std::function<void()> on_press_up_;
std::function<void()> on_long_press_;
std::function<void()> on_click_;
std::function<void()> on_double_click_;
std::function<void()> on_multiple_click_;
};
#if CONFIG_SOC_ADC_SUPPORTED
class AdcButton : public Button {
public:
AdcButton(const button_adc_config_t& adc_config);
};
#endif
class PowerSaveButton : public Button {
public:
PowerSaveButton(gpio_num_t gpio_num) : Button(gpio_num, false, 0, 0, true) {
}
};
#endif // BUTTON_H_

View File

@@ -1,15 +1,15 @@
#ifndef CAMERA_H
#define CAMERA_H
#include <string>
class Camera {
public:
virtual void SetExplainUrl(const std::string& url, const std::string& token) = 0;
virtual bool Capture() = 0;
virtual bool SetHMirror(bool enabled) = 0;
virtual bool SetVFlip(bool enabled) = 0;
virtual std::string Explain(const std::string& question) = 0;
};
#endif // CAMERA_H
#ifndef CAMERA_H
#define CAMERA_H
#include <string>
class Camera {
public:
virtual void SetExplainUrl(const std::string& url, const std::string& token) = 0;
virtual bool Capture() = 0;
virtual bool SetHMirror(bool enabled) = 0;
virtual bool SetVFlip(bool enabled) = 0;
virtual std::string Explain(const std::string& question) = 0;
};
#endif // CAMERA_H

View File

@@ -1,93 +1,93 @@
#include "dual_network_board.h"
#include "application.h"
#include "display.h"
#include "assets/lang_config.h"
#include "settings.h"
#include <esp_log.h>
static const char *TAG = "DualNetworkBoard";
DualNetworkBoard::DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin, int32_t default_net_type)
: Board(),
ml307_tx_pin_(ml307_tx_pin),
ml307_rx_pin_(ml307_rx_pin),
ml307_dtr_pin_(ml307_dtr_pin) {
// 从Settings加载网络类型
network_type_ = LoadNetworkTypeFromSettings(default_net_type);
// 只初始化当前网络类型对应的板卡
InitializeCurrentBoard();
}
NetworkType DualNetworkBoard::LoadNetworkTypeFromSettings(int32_t default_net_type) {
Settings settings("network", true);
int network_type = settings.GetInt("type", default_net_type); // 默认使用ML307 (1)
return network_type == 1 ? NetworkType::ML307 : NetworkType::WIFI;
}
void DualNetworkBoard::SaveNetworkTypeToSettings(NetworkType type) {
Settings settings("network", true);
int network_type = (type == NetworkType::ML307) ? 1 : 0;
settings.SetInt("type", network_type);
}
void DualNetworkBoard::InitializeCurrentBoard() {
if (network_type_ == NetworkType::ML307) {
ESP_LOGI(TAG, "Initialize ML307 board");
current_board_ = std::make_unique<Ml307Board>(ml307_tx_pin_, ml307_rx_pin_, ml307_dtr_pin_);
} else {
ESP_LOGI(TAG, "Initialize WiFi board");
current_board_ = std::make_unique<WifiBoard>();
}
}
void DualNetworkBoard::SwitchNetworkType() {
auto display = GetDisplay();
if (network_type_ == NetworkType::WIFI) {
SaveNetworkTypeToSettings(NetworkType::ML307);
display->ShowNotification(Lang::Strings::SWITCH_TO_4G_NETWORK);
} else {
SaveNetworkTypeToSettings(NetworkType::WIFI);
display->ShowNotification(Lang::Strings::SWITCH_TO_WIFI_NETWORK);
}
vTaskDelay(pdMS_TO_TICKS(1000));
auto& app = Application::GetInstance();
app.Reboot();
}
std::string DualNetworkBoard::GetBoardType() {
return current_board_->GetBoardType();
}
void DualNetworkBoard::StartNetwork() {
auto display = Board::GetInstance().GetDisplay();
if (network_type_ == NetworkType::WIFI) {
display->SetStatus(Lang::Strings::CONNECTING);
} else {
display->SetStatus(Lang::Strings::DETECTING_MODULE);
}
current_board_->StartNetwork();
}
NetworkInterface* DualNetworkBoard::GetNetwork() {
return current_board_->GetNetwork();
}
const char* DualNetworkBoard::GetNetworkStateIcon() {
return current_board_->GetNetworkStateIcon();
}
void DualNetworkBoard::SetPowerSaveMode(bool enabled) {
current_board_->SetPowerSaveMode(enabled);
}
std::string DualNetworkBoard::GetBoardJson() {
return current_board_->GetBoardJson();
}
std::string DualNetworkBoard::GetDeviceStatusJson() {
return current_board_->GetDeviceStatusJson();
}
#include "dual_network_board.h"
#include "application.h"
#include "display.h"
#include "assets/lang_config.h"
#include "settings.h"
#include <esp_log.h>
static const char *TAG = "DualNetworkBoard";
DualNetworkBoard::DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin, int32_t default_net_type)
: Board(),
ml307_tx_pin_(ml307_tx_pin),
ml307_rx_pin_(ml307_rx_pin),
ml307_dtr_pin_(ml307_dtr_pin) {
// 从Settings加载网络类型
network_type_ = LoadNetworkTypeFromSettings(default_net_type);
// 只初始化当前网络类型对应的板卡
InitializeCurrentBoard();
}
NetworkType DualNetworkBoard::LoadNetworkTypeFromSettings(int32_t default_net_type) {
Settings settings("network", true);
int network_type = settings.GetInt("type", default_net_type); // 默认使用ML307 (1)
return network_type == 1 ? NetworkType::ML307 : NetworkType::WIFI;
}
void DualNetworkBoard::SaveNetworkTypeToSettings(NetworkType type) {
Settings settings("network", true);
int network_type = (type == NetworkType::ML307) ? 1 : 0;
settings.SetInt("type", network_type);
}
void DualNetworkBoard::InitializeCurrentBoard() {
if (network_type_ == NetworkType::ML307) {
ESP_LOGI(TAG, "Initialize ML307 board");
current_board_ = std::make_unique<Ml307Board>(ml307_tx_pin_, ml307_rx_pin_, ml307_dtr_pin_);
} else {
ESP_LOGI(TAG, "Initialize WiFi board");
current_board_ = std::make_unique<WifiBoard>();
}
}
void DualNetworkBoard::SwitchNetworkType() {
auto display = GetDisplay();
if (network_type_ == NetworkType::WIFI) {
SaveNetworkTypeToSettings(NetworkType::ML307);
display->ShowNotification(Lang::Strings::SWITCH_TO_4G_NETWORK);
} else {
SaveNetworkTypeToSettings(NetworkType::WIFI);
display->ShowNotification(Lang::Strings::SWITCH_TO_WIFI_NETWORK);
}
vTaskDelay(pdMS_TO_TICKS(1000));
auto& app = Application::GetInstance();
app.Reboot();
}
std::string DualNetworkBoard::GetBoardType() {
return current_board_->GetBoardType();
}
void DualNetworkBoard::StartNetwork() {
auto display = Board::GetInstance().GetDisplay();
if (network_type_ == NetworkType::WIFI) {
display->SetStatus(Lang::Strings::CONNECTING);
} else {
display->SetStatus(Lang::Strings::DETECTING_MODULE);
}
current_board_->StartNetwork();
}
NetworkInterface* DualNetworkBoard::GetNetwork() {
return current_board_->GetNetwork();
}
const char* DualNetworkBoard::GetNetworkStateIcon() {
return current_board_->GetNetworkStateIcon();
}
void DualNetworkBoard::SetPowerSaveMode(bool enabled) {
current_board_->SetPowerSaveMode(enabled);
}
std::string DualNetworkBoard::GetBoardJson() {
return current_board_->GetBoardJson();
}
std::string DualNetworkBoard::GetDeviceStatusJson() {
return current_board_->GetDeviceStatusJson();
}

View File

@@ -1,59 +1,59 @@
#ifndef DUAL_NETWORK_BOARD_H
#define DUAL_NETWORK_BOARD_H
#include "board.h"
#include "wifi_board.h"
#include "ml307_board.h"
#include <memory>
//enum NetworkType
enum class NetworkType {
WIFI,
ML307
};
// 双网络板卡类可以在WiFi和ML307之间切换
class DualNetworkBoard : public Board {
private:
// 使用基类指针存储当前活动的板卡
std::unique_ptr<Board> current_board_;
NetworkType network_type_ = NetworkType::ML307; // Default to ML307
// ML307的引脚配置
gpio_num_t ml307_tx_pin_;
gpio_num_t ml307_rx_pin_;
gpio_num_t ml307_dtr_pin_;
// 从Settings加载网络类型
NetworkType LoadNetworkTypeFromSettings(int32_t default_net_type);
// 保存网络类型到Settings
void SaveNetworkTypeToSettings(NetworkType type);
// 初始化当前网络类型对应的板卡
void InitializeCurrentBoard();
public:
DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin = GPIO_NUM_NC, int32_t default_net_type = 1);
virtual ~DualNetworkBoard() = default;
// 切换网络类型
void SwitchNetworkType();
// 获取当前网络类型
NetworkType GetNetworkType() const { return network_type_; }
// 获取当前活动的板卡引用
Board& GetCurrentBoard() const { return *current_board_; }
// 重写Board接口
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual NetworkInterface* GetNetwork() override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
virtual std::string GetBoardJson() override;
virtual std::string GetDeviceStatusJson() override;
};
#ifndef DUAL_NETWORK_BOARD_H
#define DUAL_NETWORK_BOARD_H
#include "board.h"
#include "wifi_board.h"
#include "ml307_board.h"
#include <memory>
//enum NetworkType
enum class NetworkType {
WIFI,
ML307
};
// 双网络板卡类可以在WiFi和ML307之间切换
class DualNetworkBoard : public Board {
private:
// 使用基类指针存储当前活动的板卡
std::unique_ptr<Board> current_board_;
NetworkType network_type_ = NetworkType::ML307; // Default to ML307
// ML307的引脚配置
gpio_num_t ml307_tx_pin_;
gpio_num_t ml307_rx_pin_;
gpio_num_t ml307_dtr_pin_;
// 从Settings加载网络类型
NetworkType LoadNetworkTypeFromSettings(int32_t default_net_type);
// 保存网络类型到Settings
void SaveNetworkTypeToSettings(NetworkType type);
// 初始化当前网络类型对应的板卡
void InitializeCurrentBoard();
public:
DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin = GPIO_NUM_NC, int32_t default_net_type = 1);
virtual ~DualNetworkBoard() = default;
// 切换网络类型
void SwitchNetworkType();
// 获取当前网络类型
NetworkType GetNetworkType() const { return network_type_; }
// 获取当前活动的板卡引用
Board& GetCurrentBoard() const { return *current_board_; }
// 重写Board接口
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual NetworkInterface* GetNetwork() override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
virtual std::string GetBoardJson() override;
virtual std::string GetDeviceStatusJson() override;
};
#endif // DUAL_NETWORK_BOARD_H

View File

@@ -1,259 +1,260 @@
#include "esp32_camera.h"
#include "mcp_server.h"
#include "display.h"
#include "board.h"
#include "system_info.h"
#include "lvgl_display.h"
#include <esp_log.h>
#include <esp_heap_caps.h>
#include <img_converters.h>
#include <cstring>
#define TAG "Esp32Camera"
Esp32Camera::Esp32Camera(const camera_config_t& config) {
// camera init
esp_err_t err = esp_camera_init(&config); // 配置上面定义的参数
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
return;
}
sensor_t *s = esp_camera_sensor_get(); // 获取摄像头型号
if (s->id.PID == GC0308_PID) {
s->set_hmirror(s, 0); // 这里控制摄像头镜像 写1镜像 写0不镜像
}
}
Esp32Camera::~Esp32Camera() {
if (fb_) {
esp_camera_fb_return(fb_);
fb_ = nullptr;
}
esp_camera_deinit();
}
void Esp32Camera::SetExplainUrl(const std::string& url, const std::string& token) {
explain_url_ = url;
explain_token_ = token;
}
bool Esp32Camera::Capture() {
if (encoder_thread_.joinable()) {
encoder_thread_.join();
}
auto start_time = esp_timer_get_time();
int frames_to_get = 2;
// Try to get a stable frame
for (int i = 0; i < frames_to_get; i++) {
if (fb_ != nullptr) {
esp_camera_fb_return(fb_);
}
fb_ = esp_camera_fb_get();
if (fb_ == nullptr) {
ESP_LOGE(TAG, "Camera capture failed");
return false;
}
}
auto end_time = esp_timer_get_time();
ESP_LOGI(TAG, "Camera captured %d frames in %d ms", frames_to_get, int((end_time - start_time) / 1000));
// 显示预览图片
auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
if (display != nullptr) {
auto data = (uint8_t*)heap_caps_malloc(fb_->len, MALLOC_CAP_SPIRAM);
if (data == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for preview image");
return false;
}
auto src = (uint16_t*)fb_->buf;
auto dst = (uint16_t*)data;
size_t pixel_count = fb_->len / 2;
for (size_t i = 0; i < pixel_count; i++) {
// 交换每个16位字内的字节
dst[i] = __builtin_bswap16(src[i]);
}
auto image = std::make_unique<LvglAllocatedImage>(data, fb_->len, fb_->width, fb_->height, fb_->width * 2, LV_COLOR_FORMAT_RGB565);
display->SetPreviewImage(std::move(image));
}
return true;
}
bool Esp32Camera::SetHMirror(bool enabled) {
sensor_t *s = esp_camera_sensor_get();
if (s == nullptr) {
ESP_LOGE(TAG, "Failed to get camera sensor");
return false;
}
esp_err_t err = s->set_hmirror(s, enabled);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set horizontal mirror: %d", err);
return false;
}
ESP_LOGI(TAG, "Camera horizontal mirror set to: %s", enabled ? "enabled" : "disabled");
return true;
}
bool Esp32Camera::SetVFlip(bool enabled) {
sensor_t *s = esp_camera_sensor_get();
if (s == nullptr) {
ESP_LOGE(TAG, "Failed to get camera sensor");
return false;
}
esp_err_t err = s->set_vflip(s, enabled);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set vertical flip: %d", err);
return false;
}
ESP_LOGI(TAG, "Camera vertical flip set to: %s", enabled ? "enabled" : "disabled");
return true;
}
/**
* @brief 将摄像头捕获的图像发送到远程服务器进行AI分析和解释
*
* 该函数将当前摄像头缓冲区中的图像编码为JPEG格式并通过HTTP POST请求
* 以multipart/form-data的形式发送到指定的解释服务器。服务器将根据提供的
* 问题对图像进行AI分析并返回结果。
*
* 实现特点:
* - 使用独立线程编码JPEG与主线程分离
* - 采用分块传输编码(chunked transfer encoding)优化内存使用
* - 通过队列机制实现编码线程和发送线程的数据同步
* - 支持设备ID、客户端ID和认证令牌的HTTP头部配置
*
* @param question 要向AI提出的关于图像的问题将作为表单字段发送
* @return std::string 服务器返回的JSON格式响应字符串
* 成功时包含AI分析结果失败时包含错误信息
* 格式示例:{"success": true, "result": "分析结果"}
* {"success": false, "message": "错误信息"}
*
* @note 调用此函数前必须先调用SetExplainUrl()设置服务器URL
* @note 函数会等待之前的编码线程完成后再开始新的处理
* @warning 如果摄像头缓冲区为空或网络连接失败,将返回错误信息
*/
std::string Esp32Camera::Explain(const std::string& question) {
if (explain_url_.empty()) {
throw std::runtime_error("Image explain URL or token is not set");
}
// 创建局部的 JPEG 队列, 40 entries is about to store 512 * 40 = 20480 bytes of JPEG data
QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk));
if (jpeg_queue == nullptr) {
ESP_LOGE(TAG, "Failed to create JPEG queue");
throw std::runtime_error("Failed to create JPEG queue");
}
// We spawn a thread to encode the image to JPEG
encoder_thread_ = std::thread([this, jpeg_queue]() {
frame2jpg_cb(fb_, 80, [](void* arg, size_t index, const void* data, size_t len) -> unsigned int {
auto jpeg_queue = (QueueHandle_t)arg;
JpegChunk chunk = {
.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM),
.len = len
};
memcpy(chunk.data, data, len);
xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
return len;
}, jpeg_queue);
});
auto network = Board::GetInstance().GetNetwork();
auto http = network->CreateHttp(3);
// 构造multipart/form-data请求体
std::string boundary = "----ESP32_CAMERA_BOUNDARY";
// 配置HTTP客户端使用分块传输编码
http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
if (!explain_token_.empty()) {
http->SetHeader("Authorization", "Bearer " + explain_token_);
}
http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
http->SetHeader("Transfer-Encoding", "chunked");
if (!http->Open("POST", explain_url_)) {
ESP_LOGE(TAG, "Failed to connect to explain URL");
// Clear the queue
encoder_thread_.join();
JpegChunk chunk;
while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) {
if (chunk.data != nullptr) {
heap_caps_free(chunk.data);
} else {
break;
}
}
vQueueDelete(jpeg_queue);
throw std::runtime_error("Failed to connect to explain URL");
}
{
// 第一块question字段
std::string question_field;
question_field += "--" + boundary + "\r\n";
question_field += "Content-Disposition: form-data; name=\"question\"\r\n";
question_field += "\r\n";
question_field += question + "\r\n";
http->Write(question_field.c_str(), question_field.size());
}
{
// 第二块:文件字段头部
std::string file_header;
file_header += "--" + boundary + "\r\n";
file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n";
file_header += "Content-Type: image/jpeg\r\n";
file_header += "\r\n";
http->Write(file_header.c_str(), file_header.size());
}
// 第三块JPEG数据
size_t total_sent = 0;
while (true) {
JpegChunk chunk;
if (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) != pdPASS) {
ESP_LOGE(TAG, "Failed to receive JPEG chunk");
break;
}
if (chunk.data == nullptr) {
break; // The last chunk
}
http->Write((const char*)chunk.data, chunk.len);
total_sent += chunk.len;
heap_caps_free(chunk.data);
}
// Wait for the encoder thread to finish
encoder_thread_.join();
// 清理队列
vQueueDelete(jpeg_queue);
{
// 第四块multipart尾部
std::string multipart_footer;
multipart_footer += "\r\n--" + boundary + "--\r\n";
http->Write(multipart_footer.c_str(), multipart_footer.size());
}
// 结束块
http->Write("", 0);
if (http->GetStatusCode() != 200) {
ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode());
throw std::runtime_error("Failed to upload photo");
}
std::string result = http->ReadAll();
http->Close();
// Get remain task stack size
size_t remain_stack_size = uxTaskGetStackHighWaterMark(nullptr);
ESP_LOGI(TAG, "Explain image size=%dx%d, compressed size=%d, remain stack size=%d, question=%s\n%s",
fb_->width, fb_->height, total_sent, remain_stack_size, question.c_str(), result.c_str());
return result;
}
#include "esp32_camera.h"
#include "mcp_server.h"
#include "display.h"
#include "board.h"
#include "system_info.h"
#include "lvgl_display.h"
#include "jpg/image_to_jpeg.h"
#include <esp_log.h>
#include <esp_heap_caps.h>
#include <cstring>
#define TAG "Esp32Camera"
Esp32Camera::Esp32Camera(const camera_config_t& config) {
// camera init
esp_err_t err = esp_camera_init(&config); // 配置上面定义的参数
if (err != ESP_OK) {
ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
return;
}
sensor_t *s = esp_camera_sensor_get(); // 获取摄像头型号
if (s->id.PID == GC0308_PID) {
s->set_hmirror(s, 0); // 这里控制摄像头镜像 写1镜像 写0不镜像
}
}
Esp32Camera::~Esp32Camera() {
if (fb_) {
esp_camera_fb_return(fb_);
fb_ = nullptr;
}
esp_camera_deinit();
}
void Esp32Camera::SetExplainUrl(const std::string& url, const std::string& token) {
explain_url_ = url;
explain_token_ = token;
}
bool Esp32Camera::Capture() {
if (encoder_thread_.joinable()) {
encoder_thread_.join();
}
auto start_time = esp_timer_get_time();
int frames_to_get = 2;
// Try to get a stable frame
for (int i = 0; i < frames_to_get; i++) {
if (fb_ != nullptr) {
esp_camera_fb_return(fb_);
}
fb_ = esp_camera_fb_get();
if (fb_ == nullptr) {
ESP_LOGE(TAG, "Camera capture failed");
return false;
}
}
auto end_time = esp_timer_get_time();
ESP_LOGI(TAG, "Camera captured %d frames in %d ms", frames_to_get, int((end_time - start_time) / 1000));
// 显示预览图片
auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
if (display != nullptr) {
auto data = (uint8_t*)heap_caps_malloc(fb_->len, MALLOC_CAP_SPIRAM);
if (data == nullptr) {
ESP_LOGE(TAG, "Failed to allocate memory for preview image");
return false;
}
auto src = (uint16_t*)fb_->buf;
auto dst = (uint16_t*)data;
size_t pixel_count = fb_->len / 2;
for (size_t i = 0; i < pixel_count; i++) {
// 交换每个16位字内的字节
dst[i] = __builtin_bswap16(src[i]);
}
auto image = std::make_unique<LvglAllocatedImage>(data, fb_->len, fb_->width, fb_->height, fb_->width * 2, LV_COLOR_FORMAT_RGB565);
display->SetPreviewImage(std::move(image));
}
return true;
}
bool Esp32Camera::SetHMirror(bool enabled) {
sensor_t *s = esp_camera_sensor_get();
if (s == nullptr) {
ESP_LOGE(TAG, "Failed to get camera sensor");
return false;
}
esp_err_t err = s->set_hmirror(s, enabled);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set horizontal mirror: %d", err);
return false;
}
ESP_LOGI(TAG, "Camera horizontal mirror set to: %s", enabled ? "enabled" : "disabled");
return true;
}
bool Esp32Camera::SetVFlip(bool enabled) {
sensor_t *s = esp_camera_sensor_get();
if (s == nullptr) {
ESP_LOGE(TAG, "Failed to get camera sensor");
return false;
}
esp_err_t err = s->set_vflip(s, enabled);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set vertical flip: %d", err);
return false;
}
ESP_LOGI(TAG, "Camera vertical flip set to: %s", enabled ? "enabled" : "disabled");
return true;
}
/**
* @brief 将摄像头捕获的图像发送到远程服务器进行AI分析和解释
*
* 该函数将当前摄像头缓冲区中的图像编码为JPEG格式并通过HTTP POST请求
* 以multipart/form-data的形式发送到指定的解释服务器。服务器将根据提供的
* 问题对图像进行AI分析并返回结果。
*
* 实现特点:
* - 使用独立线程编码JPEG与主线程分离
* - 采用分块传输编码(chunked transfer encoding)优化内存使用
* - 通过队列机制实现编码线程和发送线程的数据同步
* - 支持设备ID、客户端ID和认证令牌的HTTP头部配置
*
* @param question 要向AI提出的关于图像的问题将作为表单字段发送
* @return std::string 服务器返回的JSON格式响应字符串
* 成功时包含AI分析结果失败时包含错误信息
* 格式示例:{"success": true, "result": "分析结果"}
* {"success": false, "message": "错误信息"}
*
* @note 调用此函数前必须先调用SetExplainUrl()设置服务器URL
* @note 函数会等待之前的编码线程完成后再开始新的处理
* @warning 如果摄像头缓冲区为空或网络连接失败,将返回错误信息
*/
std::string Esp32Camera::Explain(const std::string& question) {
if (explain_url_.empty()) {
throw std::runtime_error("Image explain URL or token is not set");
}
// 创建局部的 JPEG 队列, 40 entries is about to store 512 * 40 = 20480 bytes of JPEG data
QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk));
if (jpeg_queue == nullptr) {
ESP_LOGE(TAG, "Failed to create JPEG queue");
throw std::runtime_error("Failed to create JPEG queue");
}
// We spawn a thread to encode the image to JPEG using optimized encoder (cost about 500ms and 8KB SRAM)
encoder_thread_ = std::thread([this, jpeg_queue]() {
image_to_jpeg_cb(fb_->buf, fb_->len, fb_->width, fb_->height, fb_->format, 80,
[](void* arg, size_t index, const void* data, size_t len) -> size_t {
auto jpeg_queue = (QueueHandle_t)arg;
JpegChunk chunk = {
.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM),
.len = len
};
memcpy(chunk.data, data, len);
xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
return len;
}, jpeg_queue);
});
auto network = Board::GetInstance().GetNetwork();
auto http = network->CreateHttp(3);
// 构造multipart/form-data请求体
std::string boundary = "----ESP32_CAMERA_BOUNDARY";
// 配置HTTP客户端使用分块传输编码
http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
if (!explain_token_.empty()) {
http->SetHeader("Authorization", "Bearer " + explain_token_);
}
http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
http->SetHeader("Transfer-Encoding", "chunked");
if (!http->Open("POST", explain_url_)) {
ESP_LOGE(TAG, "Failed to connect to explain URL");
// Clear the queue
encoder_thread_.join();
JpegChunk chunk;
while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) {
if (chunk.data != nullptr) {
heap_caps_free(chunk.data);
} else {
break;
}
}
vQueueDelete(jpeg_queue);
throw std::runtime_error("Failed to connect to explain URL");
}
{
// 第一块question字段
std::string question_field;
question_field += "--" + boundary + "\r\n";
question_field += "Content-Disposition: form-data; name=\"question\"\r\n";
question_field += "\r\n";
question_field += question + "\r\n";
http->Write(question_field.c_str(), question_field.size());
}
{
// 第二块:文件字段头部
std::string file_header;
file_header += "--" + boundary + "\r\n";
file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n";
file_header += "Content-Type: image/jpeg\r\n";
file_header += "\r\n";
http->Write(file_header.c_str(), file_header.size());
}
// 第三块JPEG数据
size_t total_sent = 0;
while (true) {
JpegChunk chunk;
if (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) != pdPASS) {
ESP_LOGE(TAG, "Failed to receive JPEG chunk");
break;
}
if (chunk.data == nullptr) {
break; // The last chunk
}
http->Write((const char*)chunk.data, chunk.len);
total_sent += chunk.len;
heap_caps_free(chunk.data);
}
// Wait for the encoder thread to finish
encoder_thread_.join();
// 清理队列
vQueueDelete(jpeg_queue);
{
// 第四块multipart尾部
std::string multipart_footer;
multipart_footer += "\r\n--" + boundary + "--\r\n";
http->Write(multipart_footer.c_str(), multipart_footer.size());
}
// 结束块
http->Write("", 0);
if (http->GetStatusCode() != 200) {
ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode());
throw std::runtime_error("Failed to upload photo");
}
std::string result = http->ReadAll();
http->Close();
// Get remain task stack size
size_t remain_stack_size = uxTaskGetStackHighWaterMark(nullptr);
ESP_LOGI(TAG, "Explain image size=%dx%d, compressed size=%d, remain stack size=%d, question=%s\n%s",
fb_->width, fb_->height, total_sent, remain_stack_size, question.c_str(), result.c_str());
return result;
}

View File

@@ -1,38 +1,38 @@
#ifndef ESP32_CAMERA_H
#define ESP32_CAMERA_H
#include <esp_camera.h>
#include <lvgl.h>
#include <thread>
#include <memory>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include "camera.h"
struct JpegChunk {
uint8_t* data;
size_t len;
};
class Esp32Camera : public Camera {
private:
camera_fb_t* fb_ = nullptr;
std::string explain_url_;
std::string explain_token_;
std::thread encoder_thread_;
public:
Esp32Camera(const camera_config_t& config);
~Esp32Camera();
virtual void SetExplainUrl(const std::string& url, const std::string& token);
virtual bool Capture();
// 翻转控制函数
virtual bool SetHMirror(bool enabled) override;
virtual bool SetVFlip(bool enabled) override;
virtual std::string Explain(const std::string& question);
};
#ifndef ESP32_CAMERA_H
#define ESP32_CAMERA_H
#include <esp_camera.h>
#include <lvgl.h>
#include <thread>
#include <memory>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include "camera.h"
struct JpegChunk {
uint8_t* data;
size_t len;
};
class Esp32Camera : public Camera {
private:
camera_fb_t* fb_ = nullptr;
std::string explain_url_;
std::string explain_token_;
std::thread encoder_thread_;
public:
Esp32Camera(const camera_config_t& config);
~Esp32Camera();
virtual void SetExplainUrl(const std::string& url, const std::string& token);
virtual bool Capture();
// 翻转控制函数
virtual bool SetHMirror(bool enabled) override;
virtual bool SetVFlip(bool enabled) override;
virtual std::string Explain(const std::string& question);
};
#endif // ESP32_CAMERA_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,121 +1,151 @@
#ifndef ESP32_MUSIC_H
#define ESP32_MUSIC_H
#include <string>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <vector>
#include "music.h"
// MP3解码器支持
extern "C" {
#include "mp3dec.h"
}
// 音频数据块结构
struct AudioChunk {
uint8_t* data;
size_t size;
AudioChunk() : data(nullptr), size(0) {}
AudioChunk(uint8_t* d, size_t s) : data(d), size(s) {}
};
class Esp32Music : public Music {
public:
// 显示模式控制 - 移动到public区域
enum DisplayMode {
DISPLAY_MODE_SPECTRUM = 0, // 默认显示频谱
DISPLAY_MODE_LYRICS = 1 // 显示歌词
};
private:
std::string last_downloaded_data_;
std::string current_music_url_;
std::string current_song_name_;
bool song_name_displayed_;
// 歌词相关
std::string current_lyric_url_;
std::vector<std::pair<int, std::string>> lyrics_; // 时间戳和歌词文本
std::mutex lyrics_mutex_; // 保护lyrics_数组的互斥锁
std::atomic<int> current_lyric_index_;
std::thread lyric_thread_;
std::atomic<bool> is_lyric_running_;
std::atomic<DisplayMode> display_mode_;
std::atomic<bool> is_playing_;
std::atomic<bool> is_downloading_;
std::atomic<bool> is_paused_;
std::thread play_thread_;
std::thread download_thread_;
int64_t current_play_time_ms_; // 当前播放时间(毫秒)
int64_t last_frame_time_ms_; // 上一帧的时间戳
int total_frames_decoded_; // 已解码的帧数
// 音频缓冲区
std::queue<AudioChunk> audio_buffer_;
std::mutex buffer_mutex_;
std::condition_variable buffer_cv_;
size_t buffer_size_;
static constexpr size_t MAX_BUFFER_SIZE = 256 * 1024; // 256KB缓冲区降低以减少brownout风险
static constexpr size_t MIN_BUFFER_SIZE = 32 * 1024; // 32KB最小播放缓冲降低以减少brownout风险
// MP3解码器相关
HMP3Decoder mp3_decoder_;
MP3FrameInfo mp3_frame_info_;
bool mp3_decoder_initialized_;
// 私有方法
void DownloadAudioStream(const std::string& music_url);
void PlayAudioStream();
void ClearAudioBuffer();
bool InitializeMp3Decoder();
void CleanupMp3Decoder();
void ResetSampleRate(); // 重置采样率到原始值
// 歌词相关私有方法
bool DownloadLyrics(const std::string& lyric_url);
bool ParseLyrics(const std::string& lyric_content);
void LyricDisplayThread();
void UpdateLyricDisplay(int64_t current_time_ms);
// ID3标签处理
size_t SkipId3Tag(uint8_t* data, size_t size);
int16_t* final_pcm_data_fft = nullptr;
public:
Esp32Music();
~Esp32Music();
virtual bool Download(const std::string& song_name, const std::string& artist_name) override;
virtual std::string GetDownloadResult() override;
// 新增方法
virtual bool StartStreaming(const std::string& music_url) override;
virtual bool StopStreaming() override; // 停止流式播放
virtual size_t GetBufferSize() const override { return buffer_size_; }
virtual bool IsDownloading() const override { return is_downloading_; }
virtual bool IsPlaying() const override { return is_playing_; }
virtual bool IsPaused() const override { return is_paused_; }
virtual int16_t* GetAudioData() override { return final_pcm_data_fft; }
// 显示模式控制方法
void SetDisplayMode(DisplayMode mode);
DisplayMode GetDisplayMode() const { return display_mode_.load(); }
// MCP工具需要的方法
virtual bool PlaySong() override;
virtual bool SetVolume(int volume) override;
virtual bool StopSong() override;
virtual bool PauseSong() override;
virtual bool ResumeSong() override;
};
#ifndef ESP32_MUSIC_H
#define ESP32_MUSIC_H
#include <string>
#include <thread>
#include <atomic>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <vector>
#include "music.h"
// MP3解码器支持
extern "C" {
#include "mp3dec.h"
}
// 音频数据块结构
struct AudioChunk {
uint8_t* data;
size_t size;
AudioChunk() : data(nullptr), size(0) {}
AudioChunk(uint8_t* d, size_t s) : data(d), size(s) {}
};
// 歌曲信息结构
struct SongInfo {
std::string title;
std::string artist;
SongInfo() : title(""), artist("") {}
SongInfo(const std::string& t, const std::string& a) : title(t), artist(a) {}
};
class Esp32Music : public Music {
public:
// 显示模式控制 - 移动到public区域
enum DisplayMode {
DISPLAY_MODE_SPECTRUM = 0, // 默认显示频谱
DISPLAY_MODE_LYRICS = 1 // 显示歌词
};
private:
std::string last_downloaded_data_;
std::string current_music_url_;
std::string current_song_name_;
bool song_name_displayed_;
std::atomic<bool> stop_flag_{false}; // 停止播放标志位
// 歌词相关
std::string current_lyric_url_;
std::vector<std::pair<int, std::string>> lyrics_; // 时间戳和歌词文本
std::mutex lyrics_mutex_; // 保护lyrics_数组的互斥锁
std::atomic<int> current_lyric_index_;
std::thread lyric_thread_;
std::atomic<bool> is_lyric_running_;
std::atomic<DisplayMode> display_mode_;
std::atomic<bool> is_playing_;
std::atomic<bool> is_downloading_;
std::thread play_thread_;
std::thread download_thread_;
int64_t current_play_time_ms_; // 当前播放时间(毫秒)
int64_t last_frame_time_ms_; // 上一帧的时间戳
int total_frames_decoded_; // 已解码的帧数
int current_song_duration_seconds_; // 当前歌曲总时长(秒)
// 音频缓冲区
std::queue<AudioChunk> audio_buffer_;
std::mutex buffer_mutex_;
std::condition_variable buffer_cv_;
size_t buffer_size_;
static constexpr size_t MAX_BUFFER_SIZE = 256 * 1024; // 256KB缓冲区降低以减少brownout风险
static constexpr size_t MIN_BUFFER_SIZE = 32 * 1024; // 32KB最小播放缓冲降低以减少brownout风险
// MP3解码器相关
HMP3Decoder mp3_decoder_;
MP3FrameInfo mp3_frame_info_;
bool mp3_decoder_initialized_;
// 播放队列相关
std::vector<SongInfo> playlist_;
mutable std::mutex playlist_mutex_;
std::atomic<int> current_playlist_index_;
std::atomic<bool> playlist_mode_;
std::thread playlist_thread_;
// 私有方法
void DownloadAudioStream(const std::string& music_url);
void PlayAudioStream();
void ClearAudioBuffer();
bool InitializeMp3Decoder();
void CleanupMp3Decoder();
void ResetSampleRate(); // 重置采样率到原始值
// 歌词相关私有方法
bool DownloadLyrics(const std::string& lyric_url);
bool ParseLyrics(const std::string& lyric_content);
void LyricDisplayThread();
void UpdateLyricDisplay(int64_t current_time_ms);
// ID3标签处理
size_t SkipId3Tag(uint8_t* data, size_t size);
// 播放队列管理私有方法
void PlaylistManagerThread();
void PlayCurrentSong();
int16_t* final_pcm_data_fft = nullptr;
public:
Esp32Music();
~Esp32Music();
virtual bool Download(const std::string& song_name, const std::string& artist_name) override;
virtual std::string GetDownloadResult() override;
// 新增方法
virtual bool StartStreaming(const std::string& music_url) override;
virtual bool StopStreaming() override; // 停止流式播放
virtual size_t GetBufferSize() const override { return buffer_size_; }
virtual bool IsDownloading() const override { return is_downloading_; }
virtual int16_t* GetAudioData() override { return final_pcm_data_fft; }
// 显示模式控制方法
void SetDisplayMode(DisplayMode mode);
DisplayMode GetDisplayMode() const { return display_mode_.load(); }
// 音乐播放信息获取方法
virtual int GetCurrentSongDurationSeconds() const override { return current_song_duration_seconds_; }
virtual int GetCurrentPlayTimeSeconds() const override { return (int)(current_play_time_ms_ / 1000); }
virtual float GetPlayProgress() const override {
if (current_song_duration_seconds_ <= 0) return 0.0f;
return (float)(current_play_time_ms_ / 1000) / current_song_duration_seconds_ * 100.0f;
}
// 播放队列相关方法
virtual bool PlayPlaylist(const std::vector<SongInfo>& songs) override;
virtual bool NextSong() override;
virtual bool PreviousSong() override;
virtual void StopPlaylist() override;
virtual bool IsPlaylistMode() const override { return playlist_mode_.load(); }
virtual int GetCurrentPlaylistIndex() const override { return current_playlist_index_.load(); }
virtual size_t GetPlaylistSize() const override;
virtual SongInfo GetCurrentSong() const override;
};
#endif // ESP32_MUSIC_H

View File

@@ -1,35 +1,35 @@
#include "i2c_device.h"
#include <esp_log.h>
#define TAG "I2cDevice"
I2cDevice::I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr) {
i2c_device_config_t i2c_device_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = addr,
.scl_speed_hz = 400 * 1000,
.scl_wait_us = 0,
.flags = {
.disable_ack_check = 0,
},
};
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &i2c_device_cfg, &i2c_device_));
assert(i2c_device_ != NULL);
}
void I2cDevice::WriteReg(uint8_t reg, uint8_t value) {
uint8_t buffer[2] = {reg, value};
ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100));
}
uint8_t I2cDevice::ReadReg(uint8_t reg) {
uint8_t buffer[1];
ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, &reg, 1, buffer, 1, 100));
return buffer[0];
}
void I2cDevice::ReadRegs(uint8_t reg, uint8_t* buffer, size_t length) {
ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, &reg, 1, buffer, length, 100));
#include "i2c_device.h"
#include <esp_log.h>
#define TAG "I2cDevice"
I2cDevice::I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr) {
i2c_device_config_t i2c_device_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = addr,
.scl_speed_hz = 400 * 1000,
.scl_wait_us = 0,
.flags = {
.disable_ack_check = 0,
},
};
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &i2c_device_cfg, &i2c_device_));
assert(i2c_device_ != NULL);
}
void I2cDevice::WriteReg(uint8_t reg, uint8_t value) {
uint8_t buffer[2] = {reg, value};
ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100));
}
uint8_t I2cDevice::ReadReg(uint8_t reg) {
uint8_t buffer[1];
ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, &reg, 1, buffer, 1, 100));
return buffer[0];
}
void I2cDevice::ReadRegs(uint8_t reg, uint8_t* buffer, size_t length) {
ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, &reg, 1, buffer, length, 100));
}

View File

@@ -1,18 +1,18 @@
#ifndef I2C_DEVICE_H
#define I2C_DEVICE_H
#include <driver/i2c_master.h>
class I2cDevice {
public:
I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
protected:
i2c_master_dev_handle_t i2c_device_;
void WriteReg(uint8_t reg, uint8_t value);
uint8_t ReadReg(uint8_t reg);
void ReadRegs(uint8_t reg, uint8_t* buffer, size_t length);
};
#endif // I2C_DEVICE_H
#ifndef I2C_DEVICE_H
#define I2C_DEVICE_H
#include <driver/i2c_master.h>
class I2cDevice {
public:
I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
protected:
i2c_master_dev_handle_t i2c_device_;
void WriteReg(uint8_t reg, uint8_t value);
uint8_t ReadReg(uint8_t reg);
void ReadRegs(uint8_t reg, uint8_t* buffer, size_t length);
};
#endif // I2C_DEVICE_H

View File

@@ -1,52 +1,52 @@
#include "knob.h"
static const char* TAG = "Knob";
Knob::Knob(gpio_num_t pin_a, gpio_num_t pin_b) {
knob_config_t config = {
.default_direction = 0,
.gpio_encoder_a = static_cast<uint8_t>(pin_a),
.gpio_encoder_b = static_cast<uint8_t>(pin_b),
};
esp_err_t err = ESP_OK;
knob_handle_ = iot_knob_create(&config);
if (knob_handle_ == NULL) {
ESP_LOGE(TAG, "Failed to create knob instance");
return;
}
err = iot_knob_register_cb(knob_handle_, KNOB_LEFT, knob_callback, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register left callback: %s", esp_err_to_name(err));
return;
}
err = iot_knob_register_cb(knob_handle_, KNOB_RIGHT, knob_callback, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register right callback: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", pin_a, pin_b);
}
Knob::~Knob() {
if (knob_handle_ != NULL) {
iot_knob_delete(knob_handle_);
knob_handle_ = NULL;
}
}
void Knob::OnRotate(std::function<void(bool)> callback) {
on_rotate_ = callback;
}
void Knob::knob_callback(void* arg, void* data) {
Knob* knob = static_cast<Knob*>(data);
knob_event_t event = iot_knob_get_event(arg);
if (knob->on_rotate_) {
knob->on_rotate_(event == KNOB_RIGHT);
}
#include "knob.h"
static const char* TAG = "Knob";
Knob::Knob(gpio_num_t pin_a, gpio_num_t pin_b) {
knob_config_t config = {
.default_direction = 0,
.gpio_encoder_a = static_cast<uint8_t>(pin_a),
.gpio_encoder_b = static_cast<uint8_t>(pin_b),
};
esp_err_t err = ESP_OK;
knob_handle_ = iot_knob_create(&config);
if (knob_handle_ == NULL) {
ESP_LOGE(TAG, "Failed to create knob instance");
return;
}
err = iot_knob_register_cb(knob_handle_, KNOB_LEFT, knob_callback, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register left callback: %s", esp_err_to_name(err));
return;
}
err = iot_knob_register_cb(knob_handle_, KNOB_RIGHT, knob_callback, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register right callback: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", pin_a, pin_b);
}
Knob::~Knob() {
if (knob_handle_ != NULL) {
iot_knob_delete(knob_handle_);
knob_handle_ = NULL;
}
}
void Knob::OnRotate(std::function<void(bool)> callback) {
on_rotate_ = callback;
}
void Knob::knob_callback(void* arg, void* data) {
Knob* knob = static_cast<Knob*>(data);
knob_event_t event = iot_knob_get_event(arg);
if (knob->on_rotate_) {
knob->on_rotate_(event == KNOB_RIGHT);
}
}

View File

@@ -1,25 +1,25 @@
#ifndef KNOB_H_
#define KNOB_H_
#include <driver/gpio.h>
#include <functional>
#include <esp_log.h>
#include <iot_knob.h>
class Knob {
public:
Knob(gpio_num_t pin_a, gpio_num_t pin_b);
~Knob();
void OnRotate(std::function<void(bool)> callback);
private:
static void knob_callback(void* arg, void* data);
knob_handle_t knob_handle_;
gpio_num_t pin_a_;
gpio_num_t pin_b_;
std::function<void(bool)> on_rotate_;
};
#ifndef KNOB_H_
#define KNOB_H_
#include <driver/gpio.h>
#include <functional>
#include <esp_log.h>
#include <iot_knob.h>
class Knob {
public:
Knob(gpio_num_t pin_a, gpio_num_t pin_b);
~Knob();
void OnRotate(std::function<void(bool)> callback);
private:
static void knob_callback(void* arg, void* data);
knob_handle_t knob_handle_;
gpio_num_t pin_a_;
gpio_num_t pin_b_;
std::function<void(bool)> on_rotate_;
};
#endif // KNOB_H_

View File

@@ -1,44 +1,44 @@
#ifndef __LAMP_CONTROLLER_H__
#define __LAMP_CONTROLLER_H__
#include "mcp_server.h"
class LampController {
private:
bool power_ = false;
gpio_num_t gpio_num_;
public:
LampController(gpio_num_t gpio_num) : gpio_num_(gpio_num) {
gpio_config_t config = {
.pin_bit_mask = (1ULL << gpio_num_),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&config));
gpio_set_level(gpio_num_, 0);
auto& mcp_server = McpServer::GetInstance();
mcp_server.AddTool("self.lamp.get_state", "Get the power state of the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return power_ ? "{\"power\": true}" : "{\"power\": false}";
});
mcp_server.AddTool("self.lamp.turn_on", "Turn on the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
power_ = true;
gpio_set_level(gpio_num_, 1);
return true;
});
mcp_server.AddTool("self.lamp.turn_off", "Turn off the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
power_ = false;
gpio_set_level(gpio_num_, 0);
return true;
});
}
};
#endif // __LAMP_CONTROLLER_H__
#ifndef __LAMP_CONTROLLER_H__
#define __LAMP_CONTROLLER_H__
#include "mcp_server.h"
class LampController {
private:
bool power_ = false;
gpio_num_t gpio_num_;
public:
LampController(gpio_num_t gpio_num) : gpio_num_(gpio_num) {
gpio_config_t config = {
.pin_bit_mask = (1ULL << gpio_num_),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&config));
gpio_set_level(gpio_num_, 0);
auto& mcp_server = McpServer::GetInstance();
mcp_server.AddTool("self.lamp.get_state", "Get the power state of the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
return power_ ? "{\"power\": true}" : "{\"power\": false}";
});
mcp_server.AddTool("self.lamp.turn_on", "Turn on the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
power_ = true;
gpio_set_level(gpio_num_, 1);
return true;
});
mcp_server.AddTool("self.lamp.turn_off", "Turn off the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
power_ = false;
gpio_set_level(gpio_num_, 0);
return true;
});
}
};
#endif // __LAMP_CONTROLLER_H__

View File

@@ -1,197 +1,197 @@
#include "ml307_board.h"
#include "application.h"
#include "display.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_timer.h>
#include <font_awesome.h>
#include <opus_encoder.h>
static const char *TAG = "Ml307Board";
Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin) : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin) {
}
std::string Ml307Board::GetBoardType() {
return "ml307";
}
void Ml307Board::StartNetwork() {
auto& application = Application::GetInstance();
auto display = Board::GetInstance().GetDisplay();
display->SetStatus(Lang::Strings::DETECTING_MODULE);
while (true) {
modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600);
if (modem_ != nullptr) {
break;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
modem_->OnNetworkStateChanged([this, &application](bool network_ready) {
if (network_ready) {
ESP_LOGI(TAG, "Network is ready");
} else {
ESP_LOGE(TAG, "Network is down");
auto device_state = application.GetDeviceState();
if (device_state == kDeviceStateListening || device_state == kDeviceStateSpeaking) {
application.Schedule([this, &application]() {
application.SetDeviceState(kDeviceStateIdle);
});
}
}
});
// Wait for network ready
display->SetStatus(Lang::Strings::REGISTERING_NETWORK);
while (true) {
auto result = modem_->WaitForNetworkReady();
if (result == NetworkStatus::ErrorInsertPin) {
application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN);
} else if (result == NetworkStatus::ErrorRegistrationDenied) {
application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG);
} else {
break;
}
vTaskDelay(pdMS_TO_TICKS(10000));
}
// Print the ML307 modem information
std::string module_revision = modem_->GetModuleRevision();
std::string imei = modem_->GetImei();
std::string iccid = modem_->GetIccid();
ESP_LOGI(TAG, "ML307 Revision: %s", module_revision.c_str());
ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str());
ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str());
}
NetworkInterface* Ml307Board::GetNetwork() {
return modem_.get();
}
const char* Ml307Board::GetNetworkStateIcon() {
if (modem_ == nullptr || !modem_->network_ready()) {
return FONT_AWESOME_SIGNAL_OFF;
}
int csq = modem_->GetCsq();
if (csq == -1) {
return FONT_AWESOME_SIGNAL_OFF;
} else if (csq >= 0 && csq <= 14) {
return FONT_AWESOME_SIGNAL_WEAK;
} else if (csq >= 15 && csq <= 19) {
return FONT_AWESOME_SIGNAL_FAIR;
} else if (csq >= 20 && csq <= 24) {
return FONT_AWESOME_SIGNAL_GOOD;
} else if (csq >= 25 && csq <= 31) {
return FONT_AWESOME_SIGNAL_STRONG;
}
ESP_LOGW(TAG, "Invalid CSQ: %d", csq);
return FONT_AWESOME_SIGNAL_OFF;
}
std::string Ml307Board::GetBoardJson() {
// Set the board type for OTA
std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\",");
board_json += "\"name\":\"" BOARD_NAME "\",";
board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\",";
board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\",";
board_json += "\"csq\":\"" + std::to_string(modem_->GetCsq()) + "\",";
board_json += "\"imei\":\"" + modem_->GetImei() + "\",";
board_json += "\"iccid\":\"" + modem_->GetIccid() + "\",";
board_json += "\"cereg\":" + modem_->GetRegistrationState().ToString() + "}";
return board_json;
}
void Ml307Board::SetPowerSaveMode(bool enabled) {
// TODO: Implement power save mode for ML307
}
std::string Ml307Board::GetDeviceStatusJson() {
/*
* 返回设备状态JSON
*
* 返回的JSON结构如下
* {
* "audio_speaker": {
* "volume": 70
* },
* "screen": {
* "brightness": 100,
* "theme": "light"
* },
* "battery": {
* "level": 50,
* "charging": true
* },
* "network": {
* "type": "cellular",
* "carrier": "CHINA MOBILE",
* "csq": 10
* }
* }
*/
auto& board = Board::GetInstance();
auto root = cJSON_CreateObject();
// Audio speaker
auto audio_speaker = cJSON_CreateObject();
auto audio_codec = board.GetAudioCodec();
if (audio_codec) {
cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
}
cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
// Screen brightness
auto backlight = board.GetBacklight();
auto screen = cJSON_CreateObject();
if (backlight) {
cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
}
auto display = board.GetDisplay();
if (display && display->height() > 64) { // For LCD display only
auto theme = display->GetTheme();
if (theme != nullptr) {
cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
}
}
cJSON_AddItemToObject(root, "screen", screen);
// Battery
int battery_level = 0;
bool charging = false;
bool discharging = false;
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
cJSON* battery = cJSON_CreateObject();
cJSON_AddNumberToObject(battery, "level", battery_level);
cJSON_AddBoolToObject(battery, "charging", charging);
cJSON_AddItemToObject(root, "battery", battery);
}
// Network
auto network = cJSON_CreateObject();
cJSON_AddStringToObject(network, "type", "cellular");
cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str());
int csq = modem_->GetCsq();
if (csq == -1) {
cJSON_AddStringToObject(network, "signal", "unknown");
} else if (csq >= 0 && csq <= 14) {
cJSON_AddStringToObject(network, "signal", "very weak");
} else if (csq >= 15 && csq <= 19) {
cJSON_AddStringToObject(network, "signal", "weak");
} else if (csq >= 20 && csq <= 24) {
cJSON_AddStringToObject(network, "signal", "medium");
} else if (csq >= 25 && csq <= 31) {
cJSON_AddStringToObject(network, "signal", "strong");
}
cJSON_AddItemToObject(root, "network", network);
auto json_str = cJSON_PrintUnformatted(root);
std::string json(json_str);
cJSON_free(json_str);
cJSON_Delete(root);
return json;
}
#include "ml307_board.h"
#include "application.h"
#include "display.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_timer.h>
#include <font_awesome.h>
#include <opus_encoder.h>
static const char *TAG = "Ml307Board";
Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin) : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin) {
}
std::string Ml307Board::GetBoardType() {
return "ml307";
}
void Ml307Board::StartNetwork() {
auto& application = Application::GetInstance();
auto display = Board::GetInstance().GetDisplay();
display->SetStatus(Lang::Strings::DETECTING_MODULE);
while (true) {
modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600);
if (modem_ != nullptr) {
break;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
modem_->OnNetworkStateChanged([this, &application](bool network_ready) {
if (network_ready) {
ESP_LOGI(TAG, "Network is ready");
} else {
ESP_LOGE(TAG, "Network is down");
auto device_state = application.GetDeviceState();
if (device_state == kDeviceStateListening || device_state == kDeviceStateSpeaking) {
application.Schedule([this, &application]() {
application.SetDeviceState(kDeviceStateIdle);
});
}
}
});
// Wait for network ready
display->SetStatus(Lang::Strings::REGISTERING_NETWORK);
while (true) {
auto result = modem_->WaitForNetworkReady();
if (result == NetworkStatus::ErrorInsertPin) {
application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN);
} else if (result == NetworkStatus::ErrorRegistrationDenied) {
application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG);
} else {
break;
}
vTaskDelay(pdMS_TO_TICKS(10000));
}
// Print the ML307 modem information
std::string module_revision = modem_->GetModuleRevision();
std::string imei = modem_->GetImei();
std::string iccid = modem_->GetIccid();
ESP_LOGI(TAG, "ML307 Revision: %s", module_revision.c_str());
ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str());
ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str());
}
NetworkInterface* Ml307Board::GetNetwork() {
return modem_.get();
}
const char* Ml307Board::GetNetworkStateIcon() {
if (modem_ == nullptr || !modem_->network_ready()) {
return FONT_AWESOME_SIGNAL_OFF;
}
int csq = modem_->GetCsq();
if (csq == -1) {
return FONT_AWESOME_SIGNAL_OFF;
} else if (csq >= 0 && csq <= 14) {
return FONT_AWESOME_SIGNAL_WEAK;
} else if (csq >= 15 && csq <= 19) {
return FONT_AWESOME_SIGNAL_FAIR;
} else if (csq >= 20 && csq <= 24) {
return FONT_AWESOME_SIGNAL_GOOD;
} else if (csq >= 25 && csq <= 31) {
return FONT_AWESOME_SIGNAL_STRONG;
}
ESP_LOGW(TAG, "Invalid CSQ: %d", csq);
return FONT_AWESOME_SIGNAL_OFF;
}
std::string Ml307Board::GetBoardJson() {
// Set the board type for OTA
std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\",");
board_json += "\"name\":\"" BOARD_NAME "\",";
board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\",";
board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\",";
board_json += "\"csq\":\"" + std::to_string(modem_->GetCsq()) + "\",";
board_json += "\"imei\":\"" + modem_->GetImei() + "\",";
board_json += "\"iccid\":\"" + modem_->GetIccid() + "\",";
board_json += "\"cereg\":" + modem_->GetRegistrationState().ToString() + "}";
return board_json;
}
void Ml307Board::SetPowerSaveMode(bool enabled) {
// TODO: Implement power save mode for ML307
}
std::string Ml307Board::GetDeviceStatusJson() {
/*
* 返回设备状态JSON
*
* 返回的JSON结构如下
* {
* "audio_speaker": {
* "volume": 70
* },
* "screen": {
* "brightness": 100,
* "theme": "light"
* },
* "battery": {
* "level": 50,
* "charging": true
* },
* "network": {
* "type": "cellular",
* "carrier": "CHINA MOBILE",
* "csq": 10
* }
* }
*/
auto& board = Board::GetInstance();
auto root = cJSON_CreateObject();
// Audio speaker
auto audio_speaker = cJSON_CreateObject();
auto audio_codec = board.GetAudioCodec();
if (audio_codec) {
cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
}
cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
// Screen brightness
auto backlight = board.GetBacklight();
auto screen = cJSON_CreateObject();
if (backlight) {
cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
}
auto display = board.GetDisplay();
if (display && display->height() > 64) { // For LCD display only
auto theme = display->GetTheme();
if (theme != nullptr) {
cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
}
}
cJSON_AddItemToObject(root, "screen", screen);
// Battery
int battery_level = 0;
bool charging = false;
bool discharging = false;
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
cJSON* battery = cJSON_CreateObject();
cJSON_AddNumberToObject(battery, "level", battery_level);
cJSON_AddBoolToObject(battery, "charging", charging);
cJSON_AddItemToObject(root, "battery", battery);
}
// Network
auto network = cJSON_CreateObject();
cJSON_AddStringToObject(network, "type", "cellular");
cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str());
int csq = modem_->GetCsq();
if (csq == -1) {
cJSON_AddStringToObject(network, "signal", "unknown");
} else if (csq >= 0 && csq <= 14) {
cJSON_AddStringToObject(network, "signal", "very weak");
} else if (csq >= 15 && csq <= 19) {
cJSON_AddStringToObject(network, "signal", "weak");
} else if (csq >= 20 && csq <= 24) {
cJSON_AddStringToObject(network, "signal", "medium");
} else if (csq >= 25 && csq <= 31) {
cJSON_AddStringToObject(network, "signal", "strong");
}
cJSON_AddItemToObject(root, "network", network);
auto json_str = cJSON_PrintUnformatted(root);
std::string json(json_str);
cJSON_free(json_str);
cJSON_Delete(root);
return json;
}

View File

@@ -1,29 +1,29 @@
#ifndef ML307_BOARD_H
#define ML307_BOARD_H
#include <memory>
#include <at_modem.h>
#include "board.h"
class Ml307Board : public Board {
protected:
std::unique_ptr<AtModem> modem_;
gpio_num_t tx_pin_;
gpio_num_t rx_pin_;
gpio_num_t dtr_pin_;
virtual std::string GetBoardJson() override;
public:
Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC);
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual NetworkInterface* GetNetwork() override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
virtual std::string GetDeviceStatusJson() override;
};
#endif // ML307_BOARD_H
#ifndef ML307_BOARD_H
#define ML307_BOARD_H
#include <memory>
#include <at_modem.h>
#include "board.h"
class Ml307Board : public Board {
protected:
std::unique_ptr<AtModem> modem_;
gpio_num_t tx_pin_;
gpio_num_t rx_pin_;
gpio_num_t dtr_pin_;
virtual std::string GetBoardJson() override;
public:
Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC);
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual NetworkInterface* GetNetwork() override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
virtual std::string GetDeviceStatusJson() override;
};
#endif // ML307_BOARD_H

View File

@@ -2,6 +2,10 @@
#define MUSIC_H
#include <string>
#include <vector>
// 前向声明
struct SongInfo;
class Music {
public:
@@ -15,16 +19,22 @@ public:
virtual bool StopStreaming() = 0; // 停止流式播放
virtual size_t GetBufferSize() const = 0;
virtual bool IsDownloading() const = 0;
virtual bool IsPlaying() const = 0;
virtual bool IsPaused() const = 0;
virtual int16_t* GetAudioData() = 0;
// MCP工具需要的方法
virtual bool PlaySong() = 0;
virtual bool SetVolume(int volume) = 0;
virtual bool StopSong() = 0;
virtual bool PauseSong() = 0;
virtual bool ResumeSong() = 0;
// 音乐播放信息获取方法
virtual int GetCurrentSongDurationSeconds() const = 0;
virtual int GetCurrentPlayTimeSeconds() const = 0;
virtual float GetPlayProgress() const = 0;
// 播放队列相关方法
virtual bool PlayPlaylist(const std::vector<SongInfo>& songs) = 0;
virtual bool NextSong() = 0;
virtual bool PreviousSong() = 0;
virtual void StopPlaylist() = 0;
virtual bool IsPlaylistMode() const = 0;
virtual int GetCurrentPlaylistIndex() const = 0;
virtual size_t GetPlaylistSize() const = 0;
virtual SongInfo GetCurrentSong() const = 0;
};
#endif // MUSIC_H

View File

@@ -1,132 +1,132 @@
#include "power_save_timer.h"
#include "application.h"
#include "settings.h"
#include <esp_log.h>
#define TAG "PowerSaveTimer"
PowerSaveTimer::PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep, int seconds_to_shutdown)
: cpu_max_freq_(cpu_max_freq), seconds_to_sleep_(seconds_to_sleep), seconds_to_shutdown_(seconds_to_shutdown) {
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto self = static_cast<PowerSaveTimer*>(arg);
self->PowerSaveCheck();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "power_save_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &power_save_timer_));
}
PowerSaveTimer::~PowerSaveTimer() {
esp_timer_stop(power_save_timer_);
esp_timer_delete(power_save_timer_);
}
void PowerSaveTimer::SetEnabled(bool enabled) {
if (enabled && !enabled_) {
Settings settings("wifi", false);
if (!settings.GetBool("sleep_mode", true)) {
ESP_LOGI(TAG, "Power save timer is disabled by settings");
return;
}
ticks_ = 0;
enabled_ = enabled;
ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000));
ESP_LOGI(TAG, "Power save timer enabled");
} else if (!enabled && enabled_) {
ESP_ERROR_CHECK(esp_timer_stop(power_save_timer_));
enabled_ = enabled;
WakeUp();
ESP_LOGI(TAG, "Power save timer disabled");
}
}
void PowerSaveTimer::OnEnterSleepMode(std::function<void()> callback) {
on_enter_sleep_mode_ = callback;
}
void PowerSaveTimer::OnExitSleepMode(std::function<void()> callback) {
on_exit_sleep_mode_ = callback;
}
void PowerSaveTimer::OnShutdownRequest(std::function<void()> callback) {
on_shutdown_request_ = callback;
}
void PowerSaveTimer::PowerSaveCheck() {
auto& app = Application::GetInstance();
if (!in_sleep_mode_ && !app.CanEnterSleepMode()) {
ticks_ = 0;
return;
}
ticks_++;
if (seconds_to_sleep_ != -1 && ticks_ >= seconds_to_sleep_) {
if (!in_sleep_mode_) {
ESP_LOGI(TAG, "Enabling power save mode");
in_sleep_mode_ = true;
if (on_enter_sleep_mode_) {
on_enter_sleep_mode_();
}
if (cpu_max_freq_ != -1) {
// Disable wake word detection
auto& audio_service = app.GetAudioService();
is_wake_word_running_ = audio_service.IsWakeWordRunning();
if (is_wake_word_running_) {
audio_service.EnableWakeWordDetection(false);
vTaskDelay(pdMS_TO_TICKS(100));
}
// Disable audio input
auto codec = Board::GetInstance().GetAudioCodec();
if (codec) {
codec->EnableInput(false);
}
esp_pm_config_t pm_config = {
.max_freq_mhz = cpu_max_freq_,
.min_freq_mhz = 40,
.light_sleep_enable = true,
};
esp_pm_configure(&pm_config);
}
}
}
if (seconds_to_shutdown_ != -1 && ticks_ >= seconds_to_shutdown_ && on_shutdown_request_) {
on_shutdown_request_();
}
}
void PowerSaveTimer::WakeUp() {
ticks_ = 0;
if (in_sleep_mode_) {
ESP_LOGI(TAG, "Exiting power save mode");
in_sleep_mode_ = false;
if (cpu_max_freq_ != -1) {
esp_pm_config_t pm_config = {
.max_freq_mhz = cpu_max_freq_,
.min_freq_mhz = cpu_max_freq_,
.light_sleep_enable = false,
};
esp_pm_configure(&pm_config);
// Enable wake word detection
auto& app = Application::GetInstance();
auto& audio_service = app.GetAudioService();
if (is_wake_word_running_) {
audio_service.EnableWakeWordDetection(true);
}
}
if (on_exit_sleep_mode_) {
on_exit_sleep_mode_();
}
}
}
#include "power_save_timer.h"
#include "application.h"
#include "settings.h"
#include <esp_log.h>
#define TAG "PowerSaveTimer"
PowerSaveTimer::PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep, int seconds_to_shutdown)
: cpu_max_freq_(cpu_max_freq), seconds_to_sleep_(seconds_to_sleep), seconds_to_shutdown_(seconds_to_shutdown) {
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto self = static_cast<PowerSaveTimer*>(arg);
self->PowerSaveCheck();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "power_save_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &power_save_timer_));
}
PowerSaveTimer::~PowerSaveTimer() {
esp_timer_stop(power_save_timer_);
esp_timer_delete(power_save_timer_);
}
void PowerSaveTimer::SetEnabled(bool enabled) {
if (enabled && !enabled_) {
Settings settings("wifi", false);
if (!settings.GetBool("sleep_mode", true)) {
ESP_LOGI(TAG, "Power save timer is disabled by settings");
return;
}
ticks_ = 0;
enabled_ = enabled;
ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000));
ESP_LOGI(TAG, "Power save timer enabled");
} else if (!enabled && enabled_) {
ESP_ERROR_CHECK(esp_timer_stop(power_save_timer_));
enabled_ = enabled;
WakeUp();
ESP_LOGI(TAG, "Power save timer disabled");
}
}
void PowerSaveTimer::OnEnterSleepMode(std::function<void()> callback) {
on_enter_sleep_mode_ = callback;
}
void PowerSaveTimer::OnExitSleepMode(std::function<void()> callback) {
on_exit_sleep_mode_ = callback;
}
void PowerSaveTimer::OnShutdownRequest(std::function<void()> callback) {
on_shutdown_request_ = callback;
}
void PowerSaveTimer::PowerSaveCheck() {
auto& app = Application::GetInstance();
if (!in_sleep_mode_ && !app.CanEnterSleepMode()) {
ticks_ = 0;
return;
}
ticks_++;
if (seconds_to_sleep_ != -1 && ticks_ >= seconds_to_sleep_) {
if (!in_sleep_mode_) {
ESP_LOGI(TAG, "Enabling power save mode");
in_sleep_mode_ = true;
if (on_enter_sleep_mode_) {
on_enter_sleep_mode_();
}
if (cpu_max_freq_ != -1) {
// Disable wake word detection
auto& audio_service = app.GetAudioService();
is_wake_word_running_ = audio_service.IsWakeWordRunning();
if (is_wake_word_running_) {
audio_service.EnableWakeWordDetection(false);
vTaskDelay(pdMS_TO_TICKS(100));
}
// Disable audio input
auto codec = Board::GetInstance().GetAudioCodec();
if (codec) {
codec->EnableInput(false);
}
esp_pm_config_t pm_config = {
.max_freq_mhz = cpu_max_freq_,
.min_freq_mhz = 40,
.light_sleep_enable = true,
};
esp_pm_configure(&pm_config);
}
}
}
if (seconds_to_shutdown_ != -1 && ticks_ >= seconds_to_shutdown_ && on_shutdown_request_) {
on_shutdown_request_();
}
}
void PowerSaveTimer::WakeUp() {
ticks_ = 0;
if (in_sleep_mode_) {
ESP_LOGI(TAG, "Exiting power save mode");
in_sleep_mode_ = false;
if (cpu_max_freq_ != -1) {
esp_pm_config_t pm_config = {
.max_freq_mhz = cpu_max_freq_,
.min_freq_mhz = cpu_max_freq_,
.light_sleep_enable = false,
};
esp_pm_configure(&pm_config);
// Enable wake word detection
auto& app = Application::GetInstance();
auto& audio_service = app.GetAudioService();
if (is_wake_word_running_) {
audio_service.EnableWakeWordDetection(true);
}
}
if (on_exit_sleep_mode_) {
on_exit_sleep_mode_();
}
}
}

View File

@@ -1,34 +1,34 @@
#pragma once
#include <functional>
#include <esp_timer.h>
#include <esp_pm.h>
class PowerSaveTimer {
public:
PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep = 20, int seconds_to_shutdown = -1);
~PowerSaveTimer();
void SetEnabled(bool enabled);
void OnEnterSleepMode(std::function<void()> callback);
void OnExitSleepMode(std::function<void()> callback);
void OnShutdownRequest(std::function<void()> callback);
void WakeUp();
private:
void PowerSaveCheck();
esp_timer_handle_t power_save_timer_ = nullptr;
bool enabled_ = false;
bool in_sleep_mode_ = false;
bool is_wake_word_running_ = false;
int ticks_ = 0;
int cpu_max_freq_;
int seconds_to_sleep_;
int seconds_to_shutdown_;
std::function<void()> on_enter_sleep_mode_;
std::function<void()> on_exit_sleep_mode_;
std::function<void()> on_shutdown_request_;
};
#pragma once
#include <functional>
#include <esp_timer.h>
#include <esp_pm.h>
class PowerSaveTimer {
public:
PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep = 20, int seconds_to_shutdown = -1);
~PowerSaveTimer();
void SetEnabled(bool enabled);
void OnEnterSleepMode(std::function<void()> callback);
void OnExitSleepMode(std::function<void()> callback);
void OnShutdownRequest(std::function<void()> callback);
void WakeUp();
private:
void PowerSaveCheck();
esp_timer_handle_t power_save_timer_ = nullptr;
bool enabled_ = false;
bool in_sleep_mode_ = false;
bool is_wake_word_running_ = false;
int ticks_ = 0;
int cpu_max_freq_;
int seconds_to_sleep_;
int seconds_to_shutdown_;
std::function<void()> on_enter_sleep_mode_;
std::function<void()> on_exit_sleep_mode_;
std::function<void()> on_shutdown_request_;
};

View File

@@ -1,57 +1,57 @@
#include "press_to_talk_mcp_tool.h"
#include <esp_log.h>
static const char* TAG = "PressToTalkMcpTool";
PressToTalkMcpTool::PressToTalkMcpTool()
: press_to_talk_enabled_(false) {
}
void PressToTalkMcpTool::Initialize() {
// 从设置中读取当前状态
Settings settings("vendor");
press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0;
// 注册MCP工具
auto& mcp_server = McpServer::GetInstance();
mcp_server.AddTool("self.set_press_to_talk",
"Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n"
"The mode can be `press_to_talk` or `click_to_talk`.",
PropertyList({
Property("mode", kPropertyTypeString)
}),
[this](const PropertyList& properties) -> ReturnValue {
return HandleSetPressToTalk(properties);
});
ESP_LOGI(TAG, "PressToTalkMcpTool initialized, current mode: %s",
press_to_talk_enabled_ ? "press_to_talk" : "click_to_talk");
}
bool PressToTalkMcpTool::IsPressToTalkEnabled() const {
return press_to_talk_enabled_;
}
ReturnValue PressToTalkMcpTool::HandleSetPressToTalk(const PropertyList& properties) {
auto mode = properties["mode"].value<std::string>();
if (mode == "press_to_talk") {
SetPressToTalkEnabled(true);
ESP_LOGI(TAG, "Switched to press to talk mode");
return true;
} else if (mode == "click_to_talk") {
SetPressToTalkEnabled(false);
ESP_LOGI(TAG, "Switched to click to talk mode");
return true;
}
throw std::runtime_error("Invalid mode: " + mode);
}
void PressToTalkMcpTool::SetPressToTalkEnabled(bool enabled) {
press_to_talk_enabled_ = enabled;
Settings settings("vendor", true);
settings.SetInt("press_to_talk", enabled ? 1 : 0);
ESP_LOGI(TAG, "Press to talk enabled: %d", enabled);
#include "press_to_talk_mcp_tool.h"
#include <esp_log.h>
static const char* TAG = "PressToTalkMcpTool";
PressToTalkMcpTool::PressToTalkMcpTool()
: press_to_talk_enabled_(false) {
}
void PressToTalkMcpTool::Initialize() {
// 从设置中读取当前状态
Settings settings("vendor");
press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0;
// 注册MCP工具
auto& mcp_server = McpServer::GetInstance();
mcp_server.AddTool("self.set_press_to_talk",
"Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n"
"The mode can be `press_to_talk` or `click_to_talk`.",
PropertyList({
Property("mode", kPropertyTypeString)
}),
[this](const PropertyList& properties) -> ReturnValue {
return HandleSetPressToTalk(properties);
});
ESP_LOGI(TAG, "PressToTalkMcpTool initialized, current mode: %s",
press_to_talk_enabled_ ? "press_to_talk" : "click_to_talk");
}
bool PressToTalkMcpTool::IsPressToTalkEnabled() const {
return press_to_talk_enabled_;
}
ReturnValue PressToTalkMcpTool::HandleSetPressToTalk(const PropertyList& properties) {
auto mode = properties["mode"].value<std::string>();
if (mode == "press_to_talk") {
SetPressToTalkEnabled(true);
ESP_LOGI(TAG, "Switched to press to talk mode");
return true;
} else if (mode == "click_to_talk") {
SetPressToTalkEnabled(false);
ESP_LOGI(TAG, "Switched to click to talk mode");
return true;
}
throw std::runtime_error("Invalid mode: " + mode);
}
void PressToTalkMcpTool::SetPressToTalkEnabled(bool enabled) {
press_to_talk_enabled_ = enabled;
Settings settings("vendor", true);
settings.SetInt("press_to_talk", enabled ? 1 : 0);
ESP_LOGI(TAG, "Press to talk enabled: %d", enabled);
}

View File

@@ -1,29 +1,29 @@
#ifndef PRESS_TO_TALK_MCP_TOOL_H
#define PRESS_TO_TALK_MCP_TOOL_H
#include "mcp_server.h"
#include "settings.h"
// 可复用的按键说话模式MCP工具类
class PressToTalkMcpTool {
private:
bool press_to_talk_enabled_;
public:
PressToTalkMcpTool();
// 初始化工具注册到MCP服务器
void Initialize();
// 获取当前按键说话模式状态
bool IsPressToTalkEnabled() const;
private:
// MCP工具的回调函数
ReturnValue HandleSetPressToTalk(const PropertyList& properties);
// 内部方法设置press to talk状态并保存到设置
void SetPressToTalkEnabled(bool enabled);
};
#ifndef PRESS_TO_TALK_MCP_TOOL_H
#define PRESS_TO_TALK_MCP_TOOL_H
#include "mcp_server.h"
#include "settings.h"
// 可复用的按键说话模式MCP工具类
class PressToTalkMcpTool {
private:
bool press_to_talk_enabled_;
public:
PressToTalkMcpTool();
// 初始化工具注册到MCP服务器
void Initialize();
// 获取当前按键说话模式状态
bool IsPressToTalkEnabled() const;
private:
// MCP工具的回调函数
ReturnValue HandleSetPressToTalk(const PropertyList& properties);
// 内部方法设置press to talk状态并保存到设置
void SetPressToTalkEnabled(bool enabled);
};
#endif // PRESS_TO_TALK_MCP_TOOL_H

View File

@@ -1,133 +1,133 @@
#include "sleep_timer.h"
#include "application.h"
#include "board.h"
#include "display.h"
#include "settings.h"
#include <esp_log.h>
#include <esp_sleep.h>
#include <esp_lvgl_port.h>
#define TAG "SleepTimer"
SleepTimer::SleepTimer(int seconds_to_light_sleep, int seconds_to_deep_sleep)
: seconds_to_light_sleep_(seconds_to_light_sleep), seconds_to_deep_sleep_(seconds_to_deep_sleep) {
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto self = static_cast<SleepTimer*>(arg);
self->CheckTimer();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "sleep_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &sleep_timer_));
}
SleepTimer::~SleepTimer() {
esp_timer_stop(sleep_timer_);
esp_timer_delete(sleep_timer_);
}
void SleepTimer::SetEnabled(bool enabled) {
if (enabled && !enabled_) {
Settings settings("wifi", false);
if (!settings.GetBool("sleep_mode", true)) {
ESP_LOGI(TAG, "Power save timer is disabled by settings");
return;
}
ticks_ = 0;
enabled_ = enabled;
ESP_ERROR_CHECK(esp_timer_start_periodic(sleep_timer_, 1000000));
ESP_LOGI(TAG, "Sleep timer enabled");
} else if (!enabled && enabled_) {
ESP_ERROR_CHECK(esp_timer_stop(sleep_timer_));
enabled_ = enabled;
WakeUp();
ESP_LOGI(TAG, "Sleep timer disabled");
}
}
void SleepTimer::OnEnterLightSleepMode(std::function<void()> callback) {
on_enter_light_sleep_mode_ = callback;
}
void SleepTimer::OnExitLightSleepMode(std::function<void()> callback) {
on_exit_light_sleep_mode_ = callback;
}
void SleepTimer::OnEnterDeepSleepMode(std::function<void()> callback) {
on_enter_deep_sleep_mode_ = callback;
}
void SleepTimer::CheckTimer() {
auto& app = Application::GetInstance();
if (!app.CanEnterSleepMode()) {
ticks_ = 0;
return;
}
ticks_++;
if (seconds_to_light_sleep_ != -1 && ticks_ >= seconds_to_light_sleep_) {
if (!in_light_sleep_mode_) {
in_light_sleep_mode_ = true;
if (on_enter_light_sleep_mode_) {
on_enter_light_sleep_mode_();
}
auto& audio_service = app.GetAudioService();
bool is_wake_word_running = audio_service.IsWakeWordRunning();
if (is_wake_word_running) {
audio_service.EnableWakeWordDetection(false);
vTaskDelay(pdMS_TO_TICKS(100));
}
app.Schedule([this, &app]() {
while (in_light_sleep_mode_) {
auto& board = Board::GetInstance();
board.GetDisplay()->UpdateStatusBar(true);
lv_refr_now(nullptr);
lvgl_port_stop();
// 配置timer唤醒源30秒后自动唤醒
esp_sleep_enable_timer_wakeup(30 * 1000000);
// 进入light sleep模式
esp_light_sleep_start();
lvgl_port_resume();
auto wakeup_reason = esp_sleep_get_wakeup_cause();
ESP_LOGI(TAG, "Wake up from light sleep, wakeup_reason: %d", wakeup_reason);
if (wakeup_reason != ESP_SLEEP_WAKEUP_TIMER) {
break;
}
}
WakeUp();
});
if (is_wake_word_running) {
audio_service.EnableWakeWordDetection(true);
}
}
}
if (seconds_to_deep_sleep_ != -1 && ticks_ >= seconds_to_deep_sleep_) {
if (on_enter_deep_sleep_mode_) {
on_enter_deep_sleep_mode_();
}
esp_deep_sleep_start();
}
}
void SleepTimer::WakeUp() {
ticks_ = 0;
if (in_light_sleep_mode_) {
in_light_sleep_mode_ = false;
if (on_exit_light_sleep_mode_) {
on_exit_light_sleep_mode_();
}
}
}
#include "sleep_timer.h"
#include "application.h"
#include "board.h"
#include "display.h"
#include "settings.h"
#include <esp_log.h>
#include <esp_sleep.h>
#include <esp_lvgl_port.h>
#define TAG "SleepTimer"
SleepTimer::SleepTimer(int seconds_to_light_sleep, int seconds_to_deep_sleep)
: seconds_to_light_sleep_(seconds_to_light_sleep), seconds_to_deep_sleep_(seconds_to_deep_sleep) {
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
auto self = static_cast<SleepTimer*>(arg);
self->CheckTimer();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "sleep_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &sleep_timer_));
}
SleepTimer::~SleepTimer() {
esp_timer_stop(sleep_timer_);
esp_timer_delete(sleep_timer_);
}
void SleepTimer::SetEnabled(bool enabled) {
if (enabled && !enabled_) {
Settings settings("wifi", false);
if (!settings.GetBool("sleep_mode", true)) {
ESP_LOGI(TAG, "Power save timer is disabled by settings");
return;
}
ticks_ = 0;
enabled_ = enabled;
ESP_ERROR_CHECK(esp_timer_start_periodic(sleep_timer_, 1000000));
ESP_LOGI(TAG, "Sleep timer enabled");
} else if (!enabled && enabled_) {
ESP_ERROR_CHECK(esp_timer_stop(sleep_timer_));
enabled_ = enabled;
WakeUp();
ESP_LOGI(TAG, "Sleep timer disabled");
}
}
void SleepTimer::OnEnterLightSleepMode(std::function<void()> callback) {
on_enter_light_sleep_mode_ = callback;
}
void SleepTimer::OnExitLightSleepMode(std::function<void()> callback) {
on_exit_light_sleep_mode_ = callback;
}
void SleepTimer::OnEnterDeepSleepMode(std::function<void()> callback) {
on_enter_deep_sleep_mode_ = callback;
}
void SleepTimer::CheckTimer() {
auto& app = Application::GetInstance();
if (!app.CanEnterSleepMode()) {
ticks_ = 0;
return;
}
ticks_++;
if (seconds_to_light_sleep_ != -1 && ticks_ >= seconds_to_light_sleep_) {
if (!in_light_sleep_mode_) {
in_light_sleep_mode_ = true;
if (on_enter_light_sleep_mode_) {
on_enter_light_sleep_mode_();
}
auto& audio_service = app.GetAudioService();
bool is_wake_word_running = audio_service.IsWakeWordRunning();
if (is_wake_word_running) {
audio_service.EnableWakeWordDetection(false);
vTaskDelay(pdMS_TO_TICKS(100));
}
app.Schedule([this, &app]() {
while (in_light_sleep_mode_) {
auto& board = Board::GetInstance();
board.GetDisplay()->UpdateStatusBar(true);
lv_refr_now(nullptr);
lvgl_port_stop();
// 配置timer唤醒源30秒后自动唤醒
esp_sleep_enable_timer_wakeup(30 * 1000000);
// 进入light sleep模式
esp_light_sleep_start();
lvgl_port_resume();
auto wakeup_reason = esp_sleep_get_wakeup_cause();
ESP_LOGI(TAG, "Wake up from light sleep, wakeup_reason: %d", wakeup_reason);
if (wakeup_reason != ESP_SLEEP_WAKEUP_TIMER) {
break;
}
}
WakeUp();
});
if (is_wake_word_running) {
audio_service.EnableWakeWordDetection(true);
}
}
}
if (seconds_to_deep_sleep_ != -1 && ticks_ >= seconds_to_deep_sleep_) {
if (on_enter_deep_sleep_mode_) {
on_enter_deep_sleep_mode_();
}
esp_deep_sleep_start();
}
}
void SleepTimer::WakeUp() {
ticks_ = 0;
if (in_light_sleep_mode_) {
in_light_sleep_mode_ = false;
if (on_exit_light_sleep_mode_) {
on_exit_light_sleep_mode_();
}
}
}

View File

@@ -1,32 +1,32 @@
#pragma once
#include <functional>
#include <esp_timer.h>
#include <esp_pm.h>
class SleepTimer {
public:
SleepTimer(int seconds_to_light_sleep = 20, int seconds_to_deep_sleep = -1);
~SleepTimer();
void SetEnabled(bool enabled);
void OnEnterLightSleepMode(std::function<void()> callback);
void OnExitLightSleepMode(std::function<void()> callback);
void OnEnterDeepSleepMode(std::function<void()> callback);
void WakeUp();
private:
void CheckTimer();
esp_timer_handle_t sleep_timer_ = nullptr;
bool enabled_ = false;
int ticks_ = 0;
int seconds_to_light_sleep_;
int seconds_to_deep_sleep_;
bool in_light_sleep_mode_ = false;
std::function<void()> on_enter_light_sleep_mode_;
std::function<void()> on_exit_light_sleep_mode_;
std::function<void()> on_enter_deep_sleep_mode_;
};
#pragma once
#include <functional>
#include <esp_timer.h>
#include <esp_pm.h>
class SleepTimer {
public:
SleepTimer(int seconds_to_light_sleep = 20, int seconds_to_deep_sleep = -1);
~SleepTimer();
void SetEnabled(bool enabled);
void OnEnterLightSleepMode(std::function<void()> callback);
void OnExitLightSleepMode(std::function<void()> callback);
void OnEnterDeepSleepMode(std::function<void()> callback);
void WakeUp();
private:
void CheckTimer();
esp_timer_handle_t sleep_timer_ = nullptr;
bool enabled_ = false;
int ticks_ = 0;
int seconds_to_light_sleep_;
int seconds_to_deep_sleep_;
bool in_light_sleep_mode_ = false;
std::function<void()> on_enter_light_sleep_mode_;
std::function<void()> on_exit_light_sleep_mode_;
std::function<void()> on_enter_deep_sleep_mode_;
};

View File

@@ -1,65 +1,65 @@
#include "sy6970.h"
#include "board.h"
#include "display.h"
#include <esp_log.h>
#define TAG "Sy6970"
Sy6970::Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
}
int Sy6970::GetChangingStatus() {
return (ReadReg(0x0B) >> 3) & 0x03;
}
bool Sy6970::IsCharging() {
return GetChangingStatus() != 0;
}
bool Sy6970::IsPowerGood() {
return (ReadReg(0x0B) & 0x04) != 0;
}
bool Sy6970::IsChargingDone() {
return GetChangingStatus() == 3;
}
int Sy6970::GetBatteryVoltage() {
uint8_t value = ReadReg(0x0E);
value &= 0x7F;
if (value == 0) {
return 0;
}
return value * 20 + 2304;
}
int Sy6970::GetChargeTargetVoltage() {
uint8_t value = ReadReg(0x06);
value = (value & 0xFC) >> 2;
if (value > 0x30) {
return 4608;
}
return value * 16 + 3840;
}
int Sy6970::GetBatteryLevel() {
int level = 0;
// 电池所能掉电的最低电压
int battery_minimum_voltage = 3200;
int battery_voltage = GetBatteryVoltage();
int charge_voltage_limit = GetChargeTargetVoltage();
// ESP_LOGI(TAG, "battery_voltage: %d, charge_voltage_limit: %d", battery_voltage, charge_voltage_limit);
if (battery_voltage > battery_minimum_voltage && charge_voltage_limit > battery_minimum_voltage) {
level = (((float) battery_voltage - (float) battery_minimum_voltage) / ((float) charge_voltage_limit - (float) battery_minimum_voltage)) * 100.0;
}
// 不连接电池时读取的充电状态不稳定且battery_voltage有时会超过charge_voltage_limit
if (level > 100) {
level = 100;
}
return level;
}
void Sy6970::PowerOff() {
WriteReg(0x09, 0B01100100);
}
#include "sy6970.h"
#include "board.h"
#include "display.h"
#include <esp_log.h>
#define TAG "Sy6970"
Sy6970::Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
}
int Sy6970::GetChangingStatus() {
return (ReadReg(0x0B) >> 3) & 0x03;
}
bool Sy6970::IsCharging() {
return GetChangingStatus() != 0;
}
bool Sy6970::IsPowerGood() {
return (ReadReg(0x0B) & 0x04) != 0;
}
bool Sy6970::IsChargingDone() {
return GetChangingStatus() == 3;
}
int Sy6970::GetBatteryVoltage() {
uint8_t value = ReadReg(0x0E);
value &= 0x7F;
if (value == 0) {
return 0;
}
return value * 20 + 2304;
}
int Sy6970::GetChargeTargetVoltage() {
uint8_t value = ReadReg(0x06);
value = (value & 0xFC) >> 2;
if (value > 0x30) {
return 4608;
}
return value * 16 + 3840;
}
int Sy6970::GetBatteryLevel() {
int level = 0;
// 电池所能掉电的最低电压
int battery_minimum_voltage = 3200;
int battery_voltage = GetBatteryVoltage();
int charge_voltage_limit = GetChargeTargetVoltage();
// ESP_LOGI(TAG, "battery_voltage: %d, charge_voltage_limit: %d", battery_voltage, charge_voltage_limit);
if (battery_voltage > battery_minimum_voltage && charge_voltage_limit > battery_minimum_voltage) {
level = (((float) battery_voltage - (float) battery_minimum_voltage) / ((float) charge_voltage_limit - (float) battery_minimum_voltage)) * 100.0;
}
// 不连接电池时读取的充电状态不稳定且battery_voltage有时会超过charge_voltage_limit
if (level > 100) {
level = 100;
}
return level;
}
void Sy6970::PowerOff() {
WriteReg(0x09, 0B01100100);
}

View File

@@ -1,21 +1,21 @@
#ifndef __SY6970_H__
#define __SY6970_H__
#include "i2c_device.h"
class Sy6970 : public I2cDevice {
public:
Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
bool IsCharging();
bool IsPowerGood();
bool IsChargingDone();
int GetBatteryLevel();
void PowerOff();
private:
int GetChangingStatus();
int GetBatteryVoltage();
int GetChargeTargetVoltage();
};
#ifndef __SY6970_H__
#define __SY6970_H__
#include "i2c_device.h"
class Sy6970 : public I2cDevice {
public:
Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
bool IsCharging();
bool IsPowerGood();
bool IsChargingDone();
int GetBatteryLevel();
void PowerOff();
private:
int GetChangingStatus();
int GetBatteryVoltage();
int GetChargeTargetVoltage();
};
#endif

View File

@@ -1,72 +1,72 @@
#include "system_reset.h"
#include <esp_log.h>
#include <nvs_flash.h>
#include <driver/gpio.h>
#include <esp_partition.h>
#include <esp_system.h>
#include <freertos/FreeRTOS.h>
#define TAG "SystemReset"
SystemReset::SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin) : reset_nvs_pin_(reset_nvs_pin), reset_factory_pin_(reset_factory_pin) {
// Configure GPIO1, GPIO2 as INPUT, reset NVS flash if the button is pressed
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << reset_nvs_pin_) | (1ULL << reset_factory_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
}
void SystemReset::CheckButtons() {
if (gpio_get_level(reset_factory_pin_) == 0) {
ESP_LOGI(TAG, "Button is pressed, reset to factory");
ResetNvsFlash();
ResetToFactory();
}
if (gpio_get_level(reset_nvs_pin_) == 0) {
ESP_LOGI(TAG, "Button is pressed, reset NVS flash");
ResetNvsFlash();
}
}
void SystemReset::ResetNvsFlash() {
ESP_LOGI(TAG, "Resetting NVS flash");
esp_err_t ret = nvs_flash_erase();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase NVS flash");
}
ret = nvs_flash_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize NVS flash");
}
}
void SystemReset::ResetToFactory() {
ESP_LOGI(TAG, "Resetting to factory");
// Erase otadata partition
const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
if (partition == NULL) {
ESP_LOGE(TAG, "Failed to find otadata partition");
return;
}
esp_partition_erase_range(partition, 0, partition->size);
ESP_LOGI(TAG, "Erased otadata partition");
// Reboot in 3 seconds
RestartInSeconds(3);
}
void SystemReset::RestartInSeconds(int seconds) {
for (int i = seconds; i > 0; i--) {
ESP_LOGI(TAG, "Resetting in %d seconds", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
esp_restart();
}
#include "system_reset.h"
#include <esp_log.h>
#include <nvs_flash.h>
#include <driver/gpio.h>
#include <esp_partition.h>
#include <esp_system.h>
#include <freertos/FreeRTOS.h>
#define TAG "SystemReset"
SystemReset::SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin) : reset_nvs_pin_(reset_nvs_pin), reset_factory_pin_(reset_factory_pin) {
// Configure GPIO1, GPIO2 as INPUT, reset NVS flash if the button is pressed
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << reset_nvs_pin_) | (1ULL << reset_factory_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
}
void SystemReset::CheckButtons() {
if (gpio_get_level(reset_factory_pin_) == 0) {
ESP_LOGI(TAG, "Button is pressed, reset to factory");
ResetNvsFlash();
ResetToFactory();
}
if (gpio_get_level(reset_nvs_pin_) == 0) {
ESP_LOGI(TAG, "Button is pressed, reset NVS flash");
ResetNvsFlash();
}
}
void SystemReset::ResetNvsFlash() {
ESP_LOGI(TAG, "Resetting NVS flash");
esp_err_t ret = nvs_flash_erase();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to erase NVS flash");
}
ret = nvs_flash_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize NVS flash");
}
}
void SystemReset::ResetToFactory() {
ESP_LOGI(TAG, "Resetting to factory");
// Erase otadata partition
const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
if (partition == NULL) {
ESP_LOGE(TAG, "Failed to find otadata partition");
return;
}
esp_partition_erase_range(partition, 0, partition->size);
ESP_LOGI(TAG, "Erased otadata partition");
// Reboot in 3 seconds
RestartInSeconds(3);
}
void SystemReset::RestartInSeconds(int seconds) {
for (int i = seconds; i > 0; i--) {
ESP_LOGI(TAG, "Resetting in %d seconds", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
esp_restart();
}

View File

@@ -1,21 +1,21 @@
#ifndef _SYSTEM_RESET_H
#define _SYSTEM_RESET_H
#include <driver/gpio.h>
class SystemReset {
public:
SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin); // 构造函数私有化
void CheckButtons();
private:
gpio_num_t reset_nvs_pin_;
gpio_num_t reset_factory_pin_;
void ResetNvsFlash();
void ResetToFactory();
void RestartInSeconds(int seconds);
};
#endif
#ifndef _SYSTEM_RESET_H
#define _SYSTEM_RESET_H
#include <driver/gpio.h>
class SystemReset {
public:
SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin); // 构造函数私有化
void CheckButtons();
private:
gpio_num_t reset_nvs_pin_;
gpio_num_t reset_factory_pin_;
void ResetNvsFlash();
void ResetToFactory();
void RestartInSeconds(int seconds);
};
#endif

View File

@@ -1,265 +1,268 @@
#include "wifi_board.h"
#include "display.h"
#include "application.h"
#include "system_info.h"
#include "settings.h"
#include "assets/lang_config.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_network.h>
#include <esp_log.h>
#include <font_awesome.h>
#include <wifi_station.h>
#include <wifi_configuration_ap.h>
#include <ssid_manager.h>
#include "afsk_demod.h"
static const char *TAG = "WifiBoard";
WifiBoard::WifiBoard() {
Settings settings("wifi", true);
wifi_config_mode_ = settings.GetInt("force_ap") == 1;
if (wifi_config_mode_) {
ESP_LOGI(TAG, "force_ap is set to 1, reset to 0");
settings.SetInt("force_ap", 0);
}
}
std::string WifiBoard::GetBoardType() {
return "wifi";
}
void WifiBoard::EnterWifiConfigMode() {
auto& application = Application::GetInstance();
application.SetDeviceState(kDeviceStateWifiConfiguring);
auto& wifi_ap = WifiConfigurationAp::GetInstance();
wifi_ap.SetLanguage(Lang::CODE);
wifi_ap.SetSsidPrefix("Xiaozhi");
wifi_ap.Start();
// 显示 WiFi 配置 AP 的 SSID 和 Web 服务器 URL
std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT;
hint += wifi_ap.GetSsid();
hint += Lang::Strings::ACCESS_VIA_BROWSER;
hint += wifi_ap.GetWebServerUrl();
hint += "\n\n";
// 播报配置 WiFi 的提示
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG);
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
auto display = Board::GetInstance().GetDisplay();
auto codec = Board::GetInstance().GetAudioCodec();
int channel = 1;
if (codec) {
channel = codec->input_channels();
}
ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel);
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel);
#endif
// Wait forever until reset after configuration
while (true) {
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
void WifiBoard::StartNetwork() {
// User can press BOOT button while starting to enter WiFi configuration mode
if (wifi_config_mode_) {
EnterWifiConfigMode();
return;
}
// If no WiFi SSID is configured, enter WiFi configuration mode
auto& ssid_manager = SsidManager::GetInstance();
auto ssid_list = ssid_manager.GetSsidList();
if (ssid_list.empty()) {
wifi_config_mode_ = true;
EnterWifiConfigMode();
return;
}
auto& wifi_station = WifiStation::GetInstance();
wifi_station.OnScanBegin([this]() {
auto display = Board::GetInstance().GetDisplay();
display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000);
});
wifi_station.OnConnect([this](const std::string& ssid) {
auto display = Board::GetInstance().GetDisplay();
std::string notification = Lang::Strings::CONNECT_TO;
notification += ssid;
notification += "...";
display->ShowNotification(notification.c_str(), 30000);
});
wifi_station.OnConnected([this](const std::string& ssid) {
auto display = Board::GetInstance().GetDisplay();
std::string notification = Lang::Strings::CONNECTED_TO;
notification += ssid;
display->ShowNotification(notification.c_str(), 30000);
});
wifi_station.Start();
// Try to connect to WiFi, if failed, launch the WiFi configuration AP
if (!wifi_station.WaitForConnected(60 * 1000)) {
wifi_station.Stop();
wifi_config_mode_ = true;
EnterWifiConfigMode();
return;
}
}
NetworkInterface* WifiBoard::GetNetwork() {
static EspNetwork network;
return &network;
}
const char* WifiBoard::GetNetworkStateIcon() {
if (wifi_config_mode_) {
return FONT_AWESOME_WIFI;
}
auto& wifi_station = WifiStation::GetInstance();
if (!wifi_station.IsConnected()) {
return FONT_AWESOME_WIFI_SLASH;
}
int8_t rssi = wifi_station.GetRssi();
if (rssi >= -60) {
return FONT_AWESOME_WIFI;
} else if (rssi >= -70) {
return FONT_AWESOME_WIFI_FAIR;
} else {
return FONT_AWESOME_WIFI_WEAK;
}
}
std::string WifiBoard::GetBoardJson() {
// Set the board type for OTA
auto& wifi_station = WifiStation::GetInstance();
std::string board_json = R"({)";
board_json += R"("type":")" + std::string(BOARD_TYPE) + R"(",)";
board_json += R"("name":")" + std::string(BOARD_NAME) + R"(",)";
if (!wifi_config_mode_) {
board_json += R"("ssid":")" + wifi_station.GetSsid() + R"(",)";
board_json += R"("rssi":)" + std::to_string(wifi_station.GetRssi()) + R"(,)";
board_json += R"("channel":)" + std::to_string(wifi_station.GetChannel()) + R"(,)";
board_json += R"("ip":")" + wifi_station.GetIpAddress() + R"(",)";
}
board_json += R"("mac":")" + SystemInfo::GetMacAddress() + R"(")";
board_json += R"(})";
return board_json;
}
void WifiBoard::SetPowerSaveMode(bool enabled) {
auto& wifi_station = WifiStation::GetInstance();
wifi_station.SetPowerSaveMode(enabled);
}
void WifiBoard::ResetWifiConfiguration() {
// Set a flag and reboot the device to enter the network configuration mode
{
Settings settings("wifi", true);
settings.SetInt("force_ap", 1);
}
GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE);
vTaskDelay(pdMS_TO_TICKS(1000));
// Reboot the device
esp_restart();
}
std::string WifiBoard::GetDeviceStatusJson() {
/*
* 返回设备状态JSON
*
* 返回的JSON结构如下
* {
* "audio_speaker": {
* "volume": 70
* },
* "screen": {
* "brightness": 100,
* "theme": "light"
* },
* "battery": {
* "level": 50,
* "charging": true
* },
* "network": {
* "type": "wifi",
* "ssid": "Xiaozhi",
* "rssi": -60
* },
* "chip": {
* "temperature": 25
* }
* }
*/
auto& board = Board::GetInstance();
auto root = cJSON_CreateObject();
// Audio speaker
auto audio_speaker = cJSON_CreateObject();
auto audio_codec = board.GetAudioCodec();
if (audio_codec) {
cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
}
cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
// Screen brightness
auto backlight = board.GetBacklight();
auto screen = cJSON_CreateObject();
if (backlight) {
cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
}
auto display = board.GetDisplay();
if (display && display->height() > 64) { // For LCD display only
auto theme = display->GetTheme();
if (theme != nullptr) {
cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
}
}
cJSON_AddItemToObject(root, "screen", screen);
// Battery
int battery_level = 0;
bool charging = false;
bool discharging = false;
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
cJSON* battery = cJSON_CreateObject();
cJSON_AddNumberToObject(battery, "level", battery_level);
cJSON_AddBoolToObject(battery, "charging", charging);
cJSON_AddItemToObject(root, "battery", battery);
}
// Network
auto network = cJSON_CreateObject();
auto& wifi_station = WifiStation::GetInstance();
cJSON_AddStringToObject(network, "type", "wifi");
cJSON_AddStringToObject(network, "ssid", wifi_station.GetSsid().c_str());
int rssi = wifi_station.GetRssi();
if (rssi >= -60) {
cJSON_AddStringToObject(network, "signal", "strong");
} else if (rssi >= -70) {
cJSON_AddStringToObject(network, "signal", "medium");
} else {
cJSON_AddStringToObject(network, "signal", "weak");
}
cJSON_AddItemToObject(root, "network", network);
// Chip
float esp32temp = 0.0f;
if (board.GetTemperature(esp32temp)) {
auto chip = cJSON_CreateObject();
cJSON_AddNumberToObject(chip, "temperature", esp32temp);
cJSON_AddItemToObject(root, "chip", chip);
}
auto json_str = cJSON_PrintUnformatted(root);
std::string json(json_str);
cJSON_free(json_str);
cJSON_Delete(root);
return json;
}
#include "wifi_board.h"
#include "display.h"
#include "application.h"
#include "system_info.h"
#include "settings.h"
#include "assets/lang_config.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_network.h>
#include <esp_log.h>
#include <font_awesome.h>
#include <wifi_station.h>
#include <wifi_configuration_ap.h>
#include <ssid_manager.h>
#include "afsk_demod.h"
static const char *TAG = "WifiBoard";
WifiBoard::WifiBoard() {
Settings settings("wifi", true);
wifi_config_mode_ = settings.GetInt("force_ap") == 1;
if (wifi_config_mode_) {
ESP_LOGI(TAG, "force_ap is set to 1, reset to 0");
settings.SetInt("force_ap", 0);
}
}
std::string WifiBoard::GetBoardType() {
return "wifi";
}
void WifiBoard::EnterWifiConfigMode() {
auto& application = Application::GetInstance();
application.SetDeviceState(kDeviceStateWifiConfiguring);
auto& wifi_ap = WifiConfigurationAp::GetInstance();
wifi_ap.SetLanguage(Lang::CODE);
wifi_ap.SetSsidPrefix("Xiaozhi");
wifi_ap.Start();
// 等待 1.5 秒显示开发板信息
vTaskDelay(pdMS_TO_TICKS(1500));
// 显示 WiFi 配置 AP 的 SSID 和 Web 服务器 URL
std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT;
hint += wifi_ap.GetSsid();
hint += Lang::Strings::ACCESS_VIA_BROWSER;
hint += wifi_ap.GetWebServerUrl();
hint += "\n\n";
// 播报配置 WiFi 的提示
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG);
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
auto display = Board::GetInstance().GetDisplay();
auto codec = Board::GetInstance().GetAudioCodec();
int channel = 1;
if (codec) {
channel = codec->input_channels();
}
ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel);
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel);
#endif
// Wait forever until reset after configuration
while (true) {
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
void WifiBoard::StartNetwork() {
// User can press BOOT button while starting to enter WiFi configuration mode
if (wifi_config_mode_) {
EnterWifiConfigMode();
return;
}
// If no WiFi SSID is configured, enter WiFi configuration mode
auto& ssid_manager = SsidManager::GetInstance();
auto ssid_list = ssid_manager.GetSsidList();
if (ssid_list.empty()) {
wifi_config_mode_ = true;
EnterWifiConfigMode();
return;
}
auto& wifi_station = WifiStation::GetInstance();
wifi_station.OnScanBegin([this]() {
auto display = Board::GetInstance().GetDisplay();
display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000);
});
wifi_station.OnConnect([this](const std::string& ssid) {
auto display = Board::GetInstance().GetDisplay();
std::string notification = Lang::Strings::CONNECT_TO;
notification += ssid;
notification += "...";
display->ShowNotification(notification.c_str(), 30000);
});
wifi_station.OnConnected([this](const std::string& ssid) {
auto display = Board::GetInstance().GetDisplay();
std::string notification = Lang::Strings::CONNECTED_TO;
notification += ssid;
display->ShowNotification(notification.c_str(), 30000);
});
wifi_station.Start();
// Try to connect to WiFi, if failed, launch the WiFi configuration AP
if (!wifi_station.WaitForConnected(60 * 1000)) {
wifi_station.Stop();
wifi_config_mode_ = true;
EnterWifiConfigMode();
return;
}
}
NetworkInterface* WifiBoard::GetNetwork() {
static EspNetwork network;
return &network;
}
const char* WifiBoard::GetNetworkStateIcon() {
if (wifi_config_mode_) {
return FONT_AWESOME_WIFI;
}
auto& wifi_station = WifiStation::GetInstance();
if (!wifi_station.IsConnected()) {
return FONT_AWESOME_WIFI_SLASH;
}
int8_t rssi = wifi_station.GetRssi();
if (rssi >= -60) {
return FONT_AWESOME_WIFI;
} else if (rssi >= -70) {
return FONT_AWESOME_WIFI_FAIR;
} else {
return FONT_AWESOME_WIFI_WEAK;
}
}
std::string WifiBoard::GetBoardJson() {
// Set the board type for OTA
auto& wifi_station = WifiStation::GetInstance();
std::string board_json = R"({)";
board_json += R"("type":")" + std::string(BOARD_TYPE) + R"(",)";
board_json += R"("name":")" + std::string(BOARD_NAME) + R"(",)";
if (!wifi_config_mode_) {
board_json += R"("ssid":")" + wifi_station.GetSsid() + R"(",)";
board_json += R"("rssi":)" + std::to_string(wifi_station.GetRssi()) + R"(,)";
board_json += R"("channel":)" + std::to_string(wifi_station.GetChannel()) + R"(,)";
board_json += R"("ip":")" + wifi_station.GetIpAddress() + R"(",)";
}
board_json += R"("mac":")" + SystemInfo::GetMacAddress() + R"(")";
board_json += R"(})";
return board_json;
}
void WifiBoard::SetPowerSaveMode(bool enabled) {
auto& wifi_station = WifiStation::GetInstance();
wifi_station.SetPowerSaveMode(enabled);
}
void WifiBoard::ResetWifiConfiguration() {
// Set a flag and reboot the device to enter the network configuration mode
{
Settings settings("wifi", true);
settings.SetInt("force_ap", 1);
}
GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE);
vTaskDelay(pdMS_TO_TICKS(1000));
// Reboot the device
esp_restart();
}
std::string WifiBoard::GetDeviceStatusJson() {
/*
* 返回设备状态JSON
*
* 返回的JSON结构如下
* {
* "audio_speaker": {
* "volume": 70
* },
* "screen": {
* "brightness": 100,
* "theme": "light"
* },
* "battery": {
* "level": 50,
* "charging": true
* },
* "network": {
* "type": "wifi",
* "ssid": "Xiaozhi",
* "rssi": -60
* },
* "chip": {
* "temperature": 25
* }
* }
*/
auto& board = Board::GetInstance();
auto root = cJSON_CreateObject();
// Audio speaker
auto audio_speaker = cJSON_CreateObject();
auto audio_codec = board.GetAudioCodec();
if (audio_codec) {
cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
}
cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
// Screen brightness
auto backlight = board.GetBacklight();
auto screen = cJSON_CreateObject();
if (backlight) {
cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
}
auto display = board.GetDisplay();
if (display && display->height() > 64) { // For LCD display only
auto theme = display->GetTheme();
if (theme != nullptr) {
cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
}
}
cJSON_AddItemToObject(root, "screen", screen);
// Battery
int battery_level = 0;
bool charging = false;
bool discharging = false;
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
cJSON* battery = cJSON_CreateObject();
cJSON_AddNumberToObject(battery, "level", battery_level);
cJSON_AddBoolToObject(battery, "charging", charging);
cJSON_AddItemToObject(root, "battery", battery);
}
// Network
auto network = cJSON_CreateObject();
auto& wifi_station = WifiStation::GetInstance();
cJSON_AddStringToObject(network, "type", "wifi");
cJSON_AddStringToObject(network, "ssid", wifi_station.GetSsid().c_str());
int rssi = wifi_station.GetRssi();
if (rssi >= -60) {
cJSON_AddStringToObject(network, "signal", "strong");
} else if (rssi >= -70) {
cJSON_AddStringToObject(network, "signal", "medium");
} else {
cJSON_AddStringToObject(network, "signal", "weak");
}
cJSON_AddItemToObject(root, "network", network);
// Chip
float esp32temp = 0.0f;
if (board.GetTemperature(esp32temp)) {
auto chip = cJSON_CreateObject();
cJSON_AddNumberToObject(chip, "temperature", esp32temp);
cJSON_AddItemToObject(root, "chip", chip);
}
auto json_str = cJSON_PrintUnformatted(root);
std::string json(json_str);
cJSON_free(json_str);
cJSON_Delete(root);
return json;
}

View File

@@ -1,24 +1,24 @@
#ifndef WIFI_BOARD_H
#define WIFI_BOARD_H
#include "board.h"
class WifiBoard : public Board {
protected:
bool wifi_config_mode_ = false;
void EnterWifiConfigMode();
virtual std::string GetBoardJson() override;
public:
WifiBoard();
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual NetworkInterface* GetNetwork() override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
virtual void ResetWifiConfiguration();
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
virtual std::string GetDeviceStatusJson() override;
};
#endif // WIFI_BOARD_H
#ifndef WIFI_BOARD_H
#define WIFI_BOARD_H
#include "board.h"
class WifiBoard : public Board {
protected:
bool wifi_config_mode_ = false;
void EnterWifiConfigMode();
virtual std::string GetBoardJson() override;
public:
WifiBoard();
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual NetworkInterface* GetNetwork() override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
virtual void ResetWifiConfiguration();
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
virtual std::string GetDeviceStatusJson() override;
};
#endif // WIFI_BOARD_H

View File

@@ -1,76 +1,76 @@
#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_3
#define AUDIO_I2S_GPIO_WS GPIO_NUM_38
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_0
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR 0x23
#define BUILTIN_LED_GPIO GPIO_NUM_46
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
/* Expander */
#define DRV_IO_EXP_INPUT_MASK (IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_12)
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
/* DFRobot K10 Camera pins */
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 7
#define VSYNC_GPIO_NUM 4
#define HREF_GPIO_NUM 5
#define PCLK_GPIO_NUM 17
#define SIOD_GPIO_NUM 20
#define SIOC_GPIO_NUM 19
/* Camera pins */
#define CAMERA_PIN_PWDN PWDN_GPIO_NUM
#define CAMERA_PIN_RESET RESET_GPIO_NUM
#define CAMERA_PIN_XCLK XCLK_GPIO_NUM
#define CAMERA_PIN_SIOD SIOD_GPIO_NUM
#define CAMERA_PIN_SIOC SIOC_GPIO_NUM
#define CAMERA_PIN_D9 6
#define CAMERA_PIN_D8 15
#define CAMERA_PIN_D7 16
#define CAMERA_PIN_D6 18
#define CAMERA_PIN_D5 9
#define CAMERA_PIN_D4 11
#define CAMERA_PIN_D3 10
#define CAMERA_PIN_D2 8
#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM
#define CAMERA_PIN_HREF HREF_GPIO_NUM
#define CAMERA_PIN_PCLK PCLK_GPIO_NUM
#define XCLK_FREQ_HZ 20000000
#endif // _BOARD_CONFIG_H_
#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_3
#define AUDIO_I2S_GPIO_WS GPIO_NUM_38
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_0
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR 0x23
#define BUILTIN_LED_GPIO GPIO_NUM_46
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
/* Expander */
#define DRV_IO_EXP_INPUT_MASK (IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_12)
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
/* DFRobot K10 Camera pins */
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 7
#define VSYNC_GPIO_NUM 4
#define HREF_GPIO_NUM 5
#define PCLK_GPIO_NUM 17
#define SIOD_GPIO_NUM 20
#define SIOC_GPIO_NUM 19
/* Camera pins */
#define CAMERA_PIN_PWDN PWDN_GPIO_NUM
#define CAMERA_PIN_RESET RESET_GPIO_NUM
#define CAMERA_PIN_XCLK XCLK_GPIO_NUM
#define CAMERA_PIN_SIOD SIOD_GPIO_NUM
#define CAMERA_PIN_SIOC SIOC_GPIO_NUM
#define CAMERA_PIN_D9 6
#define CAMERA_PIN_D8 15
#define CAMERA_PIN_D7 16
#define CAMERA_PIN_D6 18
#define CAMERA_PIN_D5 9
#define CAMERA_PIN_D4 11
#define CAMERA_PIN_D3 10
#define CAMERA_PIN_D2 8
#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM
#define CAMERA_PIN_HREF HREF_GPIO_NUM
#define CAMERA_PIN_PCLK PCLK_GPIO_NUM
#define XCLK_FREQ_HZ 20000000
#endif // _BOARD_CONFIG_H_

Some files were not shown because too many files have changed in this diff Show More