Compare commits

...

10 Commits

Author SHA1 Message Date
yuquanjun 42534a28b8 Add RTT touch diagnostics: tp status/live commands and live event monitor.
Harden reset/recover when RTT is missing, and stream touch/UI events only on press for field debugging.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 21:59:13 +08:00
yuquanjun 33f48d9171 Improve mode-select focus UX and related touch hit feedback.
Redraw only changed mode buttons, restore focus-then-enter, and keep page transitions from false bottom-tab hits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 21:59:12 +08:00
yuquanjun d48e3fa63a Stabilize non-Mixer click handling and page-change touch suppress.
Report clicks on release for navigation pages, suppress residual presses after UI switch, and log touch task edges.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 21:59:12 +08:00
yuquanjun 5a9f7c81ea fix XPT2046 sampling to IRQ-only with stable screen mapping.
Reject idle ADC noise (y stuck at full scale) that caused ghost clicks; keep Y-mirror mapping for mode-select.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 21:59:12 +08:00
yuquanjun 956eeee1a5 Add RTT sys reset command and remote reboot script with CPU resume.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 12:31:53 +08:00
yuquanjun f6ae34892f Document verified J-Link log dump workflow and harden flash script.
Kill stale cspybat before download; note successful RTT log export sample in LOG_README.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 11:45:36 +08:00
yuquanjun 7fea921727 Add J-Link RTT logging module with RAM ring buffer and dump tool.
Log boot/power/touch/key/UI/NVM events; export via rtt_log_dump.py for field diagnostics.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 11:24:10 +08:00
yuquanjun b8eaf2c808 Fix mode-select focus and touch pipeline; add return-to-home gesture.
Preserve focus across redraws, improve XPT2046 sampling, and log touch coords via RTT for ongoing debug.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 11:13:01 +08:00
yuquanjun b996c8876e Use designer 0831 PNG bitmaps for mode-select labels.
Replace on-device Chinese glyphs with MiSans icon+text row assets so the trial format matches the designer's export.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 09:41:13 +08:00
yuquanjun ba31d91aef Fix mode-select touch area IDs and keep touch thread from blocking on LCD dump.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 08:21:58 +08:00
30 changed files with 5517 additions and 380 deletions

238
APP/app_log.c Normal file
View File

@ -0,0 +1,238 @@
#include "includes.h"
#include "SEGGER_RTT.h"
#include <stdarg.h>
#if APP_LOG_ENABLE
static char s_slots[APP_LOG_SLOT_COUNT][APP_LOG_SLOT_SIZE];
static uint16_t s_head;
static uint16_t s_count;
static uint32_t s_overflow;
static uint32_t s_boot_count;
static char s_ui_page[16] = "boot";
static char s_debug_level = 'D';
static char s_cmd_buf[48];
static uint8_t s_cmd_len;
static uint32_t app_log_ms(void)
{
return (uint32_t)(rt_tick_get() * 1000U / RT_TICK_PER_SECOND);
}
static void app_log_slot_store(const char *line)
{
uint16_t idx = s_head;
strncpy(s_slots[idx], line, APP_LOG_SLOT_SIZE - 1U);
s_slots[idx][APP_LOG_SLOT_SIZE - 1U] = '\0';
s_head = (uint16_t)((s_head + 1U) % APP_LOG_SLOT_COUNT);
if (s_count < APP_LOG_SLOT_COUNT) {
s_count++;
} else {
s_overflow++;
}
}
void app_log_set_ui_page(const char *name)
{
if (name == NULL) {
return;
}
strncpy(s_ui_page, name, sizeof(s_ui_page) - 1U);
s_ui_page[sizeof(s_ui_page) - 1U] = '\0';
}
void app_log_write(char level, const char *cat, const char *fmt, ...)
{
char line[APP_LOG_SLOT_SIZE];
va_list ap;
int n;
if (level == 'D' && s_debug_level != 'D') {
return;
}
n = snprintf(line, sizeof(line), "[%05u][%c][%-4s] ",
(unsigned)app_log_ms(), level, cat);
if (n < 0) {
return;
}
if ((size_t)n >= sizeof(line)) {
n = (int)sizeof(line) - 1;
}
va_start(ap, fmt);
vsnprintf(line + n, sizeof(line) - (size_t)n, fmt, ap);
va_end(ap);
app_log_slot_store(line);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "\n");
}
static void app_log_dump_ram(void)
{
uint16_t start;
uint16_t i;
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "LOG_DUMP_BEGIN\n");
{
char meta[APP_LOG_SLOT_SIZE];
snprintf(meta, sizeof(meta),
"meta boot=%u lines=%u overflow=%u ui=%s",
(unsigned)s_boot_count,
(unsigned)s_count,
(unsigned)s_overflow,
s_ui_page);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, meta);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "\n");
}
if (s_count == 0U) {
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "LOG_DUMP_END\n");
return;
}
start = (s_count < APP_LOG_SLOT_COUNT)
? 0U
: s_head;
for (i = 0; i < s_count; i++) {
uint16_t idx = (uint16_t)((start + i) % APP_LOG_SLOT_COUNT);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, s_slots[idx]);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "\n");
}
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "LOG_DUMP_END\n");
}
static void app_log_clear_ram(void)
{
s_head = 0U;
s_count = 0U;
s_overflow = 0U;
memset(s_slots, 0, sizeof(s_slots));
LOG_I("LOG", "ram cleared");
}
static void app_log_print_status(void)
{
char line[APP_LOG_SLOT_SIZE];
snprintf(line, sizeof(line),
"LOG_STATUS boot=%u lines=%u overflow=%u level=%c ui=%s tick=%u",
(unsigned)s_boot_count,
(unsigned)s_count,
(unsigned)s_overflow,
s_debug_level,
s_ui_page,
(unsigned)app_log_ms());
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, line);
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "\n");
}
static void app_log_sys_reset(void)
{
LOG_I("SYS", "reset via RTT");
SEGGER_RTT_WriteString(APP_LOG_RTT_CHANNEL, "SYS_RESET_OK\n");
rt_thread_mdelay(20);
__disable_irq();
nvic_system_reset();
}
uint8_t app_log_try_command(const char *cmd)
{
if (cmd == NULL || cmd[0] == '\0') {
return 0U;
}
if (strncmp(cmd, "log dump", 8) == 0) {
app_log_dump_ram();
return 1U;
}
if (strncmp(cmd, "log clear", 9) == 0) {
app_log_clear_ram();
return 1U;
}
if (strncmp(cmd, "log status", 10) == 0) {
app_log_print_status();
return 1U;
}
if (strncmp(cmd, "log level ", 10) == 0 && cmd[10] != '\0') {
s_debug_level = cmd[10];
LOG_I("LOG", "level set %c", s_debug_level);
return 1U;
}
if (strncmp(cmd, "tp status", 9) == 0) {
TP_LogStatus();
return 1U;
}
if (strncmp(cmd, "tp live off", 11) == 0) {
TP_SetLiveMode(0);
return 1U;
}
if (strncmp(cmd, "tp live", 7) == 0) {
TP_SetLiveMode(1);
return 1U;
}
if (strncmp(cmd, "sys reset", 9) == 0) {
app_log_sys_reset();
return 1U;
}
return 0U;
}
static void app_log_feed_char(char c)
{
if (c == '\r' || c == '\n' || c == '\0') {
if (s_cmd_len > 0U) {
s_cmd_buf[s_cmd_len] = '\0';
(void)app_log_try_command(s_cmd_buf);
}
s_cmd_len = 0U;
s_cmd_buf[0] = '\0';
return;
}
if (s_cmd_len + 1U >= sizeof(s_cmd_buf)) {
s_cmd_len = 0U;
}
s_cmd_buf[s_cmd_len++] = c;
s_cmd_buf[s_cmd_len] = '\0';
if (strstr(s_cmd_buf, "sys reset") != NULL) {
s_cmd_len = 0U;
s_cmd_buf[0] = '\0';
(void)app_log_try_command("sys reset");
}
}
void app_log_poll(void)
{
char rx[16];
unsigned read_len;
unsigned i;
read_len = SEGGER_RTT_Read(APP_LOG_RTT_CHANNEL, rx, sizeof(rx));
if (read_len == 0U) {
return;
}
for (i = 0; i < read_len; i++) {
app_log_feed_char(rx[i]);
}
}
void app_log_init(void)
{
s_boot_count++;
LOG_I("BOOT", "app_log init boot=%u", (unsigned)s_boot_count);
}
#else
void app_log_init(void) {}
void app_log_write(char level, const char *cat, const char *fmt, ...) { (void)level; (void)cat; (void)fmt; }
void app_log_poll(void) {}
uint8_t app_log_try_command(const char *cmd) { (void)cmd; return 0U; }
void app_log_set_ui_page(const char *name) { (void)name; }
#endif /* APP_LOG_ENABLE */

70
APP/app_log.h Normal file
View File

@ -0,0 +1,70 @@
#ifndef __APP_LOG_H__
#define __APP_LOG_H__
#include <stdint.h>
#ifndef APP_LOG_ENABLE
#define APP_LOG_ENABLE 1
#endif
#ifndef APP_LOG_DEBUG
#define APP_LOG_DEBUG 1
#endif
#ifndef APP_LOG_RAM_SIZE
#define APP_LOG_RAM_SIZE (4U * 1024U)
#endif
#ifndef APP_LOG_SLOT_SIZE
#define APP_LOG_SLOT_SIZE 128U
#endif
#ifndef APP_LOG_RTT_CHANNEL
#define APP_LOG_RTT_CHANNEL 0
#endif
#ifndef APP_LOG_TP_VERBOSE
#define APP_LOG_TP_VERBOSE 1
#endif
#define APP_LOG_SLOT_COUNT (APP_LOG_RAM_SIZE / APP_LOG_SLOT_SIZE)
void app_log_init(void);
void app_log_write(char level, const char *cat, const char *fmt, ...);
void app_log_poll(void);
uint8_t app_log_try_command(const char *cmd);
void app_log_set_ui_page(const char *name);
#if APP_LOG_ENABLE
#define LOG_I(cat, fmt, ...) app_log_write('I', (cat), (fmt), ##__VA_ARGS__)
#define LOG_W(cat, fmt, ...) app_log_write('W', (cat), (fmt), ##__VA_ARGS__)
#define LOG_E(cat, fmt, ...) app_log_write('E', (cat), (fmt), ##__VA_ARGS__)
#if APP_LOG_DEBUG
#define LOG_D(cat, fmt, ...) app_log_write('D', (cat), (fmt), ##__VA_ARGS__)
#else
#define LOG_D(cat, fmt, ...) ((void)0)
#endif
#if APP_LOG_TP_VERBOSE
#define LOG_TP(fmt, ...) LOG_D("TP", fmt, ##__VA_ARGS__)
#else
#define LOG_TP(fmt, ...) ((void)0)
#endif
/* 触摸按下:始终 INFO,便于 log dump 排查硬件/坐标 */
#define LOG_TPI(fmt, ...) LOG_I("TP", fmt, ##__VA_ARGS__)
#else
#define LOG_I(cat, fmt, ...) ((void)0)
#define LOG_W(cat, fmt, ...) ((void)0)
#define LOG_E(cat, fmt, ...) ((void)0)
#define LOG_D(cat, fmt, ...) ((void)0)
#define LOG_TP(fmt, ...) ((void)0)
#define LOG_TPI(fmt, ...) ((void)0)
#endif
#endif /* __APP_LOG_H__ */

View File

@ -78,6 +78,7 @@ uint8_t app_tm1617_scan_key(void)
if (key != key_last)
{
key_last = key;
LOG_I("KEY", "tm1617 key=%u", (unsigned)key);
TM1617_Handle(key);
return key;
}

View File

@ -31,6 +31,7 @@ uint8_t app_tm1629_Scan_Key(void)
if(key != key_last)
{
key_last = key;
LOG_I("KEY", "tm1629 key=%u", (unsigned)key);
TM1629_Handle(key);
return key;
}

View File

@ -1,63 +1,54 @@
#include "includes.h"
/* ============================================================================
* app_touch 实现
*
* 状态机(与原 Touch_Process 一致):
* IDLE → PRESS_WAIT_STABLE → PRESS_STABLE → SLIDING
*
* 时序参数:
* 稳定判定 20ms (TOUCH_STABLE_TIME)
* 滑动阈值 10px (TOUCH_SLIDE_THRESH)
* 滑动步进 5px (TOUCH_SLIDE_STEP)
* 长按触发 1000ms (TOUCH_LONG_PRESS_TIME)
* 长按重复 120ms (TOUCH_REPEAT_INTERVAL)
* ==========================================================================*/
/* ?????????????????????????????? */
/* ==================== 状态机枚举 ==================== */
typedef enum {
TOUCH_IDLE, /* 空闲 */
TOUCH_PRESS_WAIT_STABLE, /* 按下,等待稳定判定 */
TOUCH_PRESS_STABLE, /* 稳定按下(可长按) */
TOUCH_SLIDING /* 滑动中 */
TOUCH_IDLE,
TOUCH_PRESS_WAIT_STABLE,
TOUCH_PRESS_STABLE,
TOUCH_SLIDING
} touch_state_t;
/* ==================== 时序参数 ==================== */
#define TOUCH_STABLE_TIME 20 /* 按下稳定判定时间 ms */
#define TOUCH_SLIDE_THRESH 10 /* 进入滑动的最小位移 px */
#define TOUCH_SLIDE_STEP 5 /* 滑动事件最小步进 px */
#define TOUCH_LONG_PRESS_TIME 1000 /* 长按触发 ms */
#define TOUCH_REPEAT_INTERVAL 120 /* 长按重复间隔 ms */
#define TOUCH_STABLE_TIME 8
#define TOUCH_SLIDE_THRESH 12
#define TOUCH_SLIDE_STEP 5
#define TOUCH_LONG_PRESS_TIME 800
#define TOUCH_REPEAT_INTERVAL 120
/* ==================== 状态变量 ==================== */
static touch_state_t touch_state = TOUCH_IDLE;
static uint16_t down_x = 0;
static uint16_t down_y = 0;
static uint32_t touch_tick = 0;
static uint32_t repeat_tick = 0;
static uint8_t long_press_fired = 0;
static uint8_t click_sent = 0;
static uint16_t last_slide_x = 0;
static uint16_t last_slide_y = 0;
static uint32_t suppress_until = 0;
/* 取绝对值(避免引入 math.h) */
static inline uint16_t abs_diff(int16_t a, int16_t b)
{
int16_t d = a - b;
return (d < 0) ? (uint16_t)(-d) : (uint16_t)d;
}
/* ==================== 重置 ==================== */
void app_touch_reset(void)
{
touch_state = TOUCH_IDLE;
down_x = down_y = 0;
touch_tick = repeat_tick = 0;
long_press_fired = 0;
click_sent = 0;
last_slide_x = last_slide_y = 0;
}
/* ==================== 手势识别 ==================== */
void app_touch_suppress(uint32_t ms)
{
uint32_t now = rt_tick_get();
uint32_t ticks = (ms * RT_TICK_PER_SECOND + 999U) / 1000U;
suppress_until = now + ticks;
app_touch_reset();
}
app_touch_result_t app_touch_process(uint16_t x, uint16_t y, uint8_t pressed)
{
@ -68,22 +59,38 @@ app_touch_result_t app_touch_process(uint16_t x, uint16_t y, uint8_t pressed)
uint32_t now = rt_tick_get();
/* ---------- 抬起处理 ---------- */
/* 切页抑制窗口内丢弃按下,避免误触底栏设置 */
if ((int32_t)(now - suppress_until) < 0) {
if (!pressed) {
app_touch_reset();
}
return r;
}
if (!pressed) {
/* 若曾稳定按下且未触发长按 → 释放(可当作 TAP) */
if (touch_state == TOUCH_PRESS_STABLE && !long_press_fired) {
r.gesture = GESTURE_RELEASE;
r.x = down_x; r.y = down_y; /* 用按下点作为释放坐标 */
if (!long_press_fired && !click_sent &&
(touch_state == TOUCH_PRESS_WAIT_STABLE ||
touch_state == TOUCH_PRESS_STABLE ||
touch_state == TOUCH_SLIDING))
{
/* 导航页:抬起时发一次点击(坐标更稳,避免按下瞬间误触底栏) */
if (CurrUIProcress != UI_Mixer_Process) {
r.gesture = GESTURE_RELEASE;
r.x = down_x;
r.y = down_y;
LOG_TPI("release x=%u y=%u -> MSG", (unsigned)down_x, (unsigned)down_y);
MainTask_Sendmsg(MSG_ID_TOUCH, 1, down_x, down_y);
}
}
touch_state = TOUCH_IDLE;
down_x = down_y = 0;
last_slide_x = last_slide_y = 0;
touch_tick = repeat_tick = 0;
long_press_fired = 0;
click_sent = 0;
return r;
}
/* ---------- 按下处理 ---------- */
switch (touch_state)
{
case TOUCH_IDLE:
@ -91,44 +98,49 @@ app_touch_result_t app_touch_process(uint16_t x, uint16_t y, uint8_t pressed)
down_y = y;
touch_tick = now;
long_press_fired = 0;
click_sent = 0;
repeat_tick = 0;
touch_state = TOUCH_PRESS_WAIT_STABLE;
/* Mixer 推子:按下即报;其它页等抬起再报,防止进页后残留按下误触设置 */
if (CurrUIProcress == UI_Mixer_Process) {
click_sent = 1;
LOG_TPI("press x=%u y=%u -> MSG", (unsigned)down_x, (unsigned)down_y);
MainTask_Sendmsg(MSG_ID_TOUCH, 1, down_x, down_y);
} else {
LOG_TPI("press down x=%u y=%u (wait release)", (unsigned)down_x, (unsigned)down_y);
}
break;
case TOUCH_PRESS_WAIT_STABLE:
if ((now - touch_tick) >= TOUCH_STABLE_TIME) {
/* 位移小 → 进入稳定 */
if (abs_diff(x, down_x) < 8 && abs_diff(y, down_y) < 8) {
if (abs_diff(x, down_x) < 24 && abs_diff(y, down_y) < 24) {
/* 用稳定后的坐标替换首采样噪声 */
down_x = x;
down_y = y;
touch_state = TOUCH_PRESS_STABLE;
last_slide_x = down_x;
last_slide_y = down_y;
touch_tick = now;
MainTask_Sendmsg(MSG_ID_TOUCH, 1, down_x, down_y);
} else {
/* 位移大 → 视为误触,回到空闲 */
touch_state = TOUCH_IDLE;
down_x = x;
down_y = y;
touch_tick = now;
}
}
break;
case TOUCH_PRESS_STABLE:
{
/* ----- 长按检测 ----- */
if (!long_press_fired && (now - touch_tick) >= TOUCH_LONG_PRESS_TIME) {
long_press_fired = 1;
repeat_tick = now;
r.gesture = GESTURE_LONG_PRESS;
LOG_TPI("long_press x=%u y=%u", (unsigned)down_x, (unsigned)down_y);
MainTask_Sendmsg(MSG_ID_TOUCH_LONG_REPEAT, 1, down_x, down_y);
return r;
}
/* ----- 长按重复 ----- */
if (long_press_fired && (now - repeat_tick) >= TOUCH_REPEAT_INTERVAL) {
repeat_tick = now;
r.gesture = GESTURE_REPEAT;
MainTask_Sendmsg(MSG_ID_TOUCH_LONG_REPEAT, 1, down_x, down_y);
return r;
}
/* ----- 位移进入滑动 ----- */
if (abs_diff(x, down_x) >= TOUCH_SLIDE_THRESH ||
abs_diff(y, down_y) >= TOUCH_SLIDE_THRESH) {
touch_state = TOUCH_SLIDING;
@ -136,28 +148,27 @@ app_touch_result_t app_touch_process(uint16_t x, uint16_t y, uint8_t pressed)
last_slide_y = y;
}
break;
}
case TOUCH_SLIDING:
{
/* 位移超过步进 → 产生拖动事件 */
if (abs_diff(x, last_slide_x) >= TOUCH_SLIDE_STEP ||
abs_diff(y, last_slide_y) >= TOUCH_SLIDE_STEP) {
/* 仅 Mixer 需要拖动连续坐标;其它页拖动会误触底栏设置 Tab */
if (CurrUIProcress == UI_Mixer_Process &&
(abs_diff(x, last_slide_x) >= TOUCH_SLIDE_STEP ||
abs_diff(y, last_slide_y) >= TOUCH_SLIDE_STEP)) {
r.gesture = GESTURE_DRAG;
r.down_x = last_slide_x; /* 记录上次位置,便于消费端算增量 */
r.down_x = last_slide_x;
r.down_y = last_slide_y;
LOG_TPI("drag x=%u y=%u", (unsigned)x, (unsigned)y);
MainTask_Sendmsg(MSG_ID_TOUCH, 1, x, y);
last_slide_x = x;
last_slide_y = y;
return r;
}
break;
}
default:
touch_state = TOUCH_IDLE;
break;
}
return r; /* 无手势 */
return r;
}

View File

@ -25,6 +25,9 @@ typedef struct {
/* 重置状态机(页面切换/开机时可选调用) */
void app_touch_reset(void);
/* 切页后短暂忽略触摸,避免手指未抬起/ADC 抖动误触底栏设置 Tab */
void app_touch_suppress(uint32_t ms);
/* 核心:喂一个采样点,产出手势。
* 由 touch_task 每 20ms 调用一次。
* 有手势返回对应 gesture,无手势返回 GESTURE_NONE。 */

View File

@ -52,37 +52,33 @@ Fader_t faders[4] = {
const Touch_AreaTypeDef Touch_Areas[] =
{
// Xmin, Xmax, Ymin, Ymax, flag,value ID, action
{ 0, 60, 25, 90, -1, 0, GUI_TRANSPOSE, NULL },
{ 60, 120, 25, 90, 1, 0, GUI_TRANSPOSE, NULL },
/* Xmin,Xmax,Ymin,Ymax, Flag, Value, ID, action — 坐标相对 240x320 */
{ 0, 60, 25, 95, -1, 0, GUI_TRANSPOSE, NULL },
{ 60, 120, 25, 95, 1, 0, GUI_TRANSPOSE, NULL },
{ 125, 180, 25, 90, -1, 0, GUI_SPEED, NULL },
{ 180, 240, 25, 90, 1, 0, GUI_SPEED, NULL },
{ 125, 180, 25, 95, -1, 0, GUI_SPEED, NULL },
{ 180, 239, 25, 95, 1, 0, GUI_SPEED, NULL },
{ 0, 80, 90, 120, 0, 0, GUI_AUTOBAND_SW, NULL },
{ 90, 170, 90, 120, 0, 1, GUI_AUTOBAND_SW, NULL },
{ 80, 140, 0, 30, 0, 0, GUI_BL_SW, NULL },
{ 0, 85, 95, 130, 0, 0, GUI_AUTOBAND_SW, NULL },
{ 90, 175, 95, 130, 0, 1, GUI_AUTOBAND_SW, NULL },
{ 0, 120, 120, 150, -1, 0, GUI_MODE_PARAM, NULL },
{120, 240, 120, 150, 1, 0, GUI_MODE_PARAM, NULL },
{ 0, 120, 130, 165, -1, 0, GUI_MODE_PARAM, NULL },
{ 120, 239, 130, 165, 1, 0, GUI_MODE_PARAM, NULL },
{ 10, 120, 125, 150, -1, 0, GUI_MODE_PARAM, NULL },
{ 80, 140, 0, 30, 0, 0, GUI_MODE_PARAM, NULL },
{ 0, 120, 165, 215, -1, 0, GUI_TIMBRE_SELECT, NULL },
{ 120, 239, 165, 215, 1, 0, GUI_TIMBRE_SELECT, NULL },
{ 0, 117, 155, 200, -1, 0, GUI_TIMBRE_SELECT, NULL },
{ 120, 240, 155, 200, 1, 0, GUI_TIMBRE_SELECT, NULL },
{ 10, 50, 275, 340, 0,2, GUI_TAB_INDEX, NULL },
{ 70, 130, 275, 340, 0,0, GUI_TAB_INDEX, NULL },
{ 130, 170, 275, 340, 0,1, GUI_TAB_INDEX, NULL },
{ 190, 230, 275, 340, 0,3, GUI_TAB_INDEX, NULL },
{ 10, 60, 240, 320, 0,0, GUI_PLAY_SECTION, NULL },
{ 60, 110, 240, 320, 0,1, GUI_PLAY_SECTION, NULL },
{ 110, 170, 240, 320, 0,2, GUI_PLAY_SECTION, NULL },
{ 170, 220, 240, 320, 0,3, GUI_PLAY_SECTION, NULL },
/* 演奏段落 1~4 */
{ 10, 60, 220, 270, 0, 0, GUI_PLAY_SECTION, NULL },
{ 60, 110, 220, 270, 0, 1, GUI_PLAY_SECTION, NULL },
{ 110, 165, 220, 270, 0, 2, GUI_PLAY_SECTION, NULL },
{ 165, 220, 220, 270, 0, 3, GUI_PLAY_SECTION, NULL },
/* 底栏 TAB:万能/普通/专业/设置(Value=目标 TAB) */
{ 0, 60, 275, 319, 0, 0, GUI_TAB_INDEX, NULL },
{ 60, 120, 275, 319, 0, 2, GUI_TAB_INDEX, NULL },
{ 120, 180, 275, 319, 0, 1, GUI_TAB_INDEX, NULL },
{ 180, 239, 275, 319, 0, 3, GUI_TAB_INDEX, NULL },
};
int16_t MIC_VOL_MAP[10]= {-9000,-7000,-5000,-3000,-1000,1000,3000,5000,7000,9000};
@ -100,12 +96,22 @@ bool AutoCloseFlag = true;
void MainTask_Sendmsg(uint16_t ID, uint16_t ID2, uint16_t HiByte, uint16_t LoByte)
{
DisplayTaskMessage_Type msg;
rt_err_t ret;
msg.MessageType = ID; /* 业务消息 ID(MSG_ID_xxx) */
msg.ID = ID2;
msg.HiByte = HiByte;
msg.LoByte = LoByte;
rt_mq_send(MainTask_msg, &msg, sizeof(DisplayTaskMessage_Type));
ret = rt_mq_send(MainTask_msg, &msg, sizeof(DisplayTaskMessage_Type));
if (ret != RT_EOK) {
LOG_E("MSG", "send fail id=%u ret=%d", (unsigned)ID, (int)ret);
} else if (ID == MSG_ID_TOUCH || ID == MSG_ID_TOUCH_LONG_REPEAT ||
ID == MSG_ID_POWER_ON || ID == MSG_ID_POWER_OFF ||
ID == MSG_ID_BP_KEY || ID == MSG_ID_TAP_SPEED ||
ID == MSG_ID_BP_TRANSPOSE) {
LOG_I("MSG", "id=%u hi=%u lo=%u", (unsigned)ID, (unsigned)HiByte, (unsigned)LoByte);
}
}
@ -157,6 +163,7 @@ bool powon = false;
/* ==================== 开机/关机动作 ==================== */
void System_PowerOn(void)
{
LOG_I("PWR", "power_on begin");
/* 控制电源硬件 */
BSP_MainPowerEnable(1);
BSP_DreamCorePowerEnable(1);
@ -169,6 +176,7 @@ void System_PowerOn(void)
app_tm1617_init();
XPT2046_Init();
App_Auto_Init();
LOG_I("PWR", "periph ready send POWER_ON");
/* 通知 UI 层画开机界面 */
MainTask_Sendmsg(MSG_ID_POWER_ON,0,0,0);
}
@ -180,10 +188,14 @@ static void PowerOn(void)
static void PowerOff(void)
{
uint8_t nvm_ret;
LOG_I("PWR", "power_off begin");
powon = false; /* 更新关机标志 */
StopFullTask();
drv_nvm_save_to_flash();
nvm_ret = drv_nvm_save_to_flash();
LOG_I("PWR", "nvm_save=%u", (unsigned)nvm_ret);
LCD_FillByColor(0, 0, 240, 320, BLACK);
LCD_BLK_Clr();
@ -196,6 +208,7 @@ static void PowerOff(void)
BSP_DreamCorePowerEnable(0);
BSP_HT7178PowerEnable(0);
BSP_BlueToothPowerEnable(0);
LOG_I("PWR", "reset");
__disable_irq();
nvic_system_reset();
//MainTask_Sendmsg(MSG_ID_POWER_OFF,0,0,0);
@ -214,6 +227,7 @@ void Power_Key_Scan()
if (s_auto_poweron_pending)
{
s_auto_poweron_pending = 0;
LOG_I("PWR", "auto power_on");
PowerOn();
pwr_key_state = KEY_STATE_NONE; /* 忽略开机瞬间可能残留的按键电平 */
return;
@ -242,11 +256,13 @@ void Power_Key_Scan()
/* 用 powon 区分:关机长按 3s 开机,开机长按 2s 关机 */
if(powon == false && holdtick >= PWR_ON_HOLD_MS)
{
LOG_I("PWR", "key power_on hold=%u", (unsigned)holdtick);
PowerOn();
pwr_key_state = KEY_STATE_NONE;
}
else if(powon == true && holdtick >= PWR_OFF_HOLD_MS)
{
LOG_I("PWR", "key power_off hold=%u", (unsigned)holdtick);
PowerOff();
pwr_key_state = KEY_STATE_NONE;
}

View File

@ -19,34 +19,33 @@ void RGB_Y_TO_G()
#define UI_MODE_SCREEN_WIDTH 240
#define UI_MODE_SCREEN_HEIGHT 320
#define UI_MODE_BG_COLOR 0x1925
#define UI_MODE_LINE_COLOR DARKBLUE
#define UI_MODE_TEXT_COLOR 0xFFFF
/* PDF 标注 MiSans-Medium 21;字库无 21,用 28 与 40x30 图标比例接近标注稿 */
#define UI_MODE_FONT_SIZE 28
#define UI_MODE_ICON_W 40
#define UI_MODE_ICON_H 30
#define UI_MODE_ICON_TEXT_GAP 8
/* 汉字墨心略偏上;下移与图标光心平齐(过大易显得文字偏下) */
#define UI_MODE_TEXT_OPTICAL_DY 2
/* 模式选择页焦点:0=万能 1=普通 2=专业,默认选中万能(与规范一致) */
static uint8_t s_mode_select_focus = 0;
static void UI_DrawModeSelectButton(uint16_t y, const uint8_t *text,
const uint8_t *icon, uint8_t focused)
extern bool InModeFlag;
static void UI_DrawModeSelectButton(uint16_t y, const uint8_t *row, uint8_t focused);
static void UI_ModeSelect_DrawButtons(void);
void UI_ReturnToModeSelect(void)
{
StartFlag = 0;
InModeFlag = false;
AutoBandTop1_Stop();
s_mode_select_focus = 0;
CallUI_Idle();
UI_DrawModeSelectScreen();
}
/* 0831 美工试用图:图标+MiSans 文字整行位图 */
static void UI_DrawModeSelectButton(uint16_t y, const uint8_t *row, uint8_t focused)
{
const uint16_t btn_w = 220;
const uint16_t btn_h = 87;
const uint16_t btn_r = 10;
const uint16_t btn_x = (240 - btn_w) / 2;
const uint16_t text_w = (uint16_t)(4 * UI_MODE_FONT_SIZE);
const uint16_t group_w = (uint16_t)(UI_MODE_ICON_W + UI_MODE_ICON_TEXT_GAP + text_w);
const uint16_t group_x = (uint16_t)(btn_x + (btn_w - group_w) / 2);
const uint16_t mid_y = (uint16_t)(y + btn_h / 2);
const uint16_t icon_y = (uint16_t)(mid_y - UI_MODE_ICON_H / 2);
const uint16_t text_y = (uint16_t)(mid_y - UI_MODE_FONT_SIZE / 2 + UI_MODE_TEXT_OPTICAL_DY);
const uint16_t text_x = (uint16_t)(group_x + UI_MODE_ICON_W + UI_MODE_ICON_TEXT_GAP);
const uint16_t row_x = (uint16_t)(btn_x + (btn_w - UI_MODE0831_ROW_W) / 2);
const uint16_t row_y = (uint16_t)(y + (btn_h - UI_MODE0831_ROW_H) / 2);
if (focused)
{
@ -58,47 +57,65 @@ static void UI_DrawModeSelectButton(uint16_t y, const uint8_t *text,
LCD_FillRoundRect(btn_x, y, btn_w, btn_h, btn_r, COLOR_NORMAL_BG);
}
LCD_WR_PIC_Trans(group_x, icon_y, UI_MODE_ICON_W, UI_MODE_ICON_H, icon, BLACK);
LCD_ShowChinese_AutoAlign(
text_x, (uint16_t)(text_x + text_w - 1),
text_y,
text,
LCD_ALIGN_LEFT,
UI_MODE_TEXT_COLOR,
BLACK,
1,
UI_MODE_FONT_SIZE
);
LCD_WR_PIC_Trans(row_x, row_y, UI_MODE0831_ROW_W, UI_MODE0831_ROW_H, row, BLACK);
}
static void UI_ModeSelect_DrawButtonAt(uint8_t idx)
{
switch (idx)
{
case 0:
UI_DrawModeSelectButton(40, gImage_Mode0831_Universal_Row_180_52,
s_mode_select_focus == 0);
break;
case 1:
UI_DrawModeSelectButton(132, gImage_Mode0831_Normal_Row_180_52,
s_mode_select_focus == 1);
break;
case 2:
UI_DrawModeSelectButton(224, gImage_Mode0831_Expert_Row_180_52,
s_mode_select_focus == 2);
break;
default:
break;
}
}
static void UI_ModeSelect_SetFocus(uint8_t idx)
{
uint8_t prev;
if (idx > 2 || idx == s_mode_select_focus)
return;
prev = s_mode_select_focus;
s_mode_select_focus = idx;
/* 只重绘新旧两个按钮,避免整页三键闪烁 */
UI_ModeSelect_DrawButtonAt(prev);
UI_ModeSelect_DrawButtonAt(idx);
}
static void UI_ModeSelect_DrawButtons(void)
{
UI_ModeSelect_DrawButtonAt(0);
UI_ModeSelect_DrawButtonAt(1);
UI_ModeSelect_DrawButtonAt(2);
}
void UI_DrawModeSelectScreen(void)
{
UI_ClearFocus();
s_mode_select_focus = 0;
pCurrentTouch_Area = Touch_ModeSelect_Areas;
LCD_FillByColor(0, 0, 240, 320, BLACK);
label_top();
UI_DrawModeSelectButton(40,
(const uint8_t *)"\xCD\xF2\xC4\xDC\xC4\xA3\xCA\xBD",
gImage_FreeMode_Icon_40_30,
s_mode_select_focus == 0);
UI_DrawModeSelectButton(132,
(const uint8_t *)"\xC6\xD5\xCD\xA8\xC4\xA3\xCA\xBD",
gImage_NormalMode_Icon_40_30,
s_mode_select_focus == 1);
UI_DrawModeSelectButton(224,
(const uint8_t *)"\xD7\xA8\xD2\xB5\xC4\xA3\xCA\xBD",
gImage_ExpressMode_Icon_40_30,
s_mode_select_focus == 2);
UI_ModeSelect_DrawButtons();
}
const Touch_AreaTypeDef Touch_ModeSelect_Areas[] = {
// Xmin, Xmax, Ymin, Ymax, flag, ID, action
{ 10, 229, 40, 126, 0, 0, GUI_MODE_SELECT3, NULL }, //<2F><><EFBFBD><EFBFBD>
{ 10, 229, 132, 218, 1, 0, GUI_MODE_SELECT1, NULL }, //????/<2F><><EFBFBD><EFBFBD>
{ 10, 229, 224, 310, 2, 0, GUI_MODE_SELECT2, NULL }, //רҵ/ר???
/* Flag=按钮序号(0万能/1普通/2专业);ID=进入 TAB */
{ 0, 239, 38, 127, 0, 0, 0, NULL },
{ 0, 239, 128, 219, 1, 0, 2, NULL },
{ 0, 239, 220, 311, 2, 0, 1, NULL },
};
#define TOUCH_AREA_MODE_SELECT_NUM (sizeof(Touch_ModeSelect_Areas)/sizeof(Touch_AreaTypeDef))
@ -141,44 +158,38 @@ void LoadConfig()
void Touch_Mode_Select_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex)
{
(void)areaIndex;
StartTask();
s_mode_select_focus = (uint8_t)FLAG;
/* ID 为 TAB 索引:0=万能(Song) 1=专业(Expert) 2=普通(Free) */
mGuiData[GUI_TAB_INDEX].Current = ID;
InModeFlag = true;
// <20>ٽ<EFBFBD><D9BD><EFBFBD><EFBFBD>ٽ<EFBFBD><D9BD><EFBFBD>
switch(ID)
{
case 0:
if(mGuiData[GUI_AUTOBAND_SW].Current)
{
ADDRESS = 0x0009EB5F;
}
else
{
ADDRESS = 0x0001B8F0;
}
MenuPage_SongMode_Proc();
/* 进入子页后抑制残留按下,否则易误触底栏 GUI_TAB_INDEX(id=5)→设置 */
app_touch_suppress(500);
break;
case 1:
if(mGuiData[GUI_AUTOBAND_SW].Current)
{
switch (ID)
{
case 0: /* 万能 */
if (mGuiData[GUI_AUTOBAND_SW].Current)
ADDRESS = 0x0009EB5F;
}
else
{
// ADDRESS = 0x0001B8F0;
ADDRESS = 0x00000000;
}
ADDRESS = 0x0001B8F0;
MenuPage_SongMode_Proc();
break;
case 1: /* 专业 */
if (mGuiData[GUI_AUTOBAND_SW].Current)
ADDRESS = 0x0009EB5F;
else
ADDRESS = 0x00000000;
MenuPage_ExpertMode_Proc();
break;
case 2:
break;
case 2: /* 普通 */
ADDRESS = 0x0009598F;
MenuPage_FreeMode_Proc();
break;
break;
default:
break;
}
}
extern uint8_t bat_init_done;
@ -192,7 +203,8 @@ void IdleProcess(DisplayTaskMessage_Type msg)
LCD_FillByColor(0, 0, 240, 320, WHITE);
LCD_WR_PIC_FROM_FLASH(40, 80, 168, 155, 0x00000000);
BSP_SelectAdcChannel(6);
wk_delay_ms(1000);
/* 让出 CPU,避免忙等卡住触摸/主循环线程 */
rt_thread_mdelay(1000);
LoadConfig();
vol = ADC_ReadChannel(ADC_CHANNEL_1)>>5;
if (vol <= 5) level = 0;
@ -203,6 +215,7 @@ void IdleProcess(DisplayTaskMessage_Type msg)
else level = 5;
Send_volume(vol);
s_mode_select_focus = 0;
UI_DrawModeSelectScreen();
TM1629D_AllLedOn(LED_COLOR_G);
TM1629D_UpdateDisplay(0);
@ -221,17 +234,48 @@ void IdleProcess(DisplayTaskMessage_Type msg)
break;
case MSG_ID_TOUCH:
for(uint8_t k=0; k<TOUCH_AREA_MODE_SELECT_NUM; k++)
{
if( msg.HiByte >= Touch_ModeSelect_Areas[k].x_min && msg.HiByte <= Touch_ModeSelect_Areas[k].x_max &&
msg.LoByte >= Touch_ModeSelect_Areas[k].y_min && msg.LoByte <= Touch_ModeSelect_Areas[k].y_max )
{
Touch_Mode_Select_Action(Touch_ModeSelect_Areas[k].Flag,Touch_ModeSelect_Areas[k].ID,k);
break;
}
static uint32_t s_last_enter_ms;
uint32_t now = rt_tick_get() * 1000U / RT_TICK_PER_SECOND;
uint8_t hit = 0U;
for(uint8_t k=0; k<TOUCH_AREA_MODE_SELECT_NUM; k++)
{
if( msg.HiByte >= Touch_ModeSelect_Areas[k].x_min && msg.HiByte <= Touch_ModeSelect_Areas[k].x_max &&
msg.LoByte >= Touch_ModeSelect_Areas[k].y_min && msg.LoByte <= Touch_ModeSelect_Areas[k].y_max )
{
hit = 1U;
uint8_t row = (uint8_t)Touch_ModeSelect_Areas[k].Flag;
LOG_I("UI", "mode_select hit area=%u row=%u focus=%u x=%u y=%u",
(unsigned)k, (unsigned)row, (unsigned)s_mode_select_focus,
(unsigned)msg.HiByte, (unsigned)msg.LoByte);
/* 调试十字:确认触点是否落在按钮上 */
TP_Drow_Touch_Point(msg.HiByte, msg.LoByte, RED);
/* 已聚焦 → 进入;未聚焦 → 切蓝焦点,再点一次进入 */
if (row == s_mode_select_focus) {
if (now - s_last_enter_ms < 300U)
break;
s_last_enter_ms = now;
Touch_Mode_Select_Action(Touch_ModeSelect_Areas[k].Flag,
Touch_ModeSelect_Areas[k].ID, k);
} else {
UI_ModeSelect_SetFocus(row);
}
break;
}
}
if (!hit) {
LOG_I("UI", "mode_select miss x=%u y=%u",
(unsigned)msg.HiByte, (unsigned)msg.LoByte);
TP_Drow_Touch_Point(msg.HiByte, msg.LoByte, YELLOW);
}
}
break;
case MSG_ID_TOUCH_LONG_REPEAT:
/* 模式页暂无长按动作 */
break;
case MSG_ID_BATTERY_VALUE:
Draw_Status_Battery_Icon(msg.ID);
break;

View File

@ -377,14 +377,26 @@ void UI_SongMode_Process(DisplayTaskMessage_Type msg)
switch(msg.MessageType)
{
case MSG_ID_TOUCH:
for(uint8_t k=0; k<TOUCH_AREA_NUM; k++)
{
if( msg.HiByte >= pCurrentTouch_Area[k].x_min && msg.HiByte <= pCurrentTouch_Area[k].x_max &&
msg.LoByte >= pCurrentTouch_Area[k].y_min && msg.LoByte <= pCurrentTouch_Area[k].y_max )
{
Touch_Action(pCurrentTouch_Area[k].Flag,pCurrentTouch_Area[k].ID,k,pCurrentTouch_Area[k].Value);
ResetAutoPowerCount();
break;
{
uint8_t hit = 0U;
for(uint8_t k=0; k<TOUCH_AREA_NUM; k++)
{
if( msg.HiByte >= pCurrentTouch_Area[k].x_min && msg.HiByte <= pCurrentTouch_Area[k].x_max &&
msg.LoByte >= pCurrentTouch_Area[k].y_min && msg.LoByte <= pCurrentTouch_Area[k].y_max )
{
hit = 1U;
LOG_I("UI", "song hit area=%u id=%u val=%u x=%u y=%u",
(unsigned)k, (unsigned)pCurrentTouch_Area[k].ID,
(unsigned)pCurrentTouch_Area[k].Value,
(unsigned)msg.HiByte, (unsigned)msg.LoByte);
Touch_Action(pCurrentTouch_Area[k].Flag,pCurrentTouch_Area[k].ID,k,pCurrentTouch_Area[k].Value);
ResetAutoPowerCount();
break;
}
}
if (!hit) {
LOG_I("UI", "song miss x=%u y=%u",
(unsigned)msg.HiByte, (unsigned)msg.LoByte);
}
}
break;
@ -417,6 +429,12 @@ void UI_SongMode_Process(DisplayTaskMessage_Type msg)
break;
case MSG_ID_TOUCH_LONG_REPEAT:
{
/* 长按顶栏(y<28)返回三键模式选择页 */
if (msg.LoByte < 28)
{
UI_ReturnToModeSelect();
break;
}
for(uint8_t k=0; k<TOUCH_AREA_NUM; k++)
{
if( msg.HiByte >= pCurrentTouch_Area[k].x_min && msg.HiByte <= pCurrentTouch_Area[k].x_max &&

View File

@ -34,62 +34,43 @@ void Touch_Seting_Select_Action(int8_t FLAG, uint8_t ID, uint8_t areaIndex,uint8
switch(ID)
{
case 0:
MenuPage_Mixer_Proc();
if (CurrUIProcress != UI_Mixer_Process)
MenuPage_Mixer_Proc();
break;
// case 2: //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-Ԥ<><D4A4>
// MenuPage_FreeMode_Proc();
// break;
case 2:
MenuPage_Setting_Proc();
if (CurrUIProcress != UI_Setting_Process)
MenuPage_Setting_Proc();
break;
case 5:
last_value = mGuiData[GUI_TAB_INDEX].Current;
last_value = mGuiData[GUI_TAB_INDEX].Current;
if (last_value == value)
break;
mGuiData[GUI_TAB_INDEX].Current = value;
if(last_value != value)
switch(value)
{
switch(value)
{
case 0:
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[GUI_TAB_INDEX].Current)
break;
//AutoBandTop1_Stop(); // AutoBand 模块未加入工程
ADDRESS = 0x00000000;
//AutoBandTop1_LoadPresetItemFromFlash(0); // AutoBand 模块未加入工程
break;
case 0:
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[GUI_TAB_INDEX].Current)
break;
ADDRESS = 0x00000000;
break;
case 1:
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[GUI_TAB_INDEX].Current)
break;
//AutoBandTop1_Stop(); // AutoBand 模块未加入工程
ADDRESS = 0x0000EB41;
//AutoBandTop1_LoadPresetItemFromFlash(0); // AutoBand 模块未加入工程
break;
case 1:
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[GUI_TAB_INDEX].Current)
break;
ADDRESS = 0x0000EB41;
break;
case 2:
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[GUI_TAB_INDEX].Current)
break;
//AutoBandTop1_Stop();
ADDRESS = 0x000170EF;
break;
}
case 2:
if(mGuiData[GUI_TAB_LAST_INDEX].Current == mGuiData[GUI_TAB_INDEX].Current)
break;
ADDRESS = 0x000170EF;
break;
default:
break;
}
// switch(value)
// {
// case 0:
//
// MenuPage_SongMode_Proc();
// break;
// case 1:
// MenuPage_ExpertMode_Proc();
// break;
// case 2:
// MenuPage_FreeMode_Proc();
// break;
// }
Refresh_TabSelect_ui(&UI_Label[ID], &mGuiData[ID]);
Refresh_TabSelect_ui(&UI_Label[GUI_PLAY_SECTION], &mGuiData[GUI_TAB_INDEX]);
break;
}
@ -241,28 +222,34 @@ void UI_DrawSysModeSelectScreen(void)
void UI_SystemSet_Init(void)
{
UI_ClearFocus();
// LCD_WR_REG(0x28); // Display OFF
pCurrentTouch_Area = Touch_SetingSelect_Areas;
UI_DrawSysModeSelectScreen();
label_top();
label_Tab_Select_ui(&UI_Label[GUI_PLAY_SECTION], &mGuiData[GUI_TAB_INDEX]);
// LCD_WR_REG(0x29); // Display ON
}
void UI_SystemSet_Process(DisplayTaskMessage_Type msg)
{
static uint32_t s_last_touch_ms;
switch(msg.MessageType)
{
case MSG_ID_TOUCH:
for(uint8_t k=0; k<TOUCH_AREA_SETING_SELECT_NUM; k++)
{
if( msg.HiByte >= pCurrentTouch_Area[k].x_min &&
msg.HiByte <= pCurrentTouch_Area[k].x_max &&
msg.LoByte >= pCurrentTouch_Area[k].y_min &&
msg.LoByte <= pCurrentTouch_Area[k].y_max )
uint32_t now = rt_tick_get() * 1000U / RT_TICK_PER_SECOND;
if (now - s_last_touch_ms < 250U) {
break;
}
s_last_touch_ms = now;
for(uint8_t k=0; k<TOUCH_AREA_SETING_SELECT_NUM; k++)
{
Touch_Seting_Select_Action(pCurrentTouch_Area[k].Flag,pCurrentTouch_Area[k].ID,k,pCurrentTouch_Area[k].Value);
break;
if( msg.HiByte >= pCurrentTouch_Area[k].x_min &&
msg.HiByte <= pCurrentTouch_Area[k].x_max &&
msg.LoByte >= pCurrentTouch_Area[k].y_min &&
msg.LoByte <= pCurrentTouch_Area[k].y_max )
{
Touch_Seting_Select_Action(pCurrentTouch_Area[k].Flag,pCurrentTouch_Area[k].ID,k,pCurrentTouch_Area[k].Value);
break;
}
}
}
break;

View File

@ -32,44 +32,66 @@ void InitMenuUI(void) {
// <20><><EFBFBD><EFBFBD> UI <20><><EFBFBD>ú<EFBFBD><C3BA><EFBFBD>
// ==============================================
void CallUI_Idle(void) {
CurrUIProcress = IdleProcess;
CurrUIProcress = IdleProcess;
app_log_set_ui_page("Idle");
LOG_I("UI", "page Idle");
}
void CallUI_SongMode(void) {
app_touch_suppress(500);
UI_SongMode_Init();
CurrUIProcress = UI_SongMode_Process;
app_log_set_ui_page("Song");
LOG_I("UI", "page SongMode tab=%u", (unsigned)mGuiData[GUI_TAB_INDEX].Current);
}
void CallUI_ExpertMode(void) {
app_touch_suppress(500);
UI_ExpertMode_Init();
// CurrUIProcress = UI_ExpertMode_Process;
CurrUIProcress = UI_SongMode_Process;
app_log_set_ui_page("Expert");
LOG_I("UI", "page ExpertMode");
}
void CallUI_FreeMode(void) {
UI_FreeMode_Init();
app_touch_suppress(500);
UI_FreeMode_Init();
// CurrUIProcress = UI_FreeMode_Process;
CurrUIProcress = UI_SongMode_Process;
app_log_set_ui_page("Free");
LOG_I("UI", "page FreeMode");
}
void CallUI_Setting(void) {
app_touch_suppress(500);
CurrUIProcress = UI_Setting_Process;
UI_Setting_Init();
app_log_set_ui_page("Setting");
LOG_I("UI", "page Setting");
}
void CallUI_Mixer(void) {
app_touch_suppress(500);
CurrUIProcress = UI_Mixer_Process;
UI_Mixer_Init();
app_log_set_ui_page("Mixer");
LOG_I("UI", "page Mixer");
}
void CallUI_SystemSetting(void) {
app_touch_suppress(500);
CurrUIProcress = UI_SystemSet_Process;
UI_SystemSet_Init();
app_log_set_ui_page("SysSet");
LOG_I("UI", "page SystemSetting");
}
void CallUI_RestoreSelect(void) {
CurrUIProcress = UI_Restore_Select_Process;
UI_Restore_Select_Init();
app_log_set_ui_page("Restore");
LOG_I("UI", "page Restore");
}
static uint8_t s_ui_focus_id = UI_FOCUS_NONE;
@ -168,23 +190,34 @@ void Refresh_SectionSelect(uint16_t last_value, const LEBEL_UI *UI, GUI_SWITCH *
Draw_Tab_Section_Menu(UI,(TAB_SECTION_INDEX)Group->Current);
}
/* TAB 选择刷新(占位) */
/* TAB 选择刷新:tab 未变时不切页,避免误触/phantom touch 整页重绘闪烁 */
void Refresh_TabSelect_ui(const LEBEL_UI *UI, GUI_SWITCH *Group)
{
switch(Group->Current)
{
static uint8_t s_last_tab = 0xFFU;
uint8_t tab = (uint8_t)Group->Current;
(void)UI;
if (tab == s_last_tab) {
return;
}
s_last_tab = tab;
switch (tab)
{
case 0:
MenuPage_SongMode_Proc();
break;
MenuPage_SongMode_Proc();
break;
case 1:
MenuPage_ExpertMode_Proc();
break;
MenuPage_ExpertMode_Proc();
break;
case 2:
MenuPage_FreeMode_Proc();
break;
MenuPage_FreeMode_Proc();
break;
case 3:
MenuPage_SystemSetting_Proc();
break;
MenuPage_SystemSetting_Proc();
break;
default:
break;
}
}

View File

@ -9,7 +9,7 @@ typedef enum {
extern const Touch_AreaTypeDef Touch_ModeSelect_Areas[];
extern void UI_DrawModeSelectScreen(void);
extern void UI_ReturnToModeSelect(void);
extern void IdleProcess(DisplayTaskMessage_Type xEvent);

View File

@ -7,9 +7,11 @@
#define LCD_DUMP_RTT_UP_CH 1
#define LCD_DUMP_RTT_DOWN_CH 0
#define LCD_DUMP_CMD "DUMP"
#define LCD_DUMP_TAP_CMD "TAP"
#define LCD_DUMP_RESET_CMD "sys reset"
#define LCD_DUMP_MAGIC "SCRN"
#define LCD_DUMP_FMT_RGB565 1
#define LCD_DUMP_CMD_BUF_SIZE 16
#define LCD_DUMP_CMD_BUF_SIZE 32
#pragma pack(1)
typedef struct {
@ -197,6 +199,14 @@ static uint8_t LCD_Dump_CommandPending(void)
char c = rx[i];
if (c == '\r' || c == '\n' || c == '\0') {
if (s_cmd_len > 0U) {
s_cmd_buf[s_cmd_len] = '\0';
if (app_log_try_command(s_cmd_buf)) {
s_cmd_len = 0U;
s_cmd_buf[0] = '\0';
continue;
}
}
s_cmd_len = 0U;
s_cmd_buf[0] = '\0';
continue;
@ -208,6 +218,22 @@ static uint8_t LCD_Dump_CommandPending(void)
s_cmd_buf[s_cmd_len++] = c;
s_cmd_buf[s_cmd_len] = '\0';
/* 合成点击:验证 UI 命中路径(点万能按钮中心) */
if (strstr(s_cmd_buf, LCD_DUMP_TAP_CMD) != NULL) {
s_cmd_len = 0U;
s_cmd_buf[0] = '\0';
MainTask_Sendmsg(MSG_ID_TOUCH, 1, 120, 80);
SEGGER_RTT_WriteString(0, "TAP inject 120,80\n");
continue;
}
if (strstr(s_cmd_buf, LCD_DUMP_RESET_CMD) != NULL) {
s_cmd_len = 0U;
s_cmd_buf[0] = '\0';
(void)app_log_try_command(LCD_DUMP_RESET_CMD);
continue;
}
if (c == 0x01 || strstr(s_cmd_buf, LCD_DUMP_CMD) != NULL) {
s_cmd_len = 0U;
s_cmd_buf[0] = '\0';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
#ifndef __DRV_ILI9341_LCD_IMAGE_MODESELECT0831_H
#define __DRV_ILI9341_LCD_IMAGE_MODESELECT0831_H
#include "stdint.h"
#define UI_MODE0831_ROW_W 180
#define UI_MODE0831_ROW_H 52
extern const unsigned char gImage_Mode0831_Universal_Row_180_52[];
extern const unsigned char gImage_Mode0831_Normal_Row_180_52[];
extern const unsigned char gImage_Mode0831_Expert_Row_180_52[];
#endif

View File

@ -20,9 +20,9 @@ void XPT2046_Init(void)
gpio_init_struct.gpio_pins = TP_CLK_PIN | TP_DIN_PIN | TP_CS_PIN;
gpio_init(GPIOC, &gpio_init_struct);
// 输入引脚:OUT(上拉)、IRQ(上拉)
// DOUT:浮空输入(内部上拉易把空闲读成满量程)
gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
gpio_init_struct.gpio_pull = GPIO_PULL_UP;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init_struct.gpio_pins = TP_OUT_PIN;
gpio_init(TP_OUT_PORT, &gpio_init_struct);
@ -92,116 +92,251 @@ void TP_Write_Byte(uint8_t data)
//从触摸屏IC读取adc值
//CMD:指令
//返回值:读到的数据
uint16_t TP_Read_AD(uint8_t cmd)
{
/* 在 CS 已拉低时写命令并读:与量产一致,16 时钟取高 12bit */
static uint16_t TP_Read_AD_CSHeld(uint8_t cmd)
{
uint8_t i;
uint16_t adc_val = 0;
TP_CS_Clr(); // 片选拉低,选中XPT2046
Delay_us(2); // 片选稳定时间
TP_Write_Byte(cmd); // MCU通过DIN发送命令(XPT2046的DIN输入接收)
// 等待XPT2046完成AD转换(约6us,转换期间OUT无有效数据)
TP_Write_Byte(cmd);
Delay_us(6);
// 读取16位数据(XPT2046通过自身OUT输出数据,MCU通过OUT输入读取)
for (i = 0; i < 16; i++)
for (i = 0; i < 16; i++)
{
adc_val <<= 1; // 左移,准备接收下一位
// 时钟上升沿:XPT2046更新OUT引脚输出;下降沿:MCU读取OUT输入
TP_CLK_Set();
Delay_us(1);
TP_CLK_Clr();
Delay_us(1);
// MCU读取OUT引脚的输入数据(高12位有效,低4位丢弃)
if (TP_OUT_Read())
{
adc_val |= 0x01;
}
adc_val <<= 1;
TP_CLK_Set();
Delay_us(1);
TP_CLK_Clr();
Delay_us(1);
if (TP_OUT_Read())
adc_val |= 0x01;
}
TP_CS_Set(); // 释放片选
return adc_val >> 4; // 取高12位有效数据
return (uint16_t)(adc_val >> 4);
}
// 简单去抖动读取(连续n次相同状态视为有效)
uint8_t TP_CheckPressed(uint8_t n)
uint16_t TP_Read_AD(uint8_t cmd)
{
uint8_t count = 0;
uint16_t v;
for (uint8_t i = 0; i < n; i++)
{
if (TP_IRQ_Read() == 0)
{ // IRQ低电平表示按下
count++;
}
Delay_us(100);
}
return (count >= n/2) ? 1 : 0;
TP_CS_Clr();
Delay_us(2);
v = TP_Read_AD_CSHeld(cmd);
TP_CS_Set();
return v;
}
// 读取原始坐标(未校准)
TP_Point TP_ReadRaw(void)
{
TP_Point p = {0, 0, 0};
#define TP_ADC_PRESS_MIN 120u
#define TP_ADC_PRESS_MAX 4095u
#define TP_PRESSURE_MIN 80u
/* 本板 IRQ 无效;压力仅作弱参考,不过度拦截(真触摸 z 也常偏大) */
#define TP_PRESSURE_MAX 5000u
if (!TP_CheckPressed(3))
{ // 检查触摸状态
static uint16_t TP_Median5(uint16_t *a)
{
uint8_t i, j;
uint16_t t;
for (i = 0; i < 4; i++) {
for (j = (uint8_t)(i + 1U); j < 5U; j++) {
if (a[j] < a[i]) {
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
return a[2];
}
static void TP_SampleXY(uint16_t *out_x, uint16_t *out_y)
{
uint16_t xs[5], ys[5];
uint8_t i;
for (i = 0; i < 5; i++) {
xs[i] = TP_Read_AD(XPT2046_READ_X);
Delay_us(15);
ys[i] = TP_Read_AD(XPT2046_READ_Y);
Delay_us(15);
}
*out_x = TP_Median5(xs);
*out_y = TP_Median5(ys);
}
static uint16_t TP_ReadPressure(void)
{
uint16_t z1, z2;
z1 = TP_Read_AD(XPT2046_READ_Z1);
Delay_us(15);
z2 = TP_Read_AD(XPT2046_READ_Z2);
return (z1 > z2) ? (uint16_t)(z1 - z2) : 0U;
}
static uint8_t TP_AdcCoordValid(uint16_t x, uint16_t y)
{
/* 仅排除明显无效;双高在本板上也可能是真触摸(Y 常贴高,垂直靠 raw.x) */
if (x < TP_ADC_PRESS_MIN && y < TP_ADC_PRESS_MIN)
return 0U;
if (x > 4000u && y > 4000u)
return 0U;
return (x <= TP_ADC_PRESS_MAX && y <= TP_ADC_PRESS_MAX) ? 1U : 0U;
}
#define CALIB_X_MIN 200
#define CALIB_X_MAX 3800
#define CALIB_Y_MIN 200
#define CALIB_Y_MAX 3950
/*
* 实测(IRQ 有效按下):raw.x 常贴高,上下主要改 raw.y → 不要 SWAP。
* Y 镜像:上方「万能」对应更大 raw.y。
* 空闲 ADC 噪声是 raw.y=4095,禁止走 ADC 回退,否则幽灵点击。
*/
#define TP_SWAP_XY 0
#define TP_MIRROR_X 0
#define TP_MIRROR_Y 1
static uint16_t TP_MapAxis(uint16_t adc, uint16_t adc_min, uint16_t adc_max, uint16_t px_max)
{
int32_t v = (int32_t)adc;
int32_t a0 = (int32_t)adc_min;
int32_t a1 = (int32_t)adc_max;
int32_t span = a1 - a0;
int32_t px;
if (span <= 0)
return 0;
if (v < a0)
v = a0;
if (v > a1)
v = a1;
px = (v - a0) * (int32_t)px_max / span;
if (px < 0)
px = 0;
if (px > (int32_t)px_max)
px = (int32_t)px_max;
return (uint16_t)px;
}
static void TP_RawToScreen(uint16_t raw_x, uint16_t raw_y, uint16_t *sx, uint16_t *sy)
{
uint16_t ax, ay;
#if TP_SWAP_XY
ax = TP_MapAxis(raw_y, CALIB_Y_MIN, CALIB_Y_MAX, (uint16_t)(LCD_W - 1));
ay = TP_MapAxis(raw_x, CALIB_X_MIN, CALIB_X_MAX, (uint16_t)(LCD_H - 1));
#else
ax = TP_MapAxis(raw_x, CALIB_X_MIN, CALIB_X_MAX, (uint16_t)(LCD_W - 1));
ay = TP_MapAxis(raw_y, CALIB_Y_MIN, CALIB_Y_MAX, (uint16_t)(LCD_H - 1));
#endif
#if TP_MIRROR_X
ax = (uint16_t)(LCD_W - 1 - ax);
#endif
#if TP_MIRROR_Y
ay = (uint16_t)(LCD_H - 1 - ay);
#endif
*sx = ax;
*sy = ay;
}
void TP_LogStatus(void)
{
uint16_t x, y, z;
uint16_t sx, sy;
z = TP_ReadPressure();
TP_SampleXY(&x, &y);
TP_RawToScreen(x, y, &sx, &sy);
LOG_TPI("status irq=%u z=%u raw=%u,%u cal=%u,%u",
(unsigned)(TP_IRQ_Read() == 0U),
(unsigned)z,
(unsigned)x, (unsigned)y,
(unsigned)sx, (unsigned)sy);
}
static uint8_t s_tp_live;
void TP_SetLiveMode(uint8_t on)
{
s_tp_live = on ? 1U : 0U;
LOG_TPI("live %s (events only)", s_tp_live ? "on" : "off");
}
/* 不再空闲刷屏;按下事件仍由 touch task 的 scan/press/release 打印 */
void TP_LivePoll(void)
{
(void)s_tp_live;
}
uint8_t TP_CheckPressed(uint8_t n)
{
uint8_t count = 0;
uint8_t i;
for (i = 0; i < n; i++)
{
if (TP_IRQ_Read() == 0)
count++;
Delay_us(100);
}
return (count >= ((n + 1U) / 2U)) ? 1U : 0U;
}
TP_Point TP_ReadRaw(void)
{
TP_Point p = {0, 0, 0};
uint8_t irq_pressed;
static uint8_t s_raw_logged;
uint16_t rx, ry;
irq_pressed = TP_CheckPressed(3);
if (!irq_pressed) {
/*
* 空闲时 ADC 常读成 y=4095,会误触发幽灵点击。
* 本板真触摸 IRQ 能拉低,只认 IRQ 路径。
*/
s_raw_logged = 0U;
return p;
}
TP_SampleXY(&rx, &ry);
if (!TP_AdcCoordValid(rx, ry)) {
s_raw_logged = 0U;
return p;
}
p.x = rx;
p.y = ry;
p.pressed = 1;
if (!s_raw_logged) {
LOG_TPI("raw x=%u y=%u irq=1", (unsigned)p.x, (unsigned)p.y);
s_raw_logged = 1U;
}
return p;
}
// 多次读取取平均值,减少噪声
uint32_t x_sum = 0, y_sum = 0;
for (uint8_t i = 0; i < 5; i++)
{
x_sum += TP_Read_AD(XPT2046_READ_X);
y_sum += TP_Read_AD(XPT2046_READ_Y);
}
p.x = x_sum / 5;
p.y = y_sum / 5;
p.pressed = 1;
return p;
}
// 校准参数(需实际触摸四个角后修改,示例为240x320屏)
#define CALIB_X_MIN 200 // 最小X原始值
#define CALIB_X_MAX 3800 // 最大X原始值
#define CALIB_Y_MIN 200 // 最小Y原始值
#define CALIB_Y_MAX 3800 // 最大Y原始值
#define LCD_WIDTH 240 // LCD宽度
#define LCD_HEIGHT 320 // LCD高度
// 校准后获取LCD坐标
TP_Point TP_Read(void)
TP_Point TP_Read(void)
{
// char Info[40];
TP_Point raw = TP_ReadRaw();
// sprintf (Info, "(x=%03d y=%03d) ", raw.x, raw.y);
// LCD_ShowString(2, 176+16, (const uint8_t*)Info, RED, WHITE, 16, 0);
TP_Point calib = {0, 0, raw.pressed};
TP_Point raw = TP_ReadRaw();
TP_Point calib = {0, 0, raw.pressed};
static uint8_t s_cal_logged;
if (!raw.pressed) {
if (!raw.pressed) {
s_cal_logged = 0U;
return calib;
}
TP_RawToScreen(raw.x, raw.y, &calib.x, &calib.y);
if (!s_cal_logged) {
LOG_TPI("cal x=%u y=%u (raw %u,%u)", (unsigned)calib.x, (unsigned)calib.y,
(unsigned)raw.x, (unsigned)raw.y);
s_cal_logged = 1U;
}
return calib;
}
// 线性映射:将原始ADC值转换为LCD像素坐标
calib.x = (raw.x - CALIB_X_MIN) * LCD_WIDTH / (CALIB_X_MAX - CALIB_X_MIN);
calib.y = (raw.y - CALIB_Y_MIN) * LCD_HEIGHT / (CALIB_Y_MAX - CALIB_Y_MIN);
// 边界限制
if (calib.x > LCD_WIDTH - 1) calib.x = LCD_WIDTH - 1;
if (calib.x == 0) calib.x = 0;
if (calib.y > LCD_HEIGHT - 1) calib.y = LCD_HEIGHT - 1;
if (calib.y == 0) calib.y = 0;
return calib;
}
// EXTI15_10中断服务函数(处理TP_IRQ触发)

View File

@ -33,11 +33,11 @@
#define TP_DIN_Clr() gpio_bits_reset(TP_DIN_PORT, TP_DIN_PIN) // DIN输出低(MCU→XPT2046)
#define TP_DIN_Set() gpio_bits_set(TP_DIN_PORT, TP_DIN_PIN) // DIN输出高(MCU→XPT2046)
// XPT2046命令定义
#define XPT2046_READ_X 0xD0 // 读X坐标命令(MCU通过DIN发送给XPT2046)
#define XPT2046_READ_Y 0x90 // 读Y坐标命令
#define XPT2046_READ_Z1 0xB0 // 读压力Z1
#define XPT2046_READ_Z2 0xC0 // 读压力Z2
/* 与量产/BOOT 一致:PD1:PD0=00(转换后掉电);时序用 16clk>>4 */
#define XPT2046_READ_X 0xD0
#define XPT2046_READ_Y 0x90
#define XPT2046_READ_Z1 0xB0
#define XPT2046_READ_Z2 0xC0
// 触摸状态结构体
typedef struct
@ -64,6 +64,9 @@ extern uint16_t TP_Read_AD(uint8_t CMD); // 从触摸屏IC读取adc值
extern uint8_t TP_CheckPressed(uint8_t n); // 简单去抖动读取(连续n次相同状态视为有效)
extern TP_Point TP_ReadRaw(void); // 读取原始坐标(未校准)
extern TP_Point TP_Read(void); // 校准后获取LCD坐标
extern void TP_LogStatus(void); // 打印 IRQ/ADC 诊断(RTT tp status)
extern void TP_SetLiveMode(uint8_t on); // 实时采样日志开关(RTT tp live / tp live off)
extern void TP_LivePoll(void); // live 模式下周期性打印 raw/cal
extern void EXTI15_10_IRQHandler(void); // EXTI15_10中断服务函数(处理TP_IRQ触发)
//extern void HAL_EXTI_Callback(EXTI_HandleTypeDef *hexti); // 中断回调函数(触摸触发时执行)

View File

@ -79,7 +79,7 @@ void drv_nvm_init(void)
dataSum1 = ~dataSum0;
if ((dataSum0 != mNvmData.sum[0]) || (dataSum1 != mNvmData.sum[1]) || (mNvmData.data.nvm_version != NVM_VERSION)) {
// load default value;
LOG_W("NVM", "checksum fail load default ver=%u", (unsigned)mNvmData.data.nvm_version);
drv_nvm_load_default_value(&mNvmData.data);
drv_nvm_save_to_flash();
}else
@ -88,7 +88,8 @@ void drv_nvm_init(void)
drv_nvm_save_to_flash();
}
memcpy(&mNvmDataShadow, &mNvmData, sizeof(NvmData_Type));
}
LOG_I("NVM", "init ok ver=%u", (unsigned)mNvmData.data.nvm_version);
}
NvmParam_Type * drv_nvm_param_ptr(void)
{
@ -146,8 +147,9 @@ uint8_t drv_nvm_save_to_flash(void)
uint32_t addr = INNER_FLASH_SAVE_ADDRESS;
NvmData_Type * NvmDataToSave;
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>仯<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 无变化不保存
if (memcmp(&mNvmDataShadow, &mNvmData, sizeof(NvmData_Type)) == 0) {
LOG_D("NVM", "save skip unchanged");
return 0;
}
@ -172,6 +174,7 @@ uint8_t drv_nvm_save_to_flash(void)
flash_write_nocheck(addr,pData16,len);
flash_lock();
LOG_I("NVM", "saved to flash");
return 1;
}

View File

@ -2001,6 +2001,12 @@
<file>
<name>$PROJ_DIR$\..\..\APP\app_touch.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\APP\app_log.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\APP\app_log.h</name>
</file>
</group>
<group>
<name>AutoBand</name>
@ -2078,6 +2084,12 @@
<file>
<name>$PROJ_DIR$\..\..\device\LCD_ILI9341\Drv_ILI9341_Lcd_Image.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\device\LCD_ILI9341\Drv_ILI9341_Lcd_Image_ModeSelect0831.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\device\LCD_ILI9341\Drv_ILI9341_Lcd_Image_ModeSelect0831.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\..\device\LCD_ILI9341\Drv_ILI9341_Lcd_Image.h</name>
</file>

View File

@ -35,6 +35,7 @@
#include "Drv_ILI9341_Lcd_Init.h"
#include "Drv_ILI9341_Lcd_App.h"
#include "Drv_ILI9341_Lcd_Image.h"
#include "Drv_ILI9341_Lcd_Image_ModeSelect0831.h"
#include "Drv_ILI9341_Lcd_Dump.h"
#include "Drv_XPT2046_Touch.h"
@ -75,5 +76,6 @@
#include "app_tm1617.h"
#include "app_touch.h"
#include "app_adc.h"
#include "app_log.h"
#endif

View File

@ -83,6 +83,7 @@ int main(void)
bsp_init();
LCD_Init();
LCD_Dump_Init();
app_log_init();
drv_nvm_init();
TaskInit();
timer_init();
@ -90,6 +91,7 @@ int main(void)
BSP_HT7178PowerEnable (1);
BSP_ChargerEnable(1);
CurrUIProcress = IdleProcess;
LOG_I("BOOT", "main init done ui=Idle");
/* add user code end 2 */
while(1)
@ -102,6 +104,10 @@ int main(void)
/* add user code begin 3 */
Power_Key_Scan();
LCD_Dump_Poll();
#if !DEBUG_LCD_DUMP
app_log_poll();
TP_LivePoll();
#endif
rt_thread_delay(1);
/* add user code end 3 */

View File

@ -1,6 +1,6 @@
#include "includes.h"
static struct rt_thread TaskScanThread;
static struct rt_thread TaskScanThread;
static struct rt_thread TaskUIThread;
static struct rt_thread TaskTouchThread;
static struct rt_thread TaskMainThread;
@ -44,13 +44,24 @@ void TaskUIThread_entry(void* parameter)
void TaskTouchThread_entry(void* parameter)
{
static uint8_t s_last_pressed = 0;
LOG_TPI("touch task running");
while(1)
{
TP_Point tp = TP_Read(); /* 读 XPT2046 触摸坐标 */
app_touch_process(240-tp.x, tp.y, tp.pressed);
LCD_Dump_Poll();
TP_Point tp = TP_Read(); /* 已含轴交换/镜像,直接进手势 */
if (tp.pressed) {
if (!s_last_pressed) {
LOG_TPI("scan down x=%u y=%u", (unsigned)tp.x, (unsigned)tp.y);
}
} else if (s_last_pressed) {
LOG_TPI("scan up");
}
s_last_pressed = tp.pressed;
app_touch_process(tp.x, tp.y, tp.pressed);
TP_LivePoll();
app_log_poll();
Read_Cur_Measure();
rt_thread_mdelay(10);
rt_thread_mdelay(8);
}
}
@ -146,7 +157,7 @@ rt_err_t TaskInit(void)
BLTask_msg = rt_mq_create("BLTask msgQ",sizeof(DisplayTaskMessage_Type),128,RT_IPC_FLAG_FIFO);
if(MainTask_msg == RT_NULL || UITask_msg == RT_NULL)
{
__NOP();
LOG_E("BOOT", "mq create fail main=%p ui=%p", MainTask_msg, UITask_msg);
}
ret = rt_thread_init(&TaskScanThread,
@ -220,36 +231,126 @@ rt_err_t TaskInit(void)
void StartTask(void)
{
rt_thread_startup(&TaskScanThread);
rt_thread_startup(&TaskBTHandleThread);
rt_thread_startup(&TaskBTRecvThread);
rt_thread_startup(&TaskAutobandThread);
/* 扫描任务可能已由 StartScanTask 拉起;重复 startup 会被 RT-Thread 忽略 */
StartScanTask();
if ((TaskBTHandleThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE)
{
rt_thread_init(&TaskBTHandleThread,
"TaskBTHandleThread",
TaskBTHandleThread_entry,
RT_NULL,
&TaskBTTHandlehread_Stack,
sizeof(TaskBTTHandlehread_Stack),
5,
20);
}
if ((TaskBTHandleThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT)
rt_thread_startup(&TaskBTHandleThread);
if ((TaskBTRecvThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE)
{
rt_thread_init(&TaskBTRecvThread,
"TaskBTRecvThread",
TaskBTRecvThread_entry,
RT_NULL,
&TaskBTRecvThread_Stack,
sizeof(TaskBTRecvThread_Stack),
5,
20);
}
if ((TaskBTRecvThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT)
rt_thread_startup(&TaskBTRecvThread);
if ((TaskAutobandThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_CLOSE)
{
rt_thread_init(&TaskAutobandThread,
"TaskAutobandThread",
TaskAutobandThread_entry,
RT_NULL,
&TaskAutobandThread_Stack,
sizeof(TaskAutobandThread_Stack),
4,
20);
}
if ((TaskAutobandThread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT)
rt_thread_startup(&TaskAutobandThread);
}
void StartTouchTask(void)
{
rt_thread_startup(&TaskTouchThread);
rt_uint8_t st = (rt_uint8_t)(TaskTouchThread.stat & RT_THREAD_STAT_MASK);
XPT2046_Init();
app_touch_reset();
LOG_TPI("gpio reinit before touch task");
if (st == RT_THREAD_CLOSE)
{
rt_thread_init(&TaskTouchThread,
"TaskTouchThread",
TaskTouchThread_entry,
RT_NULL,
&TaskTouchThread_Stack,
sizeof(TaskTouchThread_Stack),
5,
20);
st = RT_THREAD_INIT;
}
if (st == RT_THREAD_INIT) {
rt_thread_startup(&TaskTouchThread);
LOG_TPI("touch task started stat=%u", (unsigned)TaskTouchThread.stat);
}
}
void StartScanTask(void)
{
rt_thread_startup(&TaskScanThread);
rt_uint8_t st = (rt_uint8_t)(TaskScanThread.stat & RT_THREAD_STAT_MASK);
if (st == RT_THREAD_CLOSE)
{
rt_thread_init(&TaskScanThread,
"TaskScanThread",
TaskScanThread_entry,
RT_NULL,
&TaskScanThread_Stack,
sizeof(TaskScanThread_Stack),
5,
20);
st = RT_THREAD_INIT;
}
if (st == RT_THREAD_INIT)
rt_thread_startup(&TaskScanThread);
}
void StopFullTask(void)
{
if((TaskScanThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE)
LOG_I("TASK", "StopFullTask begin");
if((TaskScanThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) {
LOG_I("TASK", "stop ScanThread");
rt_thread_detach(&TaskScanThread);
if((TaskUIThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE)
}
if((TaskUIThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) {
LOG_I("TASK", "stop UIThread");
rt_thread_detach(&TaskUIThread);
if((TaskTouchThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE)
}
if((TaskTouchThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) {
LOG_I("TASK", "stop TouchThread");
rt_thread_detach(&TaskTouchThread);
if((TaskMainThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE)
}
if((TaskMainThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) {
LOG_I("TASK", "stop MainThread");
rt_thread_detach(&TaskMainThread);
if((TaskBTHandleThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE)
}
if((TaskBTHandleThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) {
LOG_I("TASK", "stop BTHandleThread");
rt_thread_detach(&TaskBTHandleThread);
if((TaskBTRecvThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE)
}
if((TaskBTRecvThread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_CLOSE) {
LOG_I("TASK", "stop BTRecvThread");
rt_thread_detach(&TaskBTRecvThread);
}
LOG_I("TASK", "StopFullTask done");
}

View File

@ -3,30 +3,20 @@
* The Embedded Experts *
* www.segger.com *
**********************************************************************
* *
* SEGGER RTT * Real Time Transfer for embedded targets *
* https://github.com/SEGGERMicro/RTT *
* *
**********************************************************************
---------------------------END-OF-HEADER------------------------------
Purpose : User configuration file for RTT.
For available configuration,
refer to SEGGER_RTT_ConfDefaults.h.
----------------------------------------------------------------------
*/
#ifndef SEGGER_RTT_CONF_H
#define SEGGER_RTT_CONF_H
/*********************************************************************
*
* Defines, configurable
*
**********************************************************************
*/
#define SEGGER_RTT_MAX_NUM_UP_BUFFERS (3)
#define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (3)
#define BUFFER_SIZE_UP (2048)
#define BUFFER_SIZE_DOWN (256)
#define SEGGER_RTT_PRINTF_BUFFER_SIZE (256u)
#endif
/*************************** End of file ****************************/

79
tools/LOG_README.md Normal file
View File

@ -0,0 +1,79 @@
# K1 设备日志导出(J-Link RTT)
## 用途
固件运行时将启动/关机、触摸、按键、UI 切换、异常等事件写入 RAM 环形缓冲(约 4KB / 32 条),并通过 SEGGER RTT 实时输出。现场复现问题后,用 J-Link 一键导出日志文件发回分析。
## 前置条件
- J-Link 调试器 + SWD 连接设备
- Python 3.8+
- 依赖:`pip install pylink-square`
- 已烧录包含 `app_log` 模块的固件
## 操作步骤
1. 设备上电,复现问题(例如模式选择页触摸无响应)。
2. PC 连接 J-Link,**不要**随意 Reset(会丢失 RAM 日志;若必须复位,请先导出或复现后再导出)。
3. 在项目 `tools` 目录执行:
```bash
python rtt_log_dump.py --out problem.log
```
4. 将生成的 `problem.log` 发送给研发分析。
## 其他 RTT 命令(RTT Viewer 终端输入)
| 命令 | 说明 |
|------|------|
| `log dump` | 导出 RAM 中全部历史日志 |
| `log clear` | 清空 RAM 日志 |
| `log status` | 查看 boot 次数、条数、溢出、当前 UI 页 |
| `log level D` | 开启 Debug 级(含触摸细节);`I` 仅 Info 以上 |
| `sys reset` | MCU 软件复位(自动重新开机,**RAM 日志会丢失**) |
或通过脚本(**复位后脚本会自动 `go()` 释放 CPU,避免 J-Link 停核黑屏**):
```bash
python rtt_reset.py
```
若固件较旧无 `sys reset`,脚本会自动用 J-Link 硬件复位兜底。
## 日志格式
```
[tick_ms][LVL][CAT] message key=val ...
```
类别 CAT:`BOOT` `PWR` `UI` `TP` `KEY` `MSG` `NVM` `TASK` `ERR` `ADC`
## 配套工具
- 屏幕截图:`python rtt_lcd_capture.py --out screen.png`
- 日志导出:`python rtt_log_dump.py --out problem.log`
- 远程复位:`python rtt_reset.py`
## 触摸问题排查要点
导出后重点 grep:
- `[TP]` — raw/cal 坐标、irq 状态
- `[MSG] id=12` — 触摸消息是否发出
- `[UI] mode_select miss` — UI 命中测试是否失败
## 已验证(2026-08-31)
烧录:`cspybat` + `C:\Temp\k1flash\YNGJ-GT1-M.out` 下载,J-Link reset/run 后约 4s 开机。
日志导出示例(`python rtt_log_dump.py --out test.log`):
```
[00185][I][BOOT] app_log init boot=1
[00224][I][NVM ] init ok ver=2
[00224][I][BOOT] main init done ui=Idle
[00224][I][PWR ] auto power_on
[00401][I][PWR ] periph ready send POWER_ON
[00500][I][MSG ] id=0 hi=0 lo=0
```

View File

@ -14,10 +14,13 @@ if not exist "%OUT%" (
exit /b 1
)
REM Avoid stale debugger locking SWD (cspybat can hang otherwise)
taskkill /F /IM cspybat.exe >nul 2>&1
mkdir "%STAGE%" 2>nul
copy /Y "%OUT%" "%STAGE%\YNGJ-GT1-M.out" >nul
echo [1/3] Downloading...
echo [1/3] Downloading (may take ~60s)...
%CSPY% -f "%GEN%" "--debug_file=%STAGE%\YNGJ-GT1-M.out" --download_only --backend -f "%DRV%"
if errorlevel 1 exit /b 1
@ -25,7 +28,8 @@ echo [2/3] Reset and run...
%JLINK% -CommandFile "%~dp0reset_run.jlink"
if errorlevel 1 exit /b 1
echo [3/3] Wait for auto PowerOn + logo (about 3s)...
timeout /t 3 /nobreak >nul
echo [3/3] Wait for auto PowerOn + logo (about 4s)...
timeout /t 4 /nobreak >nul
echo Done. UI should be on mode-select screen.
echo Log dump: python rtt_log_dump.py --out problem.log
exit /b 0

View File

@ -0,0 +1,129 @@
#!/usr/bin/env python3
"""Convert Doc/UI 0831 trial PNG slices to RGB565 C arrays for mode-select."""
from PIL import Image, ImageDraw
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "tools" / "assets_0831"
OUT_C = ROOT / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_ModeSelect0831.c"
OUT_H = ROOT / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_ModeSelect0831.h"
PREV = SRC / "preview_mode_select.png"
# Only emit composite rows (icon+MiSans text) to save Flash.
ROW_W, ROW_H = 180, 52
NAMES = [
("Universal", "mode_row_0.png"), # 万能
("Normal", "mode_row_1.png"), # 普通
("Expert", "mode_row_2.png"), # 专业
]
def rgba_to_rgb565_bytes(im: Image.Image) -> bytes:
im = im.convert("RGBA")
px = im.load()
w, h = im.size
out = bytearray(w * h * 2)
i = 0
for y in range(h):
for x in range(w):
r, g, b, a = px[x, y]
if a < 40 or (r < 12 and g < 12 and b < 12):
out[i] = 0
out[i + 1] = 0
else:
r = (r * a) // 255
g = (g * a) // 255
b = (b * a) // 255
c = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)
out[i] = (c >> 8) & 0xFF
out[i + 1] = c & 0xFF
i += 2
return bytes(out)
def fit_on_canvas(src_im: Image.Image, cw: int, ch: int) -> Image.Image:
im = src_im.convert("RGBA")
bbox = im.getbbox()
if bbox:
im = im.crop(bbox)
sw, sh = im.size
scale = min(cw / sw, ch / sh)
nw = max(1, int(round(sw * scale)))
nh = max(1, int(round(sh * scale)))
im = im.resize((nw, nh), Image.Resampling.LANCZOS)
canvas = Image.new("RGBA", (cw, ch), (0, 0, 0, 255))
ox = (cw - nw) // 2
oy = (ch - nh) // 2
canvas.paste(im, (ox, oy), im)
return canvas
def emit_array(name: str, data: bytes, w: int, h: int, lines: list) -> None:
lines.append(f"const unsigned char {name}[{len(data)}] = {{ /* {w}x{h} RGB565 BE */")
for i in range(0, len(data), 16):
chunk = data[i : i + 16]
hexes = ",".join(f"0x{b:02X}" for b in chunk)
lines.append(hexes + ",")
lines.append("};")
lines.append("")
def main() -> None:
c_lines = [
'#include "Drv_ILI9341_Lcd_Image_ModeSelect0831.h"',
"",
"/* Auto-generated from Doc/UI 0831 trial PNG. Do not hand-edit. */",
"",
]
h_lines = [
"#ifndef __DRV_ILI9341_LCD_IMAGE_MODESELECT0831_H",
"#define __DRV_ILI9341_LCD_IMAGE_MODESELECT0831_H",
"",
'#include "stdint.h"',
"",
f"#define UI_MODE0831_ROW_W {ROW_W}",
f"#define UI_MODE0831_ROW_H {ROW_H}",
"",
]
screen = Image.new("RGBA", (240, 320), (0, 0, 0, 255))
d = ImageDraw.Draw(screen)
d.rectangle([0, 0, 239, 27], fill=(20, 28, 40, 255))
btn_ys = [40, 132, 224]
btn_w, btn_h, btn_r = 220, 87, 10
total = 0
for idx, (tag, row_n) in enumerate(NAMES):
row = fit_on_canvas(Image.open(SRC / row_n), ROW_W, ROW_H)
row.save(SRC / f"gen_{tag.lower()}_row.png")
rdata = rgba_to_rgb565_bytes(row)
total += len(rdata)
rname = f"gImage_Mode0831_{tag}_Row_{ROW_W}_{ROW_H}"
emit_array(rname, rdata, ROW_W, ROW_H, c_lines)
h_lines.append(f"extern const unsigned char {rname}[];")
h_lines.append("")
by = btn_ys[idx]
bx = (240 - btn_w) // 2
fill = (47, 54, 71, 255) if idx == 0 else (25, 32, 45, 255)
d.rounded_rectangle([bx, by, bx + btn_w - 1, by + btn_h - 1], radius=btn_r, fill=fill)
rx = bx + (btn_w - ROW_W) // 2
ry = by + (btn_h - ROW_H) // 2
screen.paste(row, (rx, ry), row)
h_lines += ["#endif", ""]
OUT_H.write_text("\n".join(h_lines), encoding="utf-8")
OUT_C.write_text("\n".join(c_lines), encoding="utf-8")
screen.convert("RGB").save(PREV)
print(f"wrote {OUT_C} ({OUT_C.stat().st_size} bytes)")
print(f"wrote {OUT_H}")
print(f"flash payload ~{total} bytes")
print(f"preview {PREV}")
if __name__ == "__main__":
main()

187
tools/rtt_log_dump.py Normal file
View File

@ -0,0 +1,187 @@
#!/usr/bin/env python3
"""Pull K1 device log ring buffer via J-Link RTT (log dump command)."""
from __future__ import annotations
import argparse
import os
import sys
import time
from datetime import datetime
DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A")
DUMP_CMD = b"log dump\n"
PROBE_CMD = b"tp status\n"
END_MARKERS = (b"LOG_DUMP_END",)
def import_deps():
try:
import pylink # noqa: F401
except ImportError as exc:
raise SystemExit("Missing dependency: pip install pylink-square") from exc
def connect_jlink(device: str):
import pylink
jlink = pylink.JLink()
jlink.open()
try:
jlink.exec_command("HideDeviceSelection = 1")
except Exception:
pass
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
last_error = None
for candidate in (device, *DEFAULT_DEVICES):
try:
try:
jlink.exec_command(f"Device = {candidate}")
except Exception:
pass
jlink.connect(candidate)
print(f"Connected as {candidate}")
return jlink
except pylink.errors.JLinkException as exc:
last_error = exc
jlink.close()
raise SystemExit(f"Failed to connect J-Link: {last_error}")
def find_rtt_control_block(jlink, ram_base: int = 0x20000000, ram_size: int = 0x18000) -> int | None:
needle = b"SEGGER RTT"
chunk = 0x1000
was_halted = jlink.halted()
if not was_halted:
jlink.halt()
try:
for off in range(0, ram_size, chunk):
data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off)))
idx = data.find(needle)
if idx >= 0:
return ram_base + off + idx
finally:
if not was_halted:
if hasattr(jlink, "restart"):
jlink.restart()
else:
jlink.go()
time.sleep(0.1)
return None
def wait_rtt_ready(jlink, timeout_s: float = 15.0) -> None:
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
if jlink.rtt_get_num_up_buffers() > 0:
return
except Exception:
pass
time.sleep(0.2)
raise SystemExit("RTT control block found but buffers not ready")
def send_command(jlink, payload: bytes, retries: int = 30) -> None:
for _ in range(retries):
wrote = jlink.rtt_write(0, list(payload))
if wrote > 0:
print(f"Sent command ({wrote} bytes): {payload.decode('ascii', errors='replace').strip()}")
return
time.sleep(0.2)
raise SystemExit("Failed to send RTT down command on channel 0")
def dump_log(out_path: str, device: str, timeout_s: float) -> None:
jlink = connect_jlink(device)
lines: list[str] = []
try:
cb = find_rtt_control_block(jlink)
if cb is None:
raise SystemExit("SEGGER RTT control block not found in SRAM")
print(f"RTT CB @ 0x{cb:08X}")
jlink.rtt_start(cb)
wait_rtt_ready(jlink)
# Drain stale RTT output.
for _ in range(5):
stale = jlink.rtt_read(0, 4096)
if stale:
text = bytes(stale).decode("utf-8", errors="replace")
if text.strip():
print("RTT0 stale:", text.strip()[:200])
time.sleep(0.05)
send_command(jlink, PROBE_CMD)
time.sleep(0.25)
send_command(jlink, DUMP_CMD)
buffer = b""
deadline = time.time() + timeout_s
started = False
while time.time() < deadline:
chunk = jlink.rtt_read(0, 4096)
if chunk:
buffer += bytes(chunk)
if not started and b"LOG_DUMP_BEGIN" in buffer:
started = True
if b"LOG_DUMP_END" in buffer:
break
else:
time.sleep(0.01)
if b"LOG_DUMP_END" not in buffer:
raise SystemExit("Timeout waiting for LOG_DUMP_END (is firmware with app_log flashed?)")
text = buffer.decode("utf-8", errors="replace")
capture = False
for line in text.splitlines():
if line.strip() == "LOG_DUMP_BEGIN":
capture = True
continue
if line.strip() == "LOG_DUMP_END":
break
if capture:
lines.append(line)
header = (
f"# K1 log dump {datetime.now().isoformat(timespec='seconds')}\n"
f"# device={device}\n"
)
body = "\n".join(lines) + ("\n" if lines else "")
with open(out_path, "w", encoding="utf-8", newline="\n") as f:
f.write(header)
f.write(body)
print(f"Saved {len(lines)} log lines to {out_path}")
finally:
try:
jlink.rtt_stop()
except Exception:
pass
jlink.close()
def main() -> None:
parser = argparse.ArgumentParser(description="Dump K1 RAM log ring via J-Link RTT")
parser.add_argument(
"--out",
default=f"k1_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log",
help="Output log file path",
)
parser.add_argument("--device", default="AT32F403AC", help="J-Link device name")
parser.add_argument("--timeout", type=float, default=20.0, help="Seconds to wait for dump")
args = parser.parse_args()
import_deps()
dump_log(args.out, args.device, args.timeout)
if os.name == "nt" and os.path.isfile(args.out):
print(f"Full path: {os.path.abspath(args.out)}")
if __name__ == "__main__":
main()

90
tools/rtt_recover.py Normal file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""Recover K1 from J-Link halt / black screen: hardware reset + run (no RTT required)."""
from __future__ import annotations
import argparse
import time
DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A")
def import_deps():
try:
import pylink # noqa: F401
except ImportError as exc:
raise SystemExit("Missing dependency: pip install pylink-square") from exc
def connect_jlink(device: str):
import pylink
jlink = pylink.JLink()
jlink.open()
try:
jlink.exec_command("HideDeviceSelection = 1")
except Exception:
pass
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
last_error = None
for candidate in (device, *DEFAULT_DEVICES):
try:
try:
jlink.exec_command(f"Device = {candidate}")
except Exception:
pass
jlink.connect(candidate)
print(f"Connected as {candidate}")
return jlink
except pylink.errors.JLinkException as exc:
last_error = exc
jlink.close()
raise SystemExit(f"Failed to connect J-Link: {last_error}")
def run_go(jlink) -> None:
try:
jlink.reset(halt=False)
except Exception:
pass
try:
if hasattr(jlink, "restart"):
jlink.restart()
else:
jlink.go()
except Exception:
jlink.go()
def recover(device: str, wait_boot_s: float) -> None:
jlink = connect_jlink(device)
try:
halted = jlink.halted()
print(f"Target halted={halted}")
print("Reset + run...")
run_go(jlink)
if wait_boot_s > 0:
print(f"Waiting {wait_boot_s:.0f}s for auto boot...")
time.sleep(wait_boot_s)
run_go(jlink)
print("Done. Screen should show UI; if still black, run flash_app.bat")
finally:
try:
run_go(jlink)
except Exception:
pass
jlink.close()
def main() -> None:
parser = argparse.ArgumentParser(description="Recover K1 from halt/black screen via J-Link")
parser.add_argument("--device", default="AT32F403AC")
parser.add_argument("--wait-boot", type=float, default=4.0)
args = parser.parse_args()
import_deps()
recover(args.device, args.wait_boot)
if __name__ == "__main__":
main()

204
tools/rtt_reset.py Normal file
View File

@ -0,0 +1,204 @@
#!/usr/bin/env python3
"""Send sys reset via J-Link RTT to reboot K1 MCU (software NVIC reset)."""
from __future__ import annotations
import argparse
import time
DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A")
RESET_CMD = b"sys reset\n"
ACK = b"SYS_RESET_OK"
def import_deps():
try:
import pylink # noqa: F401
except ImportError as exc:
raise SystemExit("Missing dependency: pip install pylink-square") from exc
def connect_jlink(device: str):
import pylink
jlink = pylink.JLink()
jlink.open()
try:
jlink.exec_command("HideDeviceSelection = 1")
except Exception:
pass
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
last_error = None
for candidate in (device, *DEFAULT_DEVICES):
try:
try:
jlink.exec_command(f"Device = {candidate}")
except Exception:
pass
jlink.connect(candidate)
print(f"Connected as {candidate}")
return jlink
except pylink.errors.JLinkException as exc:
last_error = exc
jlink.close()
raise SystemExit(f"Failed to connect J-Link: {last_error}")
def find_rtt_control_block(jlink, ram_base: int = 0x20000000, ram_size: int = 0x18000) -> int | None:
needle = b"SEGGER RTT"
chunk = 0x1000
was_halted = jlink.halted()
did_halt = False
if not was_halted:
jlink.halt()
did_halt = True
try:
for off in range(0, ram_size, chunk):
data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off)))
idx = data.find(needle)
if idx >= 0:
return ram_base + off + idx
finally:
if did_halt:
resume_cpu(jlink)
return None
def hw_reset_and_run(jlink, wait_s: float = 4.0) -> None:
"""J-Link 硬件复位并释放 CPU(RTT 不可用时的唯一可靠恢复手段)。"""
print("Hardware reset + run...")
resume_cpu(jlink)
if wait_s > 0:
print(f"Waiting {wait_s:.0f}s for boot...")
time.sleep(wait_s)
resume_cpu(jlink)
def resume_cpu(jlink) -> None:
"""J-Link often halts the core on reset; must run() or UI stays black."""
try:
jlink.rtt_stop()
except Exception:
pass
try:
jlink.reset(halt=False)
except Exception:
pass
try:
if hasattr(jlink, "restart"):
jlink.restart()
else:
jlink.go()
except Exception:
jlink.go()
time.sleep(0.05)
def wait_rtt_ready(jlink, timeout_s: float = 15.0) -> None:
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
if jlink.rtt_get_num_up_buffers() > 0:
return
except Exception:
pass
time.sleep(0.2)
raise SystemExit("RTT control block found but buffers not ready")
def send_reset(device: str, timeout_s: float, wait_boot_s: float, hw_fallback: bool) -> None:
jlink = connect_jlink(device)
ack_seen = False
try:
if jlink.halted():
print("Target was halted, resuming before RTT...")
resume_cpu(jlink)
time.sleep(0.2)
cb = find_rtt_control_block(jlink)
if cb is None:
print("SEGGER RTT control block not found in SRAM.")
if hw_fallback:
hw_reset_and_run(jlink, wait_boot_s)
print("Done (hardware recovery). If still black, run: flash_app.bat")
else:
raise SystemExit(
"RTT not available. Re-run without --no-hw-fallback, or flash_app.bat"
)
return
print(f"RTT CB @ 0x{cb:08X}")
resume_cpu(jlink)
time.sleep(0.15)
jlink.rtt_start(cb)
wait_rtt_ready(jlink)
wrote = 0
for _ in range(30):
wrote = jlink.rtt_write(0, list(RESET_CMD))
if wrote > 0:
break
time.sleep(0.2)
if wrote <= 0:
raise SystemExit("Failed to send sys reset on RTT down channel 0")
print(f"Sent sys reset ({wrote} bytes)")
deadline = time.time() + timeout_s
while time.time() < deadline:
chunk = jlink.rtt_read(0, 1024)
if chunk:
text = bytes(chunk)
if ACK in text:
ack_seen = True
print("SYS_RESET_OK (firmware handled reset)")
break
time.sleep(0.05)
if not ack_seen:
print("No SYS_RESET_OK from firmware.")
if hw_fallback:
hw_reset_and_run(jlink, 0.0)
else:
print("Hint: re-run with --hw-fallback or flash latest firmware.")
print("Resuming CPU after reset...")
resume_cpu(jlink)
if wait_boot_s > 0:
print(f"Waiting {wait_boot_s:.0f}s for auto boot...")
time.sleep(wait_boot_s)
resume_cpu(jlink)
print("Done. Device should be back on mode-select screen.")
finally:
try:
resume_cpu(jlink)
except Exception:
pass
jlink.close()
def main() -> None:
parser = argparse.ArgumentParser(description="Reboot K1 via RTT sys reset command")
parser.add_argument("--device", default="AT32F403AC", help="J-Link device name")
parser.add_argument("--timeout", type=float, default=3.0, help="Seconds to wait for ACK")
parser.add_argument(
"--wait-boot",
type=float,
default=4.0,
help="Seconds to wait after reset for auto power-on (0 to skip)",
)
parser.add_argument(
"--no-hw-fallback",
action="store_true",
help="Do not use J-Link hardware reset when firmware ACK is missing",
)
args = parser.parse_args()
import_deps()
send_reset(args.device, args.timeout, args.wait_boot, not args.no_hw_fallback)
if __name__ == "__main__":
main()

207
tools/rtt_touch_live.py Normal file
View File

@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""Live-stream K1 touch logs via J-Link RTT while you touch the screen.
Usage:
python rtt_touch_live.py
python rtt_touch_live.py --all # show every RTT line
python rtt_touch_live.py --seconds 120 # auto-stop after N seconds
"""
from __future__ import annotations
import argparse
import sys
import time
DEFAULT_DEVICES = ("Cortex-M4", "AT32F403AC", "AT32F403A")
LIVE_ON_CMD = b"tp live\n"
LIVE_OFF_CMD = b"tp live off\n"
# Default: touch / UI hit / message path
DEFAULT_KEYS = ("[TP", "[UI", "[MSG")
def import_deps():
try:
import pylink # noqa: F401
except ImportError as exc:
raise SystemExit("Missing dependency: pip install pylink-square") from exc
def connect_jlink(device: str):
import pylink
jlink = pylink.JLink()
jlink.open()
try:
jlink.exec_command("HideDeviceSelection = 1")
except Exception:
pass
jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
last_error = None
for candidate in (device, *DEFAULT_DEVICES):
try:
try:
jlink.exec_command(f"Device = {candidate}")
except Exception:
pass
jlink.connect(candidate)
print(f"Connected as {candidate}", flush=True)
return jlink
except pylink.errors.JLinkException as exc:
last_error = exc
jlink.close()
raise SystemExit(f"Failed to connect J-Link: {last_error}")
def find_rtt_control_block(jlink, ram_base: int = 0x20000000, ram_size: int = 0x18000) -> int | None:
needle = b"SEGGER RTT"
chunk = 0x1000
was_halted = jlink.halted()
if not was_halted:
jlink.halt()
try:
for off in range(0, ram_size, chunk):
data = bytes(jlink.memory_read8(ram_base + off, min(chunk, ram_size - off)))
idx = data.find(needle)
if idx >= 0:
return ram_base + off + idx
finally:
if not was_halted:
if hasattr(jlink, "restart"):
jlink.restart()
else:
jlink.go()
time.sleep(0.1)
return None
def wait_rtt_ready(jlink, timeout_s: float = 15.0) -> None:
deadline = time.time() + timeout_s
while time.time() < deadline:
try:
if jlink.rtt_get_num_up_buffers() > 0:
return
except Exception:
pass
time.sleep(0.2)
raise SystemExit("RTT control block found but buffers not ready")
def send_command(jlink, payload: bytes, retries: int = 40) -> None:
for _ in range(retries):
wrote = jlink.rtt_write(0, list(payload))
if wrote > 0:
print(f">> {payload.decode('ascii', errors='replace').strip()}", flush=True)
return
time.sleep(0.15)
print("WARN: failed to send RTT command", flush=True)
def want_line(line: str, show_all: bool, keys: tuple[str, ...]) -> bool:
if show_all:
return True
return any(k in line for k in keys)
def run_live(device: str, show_all: bool, seconds: float, out_path: str | None) -> None:
jlink = connect_jlink(device)
out_fp = open(out_path, "w", encoding="utf-8", newline="\n") if out_path else None
try:
if jlink.halted():
if hasattr(jlink, "restart"):
jlink.restart()
else:
jlink.go()
time.sleep(0.3)
cb = find_rtt_control_block(jlink)
if cb is None:
raise SystemExit("SEGGER RTT control block not found — is app running?")
print(f"RTT CB @ 0x{cb:08X}", flush=True)
jlink.rtt_start(cb)
wait_rtt_ready(jlink)
# Drain backlog
for _ in range(8):
junk = jlink.rtt_read(0, 4096)
if not junk:
break
print("Waiting ~8s for UI/touch task...", flush=True)
time.sleep(8.0)
for _ in range(4):
junk = jlink.rtt_read(0, 4096)
if not junk:
break
# Event-only: firmware no longer floods idle samples.
# Optional: tp live just tags the session; leave it off by default.
print("Event log ON — only touch events will print. Touch the screen. Ctrl+C to stop.", flush=True)
print("-" * 60, flush=True)
buf = b""
t0 = time.time()
while True:
if seconds > 0 and (time.time() - t0) >= seconds:
print("\n(time limit reached)", flush=True)
break
chunk = jlink.rtt_read(0, 4096)
if chunk:
buf += bytes(chunk)
while True:
nl = buf.find(b"\n")
if nl < 0:
break
raw = buf[:nl]
buf = buf[nl + 1 :]
line = raw.decode("utf-8", errors="replace").rstrip("\r")
if not line:
continue
if want_line(line, show_all, DEFAULT_KEYS):
print(line, flush=True)
if out_fp:
out_fp.write(line + "\n")
out_fp.flush()
else:
time.sleep(0.01)
except KeyboardInterrupt:
print("\nStopped by user.", flush=True)
finally:
try:
jlink.rtt_stop()
except Exception:
pass
try:
if hasattr(jlink, "restart"):
jlink.restart()
else:
jlink.go()
except Exception:
pass
jlink.close()
if out_fp:
out_fp.close()
print(f"Saved: {out_path}", flush=True)
def main() -> None:
parser = argparse.ArgumentParser(description="Live K1 touch RTT monitor")
parser.add_argument("--device", default="AT32F403AC")
parser.add_argument("--all", action="store_true", help="Print all RTT lines")
parser.add_argument("--seconds", type=float, default=0, help="Auto stop after N seconds (0=forever)")
parser.add_argument(
"--out",
default="",
help="Also append filtered lines to this file",
)
args = parser.parse_args()
import_deps()
run_live(args.device, args.all, args.seconds, args.out or None)
if __name__ == "__main__":
main()