Skip to content

Commit 8b032f4

Browse files
aredridel78
andauthored
feat(board): add LC-S3-WIFI 1.54-inch TFT (ESP32-S3) (#2293)
* feat(board): add LC-S3-WIFI 1.54-inch TFT (ESP32-S3) Port of the lc-s3-wifi-1.54tft bring-up work to current main (the original WIP branch predated the v2.x board architecture). Follows genjutech-s3-1.54tft as the closest in-tree reference: WifiBoard, ES8311 codec, ST7789 240x240 SPI display, PowerManager battery monitor, PowerSaveTimer. Audio runs 24 kHz in/out so playback matches the server stream; the AudioService down-samples the mic to 16 kHz for the AFE wake-word path. Battery level comes from ADC1 CH8 with charging-detect input on GPIO 38. PowerSaveTimer(-1, 60, -1) dims the display at idle without deep-sleep shutdown, and charging state enables/disables the timer. * fix(lc-s3-wifi): correct provisioning and volume wakeup --------- Co-authored-by: Terrence <terrence@tenclass.com>
1 parent f05adce commit 8b032f4

7 files changed

Lines changed: 502 additions & 0 deletions

File tree

‎main/CMakeLists.txt‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,11 @@ elseif(CONFIG_BOARD_TYPE_LCDWIKI_ES3C35P)
877877
set(BUILTIN_TEXT_FONT font_noto_sans_basic_20_4)
878878
set(BUILTIN_ICON_FONT font_material_symbols_20_4)
879879
set(DEFAULT_EMOJI_COLLECTION noto-color-emoji_128)
880+
elseif(CONFIG_BOARD_TYPE_LC_S3_WIFI_1_54TFT)
881+
set(BOARD_DIR "lc-s3-wifi-1.54tft")
882+
set(BUILTIN_TEXT_FONT font_noto_sans_basic_20_4)
883+
set(BUILTIN_ICON_FONT font_material_symbols_20_4)
884+
set(DEFAULT_EMOJI_COLLECTION noto-color-emoji_64)
880885
endif()
881886

882887
if(NOT BOARD_DIR AND NOT CMAKE_BUILD_EARLY_EXPANSION)

‎main/Kconfig.projbuild‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ choice BOARD_TYPE
284284
config BOARD_TYPE_LABPLUS_MPYTHON_V3
285285
bool "Labplus mPython V3"
286286
depends on IDF_TARGET_ESP32S3
287+
config BOARD_TYPE_LC_S3_WIFI_1_54TFT
288+
bool "LC-S3-WIFI 1.54-inch TFT"
289+
depends on IDF_TARGET_ESP32S3
287290
config BOARD_TYPE_LCDWIKI_ES3C35P
288291
bool "LCDWiki ES3C35P 3.5-inch ESP32-S3"
289292
depends on IDF_TARGET_ESP32S3
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# LC-S3-WIFI-1.54TFT
2+
3+
An ESP32-S3 voice-assistant board with a 1.54-inch 240x240 ST7789 display and an ES8311 audio codec.
4+
5+
## Hardware
6+
7+
- SoC: ESP32-S3
8+
- Display: 1.54-inch 240x240 SPI ST7789 (SPI mode 0, 40 MHz)
9+
- Audio codec: ES8311 over I2S + I2C; speaker PA enable on GPIO 8
10+
- Buttons: BOOT (GPIO 0), volume up (GPIO 40), volume down (GPIO 39)
11+
- LED: GPIO 46
12+
- Battery: ADC1 channel 8 with charging-detect input on GPIO 38
13+
14+
## Build
15+
16+
```sh
17+
python3 scripts/build.py lc-s3-wifi-1.54tft --name lc-s3-wifi-1.54tft
18+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#ifndef _BOARD_CONFIG_H_
2+
#define _BOARD_CONFIG_H_
3+
4+
#include <driver/gpio.h>
5+
#include <driver/uart.h>
6+
7+
// 音频配置
8+
#define AUDIO_INPUT_SAMPLE_RATE 24000
9+
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
10+
11+
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5
12+
#define AUDIO_I2S_GPIO_WS GPIO_NUM_2
13+
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_4
14+
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_3
15+
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_1
16+
17+
#define AUDIO_CODEC_PA_PIN GPIO_NUM_8
18+
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7
19+
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6
20+
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
21+
22+
// 按钮配置
23+
#define BUILTIN_LED_GPIO GPIO_NUM_46
24+
#define BOOT_BUTTON_GPIO GPIO_NUM_0
25+
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
26+
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
27+
28+
// 显示屏配置
29+
#define DISPLAY_WIDTH 240
30+
#define DISPLAY_HEIGHT 240
31+
#define DISPLAY_MIRROR_X false
32+
#define DISPLAY_MIRROR_Y false
33+
#define DISPLAY_SWAP_XY false
34+
35+
#define DISPLAY_OFFSET_X 0
36+
#define DISPLAY_OFFSET_Y 0
37+
38+
#define DISPLAY_DC_GPIO GPIO_NUM_10
39+
#define DISPLAY_CS_GPIO GPIO_NUM_13
40+
#define DISPLAY_CLK_GPIO GPIO_NUM_14
41+
#define DISPLAY_MOSI_GPIO GPIO_NUM_15
42+
#define DISPLAY_RST_GPIO GPIO_NUM_16
43+
#define DISPLAY_SPI_MODE 0
44+
#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
45+
46+
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
47+
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
48+
49+
// 扩展
50+
#define RELAY_LED GPIO_NUM_17
51+
52+
#endif // _BOARD_CONFIG_H_
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"type": "lc-s3-wifi-1.54tft",
3+
"target": "esp32s3",
4+
"builds": [
5+
{
6+
"name": "lc-s3-wifi-1.54tft",
7+
"sdkconfig_append": []
8+
}
9+
]
10+
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
#include "application.h"
2+
#include "assets/lang_config.h"
3+
#include "button.h"
4+
#include "codecs/es8311_audio_codec.h"
5+
#include "config.h"
6+
#include "display/lcd_display.h"
7+
#include "led/single_led.h"
8+
#include "system_reset.h"
9+
#include "wifi_board.h"
10+
11+
#include <driver/i2c_master.h>
12+
#include <driver/spi_common.h>
13+
#include <esp_lcd_panel_ops.h>
14+
#include <esp_lcd_panel_vendor.h>
15+
#include <esp_log.h>
16+
#include <esp_sleep.h>
17+
#include <wifi_manager.h>
18+
#include "power_manager.h"
19+
#include "power_save_timer.h"
20+
21+
#define TAG "lc-s3-wifi-1.54tft"
22+
23+
class lc_s3_wifi_1_54tft : public WifiBoard {
24+
private:
25+
i2c_master_bus_handle_t i2c_bus_;
26+
Button volume_up_button_;
27+
Button volume_down_button_;
28+
Button boot_button_;
29+
PowerManager* power_manager_;
30+
PowerSaveTimer* power_save_timer_;
31+
Display* display_;
32+
esp_lcd_panel_io_handle_t panel_io = nullptr;
33+
esp_lcd_panel_handle_t panel = nullptr;
34+
35+
// 电源管理初始化
36+
void InitializePowerManager() {
37+
power_manager_ = new PowerManager(GPIO_NUM_38);
38+
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
39+
if (is_charging) {
40+
power_save_timer_->SetEnabled(false);
41+
} else {
42+
power_save_timer_->SetEnabled(true);
43+
}
44+
});
45+
}
46+
47+
void InitializePowerSaveTimer() {
48+
power_save_timer_ = new PowerSaveTimer(-1, 60, -1);
49+
power_save_timer_->OnEnterSleepMode([this]() {
50+
GetDisplay()->SetPowerSaveMode(true);
51+
GetBacklight()->SetBrightness(1);
52+
});
53+
power_save_timer_->OnExitSleepMode([this]() {
54+
GetDisplay()->SetPowerSaveMode(false);
55+
GetBacklight()->RestoreBrightness();
56+
});
57+
power_save_timer_->OnShutdownRequest([this]() {
58+
ESP_LOGI(TAG, "Shutting down");
59+
esp_lcd_panel_disp_on_off(panel, false); // 关闭显示
60+
esp_deep_sleep_start();
61+
});
62+
power_save_timer_->SetEnabled(true);
63+
}
64+
65+
// i2c初始化,音频ES8311
66+
void InitializeI2c() {
67+
// Initialize I2C peripheral
68+
i2c_master_bus_config_t i2c_bus_cfg = {
69+
.i2c_port = I2C_NUM_0,
70+
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
71+
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
72+
.clk_source = I2C_CLK_SRC_DEFAULT,
73+
.glitch_ignore_cnt = 7,
74+
.intr_priority = 0,
75+
.trans_queue_depth = 0,
76+
.flags =
77+
{
78+
.enable_internal_pullup = 1,
79+
},
80+
};
81+
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
82+
}
83+
84+
// SPI初始化,显示屏
85+
void InitializeSpi() {
86+
spi_bus_config_t buscfg = {};
87+
buscfg.mosi_io_num = DISPLAY_MOSI_GPIO;
88+
buscfg.miso_io_num = GPIO_NUM_NC;
89+
buscfg.sclk_io_num = DISPLAY_CLK_GPIO;
90+
buscfg.quadwp_io_num = GPIO_NUM_NC;
91+
buscfg.quadhd_io_num = GPIO_NUM_NC;
92+
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
93+
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
94+
}
95+
96+
// 按钮初始化
97+
void InitializeButtons() {
98+
boot_button_.OnClick([this]() {
99+
auto& app = Application::GetInstance();
100+
if (app.GetDeviceState() == kDeviceStateStarting &&
101+
!WifiManager::GetInstance().IsConnected()) {
102+
EnterWifiConfigMode();
103+
return;
104+
}
105+
app.ToggleChatState();
106+
});
107+
108+
boot_button_.OnLongPress(
109+
[this]() { Application::GetInstance().Schedule([this]() { EnterWifiConfigMode(); }); });
110+
111+
volume_up_button_.OnClick([this]() {
112+
power_save_timer_->WakeUp();
113+
auto codec = GetAudioCodec();
114+
auto volume = codec->output_volume() + 10;
115+
if (volume > 100) {
116+
volume = 100;
117+
}
118+
codec->SetOutputVolume(volume);
119+
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
120+
});
121+
122+
volume_up_button_.OnLongPress([this]() {
123+
power_save_timer_->WakeUp();
124+
GetAudioCodec()->SetOutputVolume(100);
125+
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
126+
});
127+
128+
volume_down_button_.OnClick([this]() {
129+
power_save_timer_->WakeUp();
130+
auto codec = GetAudioCodec();
131+
auto volume = codec->output_volume() - 10;
132+
if (volume < 0) {
133+
volume = 0;
134+
}
135+
codec->SetOutputVolume(volume);
136+
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
137+
});
138+
139+
volume_down_button_.OnLongPress([this]() {
140+
power_save_timer_->WakeUp();
141+
GetAudioCodec()->SetOutputVolume(0);
142+
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
143+
});
144+
}
145+
146+
// 显示屏初始化
147+
void InitializeDisplay() {
148+
// 液晶屏控制IO初始化
149+
ESP_LOGD(TAG, "Install panel IO");
150+
esp_lcd_panel_io_spi_config_t io_config = {};
151+
io_config.cs_gpio_num = DISPLAY_CS_GPIO;
152+
io_config.dc_gpio_num = DISPLAY_DC_GPIO;
153+
io_config.spi_mode = DISPLAY_SPI_MODE;
154+
io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
155+
io_config.trans_queue_depth = 10;
156+
io_config.lcd_cmd_bits = 8;
157+
io_config.lcd_param_bits = 8;
158+
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
159+
160+
// 初始化液晶屏驱动芯片
161+
ESP_LOGD(TAG, "Install LCD driver");
162+
163+
esp_lcd_panel_dev_config_t panel_config = {};
164+
panel_config.reset_gpio_num = DISPLAY_RST_GPIO;
165+
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
166+
panel_config.bits_per_pixel = 16;
167+
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
168+
169+
esp_lcd_panel_reset(panel);
170+
esp_lcd_panel_init(panel);
171+
esp_lcd_panel_invert_color(panel, true);
172+
esp_lcd_panel_disp_on_off(panel, true);
173+
display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT,
174+
DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
175+
DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
176+
}
177+
178+
public:
179+
// 构造函数
180+
lc_s3_wifi_1_54tft()
181+
: volume_up_button_(VOLUME_UP_BUTTON_GPIO),
182+
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
183+
boot_button_(BOOT_BUTTON_GPIO) {
184+
InitializePowerManager();
185+
InitializePowerSaveTimer();
186+
InitializeI2c();
187+
InitializeSpi();
188+
InitializeDisplay();
189+
InitializeButtons();
190+
GetBacklight()->RestoreBrightness();
191+
}
192+
193+
// 获取音频编码器
194+
virtual AudioCodec* GetAudioCodec() override {
195+
static Es8311AudioCodec audio_codec(
196+
i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
197+
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT,
198+
AUDIO_I2S_GPIO_DIN, AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR);
199+
return &audio_codec;
200+
}
201+
202+
virtual Display* GetDisplay() override { return display_; }
203+
204+
virtual Backlight* GetBacklight() override {
205+
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
206+
return &backlight;
207+
}
208+
209+
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
210+
static bool last_discharging = false;
211+
charging = power_manager_->IsCharging();
212+
discharging = power_manager_->IsDischarging();
213+
if (discharging != last_discharging) {
214+
power_save_timer_->SetEnabled(discharging);
215+
last_discharging = discharging;
216+
}
217+
level = power_manager_->GetBatteryLevel();
218+
return true;
219+
}
220+
221+
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
222+
if (level != PowerSaveLevel::LOW_POWER) {
223+
power_save_timer_->WakeUp();
224+
}
225+
WifiBoard::SetPowerSaveLevel(level);
226+
}
227+
228+
virtual Led* GetLed() override {
229+
static SingleLed led(BUILTIN_LED_GPIO);
230+
return &led;
231+
}
232+
};
233+
234+
DECLARE_BOARD(lc_s3_wifi_1_54tft);

0 commit comments

Comments
 (0)