Initial commit
This commit is contained in:
41
main/boards/aipi-lite/README.md
Normal file
41
main/boards/aipi-lite/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 编译命令
|
||||
|
||||
## 一键编译
|
||||
|
||||
```bash
|
||||
python scripts/release.py aipi-lite
|
||||
```
|
||||
|
||||
## 手动配置编译
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**配置**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
选择板子
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AIPI-Lite
|
||||
```
|
||||
|
||||
## 编译烧入
|
||||
|
||||
```bash
|
||||
idf.py -DBOARD_NAME=aipi-lite build flash
|
||||
```
|
||||
|
||||
注意: 如果当前设备出货之前是AiPi-Lite 固件(非小智版本),请特别小心处理闪存固件分区地址,以避免错误擦除 AiPi-Lite 的自身设备信息(EUI 等),否则设备即使恢复成Xorigin固件也无法正确连接到 服务器!所以在刷写固件之前,请务必记录设备的相关必要信息,以确保有恢复的方法!
|
||||
|
||||
您可以使用以下命令备份生产信息
|
||||
|
||||
```bash
|
||||
# firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
|
||||
esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin
|
||||
|
||||
```
|
||||
40
main/boards/aipi-lite/README_en.md
Normal file
40
main/boards/aipi-lite/README_en.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Build Instructions
|
||||
|
||||
## One-click Build
|
||||
|
||||
```bash
|
||||
python scripts/release.py aipi-lite -c config_en.json
|
||||
```
|
||||
|
||||
## Manual Configuration and Build
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**Configuration**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
Select the board:
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AiPi-Lite
|
||||
```
|
||||
|
||||
## Build and Flash
|
||||
|
||||
```bash
|
||||
idf.py -DBOARD_NAME=aipi-lite build flash
|
||||
```
|
||||
|
||||
Note: If your device was previously shipped with the AiPi-Lite firmware (not the Xiaozhi version), please be very careful with the flash partition addresses to avoid accidentally erasing the device information (such as EUI) of the AiPi-Lite. Otherwise, even if you restore the AiPi-Lite firmware, the device may not be able to connect to the Xorigin server correctly! Therefore, before flashing the firmware, be sure to record the necessary device information to ensure you have a way to recover it!
|
||||
|
||||
You can use the following command to back up the factory information:
|
||||
|
||||
```bash
|
||||
# Firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
|
||||
esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin
|
||||
```
|
||||
246
main/boards/aipi-lite/aipi-lite.cc
Normal file
246
main/boards/aipi-lite/aipi-lite.cc
Normal file
@@ -0,0 +1,246 @@
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/rtc_io.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_sleep.h>
|
||||
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "codecs/es8311_audio_codec.h"
|
||||
#include "config.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "lamp_controller.h"
|
||||
#include "led/single_led.h"
|
||||
#include "mcp_server.h"
|
||||
#include "power_manager.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "system_reset.h"
|
||||
#include "wifi_board.h"
|
||||
|
||||
#define TAG "AIPI-Lite"
|
||||
|
||||
class AIPILite : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
Button power_button_;
|
||||
LcdDisplay* display_;
|
||||
PowerManager* power_manager_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
|
||||
void InitializePowerManager() {
|
||||
power_manager_ = new PowerManager(POWER_CHARGE_DETECT_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]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
GetBacklight()->SetBrightness(1);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
GetBacklight()->RestoreBrightness();
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
ESP_LOGI(TAG, "Shutting down");
|
||||
esp_lcd_panel_disp_on_off(panel_, false); // 关闭显示
|
||||
rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
|
||||
rtc_gpio_hold_dis(POWER_CONTROL_PIN);
|
||||
esp_deep_sleep_start();
|
||||
});
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)1,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags =
|
||||
{
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
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_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeLcdDisplay() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
// 液晶屏控制IO初始化
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
|
||||
io_config.dc_gpio_num = DISPLAY_SPI_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_SPI_RESET_PIN;
|
||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(
|
||||
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel_));
|
||||
|
||||
esp_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);
|
||||
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]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
// 设置开机按钮的长按事件(直接进入配网模式)
|
||||
boot_button_.OnLongPress([this]() {
|
||||
// 唤醒电源保存定时器
|
||||
power_save_timer_->WakeUp();
|
||||
// 获取应用程序实例
|
||||
auto& app = Application::GetInstance();
|
||||
|
||||
// 进入配网模式
|
||||
app.SetDeviceState(kDeviceStateWifiConfiguring);
|
||||
|
||||
// 重置WiFi配置以确保进入配网模式
|
||||
EnterWifiConfigMode();
|
||||
});
|
||||
|
||||
power_button_.OnClick([this]() { power_save_timer_->WakeUp(); });
|
||||
power_button_.OnLongPress([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() != kDeviceStateStarting &&
|
||||
!(power_manager_->IsCharging() &&
|
||||
power_manager_->GetBatteryLevel() < 100)) {
|
||||
ESP_LOGI(TAG, "Power button long pressed, shutting down");
|
||||
esp_lcd_panel_disp_on_off(panel_, false); // 关闭显示
|
||||
rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
|
||||
rtc_gpio_hold_dis(POWER_CONTROL_PIN);
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializePowerCtl() {
|
||||
ESP_LOGI(TAG, "Initialize Power Control GPIO");
|
||||
rtc_gpio_init(POWER_CONTROL_PIN);
|
||||
rtc_gpio_set_direction(POWER_CONTROL_PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
|
||||
rtc_gpio_set_level(POWER_CONTROL_PIN, 1);
|
||||
}
|
||||
|
||||
// 物联网初始化,添加对 AI 可见设备
|
||||
void InitializeTools() {}
|
||||
|
||||
public:
|
||||
AIPILite()
|
||||
: boot_button_(BOOT_BUTTON_GPIO), power_button_(POWER_BUTTON_GPIO) {
|
||||
InitializePowerCtl();
|
||||
InitializePowerManager();
|
||||
InitializePowerSaveTimer();
|
||||
InitializeI2c();
|
||||
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 {
|
||||
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_PA_PIN, AUDIO_CODEC_ES8311_ADDR, false);
|
||||
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 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 SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(AIPILite);
|
||||
53
main/boards/aipi-lite/config.h
Normal file
53
main/boards/aipi-lite/config.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// aipi-lite configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 // MCLK
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 // LRCK
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 // SCLK
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_13 // DIN
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 // DOUT
|
||||
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_9
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_46
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_42
|
||||
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 128
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
#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_PIN GPIO_NUM_3
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_16
|
||||
#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_17
|
||||
#define DISPLAY_SPI_CS_PIN GPIO_NUM_15
|
||||
#define DISPLAY_SPI_DC_PIN GPIO_NUM_7
|
||||
#define DISPLAY_SPI_RESET_PIN GPIO_NUM_18
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
|
||||
|
||||
#define POWER_BUTTON_GPIO GPIO_NUM_1
|
||||
#define POWER_CONTROL_PIN GPIO_NUM_10
|
||||
#define POWER_CHARGE_DETECT_PIN GPIO_NUM_8
|
||||
#define POWER_ADC_UNIT ADC_UNIT_1
|
||||
#define POWER_ADC_CHANNEL ADC_CHANNEL_1
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
12
main/boards/aipi-lite/config.json
Normal file
12
main/boards/aipi-lite/config.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "aipi-lite",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
main/boards/aipi-lite/config_en.json
Normal file
17
main/boards/aipi-lite/config_en.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "aipi-lite_en",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"",
|
||||
"CONFIG_LANGUAGE_EN_US=y",
|
||||
"CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=n",
|
||||
"CONFIG_SR_WN_WN9_JARVIS_TTS=y",
|
||||
"CONFIG_SR_WN_WN9_SOPHIA_TTS=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
187
main/boards/aipi-lite/power_manager.h
Normal file
187
main/boards/aipi-lite/power_manager.h
Normal file
@@ -0,0 +1,187 @@
|
||||
#pragma once
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
|
||||
gpio_num_t charging_pin_ = POWER_CHARGE_DETECT_PIN;
|
||||
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_) == 1;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick
|
||||
// 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_value;
|
||||
ESP_ERROR_CHECK(
|
||||
adc_oneshot_read(adc_handle_, POWER_ADC_CHANNEL, &adc_value));
|
||||
|
||||
// 将 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[] = {{1480, 0}, {1581, 20}, {1663, 40},
|
||||
{1750, 60}, {1840, 80}, {1980, 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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld",
|
||||
adc_value, average_adc, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
|
||||
// 初始化充电引脚
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << charging_pin_);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback =
|
||||
[](void* arg) {
|
||||
PowerManager* self = static_cast<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_, 100000));
|
||||
|
||||
// 初始化 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_, POWER_ADC_CHANNEL, &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;
|
||||
}
|
||||
};
|
||||
299
main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc
Normal file
299
main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc
Normal file
@@ -0,0 +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 <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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
46
main/boards/atk-dnesp32s3-box/config.h
Normal file
46
main/boards/atk-dnesp32s3-box/config.h
Normal file
@@ -0,0 +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_
|
||||
11
main/boards/atk-dnesp32s3-box/config.json
Normal file
11
main/boards/atk-dnesp32s3-box/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
388
main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc
Normal file
388
main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc
Normal file
@@ -0,0 +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 <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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
|
||||
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 SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box0);
|
||||
84
main/boards/atk-dnesp32s3-box0/config.h
Normal file
84
main/boards/atk-dnesp32s3-box0/config.h
Normal file
@@ -0,0 +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_
|
||||
|
||||
9
main/boards/atk-dnesp32s3-box0/config.json
Normal file
9
main/boards/atk-dnesp32s3-box0/config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box0",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
193
main/boards/atk-dnesp32s3-box0/power_manager.h
Normal file
193
main/boards/atk-dnesp32s3-box0/power_manager.h
Normal file
@@ -0,0 +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;
|
||||
}
|
||||
};
|
||||
477
main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc
Normal file
477
main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc
Normal file
@@ -0,0 +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 <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) {
|
||||
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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 SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
DualNetworkBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box2_4g);
|
||||
|
||||
// 定义静态成员变量
|
||||
atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr;
|
||||
80
main/boards/atk-dnesp32s3-box2-4g/config.h
Normal file
80
main/boards/atk-dnesp32s3-box2-4g/config.h
Normal file
@@ -0,0 +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_
|
||||
|
||||
9
main/boards/atk-dnesp32s3-box2-4g/config.json
Normal file
9
main/boards/atk-dnesp32s3-box2-4g/config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box2-4g",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
195
main/boards/atk-dnesp32s3-box2-4g/power_manager.h
Normal file
195
main/boards/atk-dnesp32s3-box2-4g/power_manager.h
Normal file
@@ -0,0 +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;
|
||||
}
|
||||
};
|
||||
456
main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc
Normal file
456
main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc
Normal file
@@ -0,0 +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 <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) {
|
||||
self->EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
|
||||
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 SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(atk_dnesp32s3_box2_wifi);
|
||||
|
||||
// 定义静态成员变量
|
||||
atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;
|
||||
71
main/boards/atk-dnesp32s3-box2-wifi/config.h
Normal file
71
main/boards/atk-dnesp32s3-box2-wifi/config.h
Normal file
@@ -0,0 +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_
|
||||
|
||||
9
main/boards/atk-dnesp32s3-box2-wifi/config.json
Normal file
9
main/boards/atk-dnesp32s3-box2-wifi/config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3-box2-wifi",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
195
main/boards/atk-dnesp32s3-box2-wifi/power_manager.h
Normal file
195
main/boards/atk-dnesp32s3-box2-wifi/power_manager.h
Normal file
@@ -0,0 +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;
|
||||
}
|
||||
};
|
||||
225
main/boards/atk-dnesp32s3/atk_dnesp32s3.cc
Normal file
225
main/boards/atk-dnesp32s3/atk_dnesp32s3.cc
Normal file
@@ -0,0 +1,225 @@
|
||||
#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 "esp_video.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.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_;
|
||||
EspVideo* 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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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
|
||||
|
||||
static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
|
||||
.data_width = CAM_CTLR_DATA_WIDTH_8,
|
||||
.data_io = {
|
||||
[0] = CAM_PIN_D0,
|
||||
[1] = CAM_PIN_D1,
|
||||
[2] = CAM_PIN_D2,
|
||||
[3] = CAM_PIN_D3,
|
||||
[4] = CAM_PIN_D4,
|
||||
[5] = CAM_PIN_D5,
|
||||
[6] = CAM_PIN_D6,
|
||||
[7] = CAM_PIN_D7,
|
||||
},
|
||||
.vsync_io = CAM_PIN_VSYNC,
|
||||
.de_io = CAM_PIN_HREF,
|
||||
.pclk_io = CAM_PIN_PCLK,
|
||||
.xclk_io = CAM_PIN_XCLK,
|
||||
};
|
||||
|
||||
esp_video_init_sccb_config_t sccb_config = {
|
||||
.init_sccb = true,
|
||||
.i2c_config = {
|
||||
.port = 1,
|
||||
.scl_pin = CAM_PIN_SIOC,
|
||||
.sda_pin = CAM_PIN_SIOD,
|
||||
},
|
||||
.freq = 100000,
|
||||
};
|
||||
|
||||
esp_video_init_dvp_config_t dvp_config = {
|
||||
.sccb_config = sccb_config,
|
||||
.reset_pin = CAM_PIN_RESET, // 实际由 XL9555 控制
|
||||
.pwdn_pin = CAM_PIN_PWDN, // 实际由 XL9555 控制
|
||||
.dvp_pin = dvp_pin_config,
|
||||
.xclk_freq = 20000000,
|
||||
};
|
||||
|
||||
esp_video_init_config_t video_config = {
|
||||
.dvp = &dvp_config,
|
||||
};
|
||||
|
||||
camera_ = new EspVideo(video_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);
|
||||
64
main/boards/atk-dnesp32s3/config.h
Normal file
64
main/boards/atk-dnesp32s3/config.h
Normal file
@@ -0,0 +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_
|
||||
|
||||
13
main/boards/atk-dnesp32s3/config.json
Normal file
13
main/boards/atk-dnesp32s3/config.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atk-dnesp32s3",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_CAMERA_OV2640=y",
|
||||
"CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||
"CONFIG_CAMERA_OV2640_DVP_YUV422_240X240_25FPS=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
220
main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc
Normal file
220
main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc
Normal file
@@ -0,0 +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);
|
||||
53
main/boards/atk-dnesp32s3m-4g/config.h
Normal file
53
main/boards/atk-dnesp32s3m-4g/config.h
Normal file
@@ -0,0 +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_
|
||||
|
||||
230
main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc
Normal file
230
main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc
Normal file
@@ -0,0 +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 <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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
52
main/boards/atk-dnesp32s3m-wifi/config.h
Normal file
52
main/boards/atk-dnesp32s3m-wifi/config.h
Normal file
@@ -0,0 +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_
|
||||
|
||||
45
main/boards/atom-echos3r/README.md
Normal file
45
main/boards/atom-echos3r/README.md
Normal file
@@ -0,0 +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 按钮重启设备。
|
||||
91
main/boards/atom-echos3r/atom_echos3r.cc
Normal file
91
main/boards/atom-echos3r/atom_echos3r.cc
Normal file
@@ -0,0 +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>
|
||||
|
||||
#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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
29
main/boards/atom-echos3r/config.h
Normal file
29
main/boards/atom-echos3r/config.h
Normal file
@@ -0,0 +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_
|
||||
12
main/boards/atom-echos3r/config.json
Normal file
12
main/boards/atom-echos3r/config.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atom-echos3r",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
25
main/boards/atommatrix-echo-base/README.md
Normal file
25
main/boards/atommatrix-echo-base/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
133
main/boards/atommatrix-echo-base/atommatrix_echo_base.cc
Normal file
133
main/boards/atommatrix-echo-base/atommatrix_echo_base.cc
Normal file
@@ -0,0 +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 "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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
29
main/boards/atommatrix-echo-base/config.h
Normal file
29
main/boards/atommatrix-echo-base/config.h
Normal file
@@ -0,0 +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_
|
||||
10
main/boards/atommatrix-echo-base/config.json
Normal file
10
main/boards/atommatrix-echo-base/config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"target": "esp32",
|
||||
"builds": [
|
||||
{
|
||||
"name": "atommatrix-echo-base",
|
||||
"sdkconfig_append": [
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
49
main/boards/atoms3-echo-base/README.md
Normal file
49
main/boards/atoms3-echo-base/README.md
Normal file
@@ -0,0 +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
|
||||
```
|
||||
232
main/boards/atoms3-echo-base/atoms3_echo_base.cc
Normal file
232
main/boards/atoms3-echo-base/atoms3_echo_base.cc
Normal file
@@ -0,0 +1,232 @@
|
||||
#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 <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);
|
||||
|
||||
// Ensure UI is set up before displaying error
|
||||
display_->SetupUI();
|
||||
|
||||
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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
43
main/boards/atoms3-echo-base/config.h
Normal file
43
main/boards/atoms3-echo-base/config.h
Normal file
@@ -0,0 +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
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
13
main/boards/atoms3-echo-base/config.json
Normal file
13
main/boards/atoms3-echo-base/config.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"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\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
54
main/boards/atoms3r-cam-m12-echo-base/README.md
Normal file
54
main/boards/atoms3r-cam-m12-echo-base/README.md
Normal file
@@ -0,0 +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 按钮重启。
|
||||
@@ -0,0 +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 "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(1000));
|
||||
}
|
||||
|
||||
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);
|
||||
49
main/boards/atoms3r-cam-m12-echo-base/config.h
Normal file
49
main/boards/atoms3r-cam-m12-echo-base/config.h
Normal file
@@ -0,0 +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_
|
||||
12
main/boards/atoms3r-cam-m12-echo-base/config.json
Normal file
12
main/boards/atoms3r-cam-m12-echo-base/config.json
Normal file
@@ -0,0 +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\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
43
main/boards/atoms3r-echo-base/README.md
Normal file
43
main/boards/atoms3r-echo-base/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32S3:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> AtomS3R + Echo Base
|
||||
```
|
||||
|
||||
**修改 flash 大小:**
|
||||
|
||||
```
|
||||
Serial flasher config -> Flash size -> 8 MB
|
||||
```
|
||||
|
||||
**修改分区表:**
|
||||
|
||||
```
|
||||
Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
|
||||
```
|
||||
|
||||
**修改 psram 配置:**
|
||||
|
||||
```
|
||||
Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
312
main/boards/atoms3r-echo-base/atoms3r_echo_base.cc
Normal file
312
main/boards/atoms3r-echo-base/atoms3r_echo_base.cc
Normal file
@@ -0,0 +1,312 @@
|
||||
#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 <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);
|
||||
|
||||
// Ensure UI is set up before displaying error
|
||||
display_->SetupUI();
|
||||
|
||||
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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
43
main/boards/atoms3r-echo-base/config.h
Normal file
43
main/boards/atoms3r-echo-base/config.h
Normal file
@@ -0,0 +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_
|
||||
12
main/boards/atoms3r-echo-base/config.json
Normal file
12
main/boards/atoms3r-echo-base/config.json
Normal file
@@ -0,0 +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\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
278
main/boards/bread-compact-esp32-lcd/config.h
Normal file
278
main/boards/bread-compact-esp32-lcd/config.h
Normal file
@@ -0,0 +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
|
||||
|
||||
#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_
|
||||
11
main/boards/bread-compact-esp32-lcd/config.json
Normal file
11
main/boards/bread-compact-esp32-lcd/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"target": "esp32",
|
||||
"builds": [
|
||||
{
|
||||
"name": "bread-compact-esp32-lcd",
|
||||
"sdkconfig_append": [
|
||||
"LCD_ST7789_240X240_7PIN=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
200
main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc
Normal file
200
main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc
Normal file
@@ -0,0 +1,200 @@
|
||||
#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 <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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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() : WifiBoard(),
|
||||
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);
|
||||
25
main/boards/bread-compact-esp32/README.md
Normal file
25
main/boards/bread-compact-esp32/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32:**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32
|
||||
```
|
||||
|
||||
**打开 menuconfig:**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> 面包板 ESP32 DevKit
|
||||
```
|
||||
|
||||
**编译:**
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
58
main/boards/bread-compact-esp32/config.h
Normal file
58
main/boards/bread-compact-esp32/config.h
Normal file
@@ -0,0 +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 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 display type is not selected"
|
||||
#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_
|
||||
17
main/boards/bread-compact-esp32/config.json
Normal file
17
main/boards/bread-compact-esp32/config.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
162
main/boards/bread-compact-esp32/esp32_bread_board.cc
Normal file
162
main/boards/bread-compact-esp32/esp32_bread_board.cc
Normal file
@@ -0,0 +1,162 @@
|
||||
#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 <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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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() : WifiBoard(), 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);
|
||||
191
main/boards/bread-compact-ml307/compact_ml307_board.cc
Normal file
191
main/boards/bread-compact-ml307/compact_ml307_board.cc
Normal file
@@ -0,0 +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>
|
||||
|
||||
#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) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
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);
|
||||
59
main/boards/bread-compact-ml307/config.h
Normal file
59
main/boards/bread-compact-ml307/config.h
Normal file
@@ -0,0 +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 display type is not selected"
|
||||
#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_
|
||||
17
main/boards/bread-compact-ml307/config.json
Normal file
17
main/boards/bread-compact-ml307/config.json
Normal file
@@ -0,0 +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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
181
main/boards/bread-compact-nt26/compact_nt26_board.cc
Normal file
181
main/boards/bread-compact-nt26/compact_nt26_board.cc
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "board.h"
|
||||
#include "nt26_board.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
#include "display/oled_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.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>
|
||||
|
||||
#define TAG "CompactNt26Board"
|
||||
|
||||
class CompactNt26Board : public Nt26Board {
|
||||
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([]() {
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
|
||||
touch_button_.OnPressDown([]() {
|
||||
Application::GetInstance().StartListening();
|
||||
});
|
||||
touch_button_.OnPressUp([]() {
|
||||
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:
|
||||
CompactNt26Board() :
|
||||
Nt26Board(NT26_TX_PIN, NT26_RX_PIN, NT26_DTR_PIN, NT26_RI_PIN),
|
||||
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 void StartNetwork() override {
|
||||
GetDisplay()->SetStatus(Lang::Strings::DETECTING_MODULE);
|
||||
Nt26Board::StartNetwork();
|
||||
}
|
||||
|
||||
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(CompactNt26Board);
|
||||
61
main/boards/bread-compact-nt26/config.h
Normal file
61
main/boards/bread-compact-nt26/config.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#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 display type is not selected"
|
||||
#endif
|
||||
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
|
||||
|
||||
#define NT26_DTR_PIN GPIO_NUM_9
|
||||
#define NT26_RI_PIN GPIO_NUM_10
|
||||
#define NT26_RX_PIN GPIO_NUM_11
|
||||
#define NT26_TX_PIN GPIO_NUM_12
|
||||
|
||||
|
||||
// A MCP Test: Control a lamp
|
||||
#define LAMP_GPIO GPIO_NUM_18
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
11
main/boards/bread-compact-nt26/config.json
Normal file
11
main/boards/bread-compact-nt26/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "bread-compact-nt26",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_OLED_SSD1306_128X32=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
183
main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc
Normal file
183
main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc
Normal file
@@ -0,0 +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 <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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
289
main/boards/bread-compact-wifi-lcd/config.h
Normal file
289
main/boards/bread-compact-wifi-lcd/config.h
Normal file
@@ -0,0 +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_
|
||||
31
main/boards/bread-compact-wifi-s3cam/README.md
Normal file
31
main/boards/bread-compact-wifi-s3cam/README.md
Normal file
@@ -0,0 +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
|
||||
```
|
||||
|
||||
**选择板子:**
|
||||
|
||||
```bash
|
||||
Xiaozhi Assistant -> Board Type ->面包板新版接线(WiFi)+ LCD + Camera
|
||||
```
|
||||
|
||||
**编译烧入:**
|
||||
|
||||
```bash
|
||||
idf.py build flash
|
||||
```
|
||||
214
main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc
Normal file
214
main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc
Normal file
@@ -0,0 +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 <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_VGA;
|
||||
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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
308
main/boards/bread-compact-wifi-s3cam/config.h
Normal file
308
main/boards/bread-compact-wifi-s3cam/config.h
Normal file
@@ -0,0 +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_
|
||||
188
main/boards/bread-compact-wifi/compact_wifi_board.cc
Normal file
188
main/boards/bread-compact-wifi/compact_wifi_board.cc
Normal file
@@ -0,0 +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 <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) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
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);
|
||||
59
main/boards/bread-compact-wifi/config.h
Normal file
59
main/boards/bread-compact-wifi/config.h
Normal file
@@ -0,0 +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 display type is not selected"
|
||||
#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_
|
||||
17
main/boards/bread-compact-wifi/config.json
Normal file
17
main/boards/bread-compact-wifi/config.json
Normal file
@@ -0,0 +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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
116
main/boards/common/adc_battery_monitor.cc
Normal file
116
main/boards/common/adc_battery_monitor.cc
Normal file
@@ -0,0 +1,116 @@
|
||||
#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 (only if it's not NC)
|
||||
if (charging_pin_ != GPIO_NUM_NC) {
|
||||
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_DEFAULT,
|
||||
.adc_atten = ADC_ATTEN_DB_12,
|
||||
},
|
||||
.adc_channel = adc_channel,
|
||||
.upper_resistor = upper_resistor,
|
||||
.lower_resistor = lower_resistor
|
||||
};
|
||||
|
||||
// 在ADC配置部分进行条件设置
|
||||
if (charging_pin_ != GPIO_NUM_NC) {
|
||||
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;
|
||||
} else {
|
||||
// 不设置回调,让adc_battery_estimation库使用软件估算
|
||||
adc_cfg.charging_detect_cb = nullptr;
|
||||
adc_cfg.charging_detect_user_data = nullptr;
|
||||
}
|
||||
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_));
|
||||
}
|
||||
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool AdcBatteryMonitor::IsCharging() {
|
||||
// 优先使用adc_battery_estimation库的功能
|
||||
if (adc_battery_estimation_handle_ != nullptr) {
|
||||
bool is_charging = false;
|
||||
esp_err_t err = adc_battery_estimation_get_charging_state(adc_battery_estimation_handle_, &is_charging);
|
||||
if (err == ESP_OK) {
|
||||
return is_charging;
|
||||
}
|
||||
}
|
||||
|
||||
// 回退到GPIO读取或返回默认值
|
||||
if (charging_pin_ != GPIO_NUM_NC) {
|
||||
return gpio_get_level(charging_pin_) == 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AdcBatteryMonitor::IsDischarging() {
|
||||
return !IsCharging();
|
||||
}
|
||||
|
||||
uint8_t AdcBatteryMonitor::GetBatteryLevel() {
|
||||
// 如果句柄无效,返回默认值
|
||||
if (adc_battery_estimation_handle_ == nullptr) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
float capacity = 0;
|
||||
esp_err_t err = adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity);
|
||||
if (err != ESP_OK) {
|
||||
return 100; // 出错时返回默认值
|
||||
}
|
||||
return (uint8_t)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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
main/boards/common/adc_battery_monitor.h
Normal file
30
main/boards/common/adc_battery_monitor.h
Normal file
@@ -0,0 +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
|
||||
375
main/boards/common/afsk_demod.cc
Normal file
375
main/boards/common/afsk_demod.cc
Normal file
@@ -0,0 +1,375 @@
|
||||
#include "afsk_demod.h"
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include "esp_log.h"
|
||||
#include "display.h"
|
||||
#include "ssid_manager.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,
|
||||
WifiManager *wifi_manager,
|
||||
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;
|
||||
}
|
||||
|
||||
// Save WiFi credentials using SsidManager
|
||||
auto& ssid_manager = SsidManager::GetInstance();
|
||||
ssid_manager.AddSsid(wifi_ssid, wifi_password);
|
||||
ESP_LOGI(kLogTag, "WiFi credentials saved successfully");
|
||||
|
||||
// Exit config mode (triggers ConfigModeExit event)
|
||||
wifi_manager->StopConfigAp();
|
||||
|
||||
data_buffer.decoded_text.reset(); // Clear processed data
|
||||
return; // Exit the function
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
177
main/boards/common/afsk_demod.h
Normal file
177
main/boards/common/afsk_demod.h
Normal file
@@ -0,0 +1,177 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <cmath>
|
||||
#include "wifi_manager.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, WifiManager *wifi_manager, 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;
|
||||
}
|
||||
41
main/boards/common/axp2101.cc
Normal file
41
main/boards/common/axp2101.cc
Normal file
@@ -0,0 +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);
|
||||
}
|
||||
20
main/boards/common/axp2101.h
Normal file
20
main/boards/common/axp2101.h
Normal file
@@ -0,0 +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
|
||||
121
main/boards/common/backlight.cc
Normal file
121
main/boards/common/backlight.cc
Normal file
@@ -0,0 +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);
|
||||
}
|
||||
|
||||
36
main/boards/common/backlight.h
Normal file
36
main/boards/common/backlight.h
Normal file
@@ -0,0 +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;
|
||||
};
|
||||
899
main/boards/common/blufi.cpp
Normal file
899
main/boards/common/blufi.cpp
Normal file
@@ -0,0 +1,899 @@
|
||||
#include "blufi.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "esp_bt.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "freertos/task.h"
|
||||
#include "wifi_manager.h"
|
||||
|
||||
#define BLUFI_DEVICE_NAME "Xiaozhi-Blufi"
|
||||
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
#include "console/console.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
extern void esp_blufi_gatt_svr_register_cb(struct ble_gatt_register_ctxt* ctxt, void* arg);
|
||||
extern int esp_blufi_gatt_svr_init(void);
|
||||
extern void esp_blufi_gatt_svr_deinit(void);
|
||||
extern void esp_blufi_btc_init(void);
|
||||
extern void esp_blufi_btc_deinit(void);
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
void esp_blufi_adv_start(void);
|
||||
|
||||
void esp_blufi_adv_stop(void);
|
||||
|
||||
void esp_blufi_disconnect(void);
|
||||
|
||||
void btc_blufi_report_error(esp_blufi_error_state_t state);
|
||||
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
void esp_blufi_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
void esp_blufi_gatt_svr_register_cb(struct ble_gatt_register_ctxt* ctxt, void* arg);
|
||||
int esp_blufi_gatt_svr_init(void);
|
||||
void esp_blufi_gatt_svr_deinit(void);
|
||||
void esp_blufi_btc_init(void);
|
||||
void esp_blufi_btc_deinit(void);
|
||||
#endif
|
||||
}
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include "esp_crc.h"
|
||||
#include "esp_random.h"
|
||||
#include "mbedtls/md5.h"
|
||||
#include "ssid_manager.h"
|
||||
|
||||
static const char* BLUFI_TAG = "BLUFI_CLASS";
|
||||
|
||||
static wifi_mode_t GetWifiModeWithFallback(const WifiManager& wifi) {
|
||||
if (wifi.IsConfigMode()) {
|
||||
return WIFI_MODE_AP;
|
||||
}
|
||||
if (wifi.IsInitialized() && wifi.IsConnected()) {
|
||||
return WIFI_MODE_STA;
|
||||
}
|
||||
|
||||
wifi_mode_t mode = WIFI_MODE_STA;
|
||||
esp_wifi_get_mode(&mode);
|
||||
return mode;
|
||||
}
|
||||
|
||||
Blufi& Blufi::GetInstance() {
|
||||
static Blufi instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Blufi::Blufi()
|
||||
: m_sec(nullptr),
|
||||
m_ble_is_connected(false),
|
||||
m_sta_connected(false),
|
||||
m_sta_got_ip(false),
|
||||
m_provisioned(false),
|
||||
m_deinited(false),
|
||||
m_sta_ssid_len(0),
|
||||
m_sta_is_connecting(false) {
|
||||
memset(&m_sta_config, 0, sizeof(m_sta_config));
|
||||
memset(m_sta_bssid, 0, sizeof(m_sta_bssid));
|
||||
memset(m_sta_ssid, 0, sizeof(m_sta_ssid));
|
||||
memset(&m_sta_conn_info, 0, sizeof(m_sta_conn_info));
|
||||
}
|
||||
|
||||
Blufi::~Blufi() {
|
||||
if (m_sec) {
|
||||
_security_deinit();
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t Blufi::init() {
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
inited_ = true;
|
||||
m_provisioned = false;
|
||||
m_deinited = false;
|
||||
|
||||
// Start WiFi scan early to have results ready when user connects
|
||||
auto& wifi_manager = WifiManager::GetInstance();
|
||||
if (!wifi_manager.IsInitialized() || !wifi_manager.IsConfigMode()) {
|
||||
// start scan immediately
|
||||
start_wifi_scan();
|
||||
} else {
|
||||
ESP_LOGE(BLUFI_TAG,
|
||||
"Blufi and WiFi hotspot network configuration cannot "
|
||||
"be used simultaneously.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
|
||||
ret = _controller_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "BLUFI controller init failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = _host_and_cb_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "BLUFI host and cb init failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI VERSION %04x", esp_blufi_get_version());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::deinit() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (inited_) {
|
||||
if (m_deinited) {
|
||||
return ESP_OK;
|
||||
}
|
||||
m_deinited = true;
|
||||
ret = _host_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "Host deinit failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
|
||||
ret = _controller_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "Controller deinit failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
esp_err_t Blufi::_host_init() {
|
||||
esp_err_t ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s init bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s enable bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ESP_LOGI(BLUFI_TAG, "BD ADDR: " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(esp_bt_dev_get_address()));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_deinit() {
|
||||
esp_err_t ret = esp_blufi_profile_deinit();
|
||||
if (ret != ESP_OK)
|
||||
return ret;
|
||||
|
||||
ret = esp_bluedroid_disable();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s disable bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ret = esp_bluedroid_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s deinit bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_gap_register_callback() {
|
||||
esp_err_t rc = esp_ble_gap_register_callback(esp_blufi_gap_event_handler);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
return esp_blufi_profile_init();
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_and_cb_init() {
|
||||
static esp_blufi_callbacks_t blufi_callbacks = {
|
||||
.event_cb = &_event_callback_trampoline,
|
||||
.negotiate_data_handler = &_negotiate_data_handler_trampoline,
|
||||
.encrypt_func = &_encrypt_func_trampoline,
|
||||
.decrypt_func = &_decrypt_func_trampoline,
|
||||
.checksum_func = &_checksum_func_trampoline,
|
||||
};
|
||||
|
||||
esp_err_t ret = _host_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s initialise host failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
ret = esp_blufi_register_callbacks(&blufi_callbacks);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s blufi register failed, error code = %x", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
ret = _gap_register_callback();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s gap register failed, error code = %x", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
// Stubs for NimBLE specific store functionality
|
||||
void ble_store_config_init();
|
||||
|
||||
void Blufi::_nimble_on_reset(int reason) {
|
||||
ESP_LOGE(BLUFI_TAG, "NimBLE Resetting state; reason=%d", reason);
|
||||
}
|
||||
|
||||
void Blufi::_nimble_on_sync() { esp_blufi_profile_init(); }
|
||||
|
||||
void Blufi::_nimble_host_task(void* param) {
|
||||
ESP_LOGI(BLUFI_TAG, "BLE Host Task Started");
|
||||
nimble_port_run();
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_init() {
|
||||
ble_hs_cfg.reset_cb = _nimble_on_reset;
|
||||
ble_hs_cfg.sync_cb = _nimble_on_sync;
|
||||
ble_hs_cfg.gatts_register_cb = esp_blufi_gatt_svr_register_cb;
|
||||
|
||||
ble_hs_cfg.sm_io_cap = 4;
|
||||
#ifdef CONFIG_EXAMPLE_BONDING
|
||||
ble_hs_cfg.sm_bonding = 1;
|
||||
#endif
|
||||
|
||||
int rc = esp_blufi_gatt_svr_init();
|
||||
assert(rc == 0);
|
||||
|
||||
ble_store_config_init();
|
||||
esp_blufi_btc_init();
|
||||
|
||||
esp_err_t err = esp_nimble_enable(_nimble_host_task);
|
||||
if (err) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s failed: %s", __func__, esp_err_to_name(err));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_deinit(void) {
|
||||
esp_err_t ret = nimble_port_stop();
|
||||
if (ret == ESP_OK) {
|
||||
esp_nimble_deinit();
|
||||
}
|
||||
esp_blufi_gatt_svr_deinit();
|
||||
ret = esp_blufi_profile_deinit();
|
||||
esp_blufi_btc_deinit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_gap_register_callback(void) { return ESP_OK; }
|
||||
|
||||
esp_err_t Blufi::_host_and_cb_init() {
|
||||
static esp_blufi_callbacks_t blufi_callbacks = {
|
||||
.event_cb = &_event_callback_trampoline,
|
||||
.negotiate_data_handler = &_negotiate_data_handler_trampoline,
|
||||
.encrypt_func = &_encrypt_func_trampoline,
|
||||
.decrypt_func = &_decrypt_func_trampoline,
|
||||
.checksum_func = &_checksum_func_trampoline,
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_blufi_register_callbacks(&blufi_callbacks);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s blufi register failed, error code = %x", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Host init must be called after registering callbacks for NimBLE
|
||||
ret = _host_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s initialise host failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif /* CONFIG_BT_NIMBLE_ENABLED */
|
||||
|
||||
#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
|
||||
esp_err_t Blufi::_controller_init() {
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
esp_err_t ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
ret = esp_nimble_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "esp_nimble_init() failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_controller_deinit() {
|
||||
esp_err_t ret = esp_bt_controller_disable();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s disable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
}
|
||||
ret = esp_bt_controller_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s deinit controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int myrand(void* rng_state, unsigned char* output, size_t len) {
|
||||
esp_fill_random(output, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Blufi::_security_init() {
|
||||
m_sec = new BlufiSecurity();
|
||||
if (m_sec == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to allocate security context");
|
||||
return;
|
||||
}
|
||||
memset(m_sec, 0, sizeof(BlufiSecurity));
|
||||
m_sec->dhm = new mbedtls_dhm_context();
|
||||
m_sec->aes = new mbedtls_aes_context();
|
||||
|
||||
mbedtls_dhm_init(m_sec->dhm);
|
||||
mbedtls_aes_init(m_sec->aes);
|
||||
|
||||
memset(m_sec->iv, 0x0, sizeof(m_sec->iv));
|
||||
}
|
||||
|
||||
void Blufi::_security_deinit() {
|
||||
if (m_sec == nullptr)
|
||||
return;
|
||||
|
||||
if (m_sec->dh_param) {
|
||||
free(m_sec->dh_param);
|
||||
}
|
||||
mbedtls_dhm_free(m_sec->dhm);
|
||||
mbedtls_aes_free(m_sec->aes);
|
||||
delete m_sec->dhm;
|
||||
delete m_sec->aes;
|
||||
delete m_sec;
|
||||
m_sec = nullptr;
|
||||
}
|
||||
|
||||
void Blufi::_dh_negotiate_data_handler(uint8_t* data, int len, uint8_t** output_data,
|
||||
int* output_len, bool* need_free) {
|
||||
if (m_sec == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "Security not initialized in DH handler");
|
||||
btc_blufi_report_error(ESP_BLUFI_INIT_SECURITY_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < 1) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH handler: data too short");
|
||||
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t type = data[0];
|
||||
switch (type) {
|
||||
case 0x00:
|
||||
if (len < 3) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH_PARAM_LEN packet too short");
|
||||
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
m_sec->dh_param_len = (data[1] << 8) | data[2];
|
||||
if (m_sec->dh_param) {
|
||||
free(m_sec->dh_param);
|
||||
m_sec->dh_param = nullptr;
|
||||
}
|
||||
m_sec->dh_param = (uint8_t*)malloc(m_sec->dh_param_len);
|
||||
if (m_sec->dh_param == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH malloc failed");
|
||||
btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR);
|
||||
}
|
||||
break;
|
||||
case 0x01: {
|
||||
if (m_sec->dh_param == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH param not allocated");
|
||||
btc_blufi_report_error(ESP_BLUFI_DH_PARAM_ERROR);
|
||||
return;
|
||||
}
|
||||
uint8_t* param = m_sec->dh_param;
|
||||
memcpy(m_sec->dh_param, &data[1], m_sec->dh_param_len);
|
||||
int ret = mbedtls_dhm_read_params(m_sec->dhm, ¶m, ¶m[m_sec->dh_param_len]);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_read_params failed %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_READ_PARAM_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const int dhm_len = mbedtls_dhm_get_len(m_sec->dhm);
|
||||
|
||||
ret = mbedtls_dhm_make_public(m_sec->dhm, dhm_len, m_sec->self_public_key, dhm_len,
|
||||
myrand, NULL);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_make_public failed: %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_MAKE_PUBLIC_ERROR);
|
||||
return;
|
||||
}
|
||||
ret = mbedtls_dhm_calc_secret(m_sec->dhm, m_sec->share_key, SHARE_KEY_LEN,
|
||||
&m_sec->share_len, myrand, NULL);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_calc_secret failed: %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_ENCRYPT_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mbedtls_md5(m_sec->share_key, m_sec->share_len, m_sec->psk);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_md5 failed: %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_CALC_MD5_ERROR);
|
||||
return;
|
||||
}
|
||||
ret = mbedtls_aes_setkey_enc(m_sec->aes, m_sec->psk, PSK_LEN * 8);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_aes_setkey_enc failed: -0x%04X", -ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_ENCRYPT_ERROR);
|
||||
return;
|
||||
}
|
||||
*output_data = m_sec->self_public_key;
|
||||
*output_len = dhm_len;
|
||||
*need_free = false;
|
||||
ESP_LOGI(BLUFI_TAG, "DH negotiation completed successfully");
|
||||
|
||||
free(m_sec->dh_param);
|
||||
m_sec->dh_param = nullptr;
|
||||
m_sec->dh_param_len = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BLUFI_TAG, "DH handler unknown type: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
int Blufi::_aes_encrypt(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
|
||||
if (!m_sec || !m_sec->aes || !crypt_data || crypt_len <= 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "Invalid parameters for AES encryption");
|
||||
return -ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t iv_offset = 0;
|
||||
uint8_t iv0[16];
|
||||
memcpy(iv0, m_sec->iv, 16);
|
||||
iv0[0] = iv8;
|
||||
int ret = mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0,
|
||||
crypt_data, crypt_data);
|
||||
|
||||
if (ret == 0) {
|
||||
return crypt_len;
|
||||
} else {
|
||||
ESP_LOGE(BLUFI_TAG, "AES encrypt failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
int Blufi::_aes_decrypt(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
|
||||
if (!m_sec || !m_sec->aes || !crypt_data || crypt_len < 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "Invalid parameters for AES decryption %p %p %d", m_sec->aes,
|
||||
crypt_data, crypt_len);
|
||||
return -ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t iv_offset = 0;
|
||||
uint8_t iv0[16];
|
||||
memcpy(iv0, m_sec->iv, 16);
|
||||
iv0[0] = iv8;
|
||||
int ret = mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0,
|
||||
crypt_data, crypt_data);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "AES decrypt failed: %d", ret);
|
||||
return ret;
|
||||
} else {
|
||||
return crypt_len;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Blufi::_crc_checksum(uint8_t iv8, uint8_t* data, int len) {
|
||||
return esp_crc16_be(0, data, len);
|
||||
}
|
||||
|
||||
int Blufi::_get_softap_conn_num() {
|
||||
auto& wifi = WifiManager::GetInstance();
|
||||
if (!wifi.IsInitialized() || !wifi.IsConfigMode()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
wifi_sta_list_t sta_list{};
|
||||
if (esp_wifi_ap_get_sta_list(&sta_list) == ESP_OK) {
|
||||
return sta_list.num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Blufi::start_wifi_scan() {
|
||||
ESP_LOGI(BLUFI_TAG, "Starting dedicated WiFi scan");
|
||||
|
||||
// Check if a scan is already in progress
|
||||
if (m_scan_in_progress) {
|
||||
ESP_LOGW(BLUFI_TAG, "Scan already in progress, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
m_scan_in_progress = true;
|
||||
|
||||
// Get current WiFi mode
|
||||
wifi_mode_t current_mode;
|
||||
esp_err_t err = esp_wifi_get_mode(¤t_mode);
|
||||
|
||||
if (current_mode == WIFI_MODE_AP) {
|
||||
// If in AP mode, temporarily switch to APSTA to allow scanning
|
||||
ESP_LOGI(BLUFI_TAG, "WiFi in AP mode");
|
||||
err = esp_wifi_set_mode(WIFI_MODE_STA);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to set WiFi mode to STA: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
}
|
||||
// Need to restart WiFi for mode change to take effect
|
||||
err = esp_wifi_start();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to start WiFi after mode switch: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
}
|
||||
// Register scan event handler
|
||||
esp_event_handler_instance_t scan_event_instance;
|
||||
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
|
||||
&Blufi::_wifi_scan_event_handler, this,
|
||||
&scan_event_instance);
|
||||
|
||||
// Start scan
|
||||
err = esp_wifi_scan_start(NULL, false);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
}
|
||||
} else if (current_mode == WIFI_MODE_STA) {
|
||||
// Start scan
|
||||
err = esp_wifi_scan_start(NULL, false);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(BLUFI_TAG, "Unexpected WiFi mode: %d", current_mode);
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLUFI_TAG, "WiFi scan started");
|
||||
}
|
||||
|
||||
void Blufi::_send_wifi_list() {
|
||||
if (m_ap_records.empty()) {
|
||||
ESP_LOGW(BLUFI_TAG, "No AP records available to send");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLUFI_TAG, "Sending WiFi list with %d APs", m_ap_records.size());
|
||||
|
||||
std::vector<esp_blufi_ap_record_t> blufi_ap_list;
|
||||
for (const auto& ap : m_ap_records) {
|
||||
esp_blufi_ap_record_t blufi_ap;
|
||||
memset(&blufi_ap, 0, sizeof(blufi_ap));
|
||||
memcpy(blufi_ap.ssid, ap.ssid, std::min((size_t)32, sizeof(ap.ssid)));
|
||||
blufi_ap.rssi = ap.rssi;
|
||||
blufi_ap_list.push_back(blufi_ap);
|
||||
}
|
||||
|
||||
esp_blufi_send_wifi_list(blufi_ap_list.size(), blufi_ap_list.data());
|
||||
|
||||
m_ap_records.clear();
|
||||
start_wifi_scan();
|
||||
}
|
||||
|
||||
void Blufi::_wifi_scan_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data) {
|
||||
Blufi* self = static_cast<Blufi*>(arg);
|
||||
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
|
||||
ESP_LOGI(BLUFI_TAG, "WiFi scan done");
|
||||
|
||||
uint16_t ap_num = 0;
|
||||
esp_wifi_scan_get_ap_num(&ap_num);
|
||||
|
||||
if (ap_num == 0) {
|
||||
ESP_LOGW(BLUFI_TAG, "No APs found");
|
||||
self->m_ap_records.clear();
|
||||
} else {
|
||||
if (static_cast<Blufi*>(arg)->m_scan_should_save_ssid == true) {
|
||||
self->m_ap_records.resize(ap_num);
|
||||
esp_wifi_scan_get_ap_records(&ap_num, self->m_ap_records.data());
|
||||
|
||||
ESP_LOGI(BLUFI_TAG, "Found %d APs", ap_num);
|
||||
for (const auto& ap : self->m_ap_records) {
|
||||
ESP_LOGI(BLUFI_TAG, " SSID: %s, RSSI: %d, Authmode: %d", (char*)ap.ssid,
|
||||
ap.rssi, ap.authmode);
|
||||
}
|
||||
}
|
||||
}
|
||||
self->m_scan_in_progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* param) {
|
||||
switch (event) {
|
||||
case ESP_BLUFI_EVENT_INIT_FINISH:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI init finish");
|
||||
esp_ble_gap_set_device_name(BLUFI_DEVICE_NAME);
|
||||
esp_blufi_adv_start();
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_DEINIT_FINISH:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI deinit finish");
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_BLE_CONNECT:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI ble connect");
|
||||
m_ble_is_connected = true;
|
||||
esp_blufi_adv_stop();
|
||||
_security_init();
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_BLE_DISCONNECT:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI ble disconnect");
|
||||
m_ble_is_connected = false;
|
||||
_security_deinit();
|
||||
if (!m_provisioned) {
|
||||
esp_blufi_adv_start();
|
||||
} else {
|
||||
esp_blufi_adv_stop();
|
||||
if (!m_deinited) {
|
||||
xTaskCreate(
|
||||
[](void* ctx) {
|
||||
static_cast<Blufi*>(ctx)->deinit();
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"blufi_deinit", 4096, this, 5, nullptr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: {
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI Set WIFI opmode %d", param->wifi_mode.op_mode);
|
||||
auto& wifi_manager = WifiManager::GetInstance();
|
||||
if (!wifi_manager.IsInitialized() && !wifi_manager.Initialize()) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to initialize WifiManager for opmode change");
|
||||
break;
|
||||
}
|
||||
switch (param->wifi_mode.op_mode) {
|
||||
case WIFI_MODE_STA:
|
||||
wifi_manager.StartStation();
|
||||
break;
|
||||
case WIFI_MODE_AP:
|
||||
wifi_manager.StartConfigAp();
|
||||
break;
|
||||
case WIFI_MODE_APSTA:
|
||||
ESP_LOGW(BLUFI_TAG, "APSTA mode not supported, starting station only");
|
||||
wifi_manager.StartStation();
|
||||
break;
|
||||
default:
|
||||
wifi_manager.StopStation();
|
||||
wifi_manager.StopConfigAp();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: {
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI request wifi connect to AP via esp-wifi-connect");
|
||||
std::string ssid(reinterpret_cast<const char*>(m_sta_config.sta.ssid));
|
||||
std::string password(reinterpret_cast<const char*>(m_sta_config.sta.password));
|
||||
|
||||
SsidManager::GetInstance().AddSsid(ssid, password);
|
||||
m_scan_should_save_ssid = false;
|
||||
|
||||
m_sta_ssid_len = static_cast<int>(std::min(ssid.size(), sizeof(m_sta_ssid)));
|
||||
memcpy(m_sta_ssid, ssid.c_str(), m_sta_ssid_len);
|
||||
memset(m_sta_bssid, 0, sizeof(m_sta_bssid));
|
||||
m_sta_connected = false;
|
||||
m_sta_got_ip = false;
|
||||
m_sta_is_connecting = true;
|
||||
m_sta_conn_info = {};
|
||||
m_sta_conn_info.sta_ssid = m_sta_ssid;
|
||||
m_sta_conn_info.sta_ssid_len = m_sta_ssid_len;
|
||||
|
||||
auto& wifi_manager = WifiManager::GetInstance();
|
||||
|
||||
if (wifi_manager.IsInitialized()) {
|
||||
if (wifi_manager.IsConfigMode()) {
|
||||
wifi_manager.StopConfigAp();
|
||||
}
|
||||
wifi_manager.StopStation();
|
||||
}
|
||||
|
||||
if (!wifi_manager.IsInitialized() && !wifi_manager.Initialize()) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to initialize WifiManager");
|
||||
break;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
wifi_manager.StartStation();
|
||||
|
||||
xTaskCreate(
|
||||
[](void* ctx) {
|
||||
auto* self = static_cast<Blufi*>(ctx);
|
||||
auto& wifi = WifiManager::GetInstance();
|
||||
constexpr int kConnectTimeoutMs = 10000;
|
||||
constexpr TickType_t kDelayTick = pdMS_TO_TICKS(200);
|
||||
int waited_ms = 0;
|
||||
|
||||
while (waited_ms < kConnectTimeoutMs && !wifi.IsConnected()) {
|
||||
vTaskDelay(kDelayTick);
|
||||
waited_ms += 200;
|
||||
}
|
||||
|
||||
wifi_mode_t mode = GetWifiModeWithFallback(wifi);
|
||||
const int softap_conn_num = _get_softap_conn_num();
|
||||
|
||||
if (wifi.IsConnected()) {
|
||||
self->m_sta_is_connecting = false;
|
||||
self->m_sta_connected = true;
|
||||
self->m_sta_got_ip = true;
|
||||
self->m_provisioned = true;
|
||||
|
||||
auto current_ssid = wifi.GetSsid();
|
||||
if (!current_ssid.empty()) {
|
||||
self->m_sta_ssid_len = static_cast<int>(
|
||||
std::min(current_ssid.size(), sizeof(self->m_sta_ssid)));
|
||||
memcpy(self->m_sta_ssid, current_ssid.c_str(), self->m_sta_ssid_len);
|
||||
}
|
||||
|
||||
wifi_ap_record_t ap_info{};
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
memcpy(self->m_sta_bssid, ap_info.bssid, sizeof(self->m_sta_bssid));
|
||||
}
|
||||
|
||||
esp_blufi_extra_info_t info = {};
|
||||
memcpy(info.sta_bssid, self->m_sta_bssid, sizeof(self->m_sta_bssid));
|
||||
info.sta_bssid_set = true;
|
||||
info.sta_ssid = self->m_sta_ssid;
|
||||
info.sta_ssid_len = self->m_sta_ssid_len;
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS,
|
||||
softap_conn_num, &info);
|
||||
ESP_LOGI(BLUFI_TAG, "connected to WiFi");
|
||||
|
||||
if (self->m_ble_is_connected) {
|
||||
esp_blufi_disconnect();
|
||||
}
|
||||
} else {
|
||||
self->m_sta_is_connecting = false;
|
||||
self->m_sta_connected = false;
|
||||
self->m_sta_got_ip = false;
|
||||
|
||||
esp_blufi_extra_info_t info = {};
|
||||
info.sta_ssid = self->m_sta_ssid;
|
||||
info.sta_ssid_len = self->m_sta_ssid_len;
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL,
|
||||
softap_conn_num, &info);
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to connect to WiFi via esp-wifi-connect");
|
||||
}
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"blufi_wifi_conn", 4096, this, 5, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI request wifi disconnect from AP");
|
||||
if (WifiManager::GetInstance().IsInitialized()) {
|
||||
WifiManager::GetInstance().StopStation();
|
||||
}
|
||||
m_sta_is_connecting = false;
|
||||
m_sta_connected = false;
|
||||
m_sta_got_ip = false;
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_GET_WIFI_STATUS: {
|
||||
auto& wifi = WifiManager::GetInstance();
|
||||
wifi_mode_t mode = GetWifiModeWithFallback(wifi);
|
||||
const int softap_conn_num = _get_softap_conn_num();
|
||||
|
||||
if (wifi.IsInitialized() && wifi.IsConnected()) {
|
||||
m_sta_connected = true;
|
||||
m_sta_got_ip = true;
|
||||
|
||||
auto current_ssid = wifi.GetSsid();
|
||||
if (!current_ssid.empty()) {
|
||||
m_sta_ssid_len =
|
||||
static_cast<int>(std::min(current_ssid.size(), sizeof(m_sta_ssid)));
|
||||
memcpy(m_sta_ssid, current_ssid.c_str(), m_sta_ssid_len);
|
||||
}
|
||||
|
||||
esp_blufi_extra_info_t info;
|
||||
memset(&info, 0, sizeof(esp_blufi_extra_info_t));
|
||||
memcpy(info.sta_bssid, m_sta_bssid, 6);
|
||||
info.sta_ssid = m_sta_ssid;
|
||||
info.sta_ssid_len = m_sta_ssid_len;
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, softap_conn_num,
|
||||
&info);
|
||||
} else if (m_sta_is_connecting) {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONNECTING, softap_conn_num,
|
||||
&m_sta_conn_info);
|
||||
} else {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, softap_conn_num,
|
||||
&m_sta_conn_info);
|
||||
}
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI get wifi status");
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_RECV_STA_BSSID:
|
||||
memcpy(m_sta_config.sta.bssid, param->sta_bssid.bssid, 6);
|
||||
m_sta_config.sta.bssid_set = true;
|
||||
ESP_LOGI(BLUFI_TAG, "Recv STA BSSID");
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_STA_SSID:
|
||||
strncpy((char*)m_sta_config.sta.ssid, (char*)param->sta_ssid.ssid,
|
||||
param->sta_ssid.ssid_len);
|
||||
m_sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0';
|
||||
ESP_LOGI(BLUFI_TAG, "Recv STA SSID: %s", m_sta_config.sta.ssid);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_STA_PASSWD:
|
||||
strncpy((char*)m_sta_config.sta.password, (char*)param->sta_passwd.passwd,
|
||||
param->sta_passwd.passwd_len);
|
||||
m_sta_config.sta.password[param->sta_passwd.passwd_len] = '\0';
|
||||
ESP_LOGI(BLUFI_TAG, "Recv STA PASSWORD : %s", m_sta_config.sta.password);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_GET_WIFI_LIST: {
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI get wifi list");
|
||||
while (m_scan_in_progress) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
_send_wifi_list();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(BLUFI_TAG, "Unhandled event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Blufi::_event_callback_trampoline(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* param) {
|
||||
GetInstance()._handle_event(event, param);
|
||||
}
|
||||
|
||||
void Blufi::_negotiate_data_handler_trampoline(uint8_t* data, int len, uint8_t** output_data,
|
||||
int* output_len, bool* need_free) {
|
||||
GetInstance()._dh_negotiate_data_handler(data, len, output_data, output_len, need_free);
|
||||
}
|
||||
|
||||
int Blufi::_encrypt_func_trampoline(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
|
||||
return GetInstance()._aes_encrypt(iv8, crypt_data, crypt_len);
|
||||
}
|
||||
|
||||
int Blufi::_decrypt_func_trampoline(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
|
||||
return GetInstance()._aes_decrypt(iv8, crypt_data, crypt_len);
|
||||
}
|
||||
|
||||
uint16_t Blufi::_checksum_func_trampoline(uint8_t iv8, uint8_t* data, int len) {
|
||||
return _crc_checksum(iv8, data, len);
|
||||
}
|
||||
147
main/boards/common/blufi.h
Normal file
147
main/boards/common/blufi.h
Normal file
@@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#include <aes/esp_aes.h>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include "esp_blufi_api.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "mbedtls/aes.h"
|
||||
#include "mbedtls/dhm.h"
|
||||
#include "wifi_manager.h"
|
||||
|
||||
class Blufi {
|
||||
public:
|
||||
/**
|
||||
* @brief Get the singleton instance of the Blufi class.
|
||||
*/
|
||||
static Blufi &GetInstance();
|
||||
|
||||
/**
|
||||
* @brief Start WiFi scan for Blufi provisioning
|
||||
* This method intelligently handles WiFi scanning based on current WiFi state:
|
||||
* - If WiFi config mode is active, it uses the existing scan results from WifiConfigurationAp
|
||||
* - Otherwise, it performs a dedicated scan without interfering with normal WiFi operations
|
||||
*/
|
||||
void start_wifi_scan();
|
||||
|
||||
/**
|
||||
* @brief Initializes the Bluetooth controller, host, and Blufi profile.
|
||||
* This is the main entry point to start the Blufi process.
|
||||
* @return ESP_OK on success, otherwise an error code.
|
||||
*/
|
||||
esp_err_t init();
|
||||
|
||||
/**
|
||||
* @brief Deinitializes Blufi and the Bluetooth stack.
|
||||
* @return ESP_OK on success, otherwise an error code.
|
||||
*/
|
||||
esp_err_t deinit();
|
||||
|
||||
// Delete copy constructor and assignment operator for singleton
|
||||
Blufi(const Blufi &) = delete;
|
||||
|
||||
Blufi &operator=(const Blufi &) = delete;
|
||||
|
||||
private:
|
||||
bool inited_ = false;
|
||||
|
||||
Blufi();
|
||||
|
||||
~Blufi();
|
||||
|
||||
// Initialization logic
|
||||
static esp_err_t _controller_init();
|
||||
|
||||
static esp_err_t _controller_deinit();
|
||||
|
||||
static esp_err_t _host_init();
|
||||
|
||||
static esp_err_t _host_deinit();
|
||||
|
||||
static esp_err_t _gap_register_callback();
|
||||
|
||||
static esp_err_t _host_and_cb_init();
|
||||
|
||||
void _security_init();
|
||||
|
||||
void _security_deinit();
|
||||
|
||||
void _dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len,
|
||||
bool *need_free);
|
||||
|
||||
int _aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
int _aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
static uint16_t _crc_checksum(uint8_t iv8, uint8_t *data, int len);
|
||||
|
||||
void _handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);
|
||||
|
||||
static int _get_softap_conn_num();
|
||||
|
||||
// WiFi scan methods
|
||||
void _send_wifi_list();
|
||||
void _start_dedicated_wifi_scan();
|
||||
static void _wifi_scan_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void *event_data);
|
||||
|
||||
// These C-style functions are registered with ESP-IDF and call the corresponding instance
|
||||
// methods.
|
||||
|
||||
static void _event_callback_trampoline(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);
|
||||
|
||||
static void _negotiate_data_handler_trampoline(uint8_t *data, int len, uint8_t **output_data,
|
||||
int *output_len, bool *need_free);
|
||||
|
||||
static int _encrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
static int _decrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
static uint16_t _checksum_func_trampoline(uint8_t iv8, uint8_t *data, int len);
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
static void _nimble_on_reset(int reason);
|
||||
static void _nimble_on_sync();
|
||||
static void _nimble_host_task(void *param);
|
||||
#endif
|
||||
|
||||
// Security context, formerly blufi_sec struct
|
||||
struct BlufiSecurity {
|
||||
#define DH_SELF_PUB_KEY_LEN 128
|
||||
uint8_t self_public_key[DH_SELF_PUB_KEY_LEN];
|
||||
#define SHARE_KEY_LEN 128
|
||||
uint8_t share_key[SHARE_KEY_LEN];
|
||||
size_t share_len;
|
||||
#define PSK_LEN 16
|
||||
uint8_t psk[PSK_LEN];
|
||||
uint8_t *dh_param;
|
||||
int dh_param_len;
|
||||
uint8_t iv[16];
|
||||
mbedtls_dhm_context *dhm;
|
||||
esp_aes_context *aes;
|
||||
};
|
||||
|
||||
BlufiSecurity *m_sec;
|
||||
|
||||
// State variables
|
||||
wifi_config_t m_sta_config{};
|
||||
bool m_ble_is_connected;
|
||||
bool m_sta_connected;
|
||||
bool m_sta_got_ip;
|
||||
bool m_provisioned;
|
||||
bool m_deinited;
|
||||
uint8_t m_sta_bssid[6]{};
|
||||
uint8_t m_sta_ssid[32]{};
|
||||
int m_sta_ssid_len;
|
||||
bool m_sta_is_connecting;
|
||||
esp_blufi_extra_info_t m_sta_conn_info{};
|
||||
|
||||
// WiFi scan related
|
||||
std::vector<wifi_ap_record_t> m_ap_records;
|
||||
bool m_scan_in_progress = false;
|
||||
bool m_scan_should_save_ssid = true;
|
||||
};
|
||||
178
main/boards/common/board.cc
Normal file
178
main/boards/common/board.cc
Normal file
@@ -0,0 +1,178 @@
|
||||
#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>
|
||||
|
||||
#define TAG "Board"
|
||||
|
||||
Board::Board() {
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
92
main/boards/common/board.h
Normal file
92
main/boards/common/board.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef BOARD_H
|
||||
#define BOARD_H
|
||||
|
||||
#include <http.h>
|
||||
#include <web_socket.h>
|
||||
#include <mqtt.h>
|
||||
#include <udp.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <network_interface.h>
|
||||
|
||||
#include "led/led.h"
|
||||
#include "backlight.h"
|
||||
#include "camera.h"
|
||||
#include "assets.h"
|
||||
|
||||
/**
|
||||
* Network events for unified callback
|
||||
*/
|
||||
enum class NetworkEvent {
|
||||
Scanning, // Network is scanning (WiFi scanning, etc.)
|
||||
Connecting, // Network is connecting (data: SSID/network name)
|
||||
Connected, // Network connected successfully (data: SSID/network name)
|
||||
Disconnected, // Network disconnected
|
||||
WifiConfigModeEnter, // Entered WiFi configuration mode
|
||||
WifiConfigModeExit, // Exited WiFi configuration mode
|
||||
// Cellular modem specific events
|
||||
ModemDetecting, // Detecting modem (baud rate, module type)
|
||||
ModemErrorNoSim, // No SIM card detected
|
||||
ModemErrorRegDenied, // Network registration denied
|
||||
ModemErrorInitFailed, // Modem initialization failed
|
||||
ModemErrorTimeout // Operation timeout
|
||||
};
|
||||
|
||||
// Power save level enumeration
|
||||
enum class PowerSaveLevel {
|
||||
LOW_POWER, // Maximum power saving (lowest power consumption)
|
||||
BALANCED, // Medium power saving (balanced)
|
||||
PERFORMANCE, // No power saving (maximum power consumption / full performance)
|
||||
};
|
||||
|
||||
// Network event callback type (event, data)
|
||||
// data contains additional info like SSID for Connecting/Connected events
|
||||
using NetworkEventCallback = std::function<void(NetworkEvent event, const std::string& data)>;
|
||||
|
||||
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_;
|
||||
|
||||
public:
|
||||
static Board& GetInstance() {
|
||||
static Board* instance = static_cast<Board*>(create_board());
|
||||
return *instance;
|
||||
}
|
||||
|
||||
virtual ~Board() = default;
|
||||
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 NetworkInterface* GetNetwork() = 0;
|
||||
virtual void StartNetwork() = 0;
|
||||
virtual void SetNetworkEventCallback(NetworkEventCallback callback) { (void)callback; }
|
||||
virtual const char* GetNetworkStateIcon() = 0;
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging);
|
||||
virtual std::string GetSystemInfoJson();
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) = 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
|
||||
125
main/boards/common/button.cc
Normal file
125
main/boards/common/button.cc
Normal file
@@ -0,0 +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);
|
||||
}
|
||||
49
main/boards/common/button.h
Normal file
49
main/boards/common/button.h
Normal file
@@ -0,0 +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_
|
||||
16
main/boards/common/camera.h
Normal file
16
main/boards/common/camera.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#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 bool SetSwapBytes(bool enabled) { return false; } // Optional, default no-op
|
||||
virtual std::string Explain(const std::string& question) = 0;
|
||||
};
|
||||
|
||||
#endif // CAMERA_H
|
||||
98
main/boards/common/dual_network_board.cc
Normal file
98
main/boards/common/dual_network_board.cc
Normal file
@@ -0,0 +1,98 @@
|
||||
#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();
|
||||
}
|
||||
|
||||
void DualNetworkBoard::SetNetworkEventCallback(NetworkEventCallback callback) {
|
||||
// Forward the callback to the current board
|
||||
current_board_->SetNetworkEventCallback(std::move(callback));
|
||||
}
|
||||
|
||||
NetworkInterface* DualNetworkBoard::GetNetwork() {
|
||||
return current_board_->GetNetwork();
|
||||
}
|
||||
|
||||
const char* DualNetworkBoard::GetNetworkStateIcon() {
|
||||
return current_board_->GetNetworkStateIcon();
|
||||
}
|
||||
|
||||
void DualNetworkBoard::SetPowerSaveLevel(PowerSaveLevel level) {
|
||||
current_board_->SetPowerSaveLevel(level);
|
||||
}
|
||||
|
||||
std::string DualNetworkBoard::GetBoardJson() {
|
||||
return current_board_->GetBoardJson();
|
||||
}
|
||||
|
||||
std::string DualNetworkBoard::GetDeviceStatusJson() {
|
||||
return current_board_->GetDeviceStatusJson();
|
||||
}
|
||||
60
main/boards/common/dual_network_board.h
Normal file
60
main/boards/common/dual_network_board.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#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 void SetNetworkEventCallback(NetworkEventCallback callback) override;
|
||||
virtual NetworkInterface* GetNetwork() override;
|
||||
virtual const char* GetNetworkStateIcon() override;
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
|
||||
virtual std::string GetBoardJson() override;
|
||||
virtual std::string GetDeviceStatusJson() override;
|
||||
};
|
||||
|
||||
#endif // DUAL_NETWORK_BOARD_H
|
||||
322
main/boards/common/esp32_camera.cc
Normal file
322
main/boards/common/esp32_camera.cc
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <esp_heap_caps.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <esp_log.h>
|
||||
#include <img_converters.h>
|
||||
|
||||
#include "esp32_camera.h"
|
||||
#include "board.h"
|
||||
#include "display.h"
|
||||
#include "lvgl_display.h"
|
||||
#include "mcp_server.h"
|
||||
#include "system_info.h"
|
||||
#include "jpg/image_to_jpeg.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#define TAG "Esp32Camera"
|
||||
|
||||
Esp32Camera::Esp32Camera(const camera_config_t &config) {
|
||||
esp_err_t err = esp_camera_init(&config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_camera_init failed with error 0x%x", err);
|
||||
return;
|
||||
}
|
||||
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) {
|
||||
if (s->id.PID == GC0308_PID) {
|
||||
s->set_hmirror(s, 0); // Control camera mirror: 1 for mirror, 0 for normal
|
||||
}
|
||||
ESP_LOGI(TAG, "Camera initialized: format=%d", config.pixel_format);
|
||||
}
|
||||
|
||||
streaming_on_ = true;
|
||||
}
|
||||
|
||||
Esp32Camera::~Esp32Camera() {
|
||||
if (streaming_on_) {
|
||||
if (current_fb_) {
|
||||
esp_camera_fb_return(current_fb_);
|
||||
current_fb_ = nullptr;
|
||||
}
|
||||
if (encode_buf_) {
|
||||
heap_caps_free(encode_buf_);
|
||||
encode_buf_ = nullptr;
|
||||
encode_buf_size_ = 0;
|
||||
}
|
||||
esp_camera_deinit();
|
||||
streaming_on_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (!streaming_on_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the latest frame, discard old frames for real-time performance
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (current_fb_) {
|
||||
esp_camera_fb_return(current_fb_);
|
||||
}
|
||||
current_fb_ = esp_camera_fb_get();
|
||||
if (!current_fb_) {
|
||||
ESP_LOGE(TAG, "Camera capture failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare encode buffer for RGB565 format (with optional byte swapping)
|
||||
if (current_fb_->format == PIXFORMAT_RGB565) {
|
||||
size_t pixel_count = current_fb_->width * current_fb_->height;
|
||||
size_t data_size = pixel_count * 2;
|
||||
|
||||
// Allocate or reallocate encode buffer if needed
|
||||
if (encode_buf_size_ < data_size) {
|
||||
if (encode_buf_) {
|
||||
heap_caps_free(encode_buf_);
|
||||
}
|
||||
encode_buf_ = (uint8_t *)heap_caps_malloc(data_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (encode_buf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for encode buffer");
|
||||
encode_buf_size_ = 0;
|
||||
return false;
|
||||
}
|
||||
encode_buf_size_ = data_size;
|
||||
}
|
||||
|
||||
// Copy data to encode buffer with optional byte swapping
|
||||
uint16_t *src = (uint16_t *)current_fb_->buf;
|
||||
uint16_t *dst = (uint16_t *)encode_buf_;
|
||||
if (swap_bytes_enabled_) {
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
dst[i] = __builtin_bswap16(src[i]);
|
||||
}
|
||||
} else {
|
||||
memcpy(encode_buf_, current_fb_->buf, data_size);
|
||||
}
|
||||
|
||||
// Allocate separate buffer for preview display
|
||||
uint8_t *preview_data = (uint8_t *)heap_caps_malloc(data_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (preview_data != nullptr) {
|
||||
memcpy(preview_data, encode_buf_, data_size);
|
||||
auto display = dynamic_cast<LvglDisplay *>(Board::GetInstance().GetDisplay());
|
||||
if (display != nullptr) {
|
||||
display->SetPreviewImage(std::make_unique<LvglAllocatedImage>(preview_data, data_size, current_fb_->width, current_fb_->height, current_fb_->width * 2, LV_COLOR_FORMAT_RGB565));
|
||||
} else {
|
||||
heap_caps_free(preview_data);
|
||||
}
|
||||
}
|
||||
} else if (current_fb_->format == PIXFORMAT_JPEG) {
|
||||
// JPEG format preview usually requires decoding, skip preview display for now, just log
|
||||
ESP_LOGW(TAG, "JPEG capture success, len=%zu, but not supported for preview", current_fb_->len);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Captured frame: %dx%d, len=%zu, format=%d",
|
||||
current_fb_->width, current_fb_->height, current_fb_->len, current_fb_->format);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Esp32Camera::SetHMirror(bool enabled) {
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (!s) {
|
||||
return false;
|
||||
}
|
||||
s->set_hmirror(s, enabled ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Esp32Camera::SetVFlip(bool enabled) {
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (!s) {
|
||||
return false;
|
||||
}
|
||||
s->set_vflip(s, enabled ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Esp32Camera::SetSwapBytes(bool enabled) {
|
||||
swap_bytes_enabled_ = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Esp32Camera::Explain(const std::string &question) {
|
||||
if (explain_url_.empty()) {
|
||||
throw std::runtime_error("Image explain URL or token is not set");
|
||||
}
|
||||
|
||||
if (current_fb_ == nullptr) {
|
||||
throw std::runtime_error("No camera frame captured");
|
||||
}
|
||||
|
||||
// Create local JPEG queue
|
||||
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");
|
||||
}
|
||||
|
||||
// Start encoding thread
|
||||
encoder_thread_ = std::thread([this, jpeg_queue]() {
|
||||
int64_t start_time = esp_timer_get_time();
|
||||
uint16_t w = current_fb_->width;
|
||||
uint16_t h = current_fb_->height;
|
||||
v4l2_pix_fmt_t enc_fmt;
|
||||
switch (current_fb_->format) {
|
||||
case PIXFORMAT_RGB565:
|
||||
enc_fmt = V4L2_PIX_FMT_RGB565;
|
||||
break;
|
||||
case PIXFORMAT_YUV422:
|
||||
enc_fmt = V4L2_PIX_FMT_YUYV; // YUV422 is actually YUYV format
|
||||
break;
|
||||
case PIXFORMAT_YUV420:
|
||||
enc_fmt = V4L2_PIX_FMT_YUV420;
|
||||
break;
|
||||
case PIXFORMAT_GRAYSCALE:
|
||||
enc_fmt = V4L2_PIX_FMT_GREY;
|
||||
break;
|
||||
case PIXFORMAT_JPEG:
|
||||
enc_fmt = V4L2_PIX_FMT_JPEG;
|
||||
break;
|
||||
case PIXFORMAT_RGB888:
|
||||
enc_fmt = V4L2_PIX_FMT_RGB24;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported pixel format: %d", current_fb_->format);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use encode buffer for RGB565, otherwise use original frame buffer
|
||||
uint8_t *jpeg_src_buf = current_fb_->buf;
|
||||
size_t jpeg_src_len = current_fb_->len;
|
||||
if (current_fb_->format == PIXFORMAT_RGB565 && encode_buf_ != nullptr) {
|
||||
jpeg_src_buf = encode_buf_;
|
||||
jpeg_src_len = encode_buf_size_;
|
||||
}
|
||||
|
||||
bool ok = image_to_jpeg_cb(jpeg_src_buf, jpeg_src_len, w, h, enc_fmt, 80,
|
||||
[](void* arg, size_t index, const void* data, size_t len) -> size_t {
|
||||
auto jpeg_queue = static_cast<QueueHandle_t>(arg);
|
||||
JpegChunk chunk = {.data = nullptr, .len = len};
|
||||
if (index == 0 && data != nullptr && len > 0) {
|
||||
chunk.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (chunk.data == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate %zu bytes for JPEG chunk", len);
|
||||
chunk.len = 0;
|
||||
} else {
|
||||
memcpy(chunk.data, data, len);
|
||||
}
|
||||
} else {
|
||||
chunk.len = 0; // Sentinel or error
|
||||
}
|
||||
xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
|
||||
return len;
|
||||
}, jpeg_queue);
|
||||
|
||||
if (!ok) {
|
||||
JpegChunk chunk = {.data = nullptr, .len = 0};
|
||||
xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
|
||||
}
|
||||
int64_t end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "JPEG encoding time: %ld ms", int((end_time - start_time) / 1000));
|
||||
});
|
||||
|
||||
auto network = Board::GetInstance().GetNetwork();
|
||||
auto http = network->CreateHttp(3);
|
||||
std::string boundary = "----ESP32_CAMERA_BOUNDARY";
|
||||
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
size_t total_sent = 0;
|
||||
bool saw_terminator = false;
|
||||
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) {
|
||||
saw_terminator = true;
|
||||
break;
|
||||
}
|
||||
http->Write((const char *)chunk.data, chunk.len);
|
||||
total_sent += chunk.len;
|
||||
heap_caps_free(chunk.data);
|
||||
}
|
||||
encoder_thread_.join();
|
||||
vQueueDelete(jpeg_queue);
|
||||
|
||||
if (!saw_terminator || total_sent == 0) {
|
||||
ESP_LOGE(TAG, "JPEG encoder failed or produced empty output");
|
||||
throw std::runtime_error("Failed to encode image to JPEG");
|
||||
}
|
||||
|
||||
{
|
||||
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();
|
||||
|
||||
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",
|
||||
current_fb_->width, current_fb_->height, (int)total_sent, (int)remain_stack_size, question.c_str(), result.c_str());
|
||||
return result;
|
||||
}
|
||||
44
main/boards/common/esp32_camera.h
Normal file
44
main/boards/common/esp32_camera.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
#include "camera.h"
|
||||
#include "esp_camera.h"
|
||||
#include "jpg/image_to_jpeg.h"
|
||||
|
||||
struct JpegChunk
|
||||
{
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
class Esp32Camera : public Camera
|
||||
{
|
||||
private:
|
||||
bool streaming_on_ = false;
|
||||
bool swap_bytes_enabled_ = true; // Swap pixel byte order for RGB565, enabled by default
|
||||
std::string explain_url_;
|
||||
std::string explain_token_;
|
||||
std::thread encoder_thread_;
|
||||
camera_fb_t *current_fb_ = nullptr;
|
||||
uint8_t *encode_buf_ = nullptr; // Buffer for JPEG encoding (with optional byte swap)
|
||||
size_t encode_buf_size_ = 0;
|
||||
|
||||
public:
|
||||
Esp32Camera(const camera_config_t &config);
|
||||
~Esp32Camera();
|
||||
|
||||
virtual void SetExplainUrl(const std::string &url, const std::string &token) override;
|
||||
virtual bool Capture() override;
|
||||
virtual bool SetHMirror(bool enabled) override;
|
||||
virtual bool SetVFlip(bool enabled) override;
|
||||
virtual bool SetSwapBytes(bool enabled) override;
|
||||
virtual std::string Explain(const std::string &question) override;
|
||||
};
|
||||
1041
main/boards/common/esp_video.cc
Normal file
1041
main/boards/common/esp_video.cc
Normal file
File diff suppressed because it is too large
Load Diff
53
main/boards/common/esp_video.h
Normal file
53
main/boards/common/esp_video.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <lvgl.h>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
#include "camera.h"
|
||||
#include "jpg/image_to_jpeg.h"
|
||||
#include "esp_video_init.h"
|
||||
|
||||
struct JpegChunk {
|
||||
uint8_t* data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
class EspVideo : public Camera {
|
||||
private:
|
||||
struct FrameBuffer {
|
||||
uint8_t *data = nullptr;
|
||||
size_t len = 0;
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
v4l2_pix_fmt_t format = 0;
|
||||
} frame_;
|
||||
v4l2_pix_fmt_t sensor_format_ = 0;
|
||||
#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
|
||||
uint16_t sensor_width_ = 0;
|
||||
uint16_t sensor_height_ = 0;
|
||||
#endif // CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
|
||||
int video_fd_ = -1;
|
||||
bool streaming_on_ = false;
|
||||
struct MmapBuffer { void *start = nullptr; size_t length = 0; };
|
||||
std::vector<MmapBuffer> mmap_buffers_;
|
||||
std::string explain_url_;
|
||||
std::string explain_token_;
|
||||
std::thread encoder_thread_;
|
||||
|
||||
public:
|
||||
EspVideo(const esp_video_init_config_t& config);
|
||||
~EspVideo();
|
||||
|
||||
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);
|
||||
};
|
||||
35
main/boards/common/i2c_device.cc
Normal file
35
main/boards/common/i2c_device.cc
Normal file
@@ -0,0 +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_, ®, 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_, ®, 1, buffer, length, 100));
|
||||
}
|
||||
18
main/boards/common/i2c_device.h
Normal file
18
main/boards/common/i2c_device.h
Normal file
@@ -0,0 +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
|
||||
52
main/boards/common/knob.cc
Normal file
52
main/boards/common/knob.cc
Normal file
@@ -0,0 +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);
|
||||
}
|
||||
}
|
||||
25
main/boards/common/knob.h
Normal file
25
main/boards/common/knob.h
Normal file
@@ -0,0 +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_;
|
||||
};
|
||||
|
||||
#endif // KNOB_H_
|
||||
48
main/boards/common/lamp_controller.h
Normal file
48
main/boards/common/lamp_controller.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#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) {
|
||||
if (gpio_num_ == GPIO_NUM_NC) {
|
||||
return;
|
||||
}
|
||||
|
||||
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__
|
||||
270
main/boards/common/ml307_board.cc
Normal file
270
main/boards/common/ml307_board.cc
Normal file
@@ -0,0 +1,270 @@
|
||||
#include "ml307_board.h"
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "display.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <font_awesome.h>
|
||||
#include <utility>
|
||||
|
||||
static const char *TAG = "Ml307Board";
|
||||
|
||||
// Maximum retry count for modem detection
|
||||
static constexpr int MODEM_DETECT_MAX_RETRIES = 30;
|
||||
// Maximum retry count for network registration
|
||||
static constexpr int NETWORK_REG_MAX_RETRIES = 6;
|
||||
|
||||
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::SetNetworkEventCallback(NetworkEventCallback callback) {
|
||||
network_event_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void Ml307Board::OnNetworkEvent(NetworkEvent event, const std::string& data) {
|
||||
switch (event) {
|
||||
case NetworkEvent::ModemDetecting:
|
||||
ESP_LOGI(TAG, "Detecting modem...");
|
||||
break;
|
||||
case NetworkEvent::Connecting:
|
||||
ESP_LOGI(TAG, "Registering network...");
|
||||
break;
|
||||
case NetworkEvent::Connected:
|
||||
ESP_LOGI(TAG, "Network connected");
|
||||
break;
|
||||
case NetworkEvent::Disconnected:
|
||||
ESP_LOGW(TAG, "Network disconnected");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorNoSim:
|
||||
ESP_LOGE(TAG, "No SIM card detected");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorRegDenied:
|
||||
ESP_LOGE(TAG, "Network registration denied");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorInitFailed:
|
||||
ESP_LOGE(TAG, "Modem initialization failed");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorTimeout:
|
||||
ESP_LOGE(TAG, "Operation timeout");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Notify external callback if set
|
||||
if (network_event_callback_) {
|
||||
network_event_callback_(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
void Ml307Board::NetworkTask() {
|
||||
// Notify modem detection started
|
||||
OnNetworkEvent(NetworkEvent::ModemDetecting);
|
||||
|
||||
// Try to detect modem with retry limit
|
||||
int detect_retries = 0;
|
||||
while (detect_retries < MODEM_DETECT_MAX_RETRIES) {
|
||||
modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600);
|
||||
if (modem_ != nullptr) {
|
||||
break;
|
||||
}
|
||||
detect_retries++;
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
if (modem_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to detect modem after %d retries", MODEM_DETECT_MAX_RETRIES);
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Modem detected successfully");
|
||||
|
||||
// Set up network state change callback
|
||||
// Note: Don't call GetCarrierName() here as it sends AT command and will block ReceiveTask
|
||||
modem_->OnNetworkStateChanged([this](bool network_ready) {
|
||||
if (network_ready) {
|
||||
OnNetworkEvent(NetworkEvent::Connected);
|
||||
} else {
|
||||
OnNetworkEvent(NetworkEvent::Disconnected);
|
||||
}
|
||||
});
|
||||
|
||||
// Notify network registration started
|
||||
OnNetworkEvent(NetworkEvent::Connecting);
|
||||
|
||||
// Wait for network ready with retry limit
|
||||
int reg_retries = 0;
|
||||
while (reg_retries < NETWORK_REG_MAX_RETRIES) {
|
||||
auto result = modem_->WaitForNetworkReady();
|
||||
if (result == NetworkStatus::Ready) {
|
||||
break;
|
||||
} else if (result == NetworkStatus::ErrorInsertPin) {
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorNoSim);
|
||||
} else if (result == NetworkStatus::ErrorRegistrationDenied) {
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorRegDenied);
|
||||
} else if (result == NetworkStatus::ErrorTimeout) {
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorTimeout);
|
||||
}
|
||||
reg_retries++;
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
}
|
||||
|
||||
if (!modem_->network_ready()) {
|
||||
ESP_LOGE(TAG, "Failed to register network after %d retries", NETWORK_REG_MAX_RETRIES);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
void Ml307Board::StartNetwork() {
|
||||
// Create network initialization task and return immediately
|
||||
xTaskCreate([](void* arg) {
|
||||
Ml307Board* board = static_cast<Ml307Board*>(arg);
|
||||
board->NetworkTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "ml307_net", 4096, this, 5, NULL);
|
||||
}
|
||||
|
||||
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 <= 9) {
|
||||
return FONT_AWESOME_SIGNAL_WEAK;
|
||||
} else if (csq >= 10 && csq <= 14) {
|
||||
return FONT_AWESOME_SIGNAL_FAIR;
|
||||
} else if (csq >= 15 && csq <= 19) {
|
||||
return FONT_AWESOME_SIGNAL_GOOD;
|
||||
} else if (csq >= 20 && 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::SetPowerSaveLevel(PowerSaveLevel level) {
|
||||
// TODO: Implement power save level for ML307
|
||||
(void)level;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
38
main/boards/common/ml307_board.h
Normal file
38
main/boards/common/ml307_board.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#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_;
|
||||
NetworkEventCallback network_event_callback_;
|
||||
|
||||
virtual std::string GetBoardJson() override;
|
||||
|
||||
// Internal helper to trigger network event callback
|
||||
void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
|
||||
|
||||
// Network initialization task (runs in FreeRTOS task)
|
||||
static void NetworkTaskEntry(void* arg);
|
||||
void NetworkTask();
|
||||
|
||||
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 void SetNetworkEventCallback(NetworkEventCallback callback) override;
|
||||
virtual NetworkInterface* GetNetwork() override;
|
||||
virtual const char* GetNetworkStateIcon() override;
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
|
||||
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
|
||||
virtual std::string GetDeviceStatusJson() override;
|
||||
};
|
||||
|
||||
#endif // ML307_BOARD_H
|
||||
269
main/boards/common/nt26_board.cc
Normal file
269
main/boards/common/nt26_board.cc
Normal file
@@ -0,0 +1,269 @@
|
||||
#include "nt26_board.h"
|
||||
#include "display.h"
|
||||
#include "application.h"
|
||||
#include "audio_codec.h"
|
||||
#include <esp_log.h>
|
||||
#include <font_awesome.h>
|
||||
#include <cJSON.h>
|
||||
|
||||
#define TAG "Nt26Board"
|
||||
|
||||
Nt26Board::Nt26Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, gpio_num_t ri_pin, gpio_num_t reset_pin)
|
||||
: tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin), ri_pin_(ri_pin), reset_pin_(reset_pin) {
|
||||
|
||||
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||
esp_event_loop_create_default();
|
||||
esp_netif_init();
|
||||
|
||||
// Create PM lock handle
|
||||
esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "nt26_cpu", &pm_lock_cpu_max_);
|
||||
|
||||
// Create network ready timeout timer
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = OnNetworkReadyTimeout,
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "nt26_net_timer",
|
||||
.skip_unhandled_events = true
|
||||
};
|
||||
esp_timer_create(&timer_args, &network_ready_timer_);
|
||||
}
|
||||
|
||||
Nt26Board::~Nt26Board() {
|
||||
if (current_power_level_ != PowerSaveLevel::LOW_POWER) {
|
||||
SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
|
||||
}
|
||||
|
||||
if (network_ready_timer_) {
|
||||
esp_timer_stop(network_ready_timer_);
|
||||
esp_timer_delete(network_ready_timer_);
|
||||
}
|
||||
|
||||
if (modem_) {
|
||||
modem_->Stop();
|
||||
}
|
||||
|
||||
if (pm_lock_cpu_max_) {
|
||||
esp_pm_lock_delete(pm_lock_cpu_max_);
|
||||
}
|
||||
}
|
||||
|
||||
std::string Nt26Board::GetBoardType() {
|
||||
return "nt26";
|
||||
}
|
||||
|
||||
void Nt26Board::OnNetworkEvent(NetworkEvent event, const std::string& data) {
|
||||
if (network_event_callback_) {
|
||||
network_event_callback_(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
void Nt26Board::OnNetworkReadyTimeout(void* arg) {
|
||||
auto* self = static_cast<Nt26Board*>(arg);
|
||||
ESP_LOGW(TAG, "Network ready timeout");
|
||||
self->OnNetworkEvent(NetworkEvent::ModemErrorTimeout, "网络连接超时");
|
||||
}
|
||||
|
||||
void Nt26Board::StartNetwork() {
|
||||
OnNetworkEvent(NetworkEvent::ModemDetecting);
|
||||
|
||||
UartEthModem::Config config = {
|
||||
.uart_num = UART_NUM_1,
|
||||
.baud_rate = 3000000,
|
||||
.tx_pin = tx_pin_,
|
||||
.rx_pin = rx_pin_,
|
||||
.mrdy_pin = dtr_pin_,
|
||||
.srdy_pin = ri_pin_
|
||||
};
|
||||
|
||||
modem_ = std::make_unique<UartEthModem>(config);
|
||||
modem_->SetDebug(false);
|
||||
|
||||
modem_->SetNetworkEventCallback([this](UartEthModem::UartEthModemEvent event) {
|
||||
switch (event) {
|
||||
case UartEthModem::UartEthModemEvent::Connected:
|
||||
esp_timer_stop(network_ready_timer_);
|
||||
OnNetworkEvent(NetworkEvent::Connected);
|
||||
break;
|
||||
case UartEthModem::UartEthModemEvent::Disconnected:
|
||||
OnNetworkEvent(NetworkEvent::Disconnected);
|
||||
break;
|
||||
case UartEthModem::UartEthModemEvent::ErrorNoSim:
|
||||
esp_timer_stop(network_ready_timer_);
|
||||
ScheduleAsyncStop();
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorNoSim);
|
||||
break;
|
||||
case UartEthModem::UartEthModemEvent::ErrorRegistrationDenied:
|
||||
esp_timer_stop(network_ready_timer_);
|
||||
ScheduleAsyncStop();
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorRegDenied);
|
||||
break;
|
||||
case UartEthModem::UartEthModemEvent::Connecting:
|
||||
OnNetworkEvent(NetworkEvent::Connecting);
|
||||
break;
|
||||
case UartEthModem::UartEthModemEvent::ErrorInitFailed:
|
||||
case UartEthModem::UartEthModemEvent::ErrorNoCarrier:
|
||||
esp_timer_stop(network_ready_timer_);
|
||||
ScheduleAsyncStop();
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
|
||||
break;
|
||||
case UartEthModem::UartEthModemEvent::InFlightMode:
|
||||
ESP_LOGW(TAG, "Modem in flight mode");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (modem_->Start() != ESP_OK) {
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_timer_start_once(network_ready_timer_, 30000 * 1000ULL);
|
||||
OnNetworkEvent(NetworkEvent::Connecting);
|
||||
}
|
||||
|
||||
void Nt26Board::ScheduleAsyncStop() {
|
||||
Application::GetInstance().Schedule([this]() {
|
||||
if (modem_) {
|
||||
modem_->Stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Nt26Board::SetNetworkEventCallback(NetworkEventCallback callback) {
|
||||
network_event_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
NetworkInterface* Nt26Board::GetNetwork() {
|
||||
static EspNetwork network;
|
||||
return &network;
|
||||
}
|
||||
|
||||
const char* Nt26Board::GetNetworkStateIcon() {
|
||||
if (modem_ == nullptr || !modem_->IsInitialized()) {
|
||||
return FONT_AWESOME_SIGNAL_OFF;
|
||||
}
|
||||
int csq = modem_->GetSignalStrength();
|
||||
if (csq == 99 || csq == -1) {
|
||||
return FONT_AWESOME_SIGNAL_OFF;
|
||||
} else if (csq >= 0 && csq <= 9) {
|
||||
return FONT_AWESOME_SIGNAL_WEAK;
|
||||
} else if (csq >= 10 && csq <= 14) {
|
||||
return FONT_AWESOME_SIGNAL_FAIR;
|
||||
} else if (csq >= 15 && csq <= 19) {
|
||||
return FONT_AWESOME_SIGNAL_GOOD;
|
||||
} else if (csq >= 20 && csq <= 31) {
|
||||
return FONT_AWESOME_SIGNAL_STRONG;
|
||||
}
|
||||
return FONT_AWESOME_SIGNAL_OFF;
|
||||
}
|
||||
|
||||
void Nt26Board::SetPowerSaveLevel(PowerSaveLevel level) {
|
||||
if (level == current_power_level_) return;
|
||||
|
||||
if (current_power_level_ == PowerSaveLevel::BALANCED ||
|
||||
current_power_level_ == PowerSaveLevel::PERFORMANCE) {
|
||||
if (pm_lock_cpu_max_) {
|
||||
esp_pm_lock_release(pm_lock_cpu_max_);
|
||||
}
|
||||
}
|
||||
|
||||
if (level == PowerSaveLevel::BALANCED || level == PowerSaveLevel::PERFORMANCE) {
|
||||
if (pm_lock_cpu_max_) {
|
||||
esp_pm_lock_acquire(pm_lock_cpu_max_);
|
||||
}
|
||||
}
|
||||
|
||||
current_power_level_ = level;
|
||||
}
|
||||
|
||||
std::string Nt26Board::GetBoardJson() {
|
||||
// Set the board type for OTA
|
||||
std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\",");
|
||||
board_json += "\"name\":\"" BOARD_NAME "\",";
|
||||
if (modem_) {
|
||||
board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\",";
|
||||
board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\",";
|
||||
board_json += "\"csq\":\"" + std::to_string(modem_->GetSignalStrength()) + "\",";
|
||||
board_json += "\"imei\":\"" + modem_->GetImei() + "\",";
|
||||
board_json += "\"iccid\":\"" + modem_->GetIccid() + "\",";
|
||||
board_json += "\"cereg\":" + GetRegistrationState().ToString() + "}";
|
||||
} else {
|
||||
board_json += "\"status\":\"offline\"}";
|
||||
}
|
||||
return board_json;
|
||||
}
|
||||
|
||||
Nt26CeregState Nt26Board::GetRegistrationState() {
|
||||
Nt26CeregState state;
|
||||
if (modem_) {
|
||||
auto cell_info = modem_->GetCellInfo();
|
||||
state.stat = cell_info.stat;
|
||||
state.tac = cell_info.tac;
|
||||
state.ci = cell_info.ci;
|
||||
state.AcT = cell_info.act;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
std::string Nt26Board::GetDeviceStatusJson() {
|
||||
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
|
||||
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) {
|
||||
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, discharging = false;
|
||||
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
|
||||
auto 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");
|
||||
if (modem_) {
|
||||
cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str());
|
||||
int csq = modem_->GetSignalStrength();
|
||||
if (csq == 99 || csq == -1) {
|
||||
cJSON_AddStringToObject(network, "signal", "unknown");
|
||||
} else if (csq >= 0 && csq <= 14) {
|
||||
cJSON_AddStringToObject(network, "signal", "weak");
|
||||
} else if (csq >= 15 && 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;
|
||||
}
|
||||
62
main/boards/common/nt26_board.h
Normal file
62
main/boards/common/nt26_board.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef NT26_BOARD_H
|
||||
#define NT26_BOARD_H
|
||||
|
||||
#include <memory>
|
||||
#include <uart_eth_modem.h>
|
||||
#include <esp_network.h>
|
||||
#include <esp_pm.h>
|
||||
#include <esp_timer.h>
|
||||
#include "board.h"
|
||||
|
||||
struct Nt26CeregState {
|
||||
int stat = 0;
|
||||
std::string tac;
|
||||
std::string ci;
|
||||
int AcT = -1;
|
||||
|
||||
std::string ToString() const {
|
||||
std::string json = "{";
|
||||
json += "\"stat\":" + std::to_string(stat);
|
||||
if (!tac.empty()) json += ",\"tac\":\"" + tac + "\"";
|
||||
if (!ci.empty()) json += ",\"ci\":\"" + ci + "\"";
|
||||
if (AcT >= 0) json += ",\"AcT\":" + std::to_string(AcT);
|
||||
json += "}";
|
||||
return json;
|
||||
}
|
||||
};
|
||||
|
||||
class Nt26Board : public Board {
|
||||
protected:
|
||||
std::unique_ptr<UartEthModem> modem_;
|
||||
gpio_num_t tx_pin_;
|
||||
gpio_num_t rx_pin_;
|
||||
gpio_num_t dtr_pin_; // mrdy_pin
|
||||
gpio_num_t ri_pin_; // srdy_pin
|
||||
gpio_num_t reset_pin_;
|
||||
|
||||
NetworkEventCallback network_event_callback_;
|
||||
esp_pm_lock_handle_t pm_lock_cpu_max_ = nullptr;
|
||||
PowerSaveLevel current_power_level_ = PowerSaveLevel::LOW_POWER;
|
||||
esp_timer_handle_t network_ready_timer_ = nullptr;
|
||||
|
||||
virtual std::string GetBoardJson() override;
|
||||
|
||||
void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
|
||||
static void OnNetworkReadyTimeout(void* arg);
|
||||
void ScheduleAsyncStop();
|
||||
|
||||
public:
|
||||
Nt26Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, gpio_num_t ri_pin, gpio_num_t reset_pin = GPIO_NUM_NC);
|
||||
virtual ~Nt26Board();
|
||||
virtual std::string GetBoardType() override;
|
||||
virtual void StartNetwork() override;
|
||||
virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
|
||||
virtual NetworkInterface* GetNetwork() override;
|
||||
virtual const char* GetNetworkStateIcon() override;
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
|
||||
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
|
||||
virtual std::string GetDeviceStatusJson() override;
|
||||
Nt26CeregState GetRegistrationState();
|
||||
};
|
||||
|
||||
#endif // NT26_BOARD_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user