Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,11 @@ elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307)
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_XINGZHI_METAL_1_54_WIFI)
set(BOARD_TYPE "xingzhi-metal-1.54-wifi")
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_SEEED_STUDIO_SENSECAP_WATCHER)
set(BOARD_TYPE "sensecap-watcher")
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
Expand Down
3 changes: 3 additions & 0 deletions main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ choice BOARD_TYPE
config BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307
bool "无名科技星智1.54(ML307)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_XINGZHI_METAL_1_54_WIFI
bool "无名科技星智1.54 METAL(wifi)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER
bool "Seeed Studio SenseCAP Watcher"
depends on IDF_TARGET_ESP32S3
Expand Down
61 changes: 61 additions & 0 deletions main/boards/xingzhi-metal-1.54-wifi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 无名科技星智 1.54 METAL (wifi)

## 简介
无名科技星智 1.54 METAL (wifi) 是星智 1.54 开模版的升级款,配备 1.54 寸 LCD 屏幕与 CST816 触摸芯片。它用触摸交互替代物理按键,并将外壳升级为铝合金材质,同步优化了交互体验与产品质感、手感。

>### 按键操作
>- **开机**: 关机状态,长按电源键3秒后自动开机(旧版硬件长按电源键1s后自动开机)
>- **关机**: 开机状态,长按电源键5秒后自动关机(旧版硬件插入usb时不会自动关机)
>- **唤醒/打断**: 正常通话环境下,单击中间触摸按键
>- **重新配网**: 开机后,1秒钟内单击中间触摸按键,会自动重启并进入配网界面
>- **增加音量**: 开机状态下,单击右侧触摸按键,音量增加。长按右侧触摸按键2s,音量递增。
>- **减小音量**: 开机状态下,单击左侧触摸按键,音量减小。长按左侧触摸按键2s,音量递减。

>### 休眠操作
>- **浅睡眠**: 开机后,维持待命状态60s后,进入浅睡眠(屏幕亮度调整到1%)
>- **深睡眠**: 开机后,维持待命状态300s后,进入深睡眠(自动关机)
>- **唤醒**: 浅睡眠状态下,单击中间触摸按键,唤醒设备(屏幕亮度回调)

# 编译配置命令

**克隆工程**

```bash
git clone https://github.com/78/xiaozhi-esp32.git
```

**进入工程**

```bash
cd xiaozhi-esp32
```

**配置编译目标为 ESP32S3**

```bash
idf.py set-target esp32s3
```

**打开 menuconfig**

```bash
idf.py menuconfig
```

**选择板子**

```bash
- `Xiaozhi Assistant` → `Board Type` → 选择 `无名科技星智1.54 METAL(wifi)`
```

**编译**

```ba
idf.py build
```

**下载并打开串口终端**

```bash
idf.py build flash monitor
```
53 changes: 53 additions & 0 deletions main/boards/xingzhi-metal-1.54-wifi/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_

#include <driver/gpio.h>

// 音频
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000

#define AUDIO_INPUT_REFERENCE true
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15

#define AUDIO_CODEC_USE_PCA9557
#define AUDIO_CODEC_I2C_PA_EN GPIO_NUM_21
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR

// 按键
#define BOOT_BUTTON_GPIO GPIO_NUM_0

// 屏幕
#define DISPLAY_SPI_HOST SPI3_HOST
#define DISPLAY_SDA GPIO_NUM_10
#define DISPLAY_SCL GPIO_NUM_9
#define DISPLAY_DC GPIO_NUM_8
#define DISPLAY_CS GPIO_NUM_14
#define DISPLAY_RES GPIO_NUM_18
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY false
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define BACKLIGHT_INVERT false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false

// 电源管理
#define POWER_USB_IN GPIO_NUM_1
#define Power_Control GPIO_NUM_48 // 电源控制引脚
#define Power_Dec GPIO_NUM_47 // 电源键检测引脚
#define POWER_CBS_ADC_UNIT ADC_UNIT_1 // adc检测公共unit GPIO1
#define POWER_USBIN_ADC_CHANNEL ADC_CHANNEL_0 // 检测usb是否插入 GPIO1
#define POWER_BATTERY_ADC_CHANNEL ADC_CHANNEL_6 // 电池电量检测 GPIO7

#endif // _BOARD_CONFIG_H_
10 changes: 10 additions & 0 deletions main/boards/xingzhi-metal-1.54-wifi/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"target": "esp32s3",
"builds": [
{
"name": "xingzhi-metal-1.54-wifi",
"sdkconfig_append": [
]
}
]
}
214 changes: 214 additions & 0 deletions main/boards/xingzhi-metal-1.54-wifi/cst816x.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
#include "cst816x.h"
#include "board.h"
#include "application.h"
#include "display.h"
#include "assets/lang_config.h"
// #include "audio_codec.h"
#include "wifi_board.h"
#include <wifi_station.h>
#include "power_save_timer.h"
#include "codecs/es8311_audio_codec.h"
#include <algorithm> // 用于std::max/std::min

#define TAG "Cst816x"

const Cst816x::TouchThresholdConfig& Cst816x::getThresholdConfig(int x, int y) {
for (const auto& config : TOUCH_THRESHOLD_TABLE) {
if (config.x == x && config.y == y) {
return config;
}
}
return DEFAULT_THRESHOLD;
}

Cst816x::Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
uint8_t chip_id = ReadReg(0xA7);
ESP_LOGI(TAG, "Get CST816x chip ID: 0x%02X", chip_id);
read_buffer_ = new uint8_t[6];
}

Cst816x::~Cst816x() {
if (read_buffer_ != nullptr) {
delete[] read_buffer_;
read_buffer_ = nullptr;
}
}

int64_t Cst816x::getCurrentTimeUs() {
struct timeval tv;
gettimeofday(&tv, nullptr);
return (int64_t)tv.tv_sec * 1000000L + tv.tv_usec;
}

void Cst816x::UpdateTouchPoint() {
ReadRegs(0x02, read_buffer_, 6);
tp_.num = read_buffer_[0] & 0x0F;
tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
memset(read_buffer_, 0, 6);
}

void Cst816x::resetTouchCounters() {
is_touching_ = false;
touch_start_time_ = 0;
last_release_time_ = 0;
click_count_ = 0;
long_press_started_ = false;

is_volume_long_pressing_ = false;
volume_long_press_dir_ = 0;
last_volume_adjust_time_ = 0;
}

void Cst816x::touchpad_daemon(void* arg) {
Cst816x* cst816x = static_cast<Cst816x*>(arg);
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
auto display = board.GetDisplay();

while (1) {
cst816x->UpdateTouchPoint();
auto& tp = cst816x->GetTouchPoint();
int64_t current_time = cst816x->getCurrentTimeUs();

const auto& config = cst816x->getThresholdConfig(tp.x, tp.y);
if (tp.num > 0) {
ESP_LOGD(TAG, "Touch at (%d,%d) → SingleThresh:%lldms, DoubleWindow:%lldms, LongThresh:%lldms",
tp.x, tp.y,
config.single_click_thresh_us / 1000,
config.double_click_window_us / 1000,
config.long_press_thresh_us / 1000);
}

TouchEvent current_event;
bool event_detected = false;

if (tp.num > 0 && !cst816x->is_touching_) {
cst816x->is_touching_ = true;
cst816x->touch_start_time_ = current_time;
cst816x->long_press_started_ = false;
}
else if (tp.num > 0 && cst816x->is_touching_) {
if (!cst816x->long_press_started_ &&
(current_time - cst816x->touch_start_time_ >= config.long_press_thresh_us)) {
current_event = {TouchEventType::LONG_PRESS_START, tp.x, tp.y};
event_detected = true;
cst816x->long_press_started_ = true;
}
}
else if (tp.num == 0 && cst816x->is_touching_) {
cst816x->is_touching_ = false;
int64_t touch_duration = current_time - cst816x->touch_start_time_;
cst816x->last_release_time_ = current_time;
if (cst816x->long_press_started_) {
current_event = {TouchEventType::LONG_PRESS_END, tp.x, tp.y};
event_detected = true;
}
else if (touch_duration <= config.single_click_thresh_us) {
cst816x->click_count_++;
}
}
else if (tp.num == 0 && !cst816x->is_touching_) {
if (cst816x->click_count_ > 0 &&
(current_time - cst816x->last_release_time_ >= config.double_click_window_us)) {
if (cst816x->click_count_ == 2) {
current_event = {TouchEventType::DOUBLE_CLICK, tp.x, tp.y};
event_detected = true;
}
else if (cst816x->click_count_ == 1) {
current_event = {TouchEventType::SINGLE_CLICK, tp.x, tp.y};
event_detected = true;
}
cst816x->click_count_ = 0;
}
}

if (event_detected) {
if (current_event.y == 600 && (current_event.x == 20 || current_event.x == 40 || current_event.x == 60)) {
switch (current_event.type) {
case TouchEventType::SINGLE_CLICK:
if (current_event.x == 40) {
board.SetPowerSaveMode(false);
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
auto& wifi_board = static_cast<WifiBoard&>(Board::GetInstance());
wifi_board.ResetWifiConfiguration();
}
app.ToggleChatState();
} else if (current_event.x == 20) { // 20,600 单击:音量+
int current_vol = codec->output_volume();
int new_vol = current_vol + 10;
new_vol = (new_vol >= ES8311_VOL_MAX) ? ES8311_VOL_MAX : new_vol;
ESP_LOGI(TAG, "current_vol, new_vol(%d, %d)", current_vol, new_vol);
codec->EnableOutput(true);
codec->SetOutputVolume(new_vol);
display->ShowNotification(Lang::Strings::VOLUME + std::to_string(new_vol));
} else if (current_event.x == 60) { // 60,600 单击:音量-
int current_vol = codec->output_volume();
int new_vol = current_vol - 10;
new_vol = (new_vol <= ES8311_VOL_MIN) ? ES8311_VOL_MIN : new_vol;
ESP_LOGI(TAG, "current_vol, new_vol(%d, %d)", current_vol, new_vol);
codec->EnableOutput(true);
codec->SetOutputVolume(new_vol);
display->ShowNotification(Lang::Strings::VOLUME + std::to_string(new_vol));
}
break;

case TouchEventType::DOUBLE_CLICK:
ESP_LOGI(TAG, "Double click detected at (%d, %d)", current_event.x, current_event.y);
break;

case TouchEventType::LONG_PRESS_START:
ESP_LOGI(TAG, "Long press started at (%d, %d) → Start volume adjust", current_event.x, current_event.y);
if (current_event.x == 20) {
cst816x->is_volume_long_pressing_ = true;
cst816x->volume_long_press_dir_ = 1;
cst816x->last_volume_adjust_time_ = current_time;
} else if (current_event.x == 60) {
cst816x->is_volume_long_pressing_ = true;
cst816x->volume_long_press_dir_ = -1;
cst816x->last_volume_adjust_time_ = current_time;
}
break;

case TouchEventType::LONG_PRESS_END:
ESP_LOGI(TAG, "Long press ended at (%d, %d) → Stop volume adjust", current_event.x, current_event.y);
if (current_event.x == 20 || current_event.x == 60) {
cst816x->is_volume_long_pressing_ = false;
cst816x->volume_long_press_dir_ = 0;
cst816x->last_volume_adjust_time_ = 0;
}
break;
}
}
}

if (cst816x->is_volume_long_pressing_) {
int64_t now = cst816x->getCurrentTimeUs();
if (now - cst816x->last_volume_adjust_time_ >= cst816x->VOL_ADJ_INTERVAL_US) {
int current_vol = codec->output_volume();
int new_vol = current_vol + (cst816x->volume_long_press_dir_ * cst816x->VOL_ADJ_STEP);

new_vol = std::max(ES8311_VOL_MIN, std::min(ES8311_VOL_MAX, new_vol));

if (new_vol != current_vol) {
codec->EnableOutput(true);
codec->SetOutputVolume(new_vol);
display->ShowNotification(Lang::Strings::VOLUME + std::to_string(new_vol));
cst816x->last_volume_adjust_time_ = now;
} else {
cst816x->is_volume_long_pressing_ = false;
cst816x->volume_long_press_dir_ = 0;
ESP_LOGI(TAG, "Volume reached limit (%d), stop adjusting", new_vol);
}
}
}

vTaskDelay(pdMS_TO_TICKS(40));
}
}

void Cst816x::InitCst816d() {
ESP_LOGI(TAG, "Init CST816x touch driver");
xTaskCreate(touchpad_daemon, "touch_daemon", 2048, this, 1, NULL);
}
Loading