mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-01-14 01:07:30 +08:00
Merge branch '78:main' into main
This commit is contained in:
commit
465d797237
@ -17,15 +17,15 @@ BluFi
|
||||
1) 手机端通过 BluFi(如官方 EspBlufi App 或自研客户端)连接设备,发送 Wi‑Fi SSID/密码。
|
||||
2) 设备侧在 `ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP` 中将凭据写入 `SsidManager`(存储到 NVS,属于 `esp-wifi-connect` 组件)。
|
||||
3) 随后启动 `WifiStation` 扫描并连接;状态通过 BluFi 返回。
|
||||
4) 连接成功后当前固件会延时 1 秒并重启,使主应用在下次启动时直接使用新 Wi‑Fi;失败则返回失败状态。
|
||||
4) 配网成功后设备会自动连接新 Wi‑Fi;失败则返回失败状态。
|
||||
|
||||
## 使用步骤
|
||||
|
||||
1. 配置:在 menuconfig 开启 `Esp Blufi`。编译并烧录固件。
|
||||
2. 触发配网:设备首次启动且没有已保存的 Wi‑Fi 时会自动进入配网。
|
||||
3. 手机端操作:打开 EspBlufi App(或其他 BluFi 客户端),搜索并连接设备,按提示输入 Wi‑Fi SSID/密码并发送。
|
||||
3. 手机端操作:打开 EspBlufi App(或其他 BluFi 客户端),搜索并连接设备,可以选择是否加密,按提示输入 Wi‑Fi SSID/密码并发送。
|
||||
4. 观察结果:
|
||||
- 成功:BluFi 报告连接成功,设备自动使用新的 Wi‑Fi。
|
||||
- 成功:BluFi 报告连接成功,设备自动连接 Wi‑Fi。
|
||||
- 失败:BluFi 返回失败状态,可重新发送或检查路由器。
|
||||
|
||||
## 注意事项
|
||||
|
||||
@ -286,6 +286,11 @@ elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_LCD_1_69)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_TOUCH_LCD_1_83)
|
||||
set(BOARD_TYPE "waveshare-c6-touch-lcd-1.83")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_16_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43)
|
||||
set(BOARD_TYPE "waveshare-c6-touch-amoled-1.43")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
@ -367,6 +372,11 @@ elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI2_ESP32C5)
|
||||
set(BOARD_TYPE "movecall-moji2-esp32c5")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3)
|
||||
set(BOARD_TYPE "movecall-cuican-esp32s3")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
|
||||
@ -273,6 +273,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_C6_LCD_1_69
|
||||
bool "Waveshare ESP32-C6-LCD-1.69"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_LCD_1_83
|
||||
bool "Waveshare ESP32-C6-Touch-LCD-1.83"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43
|
||||
bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
@ -330,6 +333,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_MOVECALL_MOJI_ESP32S3
|
||||
bool "Movecall Moji 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_MOVECALL_MOJI2_ESP32C5
|
||||
bool "Movecall Moji2.0 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3
|
||||
bool "Movecall CuiCan 璀璨·AI吊坠"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
|
||||
@ -1,33 +1,33 @@
|
||||
#include "blufi.h"
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "application.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_bt.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "wifi_manager.h"
|
||||
|
||||
// Bluedroid specific
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#endif
|
||||
|
||||
// NimBLE specific
|
||||
#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 "host/ble_hs.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "console/console.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);
|
||||
@ -62,11 +62,11 @@ void esp_blufi_btc_deinit(void);
|
||||
}
|
||||
|
||||
// mbedTLS for security
|
||||
#include "mbedtls/md5.h"
|
||||
#include <wifi_station.h>
|
||||
#include "esp_crc.h"
|
||||
#include "esp_random.h"
|
||||
#include "mbedtls/md5.h"
|
||||
#include "ssid_manager.h"
|
||||
#include <wifi_station.h>
|
||||
|
||||
// Logging Tag
|
||||
static const char *BLUFI_TAG = "BLUFI_CLASS";
|
||||
@ -84,20 +84,20 @@ static wifi_mode_t GetWifiModeWithFallback(const WifiManager &wifi) {
|
||||
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) {
|
||||
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) {
|
||||
// Initialize member variables
|
||||
memset(&m_sta_config, 0, sizeof(m_sta_config));
|
||||
memset(&m_ap_config, 0, sizeof(m_ap_config));
|
||||
@ -158,7 +158,6 @@ esp_err_t Blufi::deinit() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
esp_err_t Blufi::_host_init() {
|
||||
esp_err_t ret = esp_bluedroid_init();
|
||||
@ -177,7 +176,8 @@ esp_err_t Blufi::_host_init() {
|
||||
|
||||
esp_err_t Blufi::_host_deinit() {
|
||||
esp_err_t ret = esp_blufi_profile_deinit();
|
||||
if (ret != ESP_OK) return ret;
|
||||
if (ret != ESP_OK)
|
||||
return ret;
|
||||
|
||||
ret = esp_bluedroid_disable();
|
||||
if (ret) {
|
||||
@ -201,7 +201,7 @@ esp_err_t Blufi::_gap_register_callback() {
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_and_cb_init() {
|
||||
esp_blufi_callbacks_t blufi_callbacks = {
|
||||
static esp_blufi_callbacks_t blufi_callbacks = {
|
||||
.event_cb = &_event_callback_trampoline,
|
||||
.negotiate_data_handler = &_negotiate_data_handler_trampoline,
|
||||
.encrypt_func = &_encrypt_func_trampoline,
|
||||
@ -244,7 +244,7 @@ void Blufi::_nimble_on_sync() {
|
||||
|
||||
void Blufi::_nimble_host_task(void *param) {
|
||||
ESP_LOGI(BLUFI_TAG, "BLE Host Task Started");
|
||||
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
|
||||
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
@ -255,22 +255,22 @@ esp_err_t Blufi::_host_init() {
|
||||
ble_hs_cfg.gatts_register_cb = esp_blufi_gatt_svr_register_cb;
|
||||
|
||||
// Security Manager settings (can be customized)
|
||||
ble_hs_cfg.sm_io_cap = 4; // IO capability: No Input, No Output
|
||||
ble_hs_cfg.sm_io_cap = 4; // IO capability: No Input, No Output
|
||||
#ifdef CONFIG_EXAMPLE_BONDING
|
||||
ble_hs_cfg.sm_bonding=1;
|
||||
ble_hs_cfg.sm_bonding = 1;
|
||||
#endif
|
||||
|
||||
int rc = esp_blufi_gatt_svr_init();
|
||||
assert (rc== 0);
|
||||
int rc = esp_blufi_gatt_svr_init();
|
||||
assert(rc == 0);
|
||||
|
||||
ble_store_config_init(); // Configure the BLE storage
|
||||
esp_blufi_btc_init();
|
||||
ble_store_config_init(); // Configure the BLE storage
|
||||
esp_blufi_btc_init();
|
||||
|
||||
esp_err_t err = esp_nimble_enable(_nimble_host_task);
|
||||
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;
|
||||
}
|
||||
ESP_LOGE(BLUFI_TAG, "%s failed: %s", __func__, esp_err_to_name(err));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@ -286,11 +286,11 @@ esp_err_t Blufi::_host_deinit(void) {
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_gap_register_callback(void) {
|
||||
return ESP_OK; // For NimBLE, GAP callbacks are handled differently
|
||||
return ESP_OK; // For NimBLE, GAP callbacks are handled differently
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_and_cb_init() {
|
||||
esp_blufi_callbacks_t blufi_callbacks = {
|
||||
static esp_blufi_callbacks_t blufi_callbacks = {
|
||||
.event_cb = &_event_callback_trampoline,
|
||||
.negotiate_data_handler = &_negotiate_data_handler_trampoline,
|
||||
.encrypt_func = &_encrypt_func_trampoline,
|
||||
@ -350,8 +350,7 @@ esp_err_t Blufi::_controller_deinit() {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif // Generic controller init
|
||||
|
||||
#endif // Generic controller init
|
||||
|
||||
static int myrand(void *rng_state, unsigned char *output, size_t len) {
|
||||
esp_fill_random(output, len);
|
||||
@ -375,7 +374,8 @@ void Blufi::_security_init() {
|
||||
}
|
||||
|
||||
void Blufi::_security_deinit() {
|
||||
if (m_sec == nullptr) return;
|
||||
if (m_sec == nullptr)
|
||||
return;
|
||||
|
||||
if (m_sec->dh_param) {
|
||||
free(m_sec->dh_param);
|
||||
@ -388,23 +388,35 @@ void Blufi::_security_deinit() {
|
||||
m_sec = nullptr;
|
||||
}
|
||||
|
||||
void Blufi::_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len,
|
||||
bool *need_free) {
|
||||
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: /* DH_PARAM_LEN */
|
||||
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);
|
||||
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);
|
||||
@ -426,68 +438,95 @@ void Blufi::_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_
|
||||
}
|
||||
|
||||
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, DH_SELF_PUB_KEY_LEN, myrand,
|
||||
NULL);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_make_public failed %d", ret);
|
||||
|
||||
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) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_calc_secret failed %d", ret);
|
||||
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) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_md5 failed %d", ret);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_md5 failed: %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_CALC_MD5_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_aes_setkey_enc(m_sec->aes, m_sec->psk, PSK_LEN * 8);
|
||||
|
||||
*output_data = &m_sec->self_public_key[0];
|
||||
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 = NULL;
|
||||
m_sec->dh_param = nullptr;
|
||||
m_sec->dh_param_len = 0;
|
||||
break;
|
||||
}
|
||||
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) return -1;
|
||||
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;
|
||||
return mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0, crypt_data,
|
||||
crypt_data);
|
||||
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) return -1;
|
||||
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;
|
||||
return mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0, crypt_data,
|
||||
crypt_data);
|
||||
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()) {
|
||||
@ -507,6 +546,9 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *para
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI init finish");
|
||||
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;
|
||||
@ -523,10 +565,12 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *para
|
||||
esp_blufi_adv_stop();
|
||||
if (!m_deinited) {
|
||||
// Deinit BLE stack after provisioning completes to free resources.
|
||||
xTaskCreate([](void *ctx) {
|
||||
static_cast<Blufi *>(ctx)->deinit();
|
||||
vTaskDelete(nullptr);
|
||||
}, "blufi_deinit", 4096, this, 5, nullptr);
|
||||
xTaskCreate(
|
||||
[](void *ctx) {
|
||||
static_cast<Blufi *>(ctx)->deinit();
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"blufi_deinit", 4096, this, 5, nullptr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -575,71 +619,75 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *para
|
||||
m_sta_connected = false;
|
||||
m_sta_got_ip = false;
|
||||
m_sta_is_connecting = true;
|
||||
m_sta_conn_info = {}; // Reset connection info
|
||||
m_sta_conn_info = {}; // Reset connection info
|
||||
m_sta_conn_info.sta_ssid = m_sta_ssid;
|
||||
m_sta_conn_info.sta_ssid_len = m_sta_ssid_len;
|
||||
|
||||
wifi_manager.StartStation();
|
||||
|
||||
// Wait for connection in a separate task to avoid blocking the BLUFI handler.
|
||||
xTaskCreate([](void *ctx) {
|
||||
auto *self = static_cast<Blufi *>(ctx);
|
||||
auto &wifi = WifiManager::GetInstance();
|
||||
constexpr int kConnectTimeoutMs = 10000; // 10s
|
||||
constexpr TickType_t kDelayTick = pdMS_TO_TICKS(200);
|
||||
int waited_ms = 0;
|
||||
xTaskCreate(
|
||||
[](void *ctx) {
|
||||
auto *self = static_cast<Blufi *>(ctx);
|
||||
auto &wifi = WifiManager::GetInstance();
|
||||
constexpr int kConnectTimeoutMs = 10000; // 10s
|
||||
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);
|
||||
while (waited_ms < kConnectTimeoutMs && !wifi.IsConnected()) {
|
||||
vTaskDelay(kDelayTick);
|
||||
waited_ms += 200;
|
||||
}
|
||||
|
||||
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));
|
||||
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");
|
||||
|
||||
// Close BluFi session after successful provisioning to free resources.
|
||||
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");
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
// Close BluFi session after successful provisioning to free resources.
|
||||
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);
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"blufi_wifi_conn", 4096, this, 5, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
|
||||
@ -662,7 +710,8 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *para
|
||||
|
||||
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)));
|
||||
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);
|
||||
}
|
||||
|
||||
@ -671,11 +720,14 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *para
|
||||
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);
|
||||
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);
|
||||
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_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;
|
||||
@ -686,15 +738,16 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *para
|
||||
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);
|
||||
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,
|
||||
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");
|
||||
ESP_LOGI(BLUFI_TAG, "Recv STA PASSWORD : %s", m_sta_config.sta.password);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(BLUFI_TAG, "Unhandled event: %d", event);
|
||||
@ -702,13 +755,12 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *para
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include "esp32_camera.h"
|
||||
#include "power_manager.h"
|
||||
#include "power_save_timer.h"
|
||||
|
||||
#define TAG "esp32s3_korvo2_v3"
|
||||
/* ADC Buttons */
|
||||
@ -59,6 +61,24 @@ private:
|
||||
LcdDisplay* display_;
|
||||
esp_io_expander_handle_t io_expander_ = NULL;
|
||||
Esp32Camera* camera_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
void InitializePowerManager() {
|
||||
// PowerManager需要复用按钮的ADC句柄,所以在InitializeButtons之后调用
|
||||
// 传入按钮的ADC句柄指针,让PowerManager复用
|
||||
power_manager_ = new PowerManager(GPIO_NUM_NC, &bsp_adc_handle);
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
});
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@ -375,12 +395,14 @@ private:
|
||||
public:
|
||||
Esp32S3Korvo2V3Board() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
ESP_LOGI(TAG, "Initializing esp32s3_korvo2_v3 Board");
|
||||
InitializePowerSaveTimer();
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
InitializeTca9554();
|
||||
InitializeCamera();
|
||||
InitializeSpi();
|
||||
InitializeButtons();
|
||||
InitializeButtons(); // 先初始化按钮(创建ADC1句柄)
|
||||
InitializePowerManager(); // 后初始化PowerManager(复用ADC1句柄)
|
||||
#ifdef LCD_TYPE_ILI9341_SERIAL
|
||||
InitializeIli9341Display();
|
||||
#else
|
||||
@ -411,6 +433,24 @@ public:
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
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(Esp32S3Korvo2V3Board);
|
||||
|
||||
250
main/boards/esp32s3-korvo2-v3/power_manager.h
Normal file
250
main/boards/esp32s3-korvo2-v3/power_manager.h
Normal file
@ -0,0 +1,250 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
#include <esp_adc/adc_cali.h>
|
||||
#include <esp_adc/adc_cali_scheme.h>
|
||||
|
||||
|
||||
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_ = 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_;
|
||||
bool adc_handle_owned_ = false; // 标记ADC句柄是否由本类创建
|
||||
adc_cali_handle_t adc_cali_handle_ = nullptr; // ADC校准句柄
|
||||
|
||||
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_raw = 0;
|
||||
int voltage_mv = 0; // ADC校准后的电压(mV)
|
||||
|
||||
// 多次采样取平均,提高稳定性
|
||||
uint32_t adc_sum = 0;
|
||||
const int sample_count = 10;
|
||||
for (int i = 0; i < sample_count; i++) {
|
||||
int temp_raw = 0;
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &temp_raw));
|
||||
adc_sum += temp_raw;
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); // 每次采样间隔10ms
|
||||
}
|
||||
adc_raw = adc_sum / sample_count;
|
||||
|
||||
// 使用ADC校准获取准确电压
|
||||
if (adc_cali_handle_) {
|
||||
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_mv));
|
||||
} else {
|
||||
// 如果没有校准,使用线性计算
|
||||
voltage_mv = (int)(adc_raw * 3300.0f / 4095.0f);
|
||||
}
|
||||
|
||||
// 根据分压比计算实际电池电压
|
||||
// 电路分压比: R21/(R20+R21) = 100K/300K = 1/3
|
||||
// 实际电池电压 = ADC测量电压 × 3
|
||||
int battery_voltage_mv = voltage_mv * 3;
|
||||
|
||||
// 将电压值添加到队列中用于平滑
|
||||
adc_values_.push_back(battery_voltage_mv);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
|
||||
uint32_t average_voltage = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_voltage += value;
|
||||
}
|
||||
average_voltage /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间(基于实际电池电压,单位mV)
|
||||
const struct {
|
||||
uint16_t voltage_mv; // 电池电压(mV)
|
||||
uint8_t level; // 电量百分比
|
||||
} levels[] = {
|
||||
{3500, 0}, // 3.5V
|
||||
{3640, 20}, // 3.64V
|
||||
{3760, 40}, // 3.76V
|
||||
{3880, 60}, // 3.88V
|
||||
{4000, 80}, // 4.0V
|
||||
{4200, 100} // 4.2V
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_voltage < levels[0].voltage_mv) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_voltage >= levels[5].voltage_mv) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_voltage >= levels[i].voltage_mv && average_voltage < levels[i+1].voltage_mv) {
|
||||
float ratio = static_cast<float>(average_voltage - levels[i].voltage_mv) /
|
||||
(levels[i+1].voltage_mv - levels[i].voltage_mv);
|
||||
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 raw: %d, ADC voltage: %dmV, Battery: %ldmV (%.2fV), level: %ld%%",
|
||||
adc_raw, voltage_mv, average_voltage, average_voltage/1000.0f, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
// 构造函数:使用外部ADC句柄(用于复用已存在的ADC)
|
||||
PowerManager(gpio_num_t pin, adc_oneshot_unit_handle_t* external_adc_handle = nullptr)
|
||||
: charging_pin_(pin), adc_handle_owned_(false) {
|
||||
if(charging_pin_ != GPIO_NUM_NC){
|
||||
// 初始化充电引脚
|
||||
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_, 1000000));
|
||||
|
||||
// 初始化或复用 ADC
|
||||
if (external_adc_handle != nullptr && *external_adc_handle != nullptr) {
|
||||
// 复用外部ADC句柄
|
||||
adc_handle_ = *external_adc_handle;
|
||||
adc_handle_owned_ = false;
|
||||
} else {
|
||||
// 创建新的ADC句柄
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1, // GPIO6 对应 ADC1
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
adc_handle_owned_ = true;
|
||||
}
|
||||
|
||||
// 配置ADC通道
|
||||
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_5, &chan_config)); // GPIO6 = ADC1_CHANNEL_5
|
||||
|
||||
// 初始化ADC校准
|
||||
adc_cali_curve_fitting_config_t cali_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.chan = ADC_CHANNEL_5,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle_);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI("PowerManager", "ADC calibration initialized successfully");
|
||||
} else {
|
||||
ESP_LOGW("PowerManager", "ADC calibration failed, using linear calculation");
|
||||
adc_cali_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
// 删除ADC校准句柄
|
||||
if (adc_cali_handle_) {
|
||||
adc_cali_delete_scheme_curve_fitting(adc_cali_handle_);
|
||||
}
|
||||
// 只有当ADC句柄是本类创建的时候才删除
|
||||
if (adc_handle_ && adc_handle_owned_) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
57
main/boards/movecall-moji2-esp32c5/README.md
Normal file
57
main/boards/movecall-moji2-esp32c5/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Build and Configuration Guide
|
||||
|
||||
This document provides instructions on how to configure and build the firmware for **Movecall Moji2.0 (Xiaozhi AI Edition)**.
|
||||
|
||||
## 🛠 Prerequisites
|
||||
* **ESP-IDF Version**: v5.5
|
||||
* **Target Chip**: ESP32-C5
|
||||
|
||||
## 🔗 Hardware Information
|
||||
This project is based on the following open-source hardware:
|
||||
* **OSHWHub Link**: [https://oshwhub.com/movecall/moji2](https://oshwhub.com/movecall/moji2)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Build Steps
|
||||
|
||||
### 1. Set the Build Target
|
||||
Initialize the project to target the ESP32-C5 chip:
|
||||
```bash
|
||||
idf.py set-target esp32c5
|
||||
```
|
||||
|
||||
### 2. Configure the Board Type
|
||||
Open the graphical configuration menu:
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**Navigate to the following path to select your board:**
|
||||
> **Xiaozhi Assistant** -> **Board Type** -> **Movecall Moji2.0 小智AI衍生版**
|
||||
|
||||
*Note: After selecting, press **S** to save (then Enter to confirm) and press **Q** to exit.*
|
||||
|
||||
### 3. Build the Project
|
||||
Run the following command to start the compilation:
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Useful Commands
|
||||
|
||||
**Clean Build Files (Recommended if you encounter errors):**
|
||||
```bash
|
||||
idf.py fullclean
|
||||
```
|
||||
|
||||
**Flash Firmware to Device:**
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
**Monitor Serial Output:**
|
||||
```bash
|
||||
idf.py monitor
|
||||
```
|
||||
57
main/boards/movecall-moji2-esp32c5/README_zh.md
Normal file
57
main/boards/movecall-moji2-esp32c5/README_zh.md
Normal file
@ -0,0 +1,57 @@
|
||||
# 编译配置指南
|
||||
|
||||
本文档介绍了如何为 **Movecall Moji2.0 (小智AI衍生版)** 配置和编译固件。
|
||||
|
||||
## 🛠 环境要求
|
||||
* **ESP-IDF 版本**: v5.5
|
||||
* **芯片型号**: ESP32-C5
|
||||
|
||||
## 🔗 硬件开源信息
|
||||
本项目基于以下开源硬件设计:
|
||||
* **立创开源硬件平台**: [https://oshwhub.com/movecall/moji2](https://oshwhub.com/movecall/moji2)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 编译步骤
|
||||
|
||||
### 1. 设置编译目标
|
||||
首先,将项目目标芯片设置为 ESP32-C5:
|
||||
```bash
|
||||
idf.py set-target esp32c5
|
||||
```
|
||||
|
||||
### 2. 配置开发板型号
|
||||
运行以下命令打开配置菜单进行板型选择:
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**请在菜单中按照以下路径进行操作:**
|
||||
> **Xiaozhi Assistant** -> **Board Type** -> **Movecall Moji2.0 小智AI衍生版**
|
||||
|
||||
*操作提示:配置完成后,按 **S** 保存并按回车确认,按 **Q** 退出。*
|
||||
|
||||
### 3. 执行编译
|
||||
运行以下命令开始构建项目:
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常用维护命令
|
||||
|
||||
**清理编译缓存 (遇到报错建议执行):**
|
||||
```bash
|
||||
idf.py fullclean
|
||||
```
|
||||
|
||||
**烧录固件:**
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
**查看串口日志:**
|
||||
```bash
|
||||
idf.py monitor
|
||||
```
|
||||
68
main/boards/movecall-moji2-esp32c5/config.h
Normal file
68
main/boards/movecall-moji2-esp32c5/config.h
Normal file
@ -0,0 +1,68 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
// Movecall Moji 2 configuration
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
enum PowerSupply {
|
||||
kDeviceTypecSupply,
|
||||
kDeviceBatterySupply,
|
||||
};
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_25
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_24
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_11
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
|
||||
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_5
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_26
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_27
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_10
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_28
|
||||
|
||||
#define DISPLAY_WIDTH 360
|
||||
#define DISPLAY_HEIGHT 360
|
||||
#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_2
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#define DISPLAY_QSPI_H_RES (360)
|
||||
#define DISPLAY_QSPI_V_RES (360)
|
||||
#define DISPLAY_QSPI_BIT_PER_PIXEL (16)
|
||||
|
||||
#define DISPLAY_QSPI_HOST SPI2_HOST
|
||||
#define DISPLAY_QSPI_SCLK_PIN GPIO_NUM_0
|
||||
#define DISPLAY_QSPI_RESET_PIN GPIO_NUM_1
|
||||
#define DISPLAY_QSPI_D0_PIN GPIO_NUM_9
|
||||
#define DISPLAY_QSPI_D1_PIN GPIO_NUM_8
|
||||
#define DISPLAY_QSPI_D2_PIN GPIO_NUM_7
|
||||
#define DISPLAY_QSPI_D3_PIN GPIO_NUM_6
|
||||
#define DISPLAY_QSPI_CS_PIN GPIO_NUM_3
|
||||
|
||||
|
||||
#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
|
||||
|
||||
#define MOJI2_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
|
||||
{ \
|
||||
.data0_io_num = d0, \
|
||||
.data1_io_num = d1, \
|
||||
.sclk_io_num = sclk, \
|
||||
.data2_io_num = d2, \
|
||||
.data3_io_num = d3, \
|
||||
.max_transfer_sz = max_trans_sz, \
|
||||
}
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
18
main/boards/movecall-moji2-esp32c5/config.json
Normal file
18
main/boards/movecall-moji2-esp32c5/config.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"target": "esp32c5",
|
||||
"builds": [
|
||||
{
|
||||
"name": "movecall-moji2-esp32c5",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"",
|
||||
"CONFIG_PM_ENABLE=y",
|
||||
"CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
|
||||
"CONFIG_SPIRAM_MODE_QUAD=y",
|
||||
"CONFIG_SPIRAM_SPEED_80M=y",
|
||||
"CONFIG_SPIRAM_SPEED=80",
|
||||
"CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
390
main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc
Normal file
390
main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc
Normal file
@ -0,0 +1,390 @@
|
||||
#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 "led/single_led.h"
|
||||
#include "press_to_talk_mcp_tool.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_efuse_table.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include "power_save_timer.h"
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
|
||||
#include <esp_lcd_st77916.h>
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
#include "adc_battery_monitor.h"
|
||||
#include "sleep_timer.h"
|
||||
|
||||
#define TAG "MovecallMoji2ESP32C5"
|
||||
|
||||
static const st77916_lcd_init_cmd_t lcd_init_cmds[] = {
|
||||
{0xF0, (uint8_t []){0x28}, 1, 0},
|
||||
{0xF2, (uint8_t []){0x28}, 1, 0},
|
||||
{0x73, (uint8_t []){0xF0}, 1, 0},
|
||||
{0x7C, (uint8_t []){0xD1}, 1, 0},
|
||||
{0x83, (uint8_t []){0xE0}, 1, 0},
|
||||
{0x84, (uint8_t []){0x61}, 1, 0},
|
||||
{0xF2, (uint8_t []){0x82}, 1, 0},
|
||||
{0xF0, (uint8_t []){0x00}, 1, 0},
|
||||
{0xF0, (uint8_t []){0x01}, 1, 0},
|
||||
{0xF1, (uint8_t []){0x01}, 1, 0},
|
||||
{0xB0, (uint8_t []){0x56}, 1, 0},
|
||||
{0xB1, (uint8_t []){0x4D}, 1, 0},
|
||||
{0xB2, (uint8_t []){0x24}, 1, 0},
|
||||
{0xB4, (uint8_t []){0x87}, 1, 0},
|
||||
{0xB5, (uint8_t []){0x44}, 1, 0},
|
||||
{0xB6, (uint8_t []){0x8B}, 1, 0},
|
||||
{0xB7, (uint8_t []){0x40}, 1, 0},
|
||||
{0xB8, (uint8_t []){0x86}, 1, 0},
|
||||
{0xBA, (uint8_t []){0x00}, 1, 0},
|
||||
{0xBB, (uint8_t []){0x08}, 1, 0},
|
||||
{0xBC, (uint8_t []){0x08}, 1, 0},
|
||||
{0xBD, (uint8_t []){0x00}, 1, 0},
|
||||
{0xC0, (uint8_t []){0x80}, 1, 0},
|
||||
{0xC1, (uint8_t []){0x10}, 1, 0},
|
||||
{0xC2, (uint8_t []){0x37}, 1, 0},
|
||||
{0xC3, (uint8_t []){0x80}, 1, 0},
|
||||
{0xC4, (uint8_t []){0x10}, 1, 0},
|
||||
{0xC5, (uint8_t []){0x37}, 1, 0},
|
||||
{0xC6, (uint8_t []){0xA9}, 1, 0},
|
||||
{0xC7, (uint8_t []){0x41}, 1, 0},
|
||||
{0xC8, (uint8_t []){0x01}, 1, 0},
|
||||
{0xC9, (uint8_t []){0xA9}, 1, 0},
|
||||
{0xCA, (uint8_t []){0x41}, 1, 0},
|
||||
{0xCB, (uint8_t []){0x01}, 1, 0},
|
||||
{0xD0, (uint8_t []){0x91}, 1, 0},
|
||||
{0xD1, (uint8_t []){0x68}, 1, 0},
|
||||
{0xD2, (uint8_t []){0x68}, 1, 0},
|
||||
{0xF5, (uint8_t []){0x00, 0xA5}, 2, 0},
|
||||
{0xDD, (uint8_t []){0x4F}, 1, 0},
|
||||
{0xDE, (uint8_t []){0x4F}, 1, 0},
|
||||
{0xF1, (uint8_t []){0x10}, 1, 0},
|
||||
{0xF0, (uint8_t []){0x00}, 1, 0},
|
||||
{0xF0, (uint8_t []){0x02}, 1, 0},
|
||||
{0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0},
|
||||
{0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0},
|
||||
{0xF0, (uint8_t []){0x10}, 1, 0},
|
||||
{0xF3, (uint8_t []){0x10}, 1, 0},
|
||||
{0xE0, (uint8_t []){0x07}, 1, 0},
|
||||
{0xE1, (uint8_t []){0x00}, 1, 0},
|
||||
{0xE2, (uint8_t []){0x00}, 1, 0},
|
||||
{0xE3, (uint8_t []){0x00}, 1, 0},
|
||||
{0xE4, (uint8_t []){0xE0}, 1, 0},
|
||||
{0xE5, (uint8_t []){0x06}, 1, 0},
|
||||
{0xE6, (uint8_t []){0x21}, 1, 0},
|
||||
{0xE7, (uint8_t []){0x01}, 1, 0},
|
||||
{0xE8, (uint8_t []){0x05}, 1, 0},
|
||||
{0xE9, (uint8_t []){0x02}, 1, 0},
|
||||
{0xEA, (uint8_t []){0xDA}, 1, 0},
|
||||
{0xEB, (uint8_t []){0x00}, 1, 0},
|
||||
{0xEC, (uint8_t []){0x00}, 1, 0},
|
||||
{0xED, (uint8_t []){0x0F}, 1, 0},
|
||||
{0xEE, (uint8_t []){0x00}, 1, 0},
|
||||
{0xEF, (uint8_t []){0x00}, 1, 0},
|
||||
{0xF8, (uint8_t []){0x00}, 1, 0},
|
||||
{0xF9, (uint8_t []){0x00}, 1, 0},
|
||||
{0xFA, (uint8_t []){0x00}, 1, 0},
|
||||
{0xFB, (uint8_t []){0x00}, 1, 0},
|
||||
{0xFC, (uint8_t []){0x00}, 1, 0},
|
||||
{0xFD, (uint8_t []){0x00}, 1, 0},
|
||||
{0xFE, (uint8_t []){0x00}, 1, 0},
|
||||
{0xFF, (uint8_t []){0x00}, 1, 0},
|
||||
{0x60, (uint8_t []){0x40}, 1, 0},
|
||||
{0x61, (uint8_t []){0x04}, 1, 0},
|
||||
{0x62, (uint8_t []){0x00}, 1, 0},
|
||||
{0x63, (uint8_t []){0x42}, 1, 0},
|
||||
{0x64, (uint8_t []){0xD9}, 1, 0},
|
||||
{0x65, (uint8_t []){0x00}, 1, 0},
|
||||
{0x66, (uint8_t []){0x00}, 1, 0},
|
||||
{0x67, (uint8_t []){0x00}, 1, 0},
|
||||
{0x68, (uint8_t []){0x00}, 1, 0},
|
||||
{0x69, (uint8_t []){0x00}, 1, 0},
|
||||
{0x6A, (uint8_t []){0x00}, 1, 0},
|
||||
{0x6B, (uint8_t []){0x00}, 1, 0},
|
||||
{0x70, (uint8_t []){0x40}, 1, 0},
|
||||
{0x71, (uint8_t []){0x03}, 1, 0},
|
||||
{0x72, (uint8_t []){0x00}, 1, 0},
|
||||
{0x73, (uint8_t []){0x42}, 1, 0},
|
||||
{0x74, (uint8_t []){0xD8}, 1, 0},
|
||||
{0x75, (uint8_t []){0x00}, 1, 0},
|
||||
{0x76, (uint8_t []){0x00}, 1, 0},
|
||||
{0x77, (uint8_t []){0x00}, 1, 0},
|
||||
{0x78, (uint8_t []){0x00}, 1, 0},
|
||||
{0x79, (uint8_t []){0x00}, 1, 0},
|
||||
{0x7A, (uint8_t []){0x00}, 1, 0},
|
||||
{0x7B, (uint8_t []){0x00}, 1, 0},
|
||||
{0x80, (uint8_t []){0x48}, 1, 0},
|
||||
{0x81, (uint8_t []){0x00}, 1, 0},
|
||||
{0x82, (uint8_t []){0x06}, 1, 0},
|
||||
{0x83, (uint8_t []){0x02}, 1, 0},
|
||||
{0x84, (uint8_t []){0xD6}, 1, 0},
|
||||
{0x85, (uint8_t []){0x04}, 1, 0},
|
||||
{0x86, (uint8_t []){0x00}, 1, 0},
|
||||
{0x87, (uint8_t []){0x00}, 1, 0},
|
||||
{0x88, (uint8_t []){0x48}, 1, 0},
|
||||
{0x89, (uint8_t []){0x00}, 1, 0},
|
||||
{0x8A, (uint8_t []){0x08}, 1, 0},
|
||||
{0x8B, (uint8_t []){0x02}, 1, 0},
|
||||
{0x8C, (uint8_t []){0xD8}, 1, 0},
|
||||
{0x8D, (uint8_t []){0x04}, 1, 0},
|
||||
{0x8E, (uint8_t []){0x00}, 1, 0},
|
||||
{0x8F, (uint8_t []){0x00}, 1, 0},
|
||||
{0x90, (uint8_t []){0x48}, 1, 0},
|
||||
{0x91, (uint8_t []){0x00}, 1, 0},
|
||||
{0x92, (uint8_t []){0x0A}, 1, 0},
|
||||
{0x93, (uint8_t []){0x02}, 1, 0},
|
||||
{0x94, (uint8_t []){0xDA}, 1, 0},
|
||||
{0x95, (uint8_t []){0x04}, 1, 0},
|
||||
{0x96, (uint8_t []){0x00}, 1, 0},
|
||||
{0x97, (uint8_t []){0x00}, 1, 0},
|
||||
{0x98, (uint8_t []){0x48}, 1, 0},
|
||||
{0x99, (uint8_t []){0x00}, 1, 0},
|
||||
{0x9A, (uint8_t []){0x0C}, 1, 0},
|
||||
{0x9B, (uint8_t []){0x02}, 1, 0},
|
||||
{0x9C, (uint8_t []){0xDC}, 1, 0},
|
||||
{0x9D, (uint8_t []){0x04}, 1, 0},
|
||||
{0x9E, (uint8_t []){0x00}, 1, 0},
|
||||
{0x9F, (uint8_t []){0x00}, 1, 0},
|
||||
{0xA0, (uint8_t []){0x48}, 1, 0},
|
||||
{0xA1, (uint8_t []){0x00}, 1, 0},
|
||||
{0xA2, (uint8_t []){0x05}, 1, 0},
|
||||
{0xA3, (uint8_t []){0x02}, 1, 0},
|
||||
{0xA4, (uint8_t []){0xD5}, 1, 0},
|
||||
{0xA5, (uint8_t []){0x04}, 1, 0},
|
||||
{0xA6, (uint8_t []){0x00}, 1, 0},
|
||||
{0xA7, (uint8_t []){0x00}, 1, 0},
|
||||
{0xA8, (uint8_t []){0x48}, 1, 0},
|
||||
{0xA9, (uint8_t []){0x00}, 1, 0},
|
||||
{0xAA, (uint8_t []){0x07}, 1, 0},
|
||||
{0xAB, (uint8_t []){0x02}, 1, 0},
|
||||
{0xAC, (uint8_t []){0xD7}, 1, 0},
|
||||
{0xAD, (uint8_t []){0x04}, 1, 0},
|
||||
{0xAE, (uint8_t []){0x00}, 1, 0},
|
||||
{0xAF, (uint8_t []){0x00}, 1, 0},
|
||||
{0xB0, (uint8_t []){0x48}, 1, 0},
|
||||
{0xB1, (uint8_t []){0x00}, 1, 0},
|
||||
{0xB2, (uint8_t []){0x09}, 1, 0},
|
||||
{0xB3, (uint8_t []){0x02}, 1, 0},
|
||||
{0xB4, (uint8_t []){0xD9}, 1, 0},
|
||||
{0xB5, (uint8_t []){0x04}, 1, 0},
|
||||
{0xB6, (uint8_t []){0x00}, 1, 0},
|
||||
{0xB7, (uint8_t []){0x00}, 1, 0},
|
||||
{0xB8, (uint8_t []){0x48}, 1, 0},
|
||||
{0xB9, (uint8_t []){0x00}, 1, 0},
|
||||
{0xBA, (uint8_t []){0x0B}, 1, 0},
|
||||
{0xBB, (uint8_t []){0x02}, 1, 0},
|
||||
{0xBC, (uint8_t []){0xDB}, 1, 0},
|
||||
{0xBD, (uint8_t []){0x04}, 1, 0},
|
||||
{0xBE, (uint8_t []){0x00}, 1, 0},
|
||||
{0xBF, (uint8_t []){0x00}, 1, 0},
|
||||
{0xC0, (uint8_t []){0x10}, 1, 0},
|
||||
{0xC1, (uint8_t []){0x47}, 1, 0},
|
||||
{0xC2, (uint8_t []){0x56}, 1, 0},
|
||||
{0xC3, (uint8_t []){0x65}, 1, 0},
|
||||
{0xC4, (uint8_t []){0x74}, 1, 0},
|
||||
{0xC5, (uint8_t []){0x88}, 1, 0},
|
||||
{0xC6, (uint8_t []){0x99}, 1, 0},
|
||||
{0xC7, (uint8_t []){0x01}, 1, 0},
|
||||
{0xC8, (uint8_t []){0xBB}, 1, 0},
|
||||
{0xC9, (uint8_t []){0xAA}, 1, 0},
|
||||
{0xD0, (uint8_t []){0x10}, 1, 0},
|
||||
{0xD1, (uint8_t []){0x47}, 1, 0},
|
||||
{0xD2, (uint8_t []){0x56}, 1, 0},
|
||||
{0xD3, (uint8_t []){0x65}, 1, 0},
|
||||
{0xD4, (uint8_t []){0x74}, 1, 0},
|
||||
{0xD5, (uint8_t []){0x88}, 1, 0},
|
||||
{0xD6, (uint8_t []){0x99}, 1, 0},
|
||||
{0xD7, (uint8_t []){0x01}, 1, 0},
|
||||
{0xD8, (uint8_t []){0xBB}, 1, 0},
|
||||
{0xD9, (uint8_t []){0xAA}, 1, 0},
|
||||
{0xF3, (uint8_t []){0x01}, 1, 0},
|
||||
{0xF0, (uint8_t []){0x00}, 1, 0},
|
||||
{0x21, (uint8_t []){}, 0, 0},
|
||||
{0x11, (uint8_t []){}, 0, 0},
|
||||
{0x00, (uint8_t []){}, 0, 120},
|
||||
};
|
||||
|
||||
class MovecallMoji2ESP32C5 : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
Display* display_;
|
||||
|
||||
PressToTalkMcpTool* press_to_talk_tool_ = nullptr;
|
||||
|
||||
PowerSaveTimer* power_save_timer_ = nullptr;
|
||||
AdcBatteryMonitor* adc_battery_monitor_ = nullptr;
|
||||
|
||||
void InitializeBatteryMonitor() {
|
||||
adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3, 5100000, 5100000, GPIO_NUM_NC);
|
||||
adc_battery_monitor_->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(240, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
});
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
// 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, &codec_i2c_bus_));
|
||||
}
|
||||
|
||||
// SPI初始化
|
||||
void InitializeSpi() {
|
||||
ESP_LOGI(TAG, "Initialize SPI bus");
|
||||
const spi_bus_config_t bus_config = MOJI2_ST77916_PANEL_BUS_QSPI_CONFIG(DISPLAY_QSPI_SCLK_PIN,
|
||||
DISPLAY_QSPI_D0_PIN,
|
||||
DISPLAY_QSPI_D1_PIN,
|
||||
DISPLAY_QSPI_D2_PIN,
|
||||
DISPLAY_QSPI_D3_PIN,
|
||||
DISPLAY_QSPI_H_RES * 80 * sizeof(uint16_t));
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_QSPI_HOST, &bus_config, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
// St77916 初始化
|
||||
void InitializeSt77916Display() {
|
||||
ESP_LOGI(TAG, "Init St77916 display");
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
ESP_LOGI(TAG, "Install panel IO");
|
||||
|
||||
esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(DISPLAY_QSPI_CS_PIN, NULL, NULL);
|
||||
// io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)DISPLAY_QSPI_HOST, &io_config, &panel_io));
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "Install St77916 panel driver");
|
||||
st77916_vendor_config_t vendor_config = {
|
||||
.init_cmds = lcd_init_cmds,
|
||||
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st77916_lcd_init_cmd_t),
|
||||
.flags = {
|
||||
.use_qspi_interface = 1,
|
||||
},
|
||||
};
|
||||
const esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = DISPLAY_QSPI_RESET_PIN,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = DISPLAY_QSPI_BIT_PER_PIXEL,
|
||||
.vendor_config = &vendor_config,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_disp_on_off(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);
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
// During startup (before connected), pressing BOOT enters config mode without reboot
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) {
|
||||
app.ToggleChatState();
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
if (power_save_timer_) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
|
||||
Application::GetInstance().StartListening();
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressUp([this]() {
|
||||
if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
|
||||
Application::GetInstance().StopListening();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeTools() {
|
||||
press_to_talk_tool_ = new PressToTalkMcpTool();
|
||||
press_to_talk_tool_->Initialize();
|
||||
}
|
||||
|
||||
public:
|
||||
MovecallMoji2ESP32C5() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeCodecI2c();
|
||||
InitializeBatteryMonitor();
|
||||
InitializePowerSaveTimer();
|
||||
InitializeSpi();
|
||||
InitializeSt77916Display();
|
||||
InitializeButtons();
|
||||
InitializeTools();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual Led* GetLed() override {
|
||||
static SingleLed led_strip(BUILTIN_LED_GPIO);
|
||||
return &led_strip;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
charging = adc_battery_monitor_->IsCharging();
|
||||
discharging = adc_battery_monitor_->IsDischarging();
|
||||
level = adc_battery_monitor_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
DECLARE_BOARD(MovecallMoji2ESP32C5);
|
||||
49
main/boards/waveshare-c6-touch-lcd-1.83/README.md
Normal file
49
main/boards/waveshare-c6-touch-lcd-1.83/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
# 产品链接
|
||||
微雪电子 ESP32-C6-Touch-LCD-1.83
|
||||
|
||||
(https://www.waveshare.net/shop/ESP32-C6-Touch-LCD-1.83.htm)
|
||||
|
||||
# 编译配置命令
|
||||
|
||||
**克隆工程**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/78/xiaozhi-esp32.git
|
||||
```
|
||||
|
||||
**进入工程**
|
||||
|
||||
```bash
|
||||
cd xiaozhi-esp32
|
||||
```
|
||||
|
||||
**配置编译目标为 ESP32C6**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32c6
|
||||
```
|
||||
|
||||
**打开 menuconfig**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子**
|
||||
|
||||
```bash
|
||||
Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-Touch-LCD-1.83
|
||||
```
|
||||
|
||||
**编译**
|
||||
|
||||
```ba
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**下载并打开串口终端**
|
||||
|
||||
```bash
|
||||
idf.py build flash monitor
|
||||
```
|
||||
50
main/boards/waveshare-c6-touch-lcd-1.83/config.h
Normal file
50
main/boards/waveshare-c6-touch-lcd-1.83/config.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_22
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
|
||||
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_0
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_9
|
||||
#define PWR_BUTTON_GPIO GPIO_NUM_18
|
||||
|
||||
#define DISPLAY_SPI_MODE SPI2_HOST
|
||||
#define DISPLAY_CS_PIN GPIO_NUM_5
|
||||
#define DISPLAY_MOSI_PIN GPIO_NUM_2
|
||||
#define DISPLAY_MISO_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_CLK_PIN GPIO_NUM_1
|
||||
#define DISPLAY_DC_PIN GPIO_NUM_3
|
||||
#define DISPLAY_RST_PIN GPIO_NUM_4
|
||||
|
||||
#define DISPLAY_TOUCH_INT_PIN GPIO_NUM_11
|
||||
#define DISPLAY_TOUCH_RST_PIN GPIO_NUM_NC
|
||||
|
||||
#define DISPLAY_WIDTH 240
|
||||
#define DISPLAY_HEIGHT 284
|
||||
#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_6
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
12
main/boards/waveshare-c6-touch-lcd-1.83/config.json
Normal file
12
main/boards/waveshare-c6-touch-lcd-1.83/config.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"target": "esp32c6",
|
||||
"builds": [
|
||||
{
|
||||
"name": "waveshare-c6-touch-lcd-1.83",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
|
||||
"CONFIG_USE_DEVICE_AEC=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -0,0 +1,261 @@
|
||||
#include "wifi_board.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "codecs/box_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "led/single_led.h"
|
||||
#include "mcp_server.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "axp2101.h"
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_master.h>
|
||||
#include "settings.h"
|
||||
|
||||
#include <esp_lcd_touch_cst816s.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
#define TAG "WaveshareEsp32c6TouchLCD1inch83"
|
||||
|
||||
class Pmic : public Axp2101 {
|
||||
public:
|
||||
Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
|
||||
WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
|
||||
WriteReg(0x27, 0x10); // hold 4s to power off
|
||||
|
||||
// Disable All DCs but DC1
|
||||
WriteReg(0x80, 0x01);
|
||||
// Disable All LDOs
|
||||
WriteReg(0x90, 0x00);
|
||||
WriteReg(0x91, 0x00);
|
||||
|
||||
// Set DC1 to 3.3V
|
||||
WriteReg(0x82, (3300 - 1500) / 100);
|
||||
|
||||
// Set ALDO1 to 3.3V
|
||||
WriteReg(0x92, (3300 - 500) / 100);
|
||||
|
||||
// Enable ALDO1(MIC)
|
||||
WriteReg(0x90, 0x01);
|
||||
|
||||
WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
|
||||
|
||||
WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
|
||||
WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
|
||||
WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
|
||||
}
|
||||
};
|
||||
|
||||
class WaveshareEsp32c6TouchLCD1inch83 : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Pmic* pmic_ = nullptr;
|
||||
Button boot_button_;
|
||||
Display* display_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
GetBacklight()->SetBrightness(20); });
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
GetBacklight()->RestoreBrightness(); });
|
||||
power_save_timer_->OnShutdownRequest([this](){
|
||||
pmic_->PowerOff(); });
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
// 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,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeAxp2101() {
|
||||
ESP_LOGI(TAG, "Init AXP2101");
|
||||
pmic_ = new Pmic(i2c_bus_, 0x34);
|
||||
}
|
||||
|
||||
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(DISPLAY_SPI_MODE, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
#if CONFIG_USE_DEVICE_AEC
|
||||
boot_button_.OnDoubleClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateIdle) {
|
||||
app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void InitializeDisplay() {
|
||||
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 = 0;
|
||||
io_config.pclk_hz = 24 * 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(DISPLAY_SPI_MODE, &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 = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
// esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
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 InitializeTouch() {
|
||||
esp_lcd_touch_handle_t tp;
|
||||
esp_lcd_touch_config_t tp_cfg = {
|
||||
.x_max = DISPLAY_WIDTH - 1,
|
||||
.y_max = DISPLAY_HEIGHT - 1,
|
||||
.rst_gpio_num = DISPLAY_TOUCH_RST_PIN,
|
||||
.int_gpio_num = DISPLAY_TOUCH_INT_PIN,
|
||||
.levels = {
|
||||
.reset = 0,
|
||||
.interrupt = 0,
|
||||
},
|
||||
.flags = {
|
||||
.swap_xy = 0,
|
||||
.mirror_x = 0,
|
||||
.mirror_y = 0,
|
||||
},
|
||||
};
|
||||
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
|
||||
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
|
||||
tp_io_config.scl_speed_hz = 400* 1000;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
|
||||
ESP_LOGI(TAG, "Initialize touch controller");
|
||||
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &tp));
|
||||
const lvgl_port_touch_cfg_t touch_cfg = {
|
||||
.disp = lv_display_get_default(),
|
||||
.handle = tp,
|
||||
};
|
||||
lvgl_port_add_touch(&touch_cfg);
|
||||
ESP_LOGI(TAG, "Touch panel initialized successfully");
|
||||
}
|
||||
|
||||
// 初始化工具
|
||||
void InitializeTools() {
|
||||
auto &mcp_server = McpServer::GetInstance();
|
||||
mcp_server.AddTool("self.system.reconfigure_wifi",
|
||||
"Reboot the device and enter WiFi configuration mode.\n"
|
||||
"**CAUTION** You must ask the user to confirm this action.",
|
||||
PropertyList(), [this](const PropertyList& properties) {
|
||||
EnterWifiConfigMode();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
WaveshareEsp32c6TouchLCD1inch83() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializePowerSaveTimer();
|
||||
InitializeCodecI2c();
|
||||
InitializeAxp2101();
|
||||
InitializeSpi();
|
||||
InitializeDisplay();
|
||||
InitializeTouch();
|
||||
InitializeButtons();
|
||||
InitializeTools();
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static BoxAudioCodec audio_codec(
|
||||
i2c_bus_,
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK,
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
AUDIO_CODEC_ES7210_ADDR,
|
||||
AUDIO_INPUT_REFERENCE);
|
||||
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 = pmic_->IsCharging();
|
||||
discharging = pmic_->IsDischarging();
|
||||
if (discharging != last_discharging)
|
||||
{
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
|
||||
level = pmic_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
DECLARE_BOARD(WaveshareEsp32c6TouchLCD1inch83);
|
||||
@ -1,46 +1,56 @@
|
||||
# 小智云聊S3
|
||||
# 小智云聊 S3
|
||||
|
||||
## 简介
|
||||
小智云聊S3是小智AI的魔改项目,是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品,做了大量创新和优化。
|
||||
|
||||
小智云聊 S3 是小智 AI 的魔改项目,是首个 2.8 寸护眼大屏+大字体+2000mah 大电池的量产成品,做了大量创新和优化。
|
||||
|
||||
## 合并版
|
||||
合并版代码在小智AI主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。
|
||||
|
||||
>### 按键操作
|
||||
>- **开机**: 关机状态,长按1秒后释放按键,自动开机
|
||||
>- **关机**: 开机状态,长按1秒后释放按键,标题栏会显示'请稍候',再等2秒自动关机
|
||||
>- **唤醒/打断**: 正常通话环境下,单击按键
|
||||
>- **切换4G/Wifi**: 启动过程或者配网界面,1秒钟内双击按键(需安装4G模块)
|
||||
>- **重新配网**: 开机状态,1秒钟内三击按键,会自动重启并进入配网界面
|
||||
合并版代码在小智 AI 主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G 自由切换等功能。
|
||||
|
||||
> ### 按键操作
|
||||
>
|
||||
> - **开机**: 关机状态,长按 1 秒后释放按键,自动开机。
|
||||
> - **关机**: 开机状态,长按 1 秒后释放按键,标题栏会显示'请稍候',再等 2 秒自动关机。
|
||||
> - **唤醒/打断**: 正常通话环境下,单击按键。
|
||||
> - **切换 4G/Wifi**: 启动过程或者配网界面,1 秒钟内双击按键(需安装 4G 模块)。
|
||||
> - **重新配网**: 开机状态,1 秒钟内三击按键,会自动重启并进入配网界面。
|
||||
|
||||
> ### 语音指令
|
||||
>
|
||||
> - **打开/关闭语音打断模式**: 在播放音乐时,需要关闭语音打断模式,否则可能会打断音乐播放。
|
||||
> - **切换 IPS 屏幕显示模式**: 新版小智云聊 S3 升级了 IPS 屏幕,需要切换屏幕显示模式后才能正常显示,可以来回切换。
|
||||
|
||||
## 魔改版
|
||||
|
||||
魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。
|
||||
|
||||
>### 为什么是魔改
|
||||
>- 首个实现微信二维码配网。
|
||||
>- 首个支持单手机配网。
|
||||
>- 首个支持扫二维码访问控制台。
|
||||
>- 首发支持繁体、日文、英文版界面
|
||||
>- 首个全语音操控模式
|
||||
>- 独家提供一键刷机脚本等多种刷机方式
|
||||
> ### 为什么是魔改
|
||||
>
|
||||
> - 首个实现微信二维码配网。
|
||||
> - 首个支持单手机配网。
|
||||
> - 首个支持扫二维码访问控制台。
|
||||
> - 首发支持繁体、日文、英文版界面。
|
||||
> - 首个全语音操控模式。
|
||||
> - 独家提供一键刷机脚本等多种刷机方式。
|
||||
|
||||
## 版本区别
|
||||
>| 特性 | 合并版 | 魔改版 |
|
||||
>| --- | --- | --- |
|
||||
>| 语音打断 | ✓ | ✓ |
|
||||
>| 4G功能 | ✓ | ✓ |
|
||||
>| 自动更新固件 | ✓ | X |
|
||||
>| 第三方固件支持 | ✓ | X |
|
||||
>| 天气待机界面 | X | ✓ |
|
||||
>| 闹钟提醒 | X | ✓ |
|
||||
>| 网络音乐播放 | X | ✓ |
|
||||
>| 微信扫码配网 | X | ✓ |
|
||||
>| 单手机配网 | X | ✓ |
|
||||
>| 扫码访问控制台 | X | ✓ |
|
||||
>| 繁日英文界面 | X | ✓ |
|
||||
>| 多语言支持 | X | ✓ |
|
||||
>| 外接蓝牙音箱 | X | ✓ |
|
||||
|
||||
> | 特性 | 合并版 | 魔改版 |
|
||||
> | -------------- | ------ | ------ |
|
||||
> | 语音打断 | ✓ | ✓ |
|
||||
> | 4G 功能 | ✓ | ✓ |
|
||||
> | 自动更新固件 | ✓ | X |
|
||||
> | 第三方固件支持 | ✓ | X |
|
||||
> | 天气待机界面 | X | ✓ |
|
||||
> | 闹钟提醒 | X | ✓ |
|
||||
> | 网络音乐播放 | X | ✓ |
|
||||
> | 微信扫码配网 | X | ✓ |
|
||||
> | 单手机配网 | X | ✓ |
|
||||
> | 扫码访问控制台 | X | ✓ |
|
||||
> | 繁日英文界面 | X | ✓ |
|
||||
> | 多语言支持 | X | ✓ |
|
||||
> | 外接蓝牙音箱 | X | ✓ |
|
||||
|
||||
# 编译配置命令
|
||||
|
||||
@ -85,4 +95,3 @@ idf.py build
|
||||
```bash
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
|
||||
@ -1,21 +1,22 @@
|
||||
#include "lvgl_theme.h"
|
||||
#include "dual_network_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "power_manager.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
|
||||
#include <esp_log.h>
|
||||
#include "settings.h"
|
||||
#include "application.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "button.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "config.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "dual_network_board.h"
|
||||
#include "lvgl_theme.h"
|
||||
#include "power_manager.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "mcp_server.h"
|
||||
|
||||
#define TAG "YunliaoS3"
|
||||
|
||||
class YunliaoS3 : public DualNetworkBoard {
|
||||
private:
|
||||
private:
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
SpiLcdDisplay* display_;
|
||||
@ -49,9 +50,10 @@ private:
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
.flags =
|
||||
{
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
||||
}
|
||||
@ -63,8 +65,10 @@ private:
|
||||
buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK;
|
||||
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(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
buscfg.max_transfer_sz =
|
||||
DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
|
||||
ESP_ERROR_CHECK(
|
||||
spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
@ -76,23 +80,26 @@ private:
|
||||
boot_button_.OnDoubleClick([this]() {
|
||||
ESP_LOGI(TAG, "Button OnDoubleClick");
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting ||
|
||||
app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
SwitchNetworkType();
|
||||
}
|
||||
});
|
||||
boot_button_.OnMultipleClick([this]() {
|
||||
ESP_LOGI(TAG, "Button OnThreeClick");
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
}
|
||||
},3);
|
||||
});
|
||||
boot_button_.OnMultipleClick(
|
||||
[this]() {
|
||||
ESP_LOGI(TAG, "Button OnThreeClick");
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
auto& wifi_board =
|
||||
static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
}
|
||||
}, 3);
|
||||
boot_button_.OnLongPress([this]() {
|
||||
ESP_LOGI(TAG, "Button LongPress to Sleep");
|
||||
display_->SetStatus(Lang::Strings::PLEASE_WAIT);
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
power_manager_->Sleep();
|
||||
});
|
||||
});
|
||||
}
|
||||
void InitializeSt7789Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
@ -107,19 +114,23 @@ private:
|
||||
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(DISPLAY_SPI_LCD_HOST, &io_config, &panel_io));
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST,
|
||||
&io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7789
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
Settings settings("display", false);
|
||||
bool currentIpsMode = settings.GetBool("ips_mode", DISPLAY_INVERT_COLOR);
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST;
|
||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
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_invert_color(panel, currentIpsMode);
|
||||
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,
|
||||
@ -132,12 +143,50 @@ private:
|
||||
display_->SetTheme(theme);
|
||||
}
|
||||
}
|
||||
void InitializeTools(){
|
||||
auto& mcp_server = McpServer::GetInstance();
|
||||
|
||||
mcp_server.AddTool("self.system.set_aec",
|
||||
"Enable or disable voice interruption mode (AEC:Acoustic Echo Cancellation). When enabled, the device can detect voice interruptions and respond accordingly.",
|
||||
PropertyList({
|
||||
Property("enable", kPropertyTypeBoolean)
|
||||
}), [this](const PropertyList& properties) {
|
||||
bool enable = properties["enable"].value<bool>();
|
||||
SetAecMode(enable);
|
||||
return true;
|
||||
});
|
||||
|
||||
public:
|
||||
YunliaoS3() :
|
||||
DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
|
||||
boot_button_(BOOT_BUTTON_PIN),
|
||||
power_manager_(new PowerManager()){
|
||||
mcp_server.AddTool("self.system.switch_TFT",
|
||||
"Switch TFT display mode between normal and inverted colors. This will toggle the IPS mode and reboot the device.",
|
||||
PropertyList(), [this](const PropertyList& properties) {
|
||||
SwitchTFT();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
void SetAecMode(bool enable) {
|
||||
AecMode newMode = enable ? kAecOnDeviceSide : kAecOff;
|
||||
auto& app = Application::GetInstance();
|
||||
app.StopListening();
|
||||
app.SetDeviceState(kDeviceStateIdle);
|
||||
app.SetAecMode(newMode);
|
||||
Settings settings("aec", true);
|
||||
settings.SetInt("mode", newMode);
|
||||
}
|
||||
void SwitchTFT() {
|
||||
Settings settings("display", true);
|
||||
bool currentIpsMode = settings.GetBool("ips_mode", false);
|
||||
settings.SetBool("ips_mode", !currentIpsMode);
|
||||
ESP_LOGI(TAG, "IPS mode toggled to %s", !currentIpsMode ? "enabled" : "disabled");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
auto& app = Application::GetInstance();
|
||||
app.Reboot();
|
||||
}
|
||||
|
||||
public:
|
||||
YunliaoS3()
|
||||
: DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
|
||||
boot_button_(BOOT_BUTTON_PIN),
|
||||
power_manager_(new PowerManager()) {
|
||||
power_manager_->Start5V();
|
||||
power_manager_->Initialize();
|
||||
InitializeI2c();
|
||||
@ -146,7 +195,7 @@ public:
|
||||
InitializeSpi();
|
||||
InitializeSt7789Display();
|
||||
power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) {
|
||||
if(power_save_timer_){
|
||||
if (power_save_timer_) {
|
||||
if (is_discharging) {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
} else {
|
||||
@ -154,46 +203,41 @@ public:
|
||||
}
|
||||
}
|
||||
});
|
||||
if(GetNetworkType() == NetworkType::WIFI){
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
power_manager_->Shutdown4G();
|
||||
}else{
|
||||
} else {
|
||||
power_manager_->Start4G();
|
||||
}
|
||||
GetBacklight()->RestoreBrightness();
|
||||
while(gpio_get_level(BOOT_BUTTON_PIN) == 0){
|
||||
while (gpio_get_level(BOOT_BUTTON_PIN) == 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
InitializeButtons();
|
||||
Settings settings("aec", false);
|
||||
auto& app = Application::GetInstance();
|
||||
app.SetAecMode(settings.GetInt("mode",kAecOnDeviceSide) == kAecOnDeviceSide ? kAecOnDeviceSide : kAecOff);
|
||||
InitializeTools();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
codec_i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK,
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN,
|
||||
AUDIO_CODEC_ES8388_ADDR,
|
||||
AUDIO_INPUT_REFERENCE
|
||||
);
|
||||
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_ES8388_ADDR, AUDIO_INPUT_REFERENCE);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override { return display_; }
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN,
|
||||
DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging,
|
||||
bool& discharging) override {
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
|
||||
@ -82,6 +82,9 @@ GpioLed::GpioLed(gpio_num_t gpio, int output_invert, ledc_timer_t timer_num, led
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_));
|
||||
|
||||
xTaskCreate(EventTask, "LedEvent", 2048, this,
|
||||
tskIDLE_PRIORITY + 2, &event_task_handle_);
|
||||
|
||||
ledc_initialized_ = true;
|
||||
}
|
||||
|
||||
@ -194,7 +197,9 @@ void GpioLed::OnFadeEnd() {
|
||||
bool IRAM_ATTR GpioLed::FadeCallback(const ledc_cb_param_t *param, void *user_arg) {
|
||||
if (param->event == LEDC_FADE_END_EVT) {
|
||||
auto led = static_cast<GpioLed*>(user_arg);
|
||||
led->OnFadeEnd();
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
xTaskNotifyFromISR(led->event_task_handle_, 0x01, eSetValueWithOverwrite,
|
||||
&xHigherPriorityTaskWoken);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -247,3 +252,12 @@ void GpioLed::OnStateChanged() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GpioLed::EventTask(void* arg) {
|
||||
GpioLed* led = static_cast<GpioLed*>(arg);
|
||||
|
||||
while (1) {
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
led->OnFadeEnd();
|
||||
}
|
||||
}
|
||||
@ -32,7 +32,9 @@ class GpioLed : public Led {
|
||||
int blink_interval_ms_ = 0;
|
||||
esp_timer_handle_t blink_timer_ = nullptr;
|
||||
bool fade_up_ = true;
|
||||
|
||||
TaskHandle_t event_task_handle_;
|
||||
|
||||
static void EventTask(void* arg);
|
||||
void StartBlinkTask(int times, int interval_ms);
|
||||
void OnBlinkTimer();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user