HiFi player project, first commit.
This commit is contained in:
commit
4126ea13a1
49 files changed
+6333
No files matched your search
+330
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
****************************************************************************************************
|
||||
* @file app.c
|
||||
* @author HiFi Player Project
|
||||
* @version V1.2
|
||||
* @date 2026-01-01
|
||||
* @brief 应用管理器 + 全局按键服务
|
||||
*
|
||||
* 模块职责:
|
||||
* 1. 唯一持有 XL9555 按键的扫描权(其它模块不再直接读按键,避免重复扫描/漏键)。
|
||||
* 2. 按键去抖 + 区分短按/长按,统一投递到事件队列(消费者是 ui.c)。
|
||||
* 3. 维护当前应用模式、主界面应用表与选择项,并提供“返回 / 切换应用”接口。
|
||||
****************************************************************************************************
|
||||
*/
|
||||
|
||||
#include "app.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
#include "xl9555.h"
|
||||
|
||||
#define TAG "app"
|
||||
|
||||
/* ---------------- 按键服务参数 ---------------- */
|
||||
#define KEY_SCAN_PERIOD_MS 10 /* 扫描周期 */
|
||||
#define KEY_DEBOUNCE_COUNT 3 /* 连续 3 次一致才确认(约 30ms) */
|
||||
#define KEY_LONG_PRESS_MS 700 /* 长按阈值 */
|
||||
#define KEY_TASK_STACK (3 * 1024)
|
||||
#define KEY_TASK_PRIO 5 /* 低于播放器(6),高于 LVGL(4) */
|
||||
#define EVENT_QUEUE_LEN 16
|
||||
|
||||
/* ---------------- 主界面应用表(新增应用只需在这里加一条) ---------------- */
|
||||
static const app_entry_t s_apps[] = {
|
||||
{
|
||||
.icon = LV_SYMBOL_AUDIO, /* 音符:音乐播放器 */
|
||||
.title = "MUSIC",
|
||||
.subtitle = "WAV files in SD /MUSIC",
|
||||
.mode = APP_MODE_PLAYER,
|
||||
.enabled = true,
|
||||
.color_from = 0x8E5BFF,
|
||||
.color_to = 0x3FB6F5,
|
||||
},
|
||||
{
|
||||
.icon = LV_SYMBOL_DIRECTORY, /* 文件夹:SD 卡文件浏览 */
|
||||
.title = "FILES",
|
||||
.subtitle = "Browse files on SD card",
|
||||
.mode = APP_MODE_NONE,
|
||||
.enabled = false,
|
||||
.color_from = 0x31C27C,
|
||||
.color_to = 0x1E9C6B,
|
||||
},
|
||||
{
|
||||
.icon = LV_SYMBOL_SETTINGS, /* 齿轮:设置 */
|
||||
.title = "SETTINGS",
|
||||
.subtitle = "Display, audio and keys",
|
||||
.mode = APP_MODE_NONE,
|
||||
.enabled = false,
|
||||
.color_from = 0x9AA3B5,
|
||||
.color_to = 0x6A7183,
|
||||
},
|
||||
{
|
||||
.icon = LV_SYMBOL_LIST, /* 列表:关于 */
|
||||
.title = "ABOUT",
|
||||
.subtitle = "HiFi Player V1.2",
|
||||
.mode = APP_MODE_NONE,
|
||||
.enabled = false,
|
||||
.color_from = 0xFFB020,
|
||||
.color_to = 0xF07C18,
|
||||
},
|
||||
};
|
||||
|
||||
#define APP_COUNT ((int)(sizeof(s_apps) / sizeof(s_apps[0])))
|
||||
|
||||
/* ---------------- 内部状态 ---------------- */
|
||||
typedef struct {
|
||||
uint8_t last_raw; /* 上次原始电平(1=按下) */
|
||||
uint8_t stable; /* 已确认的稳定状态 */
|
||||
uint8_t counter; /* 连续一致计数 */
|
||||
TickType_t press_tick; /* 按下时刻 */
|
||||
bool long_sent; /* 本次按住是否已上报长按 */
|
||||
} key_state_t;
|
||||
|
||||
static const uint16_t s_key_mask[APP_KEY_NUM] = { KEY0_IO, KEY1_IO, KEY2_IO, KEY3_IO };
|
||||
static key_state_t s_keys[APP_KEY_NUM];
|
||||
|
||||
static QueueHandle_t s_event_queue = NULL;
|
||||
static app_mode_t s_mode = APP_MODE_HOME;
|
||||
static int s_selection = 0;
|
||||
|
||||
/* ---------------- 按键事件投递 ---------------- */
|
||||
static void key_post(uint8_t key, uint8_t type)
|
||||
{
|
||||
app_event_t ev = { .key = key, .type = type };
|
||||
|
||||
if (s_event_queue != NULL)
|
||||
{
|
||||
/* 队列满时丢弃新事件,绝不阻塞按键任务 */
|
||||
(void)xQueueSend(s_event_queue, &ev, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按键扫描任务(非阻塞去抖 + 长短按识别)
|
||||
*/
|
||||
static void key_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
memset(s_keys, 0, sizeof(s_keys));
|
||||
|
||||
while (1)
|
||||
{
|
||||
uint8_t data[2] = {0, 0};
|
||||
|
||||
if (xl9555_read_byte(data, 2) == ESP_OK)
|
||||
{
|
||||
uint16_t level = (uint16_t)(((uint16_t)data[1] << 8) | data[0]);
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
|
||||
for (int i = 0; i < APP_KEY_NUM; i++)
|
||||
{
|
||||
key_state_t *k = &s_keys[i];
|
||||
uint8_t raw = ((level & s_key_mask[i]) == 0) ? 1 : 0; /* 低电平有效 */
|
||||
|
||||
/* --- 1. 去抖计数 --- */
|
||||
if (raw != k->last_raw)
|
||||
{
|
||||
k->last_raw = raw;
|
||||
k->counter = 0;
|
||||
}
|
||||
else if (k->counter < KEY_DEBOUNCE_COUNT)
|
||||
{
|
||||
k->counter++;
|
||||
}
|
||||
|
||||
if (k->counter < KEY_DEBOUNCE_COUNT)
|
||||
{
|
||||
continue; /* 尚未稳定 */
|
||||
}
|
||||
|
||||
/* --- 2. 状态变化 --- */
|
||||
if (raw != k->stable)
|
||||
{
|
||||
k->stable = raw;
|
||||
|
||||
if (raw) /* 刚按下:记录时刻 */
|
||||
{
|
||||
k->press_tick = now;
|
||||
k->long_sent = false;
|
||||
}
|
||||
else if (!k->long_sent) /* 松开且从未上报长按 → 短按 */
|
||||
{
|
||||
key_post((uint8_t)i, APP_KEY_SHORT);
|
||||
}
|
||||
}
|
||||
/* --- 3. 按住不放 → 长按 --- */
|
||||
else if (raw && !k->long_sent &&
|
||||
((now - k->press_tick) >= pdMS_TO_TICKS(KEY_LONG_PRESS_MS)))
|
||||
{
|
||||
k->long_sent = true;
|
||||
key_post((uint8_t)i, APP_KEY_LONG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(KEY_SCAN_PERIOD_MS));
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 对外接口 ---------------- */
|
||||
void app_start(void)
|
||||
{
|
||||
if (s_event_queue == NULL)
|
||||
{
|
||||
s_event_queue = xQueueCreate(EVENT_QUEUE_LEN, sizeof(app_event_t));
|
||||
if (s_event_queue == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "create event queue failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
s_mode = APP_MODE_HOME;
|
||||
s_selection = 0;
|
||||
|
||||
if (xTaskCreate(key_task, "key_svc", KEY_TASK_STACK, NULL, KEY_TASK_PRIO, NULL) != pdPASS)
|
||||
{
|
||||
ESP_LOGE(TAG, "create key task failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(TAG, "app manager started, %d app(s) in launcher", APP_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
app_mode_t app_get_mode(void)
|
||||
{
|
||||
return s_mode;
|
||||
}
|
||||
|
||||
void app_set_mode(app_mode_t mode)
|
||||
{
|
||||
if (mode == s_mode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_mode = mode;
|
||||
|
||||
/* 丢弃切换过程中的残留事件,避免“上一个界面的按键”触发新界面的动作 */
|
||||
app_event_flush();
|
||||
|
||||
ESP_LOGI(TAG, "switch to mode %d", (int)mode);
|
||||
}
|
||||
|
||||
void app_back(void)
|
||||
{
|
||||
if (s_mode != APP_MODE_HOME)
|
||||
{
|
||||
app_set_mode(APP_MODE_HOME);
|
||||
}
|
||||
}
|
||||
|
||||
bool app_switch_next(void)
|
||||
{
|
||||
int count = 0;
|
||||
const app_entry_t *apps = app_get_entries(&count);
|
||||
|
||||
if (count <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 找到当前模式的表项下标(找不到则视为 -1,从第 1 个开始找) */
|
||||
int start = -1;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (apps[i].mode == s_mode)
|
||||
{
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 向后找下一个“可用且已实现”的应用 */
|
||||
for (int step = 1; step <= count; step++)
|
||||
{
|
||||
int idx = (start + step + count) % count;
|
||||
|
||||
if (apps[idx].enabled && (apps[idx].mode < APP_MODE_NUM))
|
||||
{
|
||||
s_selection = idx;
|
||||
app_set_mode(apps[idx].mode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const app_entry_t *app_get_entries(int *count)
|
||||
{
|
||||
if (count != NULL)
|
||||
{
|
||||
*count = APP_COUNT;
|
||||
}
|
||||
|
||||
return s_apps;
|
||||
}
|
||||
|
||||
int app_get_selection(void)
|
||||
{
|
||||
if ((s_selection < 0) || (s_selection >= APP_COUNT))
|
||||
{
|
||||
s_selection = 0;
|
||||
}
|
||||
|
||||
return s_selection;
|
||||
}
|
||||
|
||||
void app_select_move(int delta)
|
||||
{
|
||||
if (APP_COUNT <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_selection = (app_get_selection() + delta) % APP_COUNT;
|
||||
if (s_selection < 0)
|
||||
{
|
||||
s_selection += APP_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
bool app_event_poll(app_event_t *ev)
|
||||
{
|
||||
if ((s_event_queue == NULL) || (ev == NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return (xQueueReceive(s_event_queue, ev, 0) == pdTRUE);
|
||||
}
|
||||
|
||||
bool app_event_wait(app_event_t *ev, uint32_t timeout_ms)
|
||||
{
|
||||
if ((s_event_queue == NULL) || (ev == NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TickType_t ticks = (timeout_ms == 0) ? 0 : pdMS_TO_TICKS(timeout_ms);
|
||||
|
||||
return (xQueueReceive(s_event_queue, ev, ticks) == pdTRUE);
|
||||
}
|
||||
|
||||
void app_event_flush(void)
|
||||
{
|
||||
if (s_event_queue != NULL)
|
||||
{
|
||||
(void)xQueueReset(s_event_queue);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user