mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-01-14 01:07:30 +08:00
feat: add esp-spot c5 (#1462)
* feat: add esp-spot c5 * fix: fix table custom filename
This commit is contained in:
parent
860d12a12c
commit
908c9d5708
@ -200,7 +200,9 @@ elseif(CONFIG_BOARD_TYPE_ESP_SPARKBOT)
|
|||||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||||
elseif(CONFIG_BOARD_TYPE_ESP_SPOT_S3)
|
elseif(CONFIG_BOARD_TYPE_ESP_SPOT_S3)
|
||||||
set(BOARD_TYPE "esp-spot-s3")
|
set(BOARD_TYPE "esp-spot")
|
||||||
|
elseif(CONFIG_BOARD_TYPE_ESP_SPOT_C5)
|
||||||
|
set(BOARD_TYPE "esp-spot")
|
||||||
elseif(CONFIG_BOARD_TYPE_ESP_HI)
|
elseif(CONFIG_BOARD_TYPE_ESP_HI)
|
||||||
set(BOARD_TYPE "esp-hi")
|
set(BOARD_TYPE "esp-hi")
|
||||||
# Set ESP_HI emoji directory for DEFAULT_ASSETS_EXTRA_FILES
|
# Set ESP_HI emoji directory for DEFAULT_ASSETS_EXTRA_FILES
|
||||||
|
|||||||
@ -153,6 +153,9 @@ choice BOARD_TYPE
|
|||||||
config BOARD_TYPE_ESP_SPOT_S3
|
config BOARD_TYPE_ESP_SPOT_S3
|
||||||
bool "Espressif Spot-S3"
|
bool "Espressif Spot-S3"
|
||||||
depends on IDF_TARGET_ESP32S3
|
depends on IDF_TARGET_ESP32S3
|
||||||
|
config BOARD_TYPE_ESP_SPOT_C5
|
||||||
|
bool "Espressif Spot-C5"
|
||||||
|
depends on IDF_TARGET_ESP32C5
|
||||||
config BOARD_TYPE_ESP_HI
|
config BOARD_TYPE_ESP_HI
|
||||||
bool "Espressif ESP-HI"
|
bool "Espressif ESP-HI"
|
||||||
depends on IDF_TARGET_ESP32C3
|
depends on IDF_TARGET_ESP32C3
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
#ifndef _BOARD_CONFIG_H_
|
|
||||||
#define _BOARD_CONFIG_H_
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
|
|
||||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
|
||||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
|
||||||
|
|
||||||
#define AUDIO_INPUT_REFERENCE false
|
|
||||||
|
|
||||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8
|
|
||||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_17
|
|
||||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16
|
|
||||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
|
|
||||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_18
|
|
||||||
|
|
||||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_40
|
|
||||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2
|
|
||||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
|
|
||||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
|
||||||
|
|
||||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
|
||||||
#define KEY_BUTTON_GPIO GPIO_NUM_12
|
|
||||||
#define LED_PIN GPIO_NUM_11
|
|
||||||
|
|
||||||
#define VBAT_ADC_CHANNEL ADC_CHANNEL_9 // S3: IO10
|
|
||||||
#define MCU_VCC_CTL GPIO_NUM_4 // set 1 to power on MCU
|
|
||||||
#define PERP_VCC_CTL GPIO_NUM_6 // set 1 to power on peripherals
|
|
||||||
|
|
||||||
#define ADC_ATTEN ADC_ATTEN_DB_12
|
|
||||||
#define ADC_WIDTH ADC_BITWIDTH_DEFAULT
|
|
||||||
#define FULL_BATTERY_VOLTAGE 4100
|
|
||||||
#define EMPTY_BATTERY_VOLTAGE 3200
|
|
||||||
|
|
||||||
#endif // _BOARD_CONFIG_H_
|
|
||||||
@ -1,242 +0,0 @@
|
|||||||
#include "wifi_board.h"
|
|
||||||
#include "codecs/es8311_audio_codec.h"
|
|
||||||
#include "application.h"
|
|
||||||
#include "button.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "sdkconfig.h"
|
|
||||||
|
|
||||||
#include <wifi_station.h>
|
|
||||||
#include <esp_log.h>
|
|
||||||
#include <driver/i2c_master.h>
|
|
||||||
#include <driver/spi_common.h>
|
|
||||||
#include "esp_adc/adc_oneshot.h"
|
|
||||||
#include "esp_adc/adc_cali.h"
|
|
||||||
#include "esp_adc/adc_cali_scheme.h"
|
|
||||||
|
|
||||||
#include <driver/gpio.h>
|
|
||||||
#include "esp_timer.h"
|
|
||||||
#include "led/circular_strip.h"
|
|
||||||
|
|
||||||
#define TAG "esp_spot_s3"
|
|
||||||
|
|
||||||
bool button_released_ = false;
|
|
||||||
bool shutdown_ready_ = false;
|
|
||||||
esp_timer_handle_t shutdown_timer;
|
|
||||||
|
|
||||||
class EspSpotS3Bot : public WifiBoard {
|
|
||||||
private:
|
|
||||||
i2c_master_bus_handle_t i2c_bus_;
|
|
||||||
Button boot_button_;
|
|
||||||
Button key_button_;
|
|
||||||
adc_oneshot_unit_handle_t adc1_handle;
|
|
||||||
adc_cali_handle_t adc1_cali_handle;
|
|
||||||
bool do_calibration = false;
|
|
||||||
bool key_long_pressed = false;
|
|
||||||
int64_t last_key_press_time = 0;
|
|
||||||
static const int64_t LONG_PRESS_TIMEOUT_US = 5 * 1000000ULL;
|
|
||||||
|
|
||||||
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 InitializeADC() {
|
|
||||||
adc_oneshot_unit_init_cfg_t init_config1 = {
|
|
||||||
.unit_id = ADC_UNIT_1
|
|
||||||
};
|
|
||||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
|
|
||||||
|
|
||||||
adc_oneshot_chan_cfg_t chan_config = {
|
|
||||||
.atten = ADC_ATTEN,
|
|
||||||
.bitwidth = ADC_WIDTH,
|
|
||||||
};
|
|
||||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, VBAT_ADC_CHANNEL, &chan_config));
|
|
||||||
|
|
||||||
adc_cali_handle_t handle = NULL;
|
|
||||||
esp_err_t ret = ESP_FAIL;
|
|
||||||
|
|
||||||
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
|
||||||
adc_cali_curve_fitting_config_t cali_config = {
|
|
||||||
.unit_id = ADC_UNIT_1,
|
|
||||||
.atten = ADC_ATTEN,
|
|
||||||
.bitwidth = ADC_WIDTH,
|
|
||||||
};
|
|
||||||
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
|
||||||
if (ret == ESP_OK) {
|
|
||||||
do_calibration = true;
|
|
||||||
adc1_cali_handle = handle;
|
|
||||||
ESP_LOGI(TAG, "ADC Curve Fitting calibration succeeded");
|
|
||||||
}
|
|
||||||
#endif // ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeButtons() {
|
|
||||||
boot_button_.OnClick([this]() {
|
|
||||||
auto& app = Application::GetInstance();
|
|
||||||
ResetWifiConfiguration();
|
|
||||||
});
|
|
||||||
|
|
||||||
key_button_.OnClick([this]() {
|
|
||||||
auto& app = Application::GetInstance();
|
|
||||||
app.ToggleChatState();
|
|
||||||
key_long_pressed = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
key_button_.OnLongPress([this]() {
|
|
||||||
int64_t now = esp_timer_get_time();
|
|
||||||
auto* led = static_cast<CircularStrip*>(this->GetLed());
|
|
||||||
|
|
||||||
if (key_long_pressed) {
|
|
||||||
if ((now - last_key_press_time) < LONG_PRESS_TIMEOUT_US) {
|
|
||||||
ESP_LOGW(TAG, "Key button long pressed the second time within 5s, shutting down...");
|
|
||||||
led->SetSingleColor(0, {0, 0, 0});
|
|
||||||
|
|
||||||
gpio_hold_dis(MCU_VCC_CTL);
|
|
||||||
gpio_set_level(MCU_VCC_CTL, 0);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
last_key_press_time = now;
|
|
||||||
BlinkGreenFor5s();
|
|
||||||
}
|
|
||||||
key_long_pressed = true;
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "Key button first long press! Waiting second within 5s to shutdown...");
|
|
||||||
last_key_press_time = now;
|
|
||||||
key_long_pressed = true;
|
|
||||||
|
|
||||||
BlinkGreenFor5s();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializePowerCtl() {
|
|
||||||
InitializeGPIO();
|
|
||||||
|
|
||||||
gpio_set_level(MCU_VCC_CTL, 1);
|
|
||||||
gpio_hold_en(MCU_VCC_CTL);
|
|
||||||
|
|
||||||
gpio_set_level(PERP_VCC_CTL, 1);
|
|
||||||
gpio_hold_en(PERP_VCC_CTL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitializeGPIO() {
|
|
||||||
gpio_config_t io_pa = {
|
|
||||||
.pin_bit_mask = (1ULL << AUDIO_CODEC_PA_PIN),
|
|
||||||
.mode = GPIO_MODE_OUTPUT,
|
|
||||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
|
||||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
|
||||||
.intr_type = GPIO_INTR_DISABLE
|
|
||||||
};
|
|
||||||
gpio_config(&io_pa);
|
|
||||||
gpio_set_level(AUDIO_CODEC_PA_PIN, 0);
|
|
||||||
|
|
||||||
gpio_config_t io_conf_1 = {
|
|
||||||
.pin_bit_mask = (1ULL << MCU_VCC_CTL),
|
|
||||||
.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_1);
|
|
||||||
|
|
||||||
gpio_config_t io_conf_2 = {
|
|
||||||
.pin_bit_mask = (1ULL << PERP_VCC_CTL),
|
|
||||||
.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_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlinkGreenFor5s() {
|
|
||||||
auto* led = static_cast<CircularStrip*>(GetLed());
|
|
||||||
if (!led) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
led->Blink({50, 25, 0}, 100);
|
|
||||||
|
|
||||||
esp_timer_create_args_t timer_args = {
|
|
||||||
.callback = [](void* arg) {
|
|
||||||
auto* self = static_cast<EspSpotS3Bot*>(arg);
|
|
||||||
auto* led = static_cast<CircularStrip*>(self->GetLed());
|
|
||||||
if (led) {
|
|
||||||
led->SetSingleColor(0, {0, 0, 0});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.arg = this,
|
|
||||||
.dispatch_method = ESP_TIMER_TASK,
|
|
||||||
.name = "blinkGreenFor5s_timer"
|
|
||||||
};
|
|
||||||
|
|
||||||
esp_timer_handle_t blink_timer = nullptr;
|
|
||||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &blink_timer));
|
|
||||||
ESP_ERROR_CHECK(esp_timer_start_once(blink_timer, LONG_PRESS_TIMEOUT_US));
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
EspSpotS3Bot() : boot_button_(BOOT_BUTTON_GPIO), key_button_(KEY_BUTTON_GPIO, true) {
|
|
||||||
InitializePowerCtl();
|
|
||||||
InitializeADC();
|
|
||||||
InitializeI2c();
|
|
||||||
InitializeButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Led* GetLed() override {
|
|
||||||
static CircularStrip led(LED_PIN, 1);
|
|
||||||
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 bool GetBatteryLevel(int &level, bool &charging, bool &discharging) {
|
|
||||||
if (!adc1_handle) {
|
|
||||||
InitializeADC();
|
|
||||||
}
|
|
||||||
|
|
||||||
int raw_value = 0;
|
|
||||||
int voltage = 0;
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, VBAT_ADC_CHANNEL, &raw_value));
|
|
||||||
|
|
||||||
if (do_calibration) {
|
|
||||||
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle, raw_value, &voltage));
|
|
||||||
voltage = voltage * 3 / 2; // compensate for voltage divider
|
|
||||||
ESP_LOGI(TAG, "Calibrated voltage: %d mV", voltage);
|
|
||||||
} else {
|
|
||||||
ESP_LOGI(TAG, "Raw ADC value: %d", raw_value);
|
|
||||||
voltage = raw_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
voltage = voltage < EMPTY_BATTERY_VOLTAGE ? EMPTY_BATTERY_VOLTAGE : voltage;
|
|
||||||
voltage = voltage > FULL_BATTERY_VOLTAGE ? FULL_BATTERY_VOLTAGE : voltage;
|
|
||||||
|
|
||||||
// 计算电量百分比
|
|
||||||
level = (voltage - EMPTY_BATTERY_VOLTAGE) * 100 / (FULL_BATTERY_VOLTAGE - EMPTY_BATTERY_VOLTAGE);
|
|
||||||
|
|
||||||
charging = gpio_get_level(MCU_VCC_CTL);
|
|
||||||
ESP_LOGI(TAG, "Battery Level: %d%%, Charging: %s", level, charging ? "Yes" : "No");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
DECLARE_BOARD(EspSpotS3Bot);
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# ESP-Spot S3
|
# ESP-Spot
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
@ -10,15 +10,17 @@
|
|||||||
|
|
||||||
ESP-Spot 是 ESP Friends 开源的一款智能语音交互盒子,内置麦克风、扬声器、IMU 惯性传感器,可使用电池供电。ESP-Spot 不带屏幕,带有一个 RGB 指示灯和两个按钮。硬件详情可查看[立创开源项目](https://oshwhub.com/esp-college/esp-spot)。
|
ESP-Spot 是 ESP Friends 开源的一款智能语音交互盒子,内置麦克风、扬声器、IMU 惯性传感器,可使用电池供电。ESP-Spot 不带屏幕,带有一个 RGB 指示灯和两个按钮。硬件详情可查看[立创开源项目](https://oshwhub.com/esp-college/esp-spot)。
|
||||||
|
|
||||||
ESP-Spot 开源项目采用 ESP32-S3-WROOM-1-N16R8 模组。如在复刻时使用了其他大小的 Flash,需修改对应的参数。
|
ESP-Spot 开源项目采用 ESP32-S3-WROOM-1-N16R8 模组或 ESP32-C5-WROOM-1-N8R8。如在复刻时使用了其他大小的 Flash,需修改对应的参数。
|
||||||
|
|
||||||
|
|
||||||
## 配置、编译命令
|
## 配置、编译命令
|
||||||
|
|
||||||
**配置编译目标为 ESP32S3**
|
**配置编译目标**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
idf.py set-target esp32s3
|
idf.py set-target esp32s3 # Spot S3
|
||||||
|
# or
|
||||||
|
idf.py set-target esp32c5 # Spot C5
|
||||||
```
|
```
|
||||||
|
|
||||||
**打开 menuconfig 并配置**
|
**打开 menuconfig 并配置**
|
||||||
@ -29,7 +31,7 @@ idf.py menuconfig
|
|||||||
|
|
||||||
分别配置如下选项:
|
分别配置如下选项:
|
||||||
|
|
||||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `ESP-Spot-S3`
|
- `Xiaozhi Assistant` → `Board Type` → 选择 `ESP-Spot-S3` / `ESP-Spot-C5`
|
||||||
|
|
||||||
按 `S` 保存,按 `Q` 退出。
|
按 `S` 保存,按 `Q` 退出。
|
||||||
|
|
||||||
@ -53,3 +55,9 @@ idf.py flash
|
|||||||
> 3. 按住 <kbd>BOOT</kbd> 同时插回 PCB 版,注意不要颠倒;
|
> 3. 按住 <kbd>BOOT</kbd> 同时插回 PCB 版,注意不要颠倒;
|
||||||
>
|
>
|
||||||
> 此时, ESP-Spot 应当已进入下载模式。在烧录完成后,可能需要重新插拔 PCB 板。
|
> 此时, ESP-Spot 应当已进入下载模式。在烧录完成后,可能需要重新插拔 PCB 板。
|
||||||
|
|
||||||
|
## 低功耗
|
||||||
|
|
||||||
|
ESP-Spot 支持 Deep Sleep 低功耗模式。
|
||||||
|
|
||||||
|
当处于 idle 状态 10 分钟后,ESP-Spot 会自动进入 Deep Sleep 模式,按 Key 键或摇晃 ESP-Spot 即可唤醒。
|
||||||
79
main/boards/esp-spot/config.h
Normal file
79
main/boards/esp-spot/config.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
/* Audio configuration */
|
||||||
|
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||||
|
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||||
|
#define AUDIO_INPUT_REFERENCE false
|
||||||
|
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||||
|
|
||||||
|
/* ADC configuration */
|
||||||
|
#define ADC_ATTEN ADC_ATTEN_DB_12
|
||||||
|
#define ADC_WIDTH ADC_BITWIDTH_DEFAULT
|
||||||
|
#define FULL_BATTERY_VOLTAGE 4100
|
||||||
|
#define EMPTY_BATTERY_VOLTAGE 3200
|
||||||
|
|
||||||
|
/* I2C configuration */
|
||||||
|
#define I2C_MASTER_FREQ_HZ (400 * 1000)
|
||||||
|
|
||||||
|
/* Button configuration */
|
||||||
|
#define LONG_PRESS_TIMEOUT_US (5 * 1000000ULL)
|
||||||
|
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
/* Audio I2S GPIOs */
|
||||||
|
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
|
||||||
|
#define AUDIO_I2S_GPIO_WS GPIO_NUM_17
|
||||||
|
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16
|
||||||
|
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
|
||||||
|
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_18
|
||||||
|
|
||||||
|
/* Audio CODEC GPIOs */
|
||||||
|
#define AUDIO_CODEC_PA_PIN GPIO_NUM_40
|
||||||
|
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2
|
||||||
|
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
|
||||||
|
|
||||||
|
/* Button GPIOs */
|
||||||
|
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||||
|
#define KEY_BUTTON_GPIO GPIO_NUM_12
|
||||||
|
#define LED_GPIO GPIO_NUM_11
|
||||||
|
|
||||||
|
/* ADC GPIOs */
|
||||||
|
#define VBAT_ADC_CHANNEL ADC_CHANNEL_9 // S3: IO10
|
||||||
|
#define MCU_VCC_CTL GPIO_NUM_4 // set 1 to power on MCU
|
||||||
|
#define PERP_VCC_CTL GPIO_NUM_6 // set 1 to power on peripherals
|
||||||
|
|
||||||
|
/* IMU GPIOs */
|
||||||
|
#define IMU_INT_GPIO GPIO_NUM_5
|
||||||
|
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||||
|
|
||||||
|
/* Audio I2S GPIOs */
|
||||||
|
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
|
||||||
|
#define AUDIO_I2S_GPIO_WS GPIO_NUM_8
|
||||||
|
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
|
||||||
|
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
|
||||||
|
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
|
||||||
|
|
||||||
|
/* Audio CODEC GPIOs */
|
||||||
|
#define AUDIO_CODEC_PA_PIN GPIO_NUM_23
|
||||||
|
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25
|
||||||
|
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_26
|
||||||
|
|
||||||
|
/* Button GPIOs */
|
||||||
|
#define BOOT_BUTTON_GPIO GPIO_NUM_28
|
||||||
|
#define KEY_BUTTON_GPIO GPIO_NUM_5
|
||||||
|
#define LED_GPIO GPIO_NUM_27
|
||||||
|
|
||||||
|
/* ADC GPIOs */
|
||||||
|
#define VBAT_ADC_CHANNEL ADC_CHANNEL_3 // C5: IO4
|
||||||
|
#define MCU_VCC_CTL GPIO_NUM_2 // set 1 to power on MCU
|
||||||
|
#define PERP_VCC_CTL GPIO_NUM_0 // set 1 to power on peripherals
|
||||||
|
|
||||||
|
/* IMU GPIOs */
|
||||||
|
#define IMU_INT_GPIO GPIO_NUM_3
|
||||||
|
|
||||||
|
#endif // CONFIG_IDF_TARGET
|
||||||
|
|
||||||
14
main/boards/esp-spot/config.json
Normal file
14
main/boards/esp-spot/config.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"target": "esp32c5",
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"name": "esp-spot-c5",
|
||||||
|
"sdkconfig_append": [
|
||||||
|
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||||
|
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
|
||||||
|
"CONFIG_PM_ENABLE=y",
|
||||||
|
"CONFIG_FREERTOS_USE_TICKLESS_IDLE=y"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
437
main/boards/esp-spot/esp_spot_board.cc
Normal file
437
main/boards/esp-spot/esp_spot_board.cc
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/i2c_master.h>
|
||||||
|
#include <esp_adc/adc_cali.h>
|
||||||
|
#include <esp_adc/adc_cali_scheme.h>
|
||||||
|
#include <esp_adc/adc_oneshot.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_timer.h>
|
||||||
|
|
||||||
|
#include "esp_idf_version.h"
|
||||||
|
#include "led/circular_strip.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#include "application.h"
|
||||||
|
#include "button.h"
|
||||||
|
#include "codecs/es8311_audio_codec.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "sleep_timer.h"
|
||||||
|
#include "wifi_board.h"
|
||||||
|
#include "wifi_station.h"
|
||||||
|
|
||||||
|
#ifdef IMU_INT_GPIO
|
||||||
|
#include <esp_sleep.h>
|
||||||
|
|
||||||
|
#include "bmi270_api.h"
|
||||||
|
#include "i2c_bus.h"
|
||||||
|
#endif // IMU_INT_GPIO
|
||||||
|
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define TAG "esp_spot_s3"
|
||||||
|
#elif defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||||
|
#define TAG "esp_spot_c5"
|
||||||
|
#else // target
|
||||||
|
#error "Unsupported target"
|
||||||
|
#endif // target
|
||||||
|
|
||||||
|
#ifdef IMU_INT_GPIO
|
||||||
|
namespace Bmi270Imu {
|
||||||
|
|
||||||
|
static bmi270_handle_t bmi_handle_ = nullptr;
|
||||||
|
|
||||||
|
esp_err_t Initialize(i2c_bus_handle_t i2c_bus, uint8_t addr = BMI270_I2C_ADDRESS) {
|
||||||
|
if (bmi_handle_) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!i2c_bus) {
|
||||||
|
ESP_LOGE(TAG, "Invalid I2C bus for BMI270");
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ret = bmi270_sensor_create(i2c_bus, &bmi_handle_, bmi270_config_file,
|
||||||
|
BMI2_GYRO_CROSS_SENS_ENABLE | BMI2_CRT_RTOSK_ENABLE);
|
||||||
|
if (ret != ESP_OK || !bmi_handle_) {
|
||||||
|
ESP_LOGE(TAG, "BMI270 create failed: %s", esp_err_to_name(ret));
|
||||||
|
return ret == ESP_OK ? ESP_FAIL : ret;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "BMI270 initialized");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only used for deep sleep wakeup with wrist gesture interrupt
|
||||||
|
esp_err_t EnableImuIntForWakeup() {
|
||||||
|
if (!bmi_handle_) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t sens_list[] = {BMI2_ACCEL, BMI2_WRIST_GESTURE};
|
||||||
|
int8_t rslt = bmi270_sensor_enable(sens_list, 2, bmi_handle_);
|
||||||
|
if (rslt != BMI2_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to enable BMI270 sensors: %d", rslt);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bmi2_sens_config config = {.type = BMI2_WRIST_GESTURE};
|
||||||
|
rslt = bmi270_get_sensor_config(&config, 1, bmi_handle_);
|
||||||
|
if (rslt != BMI2_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to get wrist gesture config: %d", rslt);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
config.cfg.wrist_gest.wearable_arm = BMI2_ARM_RIGHT;
|
||||||
|
rslt = bmi270_set_sensor_config(&config, 1, bmi_handle_);
|
||||||
|
if (rslt != BMI2_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to set wrist gesture config: %d", rslt);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bmi2_int_pin_config pin_config = {};
|
||||||
|
pin_config.pin_type = BMI2_INT1;
|
||||||
|
pin_config.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE;
|
||||||
|
pin_config.pin_cfg[0].lvl = BMI2_INT_ACTIVE_HIGH;
|
||||||
|
pin_config.pin_cfg[0].od = BMI2_INT_PUSH_PULL;
|
||||||
|
pin_config.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE;
|
||||||
|
pin_config.int_latch = BMI2_INT_NON_LATCH;
|
||||||
|
rslt = bmi2_set_int_pin_config(&pin_config, bmi_handle_);
|
||||||
|
if (rslt != BMI2_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to set BMI270 INT pin: %d", rslt);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bmi2_sens_int_config int_config = {.type = BMI2_WRIST_GESTURE, .hw_int_pin = BMI2_INT1};
|
||||||
|
rslt = bmi270_map_feat_int(&int_config, 1, bmi_handle_);
|
||||||
|
if (rslt != BMI2_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to map BMI270 interrupt: %d", rslt);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Bmi270Imu
|
||||||
|
|
||||||
|
#endif // IMU_INT_GPIO
|
||||||
|
|
||||||
|
class EspSpot : public WifiBoard {
|
||||||
|
private:
|
||||||
|
i2c_master_bus_handle_t i2c_bus_ = nullptr;
|
||||||
|
Button boot_button_;
|
||||||
|
Button key_button_;
|
||||||
|
adc_oneshot_unit_handle_t adc1_handle_;
|
||||||
|
adc_cali_handle_t adc1_cali_handle_;
|
||||||
|
bool adc_calibration_lock_ = false;
|
||||||
|
bool key_long_pressed_ = false;
|
||||||
|
int64_t last_key_press_time = 0;
|
||||||
|
SleepTimer* sleep_timer_ = nullptr;
|
||||||
|
#ifdef IMU_INT_GPIO
|
||||||
|
i2c_bus_handle_t shared_i2c_bus_handle_ = nullptr;
|
||||||
|
static constexpr int kDeepSleepTimeoutSeconds = 10 * 60; // 10 minutes
|
||||||
|
bool imu_ready_ = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef IMU_INT_GPIO
|
||||||
|
void InitializeI2c() {
|
||||||
|
// Initialize I2C peripheral
|
||||||
|
i2c_config_t i2c_bus_cfg = {
|
||||||
|
.mode = I2C_MODE_MASTER,
|
||||||
|
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||||
|
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||||
|
.sda_pullup_en = true,
|
||||||
|
.scl_pullup_en = true,
|
||||||
|
.master =
|
||||||
|
{
|
||||||
|
.clk_speed = I2C_MASTER_FREQ_HZ,
|
||||||
|
},
|
||||||
|
.clk_flags = 0,
|
||||||
|
};
|
||||||
|
shared_i2c_bus_handle_ = i2c_bus_create(I2C_NUM_0, &i2c_bus_cfg);
|
||||||
|
if (!shared_i2c_bus_handle_) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create shared I2C bus");
|
||||||
|
ESP_ERROR_CHECK(ESP_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && !CONFIG_I2C_BUS_BACKWARD_CONFIG
|
||||||
|
i2c_bus_ = i2c_bus_get_internal_bus_handle(shared_i2c_bus_handle_);
|
||||||
|
#else
|
||||||
|
#error "ESP-Spot board requires i2c_bus_get_internal_bus_handle() support"
|
||||||
|
#endif
|
||||||
|
if (!i2c_bus_) {
|
||||||
|
ESP_LOGE(TAG, "Failed to obtain master bus handle");
|
||||||
|
ESP_ERROR_CHECK(ESP_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t imu_ret = Bmi270Imu::Initialize(shared_i2c_bus_handle_);
|
||||||
|
if (imu_ret != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "BMI270 initialization failed, deep sleep disabled (%s)", esp_err_to_name(imu_ret));
|
||||||
|
} else {
|
||||||
|
imu_ready_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
void InitializeI2c() {
|
||||||
|
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||||
|
.i2c_port = I2C_NUM_0,
|
||||||
|
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||||
|
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||||
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||||
|
.glitch_ignore_cnt = 7,
|
||||||
|
.intr_priority = 0,
|
||||||
|
.trans_queue_depth = 0,
|
||||||
|
.flags =
|
||||||
|
{
|
||||||
|
.enable_internal_pullup = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||||
|
}
|
||||||
|
#endif // IMU_INT_GPIO
|
||||||
|
|
||||||
|
void InitializeADC() {
|
||||||
|
adc_oneshot_unit_init_cfg_t init_config1 = {.unit_id = ADC_UNIT_1};
|
||||||
|
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle_));
|
||||||
|
|
||||||
|
adc_oneshot_chan_cfg_t chan_config = {
|
||||||
|
.atten = ADC_ATTEN,
|
||||||
|
.bitwidth = ADC_WIDTH,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle_, VBAT_ADC_CHANNEL, &chan_config));
|
||||||
|
|
||||||
|
#ifdef ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
||||||
|
adc_cali_handle_t handle = nullptr;
|
||||||
|
esp_err_t ret = ESP_FAIL;
|
||||||
|
|
||||||
|
adc_cali_curve_fitting_config_t cali_config = {
|
||||||
|
.unit_id = ADC_UNIT_1,
|
||||||
|
.atten = ADC_ATTEN,
|
||||||
|
.bitwidth = ADC_WIDTH,
|
||||||
|
};
|
||||||
|
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
adc_calibration_lock_ = true;
|
||||||
|
adc1_cali_handle_ = handle;
|
||||||
|
ESP_LOGI(TAG, "ADC Curve Fitting calibration succeeded");
|
||||||
|
}
|
||||||
|
#endif // ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeButtons() {
|
||||||
|
boot_button_.OnClick([this]() {
|
||||||
|
HandleUserActivity();
|
||||||
|
ResetWifiConfiguration();
|
||||||
|
});
|
||||||
|
|
||||||
|
key_button_.OnClick([this]() {
|
||||||
|
HandleUserActivity();
|
||||||
|
auto& app = Application::GetInstance();
|
||||||
|
app.ToggleChatState();
|
||||||
|
key_long_pressed_ = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
key_button_.OnLongPress([this]() {
|
||||||
|
HandleUserActivity();
|
||||||
|
int64_t now = esp_timer_get_time();
|
||||||
|
auto* led = static_cast<CircularStrip*>(this->GetLed());
|
||||||
|
|
||||||
|
if (key_long_pressed_) {
|
||||||
|
if ((now - last_key_press_time) < LONG_PRESS_TIMEOUT_US) {
|
||||||
|
ESP_LOGW(TAG, "Key button long pressed the second time within 5s, shutting down...");
|
||||||
|
led->SetSingleColor(0, {0, 0, 0});
|
||||||
|
|
||||||
|
gpio_hold_dis(MCU_VCC_CTL);
|
||||||
|
gpio_set_level(MCU_VCC_CTL, 0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
last_key_press_time = now;
|
||||||
|
BlinkGreenFor5s();
|
||||||
|
}
|
||||||
|
key_long_pressed_ = true;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Key button first long press! Waiting second within 5s to shutdown...");
|
||||||
|
last_key_press_time = now;
|
||||||
|
key_long_pressed_ = true;
|
||||||
|
|
||||||
|
BlinkGreenFor5s();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializePowerCtl() {
|
||||||
|
InitializeGPIO();
|
||||||
|
|
||||||
|
gpio_set_level(MCU_VCC_CTL, 1);
|
||||||
|
gpio_hold_en(MCU_VCC_CTL);
|
||||||
|
|
||||||
|
gpio_set_level(PERP_VCC_CTL, 1);
|
||||||
|
gpio_hold_en(PERP_VCC_CTL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitializeGPIO() {
|
||||||
|
gpio_config_t io_pa = {.pin_bit_mask = (1ULL << AUDIO_CODEC_PA_PIN),
|
||||||
|
.mode = GPIO_MODE_OUTPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE};
|
||||||
|
gpio_config(&io_pa);
|
||||||
|
gpio_set_level(AUDIO_CODEC_PA_PIN, 0);
|
||||||
|
|
||||||
|
gpio_config_t io_conf_1 = {.pin_bit_mask = (1ULL << MCU_VCC_CTL),
|
||||||
|
.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_1);
|
||||||
|
|
||||||
|
gpio_config_t io_conf_2 = {.pin_bit_mask = (1ULL << PERP_VCC_CTL),
|
||||||
|
.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_2);
|
||||||
|
|
||||||
|
#ifdef IMU_INT_GPIO
|
||||||
|
gpio_config_t io_conf_imu_int = {
|
||||||
|
.pin_bit_mask = (1ULL << IMU_INT_GPIO),
|
||||||
|
.mode = GPIO_MODE_INPUT,
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_ENABLE,
|
||||||
|
.intr_type = GPIO_INTR_NEGEDGE,
|
||||||
|
};
|
||||||
|
gpio_config(&io_conf_imu_int);
|
||||||
|
gpio_install_isr_service(0);
|
||||||
|
#endif // IMU_INT_GPIO
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleUserActivity() {
|
||||||
|
if (sleep_timer_) {
|
||||||
|
sleep_timer_->WakeUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef IMU_INT_GPIO
|
||||||
|
void InitializePowerSaveTimer() {
|
||||||
|
if (!imu_ready_) {
|
||||||
|
ESP_LOGW(TAG, "IMU not ready, skip deep sleep timer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sleep_timer_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sleep_timer_ = new SleepTimer(-1, kDeepSleepTimeoutSeconds);
|
||||||
|
sleep_timer_->OnEnterDeepSleepMode([this]() { EnterDeepSleep(); });
|
||||||
|
sleep_timer_->SetEnabled(true);
|
||||||
|
ESP_LOGI(TAG, "Deep sleep timer enabled, timeout=%ds", kDeepSleepTimeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnterDeepSleep() {
|
||||||
|
if (!imu_ready_) {
|
||||||
|
ESP_LOGW(TAG, "Skip deep sleep because IMU is not ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* led = static_cast<CircularStrip*>(GetLed());
|
||||||
|
if (led) {
|
||||||
|
led->SetSingleColor(0, {0, 0, 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Bmi270Imu::EnableImuIntForWakeup() != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "IMU wakeup configuration failed, abort deep sleep");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t wakeup_mask = (1ULL << KEY_BUTTON_GPIO) | (1ULL << IMU_INT_GPIO);
|
||||||
|
ESP_ERROR_CHECK(esp_sleep_enable_ext1_wakeup(wakeup_mask, ESP_EXT1_WAKEUP_ANY_HIGH));
|
||||||
|
ESP_LOGI(TAG, "Entering deep sleep, waiting for key or wrist gesture");
|
||||||
|
esp_deep_sleep_start();
|
||||||
|
}
|
||||||
|
#endif // IMU_INT_GPIO
|
||||||
|
|
||||||
|
void BlinkGreenFor5s() {
|
||||||
|
auto* led = static_cast<CircularStrip*>(GetLed());
|
||||||
|
if (!led) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
led->Blink({50, 25, 0}, 100);
|
||||||
|
|
||||||
|
esp_timer_create_args_t timer_args = {.callback =
|
||||||
|
[](void* arg) {
|
||||||
|
auto* self = static_cast<EspSpot*>(arg);
|
||||||
|
auto* led = static_cast<CircularStrip*>(self->GetLed());
|
||||||
|
if (led) {
|
||||||
|
led->SetSingleColor(0, {0, 0, 0});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.arg = this,
|
||||||
|
.dispatch_method = ESP_TIMER_TASK,
|
||||||
|
.name = "green_blink_timer"};
|
||||||
|
|
||||||
|
esp_timer_handle_t green_blink_timer = nullptr;
|
||||||
|
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &green_blink_timer));
|
||||||
|
ESP_ERROR_CHECK(esp_timer_start_once(green_blink_timer, LONG_PRESS_TIMEOUT_US));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
EspSpot() : boot_button_(BOOT_BUTTON_GPIO), key_button_(KEY_BUTTON_GPIO, true) {
|
||||||
|
InitializePowerCtl();
|
||||||
|
InitializeADC();
|
||||||
|
InitializeI2c();
|
||||||
|
InitializeButtons();
|
||||||
|
#ifdef IMU_INT_GPIO
|
||||||
|
InitializePowerSaveTimer();
|
||||||
|
#endif // IMU_INT_GPIO
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Led* GetLed() override {
|
||||||
|
static CircularStrip led(LED_GPIO, 1);
|
||||||
|
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 void SetPowerSaveMode(bool enabled) override {
|
||||||
|
if (sleep_timer_) {
|
||||||
|
sleep_timer_->SetEnabled(enabled);
|
||||||
|
}
|
||||||
|
WifiBoard::SetPowerSaveMode(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||||
|
if (!adc1_handle_) {
|
||||||
|
InitializeADC();
|
||||||
|
}
|
||||||
|
|
||||||
|
int raw_value = 0;
|
||||||
|
int voltage = 0;
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle_, VBAT_ADC_CHANNEL, &raw_value));
|
||||||
|
|
||||||
|
if (adc_calibration_lock_) {
|
||||||
|
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle_, raw_value, &voltage));
|
||||||
|
voltage = voltage * 3 / 2; // compensate for voltage divider
|
||||||
|
ESP_LOGI(TAG, "Calibrated voltage: %d mV", voltage);
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Raw ADC value: %d", raw_value);
|
||||||
|
voltage = raw_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
voltage = voltage < EMPTY_BATTERY_VOLTAGE ? EMPTY_BATTERY_VOLTAGE : voltage;
|
||||||
|
voltage = voltage > FULL_BATTERY_VOLTAGE ? FULL_BATTERY_VOLTAGE : voltage;
|
||||||
|
|
||||||
|
// Calculate battery level percentage
|
||||||
|
level = (voltage - EMPTY_BATTERY_VOLTAGE) * 100 / (FULL_BATTERY_VOLTAGE - EMPTY_BATTERY_VOLTAGE);
|
||||||
|
|
||||||
|
// ESP-Spot does not support charging detection, so we use MCU_VCC_CTL to determine charging status
|
||||||
|
charging = gpio_get_level(MCU_VCC_CTL);
|
||||||
|
discharging = !charging;
|
||||||
|
ESP_LOGI(TAG, "Battery Level: %d%%, Charging: %s", level, charging ? "Yes" : "No");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_BOARD(EspSpot);
|
||||||
@ -99,6 +99,11 @@ dependencies:
|
|||||||
rules:
|
rules:
|
||||||
- if: target in [esp32p4]
|
- if: target in [esp32p4]
|
||||||
|
|
||||||
|
espressif/bmi270_sensor:
|
||||||
|
version: ^0.1.0
|
||||||
|
rules:
|
||||||
|
- if: target in [esp32s3, esp32c5]
|
||||||
|
|
||||||
## Required IDF version
|
## Required IDF version
|
||||||
idf:
|
idf:
|
||||||
version: '>=5.4.0'
|
version: '>=5.4.0'
|
||||||
|
|||||||
@ -9,3 +9,6 @@ CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6
|
|||||||
CONFIG_ESP_WIFI_RX_BA_WIN=3
|
CONFIG_ESP_WIFI_RX_BA_WIN=3
|
||||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16
|
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16
|
||||||
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
|
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
|
||||||
|
|
||||||
|
CONFIG_SR_WN_WN9S_NIHAOXIAOZHI=y
|
||||||
|
CONFIG_USE_ESP_WAKE_WORD=y
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user