Skip to content

Commit 1f602fa

Browse files
authored
Add support for the Xorigin AiPi Lite (#1330)
* Add support for the Xorigin AiPi Lite * aipi-lite: correction to README_en.md * aipi-lite: use 20MHz display clock Highger speeds might work sometimes but best not to push it and use a sane speed. * aipi-lite: change English name and remove Chinese TTS
1 parent a9413e2 commit 1f602fa

File tree

9 files changed

+605
-0
lines changed

9 files changed

+605
-0
lines changed

main/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,11 @@ elseif(CONFIG_BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S)
526526
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
527527
set(BUILTIN_ICON_FONT font_awesome_30_4)
528528
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
529+
elseif(CONFIG_BOARD_TYPE_AIPI_LITE)
530+
set(BOARD_TYPE "aipi-lite")
531+
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
532+
set(BUILTIN_ICON_FONT font_awesome_14_1)
533+
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
529534
endif()
530535

531536
file(GLOB BOARD_SOURCES

main/Kconfig.projbuild

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ choice BOARD_TYPE
420420
config BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S
421421
bool "Wireless-Tag WTP4C5MP07S"
422422
depends on IDF_TARGET_ESP32P4
423+
config BOARD_TYPE_AIPI_LITE
424+
bool "AIPI-Lite"
425+
depends on IDF_TARGET_ESP32S3
423426
endchoice
424427

425428
choice

main/boards/aipi-lite/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# 编译命令
2+
3+
## 一键编译
4+
5+
```bash
6+
python scripts/release.py aipi-lite
7+
```
8+
9+
## 手动配置编译
10+
11+
```bash
12+
idf.py set-target esp32s3
13+
```
14+
15+
**配置**
16+
17+
```bash
18+
idf.py menuconfig
19+
```
20+
21+
选择板子
22+
23+
```
24+
Xiaozhi Assistant -> Board Type -> AIPI-Lite
25+
```
26+
27+
## 编译烧入
28+
29+
```bash
30+
idf.py -DBOARD_NAME=aipi-lite build flash
31+
```
32+
33+
注意: 如果当前设备出货之前是AiPi-Lite 固件(非小智版本),请特别小心处理闪存固件分区地址,以避免错误擦除 AiPi-Lite 的自身设备信息(EUI 等),否则设备即使恢复成Xorigin固件也无法正确连接到 服务器!所以在刷写固件之前,请务必记录设备的相关必要信息,以确保有恢复的方法!
34+
35+
您可以使用以下命令备份生产信息
36+
37+
```bash
38+
# firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
39+
esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin
40+
41+
```

main/boards/aipi-lite/README_en.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Build Instructions
2+
3+
## One-click Build
4+
5+
```bash
6+
python scripts/release.py aipi-lite -c config_en.json
7+
```
8+
9+
## Manual Configuration and Build
10+
11+
```bash
12+
idf.py set-target esp32s3
13+
```
14+
15+
**Configuration**
16+
17+
```bash
18+
idf.py menuconfig
19+
```
20+
21+
Select the board:
22+
23+
```
24+
Xiaozhi Assistant -> Board Type -> AiPi-Lite
25+
```
26+
27+
## Build and Flash
28+
29+
```bash
30+
idf.py -DBOARD_NAME=aipi-lite build flash
31+
```
32+
33+
Note: If your device was previously shipped with the AiPi-Lite firmware (not the Xiaozhi version), please be very careful with the flash partition addresses to avoid accidentally erasing the device information (such as EUI) of the AiPi-Lite. Otherwise, even if you restore the AiPi-Lite firmware, the device may not be able to connect to the Xorigin server correctly! Therefore, before flashing the firmware, be sure to record the necessary device information to ensure you have a way to recover it!
34+
35+
You can use the following command to back up the factory information:
36+
37+
```bash
38+
# Firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
39+
esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin
40+
```

main/boards/aipi-lite/aipi-lite.cc

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
#include <driver/gpio.h>
2+
#include <driver/i2c_master.h>
3+
#include <driver/rtc_io.h>
4+
#include <driver/spi_common.h>
5+
#include <esp_lcd_panel_io.h>
6+
#include <esp_lcd_panel_ops.h>
7+
#include <esp_lcd_panel_vendor.h>
8+
#include <esp_log.h>
9+
#include <esp_sleep.h>
10+
#include <wifi_station.h>
11+
12+
#include "application.h"
13+
#include "button.h"
14+
#include "codecs/es8311_audio_codec.h"
15+
#include "config.h"
16+
#include "display/lcd_display.h"
17+
#include "lamp_controller.h"
18+
#include "led/single_led.h"
19+
#include "mcp_server.h"
20+
#include "power_manager.h"
21+
#include "power_save_timer.h"
22+
#include "system_reset.h"
23+
#include "wifi_board.h"
24+
25+
#define TAG "AIPI-Lite"
26+
27+
class AIPILite : public WifiBoard {
28+
private:
29+
i2c_master_bus_handle_t i2c_bus_;
30+
Button boot_button_;
31+
Button power_button_;
32+
LcdDisplay* display_;
33+
PowerManager* power_manager_;
34+
PowerSaveTimer* power_save_timer_;
35+
esp_lcd_panel_handle_t panel_ = nullptr;
36+
37+
void InitializePowerManager() {
38+
power_manager_ = new PowerManager(POWER_CHARGE_DETECT_PIN);
39+
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
40+
if (is_charging) {
41+
power_save_timer_->SetEnabled(false);
42+
} else {
43+
power_save_timer_->SetEnabled(true);
44+
}
45+
});
46+
}
47+
48+
void InitializePowerSaveTimer() {
49+
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
50+
power_save_timer_->OnEnterSleepMode([this]() {
51+
GetDisplay()->SetPowerSaveMode(true);
52+
GetBacklight()->SetBrightness(1);
53+
});
54+
power_save_timer_->OnExitSleepMode([this]() {
55+
GetDisplay()->SetPowerSaveMode(false);
56+
GetBacklight()->RestoreBrightness();
57+
});
58+
power_save_timer_->OnShutdownRequest([this]() {
59+
ESP_LOGI(TAG, "Shutting down");
60+
esp_lcd_panel_disp_on_off(panel_, false); // 关闭显示
61+
rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
62+
rtc_gpio_hold_dis(POWER_CONTROL_PIN);
63+
esp_deep_sleep_start();
64+
});
65+
power_save_timer_->SetEnabled(true);
66+
}
67+
68+
void InitializeI2c() {
69+
// Initialize I2C peripheral
70+
i2c_master_bus_config_t i2c_bus_cfg = {
71+
.i2c_port = (i2c_port_t)1,
72+
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
73+
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
74+
.clk_source = I2C_CLK_SRC_DEFAULT,
75+
.glitch_ignore_cnt = 7,
76+
.intr_priority = 0,
77+
.trans_queue_depth = 0,
78+
.flags =
79+
{
80+
.enable_internal_pullup = 1,
81+
},
82+
};
83+
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
84+
}
85+
86+
void InitializeSpi() {
87+
spi_bus_config_t buscfg = {};
88+
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
89+
buscfg.miso_io_num = GPIO_NUM_NC;
90+
buscfg.sclk_io_num = DISPLAY_SPI_SCLK_PIN;
91+
buscfg.quadwp_io_num = GPIO_NUM_NC;
92+
buscfg.quadhd_io_num = GPIO_NUM_NC;
93+
buscfg.max_transfer_sz =
94+
DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
95+
ESP_ERROR_CHECK(
96+
spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
97+
}
98+
99+
void InitializeLcdDisplay() {
100+
esp_lcd_panel_io_handle_t panel_io = nullptr;
101+
// 液晶屏控制IO初始化
102+
ESP_LOGD(TAG, "Install panel IO");
103+
esp_lcd_panel_io_spi_config_t io_config = {};
104+
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
105+
io_config.dc_gpio_num = DISPLAY_SPI_DC_PIN;
106+
io_config.spi_mode = DISPLAY_SPI_MODE;
107+
io_config.pclk_hz = 40 * 1000 * 1000;
108+
io_config.trans_queue_depth = 10;
109+
io_config.lcd_cmd_bits = 8;
110+
io_config.lcd_param_bits = 8;
111+
ESP_ERROR_CHECK(
112+
esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
113+
114+
// 初始化液晶屏驱动芯片
115+
ESP_LOGD(TAG, "Install LCD driver");
116+
esp_lcd_panel_dev_config_t panel_config = {};
117+
panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN;
118+
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
119+
panel_config.bits_per_pixel = 16;
120+
ESP_ERROR_CHECK(
121+
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel_));
122+
123+
esp_lcd_panel_reset(panel_);
124+
125+
esp_lcd_panel_init(panel_);
126+
esp_lcd_panel_invert_color(panel_, DISPLAY_INVERT_COLOR);
127+
esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY);
128+
esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
129+
display_ = new SpiLcdDisplay(panel_io, panel_, DISPLAY_WIDTH,
130+
DISPLAY_HEIGHT, DISPLAY_OFFSET_X,
131+
DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
132+
DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
133+
}
134+
135+
void InitializeButtons() {
136+
boot_button_.OnClick([this]() {
137+
power_save_timer_->WakeUp();
138+
auto& app = Application::GetInstance();
139+
if (app.GetDeviceState() == kDeviceStateStarting &&
140+
!WifiStation::GetInstance().IsConnected()) {
141+
ResetWifiConfiguration();
142+
}
143+
app.ToggleChatState();
144+
});
145+
146+
// 设置开机按钮的长按事件(直接进入配网模式)
147+
boot_button_.OnLongPress([this]() {
148+
// 唤醒电源保存定时器
149+
power_save_timer_->WakeUp();
150+
// 获取应用程序实例
151+
auto& app = Application::GetInstance();
152+
153+
// 进入配网模式
154+
app.SetDeviceState(kDeviceStateWifiConfiguring);
155+
156+
// 重置WiFi配置以确保进入配网模式
157+
ResetWifiConfiguration();
158+
});
159+
160+
power_button_.OnClick([this]() { power_save_timer_->WakeUp(); });
161+
power_button_.OnLongPress([this]() {
162+
auto& app = Application::GetInstance();
163+
if (app.GetDeviceState() != kDeviceStateStarting &&
164+
!(power_manager_->IsCharging() &&
165+
power_manager_->GetBatteryLevel() < 100)) {
166+
ESP_LOGI(TAG, "Power button long pressed, shutting down");
167+
esp_lcd_panel_disp_on_off(panel_, false); // 关闭显示
168+
rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
169+
rtc_gpio_hold_dis(POWER_CONTROL_PIN);
170+
esp_deep_sleep_start();
171+
}
172+
});
173+
}
174+
175+
void InitializePowerCtl() {
176+
ESP_LOGI(TAG, "Initialize Power Control GPIO");
177+
rtc_gpio_init(POWER_CONTROL_PIN);
178+
rtc_gpio_set_direction(POWER_CONTROL_PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
179+
rtc_gpio_set_level(POWER_CONTROL_PIN, 1);
180+
}
181+
182+
// 物联网初始化,添加对 AI 可见设备
183+
void InitializeTools() {}
184+
185+
public:
186+
AIPILite()
187+
: boot_button_(BOOT_BUTTON_GPIO), power_button_(POWER_BUTTON_GPIO) {
188+
InitializePowerCtl();
189+
InitializePowerManager();
190+
InitializePowerSaveTimer();
191+
InitializeI2c();
192+
InitializeSpi();
193+
InitializeLcdDisplay();
194+
InitializeButtons();
195+
InitializeTools();
196+
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
197+
GetBacklight()->RestoreBrightness();
198+
}
199+
}
200+
201+
virtual Led* GetLed() override {
202+
static SingleLed led(BUILTIN_LED_GPIO);
203+
return &led;
204+
}
205+
206+
virtual AudioCodec* GetAudioCodec() override {
207+
static Es8311AudioCodec audio_codec(
208+
i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE,
209+
AUDIO_OUTPUT_SAMPLE_RATE, AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK,
210+
AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
211+
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, false);
212+
return &audio_codec;
213+
}
214+
215+
virtual Display* GetDisplay() override { return display_; }
216+
217+
virtual Backlight* GetBacklight() override {
218+
if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
219+
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN,
220+
DISPLAY_BACKLIGHT_OUTPUT_INVERT);
221+
return &backlight;
222+
}
223+
return nullptr;
224+
}
225+
226+
virtual bool GetBatteryLevel(int& level, bool& charging,
227+
bool& discharging) override {
228+
static bool last_discharging = false;
229+
charging = power_manager_->IsCharging();
230+
discharging = power_manager_->IsDischarging();
231+
if (discharging != last_discharging) {
232+
power_save_timer_->SetEnabled(discharging);
233+
last_discharging = discharging;
234+
}
235+
level = power_manager_->GetBatteryLevel();
236+
return true;
237+
}
238+
239+
virtual void SetPowerSaveMode(bool enabled) override {
240+
if (!enabled) {
241+
power_save_timer_->WakeUp();
242+
}
243+
WifiBoard::SetPowerSaveMode(enabled);
244+
}
245+
};
246+
247+
DECLARE_BOARD(AIPILite);

0 commit comments

Comments
 (0)