Compare commits

..

5 Commits

Author SHA1 Message Date
rsgltzyd 92bead72b9 feat: 更新 display.c,添加反转输出标志并移除不必要的日志和 GPIO 设置 2026-07-08 22:46:44 +08:00
rsgltzyd 5ef6c20862 feat: 更新ui_Screen1.c、ui_Screen2.c 和 ui_Screen3.c ,引入 board_config.h 以获取屏幕分辨率 2026-07-08 22:38:53 +08:00
rsgltzyd b154c3fa20 feat: 增强运动手势的校准与检测逻辑 2026-07-08 22:18:42 +08:00
rsgltzyd efb3c8f304 docs: 更新 README.md,添加触摸控制芯片与 IMU 传感器的 I2C 总线共享信息
- 修改触摸控制芯片 CST816 和六轴惯性测量单元 QMI8658A 的描述,说明它们共用 I2C 总线(IO47/IO48)
- 更新触摸屏和 IMU 传感器的引脚信息,确保文档准确反映硬件配置
- 添加 I2C 设备与固件配置部分,提供设备地址和说明
2026-07-05 21:29:47 +08:00
rsgltzyd ff7d82286a feat: 实现摇一摇导航功能 2026-07-05 21:28:54 +08:00
21 changed files with 882 additions and 30 deletions
+21 -10
View File
@@ -13,12 +13,12 @@
| | 叠封 PSRAM | **8MB** | 用于运行代码,性能优异 | | | 叠封 PSRAM | **8MB** | 用于运行代码,性能优异 |
| | 外接 Flash | **16MB** (W25Q128) | 存储程序和数据 | | | 外接 Flash | **16MB** (W25Q128) | 存储程序和数据 |
| **显示与触摸** | LCD 屏幕 | 1.9英寸,170×320分辨率,262K彩色 | 驱动IC为ST7789V2 | | **显示与触摸** | LCD 屏幕 | 1.9英寸,170×320分辨率,262K彩色 | 驱动IC为ST7789V2 |
| | 触摸控制芯片 | **CST816** (电容式) | **触控版特有**通过I2C接口 | | | 触摸控制芯片 | **CST816** (电容式) | **触控版特有**I2C 与 IMU 共用 IO47/IO48 |
| **无线通信** | Wi-Fi | 2.4GHz, 802.11 b/g/n | | | **无线通信** | Wi-Fi | 2.4GHz, 802.11 b/g/n | |
| | 蓝牙 | Bluetooth 5 (BLE) | | | | 蓝牙 | Bluetooth 5 (BLE) | |
| | 板载天线 | 贴片陶瓷天线 | | | | 板载天线 | 贴片陶瓷天线 | |
| | 外部天线接口 | **IPEX-1 连接器** | **触控版特有**,需修改焊盘启用 | | | 外部天线接口 | **IPEX-1 连接器** | **触控版特有**,需修改焊盘启用 |
| **传感器** | 六轴惯性测量单元 | **QMI8658** | 包含3轴加速度计和3轴陀螺仪 | | **传感器** | 六轴惯性测量单元 | **QMI8658A** | 与 CST816 共用 I2C 总线(IO47/IO48 |
| **存储扩展** | Micro SD 卡槽 | 支持Micro SD卡 | 位于PCB背面 | | **存储扩展** | Micro SD 卡槽 | 支持Micro SD卡 | 位于PCB背面 |
| **电源管理** | 电池接口 | MX1.25 连接器 | 支持3.7V锂电池**充放电** | | **电源管理** | 电池接口 | MX1.25 连接器 | 支持3.7V锂电池**充放电** |
| | USB供电 | USB Type-C 接口 | 同时用于下载调试 | | | USB供电 | USB Type-C 接口 | 同时用于下载调试 |
@@ -37,18 +37,18 @@
| | | IO12 | LCD\_CS(片选) | | | | IO12 | LCD\_CS(片选) |
| | | IO13 | LCD\_DIN(数据输入) | | | | IO13 | LCD\_DIN(数据输入) |
| | | IO14 | LCD\_BL(背光控制,PWM | | | | IO14 | LCD\_BL(背光控制,PWM |
| **触摸屏** | I2C | IO47 | TP\_SDA(数据线) | | **触摸屏 / I2C 总线** | I2C | IO47 | TP\_SDA / IMU\_SDA(与 IMU 共用) |
| | | IO48 | TP\_SCL(时钟线) | | (CST816 + QMI8658) | | IO48 | TP\_SCL / IMU\_SCL(与 IMU 共用) |
| | GPIO | IO17 | TP\_RESET(复位) | | | GPIO | IO17 | TP\_RESET(复位) |
| | | IO21 | TP\_INT(中断) | | | | IO21 | TP\_INT(中断) |
| **SD 卡** | SPI | IO5 | SD\_CS(片选) | | **SD 卡** | SPI | IO5 | SD\_CS(片选) |
| | | IO39 | SD\_CLK(时钟) | | | | IO39 | SD\_CLK(时钟) |
| | | IO40 | SD\_MISO(主入从出) | | | | IO40 | SD\_MISO(主入从出) |
| | | IO41 | SD\_MOSI(主出从入) | | | | IO41 | SD\_MOSI(主出从入) |
| **IMU 传感器** | I2C | IO8 | IMU\_SDA数据线) | | **IMU 传感器** | I2C | IO47 | IMU\_SDA与 TP 共用,见上) |
| (QMI8658A) | | IO7 | IMU\_SCL时钟线) | | (QMI8658A) | | IO48 | IMU\_SCL与 TP 共用,见上) |
| | GPIO | IO6 | IMU\_INT1(中断1 | | | GPIO | IO8 | IMU\_INT1(中断1 |
| | | IO15 | IMU\_INT2(中断2 | | | | IO7 | IMU\_INT2(中断2 |
| **UART0 串口** | UART | IO43 | U0\_TX(发送) | | **UART0 串口** | UART | IO43 | U0\_TX(发送) |
| | | IO44 | U0\_RX(接收) | | | | IO44 | U0\_RX(接收) |
| **用户按键** | GPIO | IO0 | BOOT0(启动/下载模式) | | **用户按键** | GPIO | IO0 | BOOT0(启动/下载模式) |
@@ -63,6 +63,17 @@
| **扩展排针 J3** | 混合 | IO0~IO14 | 20Pin 排针,引出主要 GPIO | | **扩展排针 J3** | 混合 | IO0~IO14 | 20Pin 排针,引出主要 GPIO |
| **扩展排针 J4** | 混合 | IO14~IO48等 | 20Pin 排针,含 USB/3V3/地 | | **扩展排针 J4** | 混合 | IO14~IO48等 | 20Pin 排针,含 USB/3V3/地 |
### I2C 设备与固件配置
触控版 PCB 上 **CST816(触摸)****QMI8658AIMU** 挂载在同一条 I2C 总线(`I2C_NUM_0`SDA=IO47SCL=IO48)。固件在 `main/bsp/board_config.h` 中定义引脚,触摸与 IMU 驱动共用该总线句柄。
| 设备 | I2C 地址(7-bit | 说明 |
| :------- | :---------------- | :---------------------------- |
| CST816S | 由驱动自动探测 | 电容触摸 |
| QMI8658A | `0x6A``0x6B` | 由 SDO 电平决定;本板实测多为 `0x6B` |
IMU 中断引脚(IO8 / IO7)当前固件采用轮询采样,Phase 4 可改为中断驱动。
## Wi-Fi Station Example ## Wi-Fi Station Example
(See the README.md file in the upper level 'examples' directory for more information about examples.) (See the README.md file in the upper level 'examples' directory for more information about examples.)
+3
View File
@@ -1,10 +1,13 @@
idf_component_register(SRCS idf_component_register(SRCS
"app/app_main.c" "app/app_main.c"
"app/ui_handlers.c" "app/ui_handlers.c"
"app/ui_nav.c"
"app/motion_gesture.c"
"bsp/display.c" "bsp/display.c"
"bsp/touch.c" "bsp/touch.c"
"bsp/lvgl_port.c" "bsp/lvgl_port.c"
"bsp/boot_button.c" "bsp/boot_button.c"
"bsp/imu.c"
"services/wifi_manager.c" "services/wifi_manager.c"
"services/ble_manager.c" "services/ble_manager.c"
"services/ble_hid.c" "services/ble_hid.c"
+19
View File
@@ -3,11 +3,15 @@
#include "board_config.h" #include "board_config.h"
#include "display.h" #include "display.h"
#include "imu.h"
#include "lvgl_port.h" #include "lvgl_port.h"
#include "motion_gesture.h"
#include "touch.h" #include "touch.h"
#include "ui.h" #include "ui.h"
#include "ui_handlers.h" #include "ui_handlers.h"
#include "ui_nav.h"
#include "ble_manager.h" #include "ble_manager.h"
#include "settings/settings_store.h"
#if BOARD_WIFI_ENABLED #if BOARD_WIFI_ENABLED
#include "wifi_manager.h" #include "wifi_manager.h"
@@ -38,10 +42,25 @@ void app_main(void)
lvgl_port_lock(); lvgl_port_lock();
ui_init(); ui_init();
ui_nav_init();
lvgl_port_unlock(); lvgl_port_unlock();
settings_store_load();
settings_store_apply();
ui_handlers_init(); ui_handlers_init();
motion_gesture_init(NULL);
#if BOARD_IMU_ENABLED
ret = imu_init();
if (ret == ESP_OK) {
imu_start_task(motion_gesture_on_sample);
} else {
ESP_LOGW(TAG, "IMU init failed, shake navigation unavailable");
}
#endif
#if BOARD_WIFI_ENABLED #if BOARD_WIFI_ENABLED
ESP_LOGI(TAG, "Starting WiFi"); ESP_LOGI(TAG, "Starting WiFi");
wifi_manager_init(); wifi_manager_init();
+295
View File
@@ -0,0 +1,295 @@
#include "motion_gesture.h"
#include "ui_nav.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include <assert.h>
#include <math.h>
static const char *TAG = "motion";
#define SAMPLE_PERIOD_MS 10
#define SAMPLE_RATE_HZ 100
/* Calibration: keep a sliding window of the last 500 ms and finish only when
* the window is stable (low variance). This avoids calibrating while the user
* is picking up or moving the device. */
#define CALIBRATION_MS 500
#define CALIBRATION_WINDOW_SAMPLES (CALIBRATION_MS / SAMPLE_PERIOD_MS)
#define CALIBRATION_MAX_VAR_G2 0.010f
/* Shake is detected on the horizontal plane (X/Y) so that picking the device
* up or putting it down (Z-axis jolt) does not trigger navigation. */
#define THRESHOLD_LOW_G 0.6f
#define THRESHOLD_MED_G 0.8f
#define THRESHOLD_HIGH_G 1.0f
/* A valid shake must produce enough strong samples within a short window. */
#define WINDOW_MS 300
#define MIN_SAMPLES 5
/* A real shake oscillates; require at least two zero-crossings on the
* dominant horizontal axis to distinguish it from a single jolt. */
#define MIN_ZERO_CROSSINGS 2
/* Cooldown prevents one physical shake from generating multiple events. */
#define COOLDOWN_MS 350
typedef enum {
GESTURE_IDLE = 0,
GESTURE_COOLDOWN,
} gesture_state_t;
static QueueHandle_t s_nav_queue;
static bool s_enabled = true;
static float s_threshold = THRESHOLD_MED_G;
static gesture_state_t s_state = GESTURE_IDLE;
/* Baseline calibration */
static float s_ref_x;
static float s_ref_y;
static float s_ref_z;
static bool s_calibrated;
static float s_cal_buf_x[CALIBRATION_WINDOW_SAMPLES];
static float s_cal_buf_y[CALIBRATION_WINDOW_SAMPLES];
static float s_cal_buf_z[CALIBRATION_WINDOW_SAMPLES];
static int s_cal_head;
static int s_cal_count;
/* Detection window state */
static int s_over_count;
static int64_t s_window_start_us;
static int s_zero_crossings;
static int s_last_x_sign;
static int s_last_y_sign;
static int64_t s_cooldown_until_us;
/* Last horizontal magnitude, exposed for UI/debug. */
static float s_last_horizontal_mag;
static void nav_handler_task(void *arg)
{
(void)arg;
motion_nav_event_t ev;
while (xQueueReceive(s_nav_queue, &ev, portMAX_DELAY) == pdTRUE) {
if (ev == MOTION_NAV_NEXT) {
ui_nav_next();
}
}
}
static void post_nav_next(void)
{
motion_nav_event_t ev = MOTION_NAV_NEXT;
if (xQueueSend(s_nav_queue, &ev, 0) != pdTRUE) {
ESP_LOGW(TAG, "nav queue full");
} else {
ESP_LOGI(TAG, "Shake detected -> next screen");
}
}
static void reset_window(void)
{
s_over_count = 0;
s_window_start_us = 0;
s_zero_crossings = 0;
s_last_x_sign = 0;
s_last_y_sign = 0;
}
static void run_calibration(float ax, float ay, float az)
{
s_cal_buf_x[s_cal_head] = ax;
s_cal_buf_y[s_cal_head] = ay;
s_cal_buf_z[s_cal_head] = az;
s_cal_head = (s_cal_head + 1) % CALIBRATION_WINDOW_SAMPLES;
if (s_cal_count < CALIBRATION_WINDOW_SAMPLES) {
s_cal_count++;
}
if (s_cal_count < CALIBRATION_WINDOW_SAMPLES) {
return;
}
float sum_x = 0.0f;
float sum_y = 0.0f;
float sum_z = 0.0f;
float sum_sq_x = 0.0f;
float sum_sq_y = 0.0f;
float sum_sq_z = 0.0f;
for (int i = 0; i < CALIBRATION_WINDOW_SAMPLES; i++) {
sum_x += s_cal_buf_x[i];
sum_y += s_cal_buf_y[i];
sum_z += s_cal_buf_z[i];
sum_sq_x += s_cal_buf_x[i] * s_cal_buf_x[i];
sum_sq_y += s_cal_buf_y[i] * s_cal_buf_y[i];
sum_sq_z += s_cal_buf_z[i] * s_cal_buf_z[i];
}
const int n = CALIBRATION_WINDOW_SAMPLES;
float var_x = (sum_sq_x / n) - (sum_x / n) * (sum_x / n);
float var_y = (sum_sq_y / n) - (sum_y / n) * (sum_y / n);
float var_z = (sum_sq_z / n) - (sum_z / n) * (sum_z / n);
float max_var = fmaxf(var_x, fmaxf(var_y, var_z));
if (max_var > CALIBRATION_MAX_VAR_G2) {
static int64_t s_last_cal_warn_us;
int64_t now_us = esp_timer_get_time();
if ((now_us - s_last_cal_warn_us) >= 1000 * 1000) {
s_last_cal_warn_us = now_us;
ESP_LOGW(TAG, "Calibration: motion detected (var=%.3f), waiting", max_var);
}
return;
}
s_ref_x = sum_x / n;
s_ref_y = sum_y / n;
s_ref_z = sum_z / n;
s_calibrated = true;
ESP_LOGI(TAG, "Baseline calibrated (ref=%.3f,%.3f,%.3f g)", s_ref_x, s_ref_y, s_ref_z);
}
static int axis_sign(float value)
{
if (value > 0.0f) {
return 1;
}
if (value < 0.0f) {
return -1;
}
return 0;
}
static void process_sample(float ax, float ay, float az)
{
int64_t now_us = esp_timer_get_time();
if (!s_calibrated) {
run_calibration(ax, ay, az);
return;
}
float dx = ax - s_ref_x;
float dy = ay - s_ref_y;
float dz = az - s_ref_z;
/* Use only horizontal magnitude for shake detection. Z-axis is dominated by
* gravity and is the main axis for pick-up/put-down false triggers. */
float horizontal_mag = sqrtf(dx * dx + dy * dy);
s_last_horizontal_mag = horizontal_mag;
if (s_state == GESTURE_COOLDOWN) {
if (now_us >= s_cooldown_until_us) {
s_state = GESTURE_IDLE;
reset_window();
}
return;
}
if (horizontal_mag > s_threshold) {
if (s_over_count == 0) {
s_window_start_us = now_us;
}
s_over_count++;
int x_sign = axis_sign(dx);
int y_sign = axis_sign(dy);
if (s_last_x_sign != 0 && x_sign != s_last_x_sign) {
s_zero_crossings++;
}
if (s_last_y_sign != 0 && y_sign != s_last_y_sign) {
s_zero_crossings++;
}
s_last_x_sign = x_sign;
s_last_y_sign = y_sign;
int64_t window_us = (int64_t)WINDOW_MS * 1000;
if ((now_us - s_window_start_us) > window_us) {
/* Window expired: start fresh with the current sample. */
reset_window();
s_window_start_us = now_us;
s_over_count = 1;
s_last_x_sign = axis_sign(dx);
s_last_y_sign = axis_sign(dy);
}
if (s_over_count >= MIN_SAMPLES && s_zero_crossings >= MIN_ZERO_CROSSINGS) {
if (s_enabled) {
post_nav_next();
}
s_state = GESTURE_COOLDOWN;
s_cooldown_until_us = now_us + (int64_t)COOLDOWN_MS * 1000;
reset_window();
}
} else if (s_over_count > 0) {
int64_t window_us = (int64_t)WINDOW_MS * 1000;
if ((now_us - s_window_start_us) > window_us) {
reset_window();
}
}
(void)dz;
}
void motion_gesture_init(motion_nav_cb_t cb)
{
(void)cb;
s_nav_queue = xQueueCreate(4, sizeof(motion_nav_event_t));
xTaskCreate(nav_handler_task, "nav_handler", 2048, NULL, 5, NULL);
s_calibrated = false;
s_cal_head = 0;
s_cal_count = 0;
ESP_LOGI(TAG, "Motion gesture ready (calibrating %d ms)", CALIBRATION_MS);
}
void motion_gesture_set_enabled(bool enabled)
{
s_enabled = enabled;
ESP_LOGI(TAG, "Shake navigation %s", enabled ? "enabled" : "disabled");
}
void motion_gesture_set_sensitivity(shake_sensitivity_t sens)
{
switch (sens) {
case SHAKE_SENS_LOW:
s_threshold = THRESHOLD_LOW_G;
break;
case SHAKE_SENS_MED:
s_threshold = THRESHOLD_MED_G;
break;
case SHAKE_SENS_HIGH:
s_threshold = THRESHOLD_HIGH_G;
break;
default:
assert(false && "unexpected shake_sensitivity_t");
break;
}
ESP_LOGI(TAG, "Sensitivity set (threshold=%.2f g)", s_threshold);
}
void motion_gesture_on_sample(float ax_g, float ay_g, float az_g)
{
process_sample(ax_g, ay_g, az_g);
}
float motion_gesture_get_last_magnitude(void)
{
return s_last_horizontal_mag;
}
bool motion_gesture_is_calibrated(void)
{
return s_calibrated;
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#include "settings_types.h"
typedef enum {
MOTION_NAV_NEXT = 0,
} motion_nav_event_t;
typedef void (*motion_nav_cb_t)(motion_nav_event_t event);
void motion_gesture_init(motion_nav_cb_t cb);
void motion_gesture_set_enabled(bool enabled);
void motion_gesture_set_sensitivity(shake_sensitivity_t sens);
void motion_gesture_on_sample(float ax_g, float ay_g, float az_g);
float motion_gesture_get_last_magnitude(void);
bool motion_gesture_is_calibrated(void);
+6
View File
@@ -105,6 +105,12 @@ void settings_on_value_changed(lv_event_t *e)
case SETTINGS_FIELD_MUTE: case SETTINGS_FIELD_MUTE:
buf.mute = lv_obj_has_state(target, LV_STATE_CHECKED); buf.mute = lv_obj_has_state(target, LV_STATE_CHECKED);
break; break;
case SETTINGS_FIELD_SHAKE_NAV:
buf.shake_nav_enabled = lv_obj_has_state(target, LV_STATE_CHECKED);
break;
case SETTINGS_FIELD_SHAKE_SENS:
buf.shake_sensitivity = (shake_sensitivity_t)lv_dropdown_get_selected(target);
break;
default: default:
LV_ASSERT_MSG(false, "unexpected settings_field_t"); LV_ASSERT_MSG(false, "unexpected settings_field_t");
break; break;
+76
View File
@@ -0,0 +1,76 @@
#include "ui_nav.h"
#include "lvgl_port.h"
#include "screens/ui_Screen1.h"
#include "screens/ui_Screen2.h"
#include "screens/ui_Screen3.h"
#include "screens/ui_Settings.h"
#include "ui_helpers.h"
#include "esp_log.h"
static const char *TAG = "ui_nav";
static lv_obj_t *s_nav_screens[4];
static const char *const s_nav_names[] = {
"Settings",
"Screen1",
"Screen2",
"Screen3",
};
#define NAV_SCREEN_COUNT (sizeof(s_nav_screens) / sizeof(s_nav_screens[0]))
static uint8_t s_nav_index;
void ui_nav_init(void)
{
s_nav_screens[0] = ui_Settings;
s_nav_screens[1] = ui_Screen1;
s_nav_screens[2] = ui_Screen2;
s_nav_screens[3] = ui_Screen3;
s_nav_index = 0;
ESP_LOGI(TAG, "Navigation ready (%u screens, start=%s)", (unsigned)NAV_SCREEN_COUNT,
s_nav_names[s_nav_index]);
}
void ui_nav_goto(uint8_t index)
{
if (index >= NAV_SCREEN_COUNT) {
return;
}
lvgl_port_lock();
s_nav_index = index;
lv_obj_t **target = &s_nav_screens[s_nav_index];
_ui_screen_change(target, LV_SCR_LOAD_ANIM_FADE_IN, 300, 0, NULL);
lvgl_port_unlock();
ESP_LOGI(TAG, "Goto %s (index %u)", s_nav_names[s_nav_index], s_nav_index);
}
void ui_nav_next(void)
{
lvgl_port_lock();
s_nav_index = (s_nav_index + 1) % NAV_SCREEN_COUNT;
lv_obj_t **target = &s_nav_screens[s_nav_index];
_ui_screen_change(target, LV_SCR_LOAD_ANIM_FADE_IN, 300, 0, NULL);
lvgl_port_unlock();
ESP_LOGI(TAG, "Next -> %s (index %u)", s_nav_names[s_nav_index], s_nav_index);
}
lv_obj_t *ui_nav_current(void)
{
return s_nav_screens[s_nav_index];
}
uint8_t ui_nav_current_index(void)
{
return s_nav_index;
}
const char *ui_nav_current_name(void)
{
return s_nav_names[s_nav_index];
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "lvgl.h"
void ui_nav_init(void);
void ui_nav_next(void);
void ui_nav_goto(uint8_t index);
lv_obj_t *ui_nav_current(void);
uint8_t ui_nav_current_index(void);
const char *ui_nav_current_name(void);
+9
View File
@@ -49,6 +49,15 @@
#define BOARD_TOUCH_I2C_FREQ_HZ 400000 #define BOARD_TOUCH_I2C_FREQ_HZ 400000
#define BOARD_TOUCH_ENABLED 1 #define BOARD_TOUCH_ENABLED 1
/* IMU (QMI8658) — shares I2C bus with touch on IO47/IO48 */
#define BOARD_IMU_I2C_NUM I2C_NUM_0
#define BOARD_PIN_IMU_SDA 47
#define BOARD_PIN_IMU_SCL 48
#define BOARD_PIN_IMU_INT1 8
#define BOARD_PIN_IMU_INT2 7
#define BOARD_IMU_I2C_FREQ_HZ 400000
#define BOARD_IMU_ENABLED 1
/* User IO */ /* User IO */
#define BOARD_PIN_BOOT 0 #define BOARD_PIN_BOOT 0
#define BOARD_PIN_KEY 4 #define BOARD_PIN_KEY 4
+1 -4
View File
@@ -2,7 +2,6 @@
#include "board_config.h" #include "board_config.h"
#include "driver/gpio.h"
#include "driver/ledc.h" #include "driver/ledc.h"
#include "driver/spi_master.h" #include "driver/spi_master.h"
#include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_ops.h"
@@ -49,6 +48,7 @@ esp_err_t display_init(esp_lcd_panel_io_handle_t *io_handle_out,
.gpio_num = BOARD_PIN_BK_LIGHT, .gpio_num = BOARD_PIN_BK_LIGHT,
.duty = 0, .duty = 0,
.hpoint = 0, .hpoint = 0,
.flags.output_invert = 1,
}; };
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
@@ -90,9 +90,6 @@ esp_err_t display_init(esp_lcd_panel_io_handle_t *io_handle_out,
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, BOARD_LCD_GAP_X, BOARD_LCD_GAP_Y)); ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, BOARD_LCD_GAP_X, BOARD_LCD_GAP_Y));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(BOARD_PIN_BK_LIGHT, BOARD_LCD_BK_LIGHT_ON_LEVEL);
*io_handle_out = io_handle; *io_handle_out = io_handle;
*panel_handle_out = panel_handle; *panel_handle_out = panel_handle;
return ESP_OK; return ESP_OK;
+308
View File
@@ -0,0 +1,308 @@
#include "imu.h"
#include "board_config.h"
#include "touch.h"
#include "driver/i2c_master.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <math.h>
static const char *TAG = "imu";
#if BOARD_IMU_ENABLED
#define QMI8658_ADDR_SDO_LOW 0x6A
#define QMI8658_ADDR_SDO_HIGH 0x6B
#define QMI8658_WHO_AM_I 0x00
#define QMI8658_WHO_AM_I_ID 0x05
#define QMI8658_CTRL1 0x02
#define QMI8658_CTRL2 0x03
#define QMI8658_CTRL3 0x04
#define QMI8658_CTRL5 0x06
#define QMI8658_CTRL6 0x07
#define QMI8658_CTRL7 0x08
#define QMI8658_RESET 0x60
#define QMI8658_ACC_X_L 0x35
#define QMI8658_RESET_VALUE 0xB0
/* Little-endian + address auto-increment (matches int16 parse below) */
#define QMI8658_CTRL1_VALUE 0x40
/* ±8g, 1000 Hz ODR */
#define QMI8658_CTRL2_VALUE 0x23
/* Gyro ±512 dps, 1000 Hz — required even when only reading accel */
#define QMI8658_CTRL3_VALUE 0x53
#define QMI8658_CTRL5_VALUE 0x11
#define QMI8658_CTRL6_VALUE 0x00
#define QMI8658_CTRL7_VALUE 0x03
#define QMI8658_ACCEL_SENS_8G 4096.0f
#define IMU_SAMPLE_PERIOD_MS 10
#define IMU_DEBUG_LOG_INTERVAL_MS 1000
static i2c_master_bus_handle_t s_imu_bus;
static i2c_master_dev_handle_t s_imu_dev;
static imu_sample_cb_t s_sample_cb;
static esp_err_t imu_read_raw(int16_t *rx, int16_t *ry, int16_t *rz);
static int16_t imu_raw16_le(const uint8_t *lo, const uint8_t *hi)
{
return (int16_t)((*hi << 8) | *lo);
}
static esp_err_t imu_write_u8(uint8_t reg, uint8_t val)
{
uint8_t buf[2] = {reg, val};
return i2c_master_transmit(s_imu_dev, buf, sizeof(buf), 100);
}
static esp_err_t imu_read_u8(uint8_t reg, uint8_t *val)
{
return i2c_master_transmit_receive(s_imu_dev, &reg, 1, val, 1, 100);
}
static esp_err_t imu_read_bytes(uint8_t reg, uint8_t *data, size_t len)
{
return i2c_master_transmit_receive(s_imu_dev, &reg, 1, data, len, 100);
}
static esp_err_t imu_probe_device(uint8_t addr)
{
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = addr,
.scl_speed_hz = BOARD_IMU_I2C_FREQ_HZ,
};
esp_err_t err = i2c_master_bus_add_device(s_imu_bus, &dev_cfg, &s_imu_dev);
if (err != ESP_OK) {
return err;
}
uint8_t who_am_i = 0;
err = imu_read_u8(QMI8658_WHO_AM_I, &who_am_i);
if (err != ESP_OK || who_am_i != QMI8658_WHO_AM_I_ID) {
i2c_master_bus_rm_device(s_imu_dev);
s_imu_dev = NULL;
if (err == ESP_OK) {
ESP_LOGW(TAG, "WHO_AM_I mismatch at 0x%02X: 0x%02X", addr, who_am_i);
return ESP_ERR_NOT_FOUND;
}
return err;
}
ESP_LOGI(TAG, "QMI8658 found at 0x%02X (WHO_AM_I=0x%02X)", addr, who_am_i);
return ESP_OK;
}
static esp_err_t imu_configure(void)
{
esp_err_t err = imu_write_u8(QMI8658_RESET, QMI8658_RESET_VALUE);
if (err != ESP_OK) {
return err;
}
vTaskDelay(pdMS_TO_TICKS(50));
err = imu_write_u8(QMI8658_CTRL1, QMI8658_CTRL1_VALUE);
if (err != ESP_OK) {
return err;
}
err = imu_write_u8(QMI8658_CTRL2, QMI8658_CTRL2_VALUE);
if (err != ESP_OK) {
return err;
}
err = imu_write_u8(QMI8658_CTRL3, QMI8658_CTRL3_VALUE);
if (err != ESP_OK) {
return err;
}
err = imu_write_u8(QMI8658_CTRL5, QMI8658_CTRL5_VALUE);
if (err != ESP_OK) {
return err;
}
err = imu_write_u8(QMI8658_CTRL6, QMI8658_CTRL6_VALUE);
if (err != ESP_OK) {
return err;
}
err = imu_write_u8(QMI8658_CTRL7, QMI8658_CTRL7_VALUE);
if (err != ESP_OK) {
return err;
}
vTaskDelay(pdMS_TO_TICKS(50));
return ESP_OK;
}
static void imu_log_sample(const char *label, float ax, float ay, float az,
int16_t rx, int16_t ry, int16_t rz)
{
float mag = sqrtf(ax * ax + ay * ay + az * az);
ESP_LOGI(TAG, "%s raw=(%d,%d,%d) g=(%.3f,%.3f,%.3f) |mag|=%.3f",
label, rx, ry, rz, ax, ay, az, mag);
}
static void imu_task(void *arg)
{
(void)arg;
int64_t last_log_us = esp_timer_get_time();
while (1) {
float ax = 0.0f;
float ay = 0.0f;
float az = 0.0f;
int16_t rx = 0;
int16_t ry = 0;
int16_t rz = 0;
esp_err_t err = imu_read_raw(&rx, &ry, &rz);
if (err == ESP_OK) {
ax = (float)rx / QMI8658_ACCEL_SENS_8G;
ay = (float)ry / QMI8658_ACCEL_SENS_8G;
az = (float)rz / QMI8658_ACCEL_SENS_8G;
int64_t now_us = esp_timer_get_time();
if ((now_us - last_log_us) >= (int64_t)IMU_DEBUG_LOG_INTERVAL_MS * 1000) {
imu_log_sample("sample", ax, ay, az, rx, ry, rz);
last_log_us = now_us;
}
if (s_sample_cb != NULL) {
s_sample_cb(ax, ay, az);
}
}
vTaskDelay(pdMS_TO_TICKS(IMU_SAMPLE_PERIOD_MS));
}
}
esp_err_t imu_init(void)
{
#if BOARD_TOUCH_ENABLED
s_imu_bus = touch_get_i2c_bus();
if (s_imu_bus == NULL) {
ESP_LOGE(TAG, "Shared I2C bus not ready (touch not initialized?)");
return ESP_ERR_INVALID_STATE;
}
ESP_LOGI(TAG, "Using shared I2C bus (SDA=%d SCL=%d)", BOARD_PIN_IMU_SDA,
BOARD_PIN_IMU_SCL);
#else
ESP_LOGI(TAG, "Initialize dedicated I2C for IMU");
i2c_master_bus_config_t bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = BOARD_IMU_I2C_NUM,
.sda_io_num = BOARD_PIN_IMU_SDA,
.scl_io_num = BOARD_PIN_IMU_SCL,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
esp_err_t bus_err = i2c_new_master_bus(&bus_config, &s_imu_bus);
if (bus_err != ESP_OK) {
return bus_err;
}
#endif
esp_err_t err = imu_probe_device(QMI8658_ADDR_SDO_LOW);
if (err != ESP_OK) {
err = imu_probe_device(QMI8658_ADDR_SDO_HIGH);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "QMI8658 not found");
return err;
}
err = imu_configure();
if (err != ESP_OK) {
ESP_LOGE(TAG, "IMU configure failed: %s", esp_err_to_name(err));
return err;
}
float ax = 0.0f;
float ay = 0.0f;
float az = 0.0f;
int16_t rx = 0;
int16_t ry = 0;
int16_t rz = 0;
if (imu_read_accel(&ax, &ay, &az) == ESP_OK) {
imu_read_raw(&rx, &ry, &rz);
imu_log_sample("init check", ax, ay, az, rx, ry, rz);
} else {
ESP_LOGW(TAG, "Init check read failed");
}
ESP_LOGI(TAG, "IMU initialized (±8g, 1000 Hz ODR)");
return ESP_OK;
}
static esp_err_t imu_read_raw(int16_t *rx, int16_t *ry, int16_t *rz)
{
if (s_imu_dev == NULL || rx == NULL || ry == NULL || rz == NULL) {
return ESP_ERR_INVALID_STATE;
}
uint8_t raw[6];
esp_err_t err = imu_read_bytes(QMI8658_ACC_X_L, raw, sizeof(raw));
if (err != ESP_OK) {
return err;
}
*rx = imu_raw16_le(&raw[0], &raw[1]);
*ry = imu_raw16_le(&raw[2], &raw[3]);
*rz = imu_raw16_le(&raw[4], &raw[5]);
return ESP_OK;
}
esp_err_t imu_read_accel(float *ax, float *ay, float *az)
{
int16_t rx = 0;
int16_t ry = 0;
int16_t rz = 0;
esp_err_t err = imu_read_raw(&rx, &ry, &rz);
if (err != ESP_OK) {
return err;
}
*ax = (float)rx / QMI8658_ACCEL_SENS_8G;
*ay = (float)ry / QMI8658_ACCEL_SENS_8G;
*az = (float)rz / QMI8658_ACCEL_SENS_8G;
return ESP_OK;
}
void imu_start_task(imu_sample_cb_t cb)
{
s_sample_cb = cb;
xTaskCreate(imu_task, "imu_task", 3072, NULL, 4, NULL);
ESP_LOGI(TAG, "IMU sample task started");
}
#else
esp_err_t imu_init(void)
{
return ESP_OK;
}
esp_err_t imu_read_accel(float *ax, float *ay, float *az)
{
(void)ax;
(void)ay;
(void)az;
return ESP_ERR_NOT_SUPPORTED;
}
void imu_start_task(imu_sample_cb_t cb)
{
(void)cb;
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "esp_err.h"
typedef void (*imu_sample_cb_t)(float ax_g, float ay_g, float az_g);
esp_err_t imu_init(void);
esp_err_t imu_read_accel(float *ax, float *ay, float *az);
void imu_start_task(imu_sample_cb_t cb);
+10
View File
@@ -103,6 +103,11 @@ esp_err_t touch_init(lv_display_t *display)
return ESP_OK; return ESP_OK;
} }
i2c_master_bus_handle_t touch_get_i2c_bus(void)
{
return s_touch_i2c_bus;
}
#else #else
esp_err_t touch_init(lv_display_t *display) esp_err_t touch_init(lv_display_t *display)
@@ -111,4 +116,9 @@ esp_err_t touch_init(lv_display_t *display)
return ESP_OK; return ESP_OK;
} }
i2c_master_bus_handle_t touch_get_i2c_bus(void)
{
return NULL;
}
#endif #endif
+2
View File
@@ -1,6 +1,8 @@
#pragma once #pragma once
#include "driver/i2c_master.h"
#include "esp_err.h" #include "esp_err.h"
#include "lvgl.h" #include "lvgl.h"
esp_err_t touch_init(lv_display_t *display); esp_err_t touch_init(lv_display_t *display);
i2c_master_bus_handle_t touch_get_i2c_bus(void);
+4 -2
View File
@@ -4,6 +4,7 @@
// Project name: HeavenlyHeroStar // Project name: HeavenlyHeroStar
#include "../ui.h" #include "../ui.h"
#include "board_config.h"
lv_obj_t * uic_Calendar1; lv_obj_t * uic_Calendar1;
lv_obj_t * ui_Screen1 = NULL; lv_obj_t * ui_Screen1 = NULL;
@@ -15,12 +16,13 @@ lv_obj_t * ui_Calendar1 = NULL;
void ui_Screen1_screen_init(void) void ui_Screen1_screen_init(void)
{ {
ui_Screen1 = lv_obj_create(NULL); ui_Screen1 = lv_obj_create(NULL);
lv_obj_set_size(ui_Screen1, BOARD_LCD_LOGIC_H_RES, BOARD_LCD_LOGIC_V_RES);
lv_obj_remove_flag(ui_Screen1, LV_OBJ_FLAG_SCROLLABLE); /// Flags lv_obj_remove_flag(ui_Screen1, LV_OBJ_FLAG_SCROLLABLE); /// Flags
ui_Calendar1 = lv_calendar_create(ui_Screen1); ui_Calendar1 = lv_calendar_create(ui_Screen1);
lv_obj_t * ui_Calendar1_header = lv_calendar_header_arrow_create(ui_Calendar1); lv_obj_t * ui_Calendar1_header = lv_calendar_header_arrow_create(ui_Calendar1);
lv_obj_set_width(ui_Calendar1, 170); lv_obj_set_width(ui_Calendar1, BOARD_LCD_LOGIC_H_RES);
lv_obj_set_height(ui_Calendar1, 320); lv_obj_set_height(ui_Calendar1, BOARD_LCD_LOGIC_V_RES);
lv_obj_set_align(ui_Calendar1, LV_ALIGN_CENTER); lv_obj_set_align(ui_Calendar1, LV_ALIGN_CENTER);
uic_Calendar1 = ui_Calendar1; uic_Calendar1 = ui_Calendar1;
+12 -6
View File
@@ -4,6 +4,7 @@
// Project name: HeavenlyHeroStar // Project name: HeavenlyHeroStar
#include "../ui.h" #include "../ui.h"
#include "board_config.h"
lv_obj_t * ui_Screen2 = NULL; lv_obj_t * ui_Screen2 = NULL;
lv_obj_t * ui_Button1 = NULL; lv_obj_t * ui_Button1 = NULL;
@@ -45,11 +46,14 @@ void ui_event_Button3(lv_event_t * e)
void ui_Screen2_screen_init(void) void ui_Screen2_screen_init(void)
{ {
ui_Screen2 = lv_obj_create(NULL); ui_Screen2 = lv_obj_create(NULL);
lv_obj_set_size(ui_Screen2, BOARD_LCD_LOGIC_H_RES, BOARD_LCD_LOGIC_V_RES);
lv_obj_remove_flag(ui_Screen2, LV_OBJ_FLAG_SCROLLABLE); /// Flags lv_obj_remove_flag(ui_Screen2, LV_OBJ_FLAG_SCROLLABLE); /// Flags
ui_Button1 = lv_button_create(ui_Screen2); ui_Button1 = lv_button_create(ui_Screen2);
lv_obj_set_width(ui_Button1, 100); lv_obj_set_width(ui_Button1, 100);
lv_obj_set_height(ui_Button1, 80); lv_obj_set_height(ui_Button1, 80);
lv_obj_set_x(ui_Button1, -100);
lv_obj_set_y(ui_Button1, 0);
lv_obj_set_align(ui_Button1, LV_ALIGN_CENTER); lv_obj_set_align(ui_Button1, LV_ALIGN_CENTER);
lv_obj_add_flag(ui_Button1, LV_OBJ_FLAG_SCROLL_ON_FOCUS); /// Flags lv_obj_add_flag(ui_Button1, LV_OBJ_FLAG_SCROLL_ON_FOCUS); /// Flags
lv_obj_remove_flag(ui_Button1, LV_OBJ_FLAG_SCROLLABLE); /// Flags lv_obj_remove_flag(ui_Button1, LV_OBJ_FLAG_SCROLLABLE); /// Flags
@@ -58,7 +62,7 @@ void ui_Screen2_screen_init(void)
lv_obj_set_width(ui_Button2, 100); lv_obj_set_width(ui_Button2, 100);
lv_obj_set_height(ui_Button2, 80); lv_obj_set_height(ui_Button2, 80);
lv_obj_set_x(ui_Button2, 0); lv_obj_set_x(ui_Button2, 0);
lv_obj_set_y(ui_Button2, 100); lv_obj_set_y(ui_Button2, 0);
lv_obj_set_align(ui_Button2, LV_ALIGN_CENTER); lv_obj_set_align(ui_Button2, LV_ALIGN_CENTER);
lv_obj_add_flag(ui_Button2, LV_OBJ_FLAG_SCROLL_ON_FOCUS); /// Flags lv_obj_add_flag(ui_Button2, LV_OBJ_FLAG_SCROLL_ON_FOCUS); /// Flags
lv_obj_remove_flag(ui_Button2, LV_OBJ_FLAG_SCROLLABLE); /// Flags lv_obj_remove_flag(ui_Button2, LV_OBJ_FLAG_SCROLLABLE); /// Flags
@@ -66,8 +70,8 @@ void ui_Screen2_screen_init(void)
ui_Button3 = lv_button_create(ui_Screen2); ui_Button3 = lv_button_create(ui_Screen2);
lv_obj_set_width(ui_Button3, 100); lv_obj_set_width(ui_Button3, 100);
lv_obj_set_height(ui_Button3, 80); lv_obj_set_height(ui_Button3, 80);
lv_obj_set_x(ui_Button3, 0); lv_obj_set_x(ui_Button3, 100);
lv_obj_set_y(ui_Button3, -100); lv_obj_set_y(ui_Button3, 0);
lv_obj_set_align(ui_Button3, LV_ALIGN_CENTER); lv_obj_set_align(ui_Button3, LV_ALIGN_CENTER);
lv_obj_add_flag(ui_Button3, LV_OBJ_FLAG_SCROLL_ON_FOCUS); /// Flags lv_obj_add_flag(ui_Button3, LV_OBJ_FLAG_SCROLL_ON_FOCUS); /// Flags
lv_obj_remove_flag(ui_Button3, LV_OBJ_FLAG_SCROLLABLE); /// Flags lv_obj_remove_flag(ui_Button3, LV_OBJ_FLAG_SCROLLABLE); /// Flags
@@ -75,6 +79,8 @@ void ui_Screen2_screen_init(void)
ui_Label2 = lv_label_create(ui_Screen2); ui_Label2 = lv_label_create(ui_Screen2);
lv_obj_set_width(ui_Label2, LV_SIZE_CONTENT); /// 1 lv_obj_set_width(ui_Label2, LV_SIZE_CONTENT); /// 1
lv_obj_set_height(ui_Label2, LV_SIZE_CONTENT); /// 1 lv_obj_set_height(ui_Label2, LV_SIZE_CONTENT); /// 1
lv_obj_set_x(ui_Label2, -100);
lv_obj_set_y(ui_Label2, 0);
lv_obj_set_align(ui_Label2, LV_ALIGN_CENTER); lv_obj_set_align(ui_Label2, LV_ALIGN_CENTER);
lv_label_set_text(ui_Label2, "Ctrl+C"); lv_label_set_text(ui_Label2, "Ctrl+C");
@@ -82,15 +88,15 @@ void ui_Screen2_screen_init(void)
lv_obj_set_width(ui_Label4, LV_SIZE_CONTENT); /// 1 lv_obj_set_width(ui_Label4, LV_SIZE_CONTENT); /// 1
lv_obj_set_height(ui_Label4, LV_SIZE_CONTENT); /// 1 lv_obj_set_height(ui_Label4, LV_SIZE_CONTENT); /// 1
lv_obj_set_x(ui_Label4, 0); lv_obj_set_x(ui_Label4, 0);
lv_obj_set_y(ui_Label4, 100); lv_obj_set_y(ui_Label4, 0);
lv_obj_set_align(ui_Label4, LV_ALIGN_CENTER); lv_obj_set_align(ui_Label4, LV_ALIGN_CENTER);
lv_label_set_text(ui_Label4, "Ctrl+V"); lv_label_set_text(ui_Label4, "Ctrl+V");
ui_Label5 = lv_label_create(ui_Screen2); ui_Label5 = lv_label_create(ui_Screen2);
lv_obj_set_width(ui_Label5, LV_SIZE_CONTENT); /// 1 lv_obj_set_width(ui_Label5, LV_SIZE_CONTENT); /// 1
lv_obj_set_height(ui_Label5, LV_SIZE_CONTENT); /// 1 lv_obj_set_height(ui_Label5, LV_SIZE_CONTENT); /// 1
lv_obj_set_x(ui_Label5, 0); lv_obj_set_x(ui_Label5, 100);
lv_obj_set_y(ui_Label5, -100); lv_obj_set_y(ui_Label5, 0);
lv_obj_set_align(ui_Label5, LV_ALIGN_CENTER); lv_obj_set_align(ui_Label5, LV_ALIGN_CENTER);
lv_label_set_text(ui_Label5, "Ctrl+X"); lv_label_set_text(ui_Label5, "Ctrl+X");
+10 -8
View File
@@ -4,6 +4,7 @@
// Project name: HeavenlyHeroStar // Project name: HeavenlyHeroStar
#include "../ui.h" #include "../ui.h"
#include "board_config.h"
lv_obj_t * ui_Screen3 = NULL; lv_obj_t * ui_Screen3 = NULL;
lv_obj_t * ui_BtnVolDown = NULL; lv_obj_t * ui_BtnVolDown = NULL;
@@ -23,8 +24,8 @@ static void ui_screen3_create_button(lv_obj_t **btn, lv_obj_t **label, int32_t x
const char *text) const char *text)
{ {
*btn = lv_button_create(ui_Screen3); *btn = lv_button_create(ui_Screen3);
lv_obj_set_width(*btn, 75); lv_obj_set_width(*btn, 90);
lv_obj_set_height(*btn, 90); lv_obj_set_height(*btn, 45);
lv_obj_set_x(*btn, x); lv_obj_set_x(*btn, x);
lv_obj_set_y(*btn, y); lv_obj_set_y(*btn, y);
lv_obj_set_align(*btn, LV_ALIGN_CENTER); lv_obj_set_align(*btn, LV_ALIGN_CENTER);
@@ -100,14 +101,15 @@ void ui_event_BtnNext(lv_event_t * e)
void ui_Screen3_screen_init(void) void ui_Screen3_screen_init(void)
{ {
ui_Screen3 = lv_obj_create(NULL); ui_Screen3 = lv_obj_create(NULL);
lv_obj_set_size(ui_Screen3, BOARD_LCD_LOGIC_H_RES, BOARD_LCD_LOGIC_V_RES);
lv_obj_remove_flag(ui_Screen3, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(ui_Screen3, LV_OBJ_FLAG_SCROLLABLE);
ui_screen3_create_button(&ui_BtnVolDown, &ui_LabelVolDown, -42, -107, "Vol-"); ui_screen3_create_button(&ui_BtnVolDown, &ui_LabelVolDown, -50, -55, "Vol-");
ui_screen3_create_button(&ui_BtnVolUp, &ui_LabelVolUp, 42, -107, "Vol+"); ui_screen3_create_button(&ui_BtnVolUp, &ui_LabelVolUp, 50, -55, "Vol+");
ui_screen3_create_button(&ui_BtnMute, &ui_LabelMute, -42, 0, "Mute"); ui_screen3_create_button(&ui_BtnMute, &ui_LabelMute, -50, 0, "Mute");
ui_screen3_create_button(&ui_BtnPlayPause, &ui_LabelPlayPause, 42, 0, "Play"); ui_screen3_create_button(&ui_BtnPlayPause, &ui_LabelPlayPause, 50, 0, "Play");
ui_screen3_create_button(&ui_BtnPrev, &ui_LabelPrev, -42, 107, "Prev"); ui_screen3_create_button(&ui_BtnPrev, &ui_LabelPrev, -50, 55, "Prev");
ui_screen3_create_button(&ui_BtnNext, &ui_LabelNext, 42, 107, "Next"); ui_screen3_create_button(&ui_BtnNext, &ui_LabelNext, 50, 55, "Next");
lv_obj_add_event_cb(ui_BtnVolDown, ui_event_BtnVolDown, LV_EVENT_ALL, NULL); lv_obj_add_event_cb(ui_BtnVolDown, ui_event_BtnVolDown, LV_EVENT_ALL, NULL);
lv_obj_add_event_cb(ui_BtnVolUp, ui_event_BtnVolUp, LV_EVENT_ALL, NULL); lv_obj_add_event_cb(ui_BtnVolUp, ui_event_BtnVolUp, LV_EVENT_ALL, NULL);
+16
View File
@@ -1,6 +1,7 @@
#include "settings_store.h" #include "settings_store.h"
#include "display.h" #include "display.h"
#include "motion_gesture.h"
#include "esp_log.h" #include "esp_log.h"
#include "nvs.h" #include "nvs.h"
@@ -15,6 +16,8 @@ static const settings_data_t SETTINGS_DEFAULTS = {
.brightness_pct = 70, .brightness_pct = 70,
.volume_pct = 50, .volume_pct = 50,
.mute = false, .mute = false,
.shake_nav_enabled = true,
.shake_sensitivity = SHAKE_SENS_MED,
}; };
static settings_data_t s_data; static settings_data_t s_data;
@@ -48,6 +51,10 @@ void settings_store_load(void)
load_key_u8(handle, "vol", &s_data.volume_pct, SETTINGS_DEFAULTS.volume_pct); load_key_u8(handle, "vol", &s_data.volume_pct, SETTINGS_DEFAULTS.volume_pct);
load_key_u8(handle, "mute", &val, SETTINGS_DEFAULTS.mute); load_key_u8(handle, "mute", &val, SETTINGS_DEFAULTS.mute);
s_data.mute = val != 0; s_data.mute = val != 0;
load_key_u8(handle, "shake_en", &val, SETTINGS_DEFAULTS.shake_nav_enabled);
s_data.shake_nav_enabled = val != 0;
load_key_u8(handle, "shake_sens", &val, SETTINGS_DEFAULTS.shake_sensitivity);
s_data.shake_sensitivity = (shake_sensitivity_t)val;
nvs_close(handle); nvs_close(handle);
ESP_LOGI(TAG, "Settings loaded (bright=%u vol=%u)", s_data.brightness_pct, ESP_LOGI(TAG, "Settings loaded (bright=%u vol=%u)", s_data.brightness_pct,
@@ -83,6 +90,12 @@ esp_err_t settings_store_commit(void)
if (err == ESP_OK) { if (err == ESP_OK) {
err = nvs_set_u8(handle, "mute", s_data.mute ? 1 : 0); err = nvs_set_u8(handle, "mute", s_data.mute ? 1 : 0);
} }
if (err == ESP_OK) {
err = nvs_set_u8(handle, "shake_en", s_data.shake_nav_enabled ? 1 : 0);
}
if (err == ESP_OK) {
err = nvs_set_u8(handle, "shake_sens", (uint8_t)s_data.shake_sensitivity);
}
if (err == ESP_OK) { if (err == ESP_OK) {
err = nvs_commit(handle); err = nvs_commit(handle);
} }
@@ -118,4 +131,7 @@ void settings_store_apply(void)
{ {
uint16_t duty = (uint16_t)((s_data.brightness_pct * 1023U) / 100U); uint16_t duty = (uint16_t)((s_data.brightness_pct * 1023U) / 100U);
display_backlight_set_brightness(duty); display_backlight_set_brightness(duty);
motion_gesture_set_enabled(s_data.shake_nav_enabled);
motion_gesture_set_sensitivity(s_data.shake_sensitivity);
} }
+41
View File
@@ -3,6 +3,8 @@
#include "ui_handlers.h" #include "ui_handlers.h"
static lv_obj_t *s_notif_switch; static lv_obj_t *s_notif_switch;
static lv_obj_t *s_shake_nav_switch;
static lv_obj_t *s_shake_sens_dropdown;
static lv_obj_t *s_brightness_slider; static lv_obj_t *s_brightness_slider;
static lv_obj_t *s_brightness_pct_label; static lv_obj_t *s_brightness_pct_label;
static lv_obj_t *s_volume_slider; static lv_obj_t *s_volume_slider;
@@ -43,6 +45,8 @@ static lv_obj_t *create_settings_body(lv_obj_t *content)
void load_system_settings(lv_obj_t *content, const settings_data_t *data) void load_system_settings(lv_obj_t *content, const settings_data_t *data)
{ {
s_notif_switch = NULL; s_notif_switch = NULL;
s_shake_nav_switch = NULL;
s_shake_sens_dropdown = NULL;
create_status_bar(content, "System Settings"); create_status_bar(content, "System Settings");
lv_obj_t *body = create_settings_body(content); lv_obj_t *body = create_settings_body(content);
@@ -62,6 +66,35 @@ void load_system_settings(lv_obj_t *content, const settings_data_t *data)
LV_EVENT_VALUE_CHANGED, LV_EVENT_VALUE_CHANGED,
(void *)(intptr_t)SETTINGS_FIELD_NOTIFICATION); (void *)(intptr_t)SETTINGS_FIELD_NOTIFICATION);
lv_obj_t *shake_nav_label = lv_label_create(body);
lv_label_set_text(shake_nav_label, "Shake Navigation");
lv_obj_set_pos(shake_nav_label, 4, 40);
lv_obj_set_size(shake_nav_label, 120, 28);
s_shake_nav_switch = lv_switch_create(body);
lv_obj_set_pos(s_shake_nav_switch, 180, 40);
lv_obj_set_size(s_shake_nav_switch, 40, 28);
if (data->shake_nav_enabled) {
lv_obj_add_state(s_shake_nav_switch, LV_STATE_CHECKED);
}
lv_obj_add_event_cb(s_shake_nav_switch, settings_on_value_changed,
LV_EVENT_VALUE_CHANGED,
(void *)(intptr_t)SETTINGS_FIELD_SHAKE_NAV);
lv_obj_t *shake_sens_label = lv_label_create(body);
lv_label_set_text(shake_sens_label, "Shake Sensitivity");
lv_obj_set_pos(shake_sens_label, 4, 76);
lv_obj_set_size(shake_sens_label, 120, 28);
s_shake_sens_dropdown = lv_dropdown_create(body);
lv_obj_set_pos(s_shake_sens_dropdown, 120, 76);
lv_obj_set_size(s_shake_sens_dropdown, 100, 28);
lv_dropdown_set_options(s_shake_sens_dropdown, "Low\nMed\nHigh");
lv_dropdown_set_selected(s_shake_sens_dropdown, data->shake_sensitivity);
lv_obj_add_event_cb(s_shake_sens_dropdown, settings_on_value_changed,
LV_EVENT_VALUE_CHANGED,
(void *)(intptr_t)SETTINGS_FIELD_SHAKE_SENS);
lv_obj_t *reset_btn = lv_button_create(body); lv_obj_t *reset_btn = lv_button_create(body);
lv_obj_set_pos(reset_btn, 4, 112); lv_obj_set_pos(reset_btn, 4, 112);
lv_obj_set_size(reset_btn, 100, 28); lv_obj_set_size(reset_btn, 100, 28);
@@ -170,6 +203,14 @@ void settings_tabs_collect(settings_tab_t tab, settings_data_t *out)
out->notification_enabled = out->notification_enabled =
lv_obj_has_state(s_notif_switch, LV_STATE_CHECKED); lv_obj_has_state(s_notif_switch, LV_STATE_CHECKED);
} }
if (s_shake_nav_switch != NULL) {
out->shake_nav_enabled =
lv_obj_has_state(s_shake_nav_switch, LV_STATE_CHECKED);
}
if (s_shake_sens_dropdown != NULL) {
out->shake_sensitivity =
(shake_sensitivity_t)lv_dropdown_get_selected(s_shake_sens_dropdown);
}
break; break;
case SETTINGS_TAB_DISPLAY: case SETTINGS_TAB_DISPLAY:
if (s_brightness_slider != NULL) { if (s_brightness_slider != NULL) {
+2
View File
@@ -7,6 +7,8 @@ typedef enum {
SETTINGS_FIELD_NOTIFICATION = 0, SETTINGS_FIELD_NOTIFICATION = 0,
SETTINGS_FIELD_VOLUME, SETTINGS_FIELD_VOLUME,
SETTINGS_FIELD_MUTE, SETTINGS_FIELD_MUTE,
SETTINGS_FIELD_SHAKE_NAV,
SETTINGS_FIELD_SHAKE_SENS,
} settings_field_t; } settings_field_t;
void load_system_settings(lv_obj_t *content, const settings_data_t *data); void load_system_settings(lv_obj_t *content, const settings_data_t *data);
+8
View File
@@ -10,9 +10,17 @@ typedef enum {
SETTINGS_TAB_NETWORK, SETTINGS_TAB_NETWORK,
} settings_tab_t; } settings_tab_t;
typedef enum {
SHAKE_SENS_LOW = 0,
SHAKE_SENS_MED,
SHAKE_SENS_HIGH,
} shake_sensitivity_t;
typedef struct { typedef struct {
bool notification_enabled; bool notification_enabled;
uint8_t brightness_pct; uint8_t brightness_pct;
uint8_t volume_pct; uint8_t volume_pct;
bool mute; bool mute;
bool shake_nav_enabled;
shake_sensitivity_t shake_sensitivity;
} settings_data_t; } settings_data_t;