Compare commits

...

1 Commits

Author SHA1 Message Date
edisondeng
d8df5d49d3 fix: 修复 main.c/event.c/lcd.c 的中文注释乱码,统一为 UTF-8 编码
lcd.c 原本整体是 GBK 编码,直接转码即可。main.c 与 event.c 曾被
Keil 环境以错误编码保存导致部分注释损坏,本次以历史提交中未损坏的
版本为基底恢复正确注释文本,并将其后新增的真实代码改动(RGB 三色
诊断指令、SOS 按键状态机解锁)对应的注释一并还原,不涉及任何代码逻辑变更。
2026-07-31 09:54:59 +08:00
4 changed files with 308 additions and 301 deletions

View File

@@ -6,17 +6,17 @@
#include "xtell.h" #include "xtell.h"
#include "../Drivers/adc.h" #include "../Drivers/adc.h"
// 定义事件<EFBFBD><EFBFBD>形队列的<EFBFBD><EFBFBD>大深度为 8 // 定义事件形队列的大深度为 8
#define EVENT_QUEUE_DEPTH 8 #define EVENT_QUEUE_DEPTH 8
// <EFBFBD><EFBFBD>形队列存储数<EFBFBD><EFBFBD> (存放在单片机 xdata 拓展 RAM区中) // 形队列存储数 (存放在单片机 xdata 拓展 RAM区中)
static SystemEvent xdata event_queue[EVENT_QUEUE_DEPTH]; static SystemEvent xdata event_queue[EVENT_QUEUE_DEPTH];
static u8 queue_head = 0; // 队<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> static u8 queue_head = 0; // 队首读指针
static u8 queue_tail = 0; // 队尾写指<EFBFBD><EFBFBD> static u8 queue_tail = 0; // 队尾写指
static u8 queue_count = 0; // 当前队列内有效事件<EFBFBD><EFBFBD> static u8 queue_count = 0; // 当前队列内有效事件
/** /**
* @brief 初<EFBFBD><EFBFBD>化/清空事件队列 * @brief 初化/清空事件队列
*/ */
void EventQueue_Init(void) void EventQueue_Init(void)
{ {
@@ -26,63 +26,63 @@ void EventQueue_Init(void)
} }
/** /**
* @brief 将普通事件压入队<EFBFBD><EFBFBD> * @brief 将普通事件压入队
* @param evt 输入的系统事<EFBFBD><EFBFBD> * @param evt 输入的系统事
* @return bit 1-成功<EFBFBD><EFBFBD>0-失败 * @return bit 1-成功0-失败
*/ */
bit EventQueue_Push(SystemEvent evt) bit EventQueue_Push(SystemEvent evt)
{ {
if (queue_count >= EVENT_QUEUE_DEPTH) return 0; // 队列满,丢弃事件 if (queue_count >= EVENT_QUEUE_DEPTH) return 0; // 队列满,丢弃事件
EA = 0; // 关闭全局<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,保护临界区操作的原子<EFBFBD> EA = 0; // 关闭全局中断,保护临界区操作的原子
event_queue[queue_tail] = evt; // 存入队尾 event_queue[queue_tail] = evt; // 存入队尾
queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH; // 尾指针向后移<EFBFBD><EFBFBD><EFBFBD><EFBFBD> queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH; // 尾指针向后移动,回环
queue_count++; // 有效计数<EFBFBD><EFBFBD> 1 queue_count++; // 有效计数 1
EA = 1; // 恢<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> EA = 1; // 恢复全局中断
return 1; return 1;
} }
/** /**
* @brief 将高优先<EFBFBD><EFBFBD>/紧<><E7B4A7>事件强行插到队<EFBFBD><EFBFBD> * @brief 将高优先级/紧急事件强行插到队
* @param evt 输入的系统事<EFBFBD><EFBFBD> * @param evt 输入的系统事
* @return bit 1-成功<EFBFBD><EFBFBD>0-失败 * @return bit 1-成功0-失败
*/ */
bit EventQueue_InsertFront(SystemEvent evt) bit EventQueue_InsertFront(SystemEvent evt)
{ {
if (queue_count >= EVENT_QUEUE_DEPTH) return 0; // 队列满,插队失败 if (queue_count >= EVENT_QUEUE_DEPTH) return 0; // 队列满,插队失败
EA = 0; // 屏蔽<EFBFBD><EFBFBD><EFBFBD><EFBFBD> EA = 0; // 屏蔽中断
// 头指针向前移动一位(带回<EFBFBD><EFBFBD>防溢出) // 头指针向前移动一位(带回防溢出)
queue_head = (queue_head - 1 + EVENT_QUEUE_DEPTH) % EVENT_QUEUE_DEPTH; queue_head = (queue_head - 1 + EVENT_QUEUE_DEPTH) % EVENT_QUEUE_DEPTH;
event_queue[queue_head] = evt; // 存入腾出的队首位<EFBFBD><EFBFBD> event_queue[queue_head] = evt; // 存入腾出的队首位
queue_count++; // 计数<EFBFBD><EFBFBD> 1 queue_count++; // 计数 1
EA = 1; // 恢<EFBFBD><EFBFBD><EFBFBD><EFBFBD> EA = 1; // 恢复中断
return 1; return 1;
} }
/** /**
* @brief 从队首弹出一<EFBFBD><EFBFBD>缓冲事件 * @brief 从队首弹出一缓冲事件
* @return SystemEvent 返回弹出的事件<EFBFBD><EFBFBD>若空返回空闲事<EFBFBD><EFBFBD> * @return SystemEvent 返回弹出的事件若空返回空闲事
*/ */
SystemEvent EventQueue_Pop(void) SystemEvent EventQueue_Pop(void)
{ {
SystemEvent evt; SystemEvent evt;
// 默<EFBFBD><EFBFBD>空事件属<EFBFBD> // 默空事件属
evt.key_event = KEY_EVENT_NONE; evt.key_event = KEY_EVENT_NONE;
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
evt.extra_data = 0; evt.extra_data = 0;
evt.extra_size = 0; evt.extra_size = 0;
if (queue_count == 0) return evt; // 队空,直接返回空闲事<EFBFBD><EFBFBD> if (queue_count == 0) return evt; // 队空,直接返回空闲事
EA = 0; // 屏蔽<EFBFBD><EFBFBD><EFBFBD><EFBFBD> EA = 0; // 屏蔽中断
evt = event_queue[queue_head]; // 读取队<EFBFBD><EFBFBD><EFBFBD><EFBFBD> evt = event_queue[queue_head]; // 读取队首数据
queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH; // 头指针向后移<EFBFBD><EFBFBD><EFBFBD><EFBFBD> queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH; // 头指针向后移动,回环
queue_count--; // 有效计数<EFBFBD><EFBFBD> 1 queue_count--; // 有效计数 1
EA = 1; // 恢<EFBFBD><EFBFBD><EFBFBD><EFBFBD> EA = 1; // 恢复中断
return evt; return evt;
} }
/** /**
* @brief 判断队列<EFBFBD><EFBFBD>否为<EFBFBD><EFBFBD> * @brief 判断队列是否为空
*/ */
bit EventQueue_IsEmpty(void) bit EventQueue_IsEmpty(void)
{ {
@@ -90,7 +90,7 @@ bit EventQueue_IsEmpty(void)
} }
/** /**
* @brief 判断队列<EFBFBD><EFBFBD>否已<EFBFBD><EFBFBD> * @brief 判断队列是否已满
*/ */
bit EventQueue_IsFull(void) bit EventQueue_IsFull(void)
{ {
@@ -98,7 +98,7 @@ bit EventQueue_IsFull(void)
} }
/** /**
* @brief 获取当前队列<EFBFBD><EFBFBD>的事件<EFBFBD><EFBFBD> * @brief 获取当前队列的事件
*/ */
u8 EventQueue_GetCount(void) u8 EventQueue_GetCount(void)
{ {
@@ -106,98 +106,98 @@ u8 EventQueue_GetCount(void)
} }
/** /**
* @brief 事件分发器核心轮询<EFBFBD><EFBFBD> * @brief 事件分发器核心轮询
* @details 逐一处理队列<EFBFBD><EFBFBD>的事件,并根<EFBFBD><EFBFBD>系统当前的运行模式进行拦<EFBFBD><EFBFBD>、跳<EFBFBD><EFBFBD>或下发派发<EFBFBD> * @details 逐一处理队列的事件,并根系统当前的运行模式进行拦截、跳转或下发派发
*/ */
void Event_Dispatcher_Loop(void) void Event_Dispatcher_Loop(void)
{ {
SystemEvent evt = EventQueue_Pop(); // 弹出<EFBFBD><EFBFBD><EFBFBD><EFBFBD>事件 SystemEvent evt = EventQueue_Pop(); // 弹出一个事件
// 如果<EFBFBD><EFBFBD>无事发生的空事件,直接<EFBFBD><EFBFBD><EFBFBD> // 如果无事发生的空事件,直接退出
if (evt.key_event == KEY_EVENT_NONE && evt.extra_size == 0) { if (evt.key_event == KEY_EVENT_NONE && evt.extra_size == 0) {
return; return;
} }
inactivity_timer = 0; // 捕获到有效事件,清零休眠计时<EFBFBD><EFBFBD><EFBFBD><EFBFBD>闲置超时 inactivity_timer = 0; // 捕获到有效事件,清零休眠计时器,重置闲置超时
// 串口打印调试信息 // 串口打印调试信息
XTELL_LOG("[EVENT] key="); XTELL_LOG("[EVENT] key=");
XTELL_LOG_HEX8((u8)evt.key_event); XTELL_LOG_HEX8((u8)evt.key_event);
XTELL_LOG("\r\n"); XTELL_LOG("\r\n");
// SOS 按键紧<EFBFBD><EFBFBD><EFBFBD><EFBFBD>理:若当前前台不为 SOS 应用,接收到 SOS <EFBFBD><EFBFBD>动作,立即强行切入到 SOS_APP 并执行求<EFBFBD><EFBFBD> // SOS 按键紧急处理:若当前前台不为 SOS 应用,接收到 SOS 动作,立即强行切入到 SOS_APP 并执行求
if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) && if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) &&
AppManager_GetActiveAppID() != APP_ID_SOS) { AppManager_GetActiveAppID() != APP_ID_SOS) {
AppManager_StartApp(APP_ID_SOS); AppManager_StartApp(APP_ID_SOS);
return; return;
} }
// 关机事件处理:捕获到长按关机指令,直接调<EFBFBD><EFBFBD> Power_Off 关断电源 // 关机事件处理:捕获到长按关机指令,直接调 Power_Off 关断电源
if (evt.key_event == KEY_EVENT_POWER_OFF) { if (evt.key_event == KEY_EVENT_POWER_OFF) {
Power_Off(); Power_Off();
return; return;
} }
// 常<EFBFBD><EFBFBD>事件(如普通的按键点击)直接<EFBFBD><EFBFBD>传分发给当前<EFBFBD><EFBFBD>于前台活跃的应<EFBFBD><EFBFBD> // 常事件(如普通的按键点击)直接传分发给当前于前台活跃的应
AppManager_DispatchEvent(evt); AppManager_DispatchEvent(evt);
} }
/** /**
* @brief 定时<EFBFBD><EFBFBD> 1 初<EFBFBD><EFBFBD><EFBFBD><EFBFBD>定 1ms <EFBFBD><EFBFBD><EFBFBD><EFBFBD>周期 * @brief 定时 1 初始化,设定 1ms 中断周期
*/ */
void Timer1_Init(void) void Timer1_Init(void)
{ {
AUXR |= 0x40; // 设定定时<EFBFBD><EFBFBD> 1 <EFBFBD><EFBFBD> 1T 模式 (无分频,主<EFBFBD><EFBFBD>直接输<EFBFBD><EFBFBD>) AUXR |= 0x40; // 设定定时 1 1T 模式 (无分频,主直接输)
TMOD &= 0x0F; // 设置定时<EFBFBD><EFBFBD> 1 模式寄存<EFBFBD><EFBFBD>,保留低四位 (定时<EFBFBD><EFBFBD>0配置) TMOD &= 0x0F; // 设置定时 1 模式寄存,保留低四位 (定时0配置)
{ {
// 按照系统主<EFBFBD> (MAIN_Fosc) 计算 1ms 对应的重装载<EFBFBD><EFBFBD> // 按照系统主 (MAIN_Fosc) 计算 1ms 对应的重装载
u16 reload = (u16)(65536UL - (MAIN_Fosc / 1000UL)); u16 reload = (u16)(65536UL - (MAIN_Fosc / 1000UL));
TL1 = (u8)reload; // 载入<EFBFBD><EFBFBD> 8 <EFBFBD><EFBFBD> TL1 = (u8)reload; // 载入 8
TH1 = (u8)(reload >> 8);// 载入<EFBFBD><EFBFBD> 8 <EFBFBD><EFBFBD> TH1 = (u8)(reload >> 8);// 载入 8
} }
ET1 = 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>定时<EFBFBD><EFBFBD> 1 <20><><EFBFBD><EFBFBD><EFBFBD> ET1 = 1; // 开启定时器 1 中断允许
TR1 = 1; // <EFBFBD><EFBFBD>动定时器 1 计数 TR1 = 1; // 动定时器 1 计数
EA = 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>全局<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> EA = 1; // 开启全局中断允许
} }
// 全局按键<EFBFBD><EFBFBD>描触发标<EFBFBD><EFBFBD> // 全局按键描触发标
volatile bit key_scan_flag = 0; volatile bit key_scan_flag = 0;
/** /**
* @brief 定时<EFBFBD><EFBFBD> 1 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>服务程序 (<EFBFBD><EFBFBD> 1ms 触发<EFBFBD><EFBFBD><EFBFBD><EFBFBD>) * @brief 定时 1 中断服务程序 ( 1ms 触发一次)
* @details 承担系统<EFBFBD><EFBFBD>时钟计算,并<EFBFBD><EFBFBD> 10ms <EFBFBD><EFBFBD>位主<EFBFBD><EFBFBD><EFBFBD><EFBFBD>按键<EFBFBD><EFBFBD>描标志不直接在<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ADC 多<EFBFBD><EFBFBD>采样<EFBFBD> * @details 承担系统时钟计算,并 10ms 置位主循环按键扫描标志,不直接在中断中进行 ADC 多采样
*/ */
void Timer1_Isr(void) interrupt 3 void Timer1_Isr(void) interrupt 3
{ {
// 1ms 定时更新<EFBFBD><EFBFBD>时钟 // 1ms 定时更新时钟
Clock_IncMS(); Clock_IncMS();
// 报<EFBFBD><EFBFBD>屏幕红边<EFBFBD><EFBFBD>以 100ms 周期<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,进行<EFBFBD><EFBFBD>色爆闪指示 // 报屏幕红边以 100ms 周期亮灭,进行色爆闪指示
alarm_flash_flag = (ms_tick / 100) % 2; alarm_flash_flag = (ms_tick / 100) % 2;
/* 物理按键定时计数 (<EFBFBD><EFBFBD> 10ms <EFBFBD><EFBFBD>位一次标志位) */ /* 物理按键定时计数 ( 10ms 位一次标志位) */
key_scan_timer++; key_scan_timer++;
if (key_scan_timer >= 10) { if (key_scan_timer >= 10) {
key_scan_timer = 0; // 重置<EFBFBD><EFBFBD>描分频<EFBFBD><EFBFBD><EFBFBD><EFBFBD> key_scan_timer = 0; // 重置描分频计数器
key_scan_flag = 1; // 仅在<EFBFBD><EFBFBD><EFBFBD><EFBFBD>里置起标志位,留待主<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>实际<EFBFBD><EFBFBD>取与消<EFBFBD><EFBFBD> key_scan_flag = 1; // 仅在中断里置起标志位,留待主循环中执行实际读取与消
} }
} }
/** /**
* @brief 主循<EFBFBD><EFBFBD>按键<EFBFBD><EFBFBD>描与消抖处理函数 * @brief 主循环按键扫描与消抖处理函数
* @details 由主<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 10ms <EFBFBD><EFBFBD>查并执<EFBFBD><EFBFBD>,完全在主线程<EFBFBD><EFBFBD><EFBFBD><EFBFBD>绝无<EFBFBD><EFBFBD><EFBFBD><EFBFBD>重入冲突<EFBFBD><EFBFBD> * @details 由主循环每 10ms 查并执,完全在主线程中运行,绝无中断重入冲突
*/ */
void Event_KeyScan_Poll(void) void Event_KeyScan_Poll(void)
{ {
SystemEvent evt; SystemEvent evt;
// 读取按键的电平信<EFBFBD><EFBFBD> (KEY_UP, KEY_CONFIRM 为低电平有效) // 读取按键的电平信 (KEY_UP, KEY_CONFIRM 为低电平有效)
u8 raw_up = !KEY_UP; u8 raw_up = !KEY_UP;
u8 raw_down = ADC_IsKeyDown(); // KEY_DOWN 通过 P0.3 ADC 通道判定 (常<EFBFBD> 2.0V, 按下 3.7V) u8 raw_down = ADC_IsKeyDown(); // KEY_DOWN 通过 P0.3 ADC 通道判定 (常 2.0V, 按下 3.7V)
u8 raw_confirm = !KEY_CONFIRM; u8 raw_confirm = !KEY_CONFIRM;
u8 raw_sos = !KEY_SOS; // 恢<EFBFBD> SOS 物理按键电平<EFBFBD><EFBFBD><EFBFBD><EFBFBD> u8 raw_sos = !KEY_SOS; // 恢 SOS 物理按键电平检测
key_scan_timer = 0; // 重置<EFBFBD><EFBFBD>描分频<EFBFBD><EFBFBD><EFBFBD><EFBFBD> key_scan_timer = 0; // 重置描分频计数器
// 【SOS 按键电平实时诊断<EFBFBD><EFBFBD> // 【SOS 按键电平实时诊断
{ {
static u8 last_raw_sos = 99; static u8 last_raw_sos = 99;
if (raw_sos != last_raw_sos) { if (raw_sos != last_raw_sos) {
@@ -210,7 +210,7 @@ void Event_KeyScan_Poll(void)
} }
} }
// 按键调试诊断:若按键 ADC 值有变化或<EFBFBD><EFBFBD>于高电平,串口输出真实 ADC <EFBFBD><EFBFBD> // 按键调试诊断:若按键 ADC 值有变化或于高电平,串口输出真实 ADC
{ {
u16 down_val = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN); u16 down_val = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN);
if (down_val > 2800) { if (down_val > 2800) {
@@ -223,35 +223,35 @@ void Event_KeyScan_Poll(void)
} }
} }
// <EFBFBD><EFBFBD>测到有任意按<EFBFBD><EFBFBD>处于下压状<EFBFBD><EFBFBD>,立即将无操作闲置休眠计时器归<EFBFBD><EFBFBD> // 测到有任意按处于下压状,立即将无操作闲置休眠计时器归
if (raw_up || raw_down || raw_confirm || raw_sos) { if (raw_up || raw_down || raw_confirm || raw_sos) {
inactivity_timer = 0; inactivity_timer = 0;
} else { } else {
// 如果在常态下(非休眠、非报<EFBFBD><EFBFBD><EFBFBD><EFBFBD>非紧<EFBFBD><EFBFBD>求救),累加闲<EFBFBD><EFBFBD>时间 // 如果在常态下(非休眠、非报警、非紧急求救),累加闲时间
if (current_state != STATE_SLEEP && current_state != STATE_ALARM && current_state != STATE_SOS_EMITTED) { if (current_state != STATE_SLEEP && current_state != STATE_ALARM && current_state != STATE_SOS_EMITTED) {
inactivity_timer++; inactivity_timer++;
} }
} }
// 默<EFBFBD><EFBFBD>将派发事件重置为空 // 默将派发事件重置为空
evt.key_event = KEY_EVENT_NONE; evt.key_event = KEY_EVENT_NONE;
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
evt.extra_data = 0; evt.extra_data = 0;
evt.extra_size = 0; evt.extra_size = 0;
// 双键组合判定:▲ <EFBFBD><EFBFBD> and <EFBFBD><EFBFBD> <20><>同时按下的消抖与生成 // 双键组合判定:▲ and ▼ 键同时按下的消抖与生成
if (raw_up && raw_down) { if (raw_up && raw_down) {
comb_hold++; comb_hold++;
if (comb_hold == 300) { // 持续按下<EFBFBD><EFBFBD> 3 <EFBFBD><EFBFBD> (300 * 10ms = 3s) if (comb_hold == 300) { // 持续按下 3 (300 * 10ms = 3s)
evt.key_event = KEY_EVENT_UP_DOWN_COMB; evt.key_event = KEY_EVENT_UP_DOWN_COMB;
EventQueue_Push(evt); // 压入队列 EventQueue_Push(evt); // 压入队列
} }
key_up_hold = 0; // 清零单个按键计数,防止双击事件干<EFBFBD><EFBFBD> key_up_hold = 0; // 清零单个按键计数,防止双击事件干
key_down_hold = 0; key_down_hold = 0;
} else { } else {
comb_hold = 0; // 组合<EFBFBD><EFBFBD>松开归零 comb_hold = 0; // 组合松开归零
// <EFBFBD><EFBFBD> (KEY_UP) 按键状<EFBFBD><EFBFBD> // (KEY_UP) 按键状
if (raw_up) { if (raw_up) {
key_up_hold++; key_up_hold++;
if (key_up_hold == 300) { // 达到 3 秒判定为长按 if (key_up_hold == 300) { // 达到 3 秒判定为长按
@@ -259,7 +259,7 @@ void Event_KeyScan_Poll(void)
EventQueue_Push(evt); EventQueue_Push(evt);
} }
} else { } else {
// 如果松开时按下<EFBFBD><EFBFBD>数在消抖阈值20ms至长按阈<EFBFBD><EFBFBD>3s之间则判定为<EFBFBD><EFBFBD>次短<EFBFBD><EFBFBD> // 如果松开时按下数在消抖阈值20ms至长按阈3s之间则判定为一次短按
if (key_up_hold >= 2 && key_up_hold < 300) { if (key_up_hold >= 2 && key_up_hold < 300) {
evt.key_event = KEY_EVENT_UP_CLICK; evt.key_event = KEY_EVENT_UP_CLICK;
EventQueue_Push(evt); EventQueue_Push(evt);
@@ -267,15 +267,15 @@ void Event_KeyScan_Poll(void)
key_up_hold = 0; // 释放归零 key_up_hold = 0; // 释放归零
} }
// <EFBFBD><EFBFBD> (KEY_DOWN / <EFBFBD><EFBFBD>关机) 按键状<EFBFBD><EFBFBD> // (KEY_DOWN / 关机) 按键状
if (raw_down) { if (raw_down) {
key_down_hold++; key_down_hold++;
if (key_down_hold == 100) { // 长按 1 <EFBFBD><EFBFBD> (100 * 10ms = 1000ms = 1s) 触发关机事件 if (key_down_hold == 100) { // 长按 1 (100 * 10ms = 1000ms = 1s) 触发关机事件
evt.key_event = KEY_EVENT_POWER_OFF; evt.key_event = KEY_EVENT_POWER_OFF;
EventQueue_Push(evt); EventQueue_Push(evt);
} }
} else { } else {
// 如果松开时按下时长在 20ms <EFBFBD><EFBFBD> 1s 之间,触发短按下<EFBFBD><EFBFBD>/选择事件 // 如果松开时按下时长在 20ms 1s 之间,触发短按下/选择事件
if (key_down_hold >= 2 && key_down_hold < 100) { if (key_down_hold >= 2 && key_down_hold < 100) {
evt.key_event = KEY_EVENT_DOWN_CLICK; evt.key_event = KEY_EVENT_DOWN_CLICK;
EventQueue_Push(evt); EventQueue_Push(evt);
@@ -283,10 +283,10 @@ void Event_KeyScan_Poll(void)
key_down_hold = 0; // 释放归零 key_down_hold = 0; // 释放归零
} }
// <EFBFBD><EFBFBD> (KEY_CONFIRM) <EFBFBD><EFBFBD>认按<EFBFBD><EFBFBD><EFBFBD><EFBFBD> // (KEY_CONFIRM) 确认按键状态
if (raw_confirm) { if (raw_confirm) {
key_confirm_hold++; key_confirm_hold++;
if (key_confirm_hold == 300) { // 长按 3 秒打<EFBFBD><EFBFBD>主菜<EFBFBD><EFBFBD> if (key_confirm_hold == 300) { // 长按 3 秒打开主菜单
evt.key_event = KEY_EVENT_CONFIRM_LONG; evt.key_event = KEY_EVENT_CONFIRM_LONG;
EventQueue_Push(evt); EventQueue_Push(evt);
} }
@@ -298,23 +298,23 @@ void Event_KeyScan_Poll(void)
key_confirm_hold = 0; // 释放归零 key_confirm_hold = 0; // 释放归零
} }
// SOS <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȱ<EFBFBD><EFBFBD><EFBFBD>״̬<EFBFBD><EFBFBD> (<28>ѽ<EFBFBD><D1BD><EFBFBD>) // SOS 物理求救按键状态机 (已解锁)
if (raw_sos) { if (raw_sos) {
key_sos_hold++; key_sos_hold++;
if (key_sos_hold == 300) { // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> 3 <20><EFBFBD><EBA3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD><C8A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> if (key_sos_hold == 300) { // 长按 3 秒,发出求救,插入队首
evt.key_event = KEY_EVENT_SOS_LONG; evt.key_event = KEY_EVENT_SOS_LONG;
evt.priority = EVENT_PRIORITY_URGENT; evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt); EventQueue_InsertFront(evt);
Uart_SendString("[KEY-DIAG] SOS Long Press Dispatched!\r\n"); Uart_SendString("[KEY-DIAG] SOS Long Press Dispatched!\r\n");
} }
} else { } else {
if (key_sos_hold >= 2 && key_sos_hold < 300) { // <EFBFBD>̰<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> if (key_sos_hold >= 2 && key_sos_hold < 300) { // 短按即呼救,插入队首
evt.key_event = KEY_EVENT_SOS_CLICK; evt.key_event = KEY_EVENT_SOS_CLICK;
evt.priority = EVENT_PRIORITY_URGENT; evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt); EventQueue_InsertFront(evt);
Uart_SendString("[KEY-DIAG] SOS Click Dispatched!\r\n"); Uart_SendString("[KEY-DIAG] SOS Click Dispatched!\r\n");
} }
key_sos_hold = 0; // <EFBFBD>ͷŹ<EFBFBD><EFBFBD><EFBFBD> key_sos_hold = 0; // 释放归零
} }
} }
} }

View File

@@ -11,82 +11,82 @@
/* ========================================================================= /* =========================================================================
* 系统全局变量定义 * 系统全局变量定义
* ========================================================================= */ * ========================================================================= */
SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默<EFBFBD><EFBFBD>为正常时钟待机<EFBFBD><EFBFBD> SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默为正常时钟待机
u8 menu_select = 0; // 主菜单当前<EFBFBD><EFBFBD>中的<EFBFBD><EFBFBD><EFBFBD><EFBFBD>索引 (0~2) u8 menu_select = 0; // 主菜单当前选中的行高亮索引 (0~2)
u32 captured_addr = 0; // 配<EFBFBD><EFBFBD>或报<EFBFBD><EFBFBD>时,捕获到的传感器 24 <EFBFBD><EFBFBD> EV1527 射<EFBFBD><EFBFBD>物理地<EFBFBD><EFBFBD> u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 EV1527 射物理地
u8 captured_type = 0; // 正在对码或触发<EFBFBD><EFBFBD>报的传感器设<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (0:门<EFBFBD>, 1:PIR...) u8 captured_type = 0; // 正在对码或触发报的传感器设备类型 (0:门, 1:PIR...)
u8 alarm_sensor_slot = 0; // 触发警报的传感器<EFBFBD><EFBFBD> sensor_list 数组<EFBFBD><EFBFBD>的索引槽位号 u8 alarm_sensor_slot = 0; // 触发警报的传感器 sensor_list 数组的索引槽位号
// 传感器存储列表数<EFBFBD><EFBFBD>库,位于单片机<EFBFBD><EFBFBD>部 XDATA 存储空间,<EFBFBD><EFBFBD>共 16 <EFBFBD><EFBFBD>槽位 // 传感器存储列表数库,位于单片机部 XDATA 存储空间,共 16 槽位
Sensor_Slot xdata sensor_list[16]; Sensor_Slot xdata sensor_list[16];
// 全局滴答与定时中<EFBFBD><EFBFBD>计数变量,位<EFBFBD><EFBFBD> volatile 防<EFBFBD><EFBFBD><EFBFBD>编译器优<EFBFBD><EFBFBD> // 全局滴答与定时中计数变量,位 volatile 防止被编译器优
volatile u16 ms_tick = 0; // 定时<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1ms 基准滴答<EFBFBD><EFBFBD>加器 volatile u16 ms_tick = 0; // 定时中断 1ms 基准滴答加器
volatile u16 inactivity_timer = 0; // 用户闲置计时<EFBFBD><EFBFBD>,用于无操作判定(单<EFBFBD><EFBFBD>: 10ms<EFBFBD><EFBFBD> volatile u16 inactivity_timer = 0; // 用户闲置计时,用于无操作判定(单: 10ms
volatile u32 pair_timeout_ms = 0; // 传感器配对<EFBFBD><EFBFBD>码超时<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (上限 30000ms <EFBFBD><EFBFBD> 30s) volatile u32 pair_timeout_ms = 0; // 传感器配对码超时毫秒计时器 (上限 30000ms 30s)
volatile u16 alarm_timer_ms = 0; // 入侵报<EFBFBD><EFBFBD>马达震动时序<EFBFBD><EFBFBD><EFBFBD><EFBFBD> volatile u16 alarm_timer_ms = 0; // 入侵报马达震动时序计数器
volatile u16 motor_timer_ms = 0; // <EFBFBD><EFBFBD>立马达震动时长<EFBFBD><EFBFBD><EFBFBD><EFBFBD> volatile u16 motor_timer_ms = 0; // 立马达震动时长计数器
volatile bit alarm_flash_flag = 0; // 报<EFBFBD><EFBFBD>时红<EFBFBD><EFBFBD>周期<EFBFBD><EFBFBD>闪烁亮<EFBFBD><EFBFBD>标志<EFBFBD><EFBFBD> volatile bit alarm_flash_flag = 0; // 报警时红框周期性闪烁亮灭标志位
// 物理按键<EFBFBD><EFBFBD>描与消抖长按计数<EFBFBD><EFBFBD> (10ms <EFBFBD><EFBFBD>描一<EFBFBD><EFBFBD>) // 物理按键描与消抖长按计数 (10ms 扫描一次)
volatile u16 key_scan_timer = 0; // 10ms <EFBFBD><EFBFBD>描周期分频器 volatile u16 key_scan_timer = 0; // 10ms 描周期分频器
volatile u16 key_up_hold = 0; // <EFBFBD><EFBFBD> (KEY_UP) 按下时长<EFBFBD><EFBFBD><EFBFBD><EFBFBD> volatile u16 key_up_hold = 0; // (KEY_UP) 按下时长累加
volatile u16 key_down_hold = 0; // <EFBFBD><EFBFBD> (KEY_DOWN) 按下时长<EFBFBD><EFBFBD><EFBFBD><EFBFBD> volatile u16 key_down_hold = 0; // (KEY_DOWN) 按下时长累加
volatile u16 key_confirm_hold = 0; // <EFBFBD><EFBFBD> (KEY_CONFIRM) 按下时长<EFBFBD><EFBFBD><EFBFBD><EFBFBD> volatile u16 key_confirm_hold = 0; // (KEY_CONFIRM) 按下时长累加
volatile u16 key_sos_hold = 0; // SOS 按下时长<EFBFBD><EFBFBD><EFBFBD><EFBFBD> volatile u16 key_sos_hold = 0; // SOS 按下时长累加
volatile u16 comb_hold = 0; // <EFBFBD><EFBFBD> + <EFBFBD><EFBFBD> 组合<EFBFBD><EFBFBD>按下时长<EFBFBD><EFBFBD><EFBFBD><EFBFBD> volatile u16 comb_hold = 0; // + 组合按下时长累加
/* ========================================================================= /* =========================================================================
* 串口调试指令解析与事件派发封<EFBFBD><EFBFBD> * 串口调试指令解析与事件派发封
* ========================================================================= */ * ========================================================================= */
/** /**
* @brief 解析通过串口 1 发<EFBFBD><EFBFBD>来的键盘及测试模拟指令 * @brief 解析通过串口 1 发来的键盘及测试模拟指令
* @param cmd 接收到的 ASCII 字<EFBFBD><EFBFBD><EFBFBD><EFBFBD> * @param cmd 接收到的 ASCII 字符命令
* @details 负责将字符指令转化<EFBFBD><EFBFBD>应的系统按<EFBFBD><EFBFBD>或射频事件,并推入事件队列以测试<EFBFBD><EFBFBD>件架构: * @details 负责将字符指令转化应的系统按或射频事件,并推入事件队列以测试件架构:
* - 'u'/'U': 模拟<EFBFBD><EFBFBD><EFBFBD><EFBFBD>/长按 <20><> <20><><EFBFBD><EFBFBD> * - 'u'/'U': 模拟短按/长按 ▲ 键。
* - 'd'/'D': 模拟<EFBFBD><EFBFBD><EFBFBD><EFBFBD>/长按 <20><> <20><><EFBFBD><EFBFBD> * - 'd'/'D': 模拟短按/长按 ▼ 键。
* - 's'/'S': 模拟<EFBFBD><EFBFBD><EFBFBD><EFBFBD>/长按 SOS 物理<EFBFBD><EFBFBD>(优先插入队首)<EFBFBD><EFBFBD> * - 's'/'S': 模拟短按/长按 SOS 物理(优先插入队首)
* - 'c'/'C': 模拟组合<EFBFBD><EFBFBD>触发<EFBFBD><EFBFBD> * - 'c'/'C': 模拟组合键触发。
* - 'a'/'A': 模拟无线传感器入侵触发<EFBFBD><EFBFBD>报信号,地<EFBFBD><EFBFBD>固定<EFBFBD><EFBFBD> 0x123456zone=1<EFBFBD><EFBFBD> * - 'a'/'A': 模拟无线传感器入侵触发报信号,地址固定为 0x123456zone=1
* - 'p'/'P': 模拟对码期间捕获<EFBFBD><EFBFBD> 24 位无线<EFBFBD><EFBFBD>码信号,地<EFBFBD><EFBFBD>固定<EFBFBD><EFBFBD> 0x789ABC<EFBFBD><EFBFBD> * - 'p'/'P': 模拟对码期间捕获 24 位无线码信号,地址固定为 0x789ABC
* - '1'/'2'/'3': 模拟系统的<EFBFBD><EFBFBD>常、已布防、已撤防状<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> * - '1'/'2'/'3': 模拟系统的常、已布防、已撤防状态切换。
* - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000触发自动深度休眠<EFBFBD> * - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000触发自动深度休眠
* - 't'/'T': 触发屏幕与无线控制引脚测试<EFBFBD> * - 't'/'T': 触发屏幕与无线控制引脚测试
*/ */
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD><EFBFBD><EFBFBD>ר<EFBFBD><EFBFBD><EFBFBD><EFBFBD>RGB <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫͨ<C9AB><CDA8><EFBFBD>Ų<EFBFBD><C5B2><EFBFBD><EFBFBD><EFBFBD> * @brief 串口调试专属RGB 物理颜色通道排查测试
* @details <EFBFBD><EFBFBD><EFBFBD>η<EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̡<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ÿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 2 <20><EFBFBD><EBA3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD><D0A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD><EFBFBD>ɫӳ<C9AB><D3B3><EFBFBD><EFBFBD>ϵ * @details 依次发送纯红、纯绿、纯蓝的数据流,每个颜色亮起 2 秒,帮助肉眼校对物理灯珠的实际颜色映射关系
*/ */
void RGB_Diagnostic_Test(void) void RGB_Diagnostic_Test(void)
{ {
Uart_SendString("\r\n=== RGB LED Color Channel Test Starting ===\r\n"); Uart_SendString("\r\n=== RGB LED Color Channel Test Starting ===\r\n");
// 1. <EFBFBD><EFBFBD><EFBFBD>Է<EFBFBD><EFBFBD>ͺ<EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (G=0, R=150, B=0) // 1. 尝试发送红色控制数据 (G=0, R=150, B=0)
Uart_SendString("[RGB-TEST] 1. Sending RED command -> G=0, R=150, B=0\r\n"); Uart_SendString("[RGB-TEST] 1. Sending RED command -> G=0, R=150, B=0\r\n");
RGB_Send(0, 150, 0, 0, 150, 0); RGB_Send(0, 150, 0, 0, 150, 0);
Delay_ms(2000); Delay_ms(2000);
// Ϩ<EFBFBD><EFBFBD> // 熄灭
RGB_Send(0, 0, 0, 0, 0, 0); RGB_Send(0, 0, 0, 0, 0, 0);
Delay_ms(300); Delay_ms(300);
// 2. <EFBFBD><EFBFBD><EFBFBD>Է<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (G=150, R=0, B=0) // 2. 尝试发送绿色控制数据 (G=150, R=0, B=0)
Uart_SendString("[RGB-TEST] 2. Sending GREEN command -> G=150, R=0, B=0\r\n"); Uart_SendString("[RGB-TEST] 2. Sending GREEN command -> G=150, R=0, B=0\r\n");
RGB_Send(150, 0, 0, 150, 0, 0); RGB_Send(150, 0, 0, 150, 0, 0);
Delay_ms(2000); Delay_ms(2000);
// Ϩ<EFBFBD><EFBFBD> // 熄灭
RGB_Send(0, 0, 0, 0, 0, 0); RGB_Send(0, 0, 0, 0, 0, 0);
Delay_ms(300); Delay_ms(300);
// 3. <EFBFBD><EFBFBD><EFBFBD>Է<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (G=0, R=0, B=150) // 3. 尝试发送蓝色控制数据 (G=0, R=0, B=150)
Uart_SendString("[RGB-TEST] 3. Sending BLUE command -> G=0, R=0, B=150\r\n"); Uart_SendString("[RGB-TEST] 3. Sending BLUE command -> G=0, R=0, B=150\r\n");
RGB_Send(0, 0, 150, 0, 0, 150); RGB_Send(0, 0, 150, 0, 0, 150);
Delay_ms(2000); Delay_ms(2000);
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϩ<EFBFBD><EFBFBD> // 彻底熄灭
RGB_Send(0, 0, 0, 0, 0, 0); RGB_Send(0, 0, 0, 0, 0, 0);
Uart_SendString("=== RGB LED Color Channel Test Ended ===\r\n"); Uart_SendString("=== RGB LED Color Channel Test Ended ===\r\n");
} }
@@ -94,7 +94,7 @@ void RGB_Diagnostic_Test(void)
void Debug_ProcessCommand(char cmd) void Debug_ProcessCommand(char cmd)
{ {
SystemEvent debug_evt; SystemEvent debug_evt;
inactivity_timer = 0; // 收到指令,立即重<EFBFBD><EFBFBD>用户的无操作闲置计时<EFBFBD><EFBFBD> inactivity_timer = 0; // 收到指令,立即重用户的无操作闲置计时
debug_evt.extra_data = 0; debug_evt.extra_data = 0;
debug_evt.extra_size = 0; debug_evt.extra_size = 0;
@@ -121,7 +121,7 @@ void Debug_ProcessCommand(char cmd)
} else if (cmd == 's') { } else if (cmd == 's') {
debug_evt.key_event = KEY_EVENT_SOS_CLICK; debug_evt.key_event = KEY_EVENT_SOS_CLICK;
debug_evt.priority = EVENT_PRIORITY_URGENT; debug_evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(debug_evt); // 紧<EFBFBD><EFBFBD>事件插入队<EFBFBD><EFBFBD> EventQueue_InsertFront(debug_evt); // 紧事件插入队
Uart_SendString("[UART] KEY_SOS Clicked\r\n"); Uart_SendString("[UART] KEY_SOS Clicked\r\n");
} else if (cmd == 'S') { } else if (cmd == 'S') {
debug_evt.key_event = KEY_EVENT_SOS_LONG; debug_evt.key_event = KEY_EVENT_SOS_LONG;
@@ -136,22 +136,22 @@ void Debug_ProcessCommand(char cmd)
} else if (cmd == 'a' || cmd == 'A') { } else if (cmd == 'a' || cmd == 'A') {
u32 mock_addr = 0x123456; // 设定模拟的报警射频源地址 u32 mock_addr = 0x123456; // 设定模拟的报警射频源地址
u8 mock_slot; u8 mock_slot;
// 如果<EFBFBD><EFBFBD>地数<EFBFBD><EFBFBD>库中没有匹配该射频,则强制登记进 1 号防<EFBFBD><EFBFBD> // 如果本地数据库中没有匹配该射频,则强制登记进 1 号防
if (!Check_Sensor_ID(mock_addr, &mock_slot)) { if (!Check_Sensor_ID(mock_addr, &mock_slot)) {
Add_Sensor_With_Zone(mock_addr, 0, 1); Add_Sensor_With_Zone(mock_addr, 0, 1);
} }
debug_evt.key_event = KEY_EVENT_NONE; debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_HIGH; // 入侵报<EFBFBD><EFBFBD><EFBFBD><EFBFBD>为 HIGH 优先<EFBFBD><EFBFBD> debug_evt.priority = EVENT_PRIORITY_HIGH; // 入侵报警设为 HIGH 优先
debug_evt.extra_data = mock_addr; debug_evt.extra_data = mock_addr;
debug_evt.extra_size = 4; debug_evt.extra_size = 4;
EventQueue_InsertFront(debug_evt); // 高优先级插队 EventQueue_InsertFront(debug_evt); // 高优先级插队
Uart_SendString("[UART] Mock RF Alarm\r\n"); Uart_SendString("[UART] Mock RF Alarm\r\n");
} else if (cmd == 'p' || cmd == 'P') { } else if (cmd == 'p' || cmd == 'P') {
// <EFBFBD><EFBFBD>有当手环切入 Pair 模式时,模拟对码信号才有<EFBFBD><EFBFBD> // 有当手环切入 Pair 模式时,模拟对码信号才有
if (AppManager_GetActiveAppID() == APP_ID_PAIR) { if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
debug_evt.key_event = KEY_EVENT_NONE; debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_NORMAL; debug_evt.priority = EVENT_PRIORITY_NORMAL;
debug_evt.extra_data = 0x789ABC; // 设定模拟对码<EFBFBD><EFBFBD>件地<EFBFBD><EFBFBD> debug_evt.extra_data = 0x789ABC; // 设定模拟对码硬件地址
debug_evt.extra_size = 4; debug_evt.extra_size = 4;
EventQueue_Push(debug_evt); EventQueue_Push(debug_evt);
Uart_SendString("[UART] Mock RF Pair Signal\r\n"); Uart_SendString("[UART] Mock RF Pair Signal\r\n");
@@ -159,157 +159,157 @@ void Debug_ProcessCommand(char cmd)
Uart_SendString("[UART] Not in PAIR mode\r\n"); Uart_SendString("[UART] Not in PAIR mode\r\n");
} }
} else if (cmd == '1') { } else if (cmd == '1') {
// 模拟切换<EFBFBD><EFBFBD> 正常撤防 (NORMAL) 状<EFBFBD><EFBFBD>并刷新时钟 // 模拟切换 正常撤防 (NORMAL) 状并刷新时钟
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_NORMAL; current_state = STATE_NORMAL;
UI_ShowClockPage(current_state, current_hour, current_min); UI_ShowClockPage(current_state, current_hour, current_min);
} }
Uart_SendString("[UART] State: NORMAL\r\n"); Uart_SendString("[UART] State: NORMAL\r\n");
} else if (cmd == '2') { } else if (cmd == '2') {
// 模拟切换<EFBFBD><EFBFBD> 已布<EFBFBD><EFBFBD> (ARMED) 状<EFBFBD><EFBFBD><EFBFBD><EFBFBD>通门磁可触发警报 // 模拟切换 已布 (ARMED) 状态,普通门磁可触发警报
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_ARMED; current_state = STATE_ARMED;
UI_ShowClockPage(current_state, current_hour, current_min); UI_ShowClockPage(current_state, current_hour, current_min);
} }
Uart_SendString("[UART] State: ARMED\r\n"); Uart_SendString("[UART] State: ARMED\r\n");
} else if (cmd == '3') { } else if (cmd == '3') {
// 模拟切换<EFBFBD><EFBFBD> 已撤<EFBFBD><EFBFBD> (DISARMED) 状<EFBFBD><EFBFBD>,忽略<EFBFBD><EFBFBD>通报<EFBFBD><EFBFBD> // 模拟切换 已撤 (DISARMED) 状,忽略普通报警
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_DISARMED; current_state = STATE_DISARMED;
UI_ShowClockPage(current_state, current_hour, current_min); UI_ShowClockPage(current_state, current_hour, current_min);
} }
Uart_SendString("[UART] State: DISARMED\r\n"); Uart_SendString("[UART] State: DISARMED\r\n");
} else if (cmd == 'z' || cmd == 'Z') { } else if (cmd == 'z' || cmd == 'Z') {
inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循<EFBFBD><EFBFBD>深度休眠 inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循深度休眠
Uart_SendString("[UART] Force Entering Sleep mode!\r\n"); Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
} else if (cmd == 'q' || cmd == 'Q') { } else if (cmd == 'q' || cmd == 'Q') {
RGB_Diagnostic_Test(); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> RGB <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫͨ<EFBFBD><EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> RGB_Diagnostic_Test(); // 启动 RGB 物理颜色通道排查测试
} else if (cmd == 't' || cmd == 'T') { } else if (cmd == 't' || cmd == 'T') {
RF_DiagnosticMode('t'); // 触发射<EFBFBD><EFBFBD><EFBFBD><EFBFBD>发射诊断 (绿色爆闪) RF_DiagnosticMode('t'); // 触发射频连续发射诊断 (绿色爆闪)
} else if (cmd == 'r' || cmd == 'R') { } else if (cmd == 'r' || cmd == 'R') {
RF_DiagnosticMode('r'); // 触发 5 秒射频接收监<EFBFBD><EFBFBD>诊断 RF_DiagnosticMode('r'); // 触发 5 秒射频接收监诊断
} else if (cmd == 'l' || cmd == 'L') { } else if (cmd == 'l' || cmd == 'L') {
Loopback_Test(); // 触发物理电气回环测试 (<EFBFBD><EFBFBD><EFBFBD><EFBFBD> P2.1 <EFBFBD><EFBFBD> P3.6 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>) Loopback_Test(); // 触发物理电气回环测试 (短路 P2.1 P3.6 自检)
} }
} }
/* ========================================================================= /* =========================================================================
* 主函<EFBFBD><EFBFBD> (入口) * 主函 (入口)
* ========================================================================= */ * ========================================================================= */
void main(void) void main(void)
{ {
char cmd; char cmd;
// ====== STC32G 特殊功能寄存器内核基<EFBFBD><EFBFBD><EFBFBD><EFBFBD>化 ====== // ====== STC32G 特殊功能寄存器内核基础初始化 ======
WTST = 0; // 设置程序 Flash 访问等待时间<EFBFBD><EFBFBD> 0 (<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) WTST = 0; // 设置程序 Flash 访问等待时间 0 (最高速运行)
EAXFR = 1; // 允<EFBFBD><EFBFBD><EFBFBD><EFBFBD>问扩展特殊功能寄存<EFBFBD><EFBFBD> (XSFR) EAXFR = 1; // 允许访问扩展特殊功能寄存 (XSFR)
CKCON = 0; // 外部总线时钟设定为最<EFBFBD><EFBFBD> CKCON = 0; // 外部总线时钟设定为最
WDT_CONTR = 0x00; // 关闭看门狗定时器 WDT_CONTR = 0x00; // 关闭看门狗定时器
// ====== <EFBFBD><EFBFBD>件各模块基<EFBFBD><EFBFBD><EFBFBD>化 ====== // ====== 件各模块基础初始化 ======
GPIO_Init(); // 配置各个引脚的推挽输<EFBFBD><EFBFBD>/准双向模式及内部上拉 (锁存 PWR_HOLD=1) GPIO_Init(); // 配置各个引脚的推挽输/准双向模式及内部上拉 (锁存 PWR_HOLD=1)
RGB_Send(0, 0, 0, 0, 0, 0); // 【<EFBFBD><EFBFBD><EFBFBD><EFBFBD>修正<EFBFBD><EFBFBD>上电<EFBFBD><EFBFBD><EFBFBD><EFBFBD>时间强制发<EFBFBD> 0 码关<EFBFBD><EFBFBD><EFBFBD><EFBFBD>条,熄灭上电暂<EFBFBD><EFBFBD>引起的随机白光<EFBFBD><EFBFBD> RGB_Send(0, 0, 0, 0, 0, 0); // 【首要修正】:上电第一时间强制发 0 码关闭灯条,熄灭上电暂引起的随机白光
RF_Init(); // 初<EFBFBD><EFBFBD>化射<EFBFBD><EFBFBD>芯片各引脚控制 RF_Init(); // 初始化射频芯片各引脚控制
Delay_ms(500); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>动延时,等待供电电<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Delay_ms(500); // 软启动延时,等待供电电容稳定
LCD_Init(); // 初<EFBFBD><EFBFBD>化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列并<EFBFBD><EFBFBD><EFBFBD><EFBFBD>背光 LCD_Init(); // 初化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列并开启背光
Uart1_Init(); // 初<EFBFBD><EFBFBD>化串口 1 波特<EFBFBD><EFBFBD> 115200 供调试日<EFBFBD><EFBFBD> Uart1_Init(); // 初化串口 1 波特 115200 供调试日
Uart_SendString("Wristband System Initialized!\r\n"); Uart_SendString("Wristband System Initialized!\r\n");
Self_Test(); // 执<><E689A7>上电硬件自<E4BBB6><E887AA> (PWR_HOLD锁存、P0.3 ADC按键常<E994AE><E5B8B8><EFBFBD><EFBFBD>P1.3 电池电量)
Load_Database(); // <20><> IAP Flash <20><> 254 扇区读取<E8AFBB><E58F96>有已绑定的传感器数据<E695B0><E68DAE> RAM
/* 步<> 1: 初<><E5889D>化事件<E4BA8B><E4BBB6>形缓冲区队列 */ Self_Test(); // 执行上电硬件自检 (PWR_HOLD锁存、P0.3 ADC按键常态、P1.3 电池电量)
Load_Database(); // 从 IAP Flash 第 254 扇区读取所有已绑定的传感器数据至 RAM
/* 步骤 1: 初始化事件环形缓冲区队列 */
EventQueue_Init(); EventQueue_Init();
/* 步<EFBFBD> 2+5: 初<EFBFBD><EFBFBD>化应用管理器并登<EFBFBD><EFBFBD>所有前台应<EFBFBD><EFBFBD>,默认加<EFBFBD><EFBFBD> ClockApp */ /* 步 2+5: 初化应用管理器并登所有前台应,默认加 ClockApp */
AppManager_Init(); AppManager_Init();
/* <EFBFBD><EFBFBD>动并挂载后台射<EFBFBD><EFBFBD>监控应<EFBFBD><EFBFBD>,开始侦<EFBFBD><EFBFBD>无线传感器信<EFBFBD><EFBFBD> */ /* 动并挂载后台射监控应,开始侦无线传感器信 */
AppManager_AttachToBackground(&rf_monitor_app); AppManager_AttachToBackground(&rf_monitor_app);
/* <EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1ms 系统基准定时<EFBFBD><EFBFBD> Timer1 */ /* 启动 1ms 系统基准定时 Timer1 */
Timer1_Init(); Timer1_Init();
/* <EFBFBD><EFBFBD>机自动加载并运<EFBFBD><EFBFBD>待机时钟前台活跃应<EFBFBD><EFBFBD> */ /* 机自动加载并运待机时钟前台活跃应 */
AppManager_StartApp(APP_ID_CLOCK); AppManager_StartApp(APP_ID_CLOCK);
Uart_SendString("[SYS] Event-driven framework ready\r\n"); Uart_SendString("[SYS] Event-driven framework ready\r\n");
// ====== 主循<EFBFBD><EFBFBD> ====== // ====== 主循 ======
while (1) { while (1) {
/* 0. <EFBFBD><EFBFBD>查主<EFBFBD><EFBFBD><EFBFBD><EFBFBD>按键定时标志 (<EFBFBD><EFBFBD> 1ms <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>位,主循<EFBFBD><EFBFBD>响应清除并<EFBFBD><EFBFBD>理) */ /* 0. 检查主循环按键定时标志 ( 1ms 中断置位,主循响应清除并理) */
if (key_scan_flag) { if (key_scan_flag) {
key_scan_flag = 0; key_scan_flag = 0;
Event_KeyScan_Poll(); Event_KeyScan_Poll();
} }
/* 1. 串口非阻塞指令捕<EFBFBD><EFBFBD> -> 装配为事件方式入<EFBFBD><EFBFBD> */ /* 1. 串口非阻塞指令捕 -> 装配为事件方式入 */
cmd = Uart_RxChar(); cmd = Uart_RxChar();
if (cmd != '\0') { if (cmd != '\0') {
Debug_ProcessCommand(cmd); Debug_ProcessCommand(cmd);
} }
/* 2. 事件分发总线:循<EFBFBD><EFBFBD><EFBFBD><EFBFBD>询并处理队列<EFBFBD><EFBFBD>的所有有效事<EFBFBD><EFBFBD> */ /* 2. 事件分发总线:循环轮询并处理队列的所有有效事 */
while (!EventQueue_IsEmpty()) { while (!EventQueue_IsEmpty()) {
Event_Dispatcher_Loop(); Event_Dispatcher_Loop();
} }
/* 3. 应用调度<EFBFBD><EFBFBD>心:运<EFBFBD><EFBFBD>当前<EFBFBD><EFBFBD>于前台活跃状<EFBFBD><EFBFBD>的应用(集成了 CPU 运<EFBFBD><EFBFBD>时间超时挂起保护) */ /* 3. 应用调度心:运行当前处于前台活跃状的应用(集成了 CPU 运时间超时挂起保护) */
AppManager_RunActiveApp(); AppManager_RunActiveApp();
/* 4. <EFBFBD><EFBFBD>动休眠判定:常<EFBFBD><EFBFBD>无操作闲置达到 60 <EFBFBD><EFBFBD> (6000 * 10ms = 60s) 时,切入低功耗停机休<EFBFBD><EFBFBD> */ /* 4. 动休眠判定:常无操作闲置达到 60 (6000 * 10ms = 60s) 时,切入低功耗停机休 */
if (inactivity_timer >= 6000) { if (inactivity_timer >= 6000) {
inactivity_timer = 0; inactivity_timer = 0;
current_state = STATE_SLEEP; // 系统<EFBFBD><EFBFBD>为休眠状<EFBFBD><EFBFBD> current_state = STATE_SLEEP; // 系统为休眠状
Enter_Low_Power_Sleep(); // 该函数是阻<EFBFBD><EFBFBD>MCU 将在此挂起<EFBFBD><EFBFBD>唤醒后将自动从其后继续执<EFBFBD> Enter_Low_Power_Sleep(); // 该函数是阻MCU 将在此挂起唤醒后将自动从其后继续执
current_state = STATE_NORMAL; // 唤醒后重新置为<EFBFBD><EFBFBD>常待机状<EFBFBD> current_state = STATE_NORMAL; // 唤醒后重新置为常待机状
AppManager_StartApp(APP_ID_CLOCK); // 重新<EFBFBD><EFBFBD><EFBFBD><EFBFBD>时钟应用以刷新画面和背光 AppManager_StartApp(APP_ID_CLOCK); // 重新开启时钟应用以刷新画面和背光
} }
} }
} }
// 唤醒源事件标志,由<EFBFBD><EFBFBD>应的<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ISR 写入 // 唤醒源事件标志,由对应的外部中断 ISR 写入
volatile u8 p0_wakeup_flag = 0; // P0 <EFBFBD><EFBFBD>口按<EFBFBD><EFBFBD>唤醒标志 volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
volatile u8 p2_wakeup_flag = 0; // P2 <EFBFBD><EFBFBD>口按<EFBFBD><EFBFBD>唤醒标志 volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
volatile u8 p3_wakeup_flag = 0; // P3 <EFBFBD><EFBFBD>口射频中<EFBFBD><EFBFBD>唤醒标志 volatile u8 p3_wakeup_flag = 0; // P3 口射频中唤醒标志
volatile u8 rf_wakeup_flag = 0; // 射<EFBFBD><EFBFBD><EFBFBD><EFBFBD>唤醒标志 volatile u8 rf_wakeup_flag = 0; // 射频中断唤醒标志
/* ========================================================================= /* =========================================================================
* STC32G <EFBFBD><EFBFBD>口掉电<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>唤醒<EFBFBD><EFBFBD><EFBFBD><EFBFBD>服务程序 (ISR) * STC32G 口掉电外部中断唤醒中断服务程序 (ISR)
* ========================================================================= */ * ========================================================================= */
/** /**
* @brief Port0 <EFBFBD><EFBFBD>口掉电中<EFBFBD><EFBFBD>服务函数 (<EFBFBD><EFBFBD> isr_jump.asm 引<EFBFBD><EFBFBD>定位至<EFBFBD><EFBFBD>) * @brief Port0 口掉电中服务函数 ( isr_jump.asm 引定位至)
* @details 负责处理 P0.1, P0.2, P0.3 按键在休眠模式下的物理触发,并立刻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>反复执行<EFBFBD> * @details 负责处理 P0.1, P0.2, P0.3 按键在休眠模式下的物理触发,并立刻禁用中断防止反复执行
*/ */
void Port0_Isr(void) interrupt 13 void Port0_Isr(void) interrupt 13
{ {
EAXFR = 1; // 使能访问扩展寄存<EFBFBD><EFBFBD> EAXFR = 1; // 使能访问扩展寄存
p0_wakeup_flag = P0INTF | 0x01;// 读取并<EFBFBD><EFBFBD>份 P0 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>标志寄存器<EFBFBD> (0x01作安全掩<EFBFBD><EFBFBD>) p0_wakeup_flag = P0INTF | 0x01;// 读取并份 P0 中断标志寄存器 (0x01作安全掩)
P0INTF = 0x00; // 清除 P0 <EFBFBD><EFBFBD>口中<EFBFBD><EFBFBD><EFBFBD><EFBFBD>挂标志位 P0INTF = 0x00; // 清除 P0 端口中断悬挂标志位
P0INTE = 0x00; // 【关<EFBFBD><EFBFBD>保护】:立即关闭 P0 <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>处于低电平期间反复触<EFBFBD><EFBFBD> ISR 导致堆栈溢出 P0INTE = 0x00; // 【关保护】:立即关闭 P0 中断允许,防止按键处于低电平期间反复触 ISR 导致堆栈溢出
} }
/** /**
* @brief Port2 <EFBFBD><EFBFBD>口掉电中<EFBFBD><EFBFBD>服务函数 (<EFBFBD><EFBFBD> isr_jump.asm 引<EFBFBD><EFBFBD>定位至<EFBFBD><EFBFBD>) * @brief Port2 口掉电中服务函数 ( isr_jump.asm 引定位至)
* @details 负责处理 P2.6 (SOS按键) <EFBFBD><EFBFBD> P2.1 (射<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) 在休眠模式下的物理跳变唤醒<EFBFBD> * @details 负责处理 P2.6 (SOS按键) P2.1 (射频数据脚) 在休眠模式下的物理跳变唤醒
*/ */
void Port2_Isr(void) interrupt 15 void Port2_Isr(void) interrupt 15
{ {
EAXFR = 1; // 使能扩展寄存<EFBFBD><EFBFBD> EAXFR = 1; // 使能扩展寄存
p2_wakeup_flag = P2INTF | 0x01;// 读取并<EFBFBD><EFBFBD>份 P2 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>标志寄存区的<EFBFBD><EFBFBD> p2_wakeup_flag = P2INTF | 0x01;// 读取并份 P2 中断标志寄存区的
P2INTF = 0x00; // 清除 P2 <EFBFBD><EFBFBD>口中<EFBFBD><EFBFBD><EFBFBD><EFBFBD>挂标志位 P2INTF = 0x00; // 清除 P2 端口中断悬挂标志位
P2INTE = 0x00; // 立即关闭 P2 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>通道,防<EFBFBD><EFBFBD>低电平期间反复重<EFBFBD><EFBFBD> P2INTE = 0x00; // 立即关闭 P2 中断通道,防低电平期间反复重
} }
/** /**
* @brief Port3 <EFBFBD><EFBFBD>口掉电中<EFBFBD><EFBFBD>服务函数 (<EFBFBD><EFBFBD> isr_jump.asm 引<EFBFBD><EFBFBD>定位至<EFBFBD><EFBFBD>) * @brief Port3 口掉电中服务函数 ( isr_jump.asm 引定位至)
* @details 负责处理 P3.6 (新射频数<EFBFBD><EFBFBD><EFBFBD><EFBFBD>) 在休眠模式下的物理跳变唤醒<EFBFBD> * @details 负责处理 P3.6 (新射频数据脚) 在休眠模式下的物理跳变唤醒
*/ */
void Port3_Isr(void) interrupt 16 void Port3_Isr(void) interrupt 16
{ {
EAXFR = 1; // 使能访问扩展寄存<EFBFBD><EFBFBD> EAXFR = 1; // 使能访问扩展寄存
p3_wakeup_flag = P3INTF | 0x01;// 读取并<EFBFBD><EFBFBD>份 P3 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>标志寄存器<EFBFBD> (0x01作安全掩<EFBFBD><EFBFBD>) p3_wakeup_flag = P3INTF | 0x01;// 读取并份 P3 中断标志寄存器 (0x01作安全掩)
P3INTF = 0x00; // 清除 P3 <EFBFBD><EFBFBD>口中<EFBFBD><EFBFBD><EFBFBD><EFBFBD>挂标志位 P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
P3INTE = 0x00; // 立即关闭 P3 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>通道,防止反复重<EFBFBD><EFBFBD> P3INTE = 0x00; // 立即关闭 P3 中断通道,防止反复重
} }

7
CLAUDE.md Normal file
View File

@@ -0,0 +1,7 @@
## Git Workflow Requirements
- You must make a separate Git commit immediately after completing every individual, logical task (e.g., a bug fix, feature addition, or test case addition).
- Never accumulate multiple independent features or fixes into a single chunk of work.
- Automatically stage the files and generate a commit message matching Conventional Commits standards (e.g., `feat(auth): add login validation`).
- Do not ask for user confirmation before running `git add` and `git commit` locally.
- each `git commit` message must not more 100 words
- always increase APP_VERSION after each change.

View File

@@ -3,108 +3,108 @@
#include "../App/config.h" #include "../App/config.h"
#include "intrins.h" #include "intrins.h"
// ====== LHS114TC-IF03 (ST7789V) <EFBFBD><EFBFBD>Ļ SPI <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӳ<EFBFBD><EFBFBD> ====== // ====== LHS114TC-IF03 (ST7789V) 屏幕 SPI 引脚映射 ======
sbit LCD_RST = P1^0; // <EFBFBD><EFBFBD>ĻӲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (RESET)<29><><EFBFBD>͵<EFBFBD>ƽ<EFBFBD><C6BD>λ sbit LCD_RST = P1^0; // 屏幕硬件复位引脚 (RESET),低电平复位
sbit LCD_DCX = P1^1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> / <20><><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (1:<3A><><EFBFBD><EFBFBD>, 0:ָ<EFBFBD><EFBFBD>) sbit LCD_DCX = P1^1; // 串行数据 / 命令选择控制线 (1:数据, 0:指令)
sbit LCD_SDI = P1^4; // SPI <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (MOSI) sbit LCD_SDI = P1^4; // SPI 主机输出从机输入数据线 (MOSI)
sbit LCD_SCL = P1^5; // SPI <EFBFBD><EFBFBD><EFBFBD>й<EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (SCLK) sbit LCD_SCL = P1^5; // SPI 串行工作时钟线 (SCLK)
sbit LCD_CS = P1^6; // SPI <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭѡѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (CS)<29><><EFBFBD>͵<EFBFBD>ƽѡͨ<D1A1><CDA8>Ļ sbit LCD_CS = P1^6; // SPI 物理片选选择端 (CS),低电平选通屏幕
// ====== LHS114TC-IF03 LEDA <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ====== // ====== LHS114TC-IF03 LEDA 背光控制引脚 ======
sbit SGM_CTRL = P1^7; // LEDA <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (0:<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, 1:<3A>ر<EFBFBD>Ϩ<EFBFBD><CFA8> - PMOS<EFBFBD><EFBFBD><EFBFBD><EFBFBD>) sbit SGM_CTRL = P1^7; // LEDA 开启引脚 (0:开启背光, 1:关闭熄灭 - PMOS控制)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ⲿ<EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> system.c <EFBFBD><EFBFBD>) // 引用外部延时函数 (定义在 system.c )
extern void Delay_ms(u16 ms); extern void Delay_ms(u16 ms);
extern void Delay10us(void); extern void Delay10us(void);
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD> 8 λ<><CEBB><EFBFBD><EFBFBD> SPI <20>ֽ<EFBFBD><D6BD><EFBFBD><EFBFBD>ݷ<EFBFBD><DDB7><EFBFBD> * @brief 软件模拟 8 位串行 SPI 字节数据发送
* @param dat <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͵<EFBFBD> 1 <20>ֽ<EFBFBD><D6BD><EFBFBD><EFBFBD><EFBFBD> * @param dat 待发送的 1 字节数据
*/ */
void SPI_SendData(u8 dat) void SPI_SendData(u8 dat)
{ {
u8 i; u8 i;
for(i = 0; i < 8; i++) for(i = 0; i < 8; i++)
{ {
LCD_SCL = 0; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>ӣ<EFBFBD>׼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> LCD_SCL = 0; // 拉低时钟,准备更新数据
if(dat & 0x80) if(dat & 0x80)
LCD_SDI = 1; LCD_SDI = 1;
else else
LCD_SDI = 0; LCD_SDI = 0;
_nop_(); _nop_();
LCD_SCL = 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>ӣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ LCD_SCL = 1; // 拉高时钟,触发屏幕在上升沿采样数据位
_nop_(); _nop_();
dat <<= 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>һλ<EFBFBD><EFBFBD>׼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һλ dat <<= 1; // 左移一位,准备发送下一位
} }
} }
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD>ֽڿ<EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> (DCX=0) * @brief 向屏幕控制器写入单字节控制指令 (DCX=0)
*/ */
void WriteComm(u8 cmd) void WriteComm(u8 cmd)
{ {
LCD_CS = 0; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭѡ LCD_CS = 0; // 开启片选
LCD_DCX = 0; // <EFBFBD>л<EFBFBD>Ϊָ<EFBFBD><EFBFBD><EFBFBD>ģʽ LCD_DCX = 0; // 切换为指令传输模式
SPI_SendData(cmd); SPI_SendData(cmd);
LCD_CS = 1; // <EFBFBD>ر<EFBFBD>Ƭѡ LCD_CS = 1; // 关闭片选
} }
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (DCX=1) * @brief 向屏幕控制器写入单字节显示数据 (DCX=1)
*/ */
void WriteData(u8 dat) void WriteData(u8 dat)
{ {
LCD_CS = 0; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭѡ LCD_CS = 0; // 开启片选
LCD_DCX = 1; // <EFBFBD>л<EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD><EFBFBD><EFBFBD>ģʽ LCD_DCX = 1; // 切换为数据传输模式
SPI_SendData(dat); SPI_SendData(dat);
LCD_CS = 1; // <EFBFBD>ر<EFBFBD>Ƭѡ LCD_CS = 1; // 关闭片选
} }
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>ִ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĻӲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ * @brief 按照物理时序执行屏幕硬件复位
*/ */
void LCD_Reset(void) void LCD_Reset(void)
{ {
LCD_RST = 1; LCD_RST = 1;
Delay_ms(10); Delay_ms(10);
LCD_RST = 0; // <EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD>Ž<EFBFBD><EFBFBD><EFBFBD>Ӳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ״̬ LCD_RST = 0; // 拉低复位引脚进入硬件复位状态
Delay_ms(20); Delay_ms(20);
LCD_RST = 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> LCD_RST = 1; // 重新拉高引脚
Delay_ms(120); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 120ms <20><><EFBFBD><EFBFBD>ִ<EFBFBD>к<EFBFBD><D0BA><EFBFBD><EFBFBD>ij<EFBFBD>ʼ<EFBFBD><CABC> Delay_ms(120); // 必须等待至少 120ms 才能执行后续的初始化
} }
/** /**
* @brief <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD> LHS114TC-IF03 (ST7789V) <EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> * @brief 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制器
*/ */
void LCD_Init(void) void LCD_Init(void)
{ {
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>չ SFR <20><><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8> (EAXFR = 1)<29><>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD>Բ<EFBFBD><D4B2><EFBFBD> P1M1 <EFBFBD><EFBFBD> P1M0 ģʽ<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // 开启扩展 SFR 访问权限 (EAXFR = 1),确保可以操作 P1M1 P1M0 模式寄存器
P_SW2 |= 0x80; P_SW2 |= 0x80;
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> P1 <20>˿ڵ<CBBF> LCD_RST(P1.0)/DCX(P1.1)/SDI(P1.4)/SCL(P1.5)/CS(P1.6)/LEDA(P1.7) Ϊ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ (M1=0, M0=1) // 配置 P1 端口的 LCD_RST(P1.0)/DCX(P1.1)/SDI(P1.4)/SCL(P1.5)/CS(P1.6)/LEDA(P1.7) 为推挽输出模式 (M1=0, M0=1)
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7)); P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7)); P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
// <EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ų<EFBFBD>ʼĬ<EFBFBD>ϵ<EFBFBD>ƽ // 设置各控制引脚初始默认电平
SGM_CTRL = 0; // PMOS<EFBFBD>͵<EFBFBD>ƽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (LEDA = 0) SGM_CTRL = 0; // PMOS低电平点亮背光 (LEDA = 0)
LCD_CS = 1; LCD_CS = 1;
LCD_SCL = 1; LCD_SCL = 1;
LCD_RST = 1; LCD_RST = 1;
LCD_DCX = 1; LCD_DCX = 1;
LCD_Reset(); // Ӳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>Ļ LCD_Reset(); // 硬件复位屏幕
// ====== д<EFBFBD><EFBFBD> ST7789V ԭ<EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ====== // ====== 写入 ST7789V 原厂寄存器初始化指令序列 ======
WriteComm(0x11); // Sleep Out <EFBFBD>˳<EFBFBD>˯<EFBFBD><EFBFBD> WriteComm(0x11); // Sleep Out 退出睡眠
Delay_ms(120); // <EFBFBD>˳<EFBFBD>˯<EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 120ms Delay_ms(120); // 退出睡眠后必须延时至少 120ms
// Memory Data Access Control (MADCTL) ɨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // Memory Data Access Control (MADCTL) 扫描方向控制
// 0x00: <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ң<EFBFBD>RGB <EFBFBD><EFBFBD>ʽ // 0x00: 从上至下,从左至右,RGB 格式
WriteComm(0x36); WriteComm(0x36);
WriteData(0x00); WriteData(0x00);
// Interface Pixel Format (COLMOD) // Interface Pixel Format (COLMOD)
// 0x05 <EFBFBD><EFBFBD><EFBFBD><EFBFBD> 16-bit/pixel (RGB565<EFBFBD><EFBFBD>ʽ) // 0x05 代表 16-bit/pixel (RGB565格式)
WriteComm(0x3A); WriteComm(0x3A);
WriteData(0x05); WriteData(0x05);
@@ -154,50 +154,50 @@ void LCD_Init(void)
WriteData(0xA4); WriteData(0xA4);
WriteData(0xA1); WriteData(0xA1);
// Positive Gamma Correction (E0h) <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>٤<EFBFBD><EFBFBD> // Positive Gamma Correction (E0h) 正极性伽马
WriteComm(0xE0); WriteComm(0xE0);
WriteData(0xD0); WriteData(0x04); WriteData(0x0D); WriteData(0x11); WriteData(0xD0); WriteData(0x04); WriteData(0x0D); WriteData(0x11);
WriteData(0x13); WriteData(0x2B); WriteData(0x3F); WriteData(0x54); WriteData(0x13); WriteData(0x2B); WriteData(0x3F); WriteData(0x54);
WriteData(0x4C); WriteData(0x18); WriteData(0x0D); WriteData(0x0B); WriteData(0x4C); WriteData(0x18); WriteData(0x0D); WriteData(0x0B);
WriteData(0x1F); WriteData(0x23); WriteData(0x1F); WriteData(0x23);
// Negative Gamma Correction (E1h) <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>٤<EFBFBD><EFBFBD> // Negative Gamma Correction (E1h) 负极性伽马
WriteComm(0xE1); WriteComm(0xE1);
WriteData(0xD0); WriteData(0x04); WriteData(0x0C); WriteData(0x11); WriteData(0xD0); WriteData(0x04); WriteData(0x0C); WriteData(0x11);
WriteData(0x13); WriteData(0x2C); WriteData(0x3F); WriteData(0x44); WriteData(0x13); WriteData(0x2C); WriteData(0x3F); WriteData(0x44);
WriteData(0x51); WriteData(0x2F); WriteData(0x1F); WriteData(0x1F); WriteData(0x51); WriteData(0x2F); WriteData(0x1F); WriteData(0x1F);
WriteData(0x20); WriteData(0x23); WriteData(0x20); WriteData(0x23);
// Display Inversion On (21h) <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԣ<EFBFBD>ST7789 IPS <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // Display Inversion On (21h) 开启反显,ST7789 IPS 屏常用
WriteComm(0x21); WriteComm(0x21);
// Display On (29h) <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ // Display On (29h) 开启显示
WriteComm(0x29); WriteComm(0x29);
Delay_ms(20); Delay_ms(20);
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>˲<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 开启显示后立即进行全屏清屏,防止屏幕亮起瞬间呈现花屏随机噪点
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
} }
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݸ<EFBFBD><EFBFBD>»<EFBFBD><EFBFBD>ƴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> * @brief 设定屏幕的数据更新绘制窗口区域
*/ */
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end) void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
{ {
// <EFBFBD>ڵײ㴰<EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD>ͳһ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƫ<EFBFBD>õ<EFBFBD>͸<EFBFBD><EFBFBD>У<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 在底层窗口设定中,统一进行物理偏置的透明校正,免除上层各个绘图函数的手工计算
x_start += LCD_X_OFFSET; x_start += LCD_X_OFFSET;
x_end += LCD_X_OFFSET; x_end += LCD_X_OFFSET;
y_start += LCD_Y_OFFSET; y_start += LCD_Y_OFFSET;
y_end += LCD_Y_OFFSET; y_end += LCD_Y_OFFSET;
// д<EFBFBD><EFBFBD> Column Address Set <EFBFBD>е<EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD>üĴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (0x2A) // 写入 Column Address Set 列地址设置寄存器命令 (0x2A)
WriteComm(0x2A); WriteComm(0x2A);
WriteData((u8)(x_start >> 8)); WriteData((u8)(x_start >> 8));
WriteData((u8)(x_start & 0xFF)); WriteData((u8)(x_start & 0xFF));
WriteData((u8)(x_end >> 8)); WriteData((u8)(x_end >> 8));
WriteData((u8)(x_end & 0xFF)); WriteData((u8)(x_end & 0xFF));
// д<EFBFBD><EFBFBD> Row Address Set <EFBFBD>е<EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD>üĴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (0x2B) // 写入 Row Address Set 行地址设置寄存器命令 (0x2B)
WriteComm(0x2B); WriteComm(0x2B);
WriteData((u8)(y_start >> 8)); WriteData((u8)(y_start >> 8));
WriteData((u8)(y_start & 0xFF)); WriteData((u8)(y_start & 0xFF));
@@ -206,26 +206,26 @@ void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
} }
/** /**
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD>ˢд<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><EFBFBD>Ļ * @brief 用指定色彩刷写清空全屏幕
* @param color 16λ RGB565 <EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ * @param color 16 RGB565 颜色填充值
*/ */
void LCD_Clear(u16 color) void LCD_Clear(u16 color)
{ {
u16 i, j; u16 i, j;
LCD_SetWindow(0, LCD_WIDTH - 1, 0, LCD_HEIGHT - 1); LCD_SetWindow(0, LCD_WIDTH - 1, 0, LCD_HEIGHT - 1);
WriteComm(0x2C); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> RAM Write (0x2C) <EFBFBD>Դ<EFBFBD>д<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> WriteComm(0x2C); // 发送 RAM Write (0x2C) 显存写入命令
for(i = 0; i < LCD_WIDTH; i++) for(i = 0; i < LCD_WIDTH; i++)
{ {
for(j = 0; j < LCD_HEIGHT; j++) for(j = 0; j < LCD_HEIGHT; j++)
{ {
WriteData((u8)(color >> 8)); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> RGB565 <EFBFBD><EFBFBD> 8 λ<EFBFBD>ֽ<EFBFBD> WriteData((u8)(color >> 8)); // 发送 RGB565 8 位字节
WriteData((u8)(color & 0xFF)); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> RGB565 <EFBFBD><EFBFBD> 8 λ<EFBFBD>ֽ<EFBFBD> WriteData((u8)(color & 0xFF)); // 发送 RGB565 8 位字节
} }
} }
} }
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļָ<EFBFBD><EFBFBD><EFBFBD>ӿڻ<EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD>ľ<EFBFBD><EFBFBD><EFBFBD> * @brief 在屏幕指定视口绘制实心矩形
*/ */
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color) void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
{ {
@@ -234,8 +234,8 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
u8 cl = (u8)(color & 0xFF); u8 cl = (u8)(color & 0xFF);
LCD_SetWindow(x, x + w - 1, y, y + h - 1); LCD_SetWindow(x, x + w - 1, y, y + h - 1);
WriteComm(0x2C); // д<EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD> WriteComm(0x2C); // 写入显存
count = w * h; // ʵ<EFBFBD>ľ<EFBFBD><EFBFBD>ΰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> count = w * h; // 实心矩形包含的总像素数
for(i = 0; i < count; i++) for(i = 0; i < count; i++)
{ {
WriteData(ch); WriteData(ch);
@@ -244,55 +244,55 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
} }
/** /**
* @brief <EFBFBD><EFBFBD><EFBFBD>ƿ<EFBFBD><EFBFBD>ľ<EFBFBD><EFBFBD>α߽<EFBFBD><EFBFBD><EFBFBD> * @brief 绘制空心矩形边界框
* @details ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD> LCD_FillRect ʵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƾ<EFBFBD><EFBFBD>ε<EFBFBD><EFBFBD>ϡ<EFBFBD><EFBFBD>¡<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ߡ<EFBFBD> * @details 通过组合调用四次 LCD_FillRect 实心线来绘制矩形的上、下、左、右四条边。
*/ */
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color) void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
{ {
LCD_FillRect(x, y, w, 1, color); // <EFBFBD>ϱ߿<EFBFBD><EFBFBD><EFBFBD> LCD_FillRect(x, y, w, 1, color); // 上边框线
LCD_FillRect(x, y + h - 1, w, 1, color); // <EFBFBD>±߿<EFBFBD><EFBFBD><EFBFBD> LCD_FillRect(x, y + h - 1, w, 1, color); // 下边框线
LCD_FillRect(x, y, 1, h, color); // <EFBFBD><EFBFBD><EFBFBD>߿<EFBFBD><EFBFBD><EFBFBD> LCD_FillRect(x, y, 1, h, color); // 左边框线
LCD_FillRect(x + w - 1, y, 1, h, color); // <EFBFBD>ұ߿<EFBFBD><EFBFBD><EFBFBD> LCD_FillRect(x + w - 1, y, 1, h, color); // 右边框线
} }
/** /**
* @brief <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ʾһ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD>ģλͼ<EFBFBD><EFBFBD>Դ * @brief 渲染显示一个单色字模位图资源
* @param x <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> X * @param x 渲染起始坐标 X
* @param y <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Y * @param y 渲染起始坐标 Y
* @param w λͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><>λ: <20><><EFBFBD>ص<EFBFBD><D8B5><EFBFBD>) * @param w 位图宽度 (单位: 像素点数)
* @param h λͼ<EFBFBD>߶<EFBFBD> (<28><>λ: <20><><EFBFBD>ص<EFBFBD><D8B5><EFBFBD>) * @param h 位图高度 (单位: 像素点数)
* @param bmp FLASH <EFBFBD>еĵ<EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> (1<><31>ʾ<EFBFBD><CABE>ǰ<EFBFBD><C7B0>ɫ<EFBFBD><C9AB>0<EFBFBD><30>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ) * @param bmp FLASH 中的单色像素数组指针 (1表示画前景色0表示画背景色)
* @param color 16λǰ<EFBFBD><EFBFBD>ɫ * @param color 16位前景色
* @param bg_color 16λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ * @param bg_color 16位背景色
*/ */
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color) void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color)
{ {
u16 r, c; u16 r, c;
u16 byte_width = (w + 7) / 8; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD>һ<EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD>ռ<EFBFBD>õ<EFBFBD>ʵ<EFBFBD><EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD><EFBFBD> u16 byte_width = (w + 7) / 8; // 计算在单色编码中,一行图像占用的实际字节数
u8 ch = (u8)(color >> 8); u8 ch = (u8)(color >> 8);
u8 cl = (u8)(color & 0xFF); u8 cl = (u8)(color & 0xFF);
u8 bgh = (u8)(bg_color >> 8); u8 bgh = (u8)(bg_color >> 8);
u8 bgl = (u8)(bg_color & 0xFF); u8 bgl = (u8)(bg_color & 0xFF);
LCD_SetWindow(x, x + w - 1, y, y + h - 1); // <EFBFBD><EFBFBD><EFBFBD>ƻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ洰<EFBFBD><EFBFBD> LCD_SetWindow(x, x + w - 1, y, y + h - 1); // 限制绘制显存窗口
WriteComm(0x2C); WriteComm(0x2C);
// Ƕ<EFBFBD><EFBFBD>ѭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӵ<EFBFBD> 0 <20>е<EFBFBD> 0 <20>п<EFBFBD>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>ÿ<EFBFBD><C3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><CEBB> // 嵌套循环,从第 0 行第 0 列开始遍历每个像素位置
for(r = 0; r < h; r++) for(r = 0; r < h; r++)
{ {
for(c = 0; c < w; c++) for(c = 0; c < w; c++)
{ {
u16 byte_idx = r * byte_width + (c / 8); // <EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD>ɫͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> u16 byte_idx = r * byte_width + (c / 8); // 定位当前像素所在单色图数组中的字节索引
u8 bit_shift = 7 - (c % 8); // <EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڸ<EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD>ж<EFBFBD>Ӧ<EFBFBD><EFBFBD> bit λ<><CEBB> (<28><>λ<EFBFBD><CEBB>ǰ) u8 bit_shift = 7 - (c % 8); // 定位该像素在该字节中对应的 bit 位置 (高位在前)
if(bmp[byte_idx] & (1 << bit_shift)) if(bmp[byte_idx] & (1 << bit_shift))
{ {
WriteData(ch); // <EFBFBD><EFBFBD>λΪ 1<><31><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0>ɫ WriteData(ch); // 该位为 1发送前景色
WriteData(cl); WriteData(cl);
} }
else else
{ {
WriteData(bgh); // <EFBFBD><EFBFBD>λΪ 0<><30><EFBFBD><EFBFBD><EFBFBD>ͱ<EFBFBD><CDB1><EFBFBD>ɫ WriteData(bgh); // 该位为 0发送背景色
WriteData(bgl); WriteData(bgl);
} }
} }
@@ -300,30 +300,30 @@ void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16
} }
/** /**
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD> 8x16 <20>ߴ<EFBFBD><DFB4><EFBFBD><EFBFBD><EFBFBD>ͨӢ<CDA8><D3A2> ASCII <EFBFBD>ַ<EFBFBD> * @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
* @param ch ASCII <EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> * @param ch ASCII 字符本身
*/ */
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color) void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{ {
u8 r, c; u8 r, c;
u8 char_idx = ch - 0x20; // ƫ<EFBFBD>Ƽ<EFBFBD><EFBFBD>㣺ASCII <20>ɴ<EFBFBD>ӡ<EFBFBD>ַ<EFBFBD><D6B7><EFBFBD> 0x20 (<28>ո<EFBFBD>) <20><>ʼ u8 char_idx = ch - 0x20; // 偏移计算ASCII 可打印字符从 0x20 (空格) 开始
u8 f_h = (u8)(color >> 8); u8 f_h = (u8)(color >> 8);
u8 f_l = (u8)(color & 0xFF); u8 f_l = (u8)(color & 0xFF);
u8 b_h = (u8)(bg_color >> 8); u8 b_h = (u8)(bg_color >> 8);
u8 b_l = (u8)(bg_color & 0xFF); u8 b_l = (u8)(bg_color & 0xFF);
if(ch < 0x20 || ch > 0x7E) return; // <EFBFBD><EFBFBD><EFBFBD>˲<EFBFBD><EFBFBD>ɼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD> if(ch < 0x20 || ch > 0x7E) return; // 过滤不可见控制字符
LCD_SetWindow(x, x + 8 - 1, y, y + 16 - 1); LCD_SetWindow(x, x + 8 - 1, y, y + 16 - 1);
WriteComm(0x2C); WriteComm(0x2C);
// ѭ<EFBFBD><EFBFBD> 16 <EFBFBD>У<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 8x16 <EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 循环 16 行,绘制 8x16 字型
for(r = 0; r < 16; r++) for(r = 0; r < 16; r++)
{ {
u8 line = FONT_8x16[char_idx][r]; // <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰ<EFBFBD>ж<EFBFBD>Ӧ<EFBFBD><EFBFBD> 8 λ<><CEBB>ɫ<EFBFBD><C9AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ u8 line = FONT_8x16[char_idx][r]; // 提取当前行对应的 8 位单色像素信息
for(c = 0; c < 8; c++) for(c = 0; c < 8; c++)
{ {
// <EFBFBD>Ӹߵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD> // 从高到低依次提取单行中的像素点
if(line & (0x80 >> c)) if(line & (0x80 >> c))
{ {
WriteData(f_h); WriteData(f_h);
@@ -339,37 +339,37 @@ void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
} }
/** /**
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӡһ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨӢ<EFBFBD><EFBFBD> ASCII <20>ַ<EFBFBD><D6B7><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) * @brief 在指定坐标打印一行普通英文 ASCII 字符串 (左对齐)
*/ */
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{ {
while(*str) while(*str)
{ {
LCD_ShowChar8x16(x, y, *str, color, bg_color); // <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ǰ<EFBFBD>ַ<EFBFBD> LCD_ShowChar8x16(x, y, *str, color, bg_color); // 渲染当前字符
x += 8; // X <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ<EFBFBD><EFBFBD>ƫ<EFBFBD><EFBFBD> 8 <20><><EFBFBD>أ<EFBFBD>׼<EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ַ<EFBFBD> x += 8; // X 轴起始位置偏移 8 像素,准备绘制下一个字符
str++; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD> str++; // 移向下一个字符
} }
} }
/** /**
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> Y <20><><EFBFBD>߶<EFBFBD><DFB6>ϣ<EFBFBD><CFA3>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>ƫ<EFBFBD><C6AB><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD>ˮƽ<CBAE><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ * @brief 在指定 Y 轴高度上,自动计算偏移以实现字符串水平居中显示
*/ */
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color)
{ {
u16 len = 0; u16 len = 0;
char *p = str; char *p = str;
while(*p++) len++; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> while(*p++) len++; // 计算字符串长度
if(len * 8 >= LCD_WIDTH) if(len * 8 >= LCD_WIDTH)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD>Ч<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>ǿ<EFBFBD>ƴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0 <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 若超出或等同于屏幕有效宽度,强制从最左侧 0 开始左对齐绘制
LCD_ShowString(0, y, str, color, bg_color); LCD_ShowString(0, y, str, color, bg_color);
else else
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ (LCD_WIDTH - <EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>) / 2 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˮƽ<CBAE><C6BD><EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼƫ<CABC><C6AB> X <20><><EFBFBD><EFBFBD><EAB2A2>Ⱦ // 否则通过公式 (LCD_WIDTH - 字符宽度) / 2 计算出水平居中的首字起始偏移 X 坐标并渲染
LCD_ShowString((LCD_WIDTH - len * 8) / 2, y, str, color, bg_color); LCD_ShowString((LCD_WIDTH - len * 8) / 2, y, str, color, bg_color);
} }
/** /**
* @brief <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ش<EFBFBD> 16x32 <20>ߴ<EFBFBD><DFB4><EFBFBD>Ӣ<EFBFBD><D3A2>/<2F><><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD> * @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
*/ */
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color) void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{ {
@@ -383,22 +383,22 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
if(ch < 0x20 || ch > 0x7E) return; if(ch < 0x20 || ch > 0x7E) return;
LCD_SetWindow(x, x + 16 - 1, y, y + 32 - 1); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> 16x32 <EFBFBD>Ļ<EFBFBD>ͼ<EFBFBD>Ӵ<EFBFBD> LCD_SetWindow(x, x + 16 - 1, y, y + 32 - 1); // 锁定 16x32 的绘图视窗
WriteComm(0x2C); WriteComm(0x2C);
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͵<EFBFBD> 16 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> data // 遍历字型的 16 个点阵行 data
for(r = 0; r < 16; r++) for(r = 0; r < 16; r++)
{ {
u8 line = FONT_8x16[char_idx][r]; u8 line = FONT_8x16[char_idx][r];
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (i = 0 <EFBFBD><EFBFBD> i = 1) // 纵向插点翻倍:将当前行重复绘制两次 (i = 0 i = 1)
for(i = 0; i < 2; i++) for(i = 0; i < 2; i++)
{ {
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD> 8 <EFBFBD><EFBFBD> bit <EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD> // 遍历单行内的 8 bit 像素点
for(c = 0; c < 8; c++) for(c = 0; c < 8; c++)
{ {
val_bit = (line & (0x80 >> c)); val_bit = (line & (0x80 >> c));
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD><EFBFBD>ظ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD> 2 <EFBFBD><EFBFBD> (j = 0 <EFBFBD><EFBFBD> j = 1) // 横向插点翻倍:单个像素点重复发送写入 2 (j = 0 j = 1)
for(j = 0; j < 2; j++) for(j = 0; j < 2; j++)
{ {
if(val_bit) if(val_bit)
@@ -418,31 +418,31 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
} }
/** /**
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ش<EFBFBD> 16x32 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><><D3A2><EFBFBD>ִ<EFBFBD> * @brief 在指定坐标绘制特大 16x32 规格的数字/英文字串
*/ */
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{ {
while(*str) while(*str)
{ {
LCD_ShowChar16x32(x, y, *str, color, bg_color); LCD_ShowChar16x32(x, y, *str, color, bg_color);
x += 16; // ÿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƫ<EFBFBD><EFBFBD> 16 <20><><EFBFBD><EFBFBD> x += 16; // 每次起始坐标偏移 16 像素
str++; str++;
} }
} }
/** /**
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> Y <20><><EFBFBD>߶<EFBFBD><DFB6>ϣ<EFBFBD>ˮƽ<CBAE><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ⱦ<EFBFBD><C8BE>ʾ<EFBFBD>ش<EFBFBD> 16x32 <20><><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD> * @brief 在指定 Y 轴高度上,水平居中渲染显示特大 16x32 规格字符串
*/ */
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color)
{ {
u16 len = 0; u16 len = 0;
char *p = str; char *p = str;
while(*p++) len++; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> while(*p++) len++; // 计算字数
if(len * 16 >= LCD_WIDTH) if(len * 16 >= LCD_WIDTH)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 超出屏幕宽度,左对齐
LCD_ShowString16x32(0, y, str, color, bg_color); LCD_ShowString16x32(0, y, str, color, bg_color);
else else
// ˮƽ<EFBFBD><EFBFBD><EFBFBD>м<EFBFBD><EFBFBD><EFBFBD> // 水平居中计算
LCD_ShowString16x32((LCD_WIDTH - len * 16) / 2, y, str, color, bg_color); LCD_ShowString16x32((LCD_WIDTH - len * 16) / 2, y, str, color, bg_color);
} }