Compare commits
52 Commits
7ebe802410
...
by_edison
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ef5991d33 | ||
|
|
6022f0a409 | ||
|
|
6baf93c550 | ||
|
|
81ebf7e302 | ||
|
|
cb37b1deea | ||
|
|
1146bade70 | ||
|
|
4a55c1f9eb | ||
|
|
983b0928dd | ||
|
|
dac2ea4335 | ||
|
|
d4b89c9336 | ||
|
|
05f05177de | ||
|
|
2a83079007 | ||
|
|
cbd480eea3 | ||
|
|
f9bea08375 | ||
|
|
79d604a387 | ||
|
|
ae235d3c51 | ||
|
|
a771d1a970 | ||
|
|
ec556fdd2a | ||
|
|
56b92a8ede | ||
|
|
655d29bbe1 | ||
|
|
653fc85ae5 | ||
|
|
3000780df5 | ||
|
|
08dbdabfcb | ||
|
|
4a89aef71e | ||
|
|
29194a4f5d | ||
|
|
18d272aac9 | ||
|
|
767aa14086 | ||
|
|
3c896ec972 | ||
|
|
a7a2cc9465 | ||
|
|
d8df5d49d3 | ||
| 41bb78aa59 | |||
| be397fba76 | |||
| 58dfe5bb9c | |||
| a766ada53d | |||
| 48db6523fb | |||
| 2f4dd2660d | |||
| 0a79e7a3b9 | |||
| d7cb2ae37f | |||
| 8367524eec | |||
| d01121e3ae | |||
|
|
47bcd252b2 | ||
|
|
7cbd83c40b | ||
|
|
32e48f0576 | ||
|
|
9b6b61739c | ||
|
|
790583781f | ||
|
|
c0d1fe74f5 | ||
|
|
8fdbb32f42 | ||
|
|
31f5066052 | ||
|
|
731115e09a | ||
|
|
afd59d28ee | ||
|
|
7292fd0fcb | ||
|
|
601e01e954 |
182
App/app_alarm.c
Normal file
182
App/app_alarm.c
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "../Drivers/lcd.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u32 captured_addr;
|
||||||
|
extern u8 alarm_sensor_slot;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
extern volatile bit alarm_flash_flag;
|
||||||
|
|
||||||
|
// 传感器存储列表数据库声明
|
||||||
|
extern Sensor_Slot xdata sensor_list[16];
|
||||||
|
|
||||||
|
// 本地私有变量
|
||||||
|
static u32 alarm_pulse_timer = 0; // 报警震动波形计数器
|
||||||
|
static u16 last_run_tick = 0; // 记录上一次轮询的 ticks
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void AlarmApp_OnStart(void);
|
||||||
|
static void AlarmApp_onRun(void);
|
||||||
|
static void AlarmApp_onClose(void);
|
||||||
|
static EventResult AlarmApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义警报应用控制块
|
||||||
|
WristbandApp alarm_app = {
|
||||||
|
APP_ID_ALARM,
|
||||||
|
"Alarm",
|
||||||
|
AlarmApp_OnStart,
|
||||||
|
AlarmApp_onRun,
|
||||||
|
AlarmApp_onClose,
|
||||||
|
AlarmApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 启动回调
|
||||||
|
*/
|
||||||
|
static void AlarmApp_OnStart(void)
|
||||||
|
{
|
||||||
|
u8 slot;
|
||||||
|
u8 type = 0;
|
||||||
|
u8 zone = 1;
|
||||||
|
|
||||||
|
current_state = STATE_ALARM; // 系统切入报警状态
|
||||||
|
alarm_pulse_timer = 0; // 振动计时器清零
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
// 比对查出当前触发警报的传感器在数据库中的属性
|
||||||
|
if (Check_Sensor_ID(captured_addr, &slot)) {
|
||||||
|
type = sensor_list[slot].type;
|
||||||
|
zone = sensor_list[slot].zone;
|
||||||
|
alarm_sensor_slot = slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染显示报警图文界面
|
||||||
|
UI_ShowAlarmPage(type, zone);
|
||||||
|
|
||||||
|
// 首次亮起防区特定的警示色彩,起到区分报警硬件的目的
|
||||||
|
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0); // 门磁: 红色 (GRB中R=180,G=0)
|
||||||
|
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0); // 红外PIR: 黄色 (G=100, R=180)
|
||||||
|
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0); // 烟感: 绿色 (G=180)
|
||||||
|
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180); // 气感: 紫色 (R=180, B=180)
|
||||||
|
else RGB_Send(0, 0, 180, 0, 0, 180); // 水浸: 蓝色 (B=180)
|
||||||
|
|
||||||
|
XTELL_LOG("[AlarmApp] Started. Type=");
|
||||||
|
XTELL_LOG_HEX8(type);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 轮询主回调
|
||||||
|
* @details 包含同步的灯光爆闪和 Truly AMOLED 像素边框闪烁动效,
|
||||||
|
* 以及马达差异化震动波形逻辑的自处理。
|
||||||
|
*/
|
||||||
|
static void AlarmApp_onRun(void)
|
||||||
|
{
|
||||||
|
u8 type = sensor_list[alarm_sensor_slot].type;
|
||||||
|
u8 flash_light = (ms_tick / 250) % 2; // 250ms 周期亮灭
|
||||||
|
u16 tick_diff;
|
||||||
|
|
||||||
|
// 1. 同步控制 RGB 幻彩灯条进行交替爆闪
|
||||||
|
if (flash_light)
|
||||||
|
{
|
||||||
|
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0);
|
||||||
|
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0);
|
||||||
|
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0);
|
||||||
|
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180);
|
||||||
|
else RGB_Send(0, 0, 180, 0, 0, 180);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 控制 Truly AMOLED 屏幕的最外围红色警告框闪烁效果
|
||||||
|
if (alarm_flash_flag) {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
||||||
|
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
|
||||||
|
} else {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
|
||||||
|
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 计算时间差并执行马达差异化震动状态机控制
|
||||||
|
if (ms_tick >= last_run_tick) {
|
||||||
|
tick_diff = ms_tick - last_run_tick;
|
||||||
|
} else {
|
||||||
|
tick_diff = 1000 + ms_tick - last_run_tick;
|
||||||
|
}
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
alarm_pulse_timer += tick_diff;
|
||||||
|
if (alarm_pulse_timer >= 5000) {
|
||||||
|
alarm_pulse_timer = 0; // 5秒一周期
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 0) {
|
||||||
|
// 门磁警报波形:单次震动 300ms,停 4700ms
|
||||||
|
if (alarm_pulse_timer < 300) MOTOR = 1; else MOTOR = 0;
|
||||||
|
} else if (type == 1) {
|
||||||
|
// PIR 警报波形:震动 300ms,停 200ms,再震动 300ms,停 4200ms
|
||||||
|
if (alarm_pulse_timer < 300) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 300 && alarm_pulse_timer < 500) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 500 && alarm_pulse_timer < 800) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else if (type == 2) {
|
||||||
|
// 烟感警报波形:急促短闪三下 200ms-150ms-200ms-150ms-200ms
|
||||||
|
if (alarm_pulse_timer < 200) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 200 && alarm_pulse_timer < 350) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 350 && alarm_pulse_timer < 550) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 550 && alarm_pulse_timer < 700) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 700 && alarm_pulse_timer < 900) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else if (type == 3) {
|
||||||
|
// 燃气报警波形:快频连续四下
|
||||||
|
if (alarm_pulse_timer < 200) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 200 && alarm_pulse_timer < 300) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 300 && alarm_pulse_timer < 500) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 500 && alarm_pulse_timer < 600) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 600 && alarm_pulse_timer < 800) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 800 && alarm_pulse_timer < 900) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 900 && alarm_pulse_timer < 1100) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else if (type == 4) {
|
||||||
|
// 水浸警报波形:慢速双长震波 600ms 和 600ms
|
||||||
|
if (alarm_pulse_timer < 600) MOTOR = 1;
|
||||||
|
else if (alarm_pulse_timer >= 600 && alarm_pulse_timer < 900) MOTOR = 0;
|
||||||
|
else if (alarm_pulse_timer >= 900 && alarm_pulse_timer < 1500) MOTOR = 1;
|
||||||
|
else MOTOR = 0;
|
||||||
|
} else {
|
||||||
|
// 缺省状态默认单次 300ms
|
||||||
|
if (alarm_pulse_timer < 300) MOTOR = 1; else MOTOR = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 退出清理
|
||||||
|
*/
|
||||||
|
static void AlarmApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
|
||||||
|
MOTOR = 0; // 停震
|
||||||
|
current_state = STATE_NORMAL;
|
||||||
|
XTELL_LOG("[AlarmApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlarmApp 事件拦截处理 (按任意键退出报警)
|
||||||
|
*/
|
||||||
|
static EventResult AlarmApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (evt->key_event != KEY_EVENT_NONE) {
|
||||||
|
AppManager_SwitchToForeground(&home_app); // 清除报警并切回时钟待机页
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
130
App/app_home.c
Normal file
130
App/app_home.c
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "clock.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
#include "../Drivers/lcd.h"
|
||||||
|
#include "../Drivers/pcf8563.h"
|
||||||
|
|
||||||
|
// 外部引用的全局系统状态变量
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
|
||||||
|
// 待机时钟界面闲置多久自动进入低功耗休眠的门限 (秒)
|
||||||
|
#define HOME_IDLE_SLEEP_SEC 10
|
||||||
|
|
||||||
|
// 本应用私有变量:累计未按任何键的秒数 (由 KEY_EVENT_SECOND_TICK 驱动,任意按键事件清零)
|
||||||
|
static u8 home_idle_sec = 0;
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void HomeApp_OnStart(void);
|
||||||
|
static void HomeApp_onRun(void);
|
||||||
|
static void HomeApp_onClose(void);
|
||||||
|
static EventResult HomeApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义待机时钟主页应用控制块 (开机默认显示的首屏)
|
||||||
|
WristbandApp home_app = {
|
||||||
|
APP_ID_HOME,
|
||||||
|
"Home",
|
||||||
|
HomeApp_OnStart,
|
||||||
|
HomeApp_onRun,
|
||||||
|
HomeApp_onClose,
|
||||||
|
HomeApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HomeApp 启动回调
|
||||||
|
*/
|
||||||
|
static void HomeApp_OnStart(void)
|
||||||
|
{
|
||||||
|
home_idle_sec = 0; // 重新进入待机主页 (开机/从菜单退回/休眠唤醒) 都清零闲置计数
|
||||||
|
current_state = STATE_NORMAL; // 系统切入正常待机状态
|
||||||
|
RF_SetMode(1); // 开启天线并置为接收模式,监听无线探测器
|
||||||
|
|
||||||
|
#if USE_PCF8563
|
||||||
|
// 尝试从 PCF8563 RTC 芯片中读取硬件时间与日期 (具备非阻塞防卡死保护)
|
||||||
|
if (PCF8563_Init()) {
|
||||||
|
PCF8563_ReadDateTime(¤t_hour, ¤t_min, ¤t_sec, ¤t_day, ¤t_month, ¤t_year);
|
||||||
|
Clock_RecalcWeekday(); // 读回的日期可能与软时钟不同,重新算一次星期
|
||||||
|
XTELL_LOG("[HomeApp] RTC Hardware Synced OK!\r\n");
|
||||||
|
} else {
|
||||||
|
XTELL_LOG("[HomeApp] RTC Hardware Offline, using system clock.\r\n");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
XTELL_LOG("[HomeApp] USE_PCF8563=0, running on software clock only.\r\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 绘制并渲染待机时钟主页面画面
|
||||||
|
UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month);
|
||||||
|
|
||||||
|
// 关断/熄灭 FCOB 双灯条
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
XTELL_LOG("[HomeApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HomeApp 轮询主回调
|
||||||
|
*/
|
||||||
|
static void HomeApp_onRun(void)
|
||||||
|
{
|
||||||
|
// 软时钟发生分钟改变,需要通知屏幕重画时钟主页
|
||||||
|
if (clock_updated) {
|
||||||
|
clock_updated = 0;
|
||||||
|
UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HomeApp 退出清理回调
|
||||||
|
*/
|
||||||
|
static void HomeApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
|
||||||
|
XTELL_LOG("[HomeApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief HomeApp 接收事件处理回调 (按键导航与布撤防交互)
|
||||||
|
*/
|
||||||
|
static EventResult HomeApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
// 0. 整秒滴答:累计闲置秒数,达到门限就进入低功耗休眠
|
||||||
|
if (evt->key_event == KEY_EVENT_SECOND_TICK) {
|
||||||
|
home_idle_sec++;
|
||||||
|
if (home_idle_sec >= HOME_IDLE_SLEEP_SEC) {
|
||||||
|
home_idle_sec = 0;
|
||||||
|
current_state = STATE_SLEEP; // 系统置为休眠状态
|
||||||
|
Enter_Low_Power_Sleep(); // 阻塞函数,MCU 在此挂起,唤醒后自动继续往下执行
|
||||||
|
AppManager_SwitchToForeground(&home_app); // 强制重新走一遍 OnStart,刷新画面和背光
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收到任意其它按键事件,说明用户仍在活动,清零闲置计数
|
||||||
|
home_idle_sec = 0;
|
||||||
|
|
||||||
|
// 1. 长按 ■ 确认键,打开设置主菜单 MenuApp
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 长按 ▲ 键,一键切换系统的 布防 (ARMED) 和 撤防 (DISARMED) 状态
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_LONG) {
|
||||||
|
if (current_state == STATE_ARMED) {
|
||||||
|
current_state = STATE_DISARMED;
|
||||||
|
} else {
|
||||||
|
current_state = STATE_ARMED;
|
||||||
|
}
|
||||||
|
// 布撤防状态切换成功后进行 100ms 短震确认,并重新刷写时钟待机页
|
||||||
|
MOTOR = 1; Delay_ms(100); MOTOR = 0;
|
||||||
|
UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EVENT_IGNORED; // 其他未被关心的事件交由后台应用或丢弃
|
||||||
|
}
|
||||||
85
App/app_ipc_bind.c
Normal file
85
App/app_ipc_bind.c
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
|
||||||
|
// 外部引用的全局系统状态变量
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
|
||||||
|
// 本应用私有变量
|
||||||
|
static u16 last_bind_tx_ms = 0; // 记录上一次绑定发射的时间戳
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void IpcBindApp_OnStart(void);
|
||||||
|
static void IpcBindApp_onRun(void);
|
||||||
|
static void IpcBindApp_onClose(void);
|
||||||
|
static EventResult IpcBindApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义摄像头一键绑定应用控制块 (从 menu_app 的"LIAISON"选项进入)
|
||||||
|
WristbandApp ipc_bind_app = {
|
||||||
|
APP_ID_IPC_BIND,
|
||||||
|
"IpcBind",
|
||||||
|
IpcBindApp_OnStart,
|
||||||
|
IpcBindApp_onRun,
|
||||||
|
IpcBindApp_onClose,
|
||||||
|
IpcBindApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief IpcBindApp 启动回调
|
||||||
|
*/
|
||||||
|
static void IpcBindApp_OnStart(void)
|
||||||
|
{
|
||||||
|
last_bind_tx_ms = ms_tick; // 记录初始发射时间
|
||||||
|
current_state = STATE_IPC_BIND; // 系统设为 IPC 绑定状态
|
||||||
|
UI_ShowBindingPage(CLONED_ADDR); // 绘制相机绑定信息页
|
||||||
|
XTELL_LOG("[IpcBindApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief IpcBindApp 轮询主回调
|
||||||
|
*/
|
||||||
|
static void IpcBindApp_onRun(void)
|
||||||
|
{
|
||||||
|
// ====== 控制青色双灯珠同步呼吸效果 (呼吸周期 1000ms) ======
|
||||||
|
u16 breath = ms_tick % 1000;
|
||||||
|
u8 val = (breath < 500) ? (breath * 2 / 5) : ((1000 - breath) * 2 / 5); // 亮度映射 0~200
|
||||||
|
RGB_Send(0, val, val, 0, val, val); // 渲染青色 (G+B)
|
||||||
|
|
||||||
|
// ====== 控制无线发射,每 500ms 广播一次配对数据 ======
|
||||||
|
if (ms_tick - last_bind_tx_ms >= 500 || ms_tick < last_bind_tx_ms)
|
||||||
|
{
|
||||||
|
last_bind_tx_ms = ms_tick; // 备份当前时间戳
|
||||||
|
RF_SetMode(2); // 暂时切换射频芯片为发射调制模式
|
||||||
|
EV1527_Transmit(CLONED_ADDR, 0x01); // 调制发射手环克隆 ID + 绑定数据码 0x01
|
||||||
|
RF_SetMode(0); // 切回关闭状态,避免产生噪声
|
||||||
|
XTELL_LOG("[IpcBindApp] Binding frame emitted\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief IpcBindApp 退出清理回调
|
||||||
|
*/
|
||||||
|
static void IpcBindApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭 RGB
|
||||||
|
XTELL_LOG("[IpcBindApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief IpcBindApp 接收按键事件处理回调
|
||||||
|
*/
|
||||||
|
static EventResult IpcBindApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
// 长按 ■ 确认键,取消绑定,退出发射并返回主菜单 (父 App)
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
||||||
|
current_state = STATE_PAIR_MENU;
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
@@ -1,783 +1,190 @@
|
|||||||
#include "app_manager.h"
|
#include "app_manager.h"
|
||||||
#include "rgb.h"
|
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
#include "database.h"
|
#include "xtell.h"
|
||||||
#include "ui.h"
|
|
||||||
#include "system.h"
|
|
||||||
#include "../Drivers/rf.h"
|
|
||||||
#include "../Drivers/lcd.h"
|
|
||||||
|
|
||||||
#define APP_ONRUN_MAX_TICKS 5
|
// 前台与后台应用的运行指针槽位
|
||||||
|
static WristbandApp *fg_app = NULL;
|
||||||
|
static WristbandApp *bg_apps[3] = {NULL, NULL, NULL};
|
||||||
|
|
||||||
// 定义局部静态注册表
|
/**
|
||||||
static WristbandApp app_registry[APP_ID_MAX];
|
* @brief 判断某个应用是否正在后台挂载运行中
|
||||||
static WristbandApp *active_app = NULL;
|
*/
|
||||||
#define DEFAULT_APP_ID APP_ID_CLOCK
|
static bit IsAppInBackground(WristbandApp *app)
|
||||||
|
{
|
||||||
// 定义菜单项定义
|
u8 i;
|
||||||
char *code menu_items[5] = {
|
if (app == NULL) return 0;
|
||||||
"DETEC. PORTE",
|
for (i = 0; i < 3; i++) {
|
||||||
"DETEC. PIR",
|
if (bg_apps[i] == app) {
|
||||||
"DETEC. FUMEE",
|
return 1;
|
||||||
"DETEC. GAZ",
|
}
|
||||||
"DETEC. EAU"
|
}
|
||||||
};
|
return 0;
|
||||||
|
}
|
||||||
/* 回调函数前置声明 */
|
|
||||||
void ClockApp_OnStart(void);
|
|
||||||
void ClockApp_onRun(void);
|
|
||||||
void ClockApp_onClose(void);
|
|
||||||
EventResult ClockApp_onEvent(SystemEvent *evt);
|
|
||||||
|
|
||||||
void MenuApp_OnStart(void);
|
|
||||||
void MenuApp_onRun(void);
|
|
||||||
void MenuApp_onClose(void);
|
|
||||||
EventResult MenuApp_onEvent(SystemEvent *evt);
|
|
||||||
|
|
||||||
void PairApp_OnStart(void);
|
|
||||||
void PairApp_onRun(void);
|
|
||||||
void PairApp_onClose(void);
|
|
||||||
EventResult PairApp_onEvent(SystemEvent *evt);
|
|
||||||
|
|
||||||
void AlarmApp_OnStart(void);
|
|
||||||
void AlarmApp_onRun(void);
|
|
||||||
void AlarmApp_onClose(void);
|
|
||||||
EventResult AlarmApp_onEvent(SystemEvent *evt);
|
|
||||||
|
|
||||||
void SOSApp_OnStart(void);
|
|
||||||
void SOSApp_onRun(void);
|
|
||||||
void SOSApp_onClose(void);
|
|
||||||
EventResult SOSApp_onEvent(SystemEvent *evt);
|
|
||||||
|
|
||||||
/* ====== AppManager 核心接口 ====== */
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化应用管理器
|
||||||
|
*/
|
||||||
void AppManager_Init(void)
|
void AppManager_Init(void)
|
||||||
{
|
{
|
||||||
u8 i;
|
u8 i;
|
||||||
/* 清零注册表 */
|
fg_app = NULL;
|
||||||
for (i = 0; i < APP_ID_MAX; i++) {
|
for (i = 0; i < 3; i++) {
|
||||||
app_registry[i].app_id = APP_ID_MAX;
|
bg_apps[i] = NULL;
|
||||||
app_registry[i].app_type = APP_TYPE_FOREGROUND;
|
|
||||||
app_registry[i].app_name = "";
|
|
||||||
app_registry[i].OnStart = NULL;
|
|
||||||
app_registry[i].onRun = NULL;
|
|
||||||
app_registry[i].onClose = NULL;
|
|
||||||
app_registry[i].onEvent = NULL;
|
|
||||||
app_registry[i].is_active = 0;
|
|
||||||
app_registry[i].is_running = 0;
|
|
||||||
}
|
}
|
||||||
|
XTELL_LOG("[AM] AppManager Initialized.\r\n");
|
||||||
/* 注册 ClockApp */
|
|
||||||
app_registry[APP_ID_CLOCK].app_id = APP_ID_CLOCK;
|
|
||||||
app_registry[APP_ID_CLOCK].app_type = APP_TYPE_FOREGROUND;
|
|
||||||
app_registry[APP_ID_CLOCK].app_name = "Clock";
|
|
||||||
app_registry[APP_ID_CLOCK].OnStart = ClockApp_OnStart;
|
|
||||||
app_registry[APP_ID_CLOCK].onRun = ClockApp_onRun;
|
|
||||||
app_registry[APP_ID_CLOCK].onClose = ClockApp_onClose;
|
|
||||||
app_registry[APP_ID_CLOCK].onEvent = ClockApp_onEvent;
|
|
||||||
|
|
||||||
/* 注册 MenuApp */
|
|
||||||
app_registry[APP_ID_MENU].app_id = APP_ID_MENU;
|
|
||||||
app_registry[APP_ID_MENU].app_type = APP_TYPE_FOREGROUND;
|
|
||||||
app_registry[APP_ID_MENU].app_name = "Menu";
|
|
||||||
app_registry[APP_ID_MENU].OnStart = MenuApp_OnStart;
|
|
||||||
app_registry[APP_ID_MENU].onRun = MenuApp_onRun;
|
|
||||||
app_registry[APP_ID_MENU].onClose = MenuApp_onClose;
|
|
||||||
app_registry[APP_ID_MENU].onEvent = MenuApp_onEvent;
|
|
||||||
|
|
||||||
/* 注册 PairApp */
|
|
||||||
app_registry[APP_ID_PAIR].app_id = APP_ID_PAIR;
|
|
||||||
app_registry[APP_ID_PAIR].app_type = APP_TYPE_FOREGROUND;
|
|
||||||
app_registry[APP_ID_PAIR].app_name = "Pair";
|
|
||||||
app_registry[APP_ID_PAIR].OnStart = PairApp_OnStart;
|
|
||||||
app_registry[APP_ID_PAIR].onRun = PairApp_onRun;
|
|
||||||
app_registry[APP_ID_PAIR].onClose = PairApp_onClose;
|
|
||||||
app_registry[APP_ID_PAIR].onEvent = PairApp_onEvent;
|
|
||||||
|
|
||||||
/* 注册 AlarmApp */
|
|
||||||
app_registry[APP_ID_ALARM].app_id = APP_ID_ALARM;
|
|
||||||
app_registry[APP_ID_ALARM].app_type = APP_TYPE_FOREGROUND;
|
|
||||||
app_registry[APP_ID_ALARM].app_name = "Alarm";
|
|
||||||
app_registry[APP_ID_ALARM].OnStart = AlarmApp_OnStart;
|
|
||||||
app_registry[APP_ID_ALARM].onRun = AlarmApp_onRun;
|
|
||||||
app_registry[APP_ID_ALARM].onClose = AlarmApp_onClose;
|
|
||||||
app_registry[APP_ID_ALARM].onEvent = AlarmApp_onEvent;
|
|
||||||
|
|
||||||
/* 注册 SOSApp */
|
|
||||||
app_registry[APP_ID_SOS].app_id = APP_ID_SOS;
|
|
||||||
app_registry[APP_ID_SOS].app_type = APP_TYPE_FOREGROUND;
|
|
||||||
app_registry[APP_ID_SOS].app_name = "SOS";
|
|
||||||
app_registry[APP_ID_SOS].OnStart = SOSApp_OnStart;
|
|
||||||
app_registry[APP_ID_SOS].onRun = SOSApp_onRun;
|
|
||||||
app_registry[APP_ID_SOS].onClose = SOSApp_onClose;
|
|
||||||
app_registry[APP_ID_SOS].onEvent = SOSApp_onEvent;
|
|
||||||
|
|
||||||
AppManager_StartApp(DEFAULT_APP_ID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppManager_StartApp(AppID app_id)
|
/**
|
||||||
|
* @brief 动态将一个应用切换至前台活跃运行
|
||||||
|
*/
|
||||||
|
bit AppManager_SwitchToForeground(WristbandApp *app)
|
||||||
{
|
{
|
||||||
WristbandApp *target;
|
if (app == NULL) return 0;
|
||||||
if (app_id >= APP_ID_MAX) return;
|
|
||||||
target = &app_registry[app_id];
|
|
||||||
if (target->OnStart == NULL) return;
|
|
||||||
if (active_app == target) return;
|
|
||||||
|
|
||||||
Uart_SendString("[APP] StartApp: ");
|
XTELL_LOG("[AM] Switch foreground to: ");
|
||||||
Uart_SendString(target->app_name);
|
XTELL_LOG(app->app_name);
|
||||||
Uart_SendString("\r\n");
|
XTELL_LOG("\r\n");
|
||||||
|
|
||||||
/* 关闭当前活跃应用 */
|
// 1. 若当前有正在运行的前台应用,将其移出前台
|
||||||
if (active_app != NULL && active_app->onClose != NULL) {
|
if (fg_app != NULL) {
|
||||||
active_app->is_active = 0;
|
WristbandApp *prev_app = fg_app;
|
||||||
active_app->onClose();
|
fg_app = NULL; // 物理断开,防止在 onClose 期间被重入轮询
|
||||||
|
|
||||||
|
// 若该应用没有在后台运行,需要彻底关闭它以清理外设
|
||||||
|
if (!IsAppInBackground(prev_app)) {
|
||||||
|
if (prev_app->onClose != NULL) {
|
||||||
|
prev_app->onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 切换并启动新应用 */
|
// 2. 挂载新的前台应用
|
||||||
active_app = target;
|
fg_app = app;
|
||||||
if (active_app->OnStart != NULL) {
|
|
||||||
active_app->is_active = 1;
|
// 3. 启动新应用生命周期
|
||||||
active_app->is_running = 1;
|
// 若该应用之前在后台运行,不用重新执行 OnStart,直接接管前台显示即可;
|
||||||
active_app->OnStart();
|
// 否则说明是初次运行,执行 OnStart 进行界面绘制与状态初始化。
|
||||||
|
if (!IsAppInBackground(app)) {
|
||||||
|
if (app->OnStart != NULL) {
|
||||||
|
app->OnStart();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppManager_DispatchEvent(SystemEvent evt)
|
/**
|
||||||
|
* @brief 动态将一个应用挂载到后台槽位
|
||||||
|
*/
|
||||||
|
bit AppManager_AttachToBackground(WristbandApp *app)
|
||||||
{
|
{
|
||||||
EventResult result = EVENT_IGNORED;
|
u8 i;
|
||||||
if (active_app != NULL && active_app->is_active && active_app->onEvent != NULL) {
|
if (app == NULL) return 0;
|
||||||
result = active_app->onEvent(&evt);
|
|
||||||
if (result == EVENT_HANDLED) return;
|
// 若已经存在于后台,直接返回成功
|
||||||
|
if (IsAppInBackground(app)) {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 寻找空闲的后台槽位进行挂载
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
if (bg_apps[i] == NULL) {
|
||||||
|
bg_apps[i] = app;
|
||||||
|
XTELL_LOG("[AM] Attach to background slot [");
|
||||||
|
XTELL_LOG_HEX8(i);
|
||||||
|
XTELL_LOG("]: ");
|
||||||
|
XTELL_LOG(app->app_name);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
|
||||||
|
// 若该应用既不处于前台,也没在后台初始化,执行其 OnStart()
|
||||||
|
if (fg_app != app) {
|
||||||
|
if (app->OnStart != NULL) {
|
||||||
|
app->OnStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XTELL_LOG("[AM] ERROR: Background slots full!\r\n");
|
||||||
|
return 0; // 槽位满,挂载失败
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppManager_RunActiveApp(void)
|
/**
|
||||||
|
* @brief 动态将一个应用从后台卸载
|
||||||
|
*/
|
||||||
|
void AppManager_DetachFromBackground(WristbandApp *app)
|
||||||
{
|
{
|
||||||
u16 start_tick;
|
u8 i;
|
||||||
u16 elapsed;
|
if (app == NULL) return;
|
||||||
if (active_app == NULL || !active_app->is_active) return;
|
|
||||||
if (!active_app->is_running) return;
|
|
||||||
if (active_app->onRun == NULL) return;
|
|
||||||
start_tick = ms_tick;
|
|
||||||
active_app->onRun();
|
|
||||||
elapsed = ms_tick - start_tick;
|
|
||||||
if (elapsed > APP_ONRUN_MAX_TICKS) {
|
|
||||||
active_app->is_running = 0;
|
|
||||||
} else {
|
|
||||||
active_app->is_running = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppID AppManager_GetActiveAppID(void)
|
for (i = 0; i < 3; i++) {
|
||||||
{
|
if (bg_apps[i] == app) {
|
||||||
if (active_app == NULL) return APP_ID_MAX;
|
bg_apps[i] = NULL;
|
||||||
return active_app->app_id;
|
XTELL_LOG("[AM] Detach from background: ");
|
||||||
}
|
XTELL_LOG(app->app_name);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
|
||||||
|
// 如果该应用未在前台运行,需要彻底关闭它
|
||||||
/* =========================================================================
|
if (fg_app != app) {
|
||||||
* ClockApp 回调实现与时间编辑状态机
|
if (app->onClose != NULL) {
|
||||||
* ========================================================================= */
|
app->onClose();
|
||||||
|
|
||||||
static u8 time_edit_active = 0;
|
|
||||||
static u8 time_edit_field = 1;
|
|
||||||
static u8 time_edit_blink_on = 1;
|
|
||||||
|
|
||||||
void Start_Time_Edit(void)
|
|
||||||
{
|
|
||||||
time_edit_active = 1;
|
|
||||||
time_edit_field = 1;
|
|
||||||
time_edit_blink_on = 1;
|
|
||||||
current_state = STATE_SET_TIME;
|
|
||||||
|
|
||||||
// FCOB 幻彩灯条蓝色常亮 (R=0, G=0, B=180)
|
|
||||||
RGB_Send(0, 0, 180, 0, 0, 180);
|
|
||||||
|
|
||||||
// 马达短振 50ms
|
|
||||||
MOTOR = 1;
|
|
||||||
Delay_ms(50);
|
|
||||||
MOTOR = 0;
|
|
||||||
|
|
||||||
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClockApp_OnStart(void)
|
|
||||||
{
|
|
||||||
time_edit_active = 0;
|
|
||||||
current_state = STATE_NORMAL;
|
|
||||||
RF_SetMode(1);
|
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
|
||||||
|
|
||||||
// 启动时熄灭灯条
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
Uart_SendString("[ClockApp] Started\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClockApp_onRun(void)
|
|
||||||
{
|
|
||||||
u32 rx_addr;
|
|
||||||
u8 rx_data;
|
|
||||||
|
|
||||||
if (time_edit_active)
|
|
||||||
{
|
|
||||||
// 500ms 周期控制闪烁
|
|
||||||
u8 blink = (ms_tick / 500) % 2;
|
|
||||||
if (blink != time_edit_blink_on)
|
|
||||||
{
|
|
||||||
time_edit_blink_on = blink;
|
|
||||||
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, time_edit_blink_on);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (clock_updated) {
|
|
||||||
clock_updated = 0;
|
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
|
||||||
}
|
|
||||||
if (EV1527_Decode(&rx_addr, &rx_data)) {
|
|
||||||
u8 slot;
|
|
||||||
if (Check_Sensor_ID(rx_addr, &slot)) {
|
|
||||||
u8 sensor_type = sensor_list[slot].type;
|
|
||||||
if (sensor_type < 5) {
|
|
||||||
if (sensor_list[slot].zone == 0 || current_state == STATE_ARMED) {
|
|
||||||
SystemEvent rf_evt;
|
|
||||||
rf_evt.key_event = KEY_EVENT_NONE;
|
|
||||||
rf_evt.priority = EVENT_PRIORITY_HIGH;
|
|
||||||
rf_evt.extra_data = rx_addr;
|
|
||||||
rf_evt.extra_size = 4;
|
|
||||||
EventQueue_InsertFront(rf_evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClockApp_onClose(void)
|
|
||||||
{
|
|
||||||
time_edit_active = 0;
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
Uart_SendString("[ClockApp] Closed\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
EventResult ClockApp_onEvent(SystemEvent *evt)
|
|
||||||
{
|
|
||||||
if (time_edit_active)
|
|
||||||
{
|
|
||||||
if (evt->key_event == KEY_EVENT_UP_CLICK)
|
|
||||||
{
|
|
||||||
if (time_edit_field == 1) {
|
|
||||||
current_hour = (current_hour + 1) % 24;
|
|
||||||
} else {
|
|
||||||
current_min = (current_min + 1) % 60;
|
|
||||||
}
|
|
||||||
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_DOWN_CLICK)
|
|
||||||
{
|
|
||||||
if (time_edit_field == 1) {
|
|
||||||
current_hour = (current_hour == 0) ? 23 : (current_hour - 1);
|
|
||||||
} else {
|
|
||||||
current_min = (current_min == 0) ? 59 : (current_min - 1);
|
|
||||||
}
|
|
||||||
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_LONG)
|
|
||||||
{
|
|
||||||
// 长按 Confirm 键跳转字段
|
|
||||||
if (time_edit_field == 1) {
|
|
||||||
time_edit_field = 2;
|
|
||||||
} else {
|
|
||||||
time_edit_field = 1;
|
|
||||||
}
|
|
||||||
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK)
|
|
||||||
{
|
|
||||||
// 短按 Confirm 键保存退出
|
|
||||||
time_edit_active = 0;
|
|
||||||
current_state = STATE_NORMAL;
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭
|
|
||||||
|
|
||||||
// 退出短振
|
|
||||||
MOTOR = 1; Delay_ms(50); MOTOR = 0;
|
|
||||||
|
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED; // 吞掉其他按键
|
|
||||||
}
|
|
||||||
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
|
||||||
AppManager_StartApp(APP_ID_MENU);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_UP_LONG) {
|
|
||||||
if (current_state == STATE_ARMED) {
|
|
||||||
current_state = STATE_DISARMED;
|
|
||||||
} else {
|
|
||||||
current_state = STATE_ARMED;
|
|
||||||
}
|
|
||||||
// 切换状态短震确认
|
|
||||||
MOTOR = 1; Delay_ms(100); MOTOR = 0;
|
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_SOS_CLICK || evt->key_event == KEY_EVENT_SOS_LONG) {
|
|
||||||
AppManager_StartApp(APP_ID_SOS);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
return EVENT_IGNORED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================================================================
|
|
||||||
* MenuApp 回调实现
|
|
||||||
* ========================================================================= */
|
|
||||||
|
|
||||||
static u8 menu_level = 0; // 0:主菜单, 1:传感器对码二级菜单, 2:IPC绑定中
|
|
||||||
static u16 last_bind_tx_ms = 0;
|
|
||||||
|
|
||||||
void MenuApp_OnStart(void)
|
|
||||||
{
|
|
||||||
menu_level = 0;
|
|
||||||
menu_select = 0;
|
|
||||||
current_state = STATE_PAIR_MENU;
|
|
||||||
RF_SetMode(0);
|
|
||||||
UI_ShowMainMenu(menu_select);
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭灯条
|
|
||||||
Uart_SendString("[MenuApp] Started (Main Menu)\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void MenuApp_onRun(void)
|
|
||||||
{
|
|
||||||
if (menu_level == 2)
|
|
||||||
{
|
|
||||||
// 绑定中:控制青色呼吸灯 (周期1000ms,双灯同步)
|
|
||||||
u16 breath = ms_tick % 1000;
|
|
||||||
u8 val = (breath < 500) ? (breath * 2 / 5) : ((1000 - breath) * 2 / 5); // 范围 0~200
|
|
||||||
RGB_Send(0, val, val, 0, val, val);
|
|
||||||
|
|
||||||
// 每 500ms 发射一次绑定信号 (出厂 Factory ID)
|
|
||||||
if (ms_tick - last_bind_tx_ms >= 500 || ms_tick < last_bind_tx_ms)
|
|
||||||
{
|
|
||||||
last_bind_tx_ms = ms_tick;
|
|
||||||
RF_SetMode(2);
|
|
||||||
EV1527_Transmit(CLONED_ADDR, 0x01);
|
|
||||||
RF_SetMode(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MenuApp_onClose(void)
|
|
||||||
{
|
|
||||||
menu_level = 0;
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
RF_SetMode(1);
|
|
||||||
Uart_SendString("[MenuApp] Closed\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
EventResult MenuApp_onEvent(SystemEvent *evt)
|
|
||||||
{
|
|
||||||
if (menu_level == 0)
|
|
||||||
{
|
|
||||||
// 主菜单层 (0-HORLOGE, 1-APPAIRAGE, 2-LIAISON)
|
|
||||||
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
|
||||||
if (menu_select > 0) {
|
|
||||||
menu_select--;
|
|
||||||
UI_ShowMainMenu(menu_select);
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
|
||||||
if (menu_select < 2) {
|
|
||||||
menu_select++;
|
|
||||||
UI_ShowMainMenu(menu_select);
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
|
||||||
if (menu_select == 0) {
|
|
||||||
// 启动时钟 App 并立即进入编辑模式
|
|
||||||
AppManager_StartApp(APP_ID_CLOCK);
|
|
||||||
Start_Time_Edit();
|
|
||||||
} else if (menu_select == 1) {
|
|
||||||
// 进入二级传感器对码菜单
|
|
||||||
menu_level = 1;
|
|
||||||
menu_select = 0;
|
|
||||||
UI_ShowPairMenuPage(menu_select);
|
|
||||||
} else if (menu_select == 2) {
|
|
||||||
// 进入摄像机绑定
|
|
||||||
menu_level = 2;
|
|
||||||
last_bind_tx_ms = ms_tick;
|
|
||||||
current_state = STATE_IPC_BIND;
|
|
||||||
UI_ShowBindingPage(CLONED_ADDR);
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
|
||||||
AppManager_StartApp(APP_ID_CLOCK);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (menu_level == 1)
|
|
||||||
{
|
|
||||||
// 传感器对码选择菜单 (0-门磁, 1-PIR, 2-烟感, 3-气感, 4-水浸)
|
|
||||||
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
|
||||||
if (menu_select > 0) {
|
|
||||||
menu_select--;
|
|
||||||
UI_ShowPairMenuPage(menu_select);
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
|
||||||
if (menu_select < 4) {
|
|
||||||
menu_select++;
|
|
||||||
UI_ShowPairMenuPage(menu_select);
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
|
||||||
// 保存当前选定的类型到 captured_type,短震 100ms 并跳转到 PairApp 对码搜索
|
|
||||||
captured_type = menu_select;
|
|
||||||
MOTOR = 1;
|
|
||||||
Delay_ms(100);
|
|
||||||
MOTOR = 0;
|
|
||||||
AppManager_StartApp(APP_ID_PAIR);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
|
|
||||||
// 返回主菜单
|
|
||||||
menu_level = 0;
|
|
||||||
menu_select = 1;
|
|
||||||
UI_ShowMainMenu(menu_select);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (menu_level == 2)
|
|
||||||
{
|
|
||||||
// 绑定中:长按 Confirm 键 (3秒) 或 长按 DOWN 键 (2秒) 退出返回主菜单
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_LONG || evt->key_event == KEY_EVENT_DOWN_LONG) {
|
|
||||||
menu_level = 0;
|
|
||||||
menu_select = 2;
|
|
||||||
current_state = STATE_PAIR_MENU;
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭
|
|
||||||
UI_ShowMainMenu(menu_select);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return EVENT_IGNORED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================================================================
|
|
||||||
* PairApp 回调实现
|
|
||||||
* ========================================================================= */
|
|
||||||
|
|
||||||
enum {
|
|
||||||
PAIR_STATE_WAIT,
|
|
||||||
PAIR_STATE_CONFIRM,
|
|
||||||
PAIR_STATE_SUCCESS,
|
|
||||||
PAIR_STATE_FAIL
|
|
||||||
};
|
|
||||||
|
|
||||||
static u8 pair_state;
|
|
||||||
static u8 last_radar_sec;
|
|
||||||
static u8 captured_zone = 1;
|
|
||||||
static u32 success_start_ms = 0;
|
|
||||||
static u32 fail_start_ms = 0;
|
|
||||||
|
|
||||||
void PairApp_OnStart(void)
|
|
||||||
{
|
|
||||||
pair_state = PAIR_STATE_WAIT;
|
|
||||||
last_radar_sec = 99;
|
|
||||||
current_state = STATE_PAIR_WAIT;
|
|
||||||
pair_timeout_ms = 0;
|
|
||||||
captured_zone = 1;
|
|
||||||
RF_SetMode(1);
|
|
||||||
|
|
||||||
// 初始化对码搜寻状态显示
|
|
||||||
UI_ShowPairWaitPage(0);
|
|
||||||
|
|
||||||
// 快闪白色 RGB (双灯)
|
|
||||||
RGB_Send(150, 150, 150, 150, 150, 150);
|
|
||||||
|
|
||||||
Uart_SendString("[PairApp] Started (WAIT)\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void PairApp_onRun(void)
|
|
||||||
{
|
|
||||||
u32 rx_addr;
|
|
||||||
u8 rx_data;
|
|
||||||
u8 flash;
|
|
||||||
u8 frame;
|
|
||||||
|
|
||||||
if (pair_state == PAIR_STATE_WAIT) {
|
|
||||||
// 白色 3Hz 快闪 (周期 333ms, 亮 166ms)
|
|
||||||
flash = (ms_tick / 166) % 2;
|
|
||||||
if (flash) RGB_Send(150, 150, 150, 150, 150, 150);
|
|
||||||
else RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
frame = (u8)((ms_tick / 250) % 3);
|
|
||||||
if (frame != last_radar_sec) {
|
|
||||||
last_radar_sec = frame;
|
|
||||||
UI_ShowPairWaitPage(frame);
|
|
||||||
}
|
|
||||||
if (pair_timeout_ms >= 30000UL) {
|
|
||||||
pair_timeout_ms = 0;
|
|
||||||
pair_state = PAIR_STATE_FAIL;
|
|
||||||
current_state = STATE_PAIR_FAIL;
|
|
||||||
fail_start_ms = ms_tick;
|
|
||||||
UI_ShowPairFailPage(1);
|
|
||||||
|
|
||||||
// 红色常亮
|
|
||||||
RGB_Send(200, 0, 0, 200, 0, 0);
|
|
||||||
|
|
||||||
Uart_SendString("[PairApp] Timeout\r\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (EV1527_Decode(&rx_addr, &rx_data)) {
|
|
||||||
// 排除 SOS 数据码 (0x08 代表 SOS 求救,不用于常规传感器配对)
|
|
||||||
if (rx_data != 0x08)
|
|
||||||
{
|
|
||||||
captured_addr = rx_addr;
|
|
||||||
pair_state = PAIR_STATE_CONFIRM;
|
|
||||||
current_state = STATE_PAIR_CONFIRM;
|
|
||||||
RF_SetMode(0);
|
|
||||||
captured_zone = 1; // 初始为防区 1
|
|
||||||
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
|
||||||
Uart_SendString("[PairApp] Sensor captured\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (pair_state == PAIR_STATE_CONFIRM) {
|
|
||||||
// 橙色慢闪 (1.5Hz, 周期 666ms)
|
|
||||||
u8 flash = (ms_tick / 333) % 2;
|
|
||||||
if (flash) RGB_Send(100, 180, 0, 100, 180, 0); // 橙色
|
|
||||||
else RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
} else if (pair_state == PAIR_STATE_SUCCESS) {
|
|
||||||
// 2 秒后自动返回二级菜单
|
|
||||||
if (ms_tick - success_start_ms >= 2000 || ms_tick < success_start_ms)
|
|
||||||
{
|
|
||||||
AppManager_StartApp(APP_ID_MENU);
|
|
||||||
}
|
|
||||||
} else if (pair_state == PAIR_STATE_FAIL) {
|
|
||||||
// 5 秒后自动返回二级菜单
|
|
||||||
if (ms_tick - fail_start_ms >= 5000 || ms_tick < fail_start_ms)
|
|
||||||
{
|
|
||||||
AppManager_StartApp(APP_ID_MENU);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PairApp_onClose(void)
|
/**
|
||||||
|
* @brief 向前台活跃应用派发事件
|
||||||
|
*/
|
||||||
|
void AppManager_DispatchEvent(SystemEvent evt)
|
||||||
{
|
{
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯光
|
if (fg_app != NULL && fg_app->onEvent != NULL) {
|
||||||
MOTOR = 0;
|
fg_app->onEvent(&evt);
|
||||||
RF_SetMode(1);
|
}
|
||||||
Uart_SendString("[PairApp] Closed\r\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EventResult PairApp_onEvent(SystemEvent *evt)
|
/**
|
||||||
|
* @brief 主循环中轮询前后台所有应用
|
||||||
|
*/
|
||||||
|
void AppManager_RunActiveApp(void)
|
||||||
{
|
{
|
||||||
if (pair_state == PAIR_STATE_CONFIRM) {
|
u8 i;
|
||||||
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
|
||||||
if (captured_zone < 99) {
|
|
||||||
captured_zone++;
|
|
||||||
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
|
||||||
if (captured_zone > 1) {
|
|
||||||
captured_zone--;
|
|
||||||
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
|
||||||
}
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
|
||||||
// 保存防区配对并显示成功反馈
|
|
||||||
Add_Sensor_With_Zone(captured_addr, captured_type, captured_zone);
|
|
||||||
pair_state = PAIR_STATE_SUCCESS;
|
|
||||||
current_state = STATE_PAIR_SUCCESS;
|
|
||||||
success_start_ms = ms_tick;
|
|
||||||
UI_ShowPairSuccessPage();
|
|
||||||
|
|
||||||
// 绿色常亮且马达长振 400ms
|
// 1. 轮询前台活跃应用
|
||||||
RGB_Send(0, 200, 0, 0, 200, 0);
|
if (fg_app != NULL && fg_app->onRun != NULL) {
|
||||||
MOTOR = 1;
|
fg_app->onRun();
|
||||||
Delay_ms(400);
|
|
||||||
MOTOR = 0;
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
|
||||||
// 长按 Confirm 取消返回主菜单
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
AppManager_StartApp(APP_ID_MENU);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜寻状态或确认状态下长按 DOWN 退出
|
// 2. 轮询后台挂载的应用
|
||||||
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
|
for (i = 0; i < 3; i++) {
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
if (bg_apps[i] != NULL && bg_apps[i]->onRun != NULL) {
|
||||||
AppManager_StartApp(APP_ID_MENU);
|
bg_apps[i]->onRun();
|
||||||
return EVENT_HANDLED;
|
}
|
||||||
}
|
}
|
||||||
return EVENT_IGNORED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
/* =========================================================================
|
* @brief 获取当前前台活跃应用的唯一 ID
|
||||||
* AlarmApp 回调实现
|
*/
|
||||||
* ========================================================================= */
|
u8 AppManager_GetActiveAppID(void)
|
||||||
|
|
||||||
void AlarmApp_OnStart(void)
|
|
||||||
{
|
{
|
||||||
u8 slot;
|
if (fg_app != NULL) {
|
||||||
u8 type = 0;
|
return fg_app->app_id;
|
||||||
u8 zone = 1;
|
|
||||||
|
|
||||||
current_state = STATE_ALARM;
|
|
||||||
alarm_timer_ms = 0;
|
|
||||||
alarm_flash_flag = 0;
|
|
||||||
|
|
||||||
if (Check_Sensor_ID(captured_addr, &slot)) {
|
|
||||||
type = sensor_list[slot].type;
|
|
||||||
zone = sensor_list[slot].zone;
|
|
||||||
alarm_sensor_slot = slot;
|
|
||||||
}
|
}
|
||||||
|
return 0xFF;
|
||||||
// 初始化显示警报界面
|
|
||||||
UI_ShowAlarmPage(type, zone);
|
|
||||||
|
|
||||||
// 首次亮起防区对应颜色
|
|
||||||
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0); // 门磁: 红色 (R=180)
|
|
||||||
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0); // PIR: 黄色
|
|
||||||
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0); // 烟感: 绿色 (G=180)
|
|
||||||
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180); // 气感: 紫色
|
|
||||||
else RGB_Send(0, 0, 180, 0, 0, 180); // 水浸: 蓝色
|
|
||||||
|
|
||||||
Uart_SendString("[AlarmApp] Started\r\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlarmApp_onRun(void)
|
// 供遗留代码兼容性引用的 API:StartApp 映射至 SwitchToForeground 动态方式
|
||||||
|
void AppManager_StartApp(AppID app_id)
|
||||||
{
|
{
|
||||||
u8 type = sensor_list[alarm_sensor_slot].type;
|
if (app_id == APP_ID_HOME) AppManager_SwitchToForeground(&home_app);
|
||||||
u8 flash = (ms_tick / 250) % 2; // 250ms 亮灭周期
|
else if (app_id == APP_ID_MENU) AppManager_SwitchToForeground(&menu_app);
|
||||||
|
else if (app_id == APP_ID_SET_CLOCK) AppManager_SwitchToForeground(&set_clock_app);
|
||||||
// 同步控制 RGB 幻彩灯闪烁
|
else if (app_id == APP_ID_PAIR) AppManager_SwitchToForeground(&pair_app);
|
||||||
if (flash)
|
else if (app_id == APP_ID_IPC_BIND) AppManager_SwitchToForeground(&ipc_bind_app);
|
||||||
{
|
else if (app_id == APP_ID_ALARM) AppManager_SwitchToForeground(&alarm_app);
|
||||||
if (type == 0) RGB_Send(0, 180, 0, 0, 180, 0);
|
else if (app_id == APP_ID_SOS) AppManager_SwitchToForeground(&sos_app);
|
||||||
else if (type == 1) RGB_Send(100, 180, 0, 100, 180, 0);
|
|
||||||
else if (type == 2) RGB_Send(180, 0, 0, 180, 0, 0);
|
|
||||||
else if (type == 3) RGB_Send(0, 180, 180, 0, 180, 180);
|
|
||||||
else RGB_Send(0, 0, 180, 0, 0, 180);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Truly AMOLED 粗红框闪烁
|
|
||||||
if (alarm_flash_flag) {
|
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
|
||||||
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
|
|
||||||
} else {
|
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
|
|
||||||
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_BLACK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlarmApp_onClose(void)
|
|
||||||
{
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
|
|
||||||
MOTOR = 0;
|
|
||||||
current_state = STATE_NORMAL;
|
|
||||||
Uart_SendString("[AlarmApp] Closed\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
EventResult AlarmApp_onEvent(SystemEvent *evt)
|
|
||||||
{
|
|
||||||
// 任意键按下或长按 ➔ 清除报警返回时钟
|
|
||||||
if (evt->key_event != KEY_EVENT_NONE) {
|
|
||||||
AppManager_StartApp(APP_ID_CLOCK);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
return EVENT_IGNORED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================================================================
|
|
||||||
* SOSApp 回调实现
|
|
||||||
* ========================================================================= */
|
|
||||||
|
|
||||||
static u8 sos_last_phase;
|
|
||||||
|
|
||||||
void SOSApp_OnStart(void)
|
|
||||||
{
|
|
||||||
current_state = STATE_SOS_EMITTED;
|
|
||||||
sos_last_phase = 99;
|
|
||||||
alarm_timer_ms = 0;
|
|
||||||
UI_ShowSosPage();
|
|
||||||
|
|
||||||
// 立即启动双灯交替爆闪
|
|
||||||
RGB_Send(0, 200, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
Uart_SendString("[SOSApp] Started\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SOSApp_onRun(void)
|
|
||||||
{
|
|
||||||
// 每 500ms 发射一次求救射频信号 (CLONED_ADDR 伴随 SOS 数据码 0x08)
|
|
||||||
u8 sec_phase = (u8)((ms_tick / 500) % 2);
|
|
||||||
if (sec_phase != sos_last_phase) {
|
|
||||||
sos_last_phase = sec_phase;
|
|
||||||
RF_SetMode(2);
|
|
||||||
EV1527_Transmit(CLONED_ADDR, 0x08);
|
|
||||||
RF_SetMode(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 50ms 周期控制双灯交替红色闪烁
|
|
||||||
{
|
|
||||||
u8 flash = (ms_tick / 50) % 2;
|
|
||||||
if (flash) {
|
|
||||||
RGB_Send(0, 200, 0, 0, 0, 0); // 灯1亮红,灯2灭
|
|
||||||
} else {
|
|
||||||
RGB_Send(0, 0, 0, 0, 200, 0); // 灯1灭,灯2亮红
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Truly AMOLED 边框闪烁 (红粗边框 + 白细边框)
|
|
||||||
if (alarm_flash_flag) {
|
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
|
||||||
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED);
|
|
||||||
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE);
|
|
||||||
} else {
|
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
|
|
||||||
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_BLACK);
|
|
||||||
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_BLACK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SOSApp_onClose(void)
|
|
||||||
{
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
|
|
||||||
MOTOR = 0;
|
|
||||||
current_state = STATE_NORMAL;
|
|
||||||
Uart_SendString("[SOSApp] Closed\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
EventResult SOSApp_onEvent(SystemEvent *evt)
|
|
||||||
{
|
|
||||||
// 短按或长按任意键 ➔ 退出求救状态返回待机时钟
|
|
||||||
if (evt->key_event != KEY_EVENT_NONE) {
|
|
||||||
AppManager_StartApp(APP_ID_CLOCK);
|
|
||||||
return EVENT_HANDLED;
|
|
||||||
}
|
|
||||||
return EVENT_IGNORED;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,77 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
// AppManager 核心接口
|
// 动态挂载的 WristbandApp 控制块定义
|
||||||
|
typedef struct WristbandApp {
|
||||||
|
u8 app_id; // 应用唯一识别 ID
|
||||||
|
char *app_name; // 应用名称
|
||||||
|
void (*OnStart)(void); // 启动生命周期,进入前台时调用
|
||||||
|
void (*onRun)(void); // 轮询生命周期,主循环每次被调度
|
||||||
|
void (*onClose)(void); // 关闭生命周期,退出前台/后台时清理资源
|
||||||
|
EventResult (*onEvent)(SystemEvent *evt); // 事件处理生命周期,接收按键事件
|
||||||
|
} WristbandApp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化应用管理器
|
||||||
|
* @details 将前台活跃槽位与后台的三个槽位全部清空为 NULL。
|
||||||
|
*/
|
||||||
void AppManager_Init(void);
|
void AppManager_Init(void);
|
||||||
void AppManager_StartApp(AppID app_id);
|
|
||||||
|
/**
|
||||||
|
* @brief 动态将一个应用切换至前台活跃运行
|
||||||
|
* @param app 目标应用的结构体指针
|
||||||
|
* @return bit 1-切换成功,0-失败
|
||||||
|
* @details 流程:
|
||||||
|
* 1. 若前台已有应用运行,且该应用未挂载在后台,则对其执行 onClose() 关闭生命周期。
|
||||||
|
* 2. 将 fg_app 指针指向新的 app。
|
||||||
|
* 3. 若该 app 目前并未在后台槽位中运行,则执行其 OnStart() 启动生命周期。
|
||||||
|
*/
|
||||||
|
bit AppManager_SwitchToForeground(WristbandApp *app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 动态将一个应用挂载到后台槽位
|
||||||
|
* @param app 目标应用的结构体指针
|
||||||
|
* @return bit 1-挂载成功,0-槽位已满挂载失败
|
||||||
|
* @details 后台总共支持 3 个槽位。若挂载成功,其 onRun 将会持续在主循环中被轮询。
|
||||||
|
*/
|
||||||
|
bit AppManager_AttachToBackground(WristbandApp *app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 动态将一个应用从后台卸载
|
||||||
|
* @param app 目标应用的结构体指针
|
||||||
|
* @details 从后台槽位数组中移除该指针。若该应用当前也不在前台运行,将对其执行 onClose() 清理外设。
|
||||||
|
*/
|
||||||
|
void AppManager_DetachFromBackground(WristbandApp *app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将捕获的事件派发给前台活跃应用
|
||||||
|
* @param evt 系统派发来的事件
|
||||||
|
*/
|
||||||
void AppManager_DispatchEvent(SystemEvent evt);
|
void AppManager_DispatchEvent(SystemEvent evt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 在系统主循环中持续轮询轮转前后台所有活跃的应用
|
||||||
|
* @details 负责轮询前台 fg_app 对应的 onRun(),并遍历轮询后台 bg_apps 数组中所有有效应用的 onRun()。
|
||||||
|
*/
|
||||||
void AppManager_RunActiveApp(void);
|
void AppManager_RunActiveApp(void);
|
||||||
AppID AppManager_GetActiveAppID(void);
|
|
||||||
|
|
||||||
// 时间编辑状态机接口 (由 MenuApp 触发)
|
/**
|
||||||
void Start_Time_Edit(void);
|
* @brief 获取当前前台活跃应用的 ID
|
||||||
|
* @return u8 当前活跃前台应用的 ID 编号。若无则返回 255 (0xFF)
|
||||||
|
*/
|
||||||
|
u8 AppManager_GetActiveAppID(void);
|
||||||
|
|
||||||
#endif
|
// 兼容性接口声明
|
||||||
|
void AppManager_StartApp(AppID app_id);
|
||||||
|
|
||||||
|
// ====== 系统中各独立 App 实例的外包声明 (各 App 内部进行实体定义) ======
|
||||||
|
extern WristbandApp home_app;
|
||||||
|
extern WristbandApp menu_app;
|
||||||
|
extern WristbandApp set_clock_app;
|
||||||
|
extern WristbandApp pair_app;
|
||||||
|
extern WristbandApp ipc_bind_app;
|
||||||
|
extern WristbandApp alarm_app;
|
||||||
|
extern WristbandApp sos_app;
|
||||||
|
extern WristbandApp rf_monitor_app; // 后台射频监控应用
|
||||||
|
|
||||||
|
#endif // __APP_MANAGER_H__
|
||||||
|
|||||||
118
App/app_menu.c
Normal file
118
App/app_menu.c
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "language.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u8 menu_select;
|
||||||
|
|
||||||
|
// 菜单闲置自动关闭超时 (秒):无任何按键操作达到这个时长,自动退出菜单返回时钟主页
|
||||||
|
#define MENU_IDLE_TIMEOUT_SEC 10
|
||||||
|
|
||||||
|
// 本应用私有变量:累计未按任何键的秒数 (由 KEY_EVENT_SECOND_TICK 驱动,任意按键事件清零)
|
||||||
|
static u8 menu_idle_sec = 0;
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void MenuApp_OnStart(void);
|
||||||
|
static void MenuApp_onClose(void);
|
||||||
|
static EventResult MenuApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义菜单应用控制块 (顶层 4 项列表:语言切换/时间设置/传感器配对/摄像头绑定)
|
||||||
|
WristbandApp menu_app = {
|
||||||
|
APP_ID_MENU,
|
||||||
|
"Menu",
|
||||||
|
MenuApp_OnStart,
|
||||||
|
NULL,
|
||||||
|
MenuApp_onClose,
|
||||||
|
MenuApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MenuApp 启动回调
|
||||||
|
*/
|
||||||
|
static void MenuApp_OnStart(void)
|
||||||
|
{
|
||||||
|
menu_select = 0;
|
||||||
|
menu_idle_sec = 0;
|
||||||
|
current_state = STATE_PAIR_MENU; // 系统运行状态设为对码配置菜单
|
||||||
|
RF_SetMode(0); // 关闭射频接收芯片,防止接收杂波干扰屏幕绘制
|
||||||
|
UI_ShowMainMenu(menu_select); // 绘制主菜单
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭所有灯条
|
||||||
|
XTELL_LOG("[MenuApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MenuApp 退出清理回调
|
||||||
|
*/
|
||||||
|
static void MenuApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭 RGB
|
||||||
|
RF_SetMode(1); // 重新开启无线接收模式
|
||||||
|
XTELL_LOG("[MenuApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MenuApp 接收按键与控制事件回调 (0-语言切换, 1-HORLOGE, 2-APPAIRAGE, 3-LIAISON)
|
||||||
|
*/
|
||||||
|
static EventResult MenuApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
// 0. 整秒滴答:累计闲置秒数,达到门限就自动关闭菜单退回待机主页
|
||||||
|
if (evt->key_event == KEY_EVENT_SECOND_TICK) {
|
||||||
|
menu_idle_sec++;
|
||||||
|
if (menu_idle_sec >= MENU_IDLE_TIMEOUT_SEC) {
|
||||||
|
menu_idle_sec = 0;
|
||||||
|
XTELL_LOG("[MenuApp] Idle timeout, closing.\r\n");
|
||||||
|
AppManager_SwitchToForeground(&home_app);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收到任意其它按键事件,说明用户仍在操作,清零闲置计数
|
||||||
|
menu_idle_sec = 0;
|
||||||
|
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
||||||
|
// 短按 ▲ 键向上移动焦点,允许循环切换 (到顶绕到底)
|
||||||
|
menu_select = (menu_select == 0) ? 3 : (menu_select - 1);
|
||||||
|
UI_ShowMainMenu(menu_select);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
||||||
|
// 短按 ▼ 键向下移动焦点,允许循环切换 (到底绕到顶)
|
||||||
|
menu_select = (menu_select == 3) ? 0 : (menu_select + 1);
|
||||||
|
UI_ShowMainMenu(menu_select);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
||||||
|
// 短按 ■ 确认键,进入选定功能的子页面
|
||||||
|
if (menu_select == 0) {
|
||||||
|
// 功能 0:语言切换。原地在 FR/EN 间切换,立即保存并用新语言重绘当前菜单
|
||||||
|
Language new_lang = (Lang_Current() == LANG_FR) ? LANG_EN : LANG_FR;
|
||||||
|
Lang_Set(new_lang);
|
||||||
|
Save_Database(); // 语言字节与传感器数据库共用同一扇区持久化
|
||||||
|
MOTOR = 1; Delay_ms(50); MOTOR = 0; // 短震反馈,与其它确认操作风格一致
|
||||||
|
UI_ShowMainMenu(menu_select);
|
||||||
|
} else if (menu_select == 1) {
|
||||||
|
// 功能 1:设置时间。切换到独立的时间设置子 App
|
||||||
|
AppManager_SwitchToForeground(&set_clock_app);
|
||||||
|
} else if (menu_select == 2) {
|
||||||
|
// 功能 2:传感器学习。切换到独立的对码子 App (类型选择是它的第一个状态)
|
||||||
|
AppManager_SwitchToForeground(&pair_app);
|
||||||
|
} else if (menu_select == 3) {
|
||||||
|
// 功能 3:相机配对。切换到独立的摄像头绑定子 App
|
||||||
|
AppManager_SwitchToForeground(&ipc_bind_app);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
|
||||||
|
// 长按 ■ 确认键,退出设置菜单直接返回待机主页
|
||||||
|
AppManager_SwitchToForeground(&home_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
275
App/app_pair.c
Normal file
275
App/app_pair.c
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "clock.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u8 captured_type;
|
||||||
|
extern u32 captured_addr;
|
||||||
|
extern u8 menu_select;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
|
||||||
|
// 二级传感器对码菜单中,各类传感器显示名称的字符串 ID,实际文案由 Lang_Get() 按当前语言取出
|
||||||
|
const StringID device_menu_ids[5] = {
|
||||||
|
STR_DEV_PORTE, // 0: 门磁探测器
|
||||||
|
STR_DEV_PIR, // 1: 红外探测器
|
||||||
|
STR_DEV_FUMEE, // 2: 烟雾传感器
|
||||||
|
STR_DEV_GAZ, // 3: 燃气探测器
|
||||||
|
STR_DEV_EAU // 4: 水浸探测器
|
||||||
|
};
|
||||||
|
|
||||||
|
// 对码状态机定义
|
||||||
|
enum {
|
||||||
|
PAIR_STATE_SELECT_TYPE, // 选择要学习的传感器类型 (门磁/PIR/烟感/气感/水浸)
|
||||||
|
PAIR_STATE_WAIT, // 等待射频探测器触发 (扩散雷达页面)
|
||||||
|
PAIR_STATE_CONFIRM, // 已捕获,进入分配防区确认页面
|
||||||
|
PAIR_STATE_SUCCESS, // 对码绑定成功界面 (SUCCESS)
|
||||||
|
PAIR_STATE_FAIL // 超时或失败界面 (FAIL)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 本地私有变量
|
||||||
|
static u8 pair_state; // 当前的对码阶段状态
|
||||||
|
static u8 last_radar_sec; // 备份上一次雷达动画的帧索引
|
||||||
|
static u8 captured_zone = 1; // 准备分配的防区号 (1~99)
|
||||||
|
static u8 success_start_sec = 0;// 对码成功时的起始秒数 (延迟退场)
|
||||||
|
static u8 fail_start_sec = 0; // 对码失败时的起始秒数
|
||||||
|
static u32 pair_pulse_timer = 0; // 对码超时毫秒计数器
|
||||||
|
static u16 last_run_tick = 0; // 上一次执行 onRun 时的毫秒滴答数
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void PairApp_OnStart(void);
|
||||||
|
static void PairApp_onRun(void);
|
||||||
|
static void PairApp_onClose(void);
|
||||||
|
static EventResult PairApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义对码应用控制块
|
||||||
|
WristbandApp pair_app = {
|
||||||
|
APP_ID_PAIR,
|
||||||
|
"Pair",
|
||||||
|
PairApp_OnStart,
|
||||||
|
PairApp_onRun,
|
||||||
|
PairApp_onClose,
|
||||||
|
PairApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 进入雷达搜索等待阶段 (选定传感器类型之后)
|
||||||
|
*/
|
||||||
|
static void PairApp_EnterWait(void)
|
||||||
|
{
|
||||||
|
pair_state = PAIR_STATE_WAIT;
|
||||||
|
last_radar_sec = 99; // 强制刷写
|
||||||
|
current_state = STATE_PAIR_WAIT;
|
||||||
|
pair_pulse_timer = 0;
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
captured_zone = 1;
|
||||||
|
RF_SetMode(1); // 开启无线接收模式
|
||||||
|
|
||||||
|
// 初始化对码搜索页面绘制
|
||||||
|
UI_ShowPairWaitPage(0);
|
||||||
|
|
||||||
|
// 开启白色幻彩双灯珠常亮 (R=150, G=150, B=150),作为对码状态视觉指示
|
||||||
|
RGB_Send(150, 150, 150, 150, 150, 150);
|
||||||
|
|
||||||
|
XTELL_LOG("[PairApp] Entering WAIT\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 启动回调
|
||||||
|
*/
|
||||||
|
static void PairApp_OnStart(void)
|
||||||
|
{
|
||||||
|
// 为了防止后台射频监听与对码射频接收冲突,将后台射频监听应用暂时卸载
|
||||||
|
AppManager_DetachFromBackground(&rf_monitor_app);
|
||||||
|
|
||||||
|
pair_state = PAIR_STATE_SELECT_TYPE;
|
||||||
|
menu_select = 0;
|
||||||
|
current_state = STATE_PAIR_MENU;
|
||||||
|
RF_SetMode(0); // 选类型阶段暂不需要天线,防止接收杂波干扰屏幕绘制
|
||||||
|
UI_ShowPairMenuPage(menu_select); // 绘制传感器类型选择页
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
XTELL_LOG("[PairApp] Started (SELECT_TYPE)\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 轮询主回调
|
||||||
|
*/
|
||||||
|
static void PairApp_onRun(void)
|
||||||
|
{
|
||||||
|
u32 rx_addr;
|
||||||
|
u8 rx_data;
|
||||||
|
u8 flash;
|
||||||
|
u8 frame;
|
||||||
|
u16 tick_diff;
|
||||||
|
|
||||||
|
// 计算非阻塞的毫秒时间增量
|
||||||
|
if (ms_tick >= last_run_tick) {
|
||||||
|
tick_diff = ms_tick - last_run_tick;
|
||||||
|
} else {
|
||||||
|
tick_diff = 1000 + ms_tick - last_run_tick;
|
||||||
|
}
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
if (pair_state == PAIR_STATE_WAIT) {
|
||||||
|
// ====== 等待射频数据期间,白光 3Hz 高频闪烁 (周期 333ms) ======
|
||||||
|
flash = (ms_tick / 166) % 2;
|
||||||
|
if (flash) RGB_Send(150, 150, 150, 150, 150, 150);
|
||||||
|
else RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 每隔 250ms,滚动绘制雷达搜索的 3 帧图片,构成动效
|
||||||
|
frame = (u8)((ms_tick / 250) % 3);
|
||||||
|
if (frame != last_radar_sec) {
|
||||||
|
last_radar_sec = frame;
|
||||||
|
UI_ShowPairWaitPage(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 累加计算 30 秒的对码时间上限
|
||||||
|
pair_pulse_timer += tick_diff;
|
||||||
|
if (pair_pulse_timer >= 30000UL) {
|
||||||
|
pair_pulse_timer = 0;
|
||||||
|
pair_state = PAIR_STATE_FAIL;
|
||||||
|
current_state = STATE_PAIR_FAIL;
|
||||||
|
fail_start_sec = current_sec;
|
||||||
|
UI_ShowPairFailPage(1); // 绘制超时大红叉页
|
||||||
|
|
||||||
|
// 红色双灯常亮警告
|
||||||
|
RGB_Send(200, 0, 0, 200, 0, 0);
|
||||||
|
XTELL_LOG("[PairApp] Timeout fail!\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 物理射频包解调接收监听 ======
|
||||||
|
if (EV1527_Decode(&rx_addr, &rx_data)) {
|
||||||
|
// 保护机制:排除 SOS 码 (0x08)
|
||||||
|
if (rx_data != 0x08) {
|
||||||
|
captured_addr = rx_addr; // 缓存捕获的物理 ID 地址 (含数据码)
|
||||||
|
pair_state = PAIR_STATE_CONFIRM; // 进入防区微调确认模式
|
||||||
|
current_state = STATE_PAIR_CONFIRM;
|
||||||
|
RF_SetMode(0); // 暂时关闭物理射频,防止再次覆盖
|
||||||
|
captured_zone = 1;
|
||||||
|
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
||||||
|
XTELL_LOG("[PairApp] Sensor captured. Addr=");
|
||||||
|
XTELL_LOG_HEX32(rx_addr);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pair_state == PAIR_STATE_CONFIRM) {
|
||||||
|
// ====== 确认状态:橙色慢闪 (1.5Hz,周期 666ms) ======
|
||||||
|
u8 flash_orange = (ms_tick / 333) % 2;
|
||||||
|
if (flash_orange) RGB_Send(100, 180, 0, 100, 180, 0); // 橙色 (G=180, R=100)
|
||||||
|
else RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
else if (pair_state == PAIR_STATE_SUCCESS) {
|
||||||
|
// ====== 成功展示状态:延时 2 秒自动返回主菜单 ======
|
||||||
|
u8 elapsed = (current_sec >= success_start_sec) ? (current_sec - success_start_sec) : (60 + current_sec - success_start_sec);
|
||||||
|
if (elapsed >= 2) {
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pair_state == PAIR_STATE_FAIL) {
|
||||||
|
// ====== 失败展示状态:延时 5 秒自动返回主菜单 ======
|
||||||
|
u8 elapsed = (current_sec >= fail_start_sec) ? (current_sec - fail_start_sec) : (60 + current_sec - fail_start_sec);
|
||||||
|
if (elapsed >= 5) {
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 退出清理
|
||||||
|
*/
|
||||||
|
static void PairApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭 RGB
|
||||||
|
MOTOR = 0; // 停震
|
||||||
|
RF_SetMode(1); // 开启无线接收
|
||||||
|
|
||||||
|
// 重新挂载后台射频监控应用,以便回到时钟状态下能继续捕获报警
|
||||||
|
AppManager_AttachToBackground(&rf_monitor_app);
|
||||||
|
|
||||||
|
XTELL_LOG("[PairApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief PairApp 事件拦截处理回调
|
||||||
|
*/
|
||||||
|
static EventResult PairApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (pair_state == PAIR_STATE_SELECT_TYPE) {
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
||||||
|
// 短按 ▲ 键上移焦点,允许循环切换 (到顶绕到底)
|
||||||
|
menu_select = (menu_select == 0) ? 4 : (menu_select - 1);
|
||||||
|
UI_ShowPairMenuPage(menu_select);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
||||||
|
// 短按 ▼ 键下移焦点,允许循环切换 (到底绕到顶)
|
||||||
|
menu_select = (menu_select == 4) ? 0 : (menu_select + 1);
|
||||||
|
UI_ShowPairMenuPage(menu_select);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
||||||
|
// 确认选择:缓存用户选好的传感器类别,短震反馈并进入雷达搜索阶段
|
||||||
|
captured_type = menu_select;
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(100);
|
||||||
|
MOTOR = 0;
|
||||||
|
PairApp_EnterWait();
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pair_state == PAIR_STATE_CONFIRM) {
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK) {
|
||||||
|
// 短按 ▲ 键增加目标分配防区号 (上限 99)
|
||||||
|
if (captured_zone < 99) {
|
||||||
|
captured_zone++;
|
||||||
|
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
|
||||||
|
// 短按 ▼ 键减小目标分配防区号 (下限 1)
|
||||||
|
if (captured_zone > 1) {
|
||||||
|
captured_zone--;
|
||||||
|
UI_ShowPairConfirmPage(captured_type, captured_zone);
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK) {
|
||||||
|
// 短按 ■ 确认键,写入 EEPROM 传感器数据库并保存
|
||||||
|
Add_Sensor_With_Zone(captured_addr, captured_type, captured_zone);
|
||||||
|
|
||||||
|
pair_state = PAIR_STATE_SUCCESS;
|
||||||
|
current_state = STATE_PAIR_SUCCESS;
|
||||||
|
success_start_sec = current_sec;
|
||||||
|
UI_ShowPairSuccessPage(); // 绘制打勾页
|
||||||
|
|
||||||
|
// 绿色双灯珠常亮,震动马达长震 400ms 反馈
|
||||||
|
RGB_Send(0, 200, 0, 0, 200, 0);
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(400);
|
||||||
|
MOTOR = 0;
|
||||||
|
|
||||||
|
XTELL_LOG("[PairApp] Sensor saved successfully.\r\n");
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在任何阶段,长按 ■ 确认键或长按 ▼ 键都代表放弃/取消对码,统一退回主菜单 (父 App)
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG || evt->key_event == KEY_EVENT_DOWN_LONG) {
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
105
App/app_rf_monitor.c
Normal file
105
App/app_rf_monitor.c
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern u32 captured_addr;
|
||||||
|
extern u8 alarm_sensor_slot;
|
||||||
|
extern Sensor_Slot xdata sensor_list[16];
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void RfMonitorApp_OnStart(void);
|
||||||
|
static void RfMonitorApp_onRun(void);
|
||||||
|
static void RfMonitorApp_onClose(void);
|
||||||
|
static EventResult RfMonitorApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义后台射频监控应用控制块
|
||||||
|
WristbandApp rf_monitor_app = {
|
||||||
|
0xFE, // 后台应用 ID 设为 0xFE
|
||||||
|
"RF_Monitor",
|
||||||
|
RfMonitorApp_OnStart,
|
||||||
|
RfMonitorApp_onRun,
|
||||||
|
RfMonitorApp_onClose,
|
||||||
|
RfMonitorApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 启动回调
|
||||||
|
*/
|
||||||
|
static void RfMonitorApp_OnStart(void)
|
||||||
|
{
|
||||||
|
RF_SetMode(1); // 开启天线并设为接收模式
|
||||||
|
XTELL_LOG("[RF_Monitor] Background RF monitor started.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 轮询主回调
|
||||||
|
* @details 负责在后台静默轮询物理射频 EV1527 解码器。
|
||||||
|
* 解码成功后在本地传感器数据库中匹配,并执行布撤防及防区过滤逻辑,
|
||||||
|
* 匹配通过后一键拉起前台报警应用。
|
||||||
|
*/
|
||||||
|
static void RfMonitorApp_onRun(void)
|
||||||
|
{
|
||||||
|
u32 rx_addr;
|
||||||
|
u8 rx_data;
|
||||||
|
u8 slot;
|
||||||
|
|
||||||
|
// 常态对待机下的射频检测与触发过滤逻辑
|
||||||
|
if (EV1527_Decode(&rx_addr, &rx_data)) {
|
||||||
|
XTELL_LOG("[RF_Monitor] Decoded Addr=");
|
||||||
|
XTELL_LOG_HEX32(rx_addr);
|
||||||
|
XTELL_LOG(", Data=");
|
||||||
|
XTELL_LOG_HEX8(rx_data);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
|
||||||
|
// 比对收到的 24 位射频源地址是否合法注册在手环本地数据库中
|
||||||
|
if (Check_Sensor_ID(rx_addr, &slot)) {
|
||||||
|
u8 sensor_type = sensor_list[slot].type; // 获取注册的设备类型
|
||||||
|
if (sensor_type < 5) {
|
||||||
|
// 防区分类过滤与布撤防过滤逻辑:
|
||||||
|
// - zone == 0,或者烟感(2)、气感(3)、水浸(4) 为 24小时紧急防区,不受当前布防状态影响,强制触发报警;
|
||||||
|
// - 门磁(0)和红外(1)为普通边界入侵防区,只有当系统处于 STATE_ARMED (布防) 状态时,才触发警报。
|
||||||
|
if (sensor_list[slot].zone == 0 ||
|
||||||
|
sensor_type == 2 ||
|
||||||
|
sensor_type == 3 ||
|
||||||
|
sensor_type == 4 ||
|
||||||
|
current_state == STATE_ARMED)
|
||||||
|
{
|
||||||
|
// 只有在前台不为紧急 SOS 呼救且当前不处于报警状态时,才执行报警调起
|
||||||
|
if (AppManager_GetActiveAppID() != APP_ID_SOS &&
|
||||||
|
AppManager_GetActiveAppID() != APP_ID_ALARM)
|
||||||
|
{
|
||||||
|
captured_addr = rx_addr; // 缓存报警地址
|
||||||
|
alarm_sensor_slot = slot; // 缓存报警槽位号
|
||||||
|
XTELL_LOG("[RF_Monitor] Intruder ALERT! Waking up alarm app...\r\n");
|
||||||
|
AppManager_SwitchToForeground(&alarm_app); // 动态切入警报前台
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
XTELL_LOG("[RF_Monitor] Ignored due to DISARMED state.\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
XTELL_LOG("[RF_Monitor] Unregistered sensor address.\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 退出清理
|
||||||
|
*/
|
||||||
|
static void RfMonitorApp_onClose(void)
|
||||||
|
{
|
||||||
|
XTELL_LOG("[RF_Monitor] Background RF monitor closed.\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RfMonitorApp 事件处理 (后台应用暂不拦截处理按键事件)
|
||||||
|
*/
|
||||||
|
static EventResult RfMonitorApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
174
App/app_set_clock.c
Normal file
174
App/app_set_clock.c
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "clock.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/pcf8563.h"
|
||||||
|
|
||||||
|
// 外部引用的全局系统状态变量
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
|
||||||
|
// 本应用私有的时间编辑状态
|
||||||
|
static u8 time_edit_field = 1; // 当前聚焦调节的字段 (1:小时, 2:分钟, 3:日, 4:月, 5:年)
|
||||||
|
static u8 time_edit_blink_on = 1; // 时钟修改时光标闪烁周期状态位
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void SetClockApp_OnStart(void);
|
||||||
|
static void SetClockApp_onRun(void);
|
||||||
|
static void SetClockApp_onClose(void);
|
||||||
|
static EventResult SetClockApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义时间设置应用控制块 (从 menu_app 的"HORLOGE"选项进入)
|
||||||
|
WristbandApp set_clock_app = {
|
||||||
|
APP_ID_SET_CLOCK,
|
||||||
|
"SetClock",
|
||||||
|
SetClockApp_OnStart,
|
||||||
|
SetClockApp_onRun,
|
||||||
|
SetClockApp_onClose,
|
||||||
|
SetClockApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SetClockApp 启动回调
|
||||||
|
*/
|
||||||
|
static void SetClockApp_OnStart(void)
|
||||||
|
{
|
||||||
|
time_edit_field = 1;
|
||||||
|
time_edit_blink_on = 1;
|
||||||
|
current_state = STATE_SET_TIME; // 系统切入时间设置状态
|
||||||
|
|
||||||
|
// FCOB 幻彩灯条双灯珠均显示蓝色常亮 (R=0, G=0, B=180),进行模式视觉区分
|
||||||
|
RGB_Send(0, 0, 180, 0, 0, 180);
|
||||||
|
|
||||||
|
// 振动马达单次短震 50 毫秒,提供触觉反馈
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(50);
|
||||||
|
MOTOR = 0;
|
||||||
|
|
||||||
|
// 渲染显示时间编辑界面,默认高亮编辑小时字段,消隐位开启
|
||||||
|
UI_ShowSetTimePage(current_hour, current_min, current_day, current_month, current_year, current_weekday, time_edit_field, 1);
|
||||||
|
XTELL_LOG("[SetClockApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SetClockApp 轮询主回调
|
||||||
|
*/
|
||||||
|
static void SetClockApp_onRun(void)
|
||||||
|
{
|
||||||
|
// ====== 时间设置闪烁动画 (周期 500ms) ======
|
||||||
|
u8 blink = (ms_tick / 500) % 2;
|
||||||
|
if (blink != time_edit_blink_on)
|
||||||
|
{
|
||||||
|
time_edit_blink_on = blink;
|
||||||
|
// 每次闪烁状态发生变化,只局部刷新正在调节的那个字段,不整屏重绘
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, time_edit_field, time_edit_blink_on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SetClockApp 退出清理回调
|
||||||
|
*/
|
||||||
|
static void SetClockApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
|
||||||
|
XTELL_LOG("[SetClockApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SetClockApp 接收按键事件处理回调
|
||||||
|
*/
|
||||||
|
static EventResult SetClockApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (evt->key_event == KEY_EVENT_UP_CLICK)
|
||||||
|
{
|
||||||
|
// 短按 ▲ 键增加当前聚焦字段的数值 (各字段独立循环)
|
||||||
|
switch (time_edit_field) {
|
||||||
|
case 1: current_hour = (current_hour + 1) % 24; break;
|
||||||
|
case 2: current_min = (current_min + 1) % 60; break;
|
||||||
|
case 3:
|
||||||
|
current_day = (current_day >= Days_In_Month(current_year, current_month)) ? 1 : (current_day + 1);
|
||||||
|
Clock_RecalcWeekday();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
current_month = (current_month >= 12) ? 1 : (current_month + 1);
|
||||||
|
// 月份变化后,若当前日超出新月份的最大天数,截断到最大天数
|
||||||
|
if (current_day > Days_In_Month(current_year, current_month)) {
|
||||||
|
current_day = Days_In_Month(current_year, current_month);
|
||||||
|
}
|
||||||
|
Clock_RecalcWeekday();
|
||||||
|
break;
|
||||||
|
default: // 5: 年
|
||||||
|
current_year = (current_year >= 99) ? 0 : (current_year + 1);
|
||||||
|
if (current_day > Days_In_Month(current_year, current_month)) {
|
||||||
|
current_day = Days_In_Month(current_year, current_month);
|
||||||
|
}
|
||||||
|
Clock_RecalcWeekday();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
time_edit_blink_on = 1; // 调值后强制显示 (不处于消隐周期)
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, time_edit_field, 1);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_DOWN_CLICK)
|
||||||
|
{
|
||||||
|
// 短按 ▼ 键减小当前聚焦字段的数值 (各字段独立循环)
|
||||||
|
switch (time_edit_field) {
|
||||||
|
case 1: current_hour = (current_hour == 0) ? 23 : (current_hour - 1); break;
|
||||||
|
case 2: current_min = (current_min == 0) ? 59 : (current_min - 1); break;
|
||||||
|
case 3:
|
||||||
|
current_day = (current_day <= 1) ? Days_In_Month(current_year, current_month) : (current_day - 1);
|
||||||
|
Clock_RecalcWeekday();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
current_month = (current_month <= 1) ? 12 : (current_month - 1);
|
||||||
|
if (current_day > Days_In_Month(current_year, current_month)) {
|
||||||
|
current_day = Days_In_Month(current_year, current_month);
|
||||||
|
}
|
||||||
|
Clock_RecalcWeekday();
|
||||||
|
break;
|
||||||
|
default: // 5: 年
|
||||||
|
current_year = (current_year == 0) ? 99 : (current_year - 1);
|
||||||
|
if (current_day > Days_In_Month(current_year, current_month)) {
|
||||||
|
current_day = Days_In_Month(current_year, current_month);
|
||||||
|
}
|
||||||
|
Clock_RecalcWeekday();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
time_edit_blink_on = 1; // 调值后强制显示 (不处于消隐周期)
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, time_edit_field, 1);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_LONG)
|
||||||
|
{
|
||||||
|
// 长按 ■ 确认键切换调节字段 (小时 -> 分钟 -> 日 -> 月 -> 年 -> 循环回小时)
|
||||||
|
time_edit_field = (time_edit_field >= 5) ? 1 : (time_edit_field + 1);
|
||||||
|
time_edit_blink_on = 1; // 切换焦点后所有字段都应处于可见状态
|
||||||
|
// 全部字段各局部刷新一次,避免切换前正处于消隐周期的那个字段停留在涂黑状态
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, 1, 1);
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, 2, 1);
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, 3, 1);
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, 4, 1);
|
||||||
|
UI_UpdateSetTimeDigit(current_hour, current_min, current_day, current_month, current_year, current_weekday, 5, 1);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
if (evt->key_event == KEY_EVENT_CONFIRM_CLICK)
|
||||||
|
{
|
||||||
|
// 短按 ■ 确认键保存最新时间与日期配置,退出到主菜单 (父 App)
|
||||||
|
current_state = STATE_PAIR_MENU;
|
||||||
|
|
||||||
|
#if USE_PCF8563
|
||||||
|
// 同步将最新时间与日期 (含算好的星期) 一次性写入 PCF8563 芯片寄存器
|
||||||
|
PCF8563_WriteDateTime(current_hour, current_min, 0, current_day, current_month, current_year, current_weekday);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 退出短振反馈
|
||||||
|
MOTOR = 1; Delay_ms(50); MOTOR = 0;
|
||||||
|
|
||||||
|
AppManager_SwitchToForeground(&menu_app);
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_HANDLED; // 在时间设置模式下屏蔽拦截其他一切无关按键事件
|
||||||
|
}
|
||||||
134
App/app_sos.c
Normal file
134
App/app_sos.c
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#include "app_manager.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "rgb.h"
|
||||||
|
#include "event.h"
|
||||||
|
#include "system.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
#include "../Drivers/lcd.h"
|
||||||
|
|
||||||
|
// 外部全局变量引用
|
||||||
|
extern SystemState current_state;
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
extern volatile bit alarm_flash_flag;
|
||||||
|
|
||||||
|
// 本地私有变量
|
||||||
|
static u8 sos_last_phase = 99; // 记录上一次发射的 500ms 半周期状态
|
||||||
|
static u32 sos_pulse_timer = 0; // SOS 马达震动波形计数器
|
||||||
|
static u16 last_run_tick = 0; // 记录上一次轮询的 ticks
|
||||||
|
|
||||||
|
// 声明本应用的生命周期回调函数
|
||||||
|
static void SOSApp_OnStart(void);
|
||||||
|
static void SOSApp_onRun(void);
|
||||||
|
static void SOSApp_onClose(void);
|
||||||
|
static EventResult SOSApp_onEvent(SystemEvent *evt);
|
||||||
|
|
||||||
|
// 实体化定义 SOS 呼救应用控制块
|
||||||
|
WristbandApp sos_app = {
|
||||||
|
APP_ID_SOS,
|
||||||
|
"SOS",
|
||||||
|
SOSApp_OnStart,
|
||||||
|
SOSApp_onRun,
|
||||||
|
SOSApp_onClose,
|
||||||
|
SOSApp_onEvent
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 启动回调
|
||||||
|
*/
|
||||||
|
static void SOSApp_OnStart(void)
|
||||||
|
{
|
||||||
|
current_state = STATE_SOS_EMITTED; // 进入 SOS 紧急求救状态
|
||||||
|
sos_last_phase = 99;
|
||||||
|
sos_pulse_timer = 0; // 马达震动计数器清零
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
UI_ShowSosPage(); // 初始化渲染 SOS 警示主页
|
||||||
|
|
||||||
|
// 立即点亮左侧灯为红色,右侧熄灭,准备开启双色红灯交替爆闪
|
||||||
|
RGB_Send(0, 200, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
XTELL_LOG("[SOSApp] Started\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 轮询主回调
|
||||||
|
* @details 包含 500ms 周期的高频射频 SOS 码发射、红蓝交替爆闪以及
|
||||||
|
* 马达 (1000ms震动 / 200ms静音) 规律震动控制。
|
||||||
|
*/
|
||||||
|
static void SOSApp_onRun(void)
|
||||||
|
{
|
||||||
|
u16 tick_diff;
|
||||||
|
|
||||||
|
// ====== 1. 控制无线射频,每 500ms 发射一次 SOS 无线数据帧 (0x08) ======
|
||||||
|
u8 sec_phase = (u8)((ms_tick / 500) % 2);
|
||||||
|
if (sec_phase != sos_last_phase) {
|
||||||
|
sos_last_phase = sec_phase;
|
||||||
|
RF_SetMode(2); // 切换发射
|
||||||
|
EV1527_Transmit(CLONED_ADDR, 0x08); // 发射克隆 ID 及其 SOS (0x08) 命令字
|
||||||
|
RF_SetMode(0); // 释放接收
|
||||||
|
XTELL_LOG("[SOSApp] SOS Radio packet emitted\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 2. 控制 RGB 双灯珠以 50ms 频率进行高频左右交替红蓝爆闪 ======
|
||||||
|
{
|
||||||
|
u8 flash = (ms_tick / 50) % 2;
|
||||||
|
if (flash) {
|
||||||
|
RGB_Send(0, 200, 0, 0, 0, 200); // 灯 1 亮红 (GRB 中 R=200),灯 2 亮蓝 (B=200)
|
||||||
|
} else {
|
||||||
|
RGB_Send(0, 0, 200, 0, 200, 0); // 灯 1 亮蓝,灯 2 亮红
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 3. 控制屏幕绘制多层爆闪红白框 ======
|
||||||
|
if (alarm_flash_flag) {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
||||||
|
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED);
|
||||||
|
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE);
|
||||||
|
} else {
|
||||||
|
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_BLACK);
|
||||||
|
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_BLACK);
|
||||||
|
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 4. 马达 SOS 震动节拍自处理 (1000ms 震动,200ms 静音) ======
|
||||||
|
if (ms_tick >= last_run_tick) {
|
||||||
|
tick_diff = ms_tick - last_run_tick;
|
||||||
|
} else {
|
||||||
|
tick_diff = 1000 + ms_tick - last_run_tick;
|
||||||
|
}
|
||||||
|
last_run_tick = ms_tick;
|
||||||
|
|
||||||
|
sos_pulse_timer += tick_diff;
|
||||||
|
if (sos_pulse_timer >= 1200) {
|
||||||
|
sos_pulse_timer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sos_pulse_timer < 1000) {
|
||||||
|
MOTOR = 1;
|
||||||
|
} else {
|
||||||
|
MOTOR = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 退出清理
|
||||||
|
*/
|
||||||
|
static void SOSApp_onClose(void)
|
||||||
|
{
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭 RGB
|
||||||
|
MOTOR = 0; // 停震
|
||||||
|
current_state = STATE_NORMAL;
|
||||||
|
XTELL_LOG("[SOSApp] Closed\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SOSApp 事件拦截处理回调 (按任意键退出 SOS)
|
||||||
|
*/
|
||||||
|
static EventResult SOSApp_onEvent(SystemEvent *evt)
|
||||||
|
{
|
||||||
|
if (evt->key_event != KEY_EVENT_NONE) {
|
||||||
|
AppManager_SwitchToForeground(&home_app); // 退出 SOS,返回时钟
|
||||||
|
return EVENT_HANDLED;
|
||||||
|
}
|
||||||
|
return EVENT_IGNORED;
|
||||||
|
}
|
||||||
99
App/clock.c
Normal file
99
App/clock.c
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#include "clock.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
|
||||||
|
// 软时钟基础计时变量
|
||||||
|
u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
|
||||||
|
u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
|
||||||
|
u8 current_sec = 0; // 系统软时钟:秒
|
||||||
|
u8 current_day = 1; // 系统软时钟:日 (默认初始化为 1 号)
|
||||||
|
u8 current_month = 1; // 系统软时钟:月 (默认初始化为 1 月)
|
||||||
|
u8 current_year = 25; // 系统软时钟:年 (默认初始化为 2025 年)
|
||||||
|
u8 current_weekday = 0; // 系统软时钟:星期 (0=周日, 由 Clock_RecalcWeekday 现算)
|
||||||
|
volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
|
||||||
|
volatile bit second_tick_flag = 0; // 整秒滴答标志位,每次秒进位置 1,由主循环转换为 KEY_EVENT_SECOND_TICK 事件
|
||||||
|
|
||||||
|
// 毫秒计数器 (Timer1 每 1ms 累加)
|
||||||
|
static u16 ms_cnt = 0;
|
||||||
|
|
||||||
|
// 每月天数表 (2 月固定按 28 天,闰年在 Days_In_Month 里单独加 1 天处理)
|
||||||
|
static code u8 days_in_month_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||||
|
|
||||||
|
u8 Days_In_Month(u8 year, u8 month)
|
||||||
|
{
|
||||||
|
if (month < 1 || month > 12) {
|
||||||
|
return 31; // 防御性兜底,不应该发生
|
||||||
|
}
|
||||||
|
if (month == 2) {
|
||||||
|
// 闰年判断:2000~2099 区间内,能被 4 整除即为闰年 (期间没有能被 100 整除的年份需要排除)
|
||||||
|
if ((year % 4) == 0) {
|
||||||
|
return 29;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return days_in_month_table[month - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clock_RecalcWeekday(void)
|
||||||
|
{
|
||||||
|
// Zeller 公式:h = (q + [13(m+1)/5] + K + [K/4] + [J/4] - 2J) mod 7
|
||||||
|
// h: 0=周六,1=周日,2=周一,...,6=周五;用 "+5J" 代替 "-2J" 避免无符号数下溢 (-2 ≡ 5 (mod 7))
|
||||||
|
// 1、2 月按上一年的 13、14 月处理
|
||||||
|
u8 m = current_month;
|
||||||
|
u8 y = current_year;
|
||||||
|
u8 j = 20; // 世纪数 (2000~2099 固定为 20)
|
||||||
|
u16 h;
|
||||||
|
|
||||||
|
if (m < 3) {
|
||||||
|
m += 12;
|
||||||
|
if (y == 0) {
|
||||||
|
y = 99; // 2000 年 1/2 月,按 1999 年处理的边界情况
|
||||||
|
j = 19;
|
||||||
|
} else {
|
||||||
|
y -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h = (current_day + (13UL * (m + 1)) / 5 + y + y / 4 + j / 4 + 5UL * j) % 7;
|
||||||
|
|
||||||
|
// 把 Zeller 原始约定 (0=周六) 转换成常用的 0=周日, 1=周一, ..., 6=周六
|
||||||
|
current_weekday = (u8)((h + 6) % 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
|
||||||
|
* @details 负责由定时器 1ms 驱动递增软时钟,并在分钟/日期发生跳变时做相应处理
|
||||||
|
*/
|
||||||
|
void Clock_IncMS(void)
|
||||||
|
{
|
||||||
|
ms_cnt++;
|
||||||
|
if (ms_cnt >= 1000) {
|
||||||
|
ms_cnt = 0; // 1秒时间到
|
||||||
|
current_sec++;
|
||||||
|
second_tick_flag = 1; // 整秒滴答,通知主循环生成 KEY_EVENT_SECOND_TICK 事件
|
||||||
|
if (current_sec >= 60) {
|
||||||
|
current_sec = 0; // 1分钟时间到
|
||||||
|
current_min++;
|
||||||
|
if (current_min >= 60) {
|
||||||
|
current_min = 0; // 1小时时间到
|
||||||
|
current_hour++;
|
||||||
|
if (current_hour >= 24) {
|
||||||
|
current_hour = 0; // 小时循环,日期进位
|
||||||
|
current_day++;
|
||||||
|
if (current_day > Days_In_Month(current_year, current_month)) {
|
||||||
|
current_day = 1; // 月份进位
|
||||||
|
current_month++;
|
||||||
|
if (current_month > 12) {
|
||||||
|
current_month = 1; // 年份进位 (不处理世纪)
|
||||||
|
current_year++;
|
||||||
|
if (current_year > 99) {
|
||||||
|
current_year = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Clock_RecalcWeekday(); // 日期变化,重新计算星期
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clock_updated = 1; // 分钟跳变,标志位置位,前台待机 App 会捕获以刷新 UI
|
||||||
|
XTELL_LOG("[CLOCK] Time updated!\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
App/clock.h
Normal file
36
App/clock.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef __CLOCK_H__
|
||||||
|
#define __CLOCK_H__
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
// 全局软时钟变量声明
|
||||||
|
extern u8 current_hour;
|
||||||
|
extern u8 current_min;
|
||||||
|
extern u8 current_sec;
|
||||||
|
extern u8 current_day; // 日 (1~31)
|
||||||
|
extern u8 current_month; // 月 (1~12)
|
||||||
|
extern u8 current_year; // 年 (0~99,代表 2000~2099,不处理世纪)
|
||||||
|
extern u8 current_weekday; // 星期 (0=周日, 1=周一, ..., 6=周六)
|
||||||
|
extern volatile u8 clock_updated;
|
||||||
|
extern volatile bit second_tick_flag; // 整秒滴答标志位,每次 current_sec 递增时置 1,供主循环转换为事件
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
|
||||||
|
*/
|
||||||
|
void Clock_IncMS(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回指定年月的天数上限 (处理闰年 2 月)
|
||||||
|
* @param year 年 (0~99,代表 2000~2099)
|
||||||
|
* @param month 月 (1~12)
|
||||||
|
*/
|
||||||
|
u8 Days_In_Month(u8 year, u8 month);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 根据年月日重新计算星期,并写回 current_weekday
|
||||||
|
* @details 采用 Zeller 公式现算,不依赖 RTC 自身的星期寄存器,
|
||||||
|
* 避免手动改日期后星期跟着错乱;日期发生变化(自然进位或手动调节)时都要调用一次。
|
||||||
|
*/
|
||||||
|
void Clock_RecalcWeekday(void);
|
||||||
|
|
||||||
|
#endif // __CLOCK_H__
|
||||||
317
App/config.h
317
App/config.h
@@ -4,156 +4,179 @@
|
|||||||
#include "stc32g.h"
|
#include "stc32g.h"
|
||||||
#include "intrins.h"
|
#include "intrins.h"
|
||||||
|
|
||||||
|
// 重新定义基础数据类型,便于跨平台和简化代码书写
|
||||||
typedef unsigned char u8;
|
typedef unsigned char u8;
|
||||||
typedef unsigned int u16;
|
typedef unsigned int u16;
|
||||||
typedef unsigned long u32;
|
typedef unsigned long u32;
|
||||||
|
|
||||||
/* ====== 硬件引脚定义 (sbit) ====== */
|
// STC32G 端口内部上拉控制寄存器地址定义 (属于扩展 XSFR 空间)
|
||||||
sbit MOTOR = P2^5; // 振动马达控制端 (推挽输出,与 SPI SCLK 物理共享引脚)
|
#ifndef P3PU
|
||||||
sbit KEY_UP = P0^1; // 侧边按键 ▲ (高阻输入 + 上拉)
|
#define P3PU (*(unsigned char volatile xdata *)0xFE13)
|
||||||
sbit KEY_CONFIRM = P0^2; // 侧边按键 ■ (高阻输入 + 上拉)
|
|
||||||
sbit KEY_DOWN = P0^3; // 侧边按键 ▼ (高阻输入 + 上拉)
|
|
||||||
sbit KEY_SOS = P2^6; // 物理 SOS 求救按键 (高阻输入 + 上拉)
|
|
||||||
sbit RGB_DIN = P2^3; // FCOB 幻彩灯条 DIN 控制线
|
|
||||||
sbit SHUT = P2^2; // LR690L 射频芯片休眠/工作控制引脚 (低使能开启工作)
|
|
||||||
sbit SGM_CTRL = P3^4; // SGM3833 PMIC 屏幕负压供电使能脚
|
|
||||||
sbit LCD_RST = P1^0; // Truly AMOLED 屏幕复位脚 (RESET)
|
|
||||||
sbit LCD_DCX = P1^1; // Truly AMOLED 屏幕数据/指令选择脚 (D/C)
|
|
||||||
sbit LCD_SDI = P1^4; // Truly AMOLED 屏幕 SPI 数据脚 (MOSI)
|
|
||||||
sbit LCD_SCL = P1^5; // Truly AMOLED 屏幕 SPI 时钟脚 (SCLK)
|
|
||||||
sbit LCD_CS = P1^6; // Truly AMOLED 屏幕 SPI 片选脚 (CS)
|
|
||||||
|
|
||||||
/* ====== 系统状态 ====== */
|
|
||||||
typedef enum {
|
|
||||||
STATE_NORMAL,
|
|
||||||
STATE_ARMED,
|
|
||||||
STATE_DISARMED,
|
|
||||||
STATE_PAIR_MENU,
|
|
||||||
STATE_PAIR_WAIT,
|
|
||||||
STATE_PAIR_CONFIRM,
|
|
||||||
STATE_PAIR_SUCCESS,
|
|
||||||
STATE_PAIR_FAIL,
|
|
||||||
STATE_ALARM,
|
|
||||||
STATE_SOS_EMITTED,
|
|
||||||
STATE_SET_TIME, // 新增:时间调整编辑状态
|
|
||||||
STATE_IPC_BIND, // 新增:摄像机绑定状态
|
|
||||||
STATE_SLEEP // 新增:低功耗休眠挂起状态
|
|
||||||
} SystemState;
|
|
||||||
|
|
||||||
/* ====== 按键事件类型 ====== */
|
|
||||||
typedef enum {
|
|
||||||
KEY_EVENT_NONE,
|
|
||||||
KEY_EVENT_UP_CLICK,
|
|
||||||
KEY_EVENT_DOWN_CLICK,
|
|
||||||
KEY_EVENT_SOS_CLICK,
|
|
||||||
KEY_EVENT_UP_LONG,
|
|
||||||
KEY_EVENT_DOWN_LONG,
|
|
||||||
KEY_EVENT_SOS_LONG,
|
|
||||||
KEY_EVENT_UP_DOWN_COMB,
|
|
||||||
KEY_EVENT_CONFIRM_CLICK, // 新增:确认键短按
|
|
||||||
KEY_EVENT_CONFIRM_LONG // 新增:确认键长按
|
|
||||||
} KeyEvent;
|
|
||||||
|
|
||||||
/* ====== 事件优先级 (步骤4) ====== */
|
|
||||||
typedef enum {
|
|
||||||
EVENT_PRIORITY_LOW, // 0: 低优先级-系统内部事件
|
|
||||||
EVENT_PRIORITY_NORMAL, // 1: 正常优先级-用户交互
|
|
||||||
EVENT_PRIORITY_HIGH, // 2: 高优先级-报警事件
|
|
||||||
EVENT_PRIORITY_URGENT // 3: 紧急优先级-SOS
|
|
||||||
} EventPriority;
|
|
||||||
|
|
||||||
/* ====== 系统事件 (步骤4) ====== */
|
|
||||||
typedef struct {
|
|
||||||
KeyEvent key_event; // 按键事件类型
|
|
||||||
EventPriority priority; // 事件优先级
|
|
||||||
u32 extra_data; // 额外数据(射频地址等)
|
|
||||||
u8 extra_size; // 额外数据有效字节数(0~4)
|
|
||||||
} SystemEvent;
|
|
||||||
|
|
||||||
/* ====== 事件处理返回值 (步骤5) ====== */
|
|
||||||
typedef enum {
|
|
||||||
EVENT_HANDLED, // 事件已处理,停止分发
|
|
||||||
EVENT_IGNORED // 事件未处理,继续传递
|
|
||||||
} EventResult;
|
|
||||||
|
|
||||||
/* ====== 应用ID枚举 (步骤2) ====== */
|
|
||||||
typedef enum {
|
|
||||||
APP_ID_CLOCK, // 0: 待机时钟
|
|
||||||
APP_ID_MENU, // 1: 主菜单
|
|
||||||
APP_ID_PAIR, // 2: 配对
|
|
||||||
APP_ID_ALARM, // 3: 传感器报警
|
|
||||||
APP_ID_SOS, // 4: 主动求救
|
|
||||||
APP_ID_MAX // 5: 应用数量上限
|
|
||||||
} AppID;
|
|
||||||
|
|
||||||
/* ====== 应用类型 (步骤5) ====== */
|
|
||||||
typedef enum {
|
|
||||||
APP_TYPE_FOREGROUND, // 前台应用:独占屏幕
|
|
||||||
APP_TYPE_BACKGROUND // 后台应用:无屏幕,监听事件
|
|
||||||
} AppType;
|
|
||||||
|
|
||||||
/* ====== 应用描述符 ====== */
|
|
||||||
struct WristbandApp;
|
|
||||||
typedef struct WristbandApp WristbandApp;
|
|
||||||
|
|
||||||
typedef void (*AppStartFunc)(void);
|
|
||||||
typedef void (*AppRunFunc)(void);
|
|
||||||
typedef void (*AppCloseFunc)(void);
|
|
||||||
typedef EventResult (*AppEventFunc)(SystemEvent *evt);
|
|
||||||
|
|
||||||
struct WristbandApp {
|
|
||||||
AppID app_id;
|
|
||||||
AppType app_type;
|
|
||||||
char *app_name;
|
|
||||||
void (*OnStart)(void);
|
|
||||||
void (*onRun)(void);
|
|
||||||
void (*onClose)(void);
|
|
||||||
EventResult (*onEvent)(SystemEvent *evt);
|
|
||||||
u8 is_active; // 是否当前活跃
|
|
||||||
u8 is_running; // 是否允许运行 onRun
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ====== 传感器存储结构 ====== */
|
|
||||||
typedef struct {
|
|
||||||
unsigned char is_used;
|
|
||||||
unsigned char addr[3];
|
|
||||||
unsigned char type;
|
|
||||||
unsigned char zone;
|
|
||||||
unsigned char name_gbk[16];
|
|
||||||
} Sensor_Slot;
|
|
||||||
|
|
||||||
/* ====== 全局变量 extern 声明 ====== */
|
|
||||||
extern SystemState current_state;
|
|
||||||
extern u8 current_hour;
|
|
||||||
extern u8 current_min;
|
|
||||||
extern u8 current_sec;
|
|
||||||
extern volatile u8 clock_updated;
|
|
||||||
|
|
||||||
extern u8 menu_select;
|
|
||||||
extern u32 captured_addr;
|
|
||||||
extern u8 captured_type;
|
|
||||||
extern u8 alarm_sensor_slot;
|
|
||||||
|
|
||||||
extern Sensor_Slot xdata sensor_list[16];
|
|
||||||
extern char *code sensor_names_fr[5];
|
|
||||||
|
|
||||||
|
|
||||||
extern volatile u16 ms_tick;
|
|
||||||
extern volatile u16 inactivity_timer; // 无操作闲置计时器
|
|
||||||
extern volatile u32 pair_timeout_ms;
|
|
||||||
extern volatile u16 alarm_timer_ms;
|
|
||||||
extern volatile u16 motor_timer_ms;
|
|
||||||
extern volatile bit alarm_flash_flag;
|
|
||||||
|
|
||||||
extern volatile u16 key_scan_timer;
|
|
||||||
extern volatile u16 key_up_hold;
|
|
||||||
extern volatile u16 key_down_hold;
|
|
||||||
extern volatile u16 key_confirm_hold;
|
|
||||||
extern volatile u16 key_sos_hold;
|
|
||||||
extern volatile u16 comb_hold;
|
|
||||||
|
|
||||||
#define MAIN_Fosc 24000000UL
|
|
||||||
#define CLONED_ADDR 0x37A86UL
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* ====== 硬件引脚定义 (sbit) ====== */
|
||||||
|
// 振动马达控制端 (推挽输出,与 SPI SCLK 物理共享引脚)
|
||||||
|
sbit MOTOR = P2^5;
|
||||||
|
// 侧边按键 ▲ (高阻输入 + 上拉),按下为低电平
|
||||||
|
sbit KEY_UP = P0^1;
|
||||||
|
// 侧边按键 ■ (高阻输入 + 上拉),按下为低电平
|
||||||
|
sbit KEY_CONFIRM = P0^2;
|
||||||
|
// 侧边按键 ▼ (高阻输入 + 上拉),按下为低电平
|
||||||
|
sbit KEY_DOWN = P0^3;
|
||||||
|
// 电源保持控制引脚 (1:锁存保持电源, 0:切断电源关机)
|
||||||
|
#define PWR_HOLD P00
|
||||||
|
// PCF8563 RTC 实时时钟芯片 I2C 通信引脚
|
||||||
|
#define RTC_SCL P32
|
||||||
|
#define RTC_SDA P33
|
||||||
|
// 物理 SOS 求救按键 (高阻输入 + 上拉),按下为低电平
|
||||||
|
sbit KEY_SOS = P2^6;
|
||||||
|
// FCOB 幻彩灯条 DIN 数据控制线
|
||||||
|
sbit RGB_DIN = P2^3;
|
||||||
|
// LR690L 射频芯片休眠/工作控制引脚 (0:开启工作, 1:关闭休眠)
|
||||||
|
sbit SHUT = P3^5;
|
||||||
|
// LHS114TC-IF03 屏幕 LEDA 白色背光开启引脚 (1点亮,0熄灭)
|
||||||
|
sbit SGM_CTRL = P1^7;
|
||||||
|
// LHS114TC-IF03 屏幕复位脚 (RESET),低电平复位
|
||||||
|
sbit LCD_RST = P1^0;
|
||||||
|
// LHS114TC-IF03 屏幕数据/指令选择脚 (D/C,1:数据, 0:命令)
|
||||||
|
sbit LCD_DCX = P1^1;
|
||||||
|
// LHS114TC-IF03 屏幕 SPI 数据脚 (MOSI/SDA)
|
||||||
|
sbit LCD_SDI = P1^4;
|
||||||
|
// LHS114TC-IF03 屏幕 SPI 时钟脚 (SCLK)
|
||||||
|
sbit LCD_SCL = P1^5;
|
||||||
|
// LHS114TC-IF03 屏幕 SPI 片选脚 (CS),低电平有效
|
||||||
|
sbit LCD_CS = P1^6;
|
||||||
|
|
||||||
|
// 充电检测引脚 (TP4054 CHRG 开漏输出,低电平=充电中)
|
||||||
|
// 硬件连接:TP4054 CHRG → DET → MCU (根据原理图,需 PCB 确认实际引脚)
|
||||||
|
// 架构文档标注为 P5.4,原理图标注为 P1.7 附近,暂用 P5.4,可根据实测调整
|
||||||
|
sbit CHRG_DET = P5^4;
|
||||||
|
|
||||||
|
/* ====== 系统运行状态枚举 ====== */
|
||||||
|
typedef enum {
|
||||||
|
STATE_NORMAL, // 正常时钟待机状态
|
||||||
|
STATE_ARMED, // 布防状态 (监测传感器警报)
|
||||||
|
STATE_DISARMED, // 撤防状态 (忽略普通传感器警报)
|
||||||
|
STATE_PAIR_MENU, // 传感器对码/配置主菜单状态
|
||||||
|
STATE_PAIR_WAIT, // 等待传感器射频信号状态 (对码搜寻中)
|
||||||
|
STATE_PAIR_CONFIRM, // 捕获到信号,进入防区微调确认状态
|
||||||
|
STATE_PAIR_SUCCESS, // 对码成功反馈状态
|
||||||
|
STATE_PAIR_FAIL, // 对码失败 (超时等) 反馈状态
|
||||||
|
STATE_ALARM, // 传感器入侵报警状态
|
||||||
|
STATE_SOS_EMITTED, // 处于 SOS 紧急求救发射状态
|
||||||
|
STATE_SET_TIME, // 时间调整编辑状态 (修改小时、分钟)
|
||||||
|
STATE_IPC_BIND, // 摄像机/IPC 绑定发射信号状态
|
||||||
|
STATE_SLEEP // 低功耗休眠挂起状态
|
||||||
|
} SystemState;
|
||||||
|
|
||||||
|
/* ====== 按键事件类型枚举 ====== */
|
||||||
|
typedef enum {
|
||||||
|
KEY_EVENT_NONE, // 无按键事件
|
||||||
|
KEY_EVENT_UP_CLICK, // ▲ 键短按释放
|
||||||
|
KEY_EVENT_DOWN_CLICK, // ▼ 键短按释放
|
||||||
|
KEY_EVENT_SOS_CLICK, // SOS 键短按释放
|
||||||
|
KEY_EVENT_UP_LONG, // ▲ 键长按事件 (触发布撤防切换)
|
||||||
|
KEY_EVENT_DOWN_LONG, // ▼ 键长按事件 (菜单返回)
|
||||||
|
KEY_EVENT_SOS_LONG, // SOS 键长按事件
|
||||||
|
KEY_EVENT_UP_DOWN_COMB, // ▲ 与 ▼ 双键组合按下事件
|
||||||
|
KEY_EVENT_CONFIRM_CLICK, // ■ 确认键短按事件 (保存设置或进入子菜单)
|
||||||
|
KEY_EVENT_CONFIRM_LONG, // ■ 确认键长按事件 (打开主菜单)
|
||||||
|
KEY_EVENT_POWER_OFF, // 长按 KEY_DOWN 开关机触发事件
|
||||||
|
KEY_EVENT_SECOND_TICK // 整秒滴答事件,每秒派发一次,各 App 自行决定是否用它累计闲置时长
|
||||||
|
} KeyEvent;
|
||||||
|
|
||||||
|
/* ====== 事件优先级枚举 ====== */
|
||||||
|
typedef enum {
|
||||||
|
EVENT_PRIORITY_LOW, // 0: 低优先级:系统内部事件,如定时更新
|
||||||
|
EVENT_PRIORITY_NORMAL, // 1: 正常优先级:大部分用户交互事件
|
||||||
|
EVENT_PRIORITY_HIGH, // 2: 高优先级:报警相关事件,插队到队首
|
||||||
|
EVENT_PRIORITY_URGENT // 3: 紧急优先级:SOS等,插队到队首
|
||||||
|
} EventPriority;
|
||||||
|
|
||||||
|
/* ====== 系统事件结构体定义 ====== */
|
||||||
|
typedef struct {
|
||||||
|
KeyEvent key_event; // 关联的按键事件类型
|
||||||
|
EventPriority priority; // 事件优先级
|
||||||
|
u32 extra_data; // 随事件携带的额外数据 (如 24 位射频源地址等)
|
||||||
|
u8 extra_size; // 额外数据的有效字节大小 (0~4)
|
||||||
|
} SystemEvent;
|
||||||
|
|
||||||
|
/* ====== 事件分发处理结果枚举 ====== */
|
||||||
|
typedef enum {
|
||||||
|
EVENT_HANDLED, // 事件已成功处理,不再继续向后传递
|
||||||
|
EVENT_IGNORED // 事件未被处理,允许继续传递给后台应用或丢弃
|
||||||
|
} EventResult;
|
||||||
|
|
||||||
|
/* ====== 应用程序 ID 枚举 ====== */
|
||||||
|
typedef enum {
|
||||||
|
APP_ID_HOME, // 待机时钟主页应用程序 (开机默认首屏)
|
||||||
|
APP_ID_MENU, // 系统设置主菜单应用程序 (顶层 4 项列表)
|
||||||
|
APP_ID_SET_CLOCK, // 时间设置应用程序 (menu_app 子应用)
|
||||||
|
APP_ID_PAIR, // 无线传感器对码学习应用程序 (menu_app 子应用)
|
||||||
|
APP_ID_IPC_BIND, // 摄像头一键绑定应用程序 (menu_app 子应用)
|
||||||
|
APP_ID_ALARM, // 报警接收与波形反馈应用程序
|
||||||
|
APP_ID_SOS, // 紧急呼救与无线发射应用程序
|
||||||
|
APP_ID_MAX // 应用程序最大数量上限,用于数组大小
|
||||||
|
} AppID;
|
||||||
|
|
||||||
|
/* ====== 应用程序类型枚举 ====== */
|
||||||
|
typedef enum {
|
||||||
|
APP_TYPE_FOREGROUND, // 前台应用:独占 OLED 屏幕显示并优先接收按键/射频事件
|
||||||
|
APP_TYPE_BACKGROUND // 后台应用:无屏幕显示,静默监听前台未处理的系统事件
|
||||||
|
} AppType;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ====== 无线传感器 Flash 存储槽位结构体 ====== */
|
||||||
|
typedef struct {
|
||||||
|
unsigned char is_used; // 槽位是否已使用标志 (0x01:已启用, 其他:空闲)
|
||||||
|
unsigned char addr[3]; // 绑定的 24 位无线传感器 EV1527 硬件地址
|
||||||
|
unsigned char type; // 传感器设备类型 (0:门磁, 1:红外PIR, 2:烟感, 3:燃气, 4:水浸)
|
||||||
|
unsigned char zone; // 防区编号 (例如 1~99,0 代表 24小时紧急防区)
|
||||||
|
unsigned char name_gbk[16];// 传感器的自定义名称 (GBK 编码,用于屏幕显示)
|
||||||
|
} Sensor_Slot;
|
||||||
|
|
||||||
|
/* ====== 全局变量 extern 外部声明 ====== */
|
||||||
|
extern SystemState current_state; // 系统当前整体运行状态
|
||||||
|
extern u8 current_hour; // 当前系统时间:小时
|
||||||
|
extern u8 current_min; // 当前系统时间:分钟
|
||||||
|
extern u8 current_sec; // 当前系统时间:秒
|
||||||
|
extern u8 current_day; // 当前系统日期:日
|
||||||
|
extern u8 current_month; // 当前系统日期:月
|
||||||
|
extern u8 current_year; // 当前系统日期:年 (0~99,代表 2000~2099)
|
||||||
|
extern u8 current_weekday; // 当前系统日期:星期 (0=周日, ..., 6=周六)
|
||||||
|
extern volatile u8 clock_updated; // 时钟分钟更新标志 (1表示需要刷新待机页时间)
|
||||||
|
extern volatile bit second_tick_flag; // 整秒滴答标志位,每秒置 1,由主循环转换为 KEY_EVENT_SECOND_TICK 事件
|
||||||
|
|
||||||
|
extern u8 menu_select; // 当前菜单高亮的选中项索引
|
||||||
|
extern u32 captured_addr; // 对码或报警时,当前捕获到的传感器 24 位射频地址
|
||||||
|
extern u8 captured_type; // 正在配对的传感器类型
|
||||||
|
extern u8 alarm_sensor_slot; // 当前触发警报的传感器在数据库列表中的槽位索引
|
||||||
|
|
||||||
|
extern Sensor_Slot xdata sensor_list[16];// 传感器数据库数组 (存储在 xdata 区,共 16 个槽位)
|
||||||
|
|
||||||
|
// 系统滴答与定时计数器 (均为 1ms 中断服务中更新)
|
||||||
|
extern volatile u16 ms_tick; // 毫秒滴答计数器 (0~999循环)
|
||||||
|
extern volatile u32 pair_timeout_ms; // 对码等待搜寻超时毫秒计数器
|
||||||
|
extern volatile u16 alarm_timer_ms; // 报警振动与闪烁定时计数器
|
||||||
|
extern volatile u16 motor_timer_ms; // 马达单次震动定时计时器
|
||||||
|
extern volatile bit alarm_flash_flag; // 报警屏幕红框闪烁的周期标志位 (0或1)
|
||||||
|
|
||||||
|
// 按键长按与消抖状态机计数器
|
||||||
|
extern volatile u16 key_scan_timer; // 按键扫描 10ms 分频计数器
|
||||||
|
extern volatile u16 key_up_hold; // ▲ 键持续按下计时器
|
||||||
|
extern volatile u16 key_down_hold; // ▼ 键持续按下计时器
|
||||||
|
extern volatile u16 key_confirm_hold; // ■ 确认键持续按下计时器
|
||||||
|
extern volatile u16 key_sos_hold; // SOS 键持续按下计时器
|
||||||
|
extern volatile u16 comb_hold; // 组合键持续按下计时器
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
#define MAIN_Fosc 24000000UL // STC32G 主频工作频率定义 (24.0 MHz)
|
||||||
|
#define CLONED_ADDR 0x37A86UL // 手环自身克隆发射的默认 24 位射频出厂 ID 地址
|
||||||
|
|
||||||
|
// 硬件模块使能开关:某些 PCB 版本可能不焊接 PCF8563 RTC 芯片,置 0 后完全跳过
|
||||||
|
// I2C 探测/读写与开机诊断扫描,只依赖软件时钟 (Timer1 驱动的 current_hour/min/sec 等),
|
||||||
|
// 不写入/不写回 RTC,断电后时间不保留,但整机功能不受影响。
|
||||||
|
#define USE_PCF8563 1
|
||||||
|
|
||||||
|
#endif // __CONFIG_H__
|
||||||
|
|||||||
209
App/database.c
209
App/database.c
@@ -1,140 +1,207 @@
|
|||||||
#include "database.h"
|
#include "database.h"
|
||||||
|
#include "language.h"
|
||||||
|
|
||||||
#define IAP_CMD_READ 1
|
// 内部 IAP 命令寄存器指令定义
|
||||||
#define IAP_CMD_WRITE 2
|
#define IAP_CMD_READ 1 // IAP 字节读指令
|
||||||
#define IAP_CMD_ERASE 3
|
#define IAP_CMD_WRITE 2 // IAP 字节写指令
|
||||||
|
#define IAP_CMD_ERASE 3 // IAP 扇区擦除指令
|
||||||
|
|
||||||
// 定义全局常量数组
|
// 传感器类型简称的字符串 ID,分别对应类型 0~4,实际文案由 Lang_Get() 按当前语言取出
|
||||||
char *code sensor_names_fr[5] = {
|
const StringID sensor_type_name_ids[5] = {
|
||||||
"PORTE",
|
STR_NAME_PORTE, // 0: 门磁
|
||||||
"PIR",
|
STR_NAME_PIR, // 1: 红外探测器
|
||||||
"FUMEE",
|
STR_NAME_FUMEE, // 2: 烟雾传感器
|
||||||
"GAZ",
|
STR_NAME_GAZ, // 3: 燃气泄漏传感器
|
||||||
"EAU"
|
STR_NAME_EAU // 4: 漏水/水浸传感器
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 紧随 sensor_list 之后存放的 1 字节界面语言设置在 Flash 扇区内的偏移地址
|
||||||
|
#define LANGUAGE_FLASH_ADDR ((u16)sizeof(sensor_list))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭 IAP 寄存器控制并清理地址
|
||||||
|
* @details 每次 IAP 闪存操作后调用,确保安全,防止意外指针写入损坏 EEPROM 中的关键配置数据。
|
||||||
|
*/
|
||||||
void IAP_Disable(void)
|
void IAP_Disable(void)
|
||||||
{
|
{
|
||||||
IAP_CONTR = 0;
|
IAP_CONTR = 0; // 关闭 IAP 功能,禁用触发
|
||||||
IAP_CMD = 0;
|
IAP_CMD = 0; // 清除内部命令
|
||||||
IAP_TRIG = 0;
|
IAP_TRIG = 0; // 清零触发寄存器
|
||||||
IAP_ADDRH = 0xff;
|
IAP_ADDRH = 0xff; // 地址指针高八位置无效,防止意外指向
|
||||||
IAP_ADDRL = 0xff;
|
IAP_ADDRL = 0xff; // 地址指针低八位置无效
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取指定 EEPROM 偏置地址处的 1 字节数据
|
||||||
|
* @param addr IAP 操作的目标物理地址
|
||||||
|
* @return u8 读取到的数据
|
||||||
|
*/
|
||||||
u8 IAP_ReadByte(u16 addr)
|
u8 IAP_ReadByte(u16 addr)
|
||||||
{
|
{
|
||||||
IAP_CONTR = 0x80;
|
IAP_CONTR = 0x80; // 开启 IAP/EEPROM 功能,设置等待时间
|
||||||
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL);
|
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 设置闪存读写等待时间参数 (以MHz为单位,例如24MHz即24)
|
||||||
IAP_CMD = IAP_CMD_READ;
|
IAP_CMD = IAP_CMD_READ; // 发送读字节命令
|
||||||
IAP_ADDRL = (u8)addr;
|
IAP_ADDRL = (u8)addr; // 加载目标低八位地址
|
||||||
IAP_ADDRH = (u8)(addr >> 8);
|
IAP_ADDRH = (u8)(addr >> 8); // 加载目标高八位地址
|
||||||
IAP_ADDRE = 0;
|
IAP_ADDRE = 0; // 重置拓展物理地址 (STC32G内部EEPROM通常不需要)
|
||||||
IAP_TRIG = 0x5a;
|
IAP_TRIG = 0x5a; // 触发写序列第一步密码
|
||||||
IAP_TRIG = 0xa5;
|
IAP_TRIG = 0xa5; // 触发写序列第二步密码 (必须连续送入,才能使能擦写)
|
||||||
_nop_(); _nop_(); _nop_(); _nop_();
|
_nop_(); _nop_(); _nop_(); _nop_(); // CPU 等待 4 个周期直至读取操作完成
|
||||||
IAP_Disable();
|
IAP_Disable(); // 读完后立即关闭 IAP 控制
|
||||||
return IAP_DATA;
|
return IAP_DATA; // 返回读取出的数据寄存器字节
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 在 EEPROM 指定地址写入 1 字节数据
|
||||||
|
* @param addr 目标物理地址
|
||||||
|
* @param dat 要写入的字节
|
||||||
|
*/
|
||||||
void IAP_WriteByte(u16 addr, u8 dat)
|
void IAP_WriteByte(u16 addr, u8 dat)
|
||||||
{
|
{
|
||||||
IAP_CONTR = 0x80;
|
IAP_CONTR = 0x80; // 开启 IAP/EEPROM 功能
|
||||||
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL);
|
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 匹配工作频率对应的读写周期
|
||||||
IAP_CMD = IAP_CMD_WRITE;
|
IAP_CMD = IAP_CMD_WRITE; // 发送写字节命令
|
||||||
IAP_ADDRL = (u8)addr;
|
IAP_ADDRL = (u8)addr; // 送入低字节地址
|
||||||
IAP_ADDRH = (u8)(addr >> 8);
|
IAP_ADDRH = (u8)(addr >> 8); // 送入高字节地址
|
||||||
IAP_ADDRE = 0;
|
IAP_ADDRE = 0; // 送入拓展地址
|
||||||
IAP_DATA = dat;
|
IAP_DATA = dat; // 装载待写入的字节数据
|
||||||
IAP_TRIG = 0x5a;
|
IAP_TRIG = 0x5a; // 送入触发密码 0x5A
|
||||||
IAP_TRIG = 0xa5;
|
IAP_TRIG = 0xa5; // 送入触发密码 0xA5 启动硬件写
|
||||||
_nop_(); _nop_(); _nop_(); _nop_();
|
_nop_(); _nop_(); _nop_(); _nop_(); // 等待写操作完成
|
||||||
IAP_Disable();
|
IAP_Disable(); // 关闭 IAP 控制器
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 擦除指定的 512 字节 EEPROM 物理扇区
|
||||||
|
* @param addr 扇区首地址
|
||||||
|
*/
|
||||||
void IAP_EraseSector(u16 addr)
|
void IAP_EraseSector(u16 addr)
|
||||||
{
|
{
|
||||||
IAP_CONTR = 0x80;
|
IAP_CONTR = 0x80; // 开启 IAP 功能
|
||||||
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL);
|
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 匹配工作频率的擦除时间周期
|
||||||
IAP_CMD = IAP_CMD_ERASE;
|
IAP_CMD = IAP_CMD_ERASE; // 发送扇区擦除指令
|
||||||
IAP_ADDRL = (u8)addr;
|
IAP_ADDRL = (u8)addr; // 送入低字节地址
|
||||||
IAP_ADDRH = (u8)(addr >> 8);
|
IAP_ADDRH = (u8)(addr >> 8); // 送入高字节地址
|
||||||
IAP_ADDRE = 0;
|
IAP_ADDRE = 0; // 拓展地址清零
|
||||||
IAP_TRIG = 0x5a;
|
IAP_TRIG = 0x5a; // 触发密码 0x5A
|
||||||
IAP_TRIG = 0xa5;
|
IAP_TRIG = 0xa5; // 触发密码 0xA5 启动硬件擦除
|
||||||
_nop_(); _nop_(); _nop_(); _nop_();
|
_nop_(); _nop_(); _nop_(); _nop_(); // 等待擦除完成 (扇区全部内容被初始化为 0xFF)
|
||||||
IAP_Disable();
|
IAP_Disable(); // 关闭 IAP 操作以保护数据
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 EEPROM 的扇区 254 中加载数据库
|
||||||
|
* @details 逐字节读取 Flash 物理数据,并直接通过指针拷贝装入 RAM 中的全局 `sensor_list` 数组;
|
||||||
|
* 随后读取紧随其后的 1 字节界面语言设置,恢复上次保存的语言选择。
|
||||||
|
*/
|
||||||
void Load_Database(void)
|
void Load_Database(void)
|
||||||
{
|
{
|
||||||
u16 addr = 0;
|
u16 addr = 0; // EEPROM 内部偏移地址从 0 开始
|
||||||
u8 *ptr = (u8 *)&sensor_list;
|
u8 *ptr = (u8 *)&sensor_list; // 获取传感器 RAM 数组的首地址指针
|
||||||
u16 size = sizeof(sensor_list);
|
u16 size = sizeof(sensor_list); // 获取结构体数组的全部占用尺寸
|
||||||
u16 i;
|
u16 i;
|
||||||
|
u8 lang_byte;
|
||||||
for (i = 0; i < size; i++) {
|
for (i = 0; i < size; i++) {
|
||||||
ptr[i] = IAP_ReadByte(addr++);
|
ptr[i] = IAP_ReadByte(addr++); // 循环读取字节并还原到 RAM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取语言设置字节 (首次开机、从未保存过时 Flash 为擦除态 0xFF,Lang_Init 会自动回退为 LANG_FR)
|
||||||
|
lang_byte = IAP_ReadByte(LANGUAGE_FLASH_ADDR);
|
||||||
|
Lang_Init((Language)lang_byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 RAM 中的最新传感器列表数据持久化保存到 EEPROM
|
||||||
|
* @details 因为 Flash 写操作不能把 0 写回 1,所以必须先调用 `IAP_EraseSector` 擦除整个扇区,
|
||||||
|
* 接着遍历整个数据结构,逐字节写入最新的 RAM 内容,最后追加写入当前界面语言设置字节。
|
||||||
|
*/
|
||||||
void Save_Database(void)
|
void Save_Database(void)
|
||||||
{
|
{
|
||||||
u16 addr = 0;
|
u16 addr = 0; // EEPROM 物理首地址
|
||||||
u8 *ptr = (u8 *)&sensor_list;
|
u8 *ptr = (u8 *)&sensor_list; // RAM 首指针
|
||||||
u16 size = sizeof(sensor_list);
|
u16 size = sizeof(sensor_list); // 总字节量
|
||||||
u16 i;
|
u16 i;
|
||||||
IAP_EraseSector(0);
|
IAP_EraseSector(0); // 擦除首扇区 (扇区 254,大小 512 字节足够存放)
|
||||||
for (i = 0; i < size; i++) {
|
for (i = 0; i < size; i++) {
|
||||||
IAP_WriteByte(addr++, ptr[i]);
|
IAP_WriteByte(addr++, ptr[i]); // 循环写入字节数据
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 紧随传感器数据之后追加写入当前界面语言设置
|
||||||
|
IAP_WriteByte(LANGUAGE_FLASH_ADDR, (u8)Lang_Current());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 新增或更新已对码的传感器,并保存数据库
|
||||||
|
* @param addr 24位射频地址
|
||||||
|
* @param type 传感器类型索引 (0~4)
|
||||||
|
* @param zone 自定义分配的防区号 (1~99, 0)
|
||||||
|
*/
|
||||||
void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone)
|
void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone)
|
||||||
{
|
{
|
||||||
u8 i;
|
u8 i;
|
||||||
u8 slot_to_use = 15;
|
u8 slot_to_use = 15; // 默认使用最后一槽
|
||||||
|
|
||||||
|
// 遍历现有的 16 个槽位,寻找匹配的地址或者空闲的槽位
|
||||||
for (i = 0; i < 16; i++) {
|
for (i = 0; i < 16; i++) {
|
||||||
if (sensor_list[i].is_used == 0x01) {
|
if (sensor_list[i].is_used == 0x01) {
|
||||||
|
// 解析该槽位中存储的 24 位传感器地址
|
||||||
u32 existing_addr = ((u32)sensor_list[i].addr[0] << 16) |
|
u32 existing_addr = ((u32)sensor_list[i].addr[0] << 16) |
|
||||||
((u32)sensor_list[i].addr[1] << 8) |
|
((u32)sensor_list[i].addr[1] << 8) |
|
||||||
sensor_list[i].addr[2];
|
sensor_list[i].addr[2];
|
||||||
|
// 如果地址已经存在,说明这是要修改现有槽位的防区或类型
|
||||||
if (existing_addr == addr) {
|
if (existing_addr == addr) {
|
||||||
slot_to_use = i;
|
slot_to_use = i; // 使用该槽位进行更新
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 如果找到未启用的空闲槽位,暂定使用它
|
||||||
slot_to_use = i;
|
slot_to_use = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sensor_list[slot_to_use].is_used = 0x01;
|
|
||||||
sensor_list[slot_to_use].addr[0] = (u8)(addr >> 16);
|
|
||||||
sensor_list[slot_to_use].addr[1] = (u8)(addr >> 8);
|
|
||||||
sensor_list[slot_to_use].addr[2] = (u8)addr;
|
|
||||||
sensor_list[slot_to_use].type = type;
|
|
||||||
sensor_list[slot_to_use].zone = zone;
|
|
||||||
|
|
||||||
|
// 配置传感器槽位数据
|
||||||
|
sensor_list[slot_to_use].is_used = 0x01; // 标记槽位为已启用状态
|
||||||
|
sensor_list[slot_to_use].addr[0] = (u8)(addr >> 16); // 存入地址高字节
|
||||||
|
sensor_list[slot_to_use].addr[1] = (u8)(addr >> 8); // 存入地址中字节
|
||||||
|
sensor_list[slot_to_use].addr[2] = (u8)addr; // 存入地址低字节
|
||||||
|
sensor_list[slot_to_use].type = type; // 记录传感器硬件类型
|
||||||
|
sensor_list[slot_to_use].zone = zone; // 记录对应的防区号
|
||||||
|
|
||||||
|
// 生成该槽位传感器的名称 (取当前界面语言下的简称)
|
||||||
|
{
|
||||||
|
const char *type_name = Lang_Get(sensor_type_name_ids[type]);
|
||||||
for (i = 0; i < 15; i++) {
|
for (i = 0; i < 15; i++) {
|
||||||
sensor_list[slot_to_use].name_gbk[i] = sensor_names_fr[type][i];
|
// 从当前语言名称字符串中复制对应名字的字符
|
||||||
if (sensor_names_fr[type][i] == '\0') break;
|
sensor_list[slot_to_use].name_gbk[i] = type_name[i];
|
||||||
|
if (type_name[i] == '\0') break; // 遇零终止
|
||||||
}
|
}
|
||||||
sensor_list[slot_to_use].name_gbk[15] = '\0';
|
}
|
||||||
Save_Database();
|
sensor_list[slot_to_use].name_gbk[15] = '\0'; // 尾部安全截断,防止字符越界
|
||||||
|
|
||||||
|
Save_Database(); // 调用保存函数,强制持久化写回 IAP Flash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测输入的 24 位射频地址是否已被当前绑定的任何槽位匹配
|
||||||
|
* @param addr 传入待匹配的 24 位射频地址
|
||||||
|
* @param out_slot_index 若匹配成功,输出槽位的索引号指针
|
||||||
|
* @return bit 1-匹配成功,0-该地址尚未被本系统学习绑定
|
||||||
|
*/
|
||||||
bit Check_Sensor_ID(u32 addr, u8 *out_slot_index)
|
bit Check_Sensor_ID(u32 addr, u8 *out_slot_index)
|
||||||
{
|
{
|
||||||
u8 i;
|
u8 i;
|
||||||
for (i = 0; i < 16; i++) {
|
for (i = 0; i < 16; i++) {
|
||||||
|
// 仅比对正在使用中的槽位
|
||||||
if (sensor_list[i].is_used == 0x01) {
|
if (sensor_list[i].is_used == 0x01) {
|
||||||
u32 slot_addr = ((u32)sensor_list[i].addr[0] << 16) |
|
u32 slot_addr = ((u32)sensor_list[i].addr[0] << 16) |
|
||||||
((u32)sensor_list[i].addr[1] << 8) |
|
((u32)sensor_list[i].addr[1] << 8) |
|
||||||
sensor_list[i].addr[2];
|
sensor_list[i].addr[2];
|
||||||
if (slot_addr == addr) {
|
if (slot_addr == addr) {
|
||||||
*out_slot_index = i;
|
*out_slot_index = i; // 写入输出变量
|
||||||
return 1;
|
return 1; // 匹配成功返回真
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0; // 遍历结束未找到,返回假
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,75 @@
|
|||||||
#define __DATABASE_H__
|
#define __DATABASE_H__
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "strings.h"
|
||||||
|
|
||||||
// 底层 IAP 接口
|
// 传感器类型简称的字符串 ID 表 (索引 0~4 对应门磁/PIR/烟感/燃气/水浸),供 UI 层按当前语言取用
|
||||||
|
extern const StringID sensor_type_name_ids[5];
|
||||||
|
|
||||||
|
/* ====== 底层 IAP (在应用编程 Flash) 读写驱动接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 禁用 IAP 模块,关闭 Flash 擦写控制器
|
||||||
|
* @details 每次完成 Flash 的读、写或擦除操作后,必须立即调用此函数,
|
||||||
|
* 清除寄存器状态以防止因外部强干扰触发 Flash 误擦写。
|
||||||
|
*/
|
||||||
void IAP_Disable(void);
|
void IAP_Disable(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从指定 IAP Flash 地址读取一个字节
|
||||||
|
* @param addr 16位目标 Flash 内部偏移物理地址 (对应扇区内偏移)
|
||||||
|
* @return u8 读取到的 8 位数据字节
|
||||||
|
*/
|
||||||
u8 IAP_ReadByte(u16 addr);
|
u8 IAP_ReadByte(u16 addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 向指定 IAP Flash 地址写入一个字节数据
|
||||||
|
* @param addr 16位目标 Flash 内部偏移物理地址
|
||||||
|
* @param dat 要写入的 8 位数据字节
|
||||||
|
* @note STC32G 的 EEPROM/IAP 写入只能把 1 写为 0,若要把 0 改为 1,必须先执行扇区擦除操作。
|
||||||
|
*/
|
||||||
void IAP_WriteByte(u16 addr, u8 dat);
|
void IAP_WriteByte(u16 addr, u8 dat);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 擦除指定的 Flash 物理扇区 (512字节)
|
||||||
|
* @param addr 目标扇区内的任意物理字节地址 (传入首地址即可)
|
||||||
|
* @note STC32G EEPROM 的擦除以扇区 (512 字节) 为基本单位。擦除后,该扇区内所有字节的数据均恢复为 0xFF。
|
||||||
|
*/
|
||||||
void IAP_EraseSector(u16 addr);
|
void IAP_EraseSector(u16 addr);
|
||||||
|
|
||||||
// 数据库持久化接口
|
|
||||||
|
/* ====== 传感器数据库业务持久化接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 Flash 扇区 254 (0xFE0000) 载入全部传感器槽位配置至 RAM 变量
|
||||||
|
* @details 系统初始化时调用,将 EEPROM 中的传感器绑定数据拷贝到 RAM 中的 `sensor_list` 数组,
|
||||||
|
* 并读取紧随其后存放的 1 字节界面语言设置,调用 `Lang_Init()` 恢复上次选择的语言。
|
||||||
|
*/
|
||||||
void Load_Database(void);
|
void Load_Database(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 RAM 中的全部传感器配置保存/写入到 Flash 扇区中
|
||||||
|
* @details 在完成对码、添加新传感器、微调防区号或切换界面语言后调用,该函数会先擦除第 254 扇区,
|
||||||
|
* 然后逐字节写入当前最新的 `sensor_list` 数组,并在其后追加写入当前界面语言 (`Lang_Current()`)。
|
||||||
|
*/
|
||||||
void Save_Database(void);
|
void Save_Database(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加一个新的传感器或更新已有的传感器防区、类型和名称
|
||||||
|
* @param addr 捕获到的 24 位无线传感器 EV1527 射频地址
|
||||||
|
* @param type 传感器类型 (0:门磁, 1:红外PIR, 2:烟感, 3:燃气, 4:水浸)
|
||||||
|
* @param zone 自定义分配的防区号 (1~99, 0代表24小时紧急防区)
|
||||||
|
* @details 如果检测到该地址已在列表,则覆盖更新防区号并保存;否则在首个空闲槽位插入该传感器,
|
||||||
|
* 自动生成其法语名称字符串,并调用 `Save_Database()` 写入 Flash 中。
|
||||||
|
*/
|
||||||
void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone);
|
void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测特定射频地址是否已被绑定在传感器数据库中
|
||||||
|
* @param addr 接收到的 24 位无线传感器射频地址
|
||||||
|
* @param out_slot_index 输出参数指针:如果匹配成功,将写入该传感器在 `sensor_list` 数组中的索引槽位号 (0~15)
|
||||||
|
* @return bit 返回匹配结果。1 表示数据库中已存在该传感器;0 表示该传感器未登记 (未知设备)
|
||||||
|
*/
|
||||||
bit Check_Sensor_ID(u32 addr, u8 *out_slot_index);
|
bit Check_Sensor_ID(u32 addr, u8 *out_slot_index);
|
||||||
|
|
||||||
#endif
|
#endif // __DATABASE_H__
|
||||||
|
|||||||
307
App/event.c
307
App/event.c
@@ -2,14 +2,21 @@
|
|||||||
#include "app_manager.h"
|
#include "app_manager.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
|
#include "xtell.h"
|
||||||
|
#include "../Drivers/adc.h"
|
||||||
|
|
||||||
|
// 定义事件环形队列的最大深度为 8
|
||||||
#define EVENT_QUEUE_DEPTH 8
|
#define EVENT_QUEUE_DEPTH 8
|
||||||
|
|
||||||
static SystemEvent idata event_queue[EVENT_QUEUE_DEPTH];
|
// 环形队列存储数组 (存放在单片机 xdata 拓展 RAM区中)
|
||||||
static u8 queue_head = 0;
|
static SystemEvent xdata event_queue[EVENT_QUEUE_DEPTH];
|
||||||
static u8 queue_tail = 0;
|
static u8 queue_head = 0; // 队首读指针
|
||||||
static u8 queue_count = 0;
|
static u8 queue_tail = 0; // 队尾写指针
|
||||||
|
static u8 queue_count = 0; // 当前队列内有效事件总数
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化/清空事件队列
|
||||||
|
*/
|
||||||
void EventQueue_Init(void)
|
void EventQueue_Init(void)
|
||||||
{
|
{
|
||||||
queue_head = 0;
|
queue_head = 0;
|
||||||
@@ -17,293 +24,245 @@ void EventQueue_Init(void)
|
|||||||
queue_count = 0;
|
queue_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将普通事件压入队尾
|
||||||
|
* @param evt 输入的系统事件
|
||||||
|
* @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;
|
EA = 0; // 关闭全局中断,保护临界区操作的原子性
|
||||||
event_queue[queue_tail] = evt;
|
event_queue[queue_tail] = evt; // 存入队尾
|
||||||
queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH;
|
queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH; // 尾指针向后移动,回环
|
||||||
queue_count++;
|
queue_count++; // 有效计数加 1
|
||||||
EA = 1;
|
EA = 1; // 恢复全局中断
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将高优先级/紧急事件强行插到队首
|
||||||
|
* @param evt 输入的系统事件
|
||||||
|
* @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;
|
EA = 0; // 屏蔽中断
|
||||||
|
// 头指针向前移动一位(带回环防溢出)
|
||||||
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;
|
event_queue[queue_head] = evt; // 存入腾出的队首位置
|
||||||
queue_count++;
|
queue_count++; // 计数加 1
|
||||||
EA = 1;
|
EA = 1; // 恢复中断
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从队首弹出一个缓冲事件
|
||||||
|
* @return SystemEvent 返回弹出的事件。若空返回空闲事件
|
||||||
|
*/
|
||||||
SystemEvent EventQueue_Pop(void)
|
SystemEvent EventQueue_Pop(void)
|
||||||
{
|
{
|
||||||
SystemEvent evt;
|
SystemEvent evt;
|
||||||
|
// 默认空事件属性
|
||||||
evt.key_event = KEY_EVENT_NONE;
|
evt.key_event = KEY_EVENT_NONE;
|
||||||
evt.priority = EVENT_PRIORITY_LOW;
|
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;
|
|
||||||
EA = 0;
|
if (queue_count == 0) return evt; // 队空,直接返回空闲事件
|
||||||
evt = event_queue[queue_head];
|
|
||||||
queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH;
|
EA = 0; // 屏蔽中断
|
||||||
queue_count--;
|
evt = event_queue[queue_head]; // 读取队首数据
|
||||||
EA = 1;
|
queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH; // 头指针向后移动,回环
|
||||||
|
queue_count--; // 有效计数减 1
|
||||||
|
EA = 1; // 恢复中断
|
||||||
return evt;
|
return evt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断队列是否为空
|
||||||
|
*/
|
||||||
bit EventQueue_IsEmpty(void)
|
bit EventQueue_IsEmpty(void)
|
||||||
{
|
{
|
||||||
return (queue_count == 0) ? 1 : 0;
|
return (queue_count == 0) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断队列是否已满
|
||||||
|
*/
|
||||||
bit EventQueue_IsFull(void)
|
bit EventQueue_IsFull(void)
|
||||||
{
|
{
|
||||||
return (queue_count >= EVENT_QUEUE_DEPTH) ? 1 : 0;
|
return (queue_count >= EVENT_QUEUE_DEPTH) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前队列中的事件总数
|
||||||
|
*/
|
||||||
u8 EventQueue_GetCount(void)
|
u8 EventQueue_GetCount(void)
|
||||||
{
|
{
|
||||||
return queue_count;
|
return queue_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 事件分发器核心轮询逻辑
|
||||||
|
* @details 逐一处理队列中的事件,并根据系统当前的运行模式进行拦截、跳转或下发派发。
|
||||||
|
*/
|
||||||
void Event_Dispatcher_Loop(void)
|
void Event_Dispatcher_Loop(void)
|
||||||
{
|
{
|
||||||
SystemEvent evt = EventQueue_Pop();
|
SystemEvent evt = EventQueue_Pop(); // 弹出一个事件
|
||||||
|
|
||||||
|
// 如果是无事发生的空事件,直接退出
|
||||||
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; // 收到有效按键或无线事件,重置闲置计时器
|
|
||||||
Uart_SendString("[EVENT] key=");
|
|
||||||
Uart_SendHex8((u8)evt.key_event);
|
|
||||||
Uart_SendString(", pri=");
|
|
||||||
Uart_SendHex8((u8)evt.priority);
|
|
||||||
Uart_SendString("\r\n");
|
|
||||||
|
|
||||||
// SOS 按键:仅在当前不处于 SOS 求求模式时触发进入 SOS 状态,否则作为普通按键进行事件分发
|
// 串口打印调试信息 (整秒滴答事件每秒都会触发一次,不打印以免刷屏)
|
||||||
|
if (evt.key_event != KEY_EVENT_SECOND_TICK) {
|
||||||
|
XTELL_LOG("[EVENT] key=");
|
||||||
|
XTELL_LOG_HEX8((u8)evt.key_event);
|
||||||
|
XTELL_LOG("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 传感器入侵警报:当前不处于 SOS 求救状态时方可跳转到报警界面
|
// 关机事件处理:捕获到长按关机指令,直接调用 Power_Off 关断电源
|
||||||
if (evt.priority == EVENT_PRIORITY_HIGH && evt.extra_size >= 4) {
|
if (evt.key_event == KEY_EVENT_POWER_OFF) {
|
||||||
u8 slot;
|
Power_Off();
|
||||||
if (Check_Sensor_ID(evt.extra_data, &slot)) {
|
|
||||||
if (AppManager_GetActiveAppID() != APP_ID_SOS) {
|
|
||||||
captured_addr = evt.extra_data;
|
|
||||||
alarm_sensor_slot = slot;
|
|
||||||
AppManager_StartApp(APP_ID_ALARM);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无线对码捕获:处于 PAIR 模式下时,将射频事件分发给 PairApp 处理并退出分发
|
|
||||||
if (evt.priority == EVENT_PRIORITY_NORMAL && evt.extra_size >= 4) {
|
|
||||||
captured_addr = evt.extra_data;
|
|
||||||
captured_type = menu_select;
|
|
||||||
if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
|
|
||||||
AppManager_DispatchEvent(evt);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 常规事件(如普通的按键点击)直接透传分发给当前处于前台活跃的应用
|
||||||
AppManager_DispatchEvent(evt);
|
AppManager_DispatchEvent(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Timer1_Init(void)
|
/**
|
||||||
{
|
* @brief 主循环按键扫描与消抖处理函数
|
||||||
AUXR |= 0x40;
|
* @details 由主循环每 10ms 检查并执行,完全在主线程中运行,绝无中断重入冲突。
|
||||||
TMOD &= 0x0F;
|
*/
|
||||||
{
|
void Event_KeyScan_Poll(void)
|
||||||
u16 reload = (u16)(65536UL - (MAIN_Fosc / 1000UL));
|
|
||||||
TL1 = (u8)reload;
|
|
||||||
TH1 = (u8)(reload >> 8);
|
|
||||||
}
|
|
||||||
ET1 = 1;
|
|
||||||
TR1 = 1;
|
|
||||||
EA = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer1_Isr(void) interrupt 3
|
|
||||||
{
|
{
|
||||||
SystemEvent evt;
|
SystemEvent evt;
|
||||||
|
// 读取按键的电平信号 (KEY_UP, KEY_CONFIRM 为低电平有效)
|
||||||
ms_tick++;
|
|
||||||
if (ms_tick >= 1000) {
|
|
||||||
ms_tick = 0;
|
|
||||||
current_sec++;
|
|
||||||
if (current_sec >= 60) {
|
|
||||||
current_sec = 0;
|
|
||||||
current_min++;
|
|
||||||
if (current_min >= 60) {
|
|
||||||
current_min = 0;
|
|
||||||
current_hour++;
|
|
||||||
if (current_hour >= 24) current_hour = 0;
|
|
||||||
}
|
|
||||||
clock_updated = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_state == STATE_PAIR_WAIT) {
|
|
||||||
pair_timeout_ms++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 振动马达定时状态机(仅控制马达,不再控制已被挪用/废弃的 LED) */
|
|
||||||
if (current_state == STATE_SOS_EMITTED) {
|
|
||||||
alarm_timer_ms++;
|
|
||||||
if (alarm_timer_ms >= 1200) alarm_timer_ms = 0;
|
|
||||||
if (alarm_timer_ms < 1000) MOTOR = 1; else MOTOR = 0;
|
|
||||||
} else if (current_state == STATE_ALARM) {
|
|
||||||
u8 stype = sensor_list[alarm_sensor_slot].type;
|
|
||||||
alarm_timer_ms++;
|
|
||||||
if (alarm_timer_ms >= 5000) alarm_timer_ms = 0;
|
|
||||||
if (stype == 0) {
|
|
||||||
// 门磁警报振动波形
|
|
||||||
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
|
|
||||||
} else if (stype == 1) {
|
|
||||||
// PIR 警报振动波形
|
|
||||||
if (alarm_timer_ms < 300) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 800) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else if (stype == 2) {
|
|
||||||
// 烟感警报振动波形
|
|
||||||
if (alarm_timer_ms < 200) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 350) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 350 && alarm_timer_ms < 550) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 550 && alarm_timer_ms < 700) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 700 && alarm_timer_ms < 900) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else if (stype == 3) {
|
|
||||||
// 气感警报振动波形
|
|
||||||
if (alarm_timer_ms < 200) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 300) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 500 && alarm_timer_ms < 600) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 800) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 800 && alarm_timer_ms < 900) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1100) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else if (stype == 4) {
|
|
||||||
// 水浸警报振动波形
|
|
||||||
if (alarm_timer_ms < 600) MOTOR = 1;
|
|
||||||
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 900) MOTOR = 0;
|
|
||||||
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1500) MOTOR = 1;
|
|
||||||
else MOTOR = 0;
|
|
||||||
} else {
|
|
||||||
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MOTOR = 0;
|
|
||||||
alarm_timer_ms = 0;
|
|
||||||
motor_timer_ms = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
alarm_flash_flag = (ms_tick / 100) % 2;
|
|
||||||
|
|
||||||
/* 按键消抖扫描 (每10ms) */
|
|
||||||
key_scan_timer++;
|
|
||||||
if (key_scan_timer >= 10) {
|
|
||||||
u8 raw_up = !KEY_UP;
|
u8 raw_up = !KEY_UP;
|
||||||
u8 raw_down = !KEY_DOWN;
|
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;
|
u8 raw_sos = !KEY_SOS; // 恢复 SOS 物理按键电平检测
|
||||||
key_scan_timer = 0;
|
key_scan_timer = 0; // 重置扫描分频计数器
|
||||||
|
|
||||||
// 如果检测到有任何按键按下,清零闲置计时器;否则在非休眠非警报下累加
|
// 【SOS 按键电平实时诊断】
|
||||||
if (raw_up || raw_down || raw_confirm || raw_sos) {
|
{
|
||||||
inactivity_timer = 0;
|
static u8 last_raw_sos = 99;
|
||||||
} else {
|
if (raw_sos != last_raw_sos) {
|
||||||
if (current_state != STATE_SLEEP && current_state != STATE_ALARM && current_state != STATE_SOS_EMITTED) {
|
last_raw_sos = raw_sos;
|
||||||
inactivity_timer++;
|
Uart_SendString("[KEY-DIAG] KEY_SOS (P2.6) logic raw: ");
|
||||||
|
Uart_SendByte(raw_sos ? '1' : '0');
|
||||||
|
Uart_SendString(", Physical GPIO Pin: ");
|
||||||
|
Uart_SendByte(KEY_SOS ? '1' : '0');
|
||||||
|
Uart_SendString("\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按键调试诊断:若按键 ADC 值有变化或处于高电平,串口输出真实 ADC 值
|
||||||
|
{
|
||||||
|
u16 down_val = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN);
|
||||||
|
if (down_val > 2800) {
|
||||||
|
Uart_SendString("[KEY-DIAG] KEY_DOWN pressed! ADC Raw: ");
|
||||||
|
Uart_SendByte((u8)('0' + (down_val / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (down_val / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (down_val / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (down_val % 10)));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认将派发事件重置为空
|
||||||
evt.key_event = KEY_EVENT_NONE;
|
evt.key_event = KEY_EVENT_NONE;
|
||||||
|
evt.priority = EVENT_PRIORITY_NORMAL;
|
||||||
evt.extra_data = 0;
|
evt.extra_data = 0;
|
||||||
evt.extra_size = 0;
|
evt.extra_size = 0;
|
||||||
|
|
||||||
|
// 双键组合判定:▲ 键 and ▼ 键同时按下的消抖与生成
|
||||||
if (raw_up && raw_down) {
|
if (raw_up && raw_down) {
|
||||||
comb_hold++;
|
comb_hold++;
|
||||||
if (comb_hold == 300) {
|
if (comb_hold == 300) { // 持续按下达 3 秒 (300 * 10ms = 3s)
|
||||||
evt.key_event = KEY_EVENT_UP_DOWN_COMB;
|
evt.key_event = KEY_EVENT_UP_DOWN_COMB;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
EventQueue_Push(evt); // 压入队列
|
||||||
EventQueue_Push(evt);
|
|
||||||
}
|
}
|
||||||
key_up_hold = 0;
|
key_up_hold = 0; // 清零单个按键计数,防止双击事件干扰
|
||||||
key_down_hold = 0;
|
key_down_hold = 0;
|
||||||
} else {
|
} else {
|
||||||
comb_hold = 0;
|
comb_hold = 0; // 组合键松开归零
|
||||||
|
|
||||||
|
// ▲ (KEY_UP) 按键状态机
|
||||||
if (raw_up) {
|
if (raw_up) {
|
||||||
key_up_hold++;
|
key_up_hold++;
|
||||||
if (key_up_hold == 300) {
|
if (key_up_hold == 300) { // 达到 3 秒判定为长按
|
||||||
evt.key_event = KEY_EVENT_UP_LONG;
|
evt.key_event = KEY_EVENT_UP_LONG;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 如果松开时按下计数在消抖阈值(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;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
key_up_hold = 0;
|
key_up_hold = 0; // 释放归零
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ▼ (KEY_DOWN / 开关机) 按键状态机
|
||||||
if (raw_down) {
|
if (raw_down) {
|
||||||
key_down_hold++;
|
key_down_hold++;
|
||||||
if (key_down_hold == 300) {
|
if (key_down_hold == 100) { // 长按 1 秒 (100 * 10ms = 1000ms = 1s) 触发关机事件
|
||||||
evt.key_event = KEY_EVENT_DOWN_LONG;
|
evt.key_event = KEY_EVENT_POWER_OFF;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (key_down_hold >= 2 && key_down_hold < 300) {
|
// 如果松开时按下时长在 20ms 至 1s 之间,触发短按下翻/选择事件
|
||||||
|
if (key_down_hold >= 2 && key_down_hold < 100) {
|
||||||
evt.key_event = KEY_EVENT_DOWN_CLICK;
|
evt.key_event = KEY_EVENT_DOWN_CLICK;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
key_down_hold = 0;
|
key_down_hold = 0; // 释放归零
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ■ (KEY_CONFIRM) 确认按键状态机
|
||||||
if (raw_confirm) {
|
if (raw_confirm) {
|
||||||
key_confirm_hold++;
|
key_confirm_hold++;
|
||||||
if (key_confirm_hold == 300) {
|
if (key_confirm_hold == 300) { // 长按 3 秒打开主菜单
|
||||||
evt.key_event = KEY_EVENT_CONFIRM_LONG;
|
evt.key_event = KEY_EVENT_CONFIRM_LONG;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (key_confirm_hold >= 2 && key_confirm_hold < 300) {
|
if (key_confirm_hold >= 2 && key_confirm_hold < 300) {
|
||||||
evt.key_event = KEY_EVENT_CONFIRM_CLICK;
|
evt.key_event = KEY_EVENT_CONFIRM_CLICK;
|
||||||
evt.priority = EVENT_PRIORITY_NORMAL;
|
|
||||||
EventQueue_Push(evt);
|
EventQueue_Push(evt);
|
||||||
}
|
}
|
||||||
key_confirm_hold = 0;
|
key_confirm_hold = 0; // 释放归零
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SOS 物理求救按键状态机 (已解锁)
|
||||||
if (raw_sos) {
|
if (raw_sos) {
|
||||||
key_sos_hold++;
|
key_sos_hold++;
|
||||||
if (key_sos_hold == 300) {
|
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");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (key_sos_hold >= 2 && key_sos_hold < 300) {
|
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");
|
||||||
}
|
}
|
||||||
key_sos_hold = 0;
|
key_sos_hold = 0; // 释放归零
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
App/event.h
65
App/event.h
@@ -3,17 +3,72 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
// 事件队列初始化与操作
|
/* ====== 事件环形缓冲区 (队列) 接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化事件队列
|
||||||
|
* @details 将队列的头指针、尾指针以及当前事件计数器全部归零,清空缓冲区。
|
||||||
|
*/
|
||||||
void EventQueue_Init(void);
|
void EventQueue_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将一个普通事件压入队列的末尾 (入队)
|
||||||
|
* @param evt 要入队的系统事件实例
|
||||||
|
* @return bit 1-入队成功,0-队列已满 (入队失败,丢弃事件)
|
||||||
|
* @note 此函数操作时会自动禁用全局中断 (EA = 0),操作完成后恢复以防止重入冲突。
|
||||||
|
*/
|
||||||
bit EventQueue_Push(SystemEvent evt);
|
bit EventQueue_Push(SystemEvent evt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将一个紧急/高优先级事件强行插入到队列的首部 (插队)
|
||||||
|
* @param evt 要优先处理的系统事件
|
||||||
|
* @return bit 1-插队成功,0-队列已满 (插队失败)
|
||||||
|
* @note 用于处理如 SOS 紧急求救和无线防区报警等需要立即响应的事件。操作受全局中断屏蔽保护。
|
||||||
|
*/
|
||||||
bit EventQueue_InsertFront(SystemEvent evt);
|
bit EventQueue_InsertFront(SystemEvent evt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从队列首部弹出一个事件 (出队)
|
||||||
|
* @return SystemEvent 被取出的系统事件。如果队列为空,将返回一个 key_event 为 KEY_EVENT_NONE 的空事件。
|
||||||
|
* @note 操作时受全局中断屏蔽保护。
|
||||||
|
*/
|
||||||
SystemEvent EventQueue_Pop(void);
|
SystemEvent EventQueue_Pop(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测事件队列当前是否为空
|
||||||
|
* @return bit 1-队列为空,0-队列中仍有事件未处理
|
||||||
|
*/
|
||||||
bit EventQueue_IsEmpty(void);
|
bit EventQueue_IsEmpty(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测事件队列当前是否已满 (即达到最大深度 8)
|
||||||
|
* @return bit 1-队列已满,0-队列仍有剩余空闲槽位
|
||||||
|
*/
|
||||||
bit EventQueue_IsFull(void);
|
bit EventQueue_IsFull(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取事件队列中当前已缓冲的事件总个数
|
||||||
|
* @return u8 当前事件的缓存个数
|
||||||
|
*/
|
||||||
u8 EventQueue_GetCount(void);
|
u8 EventQueue_GetCount(void);
|
||||||
|
|
||||||
// 事件分发与定时器/按键扫描初始化
|
|
||||||
void Event_Dispatcher_Loop(void);
|
|
||||||
void Timer1_Init(void);
|
|
||||||
|
|
||||||
#endif
|
/* ====== 事件分发环 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 事件分发轮询主处理程序
|
||||||
|
* @details 负责从事件队列中弹出当前最优先的事件,解析其内容,处理全局重置逻辑(如重置闲置休眠计时器),
|
||||||
|
* 并根据事件的类型与当前活跃的前台 APP 状态将事件分发到具体的应用程序中去处理。
|
||||||
|
*/
|
||||||
|
void Event_Dispatcher_Loop(void);
|
||||||
|
|
||||||
|
// 主循环按键扫描定时标志 (由 Drivers/timer.c 的 Timer1 1ms 中断置 1,主循环响应清除)
|
||||||
|
extern volatile bit key_scan_flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 主循环按键扫描与消抖处理函数
|
||||||
|
* @details 将按键检测逻辑从中断搬移至此,在主线程执行,避免 ADC 多重采样冲突死锁
|
||||||
|
*/
|
||||||
|
void Event_KeyScan_Poll(void);
|
||||||
|
|
||||||
|
#endif // __EVENT_H__
|
||||||
|
|||||||
@@ -1,17 +1,49 @@
|
|||||||
; 端口中断 0 / 2 的硬件中转绝对定位汇编 (A251)
|
; =========================================================================
|
||||||
; 目的:Keil C251 编译器只支持 interrupt 0~31,
|
; STC32G 端口中断硬件中转绝对定位汇编模块 (A251 语法格式)
|
||||||
; 而 STC32G 的端口中断硬件向量号为 37(Port0) 和 39(Port2),
|
; =========================================================================
|
||||||
; 超出范围无法用 C 直接声明。
|
; 【技术原理分析】:
|
||||||
; 本文件在绝对地址处放置 LJMP 指令,将硬件向量重定向到
|
; 1. Keil C251 编译器为了节省硬件资源,默认的 `interrupt` 关键字只支持 0 ~ 31 范围的软件中断向量号。
|
||||||
; Keil 可以生成的 13 号和 15 号中断服务函数入口。
|
; 2. 然而,STC32G 单片机拥有大量的扩展硬件外设,其 Port0 端口中断物理向量号为 37,Port2 端口中断物理向量号为 39,
|
||||||
NAME ISR_JUMP
|
; Port3 端口中断物理向量号为 40 (定义见 Drivers/stc32g.h 的 P0INT_VECTOR/P2INT_VECTOR/P3INT_VECTOR),
|
||||||
|
; 均超出了编译器的直接定义范畴。如果直接在 C 语言中声明 `interrupt 37`,编译器会报错。
|
||||||
|
; 3. 为此,本汇编模块采用“绝对地址跳转重定向”技术:
|
||||||
|
; - 直接在硬件物理中断向量入口地址(Port0: 0x00012B, Port2: 0x00013B, Port3: 0x000143)处放置一条长跳转 `LJMP` 汇编指令。
|
||||||
|
; - 将触发的物理中断强行重定向引导至 Keil C251 所能合法编译生成的较小编译向量入口地址(例如 13、15、16 号中断服务程序入口)。
|
||||||
|
; - 由此,我们在 C 语言中声明 `interrupt 13`、`interrupt 15`、`interrupt 16`,就能够成功被硬件
|
||||||
|
; Port0、Port2、Port3 端口中断触发调用。
|
||||||
|
; 【重要修复记录】:Port3 这条重定向此前一直缺失——C 代码里 `Port3_Isr` 声明的是 `interrupt 16`
|
||||||
|
; (对应真实向量 16 号 INT4,与 P3 端口无关),而 P3 端口真正的硬件向量号是 40,此前没有任何代码
|
||||||
|
; 把向量 40 重定向过去,导致 P3.6 硬件中断真正触发时,CPU 会跳转到 0x000143 这个从未写入过任何
|
||||||
|
; 指令的地址,造成运行跑飞/复位。现已补上对应的跳转条目。
|
||||||
|
; =========================================================================
|
||||||
|
|
||||||
; 端口 0 中断 (向量 37, 硬件绝对地址 00012BH) 跳转到 13 号中断 (地址 00006BH)
|
NAME ISR_JUMP ; 定义当前汇编模块的名字为 ISR_JUMP
|
||||||
|
|
||||||
|
; -------------------------------------------------------------------------
|
||||||
|
; 端口 0 掉电唤醒中断跳转配置
|
||||||
|
; -------------------------------------------------------------------------
|
||||||
|
; 硬件 Port0 中断向量号 37 对应的绝对闪存入口地址为 0x00012B
|
||||||
CSEG AT 00012BH
|
CSEG AT 00012BH
|
||||||
|
; 当硬件触发 Port0 中断时,程序计数器 (PC) 定位到 00012BH,此处 LJMP 指令强行将 PC 指向 00006BH
|
||||||
|
; 0x00006B 即为 C 语言声明的 `interrupt 13` 所对应的中断服务函数入口地址
|
||||||
LJMP 00006BH
|
LJMP 00006BH
|
||||||
|
|
||||||
; 端口 2 中断 (向量 39, 硬件绝对地址 00013BH) 跳转到 15 号中断 (地址 00007BH)
|
; -------------------------------------------------------------------------
|
||||||
|
; 端口 2 掉电唤醒中断跳转配置
|
||||||
|
; -------------------------------------------------------------------------
|
||||||
|
; 硬件 Port2 中断向量号 39 对应的绝对闪存入口地址为 0x00013B
|
||||||
CSEG AT 00013BH
|
CSEG AT 00013BH
|
||||||
|
; 当硬件触发 Port2 中断时,程序计数器 (PC) 定位到 00013BH,此处 LJMP 指令强行将 PC 指向 00007BH
|
||||||
|
; 0x00007B 即为 C 语言声明的 `interrupt 15` 所对应的中断服务函数入口地址
|
||||||
LJMP 00007BH
|
LJMP 00007BH
|
||||||
|
|
||||||
END
|
; -------------------------------------------------------------------------
|
||||||
|
; 端口 3 掉电唤醒/常态边沿中断跳转配置 (P3.6 / RF_RX_DATA)
|
||||||
|
; -------------------------------------------------------------------------
|
||||||
|
; 硬件 Port3 中断向量号 40 (P3INT_VECTOR) 对应的绝对闪存入口地址为 0x000143
|
||||||
|
CSEG AT 000143H
|
||||||
|
; 当硬件触发 Port3 中断时,程序计数器 (PC) 定位到 000143H,此处 LJMP 指令强行将 PC 指向 000083H
|
||||||
|
; 0x000083 即为 C 语言声明的 `interrupt 16` 所对应的中断服务函数入口地址 (Port3_Isr)
|
||||||
|
LJMP 000083H
|
||||||
|
|
||||||
|
END ; 汇编文件结束标志
|
||||||
|
|||||||
48
App/language.c
Normal file
48
App/language.c
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include "language.h"
|
||||||
|
|
||||||
|
// 生成语言字符串数据表所用的临时结构体 (fr/en 两列)
|
||||||
|
typedef struct {
|
||||||
|
char *fr;
|
||||||
|
char *en;
|
||||||
|
} LangEntry;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// !!!!!! 不要动下面的代码 !!!!!!
|
||||||
|
#ifdef DEFINE_STRING
|
||||||
|
#undef DEFINE_STRING
|
||||||
|
#endif
|
||||||
|
#define DEFINE_STRING(name, fr, en) { fr, en },
|
||||||
|
// !!!!!! 不要动上面的代码 !!!!!!
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// 复用 strings.h 里的同一份登记表,展开成 {法语, 英语} 数据数组,存放于 Flash (code 区)
|
||||||
|
LangEntry code language_pack[STR_COUNT] = {
|
||||||
|
WRISTBAND_STRING_LIST
|
||||||
|
};
|
||||||
|
|
||||||
|
static Language current_lang = LANG_EN;
|
||||||
|
|
||||||
|
void Lang_Init(Language lang)
|
||||||
|
{
|
||||||
|
current_lang = (lang < LANG_COUNT) ? lang : LANG_EN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lang_Set(Language lang)
|
||||||
|
{
|
||||||
|
if (lang < LANG_COUNT) {
|
||||||
|
current_lang = lang;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Language Lang_Current(void)
|
||||||
|
{
|
||||||
|
return current_lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *Lang_Get(StringID id)
|
||||||
|
{
|
||||||
|
if (id >= STR_COUNT) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return (current_lang == LANG_EN) ? language_pack[id].en : language_pack[id].fr;
|
||||||
|
}
|
||||||
41
App/language.h
Normal file
41
App/language.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef __LANGUAGE_H__
|
||||||
|
#define __LANGUAGE_H__
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "strings.h"
|
||||||
|
|
||||||
|
/* ====== 界面语言选择 ====== */
|
||||||
|
typedef enum {
|
||||||
|
LANG_FR = 0, // 法语 (默认)
|
||||||
|
LANG_EN, // 英语
|
||||||
|
LANG_COUNT
|
||||||
|
} Language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 语言模块初始化 (开机时由 Load_Database 读取 Flash 中保存的语言字节后调用)
|
||||||
|
* @param lang 要设为当前语言的枚举值。若超出 LANG_COUNT 范围 (如 Flash 未写过时的 0xFF),回退为 LANG_FR。
|
||||||
|
*/
|
||||||
|
void Lang_Init(Language lang);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 切换当前界面语言 (不负责持久化,调用方需自行决定是否写入 Flash)
|
||||||
|
* @param lang 目标语言
|
||||||
|
*/
|
||||||
|
void Lang_Set(Language lang);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查询当前界面语言
|
||||||
|
* @return Language 当前语言枚举值
|
||||||
|
*/
|
||||||
|
Language Lang_Current(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 取指定字符串 ID 在当前语言下的文本
|
||||||
|
* @param id 字符串 ID (见 strings.h 中 WRISTBAND_STRING_LIST 登记表)
|
||||||
|
* @return char* 当前语言对应的文案指针;id 越界时返回空字符串
|
||||||
|
* @note 返回类型不加 const,是为了直接兼容 LCD_ShowString 系列接口的 char* 参数,
|
||||||
|
* 与本项目其余字符串接口 (Uart_SendString 等) 的风格保持一致。
|
||||||
|
*/
|
||||||
|
char *Lang_Get(StringID id);
|
||||||
|
|
||||||
|
#endif // __LANGUAGE_H__
|
||||||
332
App/main.c
332
App/main.c
@@ -7,51 +7,125 @@
|
|||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "../Drivers/rf.h"
|
#include "../Drivers/rf.h"
|
||||||
#include "../Drivers/lcd.h"
|
#include "../Drivers/lcd.h"
|
||||||
|
#include "../Drivers/timer.h"
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* 【烧录必读】STC-ISP 下载时时钟频率必须选择 24MHz,且不能只改这一处
|
||||||
|
* =========================================================================
|
||||||
|
* 本项目代码里多处对"芯片实际运行在 24MHz"这件事做了硬编码假设,而不是每处都
|
||||||
|
* 从 MAIN_Fosc 动态换算;如果烧录时在 STC-ISP 里选了别的 IRC 频率(或换成外部
|
||||||
|
* 晶振但频率不是 24MHz),下面这些地方会全部跟着出错,不是改一下 config.h 里的
|
||||||
|
* MAIN_Fosc 常量就能自动生效的:
|
||||||
|
* 1. App/config.h:169 MAIN_Fosc = 24000000UL —— 大部分模块的换算基准。
|
||||||
|
* 2. Drivers/timer.c Timer1_Init() 里 1ms 定时重装值由 MAIN_Fosc 动态算出,
|
||||||
|
* 跟着 MAIN_Fosc 改没问题。
|
||||||
|
* 3. App/database.c IAP_TPS 由 MAIN_Fosc 动态算出 (Flash 读写/擦除等待周期),
|
||||||
|
* 跟着 MAIN_Fosc 改没问题,但算错会导致 Flash 读写异常甚至数据损坏。
|
||||||
|
* 4. App/system.c Uart1_Init() 里 Timer2 波特率重装值 (T2L=0xCC, T2H=0xFF)
|
||||||
|
* 是按 24MHz/115200 手工算好直接硬编码的数字,***不会跟着 MAIN_Fosc 变化***,
|
||||||
|
* 实际主频不是 24MHz 时串口波特率会直接算错,调试串口输出全部变乱码。
|
||||||
|
* 5. Drivers/rf.c/timer.c 里所有 EV1527 时序阈值和 RF_Delay_us() 都基于
|
||||||
|
* "Timer0 工作在标准 12T 模式、1 个计数节拍 = 12/Fosc 秒"这个换算关系,
|
||||||
|
* 代码里到处写的 "0.5us/tick"、"* 2UL" 都是 12/24MHz = 0.5us 这个具体
|
||||||
|
* 数字,***同样不会跟着 MAIN_Fosc 变化***,主频不对会导致 RF 收发全部解码/
|
||||||
|
* 发射失败。
|
||||||
|
* 结论:必须保证 STC-ISP 烧录时选择的时钟频率就是 24MHz,代码里各处的计时也
|
||||||
|
* 都是按实际运行在 24MHz 主频来写的,两边必须一致。
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* 系统全局变量定义
|
* 系统全局变量定义
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
SystemState current_state = STATE_NORMAL;
|
SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页
|
||||||
u8 current_hour = 10;
|
|
||||||
u8 current_min = 35;
|
|
||||||
u8 current_sec = 0;
|
|
||||||
volatile u8 clock_updated = 0;
|
|
||||||
|
|
||||||
u8 menu_select = 0;
|
|
||||||
u32 captured_addr = 0;
|
|
||||||
u8 captured_type = 0;
|
|
||||||
u8 alarm_sensor_slot = 0;
|
|
||||||
|
|
||||||
|
u8 menu_select = 0; // 当前菜单高亮的选中项索引 (menu_app 0~3,pair_app 传感器类型选择 0~4)
|
||||||
|
u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址
|
||||||
|
u8 captured_type = 0; // 正在对码或触发警报的传感器设备类型 (0:门磁, 1:PIR...)
|
||||||
|
u8 alarm_sensor_slot = 0; // 触发警报的传感器在 sensor_list 数组中的索引槽位号
|
||||||
|
|
||||||
|
// 传感器存储列表数据库,位于单片机外部 XDATA 存储空间,总共 16 个槽位
|
||||||
Sensor_Slot xdata sensor_list[16];
|
Sensor_Slot xdata sensor_list[16];
|
||||||
|
|
||||||
volatile u16 ms_tick = 0;
|
// 全局滴答与定时中断计数变量,位于 volatile 防止被编译器优化
|
||||||
volatile u16 inactivity_timer = 0; // 无操作闲置计时器
|
volatile u16 ms_tick = 0; // 定时中断 1ms 基准滴答累加器
|
||||||
volatile u32 pair_timeout_ms = 0;
|
volatile u32 pair_timeout_ms = 0; // 传感器配对对码超时毫秒计时器 (上限 30000ms 即 30s)
|
||||||
volatile u16 alarm_timer_ms = 0;
|
volatile u16 alarm_timer_ms = 0; // 入侵报警马达震动时序计数器
|
||||||
volatile u16 motor_timer_ms = 0;
|
volatile u16 motor_timer_ms = 0; // 独立马达震动时长计数器
|
||||||
volatile bit alarm_flash_flag = 0;
|
volatile bit alarm_flash_flag = 0; // 报警时红框周期性闪烁亮灭标志位
|
||||||
|
|
||||||
volatile u16 key_scan_timer = 0;
|
// 物理按键扫描与消抖长按计数器 (10ms 扫描一次)
|
||||||
volatile u16 key_up_hold = 0;
|
volatile u16 key_scan_timer = 0; // 10ms 扫描周期分频器
|
||||||
volatile u16 key_down_hold = 0;
|
volatile u16 key_up_hold = 0; // ▲ (KEY_UP) 按下时长累加
|
||||||
volatile u16 key_confirm_hold = 0;
|
volatile u16 key_down_hold = 0; // ▼ (KEY_DOWN) 按下时长累加
|
||||||
volatile u16 key_sos_hold = 0;
|
volatile u16 key_confirm_hold = 0; // ■ (KEY_CONFIRM) 按下时长累加
|
||||||
volatile u16 comb_hold = 0;
|
volatile u16 key_sos_hold = 0; // SOS 按下时长累加
|
||||||
|
volatile u16 comb_hold = 0; // ▲ + ▼ 组合键按下时长累加
|
||||||
|
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* 串口调试指令解析与事件派发封装
|
* 串口调试指令解析与事件派发封装
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
|
/**
|
||||||
|
* @brief 解析通过串口 1 发送来的键盘及测试模拟指令
|
||||||
|
* @param cmd 接收到的 ASCII 字符命令
|
||||||
|
* @details 负责将字符指令转化对应的系统按键或射频事件,并推入事件队列以测试软件架构:
|
||||||
|
* - 'u'/'U': 模拟短按/长按 ▲ 键。
|
||||||
|
* - 'd'/'D': 模拟短按/长按 ▼ 键。
|
||||||
|
* - 's'/'S': 模拟短按/长按 SOS 物理键(优先插入队首)。
|
||||||
|
* - 'c'/'C': 模拟组合键触发。
|
||||||
|
* - 'a'/'A': 模拟无线传感器入侵触发警报信号,地址固定为 0x123456,zone=1。
|
||||||
|
* - 'p'/'P': 模拟对码期间捕获的 24 位无线对码信号,地址固定为 0x789ABC。
|
||||||
|
* - '1'/'2'/'3': 模拟系统的正常、已布防、已撤防状态切换。
|
||||||
|
* - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000,触发自动深度休眠。
|
||||||
|
* - 't'/'T': 触发屏幕与无线控制引脚测试。
|
||||||
|
* - 'e'/'E': DEBUG,打印 RF 边沿中断解码排查计数器。
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @brief 串口调试专属:RGB 物理颜色通道排查测试
|
||||||
|
* @details 依次发送纯红、纯绿、纯蓝的数据流,每个颜色亮起 2 秒,帮助肉眼校对物理灯珠的实际颜色映射关系
|
||||||
|
*/
|
||||||
|
void RGB_Diagnostic_Test(void)
|
||||||
|
{
|
||||||
|
Uart_SendString("\r\n=== RGB LED Color Channel Test Starting ===\r\n");
|
||||||
|
|
||||||
|
// 1. 尝试发送红色控制数据 (G=0, R=150, B=0)
|
||||||
|
Uart_SendString("[RGB-TEST] 1. Sending RED command -> G=0, R=150, B=0\r\n");
|
||||||
|
RGB_Send(0, 150, 0, 0, 150, 0);
|
||||||
|
Delay_ms(2000);
|
||||||
|
|
||||||
|
// 熄灭
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
Delay_ms(300);
|
||||||
|
|
||||||
|
// 2. 尝试发送绿色控制数据 (G=150, R=0, B=0)
|
||||||
|
Uart_SendString("[RGB-TEST] 2. Sending GREEN command -> G=150, R=0, B=0\r\n");
|
||||||
|
RGB_Send(150, 0, 0, 150, 0, 0);
|
||||||
|
Delay_ms(2000);
|
||||||
|
|
||||||
|
// 熄灭
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
Delay_ms(300);
|
||||||
|
|
||||||
|
// 3. 尝试发送蓝色控制数据 (G=0, R=0, B=150)
|
||||||
|
Uart_SendString("[RGB-TEST] 3. Sending BLUE command -> G=0, R=0, B=150\r\n");
|
||||||
|
RGB_Send(0, 0, 150, 0, 0, 150);
|
||||||
|
Delay_ms(2000);
|
||||||
|
|
||||||
|
// 彻底熄灭
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
Uart_SendString("=== RGB LED Color Channel Test Ended ===\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
void Debug_ProcessCommand(char cmd)
|
void Debug_ProcessCommand(char cmd)
|
||||||
{
|
{
|
||||||
SystemEvent debug_evt;
|
SystemEvent debug_evt;
|
||||||
inactivity_timer = 0; // 调试命令重置闲置计时器
|
|
||||||
debug_evt.extra_data = 0;
|
debug_evt.extra_data = 0;
|
||||||
debug_evt.extra_size = 0;
|
debug_evt.extra_size = 0;
|
||||||
|
|
||||||
if (cmd == 'u') {
|
if (cmd == 'u') {
|
||||||
debug_evt.key_event = KEY_EVENT_UP_CLICK;
|
debug_evt.key_event = KEY_EVENT_UP_CLICK;
|
||||||
debug_evt.priority = EVENT_PRIORITY_NORMAL;
|
debug_evt.priority = EVENT_PRIORITY_NORMAL;
|
||||||
EventQueue_Push(debug_evt);
|
EventQueue_Push(debug_evt); // 压入队尾
|
||||||
Uart_SendString("[UART] KEY_UP Clicked\r\n");
|
Uart_SendString("[UART] KEY_UP Clicked\r\n");
|
||||||
} else if (cmd == 'U') {
|
} else if (cmd == 'U') {
|
||||||
debug_evt.key_event = KEY_EVENT_UP_LONG;
|
debug_evt.key_event = KEY_EVENT_UP_LONG;
|
||||||
@@ -71,7 +145,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);
|
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;
|
||||||
@@ -84,22 +158,24 @@ void Debug_ProcessCommand(char cmd)
|
|||||||
EventQueue_Push(debug_evt);
|
EventQueue_Push(debug_evt);
|
||||||
Uart_SendString("[UART] KEY_UP+DOWN Combined\r\n");
|
Uart_SendString("[UART] KEY_UP+DOWN Combined\r\n");
|
||||||
} 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;
|
||||||
|
// 如果本地数据库中没有匹配该射频,则强制登记进 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;
|
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') {
|
||||||
|
// 只有当手环切入 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;
|
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");
|
||||||
@@ -107,102 +183,220 @@ 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') {
|
||||||
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
|
// 模拟切换至 正常撤防 (NORMAL) 状态并刷新时钟
|
||||||
|
if (AppManager_GetActiveAppID() == APP_ID_HOME) {
|
||||||
current_state = STATE_NORMAL;
|
current_state = STATE_NORMAL;
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month);
|
||||||
}
|
}
|
||||||
Uart_SendString("[UART] State: NORMAL\r\n");
|
Uart_SendString("[UART] State: NORMAL\r\n");
|
||||||
} else if (cmd == '2') {
|
} else if (cmd == '2') {
|
||||||
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
|
// 模拟切换至 已布防 (ARMED) 状态,普通门磁可触发警报
|
||||||
|
if (AppManager_GetActiveAppID() == APP_ID_HOME) {
|
||||||
current_state = STATE_ARMED;
|
current_state = STATE_ARMED;
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month);
|
||||||
}
|
}
|
||||||
Uart_SendString("[UART] State: ARMED\r\n");
|
Uart_SendString("[UART] State: ARMED\r\n");
|
||||||
} else if (cmd == '3') {
|
} else if (cmd == '3') {
|
||||||
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
|
// 模拟切换至 已撤防 (DISARMED) 状态,忽略普通报警
|
||||||
|
if (AppManager_GetActiveAppID() == APP_ID_HOME) {
|
||||||
current_state = STATE_DISARMED;
|
current_state = STATE_DISARMED;
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
UI_ShowClockPage(current_state, current_hour, current_min, current_weekday, current_day, current_month);
|
||||||
}
|
}
|
||||||
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 = 1000; // 模拟闲置超时,强制触发休眠
|
// 直接强制进入低功耗休眠,测试用 (原来靠伪造全局闲置计时器触发,现改为直接调用)
|
||||||
Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
|
Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
|
||||||
|
current_state = STATE_SLEEP;
|
||||||
|
Enter_Low_Power_Sleep();
|
||||||
|
AppManager_SwitchToForeground(&home_app); // 唤醒后强制重绘时钟画面
|
||||||
|
} else if (cmd == 'q' || cmd == 'Q') {
|
||||||
|
RGB_Diagnostic_Test(); // 启动 RGB 物理颜色通道排查测试
|
||||||
|
} else if (cmd == 't' || cmd == 'T') {
|
||||||
|
RF_DiagnosticMode('t'); // 触发射频连续发射诊断 (绿色爆闪)
|
||||||
|
} else if (cmd == 'r' || cmd == 'R') {
|
||||||
|
RF_DiagnosticMode('r'); // 触发 5 秒射频接收监听诊断
|
||||||
|
} else if (cmd == 'l' || cmd == 'L') {
|
||||||
|
Loopback_Test(); // 触发物理电气回环测试 (短路 P2.1 和 P3.6 自检)
|
||||||
|
} else if (cmd == 'e' || cmd == 'E') {
|
||||||
|
RF_PrintDebugCounters(); // DEBUG: 打印 RF 边沿中断解码排查计数器
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====== 调试用最小化启动开关 ======
|
||||||
|
// 置 1 时 main() 只做点亮屏幕这一件事,RF/自检/数据库/事件框架/Timer1 全部跳过,
|
||||||
|
// 用于定位"开机反复重启"问题的范围。
|
||||||
|
// 根因(isr_jump.asm 缺少 Port3 中断向量重定向、且该文件从未加入工程编译)已定位并修复,
|
||||||
|
// 现改回 0,恢复完整正常启动流程,测试整机端到端是否稳定。
|
||||||
|
#define DEBUG_MINIMAL_BOOT 0
|
||||||
|
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* 主函数 (入口)
|
* 主函数 (入口)
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
|
#if DEBUG_MINIMAL_BOOT
|
||||||
|
|
||||||
|
void main(void)
|
||||||
|
{
|
||||||
|
// ====== STC32G 特殊功能寄存器内核基础初始化 ======
|
||||||
|
WTST = 0; // 设置程序 Flash 访问等待时间为 0 (最高速运行)
|
||||||
|
EAXFR = 1; // 允许访问扩展特殊功能寄存器 (XSFR)
|
||||||
|
CKCON = 0; // 外部总线时钟设定为最快
|
||||||
|
WDT_CONTR = 0x00; // 关闭看门狗定时器
|
||||||
|
|
||||||
|
// ====== 步骤 A:加回 RF_Init() 与 Timer1_Init(),先不打开 RF 接收 ======
|
||||||
|
GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉 (锁存 PWR_HOLD=1)
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0); // 上电第一时间强制发送 0 码关闭灯条,熄灭上电暂态引起的随机白光
|
||||||
|
RF_Init(); // 加回来:Timer0 常驻自由运行 + P3.6 常态双边沿中断 + 上拉,此时芯片仍 SHUT=1 关闭
|
||||||
|
Delay_ms(500); // 软启动延时,等待供电电容稳定
|
||||||
|
LCD_Init(); // 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列并开启背光
|
||||||
|
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
|
||||||
|
Uart_SendString("Wristband System Initialized! (DEBUG_MINIMAL_BOOT=1, step A: RF_Init+Timer1_Init)\r\n");
|
||||||
|
|
||||||
|
Timer1_Init(); // 全局中断 EA=1 从这里开始生效,Timer0 溢出中断开始工作
|
||||||
|
|
||||||
|
// ====== 步骤 C:真正的病根 (isr_jump.asm 缺少 Port3 重定向、且该文件从未被加入工程编译) 已修复,
|
||||||
|
// P3.6 中断与 RF_HandleEdgeInterrupt 都已恢复为正常逻辑,现在测试真实功能是否稳定 ======
|
||||||
|
RF_SetMode(1);
|
||||||
|
|
||||||
|
LCD_ShowStringCentered(100, "STEP C OK", COLOR_WHITE, COLOR_BLACK);
|
||||||
|
LCD_ShowStringCentered(130, "ISR fixed", COLOR_GRAY, COLOR_BLACK);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
Delay_ms(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
void main(void)
|
void main(void)
|
||||||
{
|
{
|
||||||
char cmd;
|
char cmd;
|
||||||
|
|
||||||
WTST = 0;
|
// ====== STC32G 特殊功能寄存器内核基础初始化 ======
|
||||||
EAXFR = 1;
|
WTST = 0; // 设置程序 Flash 访问等待时间为 0 (最高速运行)
|
||||||
CKCON = 0;
|
EAXFR = 1; // 允许访问扩展特殊功能寄存器 (XSFR)
|
||||||
WDT_CONTR = 0x00;
|
CKCON = 0; // 外部总线时钟设定为最快
|
||||||
|
WDT_CONTR = 0x00; // 关闭看门狗定时器
|
||||||
|
|
||||||
GPIO_Init();
|
// ====== 硬件各模块基础初始化 ======
|
||||||
RF_Init();
|
GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉 (锁存 PWR_HOLD=1)
|
||||||
Delay_ms(500);
|
RGB_Send(0, 0, 0, 0, 0, 0); // 【首要修正】:上电第一时间强制发送 0 码关闭灯条,熄灭上电暂态引起的随机白光!
|
||||||
SGM_SendPulse(27);
|
RF_Init(); // 初始化射频芯片各引脚控制
|
||||||
Delay_ms(50);
|
Delay_ms(500); // 软启动延时,等待供电电容稳定
|
||||||
LCD_Init();
|
LCD_Init(); // 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列 (背光保持关闭,避免闪烁)
|
||||||
Uart1_Init();
|
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
|
||||||
Uart_SendString("Wristband System Initialized!\r\n");
|
Uart_SendString("Wristband System Initialized!\r\n");
|
||||||
Load_Database();
|
|
||||||
|
|
||||||
/* 步骤1: 初始化事件队列 */
|
Self_Test(); // 执行上电硬件自检 (PWR_HOLD锁存、P0.3 ADC按键常态、P1.3 电池电量)
|
||||||
|
Load_Database(); // 从 IAP Flash 第 254 扇区读取所有已绑定的传感器数据至 RAM
|
||||||
|
|
||||||
|
/* 步骤 1: 初始化事件环形缓冲区队列 */
|
||||||
EventQueue_Init();
|
EventQueue_Init();
|
||||||
|
|
||||||
/* 步骤2+5: 初始化应用管理器 */
|
/* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 HomeApp */
|
||||||
AppManager_Init();
|
AppManager_Init();
|
||||||
|
|
||||||
/* 启动1ms定时器中断 */
|
/* 启动并挂载后台射频监控应用,开始侦听无线传感器信号 */
|
||||||
|
AppManager_AttachToBackground(&rf_monitor_app);
|
||||||
|
|
||||||
|
/* 启动 1ms 系统基准定时器 Timer1 */
|
||||||
Timer1_Init();
|
Timer1_Init();
|
||||||
|
|
||||||
|
/* 开机自动加载并运行待机时钟前台活跃应用 */
|
||||||
|
AppManager_StartApp(APP_ID_HOME);
|
||||||
|
|
||||||
Uart_SendString("[SYS] Event-driven framework ready\r\n");
|
Uart_SendString("[SYS] Event-driven framework ready\r\n");
|
||||||
|
|
||||||
|
// 所有初始化与首帧时钟界面绘制均已完成,此刻才点亮背光,避免开机过程中的中间画面被看到而产生闪烁
|
||||||
|
SGM_CTRL = 0;
|
||||||
|
|
||||||
|
// ====== 主循环 ======
|
||||||
while (1) {
|
while (1) {
|
||||||
/* 串口调试命令 → 以事件方式入队 */
|
/* 0. 检查主循环按键定时标志 (由 1ms 中断置位,主循环响应清除并处理) */
|
||||||
|
if (key_scan_flag) {
|
||||||
|
key_scan_flag = 0;
|
||||||
|
Event_KeyScan_Poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 0.5 检查整秒滴答标志 (由 Clock_IncMS 每秒置位一次),转换为事件入队分发;
|
||||||
|
* 各 App 自行决定要不要用它累计自己的闲置时长 (收到任何按键事件就清零),
|
||||||
|
* 不再由主循环维护一个全局的休眠倒计时。 */
|
||||||
|
if (second_tick_flag) {
|
||||||
|
second_tick_flag = 0;
|
||||||
|
{
|
||||||
|
SystemEvent tick_evt;
|
||||||
|
tick_evt.key_event = KEY_EVENT_SECOND_TICK;
|
||||||
|
tick_evt.priority = EVENT_PRIORITY_LOW;
|
||||||
|
tick_evt.extra_data = 0;
|
||||||
|
tick_evt.extra_size = 0;
|
||||||
|
EventQueue_Push(tick_evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. 串口非阻塞指令捕获 -> 装配为事件方式入队 */
|
||||||
cmd = Uart_RxChar();
|
cmd = Uart_RxChar();
|
||||||
if (cmd != '\0') {
|
if (cmd != '\0') {
|
||||||
Debug_ProcessCommand(cmd);
|
Debug_ProcessCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 步骤3: 事件分发 — 循环处理队列中所有事件 */
|
/* 2. 事件分发总线:循环轮询并处理队列中的所有有效事件 */
|
||||||
while (!EventQueue_IsEmpty()) {
|
while (!EventQueue_IsEmpty()) {
|
||||||
Event_Dispatcher_Loop();
|
Event_Dispatcher_Loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 步骤5: 运行活跃应用 (含CPU占用控制) */
|
/* 3. 应用调度中心:运行当前处于前台活跃状态的应用(集成了 CPU 运行时间超时挂起保护) */
|
||||||
AppManager_RunActiveApp();
|
AppManager_RunActiveApp();
|
||||||
|
|
||||||
/* 步骤6: 超时自动进入深度休眠 */
|
|
||||||
if (inactivity_timer >= 1000) {
|
|
||||||
inactivity_timer = 0;
|
|
||||||
current_state = STATE_SLEEP;
|
|
||||||
Enter_Low_Power_Sleep();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // DEBUG_MINIMAL_BOOT
|
||||||
|
|
||||||
|
// 唤醒源事件标志,由对应的外部中断 ISR 写入
|
||||||
|
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
|
||||||
|
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
|
||||||
|
volatile u8 p3_wakeup_flag = 0; // P3 端口射频中断唤醒标志
|
||||||
|
volatile u8 rf_wakeup_flag = 0; // 射频中断唤醒标志
|
||||||
|
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
* STC32G 端口中断 ISR
|
* STC32G 端口掉电外部中断唤醒中断服务程序 (ISR)
|
||||||
* ========================================================================= */
|
* ========================================================================= */
|
||||||
|
|
||||||
// Port0 中断服务函数 (由 isr_jump.asm 从向量 37 @ 0x012BH 跳转至此)
|
/**
|
||||||
|
* @brief Port0 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
|
||||||
|
* @details 负责处理 P0.1, P0.2, P0.3 按键在休眠模式下的物理触发,并立刻禁用中断防止反复执行。
|
||||||
|
*/
|
||||||
void Port0_Isr(void) interrupt 13
|
void Port0_Isr(void) interrupt 13
|
||||||
{
|
{
|
||||||
EAXFR = 1;
|
EAXFR = 1; // 使能访问扩展寄存区
|
||||||
P0INTF = 0x00; // 清除 P0 端口中断悬挂标志
|
p0_wakeup_flag = P0INTF | 0x01;// 读取并备份 P0 中断标志寄存器值 (0x01作安全掩码)
|
||||||
P0INTE = 0x00; // 立即禁用,防止低电平保持期间反复触发
|
P0INTF = 0x00; // 清除 P0 端口中断悬挂标志位
|
||||||
|
P0INTE = 0x00; // 【关键保护】:立即关闭 P0 中断允许,防止按键处于低电平期间反复触发 ISR 导致堆栈溢出
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port2 中断服务函数 (由 isr_jump.asm 从向量 39 @ 0x013BH 跳转至此)
|
/**
|
||||||
|
* @brief Port2 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
|
||||||
|
* @details 负责处理 P2.6 (SOS按键) 与 P2.1 (射频数据脚) 在休眠模式下的物理跳变唤醒。
|
||||||
|
*/
|
||||||
void Port2_Isr(void) interrupt 15
|
void Port2_Isr(void) interrupt 15
|
||||||
{
|
{
|
||||||
EAXFR = 1;
|
EAXFR = 1; // 使能扩展寄存区
|
||||||
P2INTF = 0x00; // 清除 P2 端口中断悬挂标志
|
p2_wakeup_flag = P2INTF | 0x01;// 读取并备份 P2 中断标志寄存区的值
|
||||||
P2INTE = 0x00; // 立即禁用,防止低电平保持期间反复触发
|
P2INTF = 0x00; // 清除 P2 端口中断悬挂标志位
|
||||||
|
P2INTE = 0x00; // 立即关闭 P2 中断通道,防止低电平期间反复重入
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Port3 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
|
||||||
|
* @details P3.6 (RF_RX_DATA) 单边沿触发中断现在改为常态开启 (见 RF_Init()),身兼两职:
|
||||||
|
* 1. 休眠期间作为唤醒源,记录 p3_wakeup_flag 供 Wakeup_Restore() 判定唤醒来源
|
||||||
|
* (上升沿/下降沿均支持掉电唤醒);
|
||||||
|
* 2. 正常运行期间驱动 RF_HandleEdgeInterrupt() 增量解码 EV1527 信号 (内部会在
|
||||||
|
* 每次触发后软件翻转到相反边沿,模拟双边沿触发)。
|
||||||
|
* 不再像旧版那样在首次触发后自行关闭 P3INTE,否则第 1 项唤醒功能会在第一次边沿后失效。
|
||||||
|
*/
|
||||||
|
void Port3_Isr(void) interrupt 16
|
||||||
|
{
|
||||||
|
EAXFR = 1; // 使能访问扩展寄存器
|
||||||
|
p3_wakeup_flag = P3INTF | 0x01;// 读取并备份 P3 中断标志寄存器值 (0x01作安全掩码)
|
||||||
|
P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
|
||||||
|
RF_HandleEdgeInterrupt(); // 驱动 EV1527 边沿增量解码状态机
|
||||||
}
|
}
|
||||||
|
|||||||
107
App/rgb.c
107
App/rgb.c
@@ -1,6 +1,7 @@
|
|||||||
#include "rgb.h"
|
#include "rgb.h"
|
||||||
|
#include "system.h"
|
||||||
|
|
||||||
/* ====== SPI 寄存器定义 (如果 stc32g.h 没定义) ====== */
|
/* ====== SPI Register Defines (in case stc32g.h lacks them) ====== */
|
||||||
#ifndef P_SW1
|
#ifndef P_SW1
|
||||||
sfr P_SW1 = 0xA2;
|
sfr P_SW1 = 0xA2;
|
||||||
sfr SPCTL = 0xCE;
|
sfr SPCTL = 0xCE;
|
||||||
@@ -10,75 +11,89 @@ sfr SPDAT = 0xCF;
|
|||||||
|
|
||||||
void SPI_Init(void)
|
void SPI_Init(void)
|
||||||
{
|
{
|
||||||
P_SW1 = (P_SW1 & ~0x0C) | 0x04; // 映射 SPI 至第二组 (P2.2~P2.5)
|
P_SW1 = (P_SW1 & ~0x0C) | 0x04;
|
||||||
SPCTL = 0xD1; // SSIG=1, SPEN=1, MSTR=1, SPR0=1 (3.0 MHz)
|
SPCTL = 0xD1;
|
||||||
SPSTAT = 0xC0; // 清除标志
|
SPSTAT = 0xC0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Encode_Byte(u8 val, u8 *out)
|
void Encode_Byte(u8 val, u8 *out)
|
||||||
{
|
{
|
||||||
u32 spi_val = 0;
|
|
||||||
u8 i;
|
u8 i;
|
||||||
for (i = 0; i < 8; i++)
|
for (i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
spi_val <<= 3;
|
u8 pair_val = 0;
|
||||||
if (val & (0x80 >> i))
|
if (val & (0x80 >> (2 * i)))
|
||||||
{
|
{
|
||||||
spi_val |= 6; // 1码 -> 110
|
pair_val |= 0xC0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
spi_val |= 4; // 0码 -> 100
|
pair_val |= 0x80;
|
||||||
}
|
}
|
||||||
|
if (val & (0x80 >> (2 * i + 1)))
|
||||||
|
{
|
||||||
|
pair_val |= 0x0C;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pair_val |= 0x08;
|
||||||
|
}
|
||||||
|
out[i] = pair_val;
|
||||||
}
|
}
|
||||||
out[0] = (u8)(spi_val >> 16);
|
|
||||||
out[1] = (u8)(spi_val >> 8);
|
|
||||||
out[2] = (u8)spi_val;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2)
|
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2)
|
||||||
{
|
{
|
||||||
u8 idata spi_buf[18];
|
u8 idata buf[6];
|
||||||
u8 i;
|
u8 i, mask;
|
||||||
|
buf[0] = g1; buf[1] = r1; buf[2] = b1;
|
||||||
|
buf[3] = g2; buf[4] = r2; buf[5] = b2;
|
||||||
|
|
||||||
// 转码 3-bit 编码
|
EA = 0;
|
||||||
Encode_Byte(g1, &spi_buf[0]);
|
|
||||||
Encode_Byte(r1, &spi_buf[3]);
|
|
||||||
Encode_Byte(b1, &spi_buf[6]);
|
|
||||||
Encode_Byte(g2, &spi_buf[9]);
|
|
||||||
Encode_Byte(r2, &spi_buf[12]);
|
|
||||||
Encode_Byte(b2, &spi_buf[15]);
|
|
||||||
|
|
||||||
EA = 0; // 关中断
|
|
||||||
|
|
||||||
// 临时将 P2.5 (MOTOR) 配置为高阻输入以隔离时钟信号
|
|
||||||
P2M1 |= (1 << 5);
|
|
||||||
P2M0 &= ~(1 << 5);
|
|
||||||
|
|
||||||
SPI_Init(); // 启动 SPI
|
|
||||||
|
|
||||||
for (i = 0; i < 18; i++)
|
|
||||||
{
|
|
||||||
SPDAT = spi_buf[i];
|
|
||||||
while (!(SPSTAT & 0x80)); // 等待发送完毕
|
|
||||||
SPSTAT = 0xC0;
|
|
||||||
}
|
|
||||||
|
|
||||||
SPCTL = 0; // 关闭 SPI
|
|
||||||
|
|
||||||
// 配置 P2.3 (RGB_DIN) 为推挽输出并拉低 100us 做 Reset
|
|
||||||
P2M1 &= ~(1 << 3);
|
P2M1 &= ~(1 << 3);
|
||||||
P2M0 |= (1 << 3);
|
P2M0 |= (1 << 3);
|
||||||
|
|
||||||
|
P2M1 &= ~(1 << 5);
|
||||||
|
P2M0 |= (1 << 5);
|
||||||
|
MOTOR = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
for (mask = 0x80; mask; mask >>= 1)
|
||||||
|
{
|
||||||
|
if (buf[i] & mask)
|
||||||
|
{
|
||||||
|
RGB_DIN = 1;
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
RGB_DIN = 0;
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RGB_DIN = 1;
|
||||||
|
_nop_(); _nop_(); _nop_();
|
||||||
|
RGB_DIN = 0;
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RGB_DIN = 0;
|
RGB_DIN = 0;
|
||||||
for (i = 0; i < 200; i++)
|
for (i = 0; i < 200; i++)
|
||||||
{
|
{
|
||||||
_nop_(); _nop_(); _nop_();
|
_nop_(); _nop_(); _nop_();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复 P2.5 (MOTOR) 为推挽输出以进行振动控制
|
EA = 1;
|
||||||
P2M1 &= ~(1 << 5);
|
}
|
||||||
P2M0 |= (1 << 5);
|
|
||||||
MOTOR = 0;
|
void RGB_Bitbang_Send24(u8 g, u8 r, u8 b)
|
||||||
|
{
|
||||||
EA = 1; // 开中断
|
(void)g; (void)r; (void)b;
|
||||||
}
|
}
|
||||||
|
|||||||
22
App/rgb.h
22
App/rgb.h
@@ -3,10 +3,26 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
// SPI 及 RGB 发送初始化
|
/* ====== SPI 控制与级联 WS2812B (FCOB 幻彩灯条) 驱动接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 SPI 控制器以驱动 RGB 幻彩灯条
|
||||||
|
* @details 将 SPI 引脚切换映射到第二组物理引脚 (P2.2~P2.5),并配置 SPI 模块为
|
||||||
|
* 主机模式 (Master),将通讯时钟速率设置为 3.0 MHz,以利用 SPI 字节发送功能
|
||||||
|
* 高效产生 WS2812B 所需的精确脉冲宽度时序。
|
||||||
|
*/
|
||||||
void SPI_Init(void);
|
void SPI_Init(void);
|
||||||
|
|
||||||
// 向 FCOB 幻彩灯条发送两颗 LED 的 GRB 数据
|
/**
|
||||||
|
* @brief 向 FCOB 幻彩灯条发送两颗级联 LED 灯珠的 GRB 三原色数据
|
||||||
|
* @param g1 LED 1 的绿色亮度 (0~255)
|
||||||
|
* @param r1 LED 1 的红色亮度 (0~255)
|
||||||
|
* @param b1 LED 1 的蓝色亮度 (0~255)
|
||||||
|
* @param g2 LED 2 的绿色亮度 (0~255)
|
||||||
|
* @param r2 LED 2 的红色亮度 (0~255)
|
||||||
|
* @param b2 LED 2 的蓝色亮度 (0~255)
|
||||||
|
* @note 由于 WS2812B 内部芯片使用 GRB 顺序,此函数将色彩数据组合编码后通过 SPI 发送出去。
|
||||||
|
*/
|
||||||
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2);
|
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2);
|
||||||
|
|
||||||
#endif
|
#endif // __RGB_H__
|
||||||
|
|||||||
73
App/strings.h
Normal file
73
App/strings.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#ifndef __STRINGS_H__
|
||||||
|
#define __STRINGS_H__
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// !!!!!! 不要动下面的代码 !!!!!!
|
||||||
|
#ifdef DEFINE_STRING
|
||||||
|
#undef DEFINE_STRING
|
||||||
|
#endif
|
||||||
|
#define DEFINE_STRING(name, fr, en) STR_##name,
|
||||||
|
// !!!!!! 不要动上面的代码 !!!!!!
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 界面全部可翻译文案登记表 (法语/英语)
|
||||||
|
* @details 新增文案:在 WRISTBAND_STRING_LIST 里加一行
|
||||||
|
* DEFINE_STRING(NAME, "法语文案", "English Text")
|
||||||
|
* 取用:Lang_Get(STR_NAME) 返回当前语言下对应的文案指针
|
||||||
|
*/
|
||||||
|
#define WRISTBAND_STRING_LIST \
|
||||||
|
/* ---- 主菜单 (含序号前缀,与原硬编码保持一致的排版风格) ---- */ \
|
||||||
|
DEFINE_STRING(MENU_LANGUAGE, "1. LANGUE", "1. LANGUAGE") \
|
||||||
|
DEFINE_STRING(MENU_HORLOGE, "2. HORLOGE", "2. CLOCK") \
|
||||||
|
DEFINE_STRING(MENU_APPAIRAGE, "3. APPAIRAGE", "3. PAIRING") \
|
||||||
|
DEFINE_STRING(MENU_LIAISON, "4. LIAISON", "4. LINK") \
|
||||||
|
/* ---- 对码二级菜单标题与传感器分类项 ---- */ \
|
||||||
|
DEFINE_STRING(PAIR_MENU_TITLE, "MENU APPAI.", "PAIRING MENU") \
|
||||||
|
DEFINE_STRING(DEV_PORTE, "DETEC. PORTE", "DOOR SENSOR") \
|
||||||
|
DEFINE_STRING(DEV_PIR, "DETEC. PIR", "PIR SENSOR") \
|
||||||
|
DEFINE_STRING(DEV_FUMEE, "DETEC. FUMEE", "SMOKE SENSOR") \
|
||||||
|
DEFINE_STRING(DEV_GAZ, "DETEC. GAZ", "GAS SENSOR") \
|
||||||
|
DEFINE_STRING(DEV_EAU, "DETEC. EAU", "WATER SENSOR") \
|
||||||
|
/* ---- 传感器类型简称 (对码确认页 / 报警页使用) ---- */ \
|
||||||
|
DEFINE_STRING(NAME_PORTE, "PORTE", "DOOR") \
|
||||||
|
DEFINE_STRING(NAME_PIR, "PIR", "PIR") \
|
||||||
|
DEFINE_STRING(NAME_FUMEE, "FUMEE", "SMOKE") \
|
||||||
|
DEFINE_STRING(NAME_GAZ, "GAZ", "GAS") \
|
||||||
|
DEFINE_STRING(NAME_EAU, "EAU", "WATER") \
|
||||||
|
/* ---- 对码流程页面 ---- */ \
|
||||||
|
DEFINE_STRING(PAIRING_TITLE, "APPAIRAGE", "PAIRING") \
|
||||||
|
DEFINE_STRING(PAIR_SEARCHING, "RECHERCHE", "SEARCHING") \
|
||||||
|
DEFINE_STRING(PAIR_TRIGGER, "DECLENCH. CAPT", "TRIGGER SENSOR") \
|
||||||
|
DEFINE_STRING(PAIR_RECEIVED, "v RECU", "v RECEIVED") \
|
||||||
|
DEFINE_STRING(PAIR_CONFIRM, "CONFIRMER", "CONFIRM") \
|
||||||
|
DEFINE_STRING(PAIR_SUCCESS, "SUCCES", "SUCCESS") \
|
||||||
|
DEFINE_STRING(PAIR_SAVED, "ENREGISTRE", "SAVED") \
|
||||||
|
DEFINE_STRING(PAIR_FAILED, "ECHEC", "FAILED") \
|
||||||
|
DEFINE_STRING(PAIR_TIMEOUT, "DELAI DEPASSE", "TIMEOUT") \
|
||||||
|
DEFINE_STRING(PAIR_WRONG_TYPE, "TYPE INCORRECT", "WRONG TYPE") \
|
||||||
|
/* ---- 报警 / SOS 页面 ---- */ \
|
||||||
|
DEFINE_STRING(ALARM_INTRUSION, "INTRUSION!", "INTRUSION!") \
|
||||||
|
DEFINE_STRING(SOS_TITLE, "SOS", "SOS") \
|
||||||
|
DEFINE_STRING(SOS_SENDING, "ENVOI", "SENDING") \
|
||||||
|
DEFINE_STRING(SOS_CALLING, "APPEL...", "CALLING...") \
|
||||||
|
/* ---- 摄像机绑定页面 ---- */ \
|
||||||
|
DEFINE_STRING(LINK_TITLE, "LIAISON", "LINK") \
|
||||||
|
DEFINE_STRING(BIND_SENDING, "ENVOI EN COURS", "SENDING...") \
|
||||||
|
/* ---- 时钟页公共元素 ---- */ \
|
||||||
|
DEFINE_STRING(CLOCK_AMPM, "AM", "AM") \
|
||||||
|
/* ---- 星期缩写 (顺序必须是周日~周六,与 current_weekday 的 0~6 对齐,取用时按 STR_WEEKDAY_SUN + current_weekday 直接索引,顺序不能打乱) ---- */ \
|
||||||
|
DEFINE_STRING(WEEKDAY_SUN, "DIM", "SUN") \
|
||||||
|
DEFINE_STRING(WEEKDAY_MON, "LUN", "MON") \
|
||||||
|
DEFINE_STRING(WEEKDAY_TUE, "MAR", "TUE") \
|
||||||
|
DEFINE_STRING(WEEKDAY_WED, "MER", "WED") \
|
||||||
|
DEFINE_STRING(WEEKDAY_THU, "JEU", "THU") \
|
||||||
|
DEFINE_STRING(WEEKDAY_FRI, "VEN", "FRI") \
|
||||||
|
DEFINE_STRING(WEEKDAY_SAT, "SAM", "SAT")
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WRISTBAND_STRING_LIST
|
||||||
|
STR_COUNT
|
||||||
|
} StringID;
|
||||||
|
|
||||||
|
#endif // __STRINGS_H__
|
||||||
700
App/system.c
700
App/system.c
@@ -1,327 +1,513 @@
|
|||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "rgb.h"
|
#include "rgb.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
|
#include "clock.h"
|
||||||
#include "../Drivers/lcd.h"
|
#include "../Drivers/lcd.h"
|
||||||
|
#include "../Drivers/rf.h"
|
||||||
|
#include "../Drivers/adc.h"
|
||||||
|
#include "../Drivers/pcf8563.h"
|
||||||
|
#include "intrins.h"
|
||||||
|
|
||||||
|
// 引用 main.c 中的全局唤醒与运行状态变量
|
||||||
|
extern volatile u8 p0_wakeup_flag;
|
||||||
|
extern volatile u8 p2_wakeup_flag;
|
||||||
|
extern volatile u8 p3_wakeup_flag;
|
||||||
|
extern volatile u8 rf_wakeup_flag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统 GPIO 端口模式初始化
|
||||||
|
* @details 配置各引脚的推挽输出、高阻输入及准双向模式,建立系统初始电气环境。
|
||||||
|
*/
|
||||||
void GPIO_Init(void)
|
void GPIO_Init(void)
|
||||||
{
|
{
|
||||||
IE = 0x00;
|
// 使能访问扩展特殊功能寄存器 XSFR
|
||||||
IE2 = 0x00;
|
EAXFR = 1;
|
||||||
TCON = 0x00;
|
|
||||||
TMOD &= 0xF0;
|
|
||||||
TR0 = 0;
|
|
||||||
ET0 = 0;
|
|
||||||
AUXR = 0x00;
|
|
||||||
INTCLKO = 0x00;
|
|
||||||
P_SW1 = 0x00;
|
|
||||||
P_SW2 = 0x80; // 开启 EAXFR=1,允许访问 P0PU/P2PU/P0INTE/P2INTE 等扩展 SFR 寄存器
|
|
||||||
P_SW3 = 0x00;
|
|
||||||
|
|
||||||
// 配置 P0.0 (BAT_ADC) 为高阻输入模式
|
// 1. 配置 PWR_HOLD (P0.0) 为推挽输出模式 (P0M1 bit0=0, P0M0 bit0=1)
|
||||||
P0M1 |= (1 << 0);
|
P0M1 &= ~(1 << 0);
|
||||||
P0M0 &= ~(1 << 0);
|
P0M0 |= (1 << 0);
|
||||||
|
PWR_HOLD = 1; // 上电第一时间拉高,锁存电源保持供电
|
||||||
|
|
||||||
// 配置 P0.1 (KEY_UP), P0.2 (KEY_CONFIRM), P0.3 (KEY_DOWN) 为准双向口模式并置 1
|
// 2. 配置 KEY_UP (P0.1), KEY_CONFIRM (P0.2) 为高阻输入/带上拉准双向
|
||||||
P0M1 &= ~((1 << 1) | (1 << 2) | (1 << 3));
|
P0M1 &= ~((1 << 1) | (1 << 2));
|
||||||
P0M0 &= ~((1 << 1) | (1 << 2) | (1 << 3));
|
P0M0 &= ~((1 << 1) | (1 << 2));
|
||||||
KEY_UP = 1;
|
KEY_UP = 1;
|
||||||
KEY_CONFIRM = 1;
|
KEY_CONFIRM = 1;
|
||||||
KEY_DOWN = 1;
|
|
||||||
|
|
||||||
// 配置 P2.3 (RGB_DIN) 为推挽输出模式
|
// 3. 初始化 STC32G 硬件 ADC 模块 (其中自动配置 P0.3 和 P1.3 为高阻输入模式)
|
||||||
P2M1 &= ~(1 << 3);
|
ADC_Init();
|
||||||
P2M0 |= (1 << 3);
|
|
||||||
RGB_DIN = 0;
|
|
||||||
|
|
||||||
// 配置 P2.5 (MOTOR) 为推挽输出模式
|
// 4. 配置 PCF8563 I2C 管脚 P3.2 (SCL) 与 P3.3 (SDA) 为准双向口模式,并开上拉
|
||||||
P2M1 &= ~(1 << 5);
|
P3M1 &= ~((1 << 2) | (1 << 3));
|
||||||
P2M0 |= (1 << 5);
|
P3M0 &= ~((1 << 2) | (1 << 3));
|
||||||
|
P3PU |= (1 << 2) | (1 << 3); // 开启 P3.2 与 P3.3 的内部 4.7K 上拉电阻
|
||||||
|
RTC_SCL = 1;
|
||||||
|
RTC_SDA = 1;
|
||||||
|
|
||||||
|
// 5. 配置推挽输出引脚:
|
||||||
|
// MOTOR (P2.5), RGB_DIN (P2.3) -> P2M1 bit5,3=0; P2M0 bit5,3=1
|
||||||
|
P2M1 &= ~((1 << 5) | (1 << 3));
|
||||||
|
P2M0 |= ((1 << 5) | (1 << 3));
|
||||||
MOTOR = 0;
|
MOTOR = 0;
|
||||||
|
RGB_DIN = 0; // P2.3 初始拉低,防止上电期间 WS2812B 误触发
|
||||||
|
|
||||||
// 配置 P2.6 (KEY_SOS) 为准双向口模式并置 1
|
// 配置 KEY_SOS (P2.6) 为带上拉准双向口模式以确保输入稳定
|
||||||
P2M1 &= ~(1 << 6);
|
P2M1 &= ~(1 << 6);
|
||||||
P2M0 &= ~(1 << 6);
|
P2M0 &= ~(1 << 6);
|
||||||
KEY_SOS = 1;
|
KEY_SOS = 1;
|
||||||
|
(*(unsigned char volatile xdata *)0xFE12) |= (1 << 6); // 使能 P2.6 的强内部上拉电阻 (P2PU = 0xFE12)
|
||||||
|
|
||||||
// 配置 P2.2 (SHUT) 为推挽输出模式
|
// SGM_CTRL (P1.7), LCD 控制管脚 (P1.0, P1.1, P1.4, P1.5, P1.6) 置为推挽输出
|
||||||
P2M1 &= ~(1 << 2);
|
P1M1 &= ~((1 << 7) | (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 6));
|
||||||
P2M0 |= (1 << 2);
|
P1M0 |= ((1 << 7) | (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 6));
|
||||||
SHUT = 1; // 默认拉高开启射频接收芯片工作
|
SGM_CTRL = 1; // 背光从上电第一刻起就确定关闭 (LEDA=1),避免推挽模式生效前电平不确定造成瞬间闪烁
|
||||||
|
|
||||||
// 开启按键引脚 P0.1, P0.2, P0.3 和 P2.6 的内部上拉电阻,避免引脚抖动误唤醒
|
|
||||||
P0PU |= 0x0E;
|
|
||||||
P2PU |= 0x40;
|
|
||||||
|
|
||||||
// 配置 P2.7 (DET) 为高阻输入模式
|
|
||||||
P2M1 |= (1 << 7);
|
|
||||||
P2M0 &= ~(1 << 7);
|
|
||||||
|
|
||||||
// 配置 P3.0 (TXD), P3.1 (RXD) 和 P3.4 (SGM_CTRL)
|
// SHUT (P3.5) 射频/外设开启使能脚置为推挽输出
|
||||||
P3M1 &= ~((1 << 0) | (1 << 1) | (1 << 4));
|
P3M1 &= ~(1 << 5);
|
||||||
P3M0 &= ~(1 << 0);
|
P3M0 |= (1 << 5);
|
||||||
P3M0 |= ((1 << 1) | (1 << 4));
|
SHUT = 0; // 低电平开启工作
|
||||||
}
|
|
||||||
|
// 7. 配置 CHRG_DET (P1.2) 为高阻输入模式,适配 DET 模拟 ADC 通道 2
|
||||||
void Delay_ms(u16 ms)
|
// 由于是精密模拟电平检测,切勿开启引脚内部上拉电阻
|
||||||
{
|
P1M1 |= (1 << 2);
|
||||||
u16 i, j;
|
P1M0 &= ~(1 << 2);
|
||||||
for (i = 0; i < ms; i++)
|
// P1PU 在扩展 XSFR 空间中没有被定义的话可以用 P1 端口寄存器或者是关闭上拉,
|
||||||
for (j = 12000; j > 0; j--);
|
// STC32G 中端口上拉寄存器是 P1PU (FE11H),因为是 XSFR 我们在 config.h 里有 extern 或直接操作
|
||||||
}
|
// 扩展 XSFR P1PU 地址为 0xFE11
|
||||||
|
(*(unsigned char volatile xdata *)0xFE11) &= ~(1 << 2);
|
||||||
void Delay10us(void)
|
|
||||||
{
|
|
||||||
unsigned char data i;
|
|
||||||
_nop_();
|
|
||||||
i = 30;
|
|
||||||
while (--i);
|
|
||||||
}
|
|
||||||
|
|
||||||
u16 GetTimer0_Safe(void)
|
|
||||||
{
|
|
||||||
u8 h1, l, h2;
|
|
||||||
do {
|
|
||||||
h1 = TH0;
|
|
||||||
l = TL0;
|
|
||||||
h2 = TH0;
|
|
||||||
} while (h1 != h2);
|
|
||||||
return ((u16)h1 << 8) | l;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Delay_us(u16 us)
|
|
||||||
{
|
|
||||||
u16 ticks = (u16)((u32)us * (MAIN_Fosc / 1000000UL) / 12UL);
|
|
||||||
TL0 = 0;
|
|
||||||
TH0 = 0;
|
|
||||||
TR0 = 1;
|
|
||||||
while (GetTimer0_Safe() < ticks);
|
|
||||||
TR0 = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 UART1 串口通信
|
||||||
|
* @details 设定波特率 115200 (使用 Timer2 作为发生器,24.0MHz 晶振)
|
||||||
|
*/
|
||||||
void Uart1_Init(void)
|
void Uart1_Init(void)
|
||||||
{
|
{
|
||||||
u16 reload = (u16)(65536UL - (MAIN_Fosc / 4 / 115200UL));
|
SCON = 0x50; // 8 位数据, 可变波特率
|
||||||
SCON = 0x50;
|
AUXR |= 0x01; // 串口 1 选择 Timer2 做波特率发生器
|
||||||
AUXR |= 0x01;
|
AUXR |= 0x04; // Timer2 时钟为 1T 模式
|
||||||
AUXR |= 0x04;
|
T2L = 0xCC; // 24MHz / 115200 = 208 (0xD0), 对应 reload 值 65536 - 52 = 0xFFCC
|
||||||
T2L = (u8)reload;
|
T2H = 0xFF;
|
||||||
T2H = (u8)(reload >> 8);
|
AUXR |= 0x10; // 启动 Timer2 运行
|
||||||
AUXR |= 0x10;
|
|
||||||
TI = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 发送单字节数据
|
||||||
|
* @param dat 待发送的字节
|
||||||
|
*/
|
||||||
void Uart_SendByte(u8 dat)
|
void Uart_SendByte(u8 dat)
|
||||||
{
|
{
|
||||||
REN = 0;
|
|
||||||
SBUF = dat;
|
SBUF = dat;
|
||||||
while (!TI);
|
while (!TI);
|
||||||
TI = 0;
|
TI = 0;
|
||||||
RI = 0;
|
|
||||||
REN = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 发送字符串
|
||||||
|
* @param s 字符串指针
|
||||||
|
*/
|
||||||
void Uart_SendString(char *s)
|
void Uart_SendString(char *s)
|
||||||
{
|
{
|
||||||
REN = 0;
|
|
||||||
while (*s) {
|
while (*s) {
|
||||||
SBUF = *s++;
|
Uart_SendByte(*s++);
|
||||||
while (!TI);
|
|
||||||
TI = 0;
|
|
||||||
}
|
}
|
||||||
RI = 0;
|
|
||||||
REN = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 非阻塞接收字符
|
||||||
|
* @return char 接收到的字符,若无接收数据则返回 '\0'
|
||||||
|
*/
|
||||||
char Uart_RxChar(void)
|
char Uart_RxChar(void)
|
||||||
{
|
{
|
||||||
if (RI) {
|
if (RI) {
|
||||||
char c = SBUF;
|
|
||||||
RI = 0;
|
RI = 0;
|
||||||
return c;
|
return SBUF;
|
||||||
}
|
}
|
||||||
return 0;
|
return '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 阻塞毫秒延时
|
||||||
|
* @param ms 毫秒数
|
||||||
|
*/
|
||||||
|
void Delay_ms(u16 ms)
|
||||||
|
{
|
||||||
|
u16 i, j;
|
||||||
|
for (i = 0; i < ms; i++) {
|
||||||
|
for (j = 0; j < 2000; j++) {
|
||||||
|
_nop_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 阻塞微秒延时
|
||||||
|
* @param us 微秒数
|
||||||
|
*/
|
||||||
|
void Delay_us(u16 us)
|
||||||
|
{
|
||||||
|
u16 i;
|
||||||
|
for (i = 0; i < us; i++) {
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统开机上电硬件自检函数
|
||||||
|
* @details 检测 PWR_HOLD 锁存状态、ADC 采样基准与串口通讯,输出完整自检 report
|
||||||
|
*/
|
||||||
|
void Self_Test(void)
|
||||||
|
{
|
||||||
|
u16 key_down_adc;
|
||||||
|
u16 vbat_mv;
|
||||||
|
u16 batt_adc_raw;
|
||||||
|
u8 percent;
|
||||||
|
|
||||||
|
Uart_SendString("\r\n========================================\r\n");
|
||||||
|
Uart_SendString("[SELF-TEST] System Power-On Self Test...\r\n");
|
||||||
|
|
||||||
|
#if USE_PCF8563
|
||||||
|
// 执行 I2C 总线器件扫描,查找 RTC 是否物理响应
|
||||||
|
PCF8563_ScanDiagnostic();
|
||||||
|
|
||||||
|
// 0. I2C 硬件脚电平诊断测试 (验证引脚可控性与短路)
|
||||||
|
Uart_SendString("[I2C-DIAG] Pin Level Test Starting...\r\n");
|
||||||
|
// 写 1 测试 (准双向+上拉)
|
||||||
|
P3M1 &= ~((1 << 2) | (1 << 3));
|
||||||
|
P3M0 &= ~((1 << 2) | (1 << 3));
|
||||||
|
P3PU |= (1 << 2) | (1 << 3);
|
||||||
|
RTC_SCL = 1;
|
||||||
|
RTC_SDA = 1;
|
||||||
|
Delay_ms(10);
|
||||||
|
Uart_SendString("[I2C-DIAG] Write SCL=1, SDA=1 -> Real Level: SCL=");
|
||||||
|
Uart_SendByte((u8)(RTC_SCL ? '1' : '0'));
|
||||||
|
Uart_SendString(", SDA=");
|
||||||
|
Uart_SendByte((u8)(RTC_SDA ? '1' : '0'));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
|
// 写 0 测试
|
||||||
|
RTC_SCL = 0;
|
||||||
|
RTC_SDA = 0;
|
||||||
|
Delay_ms(10);
|
||||||
|
Uart_SendString("[I2C-DIAG] Write SCL=0, SDA=0 -> Real Level: SCL=");
|
||||||
|
Uart_SendByte((u8)(RTC_SCL ? '1' : '0'));
|
||||||
|
Uart_SendString(", SDA=");
|
||||||
|
Uart_SendByte((u8)(RTC_SDA ? '1' : '0'));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
|
// 0.2 执行 RTC 底层单独试读测试
|
||||||
|
PCF8563_TryReadDiagnostic();
|
||||||
|
#else
|
||||||
|
Uart_SendString("[SELF-TEST] USE_PCF8563=0, skipping RTC/I2C diagnostics.\r\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 1. 验证 PWR_HOLD 锁存状态
|
||||||
|
if (PWR_HOLD == 1) {
|
||||||
|
Uart_SendString("[SELF-TEST] PWR_HOLD Pin State: HIGH [OK]\r\n");
|
||||||
|
} else {
|
||||||
|
Uart_SendString("[SELF-TEST] PWR_HOLD Pin State: LOW [FAIL]\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查 P0.3 ADC 按键基准常态电压
|
||||||
|
key_down_adc = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN);
|
||||||
|
Uart_SendString("[SELF-TEST] KEY_DOWN (P0.3) ADC Raw Value: ");
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (key_down_adc % 10)));
|
||||||
|
Uart_SendString(" [OK]\r\n");
|
||||||
|
|
||||||
|
// 2.2 检测 KEY_SOS (P2.6) 物理电平常态
|
||||||
|
Uart_SendString("[SELF-TEST] KEY_SOS (P2.6) Pin Level: ");
|
||||||
|
Uart_SendByte((u8)(KEY_SOS ? '1' : '0'));
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
|
// 3. 检查 P1.3 电池电压检测与百分比
|
||||||
|
vbat_mv = ADC_GetBatteryVoltage_mV();
|
||||||
|
percent = ADC_GetBatteryPercent();
|
||||||
|
batt_adc_raw = ADC_ReadFiltered(ADC_CHANNEL_BATTERY);
|
||||||
|
|
||||||
|
Uart_SendString("[SELF-TEST] Battery Raw ADC: ");
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (batt_adc_raw % 10)));
|
||||||
|
|
||||||
|
Uart_SendString(" -> Voltage: ");
|
||||||
|
Uart_SendByte((u8)('0' + (vbat_mv / 1000) % 10));
|
||||||
|
Uart_SendByte('.');
|
||||||
|
Uart_SendByte((u8)('0' + (vbat_mv / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (vbat_mv / 10) % 10));
|
||||||
|
|
||||||
|
Uart_SendString(" V, Capacity: ");
|
||||||
|
Uart_SendByte((u8)('0' + (percent / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (percent / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (percent % 10)));
|
||||||
|
Uart_SendString("% [OK]\r\n");
|
||||||
|
|
||||||
|
// 4. 检查充电状态
|
||||||
|
{
|
||||||
|
u16 det_adc = ADC_ReadFiltered(ADC_CHANNEL_CHRG_DET);
|
||||||
|
u16 det_mv = (det_adc * 3300UL) / 4095UL;
|
||||||
|
Uart_SendString("[SELF-TEST] CHRG_DET (P1.2) ADC: ");
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc / 1000) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc / 10) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_adc % 10)));
|
||||||
|
Uart_SendString(" -> Voltage: ");
|
||||||
|
Uart_SendByte((u8)('0' + (det_mv / 1000) % 10));
|
||||||
|
Uart_SendByte('.');
|
||||||
|
Uart_SendByte((u8)('0' + (det_mv / 100) % 10));
|
||||||
|
Uart_SendByte((u8)('0' + (det_mv / 10) % 10));
|
||||||
|
Uart_SendString(" V -> ");
|
||||||
|
if (det_mv >= 800 && det_mv <= 1800) {
|
||||||
|
Uart_SendString("CHARGING [OK]\r\n");
|
||||||
|
} else if (det_mv >= 2200 && det_mv <= 2800) {
|
||||||
|
Uart_SendString("FULLY CHARGED [OK]\r\n");
|
||||||
|
} else {
|
||||||
|
Uart_SendString("NOT CHARGING [OK]\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uart_SendString("[SELF-TEST] Self Test Completed Successfully!\r\n");
|
||||||
|
Uart_SendString("========================================\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行关机断电流程
|
||||||
|
* @details 关断外设、震动提示并拉低 PWR_HOLD 引脚切断总电源
|
||||||
|
*/
|
||||||
|
void Power_Off(void)
|
||||||
|
{
|
||||||
|
Uart_SendString("[SYS] Executing Power Off sequence...\r\n");
|
||||||
|
|
||||||
|
// 马达震动反馈 100ms
|
||||||
|
MOTOR = 1;
|
||||||
|
Delay_ms(100);
|
||||||
|
MOTOR = 0;
|
||||||
|
|
||||||
|
// 关断屏幕背光与 RGB 灯光
|
||||||
|
SGM_CTRL = 1; // 高电平断电,关闭背光灭屏
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 拉低 PWR_HOLD 切断电源锁存使总电源关闭
|
||||||
|
PWR_HOLD = 0;
|
||||||
|
|
||||||
|
// 进入死循环等待电源物理切断
|
||||||
|
while (1) {
|
||||||
|
PWR_HOLD = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统切入低功耗停机休眠模式
|
||||||
|
*/
|
||||||
|
void Enter_Low_Power_Sleep(void)
|
||||||
|
{
|
||||||
|
Uart_SendString("[SYS] Entering low power sleep...\r\n");
|
||||||
|
|
||||||
|
// 1. 关闭/熄灭 RGB 灯
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 2. 向 LCD 芯片写入 Display OFF 指令 (0x28) 关显示
|
||||||
|
WriteComm(0x28);
|
||||||
|
Delay_ms(20);
|
||||||
|
|
||||||
|
// 3. 向 LCD 芯片写入 Sleep In 指令 (0x10) 关内部电荷泵
|
||||||
|
WriteComm(0x10);
|
||||||
|
Delay_ms(20);
|
||||||
|
|
||||||
|
// 4. 关闭背光(高电平断电,关闭背光灭屏)
|
||||||
|
SGM_CTRL = 1;
|
||||||
|
|
||||||
|
// 5. 屏幕接口防漏电处理
|
||||||
|
LCD_CS = 1;
|
||||||
|
LCD_RST = 1;
|
||||||
|
LCD_DCX = 0;
|
||||||
|
LCD_SCL = 0;
|
||||||
|
LCD_SDI = 0;
|
||||||
|
|
||||||
|
// 6. 射频设置为关闭接收模式(低功耗,SHUT=0, RF_SetMode(0))
|
||||||
|
RF_SetMode(0);
|
||||||
|
|
||||||
|
// 使能访问扩展特殊功能寄存器
|
||||||
|
P_SW2 |= 0x80;
|
||||||
|
|
||||||
|
// 7. 配置 Port0 中断唤醒源 (KEY_UP=P0.1, KEY_CONFIRM=P0.2, KEY_DOWN=P0.3)
|
||||||
|
P0IM1 |= 0x0E;
|
||||||
|
P0IM0 &= ~0x0E; // 下降沿触发
|
||||||
|
P0INTF = 0x00; // 清除挂起的中断标志
|
||||||
|
P0INTE |= 0x0E; // 开启 P0.1, P0.2, P0.3 中断允许
|
||||||
|
P0WKUE |= 0x0E; // 使能 P0.1, P0.2, P0.3 掉电唤醒
|
||||||
|
|
||||||
|
// 8. 配置 Port2 中断唤醒源 (SOS=P2.6)
|
||||||
|
P2IM1 |= 0x40;
|
||||||
|
P2IM0 &= ~0x40; // 下降沿触发
|
||||||
|
P2INTF = 0x00; // 清除挂起标志
|
||||||
|
P2INTE |= 0x40; // 开启 P2.6 中断允许
|
||||||
|
P2WKUE |= 0x40; // 使能 P2.6 掉电唤醒
|
||||||
|
|
||||||
|
// 9. 配置 Port3 掉电唤醒源 (RF_RX_DATA=P3.6)
|
||||||
|
// RF_HandleEdgeInterrupt() 用软件乒乓单边沿切换模拟双边沿触发,运行期间 P3IM0 会在
|
||||||
|
// 上升沿/下降沿之间动态翻转;如果休眠前不重新固定它,进入 Power Down 那一刻 P3IM0
|
||||||
|
// 具体停在哪个方向是不确定的,可能导致唤醒不了或唤醒延迟。根据《STC32G 技术手册》
|
||||||
|
// 15.1.3 节真值表,PxIM1/PxIM0 的四种模式里只有下降沿(0,0)和上升沿(0,1)两种边沿模式
|
||||||
|
// 标注支持掉电唤醒,高/低电平两种模式都不支持——所以这里没有改成电平触发,而是强制
|
||||||
|
// 固定为上升沿 (与 RF_Init() 开机时的初始配置一致),确保休眠那一刻处于一个明确的、
|
||||||
|
// 手册标注支持唤醒的状态;唤醒后 P3IM0 就停在上升沿,与开机状态相同,解码状态机从
|
||||||
|
// 一个已知状态重新开始,不需要额外恢复。
|
||||||
|
P3IM1 &= ~0x40;
|
||||||
|
P3IM0 |= 0x40; // (0,1) 上升沿触发
|
||||||
|
P3INTF = 0x00; // 清除挂起标志
|
||||||
|
P3WKUE |= 0x40; // 使能 P3.6 掉电唤醒
|
||||||
|
|
||||||
|
// 确保引脚上拉和输入使能,防止因浮空产生的功耗泄漏
|
||||||
|
P2PU |= 0x40;
|
||||||
|
P2IE |= 0x40;
|
||||||
|
P3PU &= ~0x40;
|
||||||
|
P3IE |= 0x40;
|
||||||
|
|
||||||
|
// 清零各个唤醒检测标志位
|
||||||
|
P0INTF = 0x00;
|
||||||
|
P2INTF = 0x00;
|
||||||
|
P3INTF = 0x00;
|
||||||
|
|
||||||
|
p0_wakeup_flag = 0;
|
||||||
|
p2_wakeup_flag = 0;
|
||||||
|
p3_wakeup_flag = 0;
|
||||||
|
rf_wakeup_flag = 0;
|
||||||
|
|
||||||
|
// 10. 关闭系统滴答定时器 Timer1 与 RF 时间戳基准 Timer0 的中断,防止休眠时被定时器误唤醒
|
||||||
|
// (Power Down 模式下主振荡器本应整体停振,Timer0/1 都会随之停止计数;这里仍显式关闭
|
||||||
|
// 中断允许作为防御性保险,与 ET1 的处理方式保持一致)
|
||||||
|
ET0 = 0;
|
||||||
|
ET1 = 0;
|
||||||
|
EA = 1; // 开启全局中断
|
||||||
|
|
||||||
|
// 配置 PCON 挂起单片机,切入停机 (Power Down) 模式
|
||||||
|
PCON |= 0x02; // PD = 1
|
||||||
|
_nop_(); _nop_(); _nop_(); _nop_();
|
||||||
|
|
||||||
|
// ====== 被外部中断唤醒后,在此处继续运行 ======
|
||||||
|
Wakeup_Restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 休眠唤醒恢复
|
||||||
|
*/
|
||||||
|
void Wakeup_Restore(void)
|
||||||
|
{
|
||||||
|
EAXFR = 1;
|
||||||
|
// 1. 立即关闭 P0/P2 掉电外部中断允许与全部唤醒允许,防止重入;
|
||||||
|
// P3INTE 的 P3.6 位不清零 —— 该中断常态用于正常运行期间的 RF 边沿解码,唤醒后仍需保持开启
|
||||||
|
P0INTE = 0x00;
|
||||||
|
P2INTE = 0x00;
|
||||||
|
P3INTE = 0x40;
|
||||||
|
// P3.6 的悬空钳位已改用内部下拉 (P3PD,在 RF_Init() 里一次性永久开启,Power Down 期间
|
||||||
|
// SFR 状态保持不变,无需在这里重新配置);此处不再恢复上拉,避免上拉与下拉同时使能相互打架
|
||||||
|
|
||||||
|
P0WKUE = 0x00;
|
||||||
|
P2WKUE = 0x00;
|
||||||
|
P3WKUE = 0x00;
|
||||||
|
P0INTF = 0x00;
|
||||||
|
P2INTF = 0x00;
|
||||||
|
P3INTF = 0x00;
|
||||||
|
|
||||||
|
// 2. 清零掉电唤醒定时器
|
||||||
|
WKTCH = 0x00;
|
||||||
|
WKTCL = 0x00;
|
||||||
|
|
||||||
|
// 3. 开启全局中断并重启系统滴答定时器 Timer1 与 RF 时间戳基准 Timer0
|
||||||
|
EA = 1;
|
||||||
|
ET0 = 1;
|
||||||
|
ET1 = 1;
|
||||||
|
|
||||||
|
// 4. 恢复射频芯片工作模式
|
||||||
|
RF_SetMode(1);
|
||||||
|
|
||||||
|
// 打印唤醒调试日志
|
||||||
|
if ((p0_wakeup_flag & 0x0E) || (p2_wakeup_flag & 0x40)) {
|
||||||
|
Uart_SendString("[WR] Woken by KEY (P0=");
|
||||||
|
Uart_SendHex8(p0_wakeup_flag);
|
||||||
|
Uart_SendString(", P2=");
|
||||||
|
Uart_SendHex8(p2_wakeup_flag);
|
||||||
|
Uart_SendString(")\r\n");
|
||||||
|
} else if ((p3_wakeup_flag & 0x40) || rf_wakeup_flag) {
|
||||||
|
rf_wakeup_flag = 1;
|
||||||
|
Uart_SendString("[WR] Woken by RF (P3.6)!\r\n");
|
||||||
|
} else {
|
||||||
|
Uart_SendString("[WR] Woken by TIMER/Other\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 恢复屏幕供电与背光(低电平上电,恢复背光亮屏)
|
||||||
|
SGM_CTRL = 0;
|
||||||
|
|
||||||
|
// 6. 延时 200ms 等待液晶稳压板上电稳定后,重新对 LCD 寄存器初始化
|
||||||
|
Delay_ms(200);
|
||||||
|
LCD_Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 安全获取 Timer0 计数器值 (防高字节滚动跳变)
|
||||||
|
*/
|
||||||
|
u16 GetTimer0_Safe(void)
|
||||||
|
{
|
||||||
|
u8 high1, high2, low;
|
||||||
|
do {
|
||||||
|
high1 = TH0;
|
||||||
|
low = TL0;
|
||||||
|
high2 = TH0;
|
||||||
|
} while (high1 != high2);
|
||||||
|
return (((u16)high1 << 8) | low);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 发送 4 位十六进制字符 (0x0~0xF)
|
||||||
|
*/
|
||||||
void Uart_SendHex4(u8 val)
|
void Uart_SendHex4(u8 val)
|
||||||
{
|
{
|
||||||
val &= 0x0F;
|
val &= 0x0F;
|
||||||
if (val < 10)
|
if (val < 10) {
|
||||||
Uart_SendByte((u8)('0' + val));
|
Uart_SendByte('0' + val);
|
||||||
else
|
} else {
|
||||||
Uart_SendByte((u8)('A' + (val - 10)));
|
Uart_SendByte('A' + (val - 10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 发送 8 位十六进制 (0x00~0xFF)
|
||||||
|
*/
|
||||||
void Uart_SendHex8(u8 val)
|
void Uart_SendHex8(u8 val)
|
||||||
{
|
{
|
||||||
Uart_SendHex4(val >> 4);
|
Uart_SendHex4(val >> 4);
|
||||||
Uart_SendHex4(val);
|
Uart_SendHex4(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Uart_SendHex20(u32 val)
|
/**
|
||||||
{
|
* @brief 串口 1 发送 16 位十六进制 (0x0000~0xFFFF)
|
||||||
Uart_SendHex4((u8)(val >> 16));
|
*/
|
||||||
Uart_SendHex8((u8)(val >> 8));
|
|
||||||
Uart_SendHex8((u8)val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Uart_SendHex32(u32 val)
|
|
||||||
{
|
|
||||||
Uart_SendHex8((u8)(val >> 24));
|
|
||||||
Uart_SendHex8((u8)(val >> 16));
|
|
||||||
Uart_SendHex8((u8)(val >> 8));
|
|
||||||
Uart_SendHex8((u8)val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Uart_SendHex16(u16 val)
|
void Uart_SendHex16(u16 val)
|
||||||
{
|
{
|
||||||
Uart_SendHex8((u8)(val >> 8));
|
Uart_SendHex8((u8)(val >> 8));
|
||||||
Uart_SendHex8((u8)val);
|
Uart_SendHex8((u8)val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FormatHex(u32 val, char *buf)
|
/**
|
||||||
|
* @brief 串口 1 发送 20 位十六进制 (用于射频 5 位 ID 打印)
|
||||||
|
*/
|
||||||
|
void Uart_SendHex20(u32 val)
|
||||||
{
|
{
|
||||||
u8 i;
|
Uart_SendHex4((u8)(val >> 16));
|
||||||
buf[0] = 'A'; buf[1] = 'D'; buf[2] = 'D'; buf[3] = 'R'; buf[4] = ':';
|
Uart_SendHex16((u16)val);
|
||||||
buf[5] = ' '; buf[6] = '0'; buf[7] = 'x';
|
|
||||||
for (i = 0; i < 5; i++) {
|
|
||||||
u8 nibble = (val >> (4 * (4 - i))) & 0x0F;
|
|
||||||
if (nibble < 10)
|
|
||||||
buf[8 + i] = '0' + nibble;
|
|
||||||
else
|
|
||||||
buf[8 + i] = 'A' + (nibble - 10);
|
|
||||||
}
|
|
||||||
buf[13] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Enter_Low_Power_Sleep(void)
|
/**
|
||||||
|
* @brief 串口 1 发送 32 位十六进制
|
||||||
|
*/
|
||||||
|
void Uart_SendHex32(u32 val)
|
||||||
{
|
{
|
||||||
// 串口打印进入休眠提示
|
Uart_SendHex16((u16)(val >> 16));
|
||||||
Uart_SendString("[SYS] Entering low power sleep...\r\n");
|
Uart_SendHex16((u16)val);
|
||||||
|
|
||||||
// 1. 熄灭 FCOB 双幻彩灯条
|
|
||||||
RGB_Send(0, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
// 2. 向屏幕发送 Display OFF (0x28) 彻底关闭像素发光显示
|
|
||||||
WriteComm(0x28);
|
|
||||||
Delay_ms(20);
|
|
||||||
|
|
||||||
// 3. AMOLED 屏控制器写入 Sleep In (0x10) 睡眠指令
|
|
||||||
WriteComm(0x10);
|
|
||||||
Delay_ms(20);
|
|
||||||
|
|
||||||
// 4. 拉低 SGM_CTRL 彻底断开 SGM3833 负压升压芯片的供电
|
|
||||||
SGM_CTRL = 0;
|
|
||||||
|
|
||||||
// 5. 将 AMOLED 控制总线所有 IO 拉低,以防由于 IO 寄生二极管对屏幕倒灌电导致常亮
|
|
||||||
LCD_CS = 0;
|
|
||||||
LCD_RST = 0;
|
|
||||||
LCD_DCX = 0;
|
|
||||||
LCD_SCL = 0;
|
|
||||||
LCD_SDI = 0;
|
|
||||||
|
|
||||||
// 6. 休眠状态下不能关闭接收芯片,保持 SHUT 为高电平 (1) 工作状态
|
|
||||||
SHUT = 1;
|
|
||||||
|
|
||||||
// 确保开启扩展寄存器访问 (EAXFR = 1),以便能正确配置端口唤醒使能扩展寄存器
|
|
||||||
P_SW2 |= 0x80;
|
|
||||||
|
|
||||||
// 7. 配置 P0.1, P0.2, P0.3 为 低电平触发 唤醒
|
|
||||||
P0IM1 |= 0x0E;
|
|
||||||
P0IM0 &= ~0x0E;
|
|
||||||
P0INTE |= 0x0E;
|
|
||||||
|
|
||||||
// 8. 配置 P2.6 (KEY_SOS) 为 低电平触发 唤醒
|
|
||||||
P2IM1 |= 0x40;
|
|
||||||
P2IM0 &= ~0x40;
|
|
||||||
P2INTE |= 0x40;
|
|
||||||
|
|
||||||
// ===== 配置端口掉电唤醒使能寄存器 PxWKUE =====
|
|
||||||
P0WKUE |= 0x0E; // 使使能 P0.1/P0.2/P0.3 掉电唤醒
|
|
||||||
P2WKUE |= 0x40; // 使使能 P2.6 掉电唤醒
|
|
||||||
|
|
||||||
// 9. 不允许外部中断 2 (RF_RX_DATA) 沿触发唤醒源
|
|
||||||
EX2 = 0;
|
|
||||||
|
|
||||||
// 10. 清除端口中断的悬挂/标志位,防误触
|
|
||||||
P0INTF = 0x00;
|
|
||||||
P2INTF = 0x00;
|
|
||||||
|
|
||||||
// 11. 关闭 Timer1 中断,保持 EA=1 让端口中断能唤醒
|
|
||||||
ET1 = 0;
|
|
||||||
EA = 1;
|
|
||||||
|
|
||||||
// 12. 写入 PCON 掉电模式位,使 MCU 进入深度 Power-Down 挂起状态
|
|
||||||
Uart_SendString("[SYS] Entering Power-Down mode...\r\n");
|
|
||||||
PCON |= 0x02; // PD = 1
|
|
||||||
_nop_();
|
|
||||||
_nop_();
|
|
||||||
_nop_();
|
|
||||||
_nop_();
|
|
||||||
|
|
||||||
// 13. 唤醒并执行完 ISR 后,CPU 从这里继续执行
|
|
||||||
Wakeup_Restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Wakeup_Restore(void)
|
|
||||||
{
|
|
||||||
u8 p0_flag;
|
|
||||||
u8 p2_flag;
|
|
||||||
|
|
||||||
// 确保 EAXFR=1 才能访问扩展 SFR (P0INTF/P2INTF/P0INTE/P2INTE)
|
|
||||||
EAXFR = 1;
|
|
||||||
|
|
||||||
// [诊断] 在清除标志位之前先读取,判断唤醒来源
|
|
||||||
p0_flag = P0INTF;
|
|
||||||
p2_flag = P2INTF;
|
|
||||||
|
|
||||||
// 1. 立即禁用端口中断和唤醒,防止继续触发
|
|
||||||
P0INTE = 0x00;
|
|
||||||
P2INTE = 0x00;
|
|
||||||
P0WKUE = 0x00; // 清除 P0 唤醒允许
|
|
||||||
P2WKUE = 0x00; // 清除 P2 唤醒允许
|
|
||||||
P0INTF = 0x00;
|
|
||||||
P2INTF = 0x00;
|
|
||||||
EX2 = 0;
|
|
||||||
|
|
||||||
// 1b. 关闭掉电唤醒定时器
|
|
||||||
WKTCH = 0x00;
|
|
||||||
WKTCL = 0x00;
|
|
||||||
|
|
||||||
// 2. 重新开启全局中断与 Timer1 中断
|
|
||||||
EA = 1;
|
|
||||||
ET1 = 1;
|
|
||||||
|
|
||||||
// [诊断] 打印唤醒来源
|
|
||||||
if (p0_flag & 0x0E) {
|
|
||||||
Uart_SendString("[WR] Woken by KEY P0 (P0INTF=0x");
|
|
||||||
Uart_SendHex8(p0_flag);
|
|
||||||
Uart_SendString(")\r\n");
|
|
||||||
} else if (p2_flag & 0x40) {
|
|
||||||
Uart_SendString("[WR] Woken by KEY P2 (P2INTF=0x");
|
|
||||||
Uart_SendHex8(p2_flag);
|
|
||||||
Uart_SendString(")\r\n");
|
|
||||||
} else {
|
|
||||||
Uart_SendString("[WR] Woken by TIMER (no key flag)\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 保持 SHUT 开启射频接收芯片工作
|
|
||||||
SHUT = 1;
|
|
||||||
|
|
||||||
// 4. 拉高 SGM_CTRL 并发送脉冲使能 SGM3833 升压
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
SGM_SendPulse(27);
|
|
||||||
|
|
||||||
// 5. 等待负压电轨充分稳定
|
|
||||||
Delay_ms(200);
|
|
||||||
|
|
||||||
// 6. 重做屏控制器 RM69310 寄存器组的初始化
|
|
||||||
LCD_Init();
|
|
||||||
|
|
||||||
// 7. 重置闲置倒计时
|
|
||||||
inactivity_timer = 0;
|
|
||||||
|
|
||||||
// 8. 直接绘制时钟界面(绕过 AppManager 早期返回保护)
|
|
||||||
current_state = STATE_NORMAL;
|
|
||||||
UI_ShowClockPage(current_state, current_hour, current_min);
|
|
||||||
Uart_SendString("[SYS] Wakeup restored!\r\n");
|
|
||||||
}
|
}
|
||||||
|
|||||||
111
App/system.h
111
App/system.h
@@ -3,29 +3,130 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
// GPIO 与系统外设初始化
|
/* ====== GPIO 与外设初始化接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统 GPIO 端口模式初始化配置
|
||||||
|
* @details 将按键引脚 (KEY_UP, KEY_DOWN, KEY_CONFIRM, KEY_SOS) 配置为高阻输入模式,
|
||||||
|
* 并将输出控制引脚 (MOTOR, RGB_DIN, SGM_CTRL, SHUT, 以及 LCD 屏幕 SPI 管脚)
|
||||||
|
* 配置为推挽输出模式,建立系统初始电气环境。
|
||||||
|
*/
|
||||||
void GPIO_Init(void);
|
void GPIO_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 UART1 (串口 1)
|
||||||
|
* @details 设定波特率为 115200 bps (使用定时器 2 做波特率发生器,1T 模式,系统时钟为 24.0 MHz),
|
||||||
|
* 主要用于输出系统调试日志、监控按键与射频接收状态。
|
||||||
|
*/
|
||||||
void Uart1_Init(void);
|
void Uart1_Init(void);
|
||||||
|
|
||||||
// 延时与时间工具函数
|
|
||||||
|
/* ====== 阻塞式精确延时工具函数 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 阻塞毫秒级延时函数
|
||||||
|
* @param ms 延时时长 (单位: 毫秒)
|
||||||
|
*/
|
||||||
void Delay_ms(u16 ms);
|
void Delay_ms(u16 ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 阻塞 10 微秒延时函数 (汇编级精确延时)
|
||||||
|
*/
|
||||||
void Delay10us(void);
|
void Delay10us(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 阻塞微秒级延时函数
|
||||||
|
* @param us 延时时长 (单位: 微秒)
|
||||||
|
*/
|
||||||
void Delay_us(u16 us);
|
void Delay_us(u16 us);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取安全的 Timer0 计数器值 (暂未使用)
|
||||||
|
*/
|
||||||
u16 GetTimer0_Safe(void);
|
u16 GetTimer0_Safe(void);
|
||||||
|
|
||||||
// 串口数据打印函数
|
|
||||||
|
/* ====== 串口数据打印与日志辅助函数 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 发送 1 个字节数据
|
||||||
|
* @param dat 待发送的字节数据
|
||||||
|
*/
|
||||||
void Uart_SendByte(u8 dat);
|
void Uart_SendByte(u8 dat);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 发送以 '\0' 结尾的字符串
|
||||||
|
* @param s 目标字符串指针
|
||||||
|
*/
|
||||||
void Uart_SendString(char *s);
|
void Uart_SendString(char *s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 非阻塞接收字符
|
||||||
|
* @return char 接收到的字符。若接收缓冲区为空,则立即返回 '\0'。
|
||||||
|
*/
|
||||||
char Uart_RxChar(void);
|
char Uart_RxChar(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 打印输出 4 位十六进制数 (半字节,0x0~0xF)
|
||||||
|
*/
|
||||||
void Uart_SendHex4(u8 val);
|
void Uart_SendHex4(u8 val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 打印输出 8 位十六进制数 (单字节,0x00~0xFF)
|
||||||
|
*/
|
||||||
void Uart_SendHex8(u8 val);
|
void Uart_SendHex8(u8 val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 打印输出 16 位十六进制数 (双字节,0x0000~0xFFFF)
|
||||||
|
*/
|
||||||
void Uart_SendHex16(u16 val);
|
void Uart_SendHex16(u16 val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 打印输出 20 位十六进制数 (主要用于射频解码输出,5位十六进制格式)
|
||||||
|
*/
|
||||||
void Uart_SendHex20(u32 val);
|
void Uart_SendHex20(u32 val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 串口 1 打印输出 32 位十六进制数 (双字,8位十六进制格式)
|
||||||
|
*/
|
||||||
void Uart_SendHex32(u32 val);
|
void Uart_SendHex32(u32 val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 格式化 32 位数为 8 位十六进制字符串
|
||||||
|
* @param val 要格式化的 32 位数值
|
||||||
|
* @param buf 输出目标字符数组指针 (至少保留 9 字节空间)
|
||||||
|
*/
|
||||||
void FormatHex(u32 val, char *buf);
|
void FormatHex(u32 val, char *buf);
|
||||||
|
|
||||||
// 电源管理与休眠唤醒
|
|
||||||
|
/* ====== 电源管理与休眠/唤醒流程接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 系统切入低功耗深度休眠 (Power-Down Sleep)
|
||||||
|
* @details 负责关闭马达与灯光,保存当前运行的 App 现场,关闭屏幕的供电与复位,
|
||||||
|
* 并将 GPIO 端口的按键外部中断唤醒源以及射频中断唤醒源进行配置,
|
||||||
|
* 最终拉低 SHUT,置位单片机的 PCON 寄存器切入停机低功耗状态。
|
||||||
|
*/
|
||||||
void Enter_Low_Power_Sleep(void);
|
void Enter_Low_Power_Sleep(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从低功耗休眠被中断唤醒后的系统恢复流程
|
||||||
|
* @details 当检测到外部中断触发硬件唤醒后,首先将供电芯片使能拉高(SHUT=1),
|
||||||
|
* 阻塞延时 50ms 避开冲击电流浪涌,随后重新初始化屏幕 RM69310 的全部控制寄存器,
|
||||||
|
* 并重置闲置休眠计数器,启动时钟应用恢复主界面的正常显示与事件分发。
|
||||||
|
*/
|
||||||
void Wakeup_Restore(void);
|
void Wakeup_Restore(void);
|
||||||
|
|
||||||
#endif
|
/**
|
||||||
|
* @brief 系统开机上电硬件自检函数
|
||||||
|
* @details 检测 PWR_HOLD 锁存状态、ADC 采样基准与串口通讯,输出完整自检 report
|
||||||
|
*/
|
||||||
|
void Self_Test(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行关机断电流程
|
||||||
|
* @details 关断外设、震动提示并拉低 PWR_HOLD 引脚切断总电源
|
||||||
|
*/
|
||||||
|
void Power_Off(void);
|
||||||
|
|
||||||
|
#endif // __SYSTEM_H__
|
||||||
|
|||||||
487
App/ui.c
487
App/ui.c
@@ -1,145 +1,291 @@
|
|||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
|
#include "database.h"
|
||||||
|
#include "language.h"
|
||||||
#include "../Drivers/lcd.h"
|
#include "../Drivers/lcd.h"
|
||||||
|
#include "../Drivers/adc.h"
|
||||||
|
|
||||||
// 引入外部菜单项定义
|
// 引入外部主菜单对码项名称的字符串 ID 表 (例如门磁/PIR/烟感等防区类型)
|
||||||
extern char *code menu_items[5];
|
extern const StringID device_menu_ids[5];
|
||||||
|
|
||||||
void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
|
// 底部日期栏固定用 8x16 小字体、LCD_ShowStringCentered 居中绘制在 y=195;
|
||||||
|
// 编辑日期时对 日/月/年 做局部刷新需要精确知道每个字段的像素 X 坐标,这里按
|
||||||
|
// LCD_ShowStringCentered 的居中公式 (LCD_WIDTH - 字符数*8) / 2 手工算好,
|
||||||
|
// 前提是字符串格式固定为 "WWW DD-MM-YY" (12 个字符),不能随意改动格式。
|
||||||
|
#define DATE_BAR_Y 195
|
||||||
|
#define DATE_BAR_START_X 19 // (135 - 12*8) / 2 = 19
|
||||||
|
#define DATE_BAR_DAY_X (DATE_BAR_START_X + 4 * 8) // 第 4 个字符起 = 51
|
||||||
|
#define DATE_BAR_MONTH_X (DATE_BAR_START_X + 7 * 8) // 第 7 个字符起 = 75
|
||||||
|
#define DATE_BAR_YEAR_X (DATE_BAR_START_X + 10 * 8) // 第 10 个字符起 = 99
|
||||||
|
#define DATE_BAR_FIELD_W 16 // 2 位数字,8px/字符
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 拼出"星期缩写 日-月"格式的日期字符串 (如 "VEN 10-07"),供 HOME 页时钟显示
|
||||||
|
* @param out 输出缓冲区,调用方需保证至少 10 字节 (3字母+空格+2位+'-'+2位+'\0')
|
||||||
|
*/
|
||||||
|
static void Build_DateString(char *out, u8 weekday, u8 day, u8 month)
|
||||||
|
{
|
||||||
|
char *wd = Lang_Get((StringID)(STR_WEEKDAY_SUN + weekday));
|
||||||
|
u8 i = 0;
|
||||||
|
while (*wd) {
|
||||||
|
out[i++] = *wd++;
|
||||||
|
}
|
||||||
|
out[i++] = ' ';
|
||||||
|
out[i++] = '0' + (day / 10);
|
||||||
|
out[i++] = '0' + (day % 10);
|
||||||
|
out[i++] = '-';
|
||||||
|
out[i++] = '0' + (month / 10);
|
||||||
|
out[i++] = '0' + (month % 10);
|
||||||
|
out[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 拼出"星期缩写 日-月-年"格式的日期字符串 (如 "VEN 10-07-25"),供时间设置页编辑日期时显示
|
||||||
|
* @param out 输出缓冲区,调用方需保证至少 13 字节 (3字母+空格+2位+'-'+2位+'-'+2位+'\0')
|
||||||
|
*/
|
||||||
|
static void Build_DateStringWithYear(char *out, u8 weekday, u8 day, u8 month, u8 year)
|
||||||
|
{
|
||||||
|
char *wd = Lang_Get((StringID)(STR_WEEKDAY_SUN + weekday));
|
||||||
|
u8 i = 0;
|
||||||
|
while (*wd) {
|
||||||
|
out[i++] = *wd++;
|
||||||
|
}
|
||||||
|
out[i++] = ' ';
|
||||||
|
out[i++] = '0' + (day / 10);
|
||||||
|
out[i++] = '0' + (day % 10);
|
||||||
|
out[i++] = '-';
|
||||||
|
out[i++] = '0' + (month / 10);
|
||||||
|
out[i++] = '0' + (month % 10);
|
||||||
|
out[i++] = '-';
|
||||||
|
out[i++] = '0' + (year / 10);
|
||||||
|
out[i++] = '0' + (year % 10);
|
||||||
|
out[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染并显示系统待机时钟主页面
|
||||||
|
* @param state 当前的系统布防状态 (STATE_ARMED: 已布防, 否则为正常或撤防)
|
||||||
|
* @param hour 当前待显示的小时数 (0~23)
|
||||||
|
* @param min 当前待显示的分钟数 (0~59)
|
||||||
|
* @param weekday 当前星期 (0=周日, ..., 6=周六)
|
||||||
|
* @param day 当前日 (1~31)
|
||||||
|
* @param month 当前月 (1~12)
|
||||||
|
* @details 该页面被划分为三大功能视觉区:
|
||||||
|
* 1. 顶部状态栏:左侧绘制布撤防指示锁,右侧显示真实剩余电量百分比以及电池单色位图。
|
||||||
|
* 2. 中部核心栏:垂直居中以 16x32 大号粗字体绘制格式化时间 "HH:MM",在其下方标注 AM/PM 标识。
|
||||||
|
* 3. 底部信息栏:使用灰色小字模绘制真实星期日期 "VEN 10-07" (星期五 7月10日)。
|
||||||
|
*/
|
||||||
|
void UI_ShowClockPage(SystemState state, u8 hour, u8 min, u8 weekday, u8 day, u8 month)
|
||||||
{
|
{
|
||||||
char time_str[6];
|
char time_str[6];
|
||||||
|
char date_str[10];
|
||||||
|
char batt_str[7];
|
||||||
|
u8 batt;
|
||||||
|
|
||||||
|
// 清屏,底色填充为纯黑
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
// 左上角锁图标
|
// ====== 绘制左上角布撤防图标 ======
|
||||||
if (state == STATE_ARMED)
|
if (state == STATE_ARMED)
|
||||||
{
|
{
|
||||||
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED);
|
// 状态: ARMED (已布防) -> 绘制红色闭合锁头
|
||||||
LCD_FillRect(15, 15, 2, 8, COLOR_RED);
|
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED); // 锁身矩形
|
||||||
LCD_FillRect(17, 15, 10, 2, COLOR_RED);
|
LCD_DrawRectBorder(14, 15, 12, 9, COLOR_RED); // 闭合状态的拱形锁环
|
||||||
LCD_FillRect(25, 15, 2, 8, COLOR_RED);
|
LCD_FillRect(18, 28, 4, 5, COLOR_RED); // 锁芯暗扣
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN);
|
// 状态: NORMAL / DISARMED (非布防) -> 绘制绿色开启锁头
|
||||||
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN);
|
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN); // 锁身矩形
|
||||||
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN);
|
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN); // 左锁环
|
||||||
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN);
|
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN); // 锁环顶
|
||||||
|
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); // 开启状态的偏置锁环
|
||||||
}
|
}
|
||||||
|
|
||||||
// 右上角电量
|
// ====== 绘制右上角真实电池电量 ======
|
||||||
LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK);
|
batt = ADC_GetBatteryPercent();
|
||||||
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
|
if (batt >= 100) {
|
||||||
|
batt_str[0] = '1'; batt_str[1] = '0'; batt_str[2] = '0'; batt_str[3] = '%'; batt_str[4] = '\0';
|
||||||
|
} else {
|
||||||
|
batt_str[0] = '0' + (batt / 10);
|
||||||
|
batt_str[1] = '0' + (batt % 10);
|
||||||
|
batt_str[2] = '%';
|
||||||
|
batt_str[3] = '\0';
|
||||||
|
}
|
||||||
|
// 充电中则在百分比后追加 '+' 后缀
|
||||||
|
if (Is_Charging()) {
|
||||||
|
u8 len = (batt >= 100) ? 4 : 3;
|
||||||
|
batt_str[len] = '+';
|
||||||
|
batt_str[len + 1] = '\0';
|
||||||
|
}
|
||||||
|
LCD_ShowString(70, 24, batt_str, COLOR_GRAY, COLOR_BLACK); // 渲染真实动态百分比
|
||||||
|
LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池图标
|
||||||
|
|
||||||
// 中间时间
|
// ====== 绘制中部时间 "HH:MM" ======
|
||||||
time_str[0] = '0' + (hour / 10);
|
time_str[0] = '0' + (hour / 10); // 小时十位
|
||||||
time_str[1] = '0' + (hour % 10);
|
time_str[1] = '0' + (hour % 10); // 小时个位
|
||||||
time_str[2] = ':';
|
time_str[2] = ':'; // 冒号分隔符
|
||||||
time_str[3] = '0' + (min / 10);
|
time_str[3] = '0' + (min / 10); // 分钟十位
|
||||||
time_str[4] = '0' + (min % 10);
|
time_str[4] = '0' + (min % 10); // 分钟个位
|
||||||
time_str[5] = '\0';
|
time_str[5] = '\0'; // 字符串终止字符
|
||||||
LCD_ShowString16x32Centered(100, time_str, COLOR_WHITE, COLOR_BLACK);
|
LCD_ShowString16x32Centered(100, time_str, COLOR_WHITE, COLOR_BLACK); // 使用 16x32 特大字体居中绘制时间
|
||||||
|
|
||||||
// AM/PM
|
// ====== 绘制 AM/PM 状态 ======
|
||||||
LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK);
|
LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK);
|
||||||
|
|
||||||
// 日期 (法语)
|
// ====== 绘制日期星期栏 (真实日期,不再是写死的占位文案) ======
|
||||||
LCD_ShowStringCentered(195, "VEN 10-07", COLOR_GRAY, COLOR_BLACK);
|
Build_DateString(date_str, weekday, day, month);
|
||||||
|
LCD_ShowStringCentered(DATE_BAR_Y, date_str, COLOR_GRAY, COLOR_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示传感器对码二级菜单页面
|
||||||
|
* @param selected_index 当前聚焦选中的菜单行索引 (0~4,分别对应 5 种传感器)
|
||||||
|
* @details 采用典型的纵向菜单滚轮特效:
|
||||||
|
* - 中间 110 行高度为当前选中行,使用青色框高亮圈起并用青色绘制名称。
|
||||||
|
* - 上下邻近行显示为暗灰色,且无边框保护,营造层次感。
|
||||||
|
*/
|
||||||
void UI_ShowPairMenuPage(u8 selected_index)
|
void UI_ShowPairMenuPage(u8 selected_index)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
LCD_ShowStringCentered(30, "MENU APPAI.", COLOR_AMBER, COLOR_BLACK);
|
|
||||||
|
// 绘制标题 "MENU APPAI." (配对/对码设置菜单)
|
||||||
|
LCD_ShowStringCentered(30, Lang_Get(STR_PAIR_MENU_TITLE), COLOR_AMBER, COLOR_BLACK);
|
||||||
|
|
||||||
|
// 渲染上方相邻项 (如果当前选择的不是第一行)
|
||||||
if (selected_index > 0) {
|
if (selected_index > 0) {
|
||||||
LCD_ShowStringCentered(78, menu_items[selected_index - 1], COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowStringCentered(78, Lang_Get(device_menu_ids[selected_index - 1]), COLOR_GRAY, COLOR_BLACK);
|
||||||
}
|
}
|
||||||
LCD_DrawRectBorder(8, 110, 104, 28, COLOR_CYAN);
|
|
||||||
LCD_ShowStringCentered(118, menu_items[selected_index], COLOR_CYAN, COLOR_BLACK);
|
// 渲染当前选中的高亮中间项
|
||||||
|
LCD_DrawRectBorder(15, 110, 104, 28, COLOR_CYAN); // 青色对齐外框 (在 135px 宽度下居中)
|
||||||
|
LCD_ShowStringCentered(118, Lang_Get(device_menu_ids[selected_index]), COLOR_CYAN, COLOR_BLACK);
|
||||||
|
|
||||||
|
// 渲染下方相邻项 (如果当前选择的不是最后一行)
|
||||||
if (selected_index < 4) {
|
if (selected_index < 4) {
|
||||||
LCD_ShowStringCentered(158, menu_items[selected_index + 1], COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowStringCentered(158, Lang_Get(device_menu_ids[selected_index + 1]), COLOR_GRAY, COLOR_BLACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染正在搜索配对信号的雷达扫描等待页面
|
||||||
|
* @param frame_index 当前的雷达波纹帧索引号 (0~2 帧循环)
|
||||||
|
* @details 在中央以青色渲染雷达同心圆扩散动图,并在底部提示 "DECLENCH. CAPT" (请触发传感器发射)
|
||||||
|
*/
|
||||||
void UI_ShowPairWaitPage(u8 frame_index)
|
void UI_ShowPairWaitPage(u8 frame_index)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
LCD_ShowStringCentered(35, "APPAIRAGE", COLOR_GRAY, COLOR_BLACK);
|
|
||||||
|
|
||||||
|
// 绘制头部 "APPAIRAGE" (对码中)
|
||||||
|
LCD_ShowStringCentered(35, Lang_Get(STR_PAIRING_TITLE), COLOR_GRAY, COLOR_BLACK);
|
||||||
|
|
||||||
|
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉 (在 135px 下居中 X = 51)
|
||||||
if (frame_index == 0)
|
if (frame_index == 0)
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK);
|
||||||
else if (frame_index == 1)
|
else if (frame_index == 1)
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f2, COLOR_CYAN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f2, COLOR_CYAN, COLOR_BLACK);
|
||||||
else
|
else
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
||||||
|
|
||||||
LCD_ShowStringCentered(165, "RECHERCHE", COLOR_WHITE, COLOR_BLACK);
|
// 绘制状态描述
|
||||||
LCD_ShowStringCentered(200, "DECLENCH. CAPT", COLOR_CYAN, COLOR_BLACK);
|
LCD_ShowStringCentered(165, Lang_Get(STR_PAIR_SEARCHING), COLOR_WHITE, COLOR_BLACK); // 搜寻中
|
||||||
|
LCD_ShowStringCentered(200, Lang_Get(STR_PAIR_TRIGGER), COLOR_CYAN, COLOR_BLACK); // 请触发传感器以发射射频信号
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染捕获到传感器地址后的微调确认页面
|
||||||
|
* @param type 已解码出的传感器硬件类型 (0:门磁, 1:PIR, 2:烟感, 3:燃气, 4:水浸)
|
||||||
|
* @param zone_index 当前配置的防区编号 (1~99),由用户上下键修改微调
|
||||||
|
*/
|
||||||
void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
|
void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
|
||||||
{
|
{
|
||||||
char zone_str[3];
|
char zone_str[3];
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
LCD_ShowStringCentered(32, "v RECU", COLOR_GREEN, COLOR_BLACK);
|
// 顶部显示捕获回馈标志 "v RECU" (信号已接收)
|
||||||
|
LCD_ShowStringCentered(32, Lang_Get(STR_PAIR_RECEIVED), COLOR_GREEN, COLOR_BLACK);
|
||||||
|
|
||||||
|
// 在上部居中绘制设备类别所对应的图形 (135px 下居中 X = 51)
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK); // 门磁图标
|
||||||
} else if (type == 2 || type == 3) {
|
} else if (type == 2 || type == 3) {
|
||||||
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK); // 警号喇叭图标
|
||||||
} else {
|
} else {
|
||||||
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK); // 锁型常规图标
|
||||||
}
|
}
|
||||||
|
|
||||||
LCD_ShowStringCentered(105, sensor_names_fr[type], COLOR_WHITE, COLOR_BLACK);
|
// 渲染传感器类型的文字
|
||||||
|
LCD_ShowStringCentered(105, Lang_Get(sensor_type_name_ids[type]), COLOR_WHITE, COLOR_BLACK);
|
||||||
|
|
||||||
LCD_DrawRectBorder(15, 140, 90, 44, COLOR_CYAN);
|
// 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (在 135px 下居中 X=22, 宽度 90)
|
||||||
|
LCD_DrawRectBorder(22, 140, 90, 44, COLOR_CYAN);
|
||||||
zone_str[0] = '0' + (zone_index / 10);
|
zone_str[0] = '0' + (zone_index / 10);
|
||||||
zone_str[1] = '0' + (zone_index % 10);
|
zone_str[1] = '0' + (zone_index % 10);
|
||||||
zone_str[2] = '\0';
|
zone_str[2] = '\0';
|
||||||
LCD_ShowString16x32(44, 146, zone_str, COLOR_CYAN, COLOR_BLACK);
|
LCD_ShowString16x32(51, 146, zone_str, COLOR_CYAN, COLOR_BLACK); // 渲染两位防区编号 (135px 下居中 X = 51)
|
||||||
|
|
||||||
LCD_ShowStringCentered(215, "CONFIRMER", COLOR_GRAY, COLOR_BLACK);
|
// 底部提示按键动作
|
||||||
|
LCD_ShowStringCentered(215, Lang_Get(STR_PAIR_CONFIRM), COLOR_GRAY, COLOR_BLACK); // 短按以确认保存
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示配对成功反馈页
|
||||||
|
*/
|
||||||
void UI_ShowPairSuccessPage(void)
|
void UI_ShowPairSuccessPage(void)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK);
|
// 渲染 32x32 对勾图标 (135px 下居中 X = 51)
|
||||||
LCD_ShowStringCentered(145, "SUCCES", COLOR_GREEN, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK);
|
||||||
LCD_ShowStringCentered(180, "ENREGISTRE", COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowStringCentered(145, Lang_Get(STR_PAIR_SUCCESS), COLOR_GREEN, COLOR_BLACK); // 成功
|
||||||
|
LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_SAVED), COLOR_GRAY, COLOR_BLACK); // 配置已保存
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示配对失败页面
|
||||||
|
* @param is_timeout 1表示超时退出的失败,0表示其他对码错误
|
||||||
|
*/
|
||||||
void UI_ShowPairFailPage(u8 is_timeout)
|
void UI_ShowPairFailPage(u8 is_timeout)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK);
|
// 渲染 32x32 红色叉号图标 (135px 下居中 X = 51)
|
||||||
LCD_ShowStringCentered(145, "ECHEC", COLOR_RED, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK);
|
||||||
|
LCD_ShowStringCentered(145, Lang_Get(STR_PAIR_FAILED), COLOR_RED, COLOR_BLACK); // 失败
|
||||||
if (is_timeout) {
|
if (is_timeout) {
|
||||||
LCD_ShowStringCentered(180, "DELAI DEPASSE", COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_TIMEOUT), COLOR_GRAY, COLOR_BLACK); // 对码超时
|
||||||
} else {
|
} else {
|
||||||
LCD_ShowStringCentered(180, "TYPE INCORRECT", COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_WRONG_TYPE), COLOR_GRAY, COLOR_BLACK); // 传感器类型不符
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示传感器入侵警报页面
|
||||||
|
* @param type 触发报警的传感器类型 (0~4)
|
||||||
|
* @param zone_index 报警传感器的防区号 (1~99, 0)
|
||||||
|
* @details 在界面外侧四周圈起醒目的双重红色警告边框 (COLOR_RED),
|
||||||
|
* 中间绘制设备对应的大警告图标,并在下方用特大红字标明防区及设备类型(如 "PORTE 02"),
|
||||||
|
* 最下方渲染白色粗体 "INTRUSION!" (入侵警告) 提示。
|
||||||
|
*/
|
||||||
void UI_ShowAlarmPage(u8 type, u8 zone_index)
|
void UI_ShowAlarmPage(u8 type, u8 zone_index)
|
||||||
{
|
{
|
||||||
char zone_info[20];
|
char zone_info[20];
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
// 绘制双重红色爆闪粗边界框 (在新宽 135px 下对称扩展)
|
||||||
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
|
LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
|
||||||
|
LCD_DrawRectBorder(6, 6, 123, 228, COLOR_RED);
|
||||||
|
|
||||||
|
// 绘制报警设备的图形 (135px 下居中 X = 51)
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK); // 门磁报警
|
||||||
} else if (type == 2 || type == 3) {
|
} else if (type == 2 || type == 3) {
|
||||||
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK); // 烟感/燃气报警
|
||||||
} else {
|
} else {
|
||||||
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK);
|
LCD_DrawMonoBitmap(51, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK); // 其他防区报警
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拼接字符串: "设备类型名 两位防区号"
|
||||||
{
|
{
|
||||||
u8 len = 0;
|
u8 len = 0;
|
||||||
char *p = sensor_names_fr[type];
|
char *p = Lang_Get(sensor_type_name_ids[type]);
|
||||||
while(*p) {
|
while(*p) {
|
||||||
zone_info[len++] = *p++;
|
zone_info[len++] = *p++;
|
||||||
}
|
}
|
||||||
@@ -148,46 +294,59 @@ void UI_ShowAlarmPage(u8 type, u8 zone_index)
|
|||||||
zone_info[len++] = '0' + (zone_index % 10);
|
zone_info[len++] = '0' + (zone_index % 10);
|
||||||
zone_info[len] = '\0';
|
zone_info[len] = '\0';
|
||||||
}
|
}
|
||||||
LCD_ShowStringCentered(135, zone_info, COLOR_RED, COLOR_BLACK);
|
LCD_ShowStringCentered(135, zone_info, COLOR_RED, COLOR_BLACK); // 红字绘制设备防区
|
||||||
LCD_ShowStringCentered(175, "INTRUSION!", COLOR_WHITE, COLOR_BLACK);
|
LCD_ShowStringCentered(175, Lang_Get(STR_ALARM_INTRUSION), COLOR_WHITE, COLOR_BLACK); // 白字入侵提示
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示 SOS 主动求救发送状态页面
|
||||||
|
* @details 绘制红、白双色三层粗边界警告,中央绘制细致的警徽警灯图案,
|
||||||
|
* 绘制红色闪烁求助状态字样 "SOS ENVOI" (求救发送中...) 和 "APPEL..." (呼叫中)。
|
||||||
|
*/
|
||||||
void UI_ShowSosPage(void)
|
void UI_ShowSosPage(void)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
|
|
||||||
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED);
|
|
||||||
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE);
|
|
||||||
|
|
||||||
// 警灯组合绘制
|
// 绘制红白双层紧急警示框 (在新宽 135px 下对称扩展)
|
||||||
LCD_FillRect(44, 45, 32, 2, COLOR_RED);
|
LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
|
||||||
LCD_FillRect(48, 47, 24, 12, COLOR_RED);
|
LCD_DrawRectBorder(5, 5, 125, 230, COLOR_RED);
|
||||||
LCD_FillRect(40, 59, 40, 4, COLOR_GRAY);
|
LCD_DrawRectBorder(8, 8, 119, 224, COLOR_WHITE);
|
||||||
LCD_FillRect(57, 51, 6, 6, COLOR_WHITE);
|
|
||||||
LCD_FillRect(58, 36, 4, 6, COLOR_RED);
|
|
||||||
LCD_FillRect(46, 38, 4, 4, COLOR_RED);
|
|
||||||
LCD_FillRect(70, 38, 4, 4, COLOR_RED);
|
|
||||||
|
|
||||||
LCD_ShowStringCentered(120, "SOS", COLOR_RED, COLOR_BLACK);
|
// ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 (在新宽 135px 下整体右移 7 像素) ======
|
||||||
LCD_ShowStringCentered(150, "ENVOI", COLOR_RED, COLOR_BLACK);
|
LCD_FillRect(51, 45, 32, 2, COLOR_RED); // 警灯顶部
|
||||||
LCD_ShowStringCentered(195, "APPEL...", COLOR_WHITE, COLOR_BLACK);
|
LCD_FillRect(55, 47, 24, 12, COLOR_RED); // 警灯红色灯罩
|
||||||
|
LCD_FillRect(47, 59, 40, 4, COLOR_GRAY); // 警灯灰色底座
|
||||||
|
LCD_FillRect(64, 51, 6, 6, COLOR_WHITE); // 中间高亮光影
|
||||||
|
LCD_FillRect(65, 36, 4, 6, COLOR_RED); // 发射的垂直红色闪光
|
||||||
|
LCD_FillRect(53, 38, 4, 4, COLOR_RED); // 左侧散射闪光
|
||||||
|
LCD_FillRect(77, 38, 4, 4, COLOR_RED); // 右侧散射闪光
|
||||||
|
|
||||||
|
// 绘制求救中文字样
|
||||||
|
LCD_ShowStringCentered(120, Lang_Get(STR_SOS_TITLE), COLOR_RED, COLOR_BLACK);
|
||||||
|
LCD_ShowStringCentered(150, Lang_Get(STR_SOS_SENDING), COLOR_RED, COLOR_BLACK); // 求救中
|
||||||
|
LCD_ShowStringCentered(195, Lang_Get(STR_SOS_CALLING), COLOR_WHITE, COLOR_BLACK); // 紧急呼叫中
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示与网络摄像机 (IPC) 进行一键绑定配对的进度画面
|
||||||
|
* @param addr 手环自身的 24 位克隆出厂硬件地址
|
||||||
|
*/
|
||||||
void UI_ShowBindingPage(u32 addr)
|
void UI_ShowBindingPage(u32 addr)
|
||||||
{
|
{
|
||||||
char id_str[20];
|
char id_str[20];
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
// 摄像机组合绘制
|
// ====== 像素级拼装绘制一个青色网络摄像头简图 ======
|
||||||
LCD_DrawRectBorder(36, 46, 32, 26, COLOR_CYAN);
|
LCD_DrawRectBorder(43, 46, 32, 26, COLOR_CYAN); // 相机主体框 (135px 下居中等比右移 X = 43)
|
||||||
LCD_FillRect(44, 56, 6, 6, COLOR_CYAN);
|
LCD_FillRect(51, 56, 6, 6, COLOR_CYAN); // 镜头光圈点 (135px 下居中等比右移 X = 51)
|
||||||
LCD_FillRect(68, 48, 2, 22, COLOR_CYAN);
|
LCD_FillRect(75, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈 (135px 下居中等比右移 X = 75)
|
||||||
LCD_FillRect(70, 50, 2, 18, COLOR_CYAN);
|
LCD_FillRect(77, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1 (135px 下居中等比右移 X = 77)
|
||||||
LCD_FillRect(72, 52, 2, 14, COLOR_CYAN);
|
LCD_FillRect(79, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2 (135px 下居中等比右移 X = 79)
|
||||||
|
|
||||||
LCD_ShowStringCentered(120, "LIAISON", COLOR_WHITE, COLOR_BLACK);
|
LCD_ShowStringCentered(120, Lang_Get(STR_LINK_TITLE), COLOR_WHITE, COLOR_BLACK); // 连接/绑定
|
||||||
LCD_ShowStringCentered(155, "ENVOI EN COURS", COLOR_CYAN, COLOR_BLACK);
|
LCD_ShowStringCentered(155, Lang_Get(STR_BIND_SENDING), COLOR_CYAN, COLOR_BLACK); // 发送对码帧中
|
||||||
|
|
||||||
|
// 拼接要对外广播的手环 24 位克隆物理 ID 十六进制字符串并显示在最下方
|
||||||
id_str[0] = 'I'; id_str[1] = 'D'; id_str[2] = ':'; id_str[3] = ' ';
|
id_str[0] = 'I'; id_str[1] = 'D'; id_str[2] = ':'; id_str[3] = ' ';
|
||||||
id_str[4] = '0'; id_str[5] = 'x';
|
id_str[4] = '0'; id_str[5] = 'x';
|
||||||
{
|
{
|
||||||
@@ -211,80 +370,158 @@ void UI_ShowBindingPage(u32 addr)
|
|||||||
LCD_ShowStringCentered(205, id_str, COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowStringCentered(205, id_str, COLOR_GRAY, COLOR_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示设置主菜单界面
|
||||||
|
* @param selected_index 当前菜单高亮行索引 (0:语言切换, 1:时钟修改, 2:对码匹配, 3:摄像机绑定)
|
||||||
|
* @details 在顶部和底部绘制向上/向下箭头提示滑轮,中部绘制选中项的大几何图形,
|
||||||
|
* 并在下方打印对应当前语言的文字描述。
|
||||||
|
*/
|
||||||
void UI_ShowMainMenu(u8 selected_index)
|
void UI_ShowMainMenu(u8 selected_index)
|
||||||
{
|
{
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
LCD_FillRect(58, 20, 4, 2, COLOR_GRAY);
|
|
||||||
LCD_FillRect(56, 22, 8, 2, COLOR_GRAY);
|
// ====== 绘制顶部的向上箭头三角标记 (在 135px 宽度下居中) ======
|
||||||
LCD_FillRect(54, 24, 12, 2, COLOR_GRAY);
|
LCD_FillRect(65, 20, 4, 2, COLOR_GRAY);
|
||||||
|
LCD_FillRect(63, 22, 8, 2, COLOR_GRAY);
|
||||||
|
LCD_FillRect(61, 24, 12, 2, COLOR_GRAY);
|
||||||
|
|
||||||
if (selected_index == 0)
|
if (selected_index == 0)
|
||||||
{
|
{
|
||||||
LCD_DrawRectBorder(44, 70, 32, 32, COLOR_CYAN);
|
// 菜单 0:语言切换。用当前语言的两字母缩写代替图标,直接展示切换后的效果
|
||||||
LCD_FillRect(59, 76, 2, 10, COLOR_CYAN);
|
LCD_ShowString16x32Centered(75, (Lang_Current() == LANG_FR) ? "FR" : "EN", COLOR_CYAN, COLOR_BLACK);
|
||||||
LCD_FillRect(59, 85, 8, 2, COLOR_CYAN);
|
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LANGUAGE), COLOR_WHITE, COLOR_BLACK);
|
||||||
LCD_ShowStringCentered(150, "1. HORLOGE", COLOR_WHITE, COLOR_BLACK);
|
|
||||||
}
|
}
|
||||||
else if (selected_index == 1)
|
else if (selected_index == 1)
|
||||||
{
|
{
|
||||||
LCD_DrawMonoBitmap(44, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
// 菜单 1:时间修改设置。中部绘制一个青色闹钟样式 (135px 下居中 X = 51)
|
||||||
LCD_ShowStringCentered(150, "2. APPAIRAGE", COLOR_WHITE, COLOR_BLACK);
|
LCD_DrawRectBorder(51, 70, 32, 32, COLOR_CYAN); // 闹钟外圈
|
||||||
|
LCD_FillRect(66, 76, 2, 10, COLOR_CYAN); // 闹钟时针
|
||||||
|
LCD_FillRect(66, 85, 8, 2, COLOR_CYAN); // 闹钟分针
|
||||||
|
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_HORLOGE), COLOR_WHITE, COLOR_BLACK); // 时钟/时间修改
|
||||||
}
|
}
|
||||||
else if (selected_index == 2)
|
else if (selected_index == 2)
|
||||||
{
|
{
|
||||||
LCD_DrawRectBorder(44, 74, 20, 16, COLOR_CYAN);
|
// 菜单 2:无线对码匹配。中部绘制一个青色同心圆雷达波纹图形 (135px 下居中 X = 51)
|
||||||
LCD_FillRect(52, 80, 4, 4, COLOR_CYAN);
|
LCD_DrawMonoBitmap(51, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
|
||||||
LCD_FillRect(64, 76, 2, 12, COLOR_CYAN);
|
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_APPAIRAGE), COLOR_WHITE, COLOR_BLACK); // 配对
|
||||||
LCD_FillRect(66, 78, 2, 8, COLOR_CYAN);
|
}
|
||||||
LCD_FillRect(68, 80, 2, 4, COLOR_CYAN);
|
else if (selected_index == 3)
|
||||||
LCD_ShowStringCentered(150, "3. LIAISON", COLOR_WHITE, COLOR_BLACK);
|
{
|
||||||
|
// 菜单 3:网络摄像机绑定。中部绘制一个相机/联通关系图 (135px 下居中等比右移 X = 57)
|
||||||
|
LCD_DrawRectBorder(57, 74, 20, 16, COLOR_CYAN);
|
||||||
|
LCD_FillRect(65, 80, 4, 4, COLOR_CYAN);
|
||||||
|
LCD_FillRect(77, 76, 2, 12, COLOR_CYAN);
|
||||||
|
LCD_FillRect(79, 78, 2, 8, COLOR_CYAN);
|
||||||
|
LCD_FillRect(81, 80, 2, 4, COLOR_CYAN);
|
||||||
|
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LIAISON), COLOR_WHITE, COLOR_BLACK); // 摄像机绑定
|
||||||
}
|
}
|
||||||
|
|
||||||
LCD_FillRect(54, 210, 12, 2, COLOR_GRAY);
|
// ====== 绘制底部的向下箭头三角标记 (在 135px 宽度下居中) ======
|
||||||
LCD_FillRect(56, 212, 8, 2, COLOR_GRAY);
|
LCD_FillRect(61, 210, 12, 2, COLOR_GRAY);
|
||||||
LCD_FillRect(58, 214, 4, 2, COLOR_GRAY);
|
LCD_FillRect(63, 212, 8, 2, COLOR_GRAY);
|
||||||
|
LCD_FillRect(65, 214, 4, 2, COLOR_GRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
|
/**
|
||||||
|
* @brief 渲染修改时间/日期闪烁调节页面
|
||||||
|
* @param hour 修改状态中的小时
|
||||||
|
* @param min 修改状态中的分钟
|
||||||
|
* @param day 修改状态中的日
|
||||||
|
* @param month 修改状态中的月
|
||||||
|
* @param year 修改状态中的年 (0~99,代表 2000~2099)
|
||||||
|
* @param weekday 根据当前日期算出的星期 (0=周日, ..., 6=周六)
|
||||||
|
* @param selection 当前正在编辑聚焦的属性位 (1:小时, 2:分钟, 3:日, 4:月, 5:年)
|
||||||
|
* @param blink_on 当前 500ms 控制隐现的闪烁周期标志位 (1-绘制正常字,0-以空格消隐该数值字符)
|
||||||
|
*/
|
||||||
|
// 小时/分钟数字在屏幕上的固定坐标 (16x32 字体,两位数字宽度为 32px)
|
||||||
|
#define SET_TIME_DIGIT_Y 100
|
||||||
|
#define SET_TIME_DIGIT_W 32
|
||||||
|
#define SET_TIME_DIGIT_H 32
|
||||||
|
#define SET_TIME_HOUR_X 23
|
||||||
|
#define SET_TIME_MIN_X 75
|
||||||
|
|
||||||
|
void UI_ShowSetTimePage(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 weekday, u8 selection, u8 blink_on)
|
||||||
{
|
{
|
||||||
char time_str[6];
|
char time_str[6];
|
||||||
|
char date_str[13];
|
||||||
LCD_Clear(COLOR_BLACK);
|
LCD_Clear(COLOR_BLACK);
|
||||||
|
|
||||||
|
// 绘制绿锁头等背景静态信息
|
||||||
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN);
|
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN);
|
||||||
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN);
|
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN);
|
||||||
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN);
|
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN);
|
||||||
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN);
|
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN);
|
||||||
|
|
||||||
LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK);
|
// 移动电池百分比和图标到 135px 右上角
|
||||||
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
|
LCD_ShowString(77, 24, "85%", COLOR_GRAY, COLOR_BLACK);
|
||||||
|
LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
|
||||||
|
|
||||||
if (selection == 1 && !blink_on)
|
// 整页绘制时,各数字一律完整显示 (消隐闪烁效果交给 UI_UpdateSetTimeDigit 局部刷新处理)
|
||||||
{
|
|
||||||
time_str[0] = ' ';
|
|
||||||
time_str[1] = ' ';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
time_str[0] = '0' + (hour / 10);
|
time_str[0] = '0' + (hour / 10);
|
||||||
time_str[1] = '0' + (hour % 10);
|
time_str[1] = '0' + (hour % 10);
|
||||||
}
|
|
||||||
time_str[2] = '\0';
|
time_str[2] = '\0';
|
||||||
LCD_ShowString16x32(12, 100, time_str, COLOR_CYAN, COLOR_BLACK);
|
LCD_ShowString16x32(SET_TIME_HOUR_X, SET_TIME_DIGIT_Y, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘小时 (在 135px 下整体居中 X = 23)
|
||||||
|
|
||||||
LCD_ShowString16x32(48, 98, ":", COLOR_WHITE, COLOR_BLACK);
|
// 绘制中间冒号 (在 135px 下整体居中 X = 59)
|
||||||
|
LCD_ShowString16x32(59, 98, ":", COLOR_WHITE, COLOR_BLACK);
|
||||||
|
|
||||||
if (selection == 2 && !blink_on)
|
|
||||||
{
|
|
||||||
time_str[0] = ' ';
|
|
||||||
time_str[1] = ' ';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
time_str[0] = '0' + (min / 10);
|
time_str[0] = '0' + (min / 10);
|
||||||
time_str[1] = '0' + (min % 10);
|
time_str[1] = '0' + (min % 10);
|
||||||
}
|
|
||||||
time_str[2] = '\0';
|
time_str[2] = '\0';
|
||||||
LCD_ShowString16x32(64, 100, time_str, COLOR_CYAN, COLOR_BLACK);
|
LCD_ShowString16x32(SET_TIME_MIN_X, SET_TIME_DIGIT_Y, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟 (在 135px 下整体居中 X = 75)
|
||||||
LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK);
|
|
||||||
LCD_ShowStringCentered(195, "VEN 10-07", COLOR_GRAY, COLOR_BLACK);
|
// 补齐底部的固定辅助元素
|
||||||
|
LCD_ShowStringCentered(150, Lang_Get(STR_CLOCK_AMPM), COLOR_WHITE, COLOR_BLACK);
|
||||||
|
|
||||||
|
// 编辑日期时,日期栏额外带上年份 ("WWW DD-MM-YY"),方便看清正在改的年份数值
|
||||||
|
Build_DateStringWithYear(date_str, weekday, day, month, year);
|
||||||
|
LCD_ShowStringCentered(DATE_BAR_Y, date_str, COLOR_GRAY, COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 局部刷新时间设置界面里"正在调节的那一个"字段 (小时/分钟/日/月/年)
|
||||||
|
* @param weekday 根据当前日期算出的星期 (0=周日, ..., 6=周六),调节日/月/年时顺带刷新星期缩写
|
||||||
|
* @param selection 1:小时, 2:分钟, 3:日, 4:月, 5:年
|
||||||
|
* @param blink_on 1:显示数值, 0:消隐(涂黑),用于 500ms 周期闪烁效果
|
||||||
|
* @details 小时/分钟用 32x32px 大字体区域;日/月/年用日期栏里对应的 16x16px 小字体区域,
|
||||||
|
* 都只重画自己那一小块,不触碰锁头/电池/冒号/AM-PM/星期文字等其它内容,
|
||||||
|
* 避免每次闪烁或调值都整屏 LCD_Clear() 造成全屏闪烁。
|
||||||
|
*/
|
||||||
|
void UI_UpdateSetTimeDigit(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 weekday, u8 selection, u8 blink_on)
|
||||||
|
{
|
||||||
|
char buf[3];
|
||||||
|
u16 x;
|
||||||
|
u16 y;
|
||||||
|
u16 w;
|
||||||
|
u16 h;
|
||||||
|
u8 value;
|
||||||
|
|
||||||
|
switch (selection) {
|
||||||
|
case 1: x = SET_TIME_HOUR_X; y = SET_TIME_DIGIT_Y; w = SET_TIME_DIGIT_W; h = SET_TIME_DIGIT_H; value = hour; break;
|
||||||
|
case 2: x = SET_TIME_MIN_X; y = SET_TIME_DIGIT_Y; w = SET_TIME_DIGIT_W; h = SET_TIME_DIGIT_H; value = min; break;
|
||||||
|
case 3: x = DATE_BAR_DAY_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = day; break;
|
||||||
|
case 4: x = DATE_BAR_MONTH_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = month; break;
|
||||||
|
default: x = DATE_BAR_YEAR_X; y = DATE_BAR_Y; w = DATE_BAR_FIELD_W; h = 16; value = year; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调节日/月/年时,星期缩写可能因为日期变化而改变,顺带刷新一下 (不参与闪烁,始终显示)
|
||||||
|
if (selection >= 3) {
|
||||||
|
LCD_FillRect(DATE_BAR_START_X, DATE_BAR_Y, 24, 16, COLOR_BLACK);
|
||||||
|
LCD_ShowString(DATE_BAR_START_X, DATE_BAR_Y, Lang_Get((StringID)(STR_WEEKDAY_SUN + weekday)), COLOR_GRAY, COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blink_on) {
|
||||||
|
// 灭周期:直接把这一小块涂黑,形成消隐效果
|
||||||
|
LCD_FillRect(x, y, w, h, COLOR_BLACK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[0] = '0' + (value / 10);
|
||||||
|
buf[1] = '0' + (value % 10);
|
||||||
|
buf[2] = '\0';
|
||||||
|
if (selection <= 2) {
|
||||||
|
LCD_ShowString16x32(x, y, buf, COLOR_CYAN, COLOR_BLACK);
|
||||||
|
} else {
|
||||||
|
LCD_ShowString(x, y, buf, COLOR_GRAY, COLOR_BLACK);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
106
App/ui.h
106
App/ui.h
@@ -3,16 +3,98 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
void UI_ShowClockPage(SystemState state, u8 hour, u8 min);
|
/* ====== OLED 屏幕 UI (用户界面) 渲染层接口 ====== */
|
||||||
void UI_ShowPairMenuPage(u8 selected_index);
|
|
||||||
void UI_ShowPairWaitPage(u8 frame_index);
|
|
||||||
void UI_ShowPairConfirmPage(u8 type, u8 zone_index);
|
|
||||||
void UI_ShowPairSuccessPage(void);
|
|
||||||
void UI_ShowPairFailPage(u8 is_timeout);
|
|
||||||
void UI_ShowAlarmPage(u8 type, u8 zone_index);
|
|
||||||
void UI_ShowSosPage(void);
|
|
||||||
void UI_ShowBindingPage(u32 addr);
|
|
||||||
void UI_ShowMainMenu(u8 selected_index);
|
|
||||||
void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on);
|
|
||||||
|
|
||||||
#endif
|
/**
|
||||||
|
* @brief 渲染显示待机时钟主页面
|
||||||
|
* @param state 当前的系统运行状态 (STATE_NORMAL/STATE_ARMED/STATE_DISARMED)
|
||||||
|
* @param hour 当前待显示的小时
|
||||||
|
* @param min 当前待显示的分钟
|
||||||
|
* @param weekday 当前星期 (0=周日, ..., 6=周六)
|
||||||
|
* @param day 当前日 (1~31)
|
||||||
|
* @param month 当前月 (1~12)
|
||||||
|
* @details 负责在 120x240 大小的 AMOLED 屏幕上居中绘制巨大的时钟字样,并在顶部
|
||||||
|
* 绘制对应的状态图标 (如一把打开的锁代表已撤防、闭合的锁代表已布防等)。
|
||||||
|
*/
|
||||||
|
void UI_ShowClockPage(SystemState state, u8 hour, u8 min, u8 weekday, u8 day, u8 month);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示传感器对码的二级设备选择菜单页面
|
||||||
|
* @param selected_index 当前正在选中的传感器类型索引 (0:门磁, 1:PIR, 2:烟感, 3:燃气, 4:水浸)
|
||||||
|
*/
|
||||||
|
void UI_ShowPairMenuPage(u8 selected_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 动态刷新显示正在搜寻无线传感器信号的等待对码动态图画
|
||||||
|
* @param frame_index 当前雷达扫描波纹动画的帧索引 (0~2 帧循环)
|
||||||
|
*/
|
||||||
|
void UI_ShowPairWaitPage(u8 frame_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示捕获到无线信号后的传感器类型确认与自定义防区微调界面
|
||||||
|
* @param type 已捕获的传感器类型 (0~4)
|
||||||
|
* @param zone_index 当前设定的防区数值 (1~99),允许通过按键增加或减小
|
||||||
|
*/
|
||||||
|
void UI_ShowPairConfirmPage(u8 type, u8 zone_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示对码学习成功反馈页
|
||||||
|
* @details 屏幕中央显示巨大对勾图标,并绘制文字 "APPAIRAGE OK" (配对成功)。
|
||||||
|
*/
|
||||||
|
void UI_ShowPairSuccessPage(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示对码失败 (或30秒超时) 反馈页
|
||||||
|
* @param is_timeout 1表示超时失败,0表示其他错误
|
||||||
|
* @details 屏幕显示红色大叉图标,并提示 "ECHEC" (配对失败)。
|
||||||
|
*/
|
||||||
|
void UI_ShowPairFailPage(u8 is_timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示传感器入侵警报报警页面
|
||||||
|
* @param type 触发入侵的传感器类型 (0:门磁, 1:PIR, 2:烟感, 3:燃气, 4:水浸)
|
||||||
|
* @param zone_index 报警传感器所绑定的防区号 (1~99, 0)
|
||||||
|
* @details 屏幕爆闪红色粗边框,并交替绘制代表紧急入侵的巨大传感器特定警示大图标。
|
||||||
|
*/
|
||||||
|
void UI_ShowAlarmPage(u8 type, u8 zone_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示 SOS 主动求救发送状态页面
|
||||||
|
* @details 爆闪红白双层边框,在屏幕上绘制求救警示图,并闪烁显示法语 "SOS EMIS" (求救已发送)。
|
||||||
|
*/
|
||||||
|
void UI_ShowSosPage(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示与网络摄像机 (IPC) 进行一键绑定配对的进度画面
|
||||||
|
* @param addr 手环克隆发送的 24 位出厂 ID 地址 (十六进制字符串格式展示)
|
||||||
|
*/
|
||||||
|
void UI_ShowBindingPage(u32 addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示设置主菜单界面
|
||||||
|
* @param selected_index 当前菜单高亮行索引 (0:HORLOGE时间, 1:APPAIRAGE对码, 2:LIAISON绑定)
|
||||||
|
*/
|
||||||
|
void UI_ShowMainMenu(u8 selected_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示修改时间/日期页面 (时钟设置编辑状态)
|
||||||
|
* @param hour 待修改的小时
|
||||||
|
* @param min 待修改的分钟
|
||||||
|
* @param day 待修改的日
|
||||||
|
* @param month 待修改的月
|
||||||
|
* @param year 待修改的年 (0~99,代表 2000~2099)
|
||||||
|
* @param weekday 根据当前日期算出的星期 (0=周日, ..., 6=周六)
|
||||||
|
* @param selection 当前选中的修改项 (1:小时, 2:分钟, 3:日, 4:月, 5:年)
|
||||||
|
* @param blink_on 控制被选中数值以 500ms 周期进行隐现闪烁的周期标志位 (1-显示, 0-消隐)
|
||||||
|
*/
|
||||||
|
void UI_ShowSetTimePage(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 weekday, u8 selection, u8 blink_on);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 局部刷新时间设置界面里正在调节的那一个字段 (小时/分钟/日/月/年),用于闪烁/调值时避免整屏重绘
|
||||||
|
* @param weekday 根据当前日期算出的星期 (0=周日, ..., 6=周六),调节日/月/年时顺带刷新星期缩写
|
||||||
|
* @param selection 1:小时, 2:分钟, 3:日, 4:月, 5:年
|
||||||
|
* @param blink_on 1:显示数值, 0:消隐(涂黑)
|
||||||
|
*/
|
||||||
|
void UI_UpdateSetTimeDigit(u8 hour, u8 min, u8 day, u8 month, u8 year, u8 weekday, u8 selection, u8 blink_on);
|
||||||
|
|
||||||
|
#endif // __UI_H__
|
||||||
|
|||||||
24
App/xtell.h
Normal file
24
App/xtell.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef __XTELL_H__
|
||||||
|
#define __XTELL_H__
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
// 全局调试日志开关
|
||||||
|
#define XTELL_DEBUG_ENABLE
|
||||||
|
|
||||||
|
// 声明底层串口发送函数 (由 system.c 提供)
|
||||||
|
extern void Uart_SendString(char *p);
|
||||||
|
extern void Uart_SendHex8(u8 val);
|
||||||
|
extern void Uart_SendHex32(u32 val);
|
||||||
|
|
||||||
|
#ifdef XTELL_DEBUG_ENABLE
|
||||||
|
#define XTELL_LOG(s) Uart_SendString(s)
|
||||||
|
#define XTELL_LOG_HEX8(v) Uart_SendHex8(v)
|
||||||
|
#define XTELL_LOG_HEX32(v) Uart_SendHex32(v)
|
||||||
|
#else
|
||||||
|
#define XTELL_LOG(s)
|
||||||
|
#define XTELL_LOG_HEX8(v)
|
||||||
|
#define XTELL_LOG_HEX32(v)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __XTELL_H__
|
||||||
7
CLAUDE.md
Normal file
7
CLAUDE.md
Normal 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.
|
||||||
@@ -10,37 +10,46 @@
|
|||||||
* **硬件原理图**:[text.kicad_sch](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_sch)
|
* **硬件原理图**:[text.kicad_sch](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_sch)
|
||||||
* **PCB 布局图**:[text.kicad_pcb](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_pcb)
|
* **PCB 布局图**:[text.kicad_pcb](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_pcb)
|
||||||
|
|
||||||
主控芯片采用 **STC32G12K128**(工作频率 24.0 MHz,单周期 8051 内核,32位乘除法器)。
|
主控芯片采用| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | LHS114TC-IF03 屏幕复位控制脚 (RESET) |
|
||||||
显示端采用 **Truly 0.95寸 AMOLED 屏幕**,物理分辨率为 **120x240 像素**,控制芯片为 **RM69310**。
|
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 数据/指令选择脚 (D/C) |
|
||||||
|
| **Pin 7** | `P1.3` | `BAT_ADC` | ADC 输入 | 电池电压采集 (通道 3,配置为高阻输入) |
|
||||||
### 1.1 GPIO 物理映射表
|
| **Pin 3** | `P1.4` | `SPI_SDI` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 串行数据输入 (MOSI/SDA) |
|
||||||
|
| **Pin 4** | `P1.5` | `SPI_CLK` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 串行时钟信号 (SCL) |
|
||||||
| 芯片引脚号 | 引脚名称 | 逻辑网络标号 | IO 模式 | 连接外设与功能描述 |
|
| **Pin 5** | `P1.6` | `SPI_CS` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 片选信号 (CS) |
|
||||||
| :--- | :--- | :--- | :--- | :--- |
|
| **Pin 6** | `P1.7` | `LEDA` | 推挽输出 | LHS114TC-IF03 屏幕白色 LED 背光使能控制端 (0点亮,1熄灭 - PMOS控制) |
|
||||||
| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | Truly AMOLED 屏幕复位控制脚 (RESET) |
|
| **Pin 8** | `UCAP` | `UCAP` | 电源 | 外接滤波电容至 GND (LDO 稳定输出端) |
|
||||||
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | Truly AMOLED 屏幕 SPI 数据/指令选择脚 (D/C) |
|
|
||||||
| **Pin 3** | `P1.4` | `SPI_SDI` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输入 (MOSI/SDA) |
|
|
||||||
| **Pin 4** | `P1.5` |`SPI_CLK` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行时钟信号 (SCL) |
|
|
||||||
| **Pin 5** | `P1.6` | `SPI_CS` | 推挽输出 | Truly AMOLED 屏幕 SPI 片选信号 (CS) |
|
|
||||||
| **Pin 6** | `P1.7` | `SPI_SDO` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输出 (MISO) |
|
|
||||||
| **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
|
| **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
|
||||||
| **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
|
| **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电参考基准 |
|
||||||
| **Pin 12** | `GND` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
|
| **Pin 12** | `GND` | `GND` | 电源 | 系统地 (GND) |
|
||||||
| **Pin 13** |`P3.1` |`RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 |
|
| **Pin 13** | `P3.0` | `RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 |
|
||||||
| **Pin 14** | `P3.0` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 |
|
| **Pin 14** | `P3.1` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 |
|
||||||
| **Pin 19** | `P3.6` | `RF_TX_DAT` | 推挽输出 | LR690L 射频芯片发射调制数据输入引脚 |
|
| **Pin 15** | `P5.4` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
|
||||||
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | LR690L 射频芯片接收使能控制引脚 |
|
| **Pin 18** | `P3.5` | `SHUT` | 推挽输出 | LR690L 射频芯片关闭信号 (0:开启工作, 1:关闭休眠) |
|
||||||
| **Pin 21** | `P2.0` | `RF_TX` | 推挽输出 | LT4455 射频芯片发射使能控制引脚 |
|
| **Pin 19** | `P3.6` | `RF_RX_DATA` | 高阻输入 | LR690L 接收解调接收数据输出引脚 (注:代码中被映射为 P2^1) |
|
||||||
| **Pin 22** | `P2.1` | `RF_RX_DATA` | 输入中断 | ATR5179 射频开关控制芯片
|
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | ATR5179 接收使能控制引脚 |
|
||||||
| **Pin 23** | `P2.2` |`SHUT` | LR0L射频芯片关闭信号
|
| **Pin 21** | `P2.0` | `RF_TX` | 推挽输出 | ATR5179 发射使能控制引脚 |
|
||||||
|
| **Pin 22** | `P2.1` | `RF_TX_DAT` | 推挽输出 | LR690L 射频芯片发射调制数据输入引脚 (注:代码中被映射为 P3^6) |
|
||||||
|
| **Pin 23** | `P2.2` | `NC` | - | 悬空 |
|
||||||
| **Pin 24** | `P2.3` | `LED_RGB` | SPI MOSI | WS2812B 级联双幻彩灯珠 DIN 控制信号 |
|
| **Pin 24** | `P2.3` | `LED_RGB` | SPI MOSI | WS2812B 级联双幻彩灯珠 DIN 控制信号 |
|
||||||
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
|
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
|
||||||
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
|
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
|
||||||
| **Pin 28** | `P2.7` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
|
| **Pin 28** | `P2.7` | `NC` | - | 悬空 |
|
||||||
|
| **Pin 29** | `P0.0` | `NC` | - | 悬空 |
|
||||||
|
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
|
||||||
|
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |
|
||||||
|
| **Pin 32** | `P0.3` | `KEY_DOWN` | 高阻输入 + 上拉 | 侧边按键 ▼ (下移),按下为低电平 |<7C> P2^1) |
|
||||||
|
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | ATR5179 接收使能控制引脚 |
|
||||||
|
| **Pin 21** | `P2.0` | `RF_TX` | 推挽输出 | ATR5179 发射使能控制引脚 |
|
||||||
|
| **Pin 22** | `P2.1` | `RF_TX_DAT` | 推挽输出 | LR690L 射频芯片发射调制数据输入引脚 (注:代码中被映射为 P3^6) |
|
||||||
|
| **Pin 23** | `P2.2` | `NC` | - | 悬空 |
|
||||||
|
| **Pin 24** | `P2.3` | `LED_RGB` | SPI MOSI | WS2812B 级联双幻彩灯珠 DIN 控制信号 |
|
||||||
|
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
|
||||||
|
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
|
||||||
|
| **Pin 28** | `P2.7` | `NC` | - | 悬空 |
|
||||||
| **Pin 29** | `P0.0` | `BAT_ADC` | ADC 输入 | 电池电压采集 |
|
| **Pin 29** | `P0.0` | `BAT_ADC` | ADC 输入 | 电池电压采集 |
|
||||||
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
|
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
|
||||||
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |
|
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |
|
||||||
| **Pin 32** | `P0.3` | `KEY_DOWN` | 高阻输入 + 上拉 | 侧边按键 ▼ (下移),按下为低电平 |
|
| **Pin 32** | `P0.3` | `KEY_DOWN`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,8 @@
|
|||||||
## 2. 关键代码片段与逻辑实现
|
## 2. 关键代码片段与逻辑实现
|
||||||
|
|
||||||
### 2.1 供电引脚初始化
|
### 2.1 供电引脚初始化
|
||||||
系统初始化时,配置 `SHUT`(控制 SGM3833 供电的引脚 `P2.2`)为推挽输出,并拉高以开启屏幕供电:
|
|
||||||
```c
|
|
||||||
sbit SHUT = P2^2; // Pin 23
|
|
||||||
|
|
||||||
void Power_Init(void) {
|
|
||||||
// P2.2 (SHUT) 配置为推挽输出
|
|
||||||
P2M1 &= ~(1 << 2);
|
|
||||||
P2M0 |= (1 << 2);
|
|
||||||
|
|
||||||
// 拉高引脚,开启 SGM3833 负压供电
|
|
||||||
SHUT = 1;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 进入低功耗睡眠 (Enter_Low_Power_Sleep)
|
### 2.2 进入低功耗睡眠 (Enter_Low_Power_Sleep)
|
||||||
在 10 秒无按键操作发生后,关闭屏幕显示,断电,配置中断,并进入 Power-Down 挂起状态:
|
在 10 秒无按键操作发生后,关闭屏幕显示,断电,配置中断,并进入 Power-Down 挂起状态:
|
||||||
@@ -31,7 +20,7 @@ void Enter_Low_Power_Sleep(void) {
|
|||||||
LCD_WriteCmd(0x10);
|
LCD_WriteCmd(0x10);
|
||||||
|
|
||||||
// 2. 切断 SGM3833 电源,停止输出负压
|
// 2. 切断 SGM3833 电源,停止输出负压
|
||||||
SHUT = 0;
|
|
||||||
|
|
||||||
// 3. 配置引脚中断 (P0.1, P0.2, P0.3, P2.6) 允许唤醒
|
// 3. 配置引脚中断 (P0.1, P0.2, P0.3, P2.6) 允许唤醒
|
||||||
// 允许 P0 / P2 端口中断以供按键唤醒
|
// 允许 P0 / P2 端口中断以供按键唤醒
|
||||||
@@ -56,12 +45,12 @@ void Port0_Isr(void) interrupt 37 {
|
|||||||
P0INTF = 0x00; // 清除 P0 中断标志
|
P0INTF = 0x00; // 清除 P0 中断标志
|
||||||
Wakeup_Restore();
|
Wakeup_Restore();
|
||||||
}
|
}
|
||||||
|
//周期嗅探模式来接收信号源
|
||||||
void Wakeup_Restore(void) {
|
void Wakeup_Restore(void) {
|
||||||
if (SHUT == 1) return; // 已经在工作状态,直接返回
|
if // 已经在工作状态,直接返回
|
||||||
|
|
||||||
|
// 重新使能 SGM3833 供电
|
||||||
|
|
||||||
// 1. 拉高 SHUT 重新使能 SGM3833 供电
|
|
||||||
SHUT = 1;
|
|
||||||
|
|
||||||
// 2. 延时约 50ms 等待升压/负压输出稳定
|
// 2. 延时约 50ms 等待升压/负压输出稳定
|
||||||
Delay50ms();
|
Delay50ms();
|
||||||
|
|||||||
171
Drivers/adc.c
Normal file
171
Drivers/adc.c
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#include "adc.h"
|
||||||
|
#include "../App/system.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 STC32G 硬件 ADC 模块
|
||||||
|
* @details 配置 P0.3 (KEY_DOWN) 与 P1.3 (BATT_ADC) 为高阻输入模式,
|
||||||
|
* 上电开启 ADC 内部电源并配置结果对齐格式为右对齐。
|
||||||
|
*/
|
||||||
|
void ADC_Init(void)
|
||||||
|
{
|
||||||
|
// 1. 配置 P0.3 和 P1.3 引脚模式为高阻输入 (M1=1, M0=0)
|
||||||
|
P0M1 |= (1 << 3);
|
||||||
|
P0M0 &= ~(1 << 3);
|
||||||
|
|
||||||
|
P1M1 |= (1 << 3);
|
||||||
|
P1M0 &= ~(1 << 3);
|
||||||
|
|
||||||
|
// 配置 P1.2 (CHRG_DET / ADC2) 为高阻输入模式
|
||||||
|
P1M1 |= (1 << 2);
|
||||||
|
P1M0 &= ~(1 << 2);
|
||||||
|
|
||||||
|
// 2. 开启 ADC 模块主电源 (ADC_POWER = 1)
|
||||||
|
ADC_POWER = 1;
|
||||||
|
|
||||||
|
// 3. 配置 ADCCFG 寄存器:
|
||||||
|
// RESFMT (bit 5) = 1: 转换结果右对齐 (ADC_RES 存高4位, ADC_RESL 存低8位)
|
||||||
|
// 底部 4 位 SPEED: 设置 ADC 转换工作时钟频率 (0x0F 表示最快速度)
|
||||||
|
ADCCFG = 0x2F;
|
||||||
|
|
||||||
|
// 4. 软启动延迟,等待 ADC 模块内部电源稳定
|
||||||
|
Delay_ms(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 单次读取指定 ADC 通道的原始 12 位转换结果
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 12 位 ADC 采样原始值 (0 ~ 4095)
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadRaw(u8 channel)
|
||||||
|
{
|
||||||
|
u16 adc_val = 0;
|
||||||
|
|
||||||
|
// 选择指定通道并保持电源开启
|
||||||
|
ADC_CONTR = (ADC_CONTR & 0xF0) | (channel & 0x0F);
|
||||||
|
|
||||||
|
// 延时等待通道切换电容充放电稳定
|
||||||
|
Delay_us(10);
|
||||||
|
|
||||||
|
// 启动 A/D 转换
|
||||||
|
ADC_START = 1;
|
||||||
|
|
||||||
|
// 阻塞等待转换结束 (ADC_FLAG 置 1)
|
||||||
|
while (!ADC_FLAG);
|
||||||
|
|
||||||
|
// 清除转换完成标志
|
||||||
|
ADC_FLAG = 0;
|
||||||
|
|
||||||
|
// 读取 12 位右对齐结果 (ADC_RES 取低 4 位,结合 ADC_RESL 构成 12 位数据)
|
||||||
|
adc_val = ((u16)(ADC_RES & 0x0F) << 8) | ADC_RESL;
|
||||||
|
|
||||||
|
return adc_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 5 次采样排序取中值滤波算法
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 滤波后的 12 位 ADC 采样结果
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadFiltered(u8 channel)
|
||||||
|
{
|
||||||
|
u16 buf[5];
|
||||||
|
u8 i, j;
|
||||||
|
u16 temp;
|
||||||
|
|
||||||
|
// 连续采集 5 次数据
|
||||||
|
for (i = 0; i < 5; i++) {
|
||||||
|
buf[i] = ADC_ReadRaw(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 冒泡排序法进行升序排列
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
for (j = 0; j < 4 - i; j++) {
|
||||||
|
if (buf[j] > buf[j + 1]) {
|
||||||
|
temp = buf[j];
|
||||||
|
buf[j] = buf[j + 1];
|
||||||
|
buf[j + 1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取中间第 3 个值 (索引 2) 作为有效滤波输出,有效去除偶然性噪声尖峰
|
||||||
|
return buf[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测 USB 充电是否插入
|
||||||
|
* @return bit 0=未充电, 1=充电中
|
||||||
|
* @note TP4054 CHRG 开漏输出:
|
||||||
|
* - 未充电/充满:CHRG 为高阻(被内部上拉拉高)
|
||||||
|
* - 充电中:CHRG 为低电平
|
||||||
|
* 返回值 1 表示正在充电,0 表示未充电或充满。
|
||||||
|
*/
|
||||||
|
bit Is_Charging(void)
|
||||||
|
{
|
||||||
|
u32 val = ADC_ReadFiltered(ADC_CHANNEL_CHRG_DET);
|
||||||
|
u32 det_mv = (val * 3300UL) / 4095UL;
|
||||||
|
|
||||||
|
// 0.8V ~ 1.8V (800mV ~ 1800mV):正在充电
|
||||||
|
if (det_mv >= 800 && det_mv <= 1800) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测 KEY_DOWN (P0.3) 按键电平是否按下
|
||||||
|
* @details KEY_DOWN 节点未按下时电压约 2.0V,按下后升至约 3.7V。
|
||||||
|
* @return bit 1: 按下, 0: 常态未按下
|
||||||
|
*/
|
||||||
|
bit ADC_IsKeyDown(void)
|
||||||
|
{
|
||||||
|
u16 val = ADC_ReadFiltered(ADC_CHANNEL_KEY_DOWN);
|
||||||
|
// 若采样值超过阈值 KEY_DOWN_ADC_THRESHOLD (3300),判定为按键被按下
|
||||||
|
if (val > KEY_DOWN_ADC_THRESHOLD) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 P1.3 电池分压计算得出的实际电池电压值 (单位: mV)
|
||||||
|
* @details 硬件电阻分压电路: R15 (10kΩ) 上拉, R16 (22kΩ) 下拉。
|
||||||
|
* 分压公式: V_node = V_bat * (22 / 32) => V_bat = V_node * (32 / 22)
|
||||||
|
* 根据 ADC 采样: V_node(mV) = adc_val * 3300 / 4095
|
||||||
|
* 真实电池电压 V_bat(mV) = (adc_val * 3300 * 32) / (4095 * 22)
|
||||||
|
* @return u16 电池总电压 (mV, 例如 3850 代表 3.85V)
|
||||||
|
*/
|
||||||
|
u16 ADC_GetBatteryVoltage_mV(void)
|
||||||
|
{
|
||||||
|
u32 adc_val = ADC_ReadFiltered(ADC_CHANNEL_BATTERY);
|
||||||
|
u32 vbat_mv;
|
||||||
|
|
||||||
|
// 经用户实测校准:
|
||||||
|
// 当电池真实电压为 4.0V (4000mV) 时,10k/22k 分压点万用表实测为 2.7V,单片机读取的过滤 ADC 原值约为 2357。
|
||||||
|
// 换算公式:V_bat(mV) = adc_val * 4000 / 2357
|
||||||
|
vbat_mv = (adc_val * 4000UL) / 2357UL;
|
||||||
|
|
||||||
|
return (u16)vbat_mv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算电池剩余电量百分比 (0% ~ 100%)
|
||||||
|
* @details 锂电池标准电压区间定义:
|
||||||
|
* 4.20V (4200mV) 对应 100%
|
||||||
|
* 3.30V (3300mV) 对应 0%
|
||||||
|
* @return u8 电量百分比 (0 ~ 100)
|
||||||
|
*/
|
||||||
|
u8 ADC_GetBatteryPercent(void)
|
||||||
|
{
|
||||||
|
u16 vbat = ADC_GetBatteryVoltage_mV();
|
||||||
|
|
||||||
|
if (vbat >= 4200) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
if (vbat <= 3300) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 线性百分比换算: percent = (vbat - 3300) * 100 / (4200 - 3300)
|
||||||
|
return (u8)(((u32)(vbat - 3300) * 100UL) / 900UL);
|
||||||
|
}
|
||||||
61
Drivers/adc.h
Normal file
61
Drivers/adc.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef __ADC_H__
|
||||||
|
#define __ADC_H__
|
||||||
|
|
||||||
|
#include "../App/config.h"
|
||||||
|
|
||||||
|
/* ====== ADC 通道与硬件引脚定义 ====== */
|
||||||
|
#define ADC_CHANNEL_KEY_DOWN 11 // KEY_DOWN (P0.3) 对应 STC32G 的 ADC 通道 11
|
||||||
|
#define ADC_CHANNEL_BATTERY 3 // 电池检测 (P1.3) 对应 STC32G 的 ADC 通道 3
|
||||||
|
#define ADC_CHANNEL_CHRG_DET 2 // 充电检测 (P1.2) 对应 STC32G 的 ADC 通道 2
|
||||||
|
|
||||||
|
/* ====== 判定阈值定义 (基于 12 位 ADC, 0~4095) ====== */
|
||||||
|
// KEY_DOWN 2.2V 常态 (ADC~2700), 3.7V 按下 (ADC 饱和 4095)
|
||||||
|
#define KEY_DOWN_ADC_THRESHOLD 3400 // ADC 采样值超过 3400 判定为按键按下
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 STC32G 硬件 ADC 模块
|
||||||
|
* @details 开启 ADC 电源、设置结果右对齐格式及采样时钟分频,并将 P0.3 和 P1.3 设置为高阻输入模式
|
||||||
|
*/
|
||||||
|
void ADC_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取指定 ADC 通道的原始 12 位转换结果
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 12 位 ADC 采样原始值 (0 ~ 4095)
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadRaw(u8 channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 对指定 ADC 通道进行多重采样与中值滤波
|
||||||
|
* @param channel ADC 通道号 (0~15)
|
||||||
|
* @return u16 滤波后的 12 位 ADC 采样值
|
||||||
|
*/
|
||||||
|
u16 ADC_ReadFiltered(u8 channel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 判断 KEY_DOWN (P0.3) 当前是否处于按下状态 (ADC 阈值判定)
|
||||||
|
* @return bit 1: 按下 (电压 ~3.7V), 0: 松开/常态 (电压 ~2.0V)
|
||||||
|
*/
|
||||||
|
bit ADC_IsKeyDown(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 P1.3 电池分压测得的实际电压 (毫伏 mV)
|
||||||
|
* @return u16 电池电压,单位 mV (例如 3850 代表 3.85V)
|
||||||
|
*/
|
||||||
|
u16 ADC_GetBatteryVoltage_mV(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取电池电量百分比 (0% ~ 100%)
|
||||||
|
* @return u8 电量百分比 (0 ~ 100)
|
||||||
|
*/
|
||||||
|
u8 ADC_GetBatteryPercent(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检测 USB 充电是否插入
|
||||||
|
* @return bit 0=未充电, 1=充电中
|
||||||
|
* @note TP4054 CHRG 引脚开漏输出,低电平时表示正在充电。
|
||||||
|
* 需要 MCU 内部上拉或外部上拉电阻。
|
||||||
|
*/
|
||||||
|
bit Is_Charging(void);
|
||||||
|
|
||||||
|
#endif // __ADC_H__
|
||||||
456
Drivers/lcd.c
456
Drivers/lcd.c
@@ -3,278 +3,202 @@
|
|||||||
#include "../App/config.h"
|
#include "../App/config.h"
|
||||||
#include "intrins.h"
|
#include "intrins.h"
|
||||||
|
|
||||||
// RM69310 屏幕接口引脚
|
// ====== LHS114TC-IF03 (ST7789V) 屏幕 SPI 引脚映射 ======
|
||||||
sbit LCD_RST = P1^0; // 硬件复位
|
sbit LCD_RST = P1^0; // 屏幕硬件复位引脚 (RESET),低电平复位
|
||||||
sbit LCD_DCX = P1^1; // 数据/命令选择
|
sbit LCD_DCX = P1^1; // 串行数据 / 命令选择控制线 (1:数据, 0:指令)
|
||||||
sbit LCD_SDI = P1^4; // SPI 数据输入 (MOSI)
|
sbit LCD_SDI = P1^4; // SPI 主机输出从机输入数据线 (MOSI)
|
||||||
sbit LCD_SCL = P1^5; // SPI 时钟 (SCLK)
|
sbit LCD_SCL = P1^5; // SPI 串行工作时钟线 (SCLK)
|
||||||
sbit LCD_CS = P1^6; // SPI 片选 (CS)
|
sbit LCD_CS = P1^6; // SPI 物理片选选择端 (CS),低电平选通屏幕
|
||||||
|
|
||||||
// SGM3833 电源芯片控制脚
|
// ====== LHS114TC-IF03 LEDA 背光控制引脚 ======
|
||||||
sbit SGM_CTRL = P3^4;
|
sbit SGM_CTRL = P1^7; // LEDA 开启引脚 (0:开启背光, 1:关闭熄灭 - PMOS控制)
|
||||||
|
|
||||||
// External delay functions defined in main.c
|
// 引用外部延时函数 (定义在 system.c 中)
|
||||||
extern void Delay_ms(u16 ms);
|
extern void Delay_ms(u16 ms);
|
||||||
extern void Delay10us(void);
|
extern void Delay10us(void);
|
||||||
|
|
||||||
void SGM_SendPulse(u8 pulse_cnt)
|
/**
|
||||||
{
|
* @brief 软件模拟 8 位串行 SPI 字节数据发送
|
||||||
u8 i;
|
* @param dat 待发送的 1 字节数据
|
||||||
SGM_CTRL = 0;
|
*/
|
||||||
Delay_ms(5); // 确保进入关断/复位状态 (tOFF)
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
Delay_ms(5); // 确保充分初始化 (tINIT >= 300us)
|
|
||||||
for(i = 0; i < pulse_cnt; i++)
|
|
||||||
{
|
|
||||||
SGM_CTRL = 0;
|
|
||||||
Delay10us();
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
Delay10us();
|
|
||||||
}
|
|
||||||
SGM_CTRL = 1;
|
|
||||||
Delay_ms(5); // 确保数据稳定存储 (tSTORE >= 80us)
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
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;
|
LCD_SCL = 1; // 拉高时钟,触发屏幕在上升沿采样数据位
|
||||||
_nop_();
|
_nop_();
|
||||||
dat <<= 1;
|
dat <<= 1; // 左移一位,准备发送下一位
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 向屏幕控制器写入单字节控制指令 (DCX=0)
|
||||||
|
*/
|
||||||
void WriteComm(u8 cmd)
|
void WriteComm(u8 cmd)
|
||||||
{
|
{
|
||||||
LCD_CS = 0;
|
LCD_CS = 0; // 开启片选
|
||||||
LCD_DCX = 0; // 命令模式
|
LCD_DCX = 0; // 切换为指令传输模式
|
||||||
SPI_SendData(cmd);
|
SPI_SendData(cmd);
|
||||||
LCD_CS = 1;
|
LCD_CS = 1; // 关闭片选
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 向屏幕控制器写入单字节显示数据 (DCX=1)
|
||||||
|
*/
|
||||||
void WriteData(u8 dat)
|
void WriteData(u8 dat)
|
||||||
{
|
{
|
||||||
LCD_CS = 0;
|
LCD_CS = 0; // 开启片选
|
||||||
LCD_DCX = 1; // 数据模式
|
LCD_DCX = 1; // 切换为数据传输模式
|
||||||
SPI_SendData(dat);
|
SPI_SendData(dat);
|
||||||
LCD_CS = 1;
|
LCD_CS = 1; // 关闭片选
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按照物理时序执行屏幕硬件复位
|
||||||
|
*/
|
||||||
void LCD_Reset(void)
|
void LCD_Reset(void)
|
||||||
{
|
{
|
||||||
LCD_RST = 1;
|
LCD_RST = 1;
|
||||||
Delay_ms(10);
|
Delay_ms(10);
|
||||||
LCD_RST = 0;
|
LCD_RST = 0; // 拉低复位引脚进入硬件复位状态
|
||||||
Delay_ms(20);
|
Delay_ms(20);
|
||||||
LCD_RST = 1;
|
LCD_RST = 1; // 重新拉高引脚
|
||||||
Delay_ms(120);
|
Delay_ms(120); // 必须等待至少 120ms 才能执行后续的初始化
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制器
|
||||||
|
*/
|
||||||
void LCD_Init(void)
|
void LCD_Init(void)
|
||||||
{
|
{
|
||||||
// Configure LCD pins as push-pull output
|
// 开启扩展 SFR 访问权限 (EAXFR = 1),确保可以操作 P1M1 和 P1M0 模式寄存器
|
||||||
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
|
P_SW2 |= 0x80;
|
||||||
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
|
|
||||||
|
|
||||||
// Configure P3.4 (SGM_CTRL) as push-pull output
|
// 配置 P1 端口的 LCD_RST(P1.0)/DCX(P1.1)/SDI(P1.4)/SCL(P1.5)/CS(P1.6)/LEDA(P1.7) 为推挽输出模式 (M1=0, M0=1)
|
||||||
P3M1 &= ~(1 << 4);
|
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
|
||||||
P3M0 |= (1 << 4);
|
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
|
||||||
|
|
||||||
SGM_CTRL = 1;
|
// 设置各控制引脚初始默认电平 (LEDA 背光电平不在这里设置——开机路径由 GPIO_Init() 提前
|
||||||
|
// 关闭、main() 即将进入主循环前才点亮;休眠唤醒路径由 Wakeup_Restore() 在调用本函数前
|
||||||
|
// 先行点亮;两条路径对背光的时序要求不同,LCD_Init() 本身不做任何背光电平的假设)
|
||||||
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();
|
LCD_Reset(); // 硬件复位屏幕
|
||||||
|
|
||||||
// 寄存器配置(源自 Truly 信利官方初始化配置)
|
// ====== 写入 ST7789V 原厂寄存器初始化指令序列 ======
|
||||||
WriteComm(0xFE);WriteData(0x01);
|
WriteComm(0x11); // Sleep Out 退出睡眠
|
||||||
WriteComm(0x04);WriteData(0xa0); // SPI write ram enable
|
Delay_ms(120); // 退出睡眠后必须延时至少 120ms
|
||||||
WriteComm(0x05);WriteData(0x70); // 128RGB
|
|
||||||
WriteComm(0x06);WriteData(0x3C); // NL = 240 line
|
|
||||||
WriteComm(0x25);WriteData(0x06); // normal mode: gamma1
|
|
||||||
WriteComm(0x26);WriteData(0x80); // T1A = 280
|
|
||||||
WriteComm(0x27);WriteData(0x12); // normal mode VBP = 12
|
|
||||||
WriteComm(0x28);WriteData(0x12); // normal mode VFP = 8
|
|
||||||
WriteComm(0x2A);WriteData(0x06); // idle mode: gamma1
|
|
||||||
WriteComm(0x2B);WriteData(0x80); // T1B = 280
|
|
||||||
WriteComm(0x2D);WriteData(0x12); // idle mode VBP = 12
|
|
||||||
WriteComm(0x2F);WriteData(0x12); // idle mode VFP = 8
|
|
||||||
WriteComm(0x37);WriteData(0x0C); // precharge to VGSP
|
|
||||||
WriteComm(0x6D);WriteData(0x18); // skip frame VGMP and VGSP regulator off
|
|
||||||
WriteComm(0x29);WriteData(0x01); // normal mode skip frame off
|
|
||||||
WriteComm(0x30);WriteData(0x43); // idle mode skip frame = 60/2 = 30Hz
|
|
||||||
WriteComm(0x0E);WriteData(0x83); // AVDD = 5.6V normal mode
|
|
||||||
WriteComm(0x0F);WriteData(0x83); // AVDD = 5.6V idle mode
|
|
||||||
WriteComm(0x10);WriteData(0x71); // AVDD = 3xVCI
|
|
||||||
WriteComm(0x11);WriteData(0xb3); // VCL = -2xVCI
|
|
||||||
WriteComm(0x12);WriteData(0xb3); // VCL = -2xVCI
|
|
||||||
WriteComm(0x13);WriteData(0x80); // VGH = AVDD normal mode
|
|
||||||
WriteComm(0x14);WriteData(0x80); // VGH = AVDD idle mode
|
|
||||||
WriteComm(0x15);WriteData(0x81); // VGL = VCL - VCI normal mode
|
|
||||||
WriteComm(0x16);WriteData(0x81); // VGL = VCL - VCI idle mode
|
|
||||||
WriteComm(0x18);WriteData(0x66); // VGHR = 6V normal/idle mode
|
|
||||||
WriteComm(0x19);WriteData(0x44); // VGLR = -6V normal/idle mode
|
|
||||||
WriteComm(0x1E);WriteData(0x02); // Switch EQ on
|
|
||||||
WriteComm(0x5B);WriteData(0x10); // VREFN5 on
|
|
||||||
WriteComm(0x62);WriteData(0x19); // VREFN5 = -3V normal mode
|
|
||||||
WriteComm(0x63);WriteData(0x19); // VREFN5 = -3V idle mode
|
|
||||||
WriteComm(0x70);WriteData(0x55); // display off SD to AVDD
|
|
||||||
WriteComm(0x1D);WriteData(0x02); // Switch EQ on
|
|
||||||
WriteComm(0x89);WriteData(0x18); // VGMP = 5.5V
|
|
||||||
WriteComm(0x8A);WriteData(0xb9); // VGSP = 2V
|
|
||||||
WriteComm(0x8B);WriteData(0x01); // VGMP VGSP high byte
|
|
||||||
WriteComm(0x8C);WriteData(0x10); // VGMP = 5.4V
|
|
||||||
WriteComm(0x8D);WriteData(0x90); // VGSP = 2V
|
|
||||||
WriteComm(0x8E);WriteData(0x01); // VGMP VGSP high byte
|
|
||||||
WriteComm(0x6E);WriteData(0x0A); // MIPI interface off
|
|
||||||
WriteComm(0x6A);WriteData(0x05); // MUX
|
|
||||||
WriteComm(0x3A);WriteData(0x08); // T1_sd
|
|
||||||
WriteComm(0x3B);WriteData(0x00); // Tp_sd
|
|
||||||
WriteComm(0x3D);WriteData(0x16); // Th_sd
|
|
||||||
WriteComm(0x3F);WriteData(0x27); // Tsw_sd
|
|
||||||
WriteComm(0x40);WriteData(0x0F); // Thsw_sd
|
|
||||||
WriteComm(0x41);WriteData(0x0D); // Thsd_sd
|
|
||||||
|
|
||||||
WriteComm(0x42);WriteData(0x14); // Mux 142536
|
// Memory Data Access Control (MADCTL) 扫描方向控制
|
||||||
WriteComm(0x43);WriteData(0x41);
|
// 0x00: 从上至下,从左至右,RGB 格式
|
||||||
WriteComm(0x44);WriteData(0x25);
|
WriteComm(0x36);
|
||||||
WriteComm(0x45);WriteData(0x52);
|
WriteData(0x00);
|
||||||
WriteComm(0x46);WriteData(0x36);
|
|
||||||
WriteComm(0x47);WriteData(0x63);
|
|
||||||
WriteComm(0x48);WriteData(0x14);
|
|
||||||
WriteComm(0x49);WriteData(0x41);
|
|
||||||
WriteComm(0x4A);WriteData(0x25);
|
|
||||||
WriteComm(0x4B);WriteData(0x52);
|
|
||||||
WriteComm(0x4C);WriteData(0x36);
|
|
||||||
WriteComm(0x4D);WriteData(0x63);
|
|
||||||
WriteComm(0x4E);WriteData(0x14); // Data R1R2G1G2B1B2
|
|
||||||
WriteComm(0x4F);WriteData(0x41);
|
|
||||||
WriteComm(0x50);WriteData(0x25);
|
|
||||||
WriteComm(0x51);WriteData(0x52);
|
|
||||||
WriteComm(0x52);WriteData(0x36);
|
|
||||||
WriteComm(0x53);WriteData(0x63);
|
|
||||||
WriteComm(0x54);WriteData(0x14);
|
|
||||||
WriteComm(0x55);WriteData(0x41);
|
|
||||||
WriteComm(0x56);WriteData(0x25);
|
|
||||||
WriteComm(0x57);WriteData(0x52);
|
|
||||||
WriteComm(0x58);WriteData(0x36);
|
|
||||||
WriteComm(0x59);WriteData(0x63);
|
|
||||||
|
|
||||||
WriteComm(0x66);WriteData(0x90); // idle mode internal power
|
// Interface Pixel Format (COLMOD)
|
||||||
WriteComm(0x67);WriteData(0x40); // internal power delay 1 frame off
|
// 0x05 代表 16-bit/pixel (RGB565格式)
|
||||||
WriteComm(0x72);WriteData(0x1A); // internal OVDD = 4.6V
|
WriteComm(0x3A);
|
||||||
WriteComm(0x73);WriteData(0x07); // internal OVSS = -2V
|
WriteData(0x05);
|
||||||
WriteComm(0x74);WriteData(0x0C); // OVDD power from AVDD
|
|
||||||
WriteComm(0x6A);WriteData(0x3D); // swire 61=0x3D pulse
|
|
||||||
WriteComm(0x6B);WriteData(0x29); // swire 41=0x29 pulse
|
|
||||||
|
|
||||||
WriteComm(0xFE);WriteData(0x04);
|
// Porch Setting (B2h)
|
||||||
WriteComm(0x5E);WriteData(0x01);
|
WriteComm(0xB2);
|
||||||
WriteComm(0x5F);WriteData(0xB8);
|
WriteData(0x0C);
|
||||||
WriteComm(0x60);WriteData(0xBB);
|
WriteData(0x0C);
|
||||||
WriteComm(0x61);WriteData(0xBB);
|
WriteData(0x00);
|
||||||
WriteComm(0x62);WriteData(0xBB);
|
WriteData(0x33);
|
||||||
WriteComm(0x76);WriteData(0xBB);
|
WriteData(0x33);
|
||||||
WriteComm(0x77);WriteData(0x3B);
|
|
||||||
WriteComm(0x78);WriteData(0x92);
|
|
||||||
WriteComm(0x79);WriteData(0xBB);
|
|
||||||
WriteComm(0x7A);WriteData(0xBB);
|
|
||||||
|
|
||||||
WriteComm(0x00);WriteData(0xDC); // SN_CK1
|
// Gate Control (B7h)
|
||||||
WriteComm(0x01);WriteData(0x00);
|
// VGH=13.65V, VGL=-10.43V
|
||||||
WriteComm(0x02);WriteData(0x02);
|
WriteComm(0xB7);
|
||||||
WriteComm(0x03);WriteData(0x00);
|
WriteData(0x35);
|
||||||
WriteComm(0x04);WriteData(0x08);
|
|
||||||
WriteComm(0x05);WriteData(0x01);
|
|
||||||
WriteComm(0x06);WriteData(0x70);
|
|
||||||
WriteComm(0x07);WriteData(0x0A);
|
|
||||||
WriteComm(0x08);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x09);WriteData(0xDC); // SN_CK2
|
// VCOM Setting (BBh)
|
||||||
WriteComm(0x0a);WriteData(0x00);
|
// VCOM=1.175V
|
||||||
WriteComm(0x0b);WriteData(0x02);
|
WriteComm(0xBB);
|
||||||
WriteComm(0x0C);WriteData(0x00);
|
WriteData(0x19);
|
||||||
WriteComm(0x0D);WriteData(0x08);
|
|
||||||
WriteComm(0x0E);WriteData(0x00);
|
|
||||||
WriteComm(0x0F);WriteData(0x70);
|
|
||||||
WriteComm(0x10);WriteData(0x0A);
|
|
||||||
WriteComm(0x11);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x12);WriteData(0xCC); // EM_CK1
|
// LCM Control (C0h)
|
||||||
WriteComm(0x13);WriteData(0x00);
|
WriteComm(0xC0);
|
||||||
WriteComm(0x14);WriteData(0x02);
|
WriteData(0x2C);
|
||||||
WriteComm(0x15);WriteData(0x00);
|
|
||||||
WriteComm(0x16);WriteData(0x20);
|
|
||||||
WriteComm(0x17);WriteData(0x00);
|
|
||||||
WriteComm(0x18);WriteData(0x28);
|
|
||||||
WriteComm(0x19);WriteData(0x26);
|
|
||||||
WriteComm(0x1A);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x1B);WriteData(0xCC); // EM_CK2
|
// VDV and VRH Command Enable (C2h)
|
||||||
WriteComm(0x1C);WriteData(0x00);
|
WriteComm(0xC2);
|
||||||
WriteComm(0x1D);WriteData(0x02);
|
WriteData(0x01);
|
||||||
WriteComm(0x1E);WriteData(0x00);
|
|
||||||
WriteComm(0x1F);WriteData(0x20);
|
|
||||||
WriteComm(0x20);WriteData(0x01);
|
|
||||||
WriteComm(0x21);WriteData(0x28);
|
|
||||||
WriteComm(0x22);WriteData(0x26);
|
|
||||||
WriteComm(0x23);WriteData(0x00);
|
|
||||||
|
|
||||||
WriteComm(0x4C);WriteData(0x89); // SN_STV
|
// VRH Set (C3h)
|
||||||
WriteComm(0x4D);WriteData(0x00);
|
// VRH=4.3V
|
||||||
WriteComm(0x4E);WriteData(0x01);
|
WriteComm(0xC3);
|
||||||
WriteComm(0x4F);WriteData(0x00);
|
WriteData(0x12);
|
||||||
WriteComm(0x50);WriteData(0x01);
|
|
||||||
WriteComm(0x51);WriteData(0xBB);
|
|
||||||
WriteComm(0x52);WriteData(0xBB);
|
|
||||||
|
|
||||||
WriteComm(0x53);WriteData(0xCA); // EM_STV
|
// VDV Set (C4h)
|
||||||
WriteComm(0x54);WriteData(0x00);
|
// VDV=0V
|
||||||
WriteComm(0x55);WriteData(0x03);
|
WriteComm(0xC4);
|
||||||
WriteComm(0x56);WriteData(0x00);
|
WriteData(0x20);
|
||||||
WriteComm(0x58);WriteData(0x00);
|
|
||||||
WriteComm(0x59);WriteData(0x00);
|
|
||||||
WriteComm(0x65);WriteData(0x45); // 04
|
|
||||||
WriteComm(0x66);WriteData(0x0C); // 03
|
|
||||||
WriteComm(0x67);WriteData(0x00); // 10
|
|
||||||
|
|
||||||
WriteComm(0xFE);WriteData(0x01); // ID
|
// Frame Rate Control in Normal Mode (C6h)
|
||||||
WriteComm(0xE5);WriteData(0x00);
|
// 0x0F: 60Hz
|
||||||
WriteComm(0xE6);WriteData(0x10);
|
WriteComm(0xC6);
|
||||||
WriteComm(0xE7);WriteData(0x31);
|
WriteData(0x0F);
|
||||||
|
|
||||||
WriteComm(0xFE);WriteData(0x00); // 切换到 Page 0
|
// Power Control 1 (D0h)
|
||||||
WriteComm(0xC4);WriteData(0x80);
|
WriteComm(0xD0);
|
||||||
WriteComm(0x2A);WriteData(0x00);WriteData(0x04);WriteData(0x00);WriteData(0x7B); // 设置列
|
WriteData(0xA4);
|
||||||
|
WriteData(0xA1);
|
||||||
|
|
||||||
// 显式设置 16 位像素格式 (COLMOD = 0x75)
|
// Positive Gamma Correction (E0h) 正极性伽马
|
||||||
WriteComm(0x3A);WriteData(0x75);
|
WriteComm(0xE0);
|
||||||
|
WriteData(0xD0); WriteData(0x04); WriteData(0x0D); WriteData(0x11);
|
||||||
|
WriteData(0x13); WriteData(0x2B); WriteData(0x3F); WriteData(0x54);
|
||||||
|
WriteData(0x4C); WriteData(0x18); WriteData(0x0D); WriteData(0x0B);
|
||||||
|
WriteData(0x1F); WriteData(0x23);
|
||||||
|
|
||||||
WriteComm(0x11);WriteData(0x00); // 退出睡眠
|
// Negative Gamma Correction (E1h) 负极性伽马
|
||||||
Delay_ms(150);
|
WriteComm(0xE1);
|
||||||
|
WriteData(0xD0); WriteData(0x04); WriteData(0x0C); WriteData(0x11);
|
||||||
|
WriteData(0x13); WriteData(0x2C); WriteData(0x3F); WriteData(0x44);
|
||||||
|
WriteData(0x51); WriteData(0x2F); WriteData(0x1F); WriteData(0x1F);
|
||||||
|
WriteData(0x20); WriteData(0x23);
|
||||||
|
|
||||||
WriteComm(0x29);WriteData(0x00); // 开启显示
|
// Display Inversion On (21h) 开启反显,ST7789 IPS 屏常用
|
||||||
|
WriteComm(0x21);
|
||||||
|
|
||||||
|
// Display On (29h) 开启显示
|
||||||
|
WriteComm(0x29);
|
||||||
Delay_ms(20);
|
Delay_ms(20);
|
||||||
|
|
||||||
|
// 开启显示后立即进行全屏清屏,防止屏幕亮起瞬间呈现花屏随机噪点
|
||||||
|
LCD_Clear(COLOR_BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
{
|
{
|
||||||
|
// 在底层窗口设定中,统一进行物理偏置的透明校正,免除上层各个绘图函数的手工计算
|
||||||
|
x_start += LCD_X_OFFSET;
|
||||||
|
x_end += LCD_X_OFFSET;
|
||||||
|
y_start += LCD_Y_OFFSET;
|
||||||
|
y_end += LCD_Y_OFFSET;
|
||||||
|
|
||||||
|
// 写入 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));
|
||||||
|
|
||||||
|
// 写入 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));
|
||||||
@@ -282,29 +206,37 @@ void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
|
|||||||
WriteData((u8)(y_end & 0xFF));
|
WriteData((u8)(y_end & 0xFF));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 用指定色彩刷写清空全屏幕
|
||||||
|
* @param color 16位 RGB565 颜色填充值
|
||||||
|
*/
|
||||||
void LCD_Clear(u16 color)
|
void LCD_Clear(u16 color)
|
||||||
{
|
{
|
||||||
u16 i, j;
|
u16 i, j;
|
||||||
LCD_SetWindow(4, 123, 0, 239);
|
LCD_SetWindow(0, LCD_WIDTH - 1, 0, LCD_HEIGHT - 1);
|
||||||
WriteComm(0x2C);
|
WriteComm(0x2C); // 发送 RAM Write (0x2C) 显存写入命令
|
||||||
for(i = 0; i < 120; i++)
|
for(i = 0; i < LCD_WIDTH; i++)
|
||||||
{
|
{
|
||||||
for(j = 0; j < 240; j++)
|
for(j = 0; j < LCD_HEIGHT; j++)
|
||||||
{
|
{
|
||||||
WriteData((u8)(color >> 8));
|
WriteData((u8)(color >> 8)); // 发送 RGB565 高 8 位字节
|
||||||
WriteData((u8)(color & 0xFF));
|
WriteData((u8)(color & 0xFF)); // 发送 RGB565 低 8 位字节
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
{
|
{
|
||||||
u16 i, count;
|
u16 i, count;
|
||||||
u8 ch = (u8)(color >> 8);
|
u8 ch = (u8)(color >> 8);
|
||||||
u8 cl = (u8)(color & 0xFF);
|
u8 cl = (u8)(color & 0xFF);
|
||||||
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1);
|
|
||||||
WriteComm(0x2C);
|
LCD_SetWindow(x, x + w - 1, y, y + h - 1);
|
||||||
count = w * h;
|
WriteComm(0x2C); // 写入显存
|
||||||
|
count = w * h; // 实心矩形包含的总像素数
|
||||||
for(i = 0; i < count; i++)
|
for(i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
WriteData(ch);
|
WriteData(ch);
|
||||||
@@ -312,63 +244,87 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制空心矩形边界框
|
||||||
|
* @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);
|
LCD_FillRect(x, y, w, 1, color); // 上边框线
|
||||||
LCD_FillRect(x, y + h - 1, w, 1, color);
|
LCD_FillRect(x, y + h - 1, w, 1, color); // 下边框线
|
||||||
LCD_FillRect(x, y, 1, h, color);
|
LCD_FillRect(x, y, 1, h, color); // 左边框线
|
||||||
LCD_FillRect(x + w - 1, y, 1, h, color);
|
LCD_FillRect(x + w - 1, y, 1, h, color); // 右边框线
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示一个单色字模位图资源
|
||||||
|
* @param x 渲染起始坐标 X
|
||||||
|
* @param y 渲染起始坐标 Y
|
||||||
|
* @param w 位图宽度 (单位: 像素点数)
|
||||||
|
* @param h 位图高度 (单位: 像素点数)
|
||||||
|
* @param bmp FLASH 中的单色像素数组指针 (1表示画前景色,0表示画背景色)
|
||||||
|
* @param color 16位前景色
|
||||||
|
* @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;
|
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 + 4, x + w - 1 + 4, y, y + h - 1);
|
LCD_SetWindow(x, x + w - 1, y, y + h - 1); // 限制绘制显存窗口
|
||||||
WriteComm(0x2C);
|
WriteComm(0x2C);
|
||||||
|
|
||||||
|
// 嵌套循环,从第 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);
|
u16 byte_idx = r * byte_width + (c / 8); // 定位当前像素所在单色图数组中的字节索引
|
||||||
u8 bit_shift = 7 - (c % 8);
|
u8 bit_shift = 7 - (c % 8); // 定位该像素在该字节中对应的 bit 位置 (高位在前)
|
||||||
|
|
||||||
if(bmp[byte_idx] & (1 << bit_shift))
|
if(bmp[byte_idx] & (1 << bit_shift))
|
||||||
{
|
{
|
||||||
WriteData(ch);
|
WriteData(ch); // 该位为 1,发送前景色
|
||||||
WriteData(cl);
|
WriteData(cl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WriteData(bgh);
|
WriteData(bgh); // 该位为 0,发送背景色
|
||||||
WriteData(bgl);
|
WriteData(bgl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
|
||||||
|
* @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;
|
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;
|
if(ch < 0x20 || ch > 0x7E) return; // 过滤不可见控制字符
|
||||||
|
|
||||||
LCD_SetWindow(x + 4, x + 8 - 1 + 4, y, y + 16 - 1);
|
LCD_SetWindow(x, x + 8 - 1, y, y + 16 - 1);
|
||||||
WriteComm(0x2C);
|
WriteComm(0x2C);
|
||||||
|
|
||||||
|
// 循环 16 行,绘制 8x16 字型
|
||||||
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]; // 提取当前行对应的 8 位单色像素信息
|
||||||
for(c = 0; c < 8; c++)
|
for(c = 0; c < 8; c++)
|
||||||
{
|
{
|
||||||
|
// 从高到低依次提取单行中的像素点
|
||||||
if(line & (0x80 >> c))
|
if(line & (0x80 >> c))
|
||||||
{
|
{
|
||||||
WriteData(f_h);
|
WriteData(f_h);
|
||||||
@@ -383,30 +339,43 @@ void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
LCD_ShowChar8x16(x, y, *str, color, bg_color); // 渲染当前字符
|
||||||
x += 8;
|
x += 8; // X 轴起始位置偏移 8 像素,准备绘制下一个字符
|
||||||
str++;
|
str++; // 移向下一个字符
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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++;
|
while(*p++) len++; // 计算字符串长度
|
||||||
if(len * 8 >= 120)
|
|
||||||
|
if(len * 8 >= LCD_WIDTH)
|
||||||
|
// 若超出或等同于屏幕有效宽度,强制从最左侧 0 开始左对齐绘制
|
||||||
LCD_ShowString(0, y, str, color, bg_color);
|
LCD_ShowString(0, y, str, color, bg_color);
|
||||||
else
|
else
|
||||||
LCD_ShowString((120 - len * 8) / 2, y, str, color, bg_color);
|
// 否则通过公式 (LCD_WIDTH - 字符宽度) / 2 计算出水平居中的首字起始偏移 X 坐标并渲染
|
||||||
|
LCD_ShowString((LCD_WIDTH - len * 8) / 2, y, str, color, bg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
||||||
{
|
{
|
||||||
u8 r, c, i, j;
|
u8 r, c, i, j;
|
||||||
|
u8 val_bit;
|
||||||
u8 char_idx = ch - 0x20;
|
u8 char_idx = ch - 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);
|
||||||
@@ -415,16 +384,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 + 4, x + 16 - 1 + 4, y, y + 32 - 1);
|
LCD_SetWindow(x, x + 16 - 1, y, y + 32 - 1); // 锁定 16x32 的绘图视窗
|
||||||
WriteComm(0x2C);
|
WriteComm(0x2C);
|
||||||
|
|
||||||
|
// 遍历字型的 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];
|
||||||
|
|
||||||
|
// 纵向插点翻倍:将当前行重复绘制两次 (i = 0 和 i = 1)
|
||||||
for(i = 0; i < 2; i++)
|
for(i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
|
// 遍历单行内的 8 个 bit 像素点
|
||||||
for(c = 0; c < 8; c++)
|
for(c = 0; c < 8; c++)
|
||||||
{
|
{
|
||||||
u8 val_bit = (line & (0x80 >> c));
|
val_bit = (line & (0x80 >> c));
|
||||||
|
// 横向插点翻倍:单个像素点重复发送写入 2 次 (j = 0 和 j = 1)
|
||||||
for(j = 0; j < 2; j++)
|
for(j = 0; j < 2; j++)
|
||||||
{
|
{
|
||||||
if(val_bit)
|
if(val_bit)
|
||||||
@@ -443,23 +418,32 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
x += 16; // 每次起始坐标偏移 16 像素
|
||||||
str++;
|
str++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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++;
|
while(*p++) len++; // 计算字数
|
||||||
if(len * 16 >= 120)
|
|
||||||
|
if(len * 16 >= LCD_WIDTH)
|
||||||
|
// 超出屏幕宽度,左对齐
|
||||||
LCD_ShowString16x32(0, y, str, color, bg_color);
|
LCD_ShowString16x32(0, y, str, color, bg_color);
|
||||||
else
|
else
|
||||||
LCD_ShowString16x32((120 - len * 16) / 2, y, str, color, bg_color);
|
// 水平居中计算
|
||||||
|
LCD_ShowString16x32((LCD_WIDTH - len * 16) / 2, y, str, color, bg_color);
|
||||||
}
|
}
|
||||||
|
|||||||
123
Drivers/lcd.h
123
Drivers/lcd.h
@@ -4,49 +4,118 @@
|
|||||||
#include "../App/config.h"
|
#include "../App/config.h"
|
||||||
#include "stc32g.h"
|
#include "stc32g.h"
|
||||||
|
|
||||||
// Color Constants (RGB565)
|
/* ====== LHS114TC-IF03 (ST7789V) 屏幕参数与常数定义 ====== */
|
||||||
#define COLOR_BLACK 0x0000
|
#define LCD_WIDTH 135 // 屏幕物理宽度
|
||||||
#define COLOR_WHITE 0xFFFF
|
#define LCD_HEIGHT 240 // 屏幕物理高度
|
||||||
#define COLOR_CYAN 0x073F
|
#define LCD_X_OFFSET 52 // 水平 X 轴物理偏置量
|
||||||
#define COLOR_RED 0xF800
|
#define LCD_Y_OFFSET 40 // 垂直 Y 轴物理偏置量
|
||||||
#define COLOR_GREEN 0x07E0
|
|
||||||
#define COLOR_BLUE 0x001F
|
|
||||||
#define COLOR_GRAY 0x5AEB
|
|
||||||
#define COLOR_AMBER 0xFDC0
|
|
||||||
|
|
||||||
// Public screen functions
|
// 16位 RGB565 颜色常量定义:
|
||||||
|
#define COLOR_BLACK 0x0000 // 黑色
|
||||||
|
#define COLOR_WHITE 0xFFFF // 白色
|
||||||
|
#define COLOR_CYAN 0x073F // 青色
|
||||||
|
#define COLOR_RED 0xF800 // 红色
|
||||||
|
#define COLOR_GREEN 0x07E0 // 绿色
|
||||||
|
#define COLOR_BLUE 0x001F // 蓝色
|
||||||
|
#define COLOR_GRAY 0x5AEB // 灰色
|
||||||
|
#define COLOR_AMBER 0xFDC0 // 琥珀色
|
||||||
|
|
||||||
|
/* ====== 屏幕电源控制与基本驱动接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 通过脉冲序列为 SGM3833 供电芯片写入配置
|
||||||
|
*/
|
||||||
void SGM_SendPulse(u8 pulse_cnt);
|
void SGM_SendPulse(u8 pulse_cnt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行 LHS114TC-IF03 屏幕的硬件复位
|
||||||
|
*/
|
||||||
void LCD_Reset(void);
|
void LCD_Reset(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制器
|
||||||
|
*/
|
||||||
void LCD_Init(void);
|
void LCD_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 向屏幕写入 1 字节控制命令
|
||||||
|
*/
|
||||||
void WriteComm(u8 cmd);
|
void WriteComm(u8 cmd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 向屏幕写入 1 字节显示数据
|
||||||
|
*/
|
||||||
|
void WriteData(u8 dat);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 用指定色彩刷写清空全屏幕
|
||||||
|
*/
|
||||||
void LCD_Clear(u16 color);
|
void LCD_Clear(u16 color);
|
||||||
|
|
||||||
// Drawing Primitives
|
/* ====== 关键图形与点阵字符渲染接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制空心矩形边界框
|
||||||
|
*/
|
||||||
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染显示一个单色字模位图资源
|
||||||
|
*/
|
||||||
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/* ====== 图标外部点阵数据声明 ====== */
|
||||||
|
extern unsigned char code FONT_8x16[95][16]; // 8x16 英文点阵字库
|
||||||
|
extern unsigned char code bmp_battery_20x10[]; // 20x10 电池指示位图
|
||||||
|
extern unsigned char code bmp_lock_16x16[]; // 16x16 闭合锁头位图
|
||||||
|
extern unsigned char code bmp_unlock_16x16[]; // 16x16 开启锁头位图
|
||||||
|
extern unsigned char code bmp_radar_32x32_f1[]; // 32x32 雷达扩散帧1
|
||||||
|
extern unsigned char code bmp_radar_32x32_f2[]; // 32x32 雷达扩散帧2
|
||||||
|
extern unsigned char code bmp_radar_32x32_f3[]; // 32x32 雷达扩散帧3
|
||||||
|
extern unsigned char code bmp_confirm_lock_32x32[]; // 32x32 锁型常规大图标
|
||||||
|
extern unsigned char code bmp_checkmark_32x32[]; // 32x32 成功大对勾
|
||||||
|
extern unsigned char code bmp_cross_32x32[]; // 32x32 失败大红叉
|
||||||
|
extern unsigned char code bmp_door_32x32[]; // 32x32 门磁大警示图
|
||||||
|
extern unsigned char code bmp_siren_32x32[]; // 32x32 烟感喇叭大警示图
|
||||||
|
|
||||||
|
#endif // __LCD_H__
|
||||||
// Font and Bitmap externs (defined in lcd_font.h via lcd.c)
|
|
||||||
extern unsigned char code FONT_8x16[95][16];
|
|
||||||
extern unsigned char code bmp_battery_20x10[];
|
|
||||||
extern unsigned char code bmp_lock_16x16[];
|
|
||||||
extern unsigned char code bmp_unlock_16x16[];
|
|
||||||
extern unsigned char code bmp_radar_32x32_f1[];
|
|
||||||
extern unsigned char code bmp_radar_32x32_f2[];
|
|
||||||
extern unsigned char code bmp_radar_32x32_f3[];
|
|
||||||
extern unsigned char code bmp_confirm_lock_32x32[];
|
|
||||||
extern unsigned char code bmp_checkmark_32x32[];
|
|
||||||
extern unsigned char code bmp_cross_32x32[];
|
|
||||||
extern unsigned char code bmp_door_32x32[];
|
|
||||||
extern unsigned char code bmp_siren_32x32[];
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
#ifndef __FONT_H__
|
#ifndef __FONT_H__
|
||||||
#define __FONT_H__
|
#define __FONT_H__
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
* @file lcd_font.h
|
||||||
|
* @brief 屏幕显示字库与单色位图数据定义文件
|
||||||
|
* @details 本文件存储了手环系统在 AMOLED 屏幕上渲染所使用的各类静态图形资源,
|
||||||
|
* 包含以下内容:
|
||||||
|
* 1. 电池电量单色图 (bmp_battery_20x10)
|
||||||
|
* 2. 布防/撤防指示用 16x16 锁图标 (bmp_lock, bmp_unlock)
|
||||||
|
* 3. 对码动画所需的 32x32 雷达动态扫描帧 (bmp_radar_32x32_f1 ~ f3)
|
||||||
|
* 4. 设备类型确认与保存界面用的 32x32 锁、成功对勾、失败红叉位图
|
||||||
|
* 5. 报警入侵指示用的 32x32 门磁大图 (bmp_door_32x32) 与警笛喇叭大图
|
||||||
|
* 6. 标准 8x16 英文 ASCII 字符点阵字库 (FONT_8x16),用于文本渲染
|
||||||
|
* @note 所有数据均使用 `code` 关键字修饰,强制存储在单片机 Flash 空间中以节省 RAM。
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
// 1. Horizontal Battery Icon (20x10 pixels)
|
// 1. Horizontal Battery Icon (20x10 pixels)
|
||||||
// Width: 20 pixels (3 bytes per row), Height: 10 rows
|
// Width: 20 pixels (3 bytes per row), Height: 10 rows
|
||||||
|
|||||||
458
Drivers/pcf8563.c
Normal file
458
Drivers/pcf8563.c
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
#include "pcf8563.h"
|
||||||
|
#include "../App/system.h"
|
||||||
|
|
||||||
|
// 动态引脚对调全局标志 (0: 默认 SCL=P3.2, SDA=P3.3; 1: 对调 SCL=P3.3, SDA=P3.2)
|
||||||
|
volatile u8 i2c_swapped = 0;
|
||||||
|
|
||||||
|
static void SCL_W(u8 val)
|
||||||
|
{
|
||||||
|
if (!i2c_swapped) P32 = val;
|
||||||
|
else P33 = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SDA_W(u8 val)
|
||||||
|
{
|
||||||
|
if (!i2c_swapped) P33 = val;
|
||||||
|
else P32 = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 SDA_R(void)
|
||||||
|
{
|
||||||
|
if (!i2c_swapped) return P33;
|
||||||
|
else return P32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态延时参数 (单位: us)
|
||||||
|
volatile u8 i2c_delay_us = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 产生起始信号 (START)
|
||||||
|
*/
|
||||||
|
static void I2C_Start(void)
|
||||||
|
{
|
||||||
|
SDA_W(1);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SDA_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 产生停止信号 (STOP)
|
||||||
|
*/
|
||||||
|
static void I2C_Stop(void)
|
||||||
|
{
|
||||||
|
SDA_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SDA_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 发送应答/非应答信号
|
||||||
|
* @param ack 1: ACK(0), 0: NACK(1)
|
||||||
|
*/
|
||||||
|
static void I2C_SendAck(bit ack)
|
||||||
|
{
|
||||||
|
SDA_W(!ack);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SDA_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 等待从机应答 (Wait ACK)
|
||||||
|
* @return bit 1: 收到应答, 0: 未收到应答
|
||||||
|
*/
|
||||||
|
static bit I2C_WaitAck(void)
|
||||||
|
{
|
||||||
|
u8 retry = 0;
|
||||||
|
SDA_W(1); // 释放 SDA 接收 ACK
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
while (SDA_R()) {
|
||||||
|
retry++;
|
||||||
|
Delay_us(5);
|
||||||
|
if (retry > 250) {
|
||||||
|
I2C_Stop();
|
||||||
|
return 0; // 超时未收到 ACK 应答,立即优雅退出,防止死锁
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 发送单字节数据
|
||||||
|
* @param dat 待发送 of 字节数据
|
||||||
|
*/
|
||||||
|
static void I2C_SendByte(u8 dat)
|
||||||
|
{
|
||||||
|
u8 i;
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
SDA_W((dat & 0x80) ? 1 : 0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
dat <<= 1;
|
||||||
|
}
|
||||||
|
SDA_W(1); // 释放 SDA 接收 ACK
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 读取单字节数据
|
||||||
|
* @return u8 接收到的字节数据
|
||||||
|
*/
|
||||||
|
static u8 I2C_ReadByte(void)
|
||||||
|
{
|
||||||
|
u8 i, dat = 0;
|
||||||
|
SDA_W(1); // 设为输入
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
dat <<= 1;
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
if (SDA_R()) {
|
||||||
|
dat |= 0x01;
|
||||||
|
}
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(i2c_delay_us);
|
||||||
|
}
|
||||||
|
return dat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== BCD 与二进制 10 进制互转辅助函数 ====== */
|
||||||
|
|
||||||
|
static u8 BCD2DEC(u8 bcd)
|
||||||
|
{
|
||||||
|
return ((bcd >> 4) * 10) + (bcd & 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 DEC2BCD(u8 dec)
|
||||||
|
{
|
||||||
|
return (((dec / 10) << 4) | (dec % 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void I2C_ReleaseBus(void)
|
||||||
|
{
|
||||||
|
u8 i;
|
||||||
|
SDA_W(1);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
|
||||||
|
// 如果 SDA 被拉低,说明总线被死锁占用
|
||||||
|
if (SDA_R() == 0) {
|
||||||
|
for (i = 0; i < 9; i++) {
|
||||||
|
SCL_W(0);
|
||||||
|
Delay_us(30);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
if (SDA_R() == 1) {
|
||||||
|
break; // 从机已释放总线,提前退出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制产生停止条件恢复空闲
|
||||||
|
SDA_W(0);
|
||||||
|
Delay_us(30);
|
||||||
|
SCL_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
SDA_W(1);
|
||||||
|
Delay_us(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ====== PCF8563 驱动核心接口 ====== */
|
||||||
|
|
||||||
|
bit PCF8563_Init(void)
|
||||||
|
{
|
||||||
|
// 1. 默认引脚尝试 (SCL=P3.2, SDA=P3.3)
|
||||||
|
i2c_swapped = 0;
|
||||||
|
I2C_ReleaseBus(); // 强力总线死锁释放
|
||||||
|
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[RTC] Detected Normal pin mapping (SCL=P32, SDA=P33)\r\n");
|
||||||
|
} else {
|
||||||
|
// 2. 尝试对调引脚 (SCL=P3.3, SDA=P3.2)
|
||||||
|
I2C_Stop();
|
||||||
|
i2c_swapped = 1;
|
||||||
|
I2C_ReleaseBus(); // 强力总线死锁释放
|
||||||
|
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[RTC] Detected SWAPPED pin mapping (SCL=P33, SDA=P32)!\r\n");
|
||||||
|
} else {
|
||||||
|
// 恢复默认,防止干扰
|
||||||
|
i2c_swapped = 0;
|
||||||
|
Uart_SendString("[RTC-ERR] PCF8563 Offline on both normal/swapped pins!\r\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I2C_SendByte(REG_CTRL_STATUS1);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(0x00); // 正常模式,不停止芯片计数
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PCF8563_ScanDiagnostic(void)
|
||||||
|
{
|
||||||
|
u16 addr;
|
||||||
|
u8 found = 0;
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-SCAN] --- 1. Scanning with default pins (SCL=P32, SDA=P33) ---\r\n");
|
||||||
|
i2c_swapped = 0;
|
||||||
|
for (addr = 0x02; addr < 0x100; addr += 2) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte((u8)addr);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-SCAN] Device Found: 0x");
|
||||||
|
Uart_SendHex8((u8)addr);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
found = 1;
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
Delay_ms(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-SCAN] --- 2. Scanning with SWAPPED pins (SCL=P33, SDA=P32) ---\r\n");
|
||||||
|
i2c_swapped = 1;
|
||||||
|
for (addr = 0x02; addr < 0x100; addr += 2) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte((u8)addr);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-SCAN] Device Found: 0x");
|
||||||
|
Uart_SendHex8((u8)addr);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
found = 1;
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
Delay_ms(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描结束后,根据 init 侦测结果重新绑定,此处临时归零
|
||||||
|
i2c_swapped = 0;
|
||||||
|
Uart_SendString("[I2C-SCAN] Dual-mode Address Scan Completed!\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 PCF8563 连续读取时间与日期 (秒/分/时/日/星期/月/年,寄存器 0x02~0x08 地址连续)
|
||||||
|
*/
|
||||||
|
bit PCF8563_ReadDateTime(u8 *hour, u8 *min, u8 *sec, u8 *day, u8 *month, u8 *year)
|
||||||
|
{
|
||||||
|
u8 raw_sec, raw_min, raw_hour, raw_day, raw_weekday, raw_month, raw_year;
|
||||||
|
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 设置起始读取地址为 0x02 (REG_VL_SECONDS)
|
||||||
|
I2C_SendByte(REG_VL_SECONDS);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
// 重新产生 Start 读取数据
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_READ);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
raw_sec = I2C_ReadByte();
|
||||||
|
I2C_SendAck(1);
|
||||||
|
|
||||||
|
raw_min = I2C_ReadByte();
|
||||||
|
I2C_SendAck(1);
|
||||||
|
|
||||||
|
raw_hour = I2C_ReadByte();
|
||||||
|
I2C_SendAck(1);
|
||||||
|
|
||||||
|
raw_day = I2C_ReadByte();
|
||||||
|
I2C_SendAck(1);
|
||||||
|
|
||||||
|
raw_weekday = I2C_ReadByte(); // 星期寄存器不使用,芯片自身的星期在改日期后可能与实际不符
|
||||||
|
I2C_SendAck(1);
|
||||||
|
(void)raw_weekday;
|
||||||
|
|
||||||
|
raw_month = I2C_ReadByte();
|
||||||
|
I2C_SendAck(1);
|
||||||
|
|
||||||
|
raw_year = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0); // 发送 NACK 结束连续读取
|
||||||
|
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
// 解析 BCD 数据 (掩码屏蔽无效位)
|
||||||
|
*sec = BCD2DEC(raw_sec & 0x7F);
|
||||||
|
*min = BCD2DEC(raw_min & 0x7F);
|
||||||
|
*hour = BCD2DEC(raw_hour & 0x3F);
|
||||||
|
*day = BCD2DEC(raw_day & 0x3F);
|
||||||
|
*month = BCD2DEC(raw_month & 0x1F); // 忽略 bit7 世纪位,固定按 2000~2099 处理
|
||||||
|
*year = BCD2DEC(raw_year);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将时间与日期一次性连续写入 PCF8563 RTC 芯片 (寄存器 0x02~0x08 共 7 字节连续写入)
|
||||||
|
*/
|
||||||
|
bit PCF8563_WriteDateTime(u8 hour, u8 min, u8 sec, u8 day, u8 month, u8 year, u8 weekday)
|
||||||
|
{
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(PCF8563_ADDR_WRITE);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(REG_VL_SECONDS);
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(DEC2BCD(sec & 0x7F));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(DEC2BCD(min & 0x7F));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(DEC2BCD(hour & 0x3F));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(DEC2BCD(day & 0x3F));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(weekday & 0x07); // 星期不是 BCD,直接写 0~6
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(DEC2BCD(month & 0x1F)); // bit7 世纪位固定写 0,不处理跨世纪
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_SendByte(DEC2BCD(year));
|
||||||
|
if (!I2C_WaitAck()) return 0;
|
||||||
|
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bit PCF8563_TryReadDiagnostic(void)
|
||||||
|
{
|
||||||
|
u8 val = 0;
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-DIAG] --- Test 1: Trying 30us Delay (30kHz) ---\r\n");
|
||||||
|
i2c_delay_us = 30;
|
||||||
|
|
||||||
|
i2c_swapped = 0;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 30us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 30us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
i2c_swapped = 1;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 30us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 30us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
Uart_SendString("[I2C-DIAG] --- Test 2: Retrying with ultra-slow 200us Delay (2.5kHz) ---\r\n");
|
||||||
|
i2c_delay_us = 200;
|
||||||
|
|
||||||
|
i2c_swapped = 0;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 200us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=0, 200us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
i2c_swapped = 1;
|
||||||
|
I2C_ReleaseBus();
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA2);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 200us) 0xA2 ACK OK\r\n");
|
||||||
|
I2C_SendByte(0x00);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
I2C_Start();
|
||||||
|
I2C_SendByte(0xA3);
|
||||||
|
if (I2C_WaitAck()) {
|
||||||
|
val = I2C_ReadByte();
|
||||||
|
I2C_SendAck(0);
|
||||||
|
I2C_Stop();
|
||||||
|
Uart_SendString("[I2C-DIAG] (swapped=1, 200us) Success! Reg 0x00=0x");
|
||||||
|
Uart_SendHex8(val);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_Stop();
|
||||||
|
|
||||||
|
// 恢复默认设置
|
||||||
|
i2c_swapped = 0;
|
||||||
|
i2c_delay_us = 30;
|
||||||
|
Uart_SendString("[I2C-DIAG] Both 30us and 200us diagnostic trial failed.\r\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
66
Drivers/pcf8563.h
Normal file
66
Drivers/pcf8563.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef __PCF8563_H__
|
||||||
|
#define __PCF8563_H__
|
||||||
|
|
||||||
|
#include "../App/config.h"
|
||||||
|
|
||||||
|
/* ====== PCF8563 器件地址与寄存器定义 ====== */
|
||||||
|
#define PCF8563_ADDR_READ 0xA3 // PCF8563 读地址 (0xA2 | 0x01)
|
||||||
|
#define PCF8563_ADDR_WRITE 0xA2 // PCF8563 写地址 (0xA2)
|
||||||
|
|
||||||
|
#define REG_CTRL_STATUS1 0x00 // 控制/状态寄存器 1
|
||||||
|
#define REG_CTRL_STATUS2 0x01 // 控制/状态寄存器 2
|
||||||
|
#define REG_VL_SECONDS 0x02 // 秒寄存器 (bit7 为 VL 保证标志)
|
||||||
|
#define REG_MINUTES 0x03 // 分钟寄存器
|
||||||
|
#define REG_HOURS 0x04 // 小时寄存器
|
||||||
|
#define REG_DAYS 0x05 // 日寄存器
|
||||||
|
#define REG_WEEKDAYS 0x06 // 星期寄存器
|
||||||
|
#define REG_MONTHS 0x07 // 月份寄存器 (bit7 为 C 世纪位)
|
||||||
|
#define REG_YEARS 0x08 // 年份寄存器
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 PCF8563 RTC 芯片
|
||||||
|
* @details 初始化 I2C 管脚高电平,并检测芯片启动状态
|
||||||
|
* @return bit 1: 芯片响应正常, 0: 通讯异常
|
||||||
|
*/
|
||||||
|
bit PCF8563_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 PCF8563 芯片连续读取时间与日期 (时/分/秒/日/月/年)
|
||||||
|
* @param hour 小时指针 (0~23)
|
||||||
|
* @param min 分钟指针 (0~59)
|
||||||
|
* @param sec 秒钟指针 (0~59)
|
||||||
|
* @param day 日指针 (1~31)
|
||||||
|
* @param month 月指针 (1~12)
|
||||||
|
* @param year 年指针 (0~99,代表 2000~2099,不处理世纪位)
|
||||||
|
* @return bit 1: 读取成功, 0: 通讯失败
|
||||||
|
*/
|
||||||
|
bit PCF8563_ReadDateTime(u8 *hour, u8 *min, u8 *sec, u8 *day, u8 *month, u8 *year);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将指定的时间与日期一次性连续写入 PCF8563 芯片
|
||||||
|
* @param hour 小时 (0~23)
|
||||||
|
* @param min 分钟 (0~59)
|
||||||
|
* @param sec 秒钟 (0~59)
|
||||||
|
* @param day 日 (1~31)
|
||||||
|
* @param month 月 (1~12)
|
||||||
|
* @param year 年 (0~99,代表 2000~2099)
|
||||||
|
* @param weekday 星期 (0~6,需与软件侧约定的星期数值一致,芯片本身不做校验)
|
||||||
|
* @return bit 1: 写入成功, 0: 通讯失败
|
||||||
|
*/
|
||||||
|
bit PCF8563_WriteDateTime(u8 hour, u8 min, u8 sec, u8 day, u8 month, u8 year, u8 weekday);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief I2C 总线器件扫描诊断函数
|
||||||
|
* @details 逐个发送 I2C 写地址,检测总线上是否有响应的器件,并在串口打印发现的设备地址
|
||||||
|
*/
|
||||||
|
void PCF8563_ScanDiagnostic(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 尝试直接用最底层时序试读 RTC 控制寄存器并输出 ACK 调试日志
|
||||||
|
*/
|
||||||
|
bit PCF8563_TryReadDiagnostic(void);
|
||||||
|
|
||||||
|
// 动态引脚对调标志 (由 pcf8563.c 维护,0: 默认, 1: 互换)
|
||||||
|
extern volatile u8 i2c_swapped;
|
||||||
|
|
||||||
|
#endif // __PCF8563_H__
|
||||||
616
Drivers/rf.c
616
Drivers/rf.c
@@ -1,263 +1,504 @@
|
|||||||
#include "rf.h"
|
#include "rf.h"
|
||||||
|
#include "timer.h"
|
||||||
#include "../App/config.h"
|
#include "../App/config.h"
|
||||||
|
#include "../App/rgb.h"
|
||||||
#include "intrins.h"
|
#include "intrins.h"
|
||||||
|
|
||||||
// ATR5179 射频开关控制脚
|
// ATR5179 射频前端天线开关控制引脚定义
|
||||||
sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到 TX 路径)
|
sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到发射 TX 路径)
|
||||||
sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到 RX 路径)
|
sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到接收 RX 路径)
|
||||||
|
|
||||||
// 射频数据与控制引脚
|
// 射频芯片数据与电源控制引脚定义 (根据新版 PCB 引脚对调修正)
|
||||||
sbit RF_TX_DAT = P3^6; // LT4455 数据输入 (DIN)
|
sbit RF_TX_DAT = P2^1; // LR690L 发射调制数据输入引脚 (DIN) - 物理连接 Pin 22 (P2.1)
|
||||||
sbit RF_RX_DATA = P2^1; // LR690L 数据输出 (DATA)
|
sbit RF_RX_DATA = P3^6; // LR690L 接收解调数据输出引脚 (DATA) - 物理连接 Pin 19 (P3.6)
|
||||||
sbit SHUT = P2^2; // LR690L 休眠脚 (0:工作, 1:休眠)
|
|
||||||
|
|
||||||
// External functions defined in main.c
|
// 声明外部延时及串口打印函数 (在 system.c 中定义)
|
||||||
extern void Delay_us(u16 us);
|
extern void Delay_us(u16 us);
|
||||||
extern void Delay_ms(u16 ms);
|
extern void Delay_ms(u16 ms);
|
||||||
extern u16 GetTimer0_Safe(void);
|
|
||||||
extern void Uart_SendString(char *s);
|
extern void Uart_SendString(char *s);
|
||||||
extern void Uart_SendHex4(u8 val);
|
extern void Uart_SendHex4(u8 val);
|
||||||
extern void Uart_SendHex8(u8 val);
|
extern void Uart_SendHex8(u8 val);
|
||||||
extern void Uart_SendHex16(u16 val);
|
extern void Uart_SendHex16(u16 val);
|
||||||
extern void Uart_SendHex20(u32 val);
|
extern void Uart_SendHex20(u32 val);
|
||||||
|
extern void Uart_SendHex32(u32 val);
|
||||||
extern void Uart_SendByte(u8 dat);
|
extern void Uart_SendByte(u8 dat);
|
||||||
|
extern volatile u16 ms_tick; // 全局毫秒滴答 (0~999 循环),用于半成品帧看门狗超时判断
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化无线射频芯片相关的 GPIO 引脚方向与状态
|
||||||
|
* @details 同时在此完成 Timer0 自由运行时间戳基准与 P3.6 (RF_RX_DATA) 常态单边沿触发中断的
|
||||||
|
* 一次性开机配置 (软件乒乓切换模拟双边沿,见 RF_HandleEdgeInterrupt()),
|
||||||
|
* 供增量解码状态机全程使用。
|
||||||
|
*/
|
||||||
void RF_Init(void)
|
void RF_Init(void)
|
||||||
{
|
{
|
||||||
// P2.0 (RF_TX) 和 P2.2 (SHUT) 配置为推挽输出,P2.1 (RF_RX_DATA) 配置为双向口/输入
|
// EAXFR = 1 允许访问扩展特殊功能寄存器 (XSFR)
|
||||||
P2M1 &= ~((1 << 0) | (1 << 1) | (1 << 2));
|
EAXFR = 1;
|
||||||
P2M0 &= ~(1 << 1);
|
|
||||||
P2M0 |= ((1 << 0) | (1 << 2));
|
|
||||||
|
|
||||||
// P3.6 (RF_TX_DAT), P3.7 (RF_RX) 配置为推挽输出
|
// 1. 配置 P2.0 (RF_TX) 和 P2.1 (RF_TX_DAT) 为推挽输出模式 (M1=0, M0=1)
|
||||||
P3M1 &= ~((1 << 6) | (1 << 7));
|
P2M1 &= ~((1 << 0) | (1 << 1));
|
||||||
P3M0 |= ((1 << 6) | (1 << 7));
|
P2M0 |= ((1 << 0) | (1 << 1));
|
||||||
|
|
||||||
RF_TX = 0;
|
// 2. 配置 P3.7 (RF_RX) 和 P3.5 (SHUT) 为推挽输出模式 (M1=0, M0=1)
|
||||||
SHUT = 1; // 默认关闭接收芯片
|
// 配置 P3.6 (RF_RX_DATA) 为高阻输入模式 (M1=1, M0=0)以匹配接收解调输入
|
||||||
RF_RX = 0;
|
P3M1 &= ~((1 << 5) | (1 << 7));
|
||||||
RF_TX_DAT = 0;
|
P3M1 |= (1 << 6);
|
||||||
|
P3M0 &= ~(1 << 6);
|
||||||
|
P3M0 |= ((1 << 5) | (1 << 7));
|
||||||
|
|
||||||
|
// P3.6 内部下拉:SHUT=1(芯片关断)期间 DATA 脚高阻悬空,下拉把悬空状态钳在低电平。
|
||||||
|
// 之前误用上拉,配合当年错误的"高电平中断"配置,悬空高电平会导致中断风暴——
|
||||||
|
// 现在确认端口中断硬件只有下降沿/上升沿/低电平/高电平四选一,没有真正的"双边沿"模式,
|
||||||
|
// 已改为下面的软件乒乓单边沿切换方案,配下拉才是让悬空态落在"不触发"一侧的正确选择。
|
||||||
|
P3PD |= 0x40;
|
||||||
|
|
||||||
|
// 3. 设置系统默认的初始电平
|
||||||
|
RF_TX = 0; // 断开发射天线通路
|
||||||
|
SHUT = 1; // 默认拉高 SHUT,使 LR690L 处于休眠关闭状态
|
||||||
|
RF_RX = 0; // 断开接收天线通路
|
||||||
|
RF_TX_DAT = 0; // 调制发送数据线置低
|
||||||
|
|
||||||
|
// 4. 启动 Timer0 常驻自由运行,作为边沿中断增量解码的时间戳基准 (实现见 Drivers/timer.c)
|
||||||
|
Timer0_Init();
|
||||||
|
|
||||||
|
// 5. P3.6 (RF_RX_DATA) 中断常态开启。硬件 PxIM1/PxIM0 只支持下降沿/上升沿/低电平/高电平
|
||||||
|
// 四选一,没有"双边沿都触发"的模式;(IM1=1,IM0=1) 实际是"高电平中断",会在整个高电平
|
||||||
|
// 期间不断重复触发,这正是之前边沿计数异常偏高(远超协议理论值)的真正原因。
|
||||||
|
// 现改为初始配置成"上升沿触发",配合 RF_HandleEdgeInterrupt() 里的软件乒乓切换
|
||||||
|
// (每次触发后立即翻转到相反边沿) 来模拟真正的双边沿触发,每次真实翻转只精确响应一次。
|
||||||
|
P3IM1 &= ~0x40; // IM1=0
|
||||||
|
P3IM0 |= 0x40; // IM0=1 => (0,1) 上升沿触发,先等待同步头/数据位高电平的起始
|
||||||
|
P3INTF = 0x00; // 清除挂起的中断标志
|
||||||
|
P3INTE |= 0x40; // 开启 P3.6 中断允许 (全局 EA 由 Timer1_Init() 统一开启)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 配置无线射频收发系统的运行模式与天线切换
|
||||||
|
* @param mode 运行模式 (0-休眠隔离, 1-接收工作, 2-发射工作)
|
||||||
|
*/
|
||||||
void RF_SetMode(u8 mode)
|
void RF_SetMode(u8 mode)
|
||||||
{
|
{
|
||||||
if (mode == 0) // Sleep/Idle
|
if (mode == 0) // ====== 模式 0: 休眠隔离模式 (Sleep/Idle) ======
|
||||||
{
|
{
|
||||||
SHUT = 1; // LR690L Sleep
|
SHUT = 1; // 强制拉高 SHUT 引脚,使 LR690L 彻底进入低功耗待机
|
||||||
RF_RX = 0; // 断开接收天线
|
RF_RX = 0; // 关闭接收天线开关,防止外界电磁杂波串入接收电路
|
||||||
RF_TX = 0; // 断开发射天线
|
RF_TX = 0; // 关闭发射天线开关
|
||||||
}
|
}
|
||||||
else if (mode == 1) // Rx Mode
|
else if (mode == 1) // ====== 模式 1: 接收工作模式 (RX Mode) ======
|
||||||
{
|
{
|
||||||
SHUT = 0; // LR690L 工作
|
SHUT = 0; // 拉低 SHUT 使能 LR690L 芯片,开始解调接收
|
||||||
RF_RX = 1; // 天线切至接收端
|
RF_TX = 0; // 关闭发射开关
|
||||||
RF_TX = 0;
|
RF_RX = 1; // 选通天线到 RX 接收路径,使能接收链路高频放大
|
||||||
}
|
}
|
||||||
else if (mode == 2) // Tx Mode
|
else if (mode == 2) // ====== 模式 2: 发发射工作模式 (TX Mode) ======
|
||||||
{
|
{
|
||||||
SHUT = 1; // LR690L 睡眠
|
SHUT = 1; // 接收机停机,防止发射强信号顶坏接收前端
|
||||||
RF_RX = 0;
|
RF_RX = 0; // 关闭接收开关
|
||||||
RF_TX = 1; // 天线切至发射端
|
RF_TX = 1; // 选通天线到 TX 发射路径,使能天线发射
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 使用 Timer0 自由运行时间戳实现的高精度微秒延时
|
||||||
|
* @param us 延时微秒数
|
||||||
|
* @note 旧版实现会启停复位 Timer0,与本文件新增的常驻自由运行用法冲突 (会导致 RX 时间戳永久停摆),
|
||||||
|
* 改为对运行中的 Timer0 做起止快照比较,纯只读、不影响其自由运行状态。
|
||||||
|
*/
|
||||||
|
void RF_Delay_us(u16 us)
|
||||||
|
{
|
||||||
|
u32 start = Timer0_GetTimestamp();
|
||||||
|
u32 wait_ticks = (u32)us * 2UL; // 0.5us/tick
|
||||||
|
while ((u32)(Timer0_GetTimestamp() - start) < wait_ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* EV1527 边沿中断增量解码状态机 (真正的单边沿触发版本)
|
||||||
|
* @details 硬件 PxIM1/PxIM0 只有下降沿/上升沿/低电平/高电平四选一,没有"双边沿都触发"
|
||||||
|
* 的模式,之前 (IM1=1,IM0=1) 其实是"高电平中断",会在一整段高电平期间反复
|
||||||
|
* 重触发——这才是边沿计数远超协议理论值 50 的真正原因,与信号噪声无关(示波器已
|
||||||
|
* 确认信号干净)。现在改为 RF_Init() 里先配置成单一边沿(上升沿)触发,每次触发后
|
||||||
|
* 在本函数里立即软件翻转到相反边沿(乒乓切换),从而让每一次真实的电平翻转都精确
|
||||||
|
* 触发一次中断,不多不少。由于翻转前的 P3IM0 状态本身就明确告诉了我们"刚刚发生的
|
||||||
|
* 是哪种边沿",不再需要像旧版那样读取电平猜测方向,也不需要毛刺过滤器。
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
// 计时阈值 (单位: tick,0.5us/tick),与原阻塞轮询版 EV1527_Decode 的判定区间保持一致
|
||||||
|
#define RF_SYNC_HIGH_MIN_TICKS (50UL * 2UL) // 50us
|
||||||
|
#define RF_SYNC_HIGH_MAX_TICKS (2500UL * 2UL) // 2500us
|
||||||
|
#define RF_SYNC_LOW_MIN_TICKS (1500UL * 2UL) // 1500us
|
||||||
|
#define RF_SYNC_LOW_MAX_TICKS (60000UL* 2UL) // 60000us
|
||||||
|
#define RF_T_MIN_TICKS (100UL * 2UL) // 100us
|
||||||
|
#define RF_T_MAX_TICKS (1200UL * 2UL) // 1200us
|
||||||
|
|
||||||
|
#define RF_ISR_STATE_IDLE 0 // 搜索同步头候选高电平
|
||||||
|
#define RF_ISR_STATE_SYNC_SEEN 1 // 已见候选同步高电平,等待其后的低电平确认
|
||||||
|
#define RF_ISR_STATE_COLLECTING 2 // 正在逐位收集 24 位数据
|
||||||
|
|
||||||
|
#define RF_DECODE_TIMEOUT_MS 200 // 半成品帧看门狗超时 (远大于一帧最坏情况约 95ms 的总耗时)
|
||||||
|
|
||||||
|
static volatile u8 rf_isr_state = RF_ISR_STATE_IDLE;
|
||||||
|
static volatile u32 rf_isr_last_edge_ts = 0;
|
||||||
|
static volatile u32 rf_isr_pending_high = 0; // 候选同步头高电平宽度 / 收集阶段暂存的本位高电平宽度
|
||||||
|
static volatile u32 rf_isr_bit_T = 0; // 本帧的基准单位时间 T
|
||||||
|
static volatile u8 rf_isr_bit_index = 0;
|
||||||
|
static volatile u32 rf_isr_addr_acc = 0;
|
||||||
|
static volatile u8 rf_isr_dat_acc = 0;
|
||||||
|
static volatile u16 rf_isr_state_enter_ms = 0;
|
||||||
|
|
||||||
|
// 中断解码出的结果,供 EV1527_Decode() 非阻塞取用
|
||||||
|
static volatile bit rf_decoded_ready = 0;
|
||||||
|
static volatile u32 rf_decoded_addr = 0;
|
||||||
|
static volatile u8 rf_decoded_data = 0;
|
||||||
|
|
||||||
|
// 简易运行计数器:解码成功帧数 / 因校验不通过而放弃的候选帧数,供 'e' 调试指令查看
|
||||||
|
static volatile u32 dbg_frame_count = 0;
|
||||||
|
static volatile u32 dbg_abort_count = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DEBUG: 打印边沿解码状态机的简易运行计数器
|
||||||
|
*/
|
||||||
|
void RF_PrintDebugCounters(void)
|
||||||
|
{
|
||||||
|
Uart_SendString("[RF-DBG] state=");
|
||||||
|
Uart_SendHex8(rf_isr_state);
|
||||||
|
Uart_SendString(" bit_idx=");
|
||||||
|
Uart_SendHex8(rf_isr_bit_index);
|
||||||
|
Uart_SendString(" frames=");
|
||||||
|
Uart_SendHex32(dbg_frame_count);
|
||||||
|
Uart_SendString(" aborts=");
|
||||||
|
Uart_SendHex32(dbg_abort_count);
|
||||||
|
Uart_SendString("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RF_ResetDecodeState(void)
|
||||||
|
{
|
||||||
|
rf_isr_state = RF_ISR_STATE_IDLE;
|
||||||
|
rf_isr_bit_index = 0;
|
||||||
|
rf_isr_addr_acc = 0;
|
||||||
|
rf_isr_dat_acc = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理"刚刚结束的一段高电平"(即将开始新的低电平)
|
||||||
|
* @param duration 刚结束的高电平宽度 (tick)
|
||||||
|
*/
|
||||||
|
static void RF_HandleHighSegmentEnd(u32 duration)
|
||||||
|
{
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_IDLE)
|
||||||
|
{
|
||||||
|
// 空闲搜索阶段:只关心刚结束的高电平脉宽是否落在同步头候选区间
|
||||||
|
if (duration >= RF_SYNC_HIGH_MIN_TICKS && duration <= RF_SYNC_HIGH_MAX_TICKS)
|
||||||
|
{
|
||||||
|
rf_isr_pending_high = duration;
|
||||||
|
rf_isr_state = RF_ISR_STATE_SYNC_SEEN;
|
||||||
|
rf_isr_state_enter_ms = ms_tick;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_COLLECTING)
|
||||||
|
{
|
||||||
|
// 本位的高电平段先暂存,等对应的低电平段结束后一起判定这一位
|
||||||
|
rf_isr_pending_high = duration;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SYNC_SEEN 阶段理应只等待低电平段 (乒乓切换保证了 高/低 严格交替出现),
|
||||||
|
// 不会在这里收到高电平结束事件;防御性地放弃当前候选,避免状态卡死
|
||||||
|
RF_ResetDecodeState();
|
||||||
|
dbg_abort_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理"刚刚结束的一段低电平"(即将开始新的高电平)
|
||||||
|
* @param duration 刚结束的低电平宽度 (tick)
|
||||||
|
*/
|
||||||
|
static void RF_HandleLowSegmentEnd(u32 duration)
|
||||||
|
{
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_IDLE)
|
||||||
|
{
|
||||||
|
return; // 同步头候选还没确立前,空闲期低电平时长任意,不关心
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_SYNC_SEEN)
|
||||||
|
{
|
||||||
|
// 期望这段低电平是同步头的 31T 低电平
|
||||||
|
if (duration >= RF_SYNC_LOW_MIN_TICKS && duration <= RF_SYNC_LOW_MAX_TICKS &&
|
||||||
|
rf_isr_pending_high >= RF_T_MIN_TICKS && rf_isr_pending_high <= RF_T_MAX_TICKS)
|
||||||
|
{
|
||||||
|
rf_isr_bit_T = rf_isr_pending_high; // 基准单位时间 T 取自同步头高电平宽度
|
||||||
|
rf_isr_bit_index = 0;
|
||||||
|
rf_isr_addr_acc = 0;
|
||||||
|
rf_isr_dat_acc = 0;
|
||||||
|
rf_isr_state = RF_ISR_STATE_COLLECTING;
|
||||||
|
rf_isr_state_enter_ms = ms_tick;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RF_ResetDecodeState(); // 不满足同步条件,放弃候选,回到空闲搜索
|
||||||
|
dbg_abort_count++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_COLLECTING)
|
||||||
|
{
|
||||||
|
u32 total = rf_isr_pending_high + duration;
|
||||||
|
if (total < rf_isr_bit_T * 3UL || total > rf_isr_bit_T * 6UL)
|
||||||
|
{
|
||||||
|
RF_ResetDecodeState(); // 单个数据位周期超出 3T~6T 范围,判定畸变,放弃本帧
|
||||||
|
dbg_abort_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rf_isr_pending_high > duration * 2UL)
|
||||||
|
{
|
||||||
|
// 高电平明显长于低电平:逻辑 "1"
|
||||||
|
if (rf_isr_bit_index < 20) rf_isr_addr_acc = (rf_isr_addr_acc << 1) | 1;
|
||||||
|
else rf_isr_dat_acc = (rf_isr_dat_acc << 1) | 1;
|
||||||
|
}
|
||||||
|
else if (duration > rf_isr_pending_high * 2UL)
|
||||||
|
{
|
||||||
|
// 低电平明显长于高电平:逻辑 "0"
|
||||||
|
if (rf_isr_bit_index < 20) rf_isr_addr_acc = (rf_isr_addr_acc << 1);
|
||||||
|
else rf_isr_dat_acc = (rf_isr_dat_acc << 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RF_ResetDecodeState(); // 电平脉冲比值不合规,放弃本帧
|
||||||
|
dbg_abort_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rf_isr_bit_index++;
|
||||||
|
if (rf_isr_bit_index >= 24)
|
||||||
|
{
|
||||||
|
// 24 位全部收齐,输出解码结果供主循环取用
|
||||||
|
rf_decoded_addr = rf_isr_addr_acc;
|
||||||
|
rf_decoded_data = rf_isr_dat_acc;
|
||||||
|
rf_decoded_ready = 1;
|
||||||
|
dbg_frame_count++;
|
||||||
|
RF_ResetDecodeState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief P3.6 (RF_RX_DATA) 边沿中断处理 (由 main.c 的 Port3_Isr 调用)
|
||||||
|
* @details 每次触发时,P3IM0 的当前值就是"触发前配置的是哪种边沿"——因为软件每次都会
|
||||||
|
* 在处理完当前事件后立即翻转到相反边沿,所以这个值精确对应"刚刚发生的是上升沿
|
||||||
|
* 还是下降沿",不需要额外读取电平判断方向。翻转完成后再把 (方向, 距上次边沿的
|
||||||
|
* 时间差) 喂给增量解码状态机。
|
||||||
|
*/
|
||||||
|
void RF_HandleEdgeInterrupt(void)
|
||||||
|
{
|
||||||
|
u32 now;
|
||||||
|
u32 duration;
|
||||||
|
bit was_rising;
|
||||||
|
|
||||||
|
now = Timer0_GetTimestamp();
|
||||||
|
was_rising = (P3IM0 & 0x40) ? 1 : 0;
|
||||||
|
duration = now - rf_isr_last_edge_ts;
|
||||||
|
rf_isr_last_edge_ts = now;
|
||||||
|
|
||||||
|
// 乒乓切换触发边沿,为下一次真实翻转做准备 (模拟双边沿触发)
|
||||||
|
if (was_rising) {
|
||||||
|
P3IM0 &= ~0x40; // 现在已进入高电平,下一次要抓它结束 (改为下降沿触发)
|
||||||
|
} else {
|
||||||
|
P3IM0 |= 0x40; // 现在已进入低电平,下一次要抓它结束 (改为上升沿触发)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (was_rising) {
|
||||||
|
RF_HandleLowSegmentEnd(duration); // duration = 刚结束的低电平宽度
|
||||||
|
} else {
|
||||||
|
RF_HandleHighSegmentEnd(duration); // duration = 刚结束的高电平宽度
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 半成品帧看门狗:若中断状态机卡在同步头确认/数据收集阶段太久 (信号中途被干扰打断),
|
||||||
|
* 超时后强制复位回空闲搜索,防止后续真实信号被误当作"半成品帧的延续"而永远解不出来。
|
||||||
|
*/
|
||||||
|
static void RF_CheckDecodeWatchdog(void)
|
||||||
|
{
|
||||||
|
u16 elapsed;
|
||||||
|
if (rf_isr_state == RF_ISR_STATE_IDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ms_tick >= rf_isr_state_enter_ms) {
|
||||||
|
elapsed = ms_tick - rf_isr_state_enter_ms;
|
||||||
|
} else {
|
||||||
|
elapsed = 1000 + ms_tick - rf_isr_state_enter_ms;
|
||||||
|
}
|
||||||
|
if (elapsed > RF_DECODE_TIMEOUT_MS) {
|
||||||
|
RF_ResetDecodeState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 非阻塞地检测并取出一个已解码完成的 EV1527 射频信号数据帧
|
||||||
|
* @details 真正的解码工作已经在 P3.6 边沿中断 (RF_HandleEdgeInterrupt) 中增量完成;
|
||||||
|
* 本函数只是原子地检查并取出中断解码结果,可在主循环中高频调用而不会造成阻塞。
|
||||||
|
*/
|
||||||
|
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
||||||
|
{
|
||||||
|
bit ready;
|
||||||
|
|
||||||
|
RF_CheckDecodeWatchdog();
|
||||||
|
|
||||||
|
EA = 0; // 与中断互斥,保证 32 位地址与结果标志的原子性读取
|
||||||
|
ready = rf_decoded_ready;
|
||||||
|
if (ready) {
|
||||||
|
*out_addr = rf_decoded_addr;
|
||||||
|
*out_data = rf_decoded_data;
|
||||||
|
rf_decoded_ready = 0;
|
||||||
|
}
|
||||||
|
EA = 1;
|
||||||
|
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按照 EV1527 协议单次调制发送一帧 24 位射频数据 (Sync + 24bit Data)
|
||||||
|
*/
|
||||||
void EV1527_TxFrame(u32 addr, u8 dat)
|
void EV1527_TxFrame(u32 addr, u8 dat)
|
||||||
{
|
{
|
||||||
u8 i;
|
u8 i;
|
||||||
|
u32 tx_val;
|
||||||
|
|
||||||
// 1. 同步脉冲: 1T 高电平 + 31T 低电平
|
// 合并 20 位地址与 4 位数据,成为 24 位完整 EV1527 帧
|
||||||
RF_TX_DAT = 1;
|
tx_val = (addr << 4) | (dat & 0x0F);
|
||||||
Delay_us(320);
|
|
||||||
RF_TX_DAT = 0;
|
|
||||||
Delay_us(9920);
|
|
||||||
|
|
||||||
// 2. 20位地址码 (MSB first)
|
// 1. 同步头脉冲: 高电平 1T (350us) + 低电平 31T (10850us)
|
||||||
for (i = 0; i < 20; i++)
|
|
||||||
{
|
|
||||||
if ((addr >> (19 - i)) & 1)
|
|
||||||
{
|
|
||||||
// 逻辑 1: 3T 高电平 + 1T 低电平
|
|
||||||
RF_TX_DAT = 1;
|
RF_TX_DAT = 1;
|
||||||
Delay_us(960);
|
RF_Delay_us(350);
|
||||||
RF_TX_DAT = 0;
|
RF_TX_DAT = 0;
|
||||||
Delay_us(320);
|
RF_Delay_us(10850);
|
||||||
|
|
||||||
|
// 2. 依次发送 24 个数据位 (MSB First)
|
||||||
|
for (i = 0; i < 24; i++)
|
||||||
|
{
|
||||||
|
if (tx_val & (0x800000UL >> i))
|
||||||
|
{
|
||||||
|
// 逻辑 "1": 高电平 3T (1050us) + 低电平 1T (350us)
|
||||||
|
RF_TX_DAT = 1;
|
||||||
|
RF_Delay_us(1050);
|
||||||
|
RF_TX_DAT = 0;
|
||||||
|
RF_Delay_us(350);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 逻辑 0: 1T 高电平 + 3T 低电平
|
// 逻辑 "0": 高电平 1T (350us) + 低电平 3T (1050us)
|
||||||
RF_TX_DAT = 1;
|
RF_TX_DAT = 1;
|
||||||
Delay_us(320);
|
RF_Delay_us(350);
|
||||||
RF_TX_DAT = 0;
|
RF_TX_DAT = 0;
|
||||||
Delay_us(960);
|
RF_Delay_us(1050);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 4位数据码 (MSB first)
|
|
||||||
for (i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
if ((dat >> (3 - i)) & 1)
|
|
||||||
{
|
|
||||||
// 逻辑 1: 3T 高电平 + 1T 低电平
|
|
||||||
RF_TX_DAT = 1;
|
|
||||||
Delay_us(960);
|
|
||||||
RF_TX_DAT = 0;
|
|
||||||
Delay_us(320);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 逻辑 0: 1T 高电平 + 3T 低电平
|
|
||||||
RF_TX_DAT = 1;
|
|
||||||
Delay_us(320);
|
|
||||||
RF_TX_DAT = 0;
|
|
||||||
Delay_us(960);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调制发射 25 帧 EV1527 射频同步电平信号以确保接收侧可靠触发
|
||||||
|
*/
|
||||||
void EV1527_Transmit(u32 addr, u8 dat)
|
void EV1527_Transmit(u32 addr, u8 dat)
|
||||||
{
|
{
|
||||||
u8 r;
|
u8 r;
|
||||||
for (r = 0; r < 25; r++) // 增加到 25 帧(约 340ms)
|
bit ea_bak = EA; // 备份并屏蔽全局中断,确保发射高低电平周期的绝对纯净与防抖
|
||||||
|
EA = 0;
|
||||||
|
|
||||||
|
RF_SetMode(2); // 切换为发射状态,接通天线与射频模块通道
|
||||||
|
Delay_ms(5); // 稍作延时使射频通路稳定
|
||||||
|
|
||||||
|
// 连续发射 25 帧以保证接收设备顺利锁定并解码
|
||||||
|
for (r = 0; r < 25; r++)
|
||||||
{
|
{
|
||||||
EV1527_TxFrame(addr, dat);
|
EV1527_TxFrame(addr, dat);
|
||||||
|
// 去除帧与帧之间可能导致波形不连续的 Delay_ms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RF_TX_DAT = 0; // 结束发射,拉低数据脚
|
||||||
|
RF_SetMode(1); // 恢复为常态后台接收模式
|
||||||
|
|
||||||
|
EA = ea_bak; // 还原全局中断开关状态
|
||||||
}
|
}
|
||||||
|
|
||||||
u16 GetPulseDuration(u8 state, u16 timeout_us)
|
/**
|
||||||
|
* @brief RF 收发诊断测试功能 (支持发送与接收监听打印)
|
||||||
|
*/
|
||||||
|
void RF_DiagnosticMode(char choice)
|
||||||
{
|
{
|
||||||
u16 timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL);
|
if (choice == 't')
|
||||||
TL0 = 0;
|
|
||||||
TH0 = 0;
|
|
||||||
TR0 = 1; // 启动定时器0
|
|
||||||
while (RF_RX_DATA == state)
|
|
||||||
{
|
{
|
||||||
if (GetTimer0_Safe() > timeout_ticks)
|
|
||||||
{
|
|
||||||
TR0 = 0;
|
|
||||||
return 0; // 超时
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TR0 = 0;
|
|
||||||
return (u16)((u32)GetTimer0_Safe() * 12UL / (MAIN_Fosc / 1000000UL)); // 返回微秒数
|
|
||||||
}
|
|
||||||
|
|
||||||
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
|
|
||||||
{
|
|
||||||
u16 high_time, low_time;
|
|
||||||
u8 i;
|
u8 i;
|
||||||
u32 addr = 0;
|
Uart_SendString("=== RF TX Test: Transmitting 20 test frames... ===\r\n");
|
||||||
u8 dat = 0;
|
// 开启发射模式并配置天线开关
|
||||||
u16 T;
|
RF_SetMode(2);
|
||||||
u16 idata raw_h[24];
|
Delay_ms(5);
|
||||||
u16 idata raw_l[24];
|
|
||||||
|
|
||||||
// 1. 同步头捕获: 必须是低电平状态之后变高电平开始
|
for (i = 0; i < 20; i++)
|
||||||
if (RF_RX_DATA == 0)
|
|
||||||
{
|
{
|
||||||
u16 wait_cnt = 0;
|
// 连续发送测试包,每次发送伴随马达短震及指示灯闪烁
|
||||||
while (RF_RX_DATA == 0)
|
MOTOR = 1;
|
||||||
|
RGB_Send(0, 150, 0, 0, 150, 0); // 绿光闪烁
|
||||||
|
EV1527_TxFrame(0x37A86UL, 0x01);
|
||||||
|
MOTOR = 0;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
Delay_ms(150); // 帧间隔
|
||||||
|
}
|
||||||
|
RF_TX_DAT = 0;
|
||||||
|
RF_SetMode(1); // 重新恢复接收模式
|
||||||
|
Uart_SendString("[RF] Transmit Done!\r\n");
|
||||||
|
}
|
||||||
|
else if (choice == 'r')
|
||||||
{
|
{
|
||||||
Delay_us(10);
|
u16 wait_ms;
|
||||||
wait_cnt++;
|
u32 rx_addr;
|
||||||
if (wait_cnt > 3000) // 30ms 超时
|
u8 rx_data;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测量同步头高电平时间 (放宽至 2500us)
|
Uart_SendString("=== RF RX Test: Listening for 5 seconds... ===\r\n");
|
||||||
high_time = GetPulseDuration(1, 2500);
|
RF_SetMode(1); // 配置进入接收模式并使能天线
|
||||||
if (high_time < 50 || high_time > 2500)
|
Delay_ms(10);
|
||||||
return 0;
|
|
||||||
|
|
||||||
// 测量同步头低电平时间 (放宽至 60000us)
|
// 5秒的非阻塞监听
|
||||||
low_time = GetPulseDuration(0, 60000);
|
for (wait_ms = 0; wait_ms < 5000; wait_ms++)
|
||||||
if (low_time < 1500 || low_time > 60000)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// 计算高低电平时间之比。对于标准 EV1527,低电平是高电平的 31 倍。
|
|
||||||
// 放宽比例范围为 15 到 48,以应对各种不同的遥控器电阻。
|
|
||||||
{
|
{
|
||||||
u32 ratio = (u32)low_time / high_time;
|
if (EV1527_Decode(&rx_addr, &rx_data))
|
||||||
if (ratio < 15 || ratio > 48)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 估算基准脉冲周期 T = low_time / 31
|
|
||||||
T = low_time / 31;
|
|
||||||
if (T == 0) T = 1;
|
|
||||||
|
|
||||||
// 2. 连续抓取 24 个数据脉冲
|
|
||||||
for (i = 0; i < 24; i++)
|
|
||||||
{
|
{
|
||||||
u16 wait_cnt = 0;
|
Uart_SendString("[RF] Decoded successfully! Addr: 0x");
|
||||||
// 等待下一个数据脉冲的上升沿 (如果当前是低电平,等待它变高)
|
Uart_SendHex32(rx_addr);
|
||||||
while (RF_RX_DATA == 0)
|
Uart_SendString(", Cmd/Data: ");
|
||||||
{
|
Uart_SendHex8(rx_data);
|
||||||
Delay_us(5);
|
|
||||||
wait_cnt++;
|
|
||||||
if (wait_cnt > 1000) // 5ms 超时
|
|
||||||
{
|
|
||||||
return 0; // 解码失败
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测量高电平持续时间和低电平持续时间
|
|
||||||
raw_h[i] = GetPulseDuration(1, 5000);
|
|
||||||
if (raw_h[i] == 0) return 0;
|
|
||||||
|
|
||||||
raw_l[i] = GetPulseDuration(0, 5000);
|
|
||||||
if (raw_l[i] == 0) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 将抓到的全部 24 位脉冲数据无条件输出到串口 (缩短输出信息以防止阻塞接收)
|
|
||||||
Uart_SendString("Captured: Sync H=");
|
|
||||||
Uart_SendHex16(high_time);
|
|
||||||
Uart_SendString("us, L=");
|
|
||||||
Uart_SendHex16(low_time);
|
|
||||||
Uart_SendString("us, T=");
|
|
||||||
Uart_SendHex16(T);
|
|
||||||
Uart_SendString("us\r\n");
|
|
||||||
|
|
||||||
// 4. 组合成 24 位地址与数据,使用自适应阈值:如果高电平时间比低电平时间长,判定为 1,反之为 0
|
|
||||||
for (i = 0; i < 24; i++)
|
|
||||||
{
|
|
||||||
if (raw_h[i] > raw_l[i])
|
|
||||||
{
|
|
||||||
if (i < 20) addr = (addr << 1) | 1;
|
|
||||||
else dat = (dat << 1) | 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (i < 20) addr = (addr << 1);
|
|
||||||
else dat = (dat << 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_addr = addr;
|
|
||||||
*out_data = dat;
|
|
||||||
|
|
||||||
// 串口打印解码结果方便现场调试
|
|
||||||
Uart_SendString("Decoded ADDR: 0x");
|
|
||||||
Uart_SendHex20(addr);
|
|
||||||
Uart_SendString(", DATA: 0x");
|
|
||||||
Uart_SendHex4(dat);
|
|
||||||
Uart_SendString("\r\n");
|
Uart_SendString("\r\n");
|
||||||
|
|
||||||
return 1;
|
// 收到信号后马达震动并亮青色灯提醒
|
||||||
|
MOTOR = 1;
|
||||||
|
RGB_Send(0, 150, 150, 0, 150, 150);
|
||||||
|
Delay_ms(100);
|
||||||
|
MOTOR = 0;
|
||||||
|
RGB_Send(0, 0, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
Delay_ms(1);
|
||||||
|
}
|
||||||
|
Uart_SendString("=== RF RX Test End ===\r\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 电气回环自检测试 (物理短路 P2.1 与 P3.6 自检)
|
||||||
|
* @details 用户通过短路 P2.1 和 P3.6 来实现 GPIO 的物理连通性自检
|
||||||
|
*/
|
||||||
void Loopback_Test(void)
|
void Loopback_Test(void)
|
||||||
{
|
{
|
||||||
u16 match_count = 0;
|
u16 match_count = 0;
|
||||||
u16 i;
|
u16 i;
|
||||||
|
|
||||||
SHUT = 0; // 使能接收芯片
|
EAXFR = 1;
|
||||||
RF_RX = 1; // 选通天线到接收端
|
RF_SetMode(1); // 开启接收芯片
|
||||||
RF_TX = 0; // 发送侧断开
|
|
||||||
Delay_ms(100);
|
Delay_ms(100);
|
||||||
|
|
||||||
Uart_SendString("Starting RF Loopback Self-Test...\r\n");
|
Uart_SendString("Starting RF Hardware Loopback Test (P2.1 -> P3.6)...\r\n");
|
||||||
|
Uart_SendString("Please use a metal tweezers to short-circuit P2.1 (Pin 22) and P3.6 (Pin 19)!\r\n");
|
||||||
|
|
||||||
for (i = 0; i < 100; i++)
|
for (i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
@@ -276,13 +517,12 @@ void Loopback_Test(void)
|
|||||||
|
|
||||||
if (match_count > 150)
|
if (match_count > 150)
|
||||||
{
|
{
|
||||||
Uart_SendString("Result: PASS! Transmitter is emitting RF, and Receiver is picking it up!\r\n");
|
Uart_SendString("Result: PASS! Pin P2.1 and P3.6 are electrical connected successfully!\r\n");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Uart_SendString("Result: FAIL! No RF signal detected. Check hardware.\r\n");
|
Uart_SendString("Result: FAIL! GPIO coupling check failed. Short-circuit the pins and test again.\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
SHUT = 1;
|
RF_SetMode(0); // 关断射频
|
||||||
RF_RX = 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
68
Drivers/rf.h
68
Drivers/rf.h
@@ -4,13 +4,73 @@
|
|||||||
#include "../App/config.h"
|
#include "../App/config.h"
|
||||||
#include "stc32g.h"
|
#include "stc32g.h"
|
||||||
|
|
||||||
// Public RF functions
|
/* ====== LR690L / EV1527 无线射频编解码驱动接口 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化无线射频芯片相关的 GPIO 引脚
|
||||||
|
* @details 配置 RF_TX_DAT (P3.6) 为推挽输出,配置 RF_TX (P2.0)、RF_RX (P3.7)、SHUT (P2.2) 等引脚,
|
||||||
|
* 使能 ATR5179 射频前端开关,为无线射频收发建立硬件基础。
|
||||||
|
*/
|
||||||
void RF_Init(void);
|
void RF_Init(void);
|
||||||
void RF_SetMode(u8 mode); // mode: 0=Sleep, 1=Rx, 2=Tx
|
|
||||||
|
/**
|
||||||
|
* @brief 配置无线射频收发系统的运行模式
|
||||||
|
* @param mode 0-休眠模式 (Sleep),1-接收模式 (Rx),2-发射模式 (Tx)
|
||||||
|
* @details 负责联合调度 SHUT 控制引脚、天线切换使能引脚以及 ATR5179 射频开关:
|
||||||
|
* - 模式 0 (Sleep): 拉高 SHUT 以关断 LR690L 芯片电源,实现零电流待机。
|
||||||
|
* - 模式 1 (Rx): 拉低 SHUT 启动 LR690L 芯片,接通天线并配置前端为接收放大状态。
|
||||||
|
* - 模式 2 (Tx): 切换天线前端开关为发射直通状态,准备调制发送。
|
||||||
|
*/
|
||||||
|
void RF_SetMode(u8 mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 按照 EV1527 协议单次调制发送一帧 24 位射频数据
|
||||||
|
* @param addr 24 位发射源地址
|
||||||
|
* @param dat 4 位命令状态数据码
|
||||||
|
* @details 会生成由 1 个同步码 (Sync Code) 和 24 个数据编码(由 addr + dat 组成)组成的完整帧脉冲。
|
||||||
|
*/
|
||||||
void EV1527_TxFrame(u32 addr, u8 dat);
|
void EV1527_TxFrame(u32 addr, u8 dat);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 连续多次发送 EV1527 射频帧,防止单帧丢失
|
||||||
|
* @param addr 24 位源地址
|
||||||
|
* @param dat 4 位命令状态码
|
||||||
|
* @details 连续发送多次以保证无线通信在嘈杂噪声环境下的接收可靠性。
|
||||||
|
*/
|
||||||
void EV1527_Transmit(u32 addr, u8 dat);
|
void EV1527_Transmit(u32 addr, u8 dat);
|
||||||
u16 GetPulseDuration(u8 state, u16 timeout_us);
|
|
||||||
|
/**
|
||||||
|
* @brief P3.6 (RF_RX_DATA) 单边沿触发中断处理,由 main.c 的 Port3_Isr 在每次电平跳变时调用
|
||||||
|
* @details 硬件 PxIM1/PxIM0 只支持下降沿/上升沿/低电平/高电平四选一,没有真正的"双边沿"模式;
|
||||||
|
* 本函数在每次触发后都会立即把 P3IM0 翻转到相反边沿(乒乓切换),模拟出双边沿触发的效果,
|
||||||
|
* 并据此驱动内部的三态增量解码状态机 (空闲搜索 -> 同步头确认 -> 逐位收集),凑齐 24 位后
|
||||||
|
* 把结果写入内部标志供 EV1527_Decode() 非阻塞取用。全程不做任何阻塞等待。
|
||||||
|
*/
|
||||||
|
void RF_HandleEdgeInterrupt(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 非阻塞地检查一次 EV1527 射频信号是否已解码完成
|
||||||
|
* @param out_addr 输出参数指针:若解码成功,将写入解析得到的 20 位发送地址 (即 24 位帧除去 4 位数据位后的剩余高 20 位)
|
||||||
|
* @param out_data 输出参数指针:若解码成功,将写入解析出的 4 位命令状态数据码 (例如报警/SOS等)
|
||||||
|
* @return bit 1-本次调用取到了一帧新解码出的有效数据;0-当前尚无新结果
|
||||||
|
* @note 真正的解码工作已经在 P3.6 边沿中断 (RF_HandleEdgeInterrupt) 中增量完成,
|
||||||
|
* 本函数只是原子地检查并取出中断解码结果,可在主循环中高频调用而不会造成任何阻塞。
|
||||||
|
*/
|
||||||
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
|
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RF 收发诊断测试功能 (支持发送与接收监听打印)
|
||||||
|
*/
|
||||||
|
void RF_DiagnosticMode(char choice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 电气回环自检测试 (物理短路 P2.1 与 P3.6 自检)
|
||||||
|
*/
|
||||||
void Loopback_Test(void);
|
void Loopback_Test(void);
|
||||||
|
|
||||||
#endif
|
/**
|
||||||
|
* @brief DEBUG: 打印边沿解码状态机的简易运行计数器 (当前状态/位索引/成功帧数/放弃次数)
|
||||||
|
*/
|
||||||
|
void RF_PrintDebugCounters(void);
|
||||||
|
|
||||||
|
#endif // __RF_H__
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
#ifndef __STC32G_H__
|
#ifndef __STC32G_H__
|
||||||
#define __STC32G_H__
|
#define __STC32G_H__
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
* @file stc32g.h
|
||||||
|
* @brief STC32G 系列单片机官方特殊功能寄存器 (SFR) 定义头文件
|
||||||
|
* @details 本文件包含了 STC32G12K128 单片机内核与所有外设寄存器地址的宏定义。
|
||||||
|
* 为保持与官方标准库及编译器的最大兼容性,本文件不对寄存器宏进行逐行改动,
|
||||||
|
* 仅在此处添加中文引导说明。
|
||||||
|
**************************************************************************/
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
|
|
||||||
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͷ<EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>,<2C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٰ<EFBFBD><D9B0><EFBFBD>"REG51.H"
|
//包含本头文件后,不用另外再包含"REG51.H"
|
||||||
|
|
||||||
#include "stdio.h"
|
#include "stdio.h"
|
||||||
#include "intrins.h"
|
#include "intrins.h"
|
||||||
#include "string.h"
|
#include "string.h"
|
||||||
|
|
||||||
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>LCALL 0x0000<EFBFBD>쳣
|
//修正编译器的LCALL 0x0000异常
|
||||||
extern void _m(void);
|
extern void _m(void);
|
||||||
#define main(x) main(x){_m();sprintf(0, "0");strlen("0");}; void _m(x)
|
#define main(x) main(x){_m();sprintf(0, "0");strlen("0");}; void _m(x)
|
||||||
|
|
||||||
@@ -529,10 +537,10 @@ sfr RSTCFG = 0xff;
|
|||||||
sbit ENLVR = RSTCFG^6;
|
sbit ENLVR = RSTCFG^6;
|
||||||
sbit P54RST = RSTCFG^4;
|
sbit P54RST = RSTCFG^4;
|
||||||
|
|
||||||
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܼĴ<EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>չRAM<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
//如下特殊功能寄存器位于扩展RAM区域
|
||||||
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Щ<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>,<2C><><EFBFBD>Ƚ<EFBFBD>EAXFR<46><52><EFBFBD><EFBFBD>Ϊ1,<2C>ſ<EFBFBD><C5BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д
|
//访问这些寄存器,需先将EAXFR设置为1,才可正常读写
|
||||||
// EAXFR = 1;
|
// EAXFR = 1;
|
||||||
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
//或者
|
||||||
// P_SW2 |= 0x80;
|
// P_SW2 |= 0x80;
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
@@ -949,14 +957,14 @@ typedef struct TAG_PWM_STRUCT
|
|||||||
#define HSSPI_CFG2 (*(unsigned char volatile far *)0x7efbf9)
|
#define HSSPI_CFG2 (*(unsigned char volatile far *)0x7efbf9)
|
||||||
#define HSSPI_STA (*(unsigned char volatile far *)0x7efbfa)
|
#define HSSPI_STA (*(unsigned char volatile far *)0x7efbfa)
|
||||||
|
|
||||||
//ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĺ<EFBFBD>,<2C><><EFBFBD>Ƚ<EFBFBD>EAXFR<46><52><EFBFBD><EFBFBD>Ϊ1
|
//使用下面的宏,需先将EAXFR设置为1
|
||||||
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>:
|
//使用方法:
|
||||||
// char val;
|
// char val;
|
||||||
//
|
//
|
||||||
// EAXFR = 1; //ʹ<EFBFBD>ܷ<EFBFBD><EFBFBD><EFBFBD>XFR
|
// EAXFR = 1; //使能访问XFR
|
||||||
// READ_HSPWMA(PWMA_CR1, val); //<EFBFBD>첽<EFBFBD><EFBFBD>PWMA<EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// READ_HSPWMA(PWMA_CR1, val); //异步读PWMA组寄存器
|
||||||
// val |= 0x01;
|
// val |= 0x01;
|
||||||
// WRITE_HSPWMA(PWMA_CR1, val); //<EFBFBD>첽дPWMA<EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// WRITE_HSPWMA(PWMA_CR1, val); //异步写PWMA组寄存器
|
||||||
|
|
||||||
#define READ_HSPWMA(reg, dat) \
|
#define READ_HSPWMA(reg, dat) \
|
||||||
{ \
|
{ \
|
||||||
@@ -1168,13 +1176,13 @@ typedef struct TAG_PWM_STRUCT
|
|||||||
//#define CANAR (*(unsigned char volatile far *)0x7efebb)
|
//#define CANAR (*(unsigned char volatile far *)0x7efebb)
|
||||||
//#define CANDR (*(unsigned char volatile far *)0x7efebc)
|
//#define CANDR (*(unsigned char volatile far *)0x7efebc)
|
||||||
|
|
||||||
//ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĺ<EFBFBD>,<2C><><EFBFBD>Ƚ<EFBFBD>EAXFR<46><52><EFBFBD><EFBFBD>Ϊ1
|
//使用下面的宏,需先将EAXFR设置为1
|
||||||
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>:
|
//使用方法:
|
||||||
// char dat;
|
// char dat;
|
||||||
//
|
//
|
||||||
// EAXFR = 1; //ʹ<EFBFBD>ܷ<EFBFBD><EFBFBD><EFBFBD>XFR
|
// EAXFR = 1; //使能访问XFR
|
||||||
// dat = READ_CAN(RX_BUF0); //<EFBFBD><EFBFBD>CAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// dat = READ_CAN(RX_BUF0); //读CAN寄存器
|
||||||
// WRITE_CAN(TX_BUF0, 0x55); //дCAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// WRITE_CAN(TX_BUF0, 0x55); //写CAN寄存器
|
||||||
|
|
||||||
#define READ_CAN(reg) (CANAR = (reg), CANDR)
|
#define READ_CAN(reg) (CANAR = (reg), CANDR)
|
||||||
#define WRITE_CAN(reg, dat) (CANAR = (reg), CANDR = (dat))
|
#define WRITE_CAN(reg, dat) (CANAR = (reg), CANDR = (dat))
|
||||||
@@ -1218,11 +1226,11 @@ typedef struct TAG_PWM_STRUCT
|
|||||||
//sfr LINAR = 0xfa;
|
//sfr LINAR = 0xfa;
|
||||||
//sfr LINDR = 0xfb;
|
//sfr LINDR = 0xfb;
|
||||||
|
|
||||||
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>:
|
//使用方法:
|
||||||
// char dat;
|
// char dat;
|
||||||
//
|
//
|
||||||
// dat = READ_LIN(LBUF); //<EFBFBD><EFBFBD>CAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// dat = READ_LIN(LBUF); //读CAN寄存器
|
||||||
// WRITE_LIN(LBUF, 0x55); //дCAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// WRITE_LIN(LBUF, 0x55); //写CAN寄存器
|
||||||
|
|
||||||
#define READ_LIN(reg) (LINAR = (reg), LINDR)
|
#define READ_LIN(reg) (LINAR = (reg), LINDR)
|
||||||
#define WRITE_LIN(reg, dat) (LINAR = (reg), LINDR = (dat))
|
#define WRITE_LIN(reg, dat) (LINAR = (reg), LINDR = (dat))
|
||||||
@@ -1249,11 +1257,11 @@ typedef struct TAG_PWM_STRUCT
|
|||||||
//sfr USBCON = 0xf4;
|
//sfr USBCON = 0xf4;
|
||||||
//sfr USBADR = 0xfc;
|
//sfr USBADR = 0xfc;
|
||||||
|
|
||||||
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>:
|
//使用方法:
|
||||||
// char dat;
|
// char dat;
|
||||||
//
|
//
|
||||||
// READ_USB(CSR0, dat); //<EFBFBD><EFBFBD>USB<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// READ_USB(CSR0, dat); //读USB寄存器
|
||||||
// WRITE_USB(FADDR, 0x00); //дUSB<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
// WRITE_USB(FADDR, 0x00); //写USB寄存器
|
||||||
|
|
||||||
#define READ_USB(reg, dat) \
|
#define READ_USB(reg, dat) \
|
||||||
{ \
|
{ \
|
||||||
@@ -1435,8 +1443,8 @@ typedef struct TAG_PWM_STRUCT
|
|||||||
#define DMA_I2SR_VECTOR 64 //0203H
|
#define DMA_I2SR_VECTOR 64 //0203H
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
#define EAXSFR() EAXFR = 1 /* MOVX A,@DPTR/MOVX @DPTR,Aָ<EFBFBD><EFBFBD><EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>չSFR(XSFR) */
|
#define EAXSFR() EAXFR = 1 /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
|
||||||
#define EAXRAM() EAXFR = 0 /* MOVX A,@DPTR/MOVX @DPTR,Aָ<EFBFBD><EFBFBD><EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>չRAM(XRAM) */
|
#define EAXRAM() EAXFR = 0 /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
|
||||||
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////
|
/////////////////////////////////////////////////
|
||||||
|
|||||||
111
Drivers/timer.c
Normal file
111
Drivers/timer.c
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#include "timer.h"
|
||||||
|
#include "../App/clock.h"
|
||||||
|
|
||||||
|
// 外部引用的全局滴答/标志变量 (在 main.c 中定义)
|
||||||
|
extern volatile u16 ms_tick;
|
||||||
|
extern volatile bit alarm_flash_flag;
|
||||||
|
extern volatile u16 key_scan_timer;
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* Timer0:自由运行硬件时间戳基准
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
// Timer0 溢出次数,扩展 16 位硬件计数为 32 位软件时间戳的高位部分
|
||||||
|
static volatile u16 timer0_ovf_count = 0;
|
||||||
|
|
||||||
|
void Timer0_Init(void)
|
||||||
|
{
|
||||||
|
TMOD &= 0xF0; // Timer0 模式位清零 -> Mode 0 (STC 上为 16 位不停表计数)
|
||||||
|
TL0 = 0;
|
||||||
|
TH0 = 0;
|
||||||
|
ET0 = 1; // 开启 Timer0 溢出中断允许
|
||||||
|
TR0 = 1; // 启动 Timer0 计数,此后永不停止 (全局 EA 由 Timer1_Init() 统一开启)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Timer0 溢出中断服务程序 (每约 32.768ms 触发一次)
|
||||||
|
* @details 只做计数扩展,不做任何耗时操作,保持中断尽量短。
|
||||||
|
*/
|
||||||
|
void Timer0_Isr(void) interrupt 1
|
||||||
|
{
|
||||||
|
timer0_ovf_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 Timer0_GetTimestamp(void)
|
||||||
|
{
|
||||||
|
u16 ovf1, ovf2;
|
||||||
|
u8 h1, h2, l;
|
||||||
|
bit tf, ea_bak;
|
||||||
|
|
||||||
|
do {
|
||||||
|
ovf1 = timer0_ovf_count;
|
||||||
|
h1 = TH0;
|
||||||
|
l = TL0;
|
||||||
|
h2 = TH0;
|
||||||
|
ovf2 = timer0_ovf_count;
|
||||||
|
} while (h1 != h2 || ovf1 != ovf2);
|
||||||
|
|
||||||
|
// 上面的撕裂读保护只处理了 TH0/TL0 两字节配对读取时的撕裂,但还有另一种更隐蔽的竞态:
|
||||||
|
// 硬件计数器已经物理溢出归零 (TH0/TL0 已经是溢出后的小数值),但 Timer0_Isr 因为中断延迟
|
||||||
|
// 还没来得及把 timer0_ovf_count 加一,导致算出来的时间戳比真实值凭空小了一整个溢出周期
|
||||||
|
// (65536 tick ≈ 32.768ms),看起来像是"时间倒流"。这里读一次硬件溢出标志位 TF0 做补偿:
|
||||||
|
// 若 TF0 已经置位但还未被服务,本次读数临时按 ovf+1 计算,不清 TF0,留给真正的
|
||||||
|
// Timer0_Isr 之后正常执行、更新持久计数,不影响后续调用。
|
||||||
|
ea_bak = EA;
|
||||||
|
EA = 0;
|
||||||
|
tf = TF0;
|
||||||
|
EA = ea_bak;
|
||||||
|
if (tf) {
|
||||||
|
ovf1++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((u32)ovf1 << 16) | ((u16)h1 << 8) | l;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
* Timer1:系统 1ms 基准滴答
|
||||||
|
* ========================================================================= */
|
||||||
|
|
||||||
|
// 主循环按键扫描定时标志 (由 1ms 中断置 1,主循环响应清除),定义详见 event.h 的 extern 声明
|
||||||
|
volatile bit key_scan_flag = 0;
|
||||||
|
|
||||||
|
void Timer1_Init(void)
|
||||||
|
{
|
||||||
|
AUXR |= 0x40; // 设定定时器 1 为 1T 模式 (无分频,主频直接输入)
|
||||||
|
TMOD &= 0x0F; // 设置定时器 1 模式寄存器,保留低四位 (定时器0配置)
|
||||||
|
{
|
||||||
|
// 按照系统主频 (MAIN_Fosc) 计算 1ms 对应的重装载值
|
||||||
|
u16 reload = (u16)(65536UL - (MAIN_Fosc / 1000UL));
|
||||||
|
TL1 = (u8)reload; // 载入低 8 位
|
||||||
|
TH1 = (u8)(reload >> 8);// 载入高 8 位
|
||||||
|
}
|
||||||
|
ET1 = 1; // 开启定时器 1 中断允许
|
||||||
|
TR1 = 1; // 启动定时器 1 计数
|
||||||
|
EA = 1; // 开启全局中断允许
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
|
||||||
|
* @details 承担系统软时钟计算,并每 10ms 置位主循环按键扫描标志,不直接在中断中进行 ADC 多次采样。
|
||||||
|
*/
|
||||||
|
void Timer1_Isr(void) interrupt 3
|
||||||
|
{
|
||||||
|
// 1ms 递增全局滴答计数器 (0~999 循环),供各 App 的呼吸灯/闪烁/超时等周期性效果使用
|
||||||
|
ms_tick++;
|
||||||
|
if (ms_tick >= 1000) {
|
||||||
|
ms_tick = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1ms 定时更新软时钟
|
||||||
|
Clock_IncMS();
|
||||||
|
|
||||||
|
// 报警屏幕红边框以 100ms 周期亮灭,进行颜色爆闪指示
|
||||||
|
alarm_flash_flag = (ms_tick / 100) % 2;
|
||||||
|
|
||||||
|
/* 物理按键定时计数 (每 10ms 置位一次标志位) */
|
||||||
|
key_scan_timer++;
|
||||||
|
if (key_scan_timer >= 10) {
|
||||||
|
key_scan_timer = 0; // 重置扫描分频计数器
|
||||||
|
key_scan_flag = 1; // 仅在中断里置起标志位,留待主循环中执行实际读取与消抖
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Drivers/timer.h
Normal file
31
Drivers/timer.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef __TIMER_H__
|
||||||
|
#define __TIMER_H__
|
||||||
|
|
||||||
|
#include "../App/config.h"
|
||||||
|
|
||||||
|
/* ====== Timer0:自由运行硬件时间戳基准 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 Timer0 为常驻自由运行计数器
|
||||||
|
* @details 配置为 Mode 0 (STC 上为 16 位不停表计数),12T 模式下 24MHz 晶振对应 1 tick = 0.5us。
|
||||||
|
* 开机启动后永不停止,配合溢出中断 (Timer0_Isr) 把 16 位硬件计数扩展为 32 位软件时间戳,
|
||||||
|
* 供 RF 边沿中断解码等需要高精度时间差测量的场景使用。
|
||||||
|
*/
|
||||||
|
void Timer0_Init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取以 Timer0 为基准的 32 位自由运行时间戳 (单位: 0.5us tick)
|
||||||
|
* @details 内部处理了读取瞬间恰逢硬件计数器溢出的读数撕裂问题。
|
||||||
|
*/
|
||||||
|
u32 Timer0_GetTimestamp(void);
|
||||||
|
|
||||||
|
/* ====== Timer1:系统 1ms 基准滴答 ====== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 Timer1 定时器,生成系统基准时钟 (1ms 中断)
|
||||||
|
* @details 配置定时器 1 为 16 位自动重装载模式,设定中断周期为 1.0 毫秒,
|
||||||
|
* 开启 Timer1 中断并启动计数,同时在此统一开启全局中断允许 (EA = 1)。
|
||||||
|
*/
|
||||||
|
void Timer1_Init(void);
|
||||||
|
|
||||||
|
#endif // __TIMER_H__
|
||||||
64361
Hardware/text.kicad_pcb
64361
Hardware/text.kicad_pcb
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"board": {
|
"board": {
|
||||||
"active_layer": 0,
|
"active_layer": 2,
|
||||||
"active_layer_preset": "",
|
"active_layer_preset": "Back Layers",
|
||||||
"auto_track_width": true,
|
"auto_track_width": true,
|
||||||
"hidden_netclasses": [],
|
"hidden_netclasses": [],
|
||||||
"hidden_nets": [
|
"hidden_nets": [
|
||||||
"GND"
|
"GND"
|
||||||
],
|
],
|
||||||
"high_contrast_mode": 1,
|
"high_contrast_mode": 0,
|
||||||
"net_color_mode": 1,
|
"net_color_mode": 1,
|
||||||
"opacity": {
|
"opacity": {
|
||||||
"images": 0.6,
|
"images": 0.6,
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
},
|
},
|
||||||
"visible_items": [
|
"visible_items": [
|
||||||
"vias",
|
"vias",
|
||||||
"footprint_text",
|
|
||||||
"footprint_anchors",
|
"footprint_anchors",
|
||||||
"ratsnest",
|
"ratsnest",
|
||||||
"grid",
|
"grid",
|
||||||
|
|||||||
@@ -39,9 +39,9 @@
|
|||||||
"other_text_thickness": 0.15,
|
"other_text_thickness": 0.15,
|
||||||
"other_text_upright": false,
|
"other_text_upright": false,
|
||||||
"pads": {
|
"pads": {
|
||||||
"drill": 0.0,
|
"drill": 1.1,
|
||||||
"height": 0.310008,
|
"height": 2.0,
|
||||||
"width": 0.529997
|
"width": 2.0
|
||||||
},
|
},
|
||||||
"silk_line_width": 0.1,
|
"silk_line_width": 0.1,
|
||||||
"silk_text_italic": false,
|
"silk_text_italic": false,
|
||||||
@@ -210,7 +210,10 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"track_widths": [
|
"track_widths": [
|
||||||
0.0
|
0.0,
|
||||||
|
0.15,
|
||||||
|
0.25,
|
||||||
|
0.4
|
||||||
],
|
],
|
||||||
"tuning_pattern_settings": {
|
"tuning_pattern_settings": {
|
||||||
"diff_pair_defaults": {
|
"diff_pair_defaults": {
|
||||||
@@ -279,12 +282,7 @@
|
|||||||
"equivalence_files": []
|
"equivalence_files": []
|
||||||
},
|
},
|
||||||
"erc": {
|
"erc": {
|
||||||
"erc_exclusions": [
|
"erc_exclusions": [],
|
||||||
[
|
|
||||||
"power_pin_not_driven|3200400|1346200|270b45f3-22a3-458a-a43c-db627535af63|00000000-0000-0000-0000-000000000000|/72df0bee-e55e-4d81-989a-168573031a0a|/72df0bee-e55e-4d81-989a-168573031a0a|",
|
|
||||||
""
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": 0
|
"version": 0
|
||||||
},
|
},
|
||||||
@@ -470,9 +468,9 @@
|
|||||||
"endpoint_off_grid": "warning",
|
"endpoint_off_grid": "warning",
|
||||||
"extra_units": "error",
|
"extra_units": "error",
|
||||||
"field_name_whitespace": "warning",
|
"field_name_whitespace": "warning",
|
||||||
"footprint_filter": "ignore",
|
"footprint_filter": "warning",
|
||||||
"footprint_link_issues": "warning",
|
"footprint_link_issues": "warning",
|
||||||
"four_way_junction": "ignore",
|
"four_way_junction": "warning",
|
||||||
"ground_pin_not_ground": "warning",
|
"ground_pin_not_ground": "warning",
|
||||||
"hier_label_mismatch": "error",
|
"hier_label_mismatch": "error",
|
||||||
"isolated_pin_label": "warning",
|
"isolated_pin_label": "warning",
|
||||||
@@ -488,7 +486,7 @@
|
|||||||
"net_not_bus_member": "warning",
|
"net_not_bus_member": "warning",
|
||||||
"no_connect_connected": "warning",
|
"no_connect_connected": "warning",
|
||||||
"no_connect_dangling": "warning",
|
"no_connect_dangling": "warning",
|
||||||
"pin_not_connected": "error",
|
"pin_not_connected": "warning",
|
||||||
"pin_not_driven": "error",
|
"pin_not_driven": "error",
|
||||||
"pin_to_pin": "warning",
|
"pin_to_pin": "warning",
|
||||||
"power_pin_not_driven": "error",
|
"power_pin_not_driven": "error",
|
||||||
@@ -496,8 +494,8 @@
|
|||||||
"similar_label_and_power": "warning",
|
"similar_label_and_power": "warning",
|
||||||
"similar_labels": "warning",
|
"similar_labels": "warning",
|
||||||
"similar_power": "warning",
|
"similar_power": "warning",
|
||||||
"simulation_model_issue": "ignore",
|
"simulation_model_issue": "warning",
|
||||||
"single_global_label": "ignore",
|
"single_global_label": "warning",
|
||||||
"stacked_pin_name": "warning",
|
"stacked_pin_name": "warning",
|
||||||
"unannotated": "error",
|
"unannotated": "error",
|
||||||
"unconnected_wire_endpoint": "warning",
|
"unconnected_wire_endpoint": "warning",
|
||||||
@@ -548,7 +546,7 @@
|
|||||||
"last_paths": {
|
"last_paths": {
|
||||||
"idf": "",
|
"idf": "",
|
||||||
"netlist": "",
|
"netlist": "",
|
||||||
"plot": "",
|
"plot": "新建文件夹/",
|
||||||
"specctra_dsn": "",
|
"specctra_dsn": "",
|
||||||
"vrml": ""
|
"vrml": ""
|
||||||
},
|
},
|
||||||
@@ -622,14 +620,50 @@
|
|||||||
"label": "Datasheet",
|
"label": "Datasheet",
|
||||||
"name": "Datasheet",
|
"name": "Datasheet",
|
||||||
"show": true
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Sim.Device",
|
||||||
|
"name": "Sim.Device",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Sim.Pins",
|
||||||
|
"name": "Sim.Pins",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Sim.Type",
|
||||||
|
"name": "Sim.Type",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "LCSC",
|
||||||
|
"name": "LCSC",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "描述",
|
||||||
|
"name": "Description",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "#",
|
||||||
|
"name": "${ITEM_NUMBER}",
|
||||||
|
"show": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"filter_string": "",
|
"filter_string": "",
|
||||||
"group_symbols": true,
|
"group_symbols": true,
|
||||||
"include_excluded_from_bom": true,
|
"include_excluded_from_bom": true,
|
||||||
"name": "Default Editing",
|
"name": "",
|
||||||
"sort_asc": true,
|
"sort_asc": true,
|
||||||
"sort_field": "位号"
|
"sort_field": "${EXCLUDE_FROM_BOM}"
|
||||||
},
|
},
|
||||||
"bus_aliases": {},
|
"bus_aliases": {},
|
||||||
"connection_grid_size": 50.0,
|
"connection_grid_size": 50.0,
|
||||||
@@ -660,6 +694,14 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"version": 1
|
"version": 1
|
||||||
},
|
},
|
||||||
|
"ngspice": {
|
||||||
|
"fix_include_paths": true,
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"model_mode": 4,
|
||||||
|
"workbook_filename": ""
|
||||||
|
},
|
||||||
"page_layout_descr_file": "",
|
"page_layout_descr_file": "",
|
||||||
"plot_directory": "",
|
"plot_directory": "",
|
||||||
"reuse_designators": true,
|
"reuse_designators": true,
|
||||||
@@ -672,7 +714,7 @@
|
|||||||
"uuid": "72df0bee-e55e-4d81-989a-168573031a0a"
|
"uuid": "72df0bee-e55e-4d81-989a-168573031a0a"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"used_designators": "#PWR28,#PWR57-58",
|
"used_designators": "",
|
||||||
"variants": []
|
"variants": []
|
||||||
},
|
},
|
||||||
"sheets": [
|
"sheets": [
|
||||||
|
|||||||
16038
Hardware/text.kicad_sch
16038
Hardware/text.kicad_sch
File diff suppressed because it is too large
Load Diff
1
Hardware/~text.kicad_pcb.lck
Normal file
1
Hardware/~text.kicad_pcb.lck
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hostname":"DESKTOP-0S8JI7H","username":"xtell"}
|
||||||
1
Hardware/~text.kicad_pro.lck
Normal file
1
Hardware/~text.kicad_pro.lck
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"hostname":"DESKTOP-0S8JI7H","username":"xtell"}
|
||||||
7
work-order/wo-00需要完成的功能
Normal file
7
work-order/wo-00需要完成的功能
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
1 屏幕显示
|
||||||
|
2 按键响应
|
||||||
|
3 射频收发
|
||||||
|
4 马达响应
|
||||||
|
5 RGB控制
|
||||||
|
6 电池电压检测和充电
|
||||||
|
7 板子各状态下的功耗 。
|
||||||
72
work-order/wo-01
Normal file
72
work-order/wo-01
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# 📋 工单 wo-01: ADC按键检测、PWR_HOLD开关机控制、P1.3电池电量检测与 PCF8563 硬件 RTC 整合
|
||||||
|
|
||||||
|
**工单编号**: WO-01
|
||||||
|
**创建时间**: 2026-07-27
|
||||||
|
**模块组件**: 按键驱动 (ADC Key) / 电源管理 (Power Control) / 电池检测 (Battery ADC) / 实时时钟 (PCF8563 RTC) / 待机应用 (ClockApp & UI)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 一、 需求概述与按阶段分步规划
|
||||||
|
|
||||||
|
根据项目开发优先级与依赖关系,按以下四个顺序阶段分步完成:
|
||||||
|
|
||||||
|
### 阶段 1: P0.3 ADC 按键检测
|
||||||
|
- **硬件引脚**: `P0.3` 对应 STC32G 硬件 `ADC3` 通道。
|
||||||
|
- **电平特征**: 开机常态下 `KEY_DOWN` 节点电压约为 **2.0V**(未按下),按键按下后电压升至约为 **3.7V**。
|
||||||
|
- **算法与逻辑**:
|
||||||
|
- 配置 ADC 模块,引入滑动窗口均值/中值滤波消抖。
|
||||||
|
- 按键判定:未按下区间 (1.5V ~ 2.5V),按下区间 (3.2V ~ 4.2V)。
|
||||||
|
- 保留并兼容原本 KEY_DOWN 的短按功能(消抖释放后触发 `KEY_EVENT_DOWN_CLICK` 供 UI 菜单与时间编辑下翻数值)。
|
||||||
|
|
||||||
|
### 阶段 2: PWR_HOLD 开关机控制与上电自检
|
||||||
|
- **硬件引脚**: `PWR_HOLD` 控制引脚(默认分配为 `P0.0`)。
|
||||||
|
- **开关机流程**:
|
||||||
|
- **开机锁存**: 按下开机键硬件通电后,单片机在入口 `main()` **第一时间拉高 `PWR_HOLD` 引脚**,锁存电源保持供电。
|
||||||
|
- **长按关机**: 开机状态下长按 KEY_DOWN 按键 1 秒以上,触发关机流程 `KEY_EVENT_POWER_OFF`,拉低 `PWR_HOLD` 切断总电源。
|
||||||
|
- **上电自检 (Self-Test)**:
|
||||||
|
- 启动阶段自动执行硬件自检(电源锁存状态、ADC 采样功能、串口通信校验等),并在串口 1 输出完整的格式化自检报告。
|
||||||
|
|
||||||
|
### 阶段 3: P1.3 电池电压检测与真实电量显示
|
||||||
|
- **硬件引脚**: `P1.3` 对应 STC32G 硬件 `ADC11` 通道(电池分压检测脚)。
|
||||||
|
- **计算与转换**:
|
||||||
|
- 读取 ADC11 通道计算电池输入电压 $V_{bat}$。
|
||||||
|
- 将电池电压(锂电池 3.3V ~ 4.2V 范围)线性/分段映射转换为 **0% ~ 100%** 百分比电量。
|
||||||
|
- **UI 真实电量刷新**:
|
||||||
|
- 重构 `ui.c` 的 `UI_ShowClockPage` 及待机界面电量渲染代码,替换原写死固定的静态 "85%" 字符。
|
||||||
|
- 动态显示真实电池百分比(如 "95%")及绘制对应的电量电池柱位图。
|
||||||
|
|
||||||
|
### 阶段 4: PCF8563 (D8563) 硬件 RTC 深度整合
|
||||||
|
- **硬件连接**: PCF8563 RTC 芯片通过 I2C 总线连接至 STC32G (`P3.2` -> SCL, `P3.3` -> SDA)。
|
||||||
|
- **深度整合**:
|
||||||
|
- **开机/唤醒即读**: 系统开机或从低功耗休眠唤醒时,自动通过 I2C 读取 PCF8563 芯片的时间,更新系统全局时钟变量。
|
||||||
|
- **前台编辑直接写入**: 在 `ClockApp` 编辑模式下按确认键保存时,同步将最新时间写入 PCF8563 芯片寄存器中,实现真正的硬件级掉电时间保存。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 二、 硬件引脚配置汇总
|
||||||
|
|
||||||
|
| 功能名称 | 关联引脚 | 端口模式配置 | 采样/控制参数 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| **KEY_DOWN** | `P0.3` / ADC3 | 高阻输入 | 常态 **2.0V**;按下 **3.7V**;消抖 50ms |
|
||||||
|
| **PWR_HOLD** | `P0.0` (可配置) | 推挽输出 | 上电第一时间置 **高电平 (1)**;关机置 **低电平 (0)** |
|
||||||
|
| **BATT_ADC** | `P1.3` / ADC11 | 高阻输入 | 电池分压输入,计算 3.3V(0%) ~ 4.2V(100%) 电量 |
|
||||||
|
| **RTC_SCL** | `P3.2` | 准双向 / 模拟 I2C | PCF8563 SCL 时钟线 |
|
||||||
|
| **RTC_SDA** | `P3.3` | 准双向 / 模拟 I2C | PCF8563 SDA 数据线 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 三、 分阶段验收标准
|
||||||
|
|
||||||
|
1. **阶段 1 验收 (ADC按键)**:
|
||||||
|
- [ ] 串口实时打印 P0.3 ADC 电压,常态 2.0V 不误触发。
|
||||||
|
- [ ] 按下 3.7V 稳定识别,短按能够正常执行 UI 菜单下翻和数值减小。
|
||||||
|
2. **阶段 2 验收 (开关机与自检)**:
|
||||||
|
- [ ] 长按按键 >1s 开机,系统完成自检后松开按键,`PWR_HOLD` 持续高电平保持供电。
|
||||||
|
- [ ] 开机状态下长按按键 >1s,触发关机提示后 `PWR_HOLD` 拉低,系统断电。
|
||||||
|
- [ ] 开机串口打印完整自检日志。
|
||||||
|
3. **阶段 3 验收 (P1.3电池检测与UI)**:
|
||||||
|
- [ ] 可通过 P1.3 ADC 测量准确计算实际电池电压。
|
||||||
|
- [ ] 待机 UI 屏幕上实时绘制正确的百分比 (0%~100%) 与对应电量电池图标。
|
||||||
|
4. **阶段 4 验收 (PCF8563 RTC)**:
|
||||||
|
- [ ] 开机从 PCF8563 读取硬件时间。
|
||||||
|
- [ ] 前台编辑保存后直接同步写入 PCF8563 芯片,掉电重启时间不丢失。
|
||||||
75
work-order/wo-02
Normal file
75
work-order/wo-02
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# 📋 工单 wo-02: 改动总结与硬件调试归档
|
||||||
|
|
||||||
|
**工单编号**: WO-02
|
||||||
|
**归档时间**: 2026-07-28
|
||||||
|
**处理状态**: 已完成 (Completed)
|
||||||
|
**涉及模块**: 电池检测 (Battery ADC) / 按键驱动 (ADC Key) / 实时时钟 (PCF8563 RTC) / 系统自检 (Self-Test)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 一、 软件改动与底层逻辑修正
|
||||||
|
|
||||||
|
在本次工单调试执行过程中,针对硬件表现的差异与异常,对底层驱动及自检模块进行了如下修正:
|
||||||
|
|
||||||
|
### 1. 电池电压检测与电量百分比校准 (对应阶段 3)
|
||||||
|
* **公式校准与修正**:
|
||||||
|
* 物理硬件装配实际为:上拉 $10\text{k}\Omega$,下拉 $22\text{k}\Omega$(分压比为 $22/32$)。
|
||||||
|
* 经用户万用表与 ADC 联合实测:当电池电压为 $4.0\text{V}$ (4000mV) 时,分压点物理电压为 $2.7\text{V}$,单片机读出的原始 ADC 过滤值为 `2357`。
|
||||||
|
* 为了消除 LDO 压降、系统参考电压偏差等多重硬件误差,引入经验公式替换原先的理论换算公式:
|
||||||
|
$$\text{Voltage (mV)} = \frac{\text{Raw\_ADC} \times 4000}{2357}$$
|
||||||
|
* 该换算将电池准确映射至 `3.3V (0%) ~ 4.2V (100%)`。在 [adc.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.c) 中已完全部署,并在前台时钟界面和自检中完美实时呈现。
|
||||||
|
|
||||||
|
### 2. ADC 按键与电池通道互换 (对应阶段 1 & 2)
|
||||||
|
* **故障定位**:
|
||||||
|
* 物理走线中,`P0.3` (按键) 实际对应单片机的 `ADC11` 通道;`P1.3` (电池) 实际对应 `ADC3` 通道。原先代码中将两个通道错调。
|
||||||
|
* **修正方案**:
|
||||||
|
* 在 [adc.h](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.h) 中对调了通道定义:
|
||||||
|
`#define ADC_CHANNEL_KEY_DOWN 11` (P0.3)
|
||||||
|
`#define ADC_CHANNEL_BATTERY 3` (P1.3)
|
||||||
|
* 修正后,按键常态值成功捕获为正常的 `2712` (约 2.2V),按下后正常达到 `4095` 饱和值。
|
||||||
|
|
||||||
|
### 3. PCF8563 (D8563) RTC 通信时序与机制重构 (对应阶段 4)
|
||||||
|
* **I2C 物理模式优化**: 移除了高频读写 XSFR 带来的推挽/开漏动态切换(避免产生高频暂态毛刺),完全使用准双向口硬件 of “过渡强上拉”机制。
|
||||||
|
* **死锁释放机制 (`I2C_ReleaseBus`)**: 在 PCF8563 通信或自检开始前,自动输出 9 个时钟脉冲配合 Stop 信号,强行迫使因常供电而进入错乱挂起状态的 RTC 芯片释放 I2C 总线,彻底避免死锁。
|
||||||
|
* **自适应引脚探测**: 内置支持运行时自动侦测 `P3.2` 与 `P3.3` 的连线是否在画板时反接(默认极性与互换极性自动轮询),增强了硬件兼容度。
|
||||||
|
* **双时延诊断测试**: 实现了 `PCF8563_TryReadDiagnostic()` 函数,同时使用常规速率 (30us延时) 与超低速时序 (200us延时,2.5kHz) 进行试读测试。
|
||||||
|
|
||||||
|
### 4. RGB 幻彩灯条上电亮白灯与 4-bit SPI 时序重构
|
||||||
|
* **故障定位**:
|
||||||
|
1. 上电复位暂态:单片机复位期间引脚默认弱上拉输出 `1`,这被灯珠移位寄存器误读为“全1”色彩(白光)。当单片机完成复位并拉低引脚时,产生 RESET 信号迫使灯珠锁存该色彩,导致开机长时间亮白灯。
|
||||||
|
2. 3-bit 编码周期过短:在 3MHz 速率下,3-bit 模拟波形(100b/110b)的整个脉冲周期仅有 1.0us,低于规格书规定的 1.2us 最小值,引发灯珠识别率低下。
|
||||||
|
* **软件重构方案**:
|
||||||
|
1. **4-bit 编码重构**: 将转码改为 4-bit 编码(0码=1000b, 1码=1100b),高电平分别保持 333ns 和 666ns,完全处于规格书安全区间;总周期为 4 bits = 1.33us,完美吻合典型值 1.25us,数据传输稳定度极高。
|
||||||
|
2. **上电显式关灯**: 在 `main()` 的 `GPIO_Init()` 执行完毕的第一个瞬间,强制调用 `RGB_Send(0, 0, 0, 0, 0, 0);` 送出关灯帧,在极速开机阶段的几微秒内强行熄灭白光。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 二、 物理硬件排障记录
|
||||||
|
|
||||||
|
在联合调试期间,定位并解决了一项关键的物理硬件故障:
|
||||||
|
|
||||||
|
* **RTC 晶振不起振故障**:
|
||||||
|
* **现象**: MCU 引脚输出波形完美,但 RTC 芯片一直不回 ACK。
|
||||||
|
* **病因**: 32.768kHz 晶体两端的**旁路电容参数焊接错误**,导致晶振无法起振,从而使芯片的 I2C 接口内部控制块锁定并忽略全部寻址。
|
||||||
|
* **解决**: 修正贴片电容后,晶振起振,RTC 硬件通信与自检完全恢复正常。
|
||||||
|
|
||||||
|
* **充电检测引脚配置与校准**:
|
||||||
|
* **引脚定义**: 充电状态 DET 物理连接至 MCU 的 **`P1.2`** 引脚(对应 QFN32 第 10 脚,硬件为 **`ADC 通道 2`**)。
|
||||||
|
* **电气优化**: 硬件配置为高阻输入,且在 `system.c` 和 `adc.c` 初始化中**显式关闭了 P1.2 的内部上拉电阻**(通过置空寄存器位 `(*(unsigned char volatile xdata *)0xFE11) &= ~(1 << 2)`),彻底消除了上电引起的电平偏置。
|
||||||
|
* **阈值检测标准**:
|
||||||
|
- $V_{\text{DET}} < 0.5\text{V}$ ($<500\text{mV}$): 未插充电器
|
||||||
|
- $0.8\text{V} \le V_{\text{DET}} \le 1.8\text{V}$ ($800\text{mV} \sim 1800\text{mV}$): 正在充电 (充电指示闪烁)
|
||||||
|
- $2.2\text{V} \le V_{\text{DET}} \le 2.8\text{V}$ ($2200\text{mV} \sim 2800\text{mV}$): 已充满 (停止闪烁)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 三、 交付与归档文件
|
||||||
|
|
||||||
|
|
||||||
|
* **最新源文件**:
|
||||||
|
* 电池与按键检测算法: [adc.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.c)
|
||||||
|
* 引脚定义配置表: [adc.h](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.h)
|
||||||
|
* 自适应防死锁 RTC 驱动: [pcf8563.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20%281%29/stc32g128k/Drivers/pcf8563.c)
|
||||||
|
* 极速自检与诊断流程: [system.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/App/system.c)
|
||||||
|
* **烧录固件**:
|
||||||
|
* 归档 HEX 路径: [Build\wristband.hex](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Build/wristband.hex)
|
||||||
126
work-order/wo-03
Normal file
126
work-order/wo-03
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# 工单 WO-03:RGB 灯控故障排查 & 电池充电功能测试计划
|
||||||
|
|
||||||
|
**优先级**:高
|
||||||
|
**状态**:进行中
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、RGB 灯控 — 故障排查与修复
|
||||||
|
|
||||||
|
### 现象
|
||||||
|
开机后两颗 WS2812B 灯珠一直亮白灯,无法通过软件控制熄灭或变色。
|
||||||
|
|
||||||
|
### 1.1 硬件电路复查
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 1.1.1 | 用万用表测量 P2.3 对地电阻 | 断电测 | 非短路 | 待执行 |
|
||||||
|
| 1.1.2 | 上电测 P2.3 引脚电平 | 示波器捕获上电瞬间到 main() 执行前的波形 | 应快速被拉低,无长时间高电平 | 待执行 |
|
||||||
|
| 1.1.3 | 测量 TP4054 VCC (5V) 和 VSYS 供电 | 万用表直流档 | VSYS 约 3.7~4.2V 或 5V | 待执行 |
|
||||||
|
| 1.1.4 | 测量 WS2812B VDD (Pin4) 电压 | 万用表 | 与 VSYS 一致 | 待执行 |
|
||||||
|
| 1.1.5 | 测量 WS2812B DIN (Pin3) 静态电平 | 未发送数据时示波器/万用表 | 应接近 0V | 待执行 |
|
||||||
|
|
||||||
|
### 1.2 引脚初始化修复
|
||||||
|
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|
||||||
|
|---|---------|---------|------|------|
|
||||||
|
| 1.2.1 | GPIO_Init() 中增设 RGB_DIN = 0 | system.c:50 | 将 P2.3 初始输出拉低,消除上电不确定态 | ✔ 已修复 |
|
||||||
|
| 1.2.2 | main() 中发一次 RESET 脉冲确保 WS2812B 复位 | main.c | 确保 WS2812B 在上电后处于已知复位状态 | ✔ 已修复 |
|
||||||
|
| 1.2.3 | 验证 P_SW1 SPI 引脚映射 | rgb.c:19 | P_SW1 &= ~0x0C; P_SW1 |= 0x04 将 SPI 映射到 P2.3(MOSI)/P2.5(SCLK) | ✔ 已验证 |
|
||||||
|
|
||||||
|
### 1.3 SPI 时序验证
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 1.3.1 | 示波器探头接 P2.3 (MOSI/DIN) 和 GND | 调用 RGB_Send(0, 180, 0, 0, 0, 0) 只亮红灯 | 观察到 3MHz 时钟波形 | 待执行 |
|
||||||
|
| 1.3.2 | 示波器接 P2.5 (SCLK) 观察 | 同一调用 | 发送期间无 SCLK 信号(因已被设为高阻) | 待执行 |
|
||||||
|
| 1.3.3 | 测量 RESET 低电平持续时间 | 在 RGB_Send() 最后 RGB_DIN=0 后测 | >80μs | 待执行 |
|
||||||
|
| 1.3.4 | 验证 SPCTL = 0xD1 配置 | 核对 STC32G 数据手册 | SSIG=1, SPEN=1, MSTR=1, CPOL=0, CPHA=0, SPR0=1 → 3MHz @24MHz | ✔ 已验证 |
|
||||||
|
|
||||||
|
### 1.4 代码逻辑复查
|
||||||
|
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|
||||||
|
|---|---------|---------|------|------|
|
||||||
|
| 1.4.1 | 检查 Encode_Byte 编码 | rgb.c:42-62 | 每个 WS2812B 的 8bit 展开为 3 字节 SPI 数据 = 24 SPI bits | ✔ 已验证 |
|
||||||
|
| 1.4.2 | 检查 RGB_Send 中 SPI 收发循环 | rgb.c:94-100 | 18 字节全部通过 SPDAT 发出,含等待 SPIF | ✔ 已验证 |
|
||||||
|
| 1.4.3 | SPI_Init 调用频次 | rgb.c:92 | 每次 RGB_Send 都重新 init SPI,不影响功能 | ✔ 已验证 |
|
||||||
|
| 1.4.4 | RGB_Send(0,0,0,0,0,0) 能否关灯 | 调试指令 | 需实测验证 | 待执行 |
|
||||||
|
|
||||||
|
### 1.5 功能验证
|
||||||
|
| # | 测试用例 | 操作 | 预期结果 | 状态 |
|
||||||
|
|---|---------|------|---------|------|
|
||||||
|
| 1.5.1 | 开机默认灭灯 | 上电 | 两颗灯全灭 | 待执行 |
|
||||||
|
| 1.5.2 | 对码模式白灯 | 进入 PairApp | 两颗灯亮白灯(G=R=B=150) | 待执行 |
|
||||||
|
| 1.5.3 | 对码成功绿灯 | PairApp 确认保存 | 两颗亮绿灯(G=200) | 待执行 |
|
||||||
|
| 1.5.4 | 报警红灯 | 触发门磁报警 | 红灯闪烁 | 待执行 |
|
||||||
|
| 1.5.5 | SOS 红蓝交替 | 触发 SOS | 灯1红灯、灯2蓝灯交替闪烁 | 待执行 |
|
||||||
|
| 1.5.6 | 时间设置蓝灯 | 长按确认进入时间设置 | 双蓝灯常亮 | 待执行 |
|
||||||
|
| 1.5.7 | 马达无异常振动 | 任何 RGB_Send 调用时 | 马达不应发出异常噪音或振动 | 待执行 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、电池充电功能测试计划
|
||||||
|
|
||||||
|
### 2.1 硬件充电回路验证
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 2.1.1 | 万用表测 TP4054 的 VCC(Pin4) 对 GND | 插入 USB 5V 供电 | 5.0V ±0.25V | 待执行 |
|
||||||
|
| 2.1.2 | 测 TP4054 的 PROG(Pin5) 对 GND 电阻 | 断电测 R1 阻值 | 24kΩ → 充电电流约 41.7mA | 待执行 |
|
||||||
|
| 2.1.3 | 测 TP4054 的 BAT(Pin3) 对 GND 电压 | 插入 USB + 接电池 | 电池电压 3.7~4.2V | 待执行 |
|
||||||
|
| 2.1.4 | 测 TP4054 的 CHRG(Pin1) 电平 | 未接USB/充电中/充满 | 未接:高阻;充电中:低电平;充满:高阻 | 待执行 |
|
||||||
|
| 2.1.5 | 追踪 CHRG → DET → MCU 引脚号的 PCB 走线 | 对照原理图 | 确认连接至 MCU GPIO 引脚号 | 待执行 |
|
||||||
|
| 2.1.6 | 确认 DET 网络终端的 MCU 引脚电平兼容性 | 万用表测 MCU 侧 | TP4054 CHRG 需 MCU 内部上拉 | 待执行 |
|
||||||
|
| 2.1.7 | 测量 BAT_ADC(P1.3) 分压网络 | 万用表 | R15=10kΩ, R16=22kΩ, 分压比 22/32 | 待执行 |
|
||||||
|
|
||||||
|
### 2.2 充电检测代码实现
|
||||||
|
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|
||||||
|
|---|---------|---------|------|------|
|
||||||
|
| 2.2.1 | 确认 MCU 实际连接 CHRG/DET 的引脚号 | 原理图追踪 | CHRG → DET → MCU | ✔ 已完成 |
|
||||||
|
| 2.2.2 | 在 config.h 添加 sbit CHRG_DET 定义 | config.h | 定义充电检测引脚 | ✔ 已添加 |
|
||||||
|
| 2.2.3 | 在 GPIO_Init() 中将该引脚配置为高阻输入+内部上拉 | system.c | 适配 TP4054 开漏输出 | ✔ 已添加 |
|
||||||
|
| 2.2.4 | 实现 bit Is_Charging(void) | adc.c / adc.h | 返回 0=未充电, 1=充电中 | ✔ 已实现 |
|
||||||
|
| 2.2.5 | 在 UI 层接入充电状态显示 | ui.c / app_clock.c | 充电时显示 `+` 后缀 | ✔ 已完成 |
|
||||||
|
| 2.2.6 | 在 Self_Test() 中输出充电状态 | system.c | 上电自检时UART输出充电状态 | ✔ 已添加 |
|
||||||
|
|
||||||
|
### 2.3 充电功能验证
|
||||||
|
| # | 测试用例 | 操作 | 预期结果 | 状态 |
|
||||||
|
|---|---------|------|---------|------|
|
||||||
|
| 2.3.1 | 未插入 USB 充电 | 观察待机界面 | 仅显示百分比,无 `+` 后缀 | 待执行 |
|
||||||
|
| 2.3.2 | 插入 USB 充电(电池未满) | USB 供电 | 状态栏显示 `85%+`,CHRG 低电平 | 待执行 |
|
||||||
|
| 2.3.3 | 电池充满后 | 等待充电完成 | `+` 后缀消失,CHRG 变高阻 | 待执行 |
|
||||||
|
| 2.3.4 | 充电时热插拔 USB | 反复插拔 | 状态切换无延迟 > 2s | 待执行 |
|
||||||
|
| 2.3.5 | 低电量充电(电池 < 3.3V) | 电池耗尽后充电 | 充电电流约 41.7mA,UI 显示充电中 | 待执行 |
|
||||||
|
| 2.3.6 | 充电时进入休眠 | 插入 USB 等待 60s 自动休眠 | 休眠后应能被唤醒 | 待执行 |
|
||||||
|
| 2.3.7 | 充电时触发报警/SOS | 充电中触发报警 | 报警功能正常 | 待执行 |
|
||||||
|
| 2.3.8 | Is_Charging() 电平反转验证 | 强制拉高/拉低 DET 引脚 | UART 调试输出正确状态 | 待执行 |
|
||||||
|
|
||||||
|
### 2.4 ADC 电池电压校准
|
||||||
|
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|
||||||
|
|---|---------|---------|---------|------|
|
||||||
|
| 2.4.1 | 用万用表实测电池电压 | 记录 Vref | 如 3.85V | 待执行 |
|
||||||
|
| 2.4.2 | UART 读取 ADC_GetBatteryVoltage_mV() 返回值 | 串口助手 | 偏差应在 ±50mV 内 | 待执行 |
|
||||||
|
| 2.4.3 | 如果偏差过大,调整校准系数 | adc.c:122 | val * Vref_mV / adc_raw | 待执行 |
|
||||||
|
| 2.4.4 | 验证电量百分比线性度 | 可调电源模拟 3.3V~4.2V | 0%~100% 线性输出 | 待执行 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、测试环境与工具清单
|
||||||
|
- Keil C251 编译器 + STC-ISP 烧录工具
|
||||||
|
- USB 转 TTL 串口模块 (115200 baud)
|
||||||
|
- 数字万用表
|
||||||
|
- 双通道数字示波器(至少 10MHz 带宽)
|
||||||
|
- 可调直流稳压电源(模拟电池 3.0~4.2V)
|
||||||
|
- 逻辑分析仪(可选)
|
||||||
|
- 已组装好的手环 PCB x 1~2pcs
|
||||||
|
- 433MHz 发射模块/遥控器
|
||||||
|
- 锂电池 3.7V
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、代码修改记录
|
||||||
|
|
||||||
|
| 文件 | 修改内容 | 状态 |
|
||||||
|
|------|---------|------|
|
||||||
|
| App/config.h | 新增 CHRG_DET sbit 定义 | ✔ |
|
||||||
|
| App/system.c | GPIO_Init() 中增设 RGB_DIN=0,配置 CHRG_DET 引脚,Self_Test() 输出充电状态 | ✔ |
|
||||||
|
| App/main.c | GPIO_Init() 后立即发 WS2812B RESET 脉冲 | ✔ |
|
||||||
|
| Drivers/adc.h | 新增 Is_Charging() 声明 | ✔ |
|
||||||
|
| Drivers/adc.c | 实现 Is_Charging() | ✔ |
|
||||||
|
| App/rgb.c | 确认 SPI 配置正确 | ✔ 已验证 |
|
||||||
|
| App/ui.c | UI_ShowClockPage 中接入 Is_Charging() 显示 `+` 后缀 | ✔ |
|
||||||
222
work-order/wo-04
Normal file
222
work-order/wo-04
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
# 📋 工单 wo-04: 多国语言支持(法/英)与 RF 接收中断化改造
|
||||||
|
|
||||||
|
**工单编号**: WO-04
|
||||||
|
**创建时间**: 2026-07-31
|
||||||
|
**模块组件**: 菜单/UI (app_menu.c, ui.c) / 数据持久化 (database.c) / 射频驱动 (rf.c) / 定时器 (Timer0)
|
||||||
|
**优先级**: 中
|
||||||
|
**状态**: 进行中(阶段 1 已实机验证通过;阶段 2 代码已完成,待编译烧录验证)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 零、阶段 2 开发过程中顺带发现并修复的既有 bug
|
||||||
|
|
||||||
|
在设计 RF 中断化的看门狗超时判断(需要用 `ms_tick` 判断是否卡在半成品帧状态太久)时,发现 **`ms_tick` 全局变量从未被递增过**,永远等于 0:
|
||||||
|
|
||||||
|
* `App/main.c` 声明 `volatile u16 ms_tick = 0;`,全项目除此之外没有任何地方对它做 `++`。
|
||||||
|
* 用 `git log -S "ms_tick++"` 定位到 commit `c0d1fe7`(App 框架重构):重构前 `Timer1_Isr` 里确实有 `ms_tick++;` 及配套的 1000 归零逻辑,重构时把时钟相关代码抽到 `Clock_IncMS()`(用它自己的私有计数器 `ms_cnt`),误删了 `ms_tick++` 这一行,此后再未补回。
|
||||||
|
* 影响范围广:`app_menu.c` 呼吸灯与 IPC 绑定帧重发、`app_pair.c` 对码等待动画与 30 秒超时、`app_alarm.c`/`app_sos.c` 灯光闪烁与马达节拍、`app_clock.c` 时间设置光标闪烁、`event.c` 报警红框闪烁,全部依赖 `ms_tick` 计时,此前实际上都是冻结/不生效的。
|
||||||
|
* **修复**:在 `App/event.c` 的 `Timer1_Isr` 中补回 `ms_tick++;`(0~999 循环,与原逻辑一致)。已完成,不在此工单原定范围内,但顺带修复。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、需求概述
|
||||||
|
|
||||||
|
本工单包含两项互相独立的改造,按顺序分阶段实施,每阶段单独提交、单独验证:
|
||||||
|
|
||||||
|
1. **阶段 1 — 多国语言支持**:菜单与界面文案支持法语(FR)/英语(EN)切换,断电重启后保留上次选择。
|
||||||
|
2. **阶段 2 — RF 接收中断化**:把现在阻塞轮询的 `EV1527_Decode()` 改造成外部中断驱动 + Timer0 时间戳测量,消除主循环阻塞。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、阶段 1: 多国语言支持(法/英)
|
||||||
|
|
||||||
|
### 1.1 背景与结论
|
||||||
|
- 目标语言为法语 + 英语,均为纯 ASCII 字符,现有 `FONT_8x16` 字库已完全覆盖,**不需要新增任何字模资源**。
|
||||||
|
- 参考了 `100_smart_boat` 项目 `xtell_remote_control/language` 的 X-Macro 字符串表模式,去掉了其中中文字库相关的部分(本工单不涉及中文)。
|
||||||
|
|
||||||
|
### 1.2 设计要点(已与用户确认)
|
||||||
|
| 决策项 | 结论 |
|
||||||
|
|---|---|
|
||||||
|
| 语言选择是否持久化 | 需要。断电重启后恢复上次选择 |
|
||||||
|
| 持久化存储位置 | **不单开新 Flash 扇区**,追加存入 `sensor_list` 所在的同一扇区(该扇区 512 字节仅用了 352 字节,剩余空间足够),复用现有 `Load_Database`/`Save_Database` 读写流程 |
|
||||||
|
| 菜单位置与顺序 | 语言切换项放在**主菜单第 1 项**,原有 3 项("HORLOGE 时间/APPAIRAGE 对码/LIAISON 绑定")依次后移一位 |
|
||||||
|
| 交互方式 | **不做二级选择子菜单**。菜单高亮在语言项时,直接按■(确认键)在 FR/EN 间原地切换并立即保存,同时用新语言重绘当前菜单 |
|
||||||
|
|
||||||
|
### 1.3 实现内容
|
||||||
|
1. 新增 `App/language.h` + `App/language.c`:
|
||||||
|
- `typedef enum { LANG_FR = 0, LANG_EN, LANG_COUNT } Language;`
|
||||||
|
- `void Lang_Init(Language lang);` / `void Lang_Set(Language lang);` / `Language Lang_Current(void);`
|
||||||
|
- `const char* Lang_Get(StringID id);`
|
||||||
|
2. 新增 `App/strings.h`:用 X-Macro (`WRISTBAND_STRING_LIST`)登记 `ui.c` 中全部界面文案、`database.c` 的 `sensor_names_fr[5]`、`app_menu.c` 的 `menu_items[5]`,每条包含 FR/EN 两列译文。
|
||||||
|
3. `App/ui.c`:所有 `LCD_ShowStringCentered(y, "字面量", ...)` / `LCD_ShowString(...)` 改为经 `Lang_Get(STR_XXX)` 取值。
|
||||||
|
4. `App/app_menu.c`:
|
||||||
|
- 主菜单项数从 3 改为 4,`menu_select` 上限从 2 改为 3。
|
||||||
|
- 序号重排:0-语言切换,1-HORLOGE,2-APPAIRAGE,3-LIAISON。
|
||||||
|
- `menu_level == 0` 分支中,`menu_select == 0` 时按 CONFIRM_CLICK 直接切换语言 + 保存 + 重绘,不进入新的 `menu_level`。
|
||||||
|
5. `App/ui.c` 的 `UI_ShowMainMenu()`:新增语言项对应的图标与文案(法语环境显示 "LANGUE",英语环境显示 "LANGUAGE"),原有 3 个图标绘制的 `selected_index` 判断值整体 +1。
|
||||||
|
6. `App/database.c`:
|
||||||
|
- `Load_Database()`/`Save_Database()` 扩展,在 `sensor_list` 数组之后追加 1 字节语言标志的读写(同一扇区、同一次擦写周期)。
|
||||||
|
- 开机时 `Load_Database()` 读到的语言字节传给 `Lang_Init()`。
|
||||||
|
|
||||||
|
### 1.4 验收标准
|
||||||
|
- [ ] 开机默认语言为法语(或 Flash 中已保存的上次选择)。
|
||||||
|
- [ ] 主菜单第 1 项显示"语言"选项,上下键可以正常移动到该项(不影响其余 3 项的原有功能)。
|
||||||
|
- [ ] 在语言项上按确认键,菜单文案立即从法语切换为英语(或反之),无需退出重进菜单。
|
||||||
|
- [ ] 切换语言后,退出菜单进入其余各页面(时钟页、对码流程、报警页、SOS页)文案均正确跟随当前语言显示。
|
||||||
|
- [ ] 切换语言后断电重启,开机后菜单语言与断电前一致。
|
||||||
|
- [ ] 现有传感器数据库(已绑定的传感器列表)在语言切换与断电重启后不受影响、不丢失。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、阶段 2: RF 接收中断化改造
|
||||||
|
|
||||||
|
### 2.1 背景
|
||||||
|
- 现状:`EV1527_Decode()`([Drivers/rf.c](Drivers/rf.c))为阻塞轮询实现,同步头捕获阶段最坏情况阻塞达 30ms,由后台 `RfMonitorApp_onRun()` 在主循环中无条件调用,可能干扰按键长按计时的准确性(按键计数按"调用次数"而非"真实时间"累加)。
|
||||||
|
- 确认结论:STC32G12K128(至少本项目 `stc32g.h` 覆盖的寄存器范围)**没有硬件捕获(CAP/PCA)寄存器**,只能用"外部中断 + 自由运行定时器时间戳"的软件方案实现等效功能。
|
||||||
|
|
||||||
|
### 2.2 设计要点
|
||||||
|
| 决策项 | 结论 |
|
||||||
|
|---|---|
|
||||||
|
| 边沿检测 | 复用现有 P3.6(`RF_RX_DATA`)端口中断(`interrupt 16`),从"只在休眠时开启"改为"常态双边沿触发常驻开启" |
|
||||||
|
| 时间戳来源 | 复用现有 Timer0(已用于 `RF_Delay_us`/`GetPulseDuration`),精度 0.5us/tick(24MHz 晶振 12T 模式下),来自晶振本身,精度足够,不需要改 1T 模式 |
|
||||||
|
| 计时范围 | Timer0 为 16 位,0.5us/tick 约 32.768ms 溢出一次,而同步头低电平上限要求测到 60ms —— 需要加 Timer0 溢出中断,把 16 位硬件计数扩展为软件维护的更宽时间戳,避免溢出截断 |
|
||||||
|
| 解码逻辑归属 | 沿用现有的同步头判定 + 24 位比值解调算法,只是从"主循环阻塞轮询"搬到"中断内增量式状态机推进";中断中不直接操作事件队列,只写标志位/结果变量,由主循环取用后生成 `SystemEvent` |
|
||||||
|
|
||||||
|
### 2.3 顺带修复
|
||||||
|
- `GetPulseDuration()`([Drivers/rf.c:160-172](Drivers/rf.c#L160-L172))中 `timeout_ticks` 用 `u16` 存储,60ms 超时换算成 tick(120000)超出 `u16` 上限被截断为约 27.2ms,与注释/调用方预期不符。本次改造一并修正(改用能容纳完整量程的类型)。
|
||||||
|
|
||||||
|
### 2.4 实现内容
|
||||||
|
1. Timer0 改为开机后常驻自由运行(不再是 `RF_Delay_us`/`GetPulseDuration` 里那种每次启停复位的一次性用法),新增 Timer0 溢出中断以扩展时间戳位宽。
|
||||||
|
2. P3.6 端口中断改为常态开启双边沿触发;`Port3_Isr` 内根据系统是否处于休眠切换态,分别处理"仅唤醒"和"正常解码"两种职责。
|
||||||
|
3. 中断内维护一个小状态机(等待同步头 / 累计第几位 / 暂存 addr 和 data),每次边沿中断推进一步,solved 24 位后把结果写入类似 `p0_wakeup_flag` 模式的标志变量。
|
||||||
|
4. `RfMonitorApp_onRun()` 及 `PairApp_onRun()` 改为检查该标志变量,而不是直接调用阻塞的 `EV1527_Decode()`。
|
||||||
|
5. 增加"半成品帧超时看门狗":同步头之后若在预期时间内未凑够 24 位,自动放弃并复位状态机,防止被干扰卡死。
|
||||||
|
|
||||||
|
### 2.5 验收标准
|
||||||
|
- [ ] 主循环轮询 RF 不再有阻塞现象(可用示波器/计时对比主循环单次迭代耗时)。
|
||||||
|
- [ ] 对码(`PairApp`)、后台报警监听(`RfMonitorApp`)功能行为与改造前一致,能正确捕获/解码射频信号。
|
||||||
|
- [ ] 按键长按计时(3 秒判定)在有射频信号持续输入时不再受干扰、计时准确。
|
||||||
|
- [ ] 休眠状态下 P3.6 收到射频信号仍能正常唤醒,唤醒功能不受本次改造影响。
|
||||||
|
- [ ] 干扰噪声下(无有效信号)状态机不会卡死在"半成品帧"状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、代码修改记录
|
||||||
|
|
||||||
|
| 阶段 | 文件 | 修改内容 | 状态 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | App/language.h / App/language.c | 新建,语言状态管理与取字符串接口(`Lang_Init/Lang_Set/Lang_Current/Lang_Get`) | ✔ 已完成 |
|
||||||
|
| 1 | App/strings.h | 新建,`WRISTBAND_STRING_LIST` X-Macro 字符串表(法/英) | ✔ 已完成 |
|
||||||
|
| 1 | App/config.h | 移除旧的 `sensor_names_fr` extern 声明 | ✔ 已完成 |
|
||||||
|
| 1 | App/database.h / App/database.c | 新增 `sensor_type_name_ids[5]` 字符串 ID 表;`Load_Database`/`Save_Database` 追加读写语言字节(与 `sensor_list` 共用同一扇区,偏移地址 `sizeof(sensor_list)`);`Add_Sensor_With_Zone` 改用 `Lang_Get()` 生成名称 | ✔ 已完成 |
|
||||||
|
| 1 | App/app_menu.c | 主菜单改 4 项并重排序号(0-语言/1-HORLOGE/2-APPAIRAGE/3-LIAISON);语言项按确认键原地切换 FR/EN 并调用 `Save_Database()` 持久化;`menu_items` 改名并改造为 `device_menu_ids` 字符串 ID 表 | ✔ 已完成 |
|
||||||
|
| 1 | App/ui.c | 全部界面文案字面量替换为 `Lang_Get(STR_XXX)`;`UI_ShowMainMenu` 新增第 0 项语言图标(直接显示当前语言 "FR"/"EN" 缩写) | ✔ 已完成 |
|
||||||
|
| 1 | wristband.uvproj | 新增 `App/language.c` 参与 Keil 工程编译 | ✔ 已完成 |
|
||||||
|
| 2 | Drivers/rf.c | 新增 Timer0 常驻自由运行 + 溢出中断 (`Timer0_Isr`) 扩展为 32 位时间戳;新增 `RF_HandleEdgeInterrupt()` 边沿增量解码状态机;`RF_Delay_us` 改为对运行中 Timer0 的快照式等待 (不再启停复位);`EV1527_Decode()` 改为非阻塞检查中断解码结果 (签名不变);移除死代码 `mock_rf_*` 与旧版 `GetPulseDuration` | ✔ 已完成 |
|
||||||
|
| 2 | Drivers/rf.h | 移除 `GetPulseDuration` 声明,新增 `RF_HandleEdgeInterrupt` 声明,更新 `EV1527_Decode` 注释 | ✔ 已完成 |
|
||||||
|
| 2 | App/main.c | `Port3_Isr` 去掉旧版"触发一次即自禁用 P3INTE"的逻辑,改为每次都调用 `RF_HandleEdgeInterrupt()`,身兼休眠唤醒与常态解码两职 | ✔ 已完成 |
|
||||||
|
| 2 | App/system.c | `Enter_Low_Power_Sleep`:P3.6 双边沿触发与中断允许改为常态 (在 `RF_Init` 一次性配置),此处只保留重新武装 `P3WKUE`;新增休眠前禁用 `ET0`。`Wakeup_Restore`:`P3INTE` 唤醒后保留 P3.6 位 (不再清零),新增唤醒后恢复 `ET0` | ✔ 已完成 |
|
||||||
|
| 2 | App/app_rf_monitor.c / App/app_pair.c | 无需改动 —— `EV1527_Decode()` 函数签名与调用方式保持不变 | 不涉及 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五之一、后续结构调整:新增 Drivers/timer.c 统一存放定时器驱动代码
|
||||||
|
|
||||||
|
用户指出 `Timer0_Isr`/`Timer1_Isr` 属于底层驱动代码,不应该分散在 `Drivers/rf.c` 和 `App/event.c` 里,要求集中放到 `Drivers/timer.c`。据此做了以下调整(纯代码搬家,行为不变):
|
||||||
|
|
||||||
|
* 新建 `Drivers/timer.h` / `Drivers/timer.c`,收纳:
|
||||||
|
- `Timer0_Init()` / `Timer0_Isr()` / `Timer0_GetTimestamp()`(原来内联在 `RF_Init()` 里 + `rf.c` 的 `Timer0_Isr`/`RF_GetTimestamp`)
|
||||||
|
- `Timer1_Init()` / `Timer1_Isr()` / `key_scan_flag` 的定义(原来在 `App/event.c`)
|
||||||
|
* `Drivers/rf.c`:`RF_Init()` 改为调用 `Timer0_Init()`,`RF_Delay_us`/`RF_HandleEdgeInterrupt` 里的 `RF_GetTimestamp()` 改调用 `Timer0_GetTimestamp()`。
|
||||||
|
* `App/event.c`:删除 `Timer1_Init`/`Timer1_Isr`/`key_scan_flag` 定义与不再需要的 `clock.h` 引入;`Event_KeyScan_Poll` 等其余逻辑不变。
|
||||||
|
* `App/event.h`:移除 `Timer1_Init` 声明(改由 `Drivers/timer.h` 提供),`key_scan_flag` 的 extern 声明保留(定义位置变了,对外接口不变)。
|
||||||
|
* `App/main.c`:新增 `#include "../Drivers/timer.h"` 以取得 `Timer1_Init` 声明。
|
||||||
|
* `wristband.uvproj`:新增 `Drivers/timer.c` 参与编译。
|
||||||
|
|
||||||
|
## 五、阶段 2 实现备注
|
||||||
|
|
||||||
|
* Timer0 改为开机后 (`RF_Init()`) 启动一次、永不停止的自由运行计数器,配合 `Timer0_Isr` 溢出中断把 16 位硬件计数扩展为 32 位软件时间戳 (`RF_GetTimestamp()`),解决了原 `GetPulseDuration()` 里 `u16 timeout_ticks` 存不下 60ms 对应的 120000 tick、被截断成约 27.2ms 的既有 bug。
|
||||||
|
* `EV1527_TxFrame()` 用到的 `RF_Delay_us()` 原本会启停复位 Timer0,与"常驻自由运行"直接冲突 (会导致发射一次后 RX 时间戳永久停摆),已改为对运行中的 Timer0 做起止快照差值等待,纯只读,不影响其自由运行状态。
|
||||||
|
* `EV1527_Transmit()` 整个发射过程是 `EA=0` 屏蔽全局中断执行的 (原有设计,未改动),期间 Timer0 硬件仍在计数但溢出中断进不来,`rf_timer_ovf_count` 会短暂漏计;发射结束后的第一条 RX 边沿会因为时间差异常大而被判定超出合法区间、直接丢弃,随后自动恢复正常——这是状态机自愈的正常代价,不需要额外处理。开机阶段 `Self_Test()`/`Load_Database()` 耗时较长同理,也只会导致开机后第一条边沿被丢弃一次。
|
||||||
|
* 看门狗超时阈值取 200ms (`RF_DECODE_TIMEOUT_MS`),明显大于一帧最坏情况的理论总耗时 (~95ms:同步头最长 61.5ms + 24 位数据最长约 33.6ms),避免正常长帧被误杀。
|
||||||
|
* P3.6 双边沿中断现在身兼两职:休眠期间做唤醒源 (`p3_wakeup_flag`),正常运行期间驱动 `RF_HandleEdgeInterrupt()` 解码;`Enter_Low_Power_Sleep`/`Wakeup_Restore` 中对应调整为"不再清零 P3.6 的中断允许位",避免唤醒后 RX 解码功能失效。
|
||||||
|
* 尚未实机编译烧录验证,第三节验收标准仍需在真实硬件上逐项确认 (尤其是干扰环境下的解码成功率、休眠唤醒功能是否受影响)。
|
||||||
|
|
||||||
|
### 2.7 实机验证发现的真正根因:Port3 中断向量重定向缺失 + isr_jump.asm 从未参与编译
|
||||||
|
|
||||||
|
在 2.6 节的排查过程中,通过逐步二分法(先屏蔽 `RF_HandleEdgeInterrupt` 函数体、再屏蔽 `P3INTE`)最终定位到**真正的病根**,2.6 节"P3.6 悬空"的诊断被推翻,记录如下供后续参考:
|
||||||
|
|
||||||
|
* `Drivers/stc32g.h` 里的官方向量表显示:P3 端口中断的真实硬件向量号是 **40**(地址 `0x000143`,`P3INT_VECTOR`),而 [App/main.c](App/main.c) 里 `Port3_Isr` 声明的是 `interrupt 16`,对应的其实是向量 16 号 `INT4_VECTOR`(地址 `0x000083`,"外部中断 4",与 P3 端口毫无关系)。
|
||||||
|
* 向量号 40 超出 Keil C251 `interrupt` 关键字原生支持的 0~31 范围,跟 Port0(37 号)、Port2(39 号)一样,必须靠 [App/isr_jump.asm](App/isr_jump.asm) 里的汇编跳转表把硬件真实向量地址重定向到 C 代码能声明的低位向量——但这个文件里**只有 Port0、Port2 的重定向,Port3 这一条从建立以来就一直缺失**。
|
||||||
|
* 更严重的是,进一步检查发现 **`isr_jump.asm` 这个文件本身从未被加入 `wristband.uvproj` 工程**,也就是说不止 Port3,Port0/Port2 的重定向指令这么多年也从未被真正汇编链接进过固件——这是这份代码库里一直存在、此前从未暴露的问题。
|
||||||
|
* 之所以此前测试(尤其是低功耗休眠唤醒功能)没有暴露这个问题,推测是因为 P0/P2/P3 的中断使能窗口一直很短(仅在睡眠前临时打开、触发一次就自行关闭),硬件的"从掉电模式被中断请求唤醒"这个动作可能不需要真正完成向量跳转就能生效,狭窄的时间窗口侥幸没有触发跳转到未写入指令的地址。本次工单把 P3.6 的中断改为开机后永久常开、且芯片持续真实接收产生大量边沿,第一次让这个跳转被稳定触发,问题才暴露出来。
|
||||||
|
* **修复**(两处缺一不可):
|
||||||
|
1. 在 `App/isr_jump.asm` 里补上 Port3 的重定向条目:`CSEG AT 000143H` → `LJMP 000083H`(跳转到 `interrupt 16` 即 `Port3_Isr` 的入口)。
|
||||||
|
2. 把 `App/isr_jump.asm` 加入 `wristband.uvproj` 工程文件(`FileType=2`,汇编源文件),确保它真正参与编译链接。
|
||||||
|
* 修复后,2.6 节里为排查而加的调试代码(`RF_HandleEdgeInterrupt` 开头的 `return;`、注释掉的 `P3INTE |= 0x40;`)均已撤销,恢复正常逻辑。
|
||||||
|
|
||||||
|
### 2.6 实机验证过程记录:P3.6 悬空猜想(已被 2.7 节推翻,仅作排查过程存档)
|
||||||
|
|
||||||
|
用户实测反馈:改动后开机背光闪一下就灭,按住开机键不放背光反复闪烁。排查确认根因:
|
||||||
|
|
||||||
|
* LR690L 芯片在 `SHUT=1`(休眠/发射时也会关断接收,见 `RF_SetMode(0)`/`RF_SetMode(2)`)期间,数据脚 (P3.6) 为高阻输出。原来的 P3.6 配置是纯高阻输入、**没有内部上拉**。
|
||||||
|
* 开机时射频芯片默认关闭 (`RF_Init()` 里 `SHUT=1`),要到 `AppManager_StartApp(APP_ID_CLOCK)` 里才会调用 `RF_SetMode(1)` 真正打开接收——这段时间 P3.6 处于悬空状态,容易受旁边射频电路干扰而电平乱跳。
|
||||||
|
* 旧版设计里 P3.6 中断只在临睡前临时开启且触发一次自动关闭,悬空乱跳最多误触发一次,代价很小;本次改造把它变成开机后永久常开、不再自动关闭,悬空噪声在 `EA=1` 之后会引发中断风暴,CPU 完全无法执行主循环,导致开机画面卡在初始化那一下就再也画不出时钟界面。
|
||||||
|
* **修复**:给 P3.6 加内部上拉 (`P3PU |= 0x40;`),在悬空 (芯片输出高阻) 时把电平钳在稳定的高电平,不再乱跳;芯片主动驱动数据时上拉阻值足够弱,不影响正常接收。
|
||||||
|
- `Drivers/rf.c` 的 `RF_Init()` 中新增。
|
||||||
|
- `App/system.c` 的 `Wakeup_Restore()` 中新增恢复 (因为 `Enter_Low_Power_Sleep()` 休眠前为了省电特意把这个上拉关掉了,唤醒后要重新打开)。
|
||||||
|
|
||||||
|
### 2.8 对照实验:轮询解码 + 中断纯计数,验证毛刺/降噪分析是否成立
|
||||||
|
|
||||||
|
背景:中断增量解码状态机(2.2 节设计)接入真机后,`sync_seen` 长期为 0 或解码一直失败;调整"毛刺过滤下限" (`RF_GLITCH_MIN_TICKS`) 从 10us 逐步提高到 100us 的过程中,`ss_dur_max`/`ss_high_max` 等诊断量时而逼近理论值、时而超出合法区间,未能收敛到稳定结论。用户明确要求停止继续盲目调参,并提出一个可以直接证伪/证实"毛刺导致解码状态机误判"这一假设的对照实验:
|
||||||
|
|
||||||
|
* 解码逻辑临时改回 commit `d8df5d4` 已验证长期稳定工作的阻塞轮询实现 (`GetPulseDuration()` + `EV1527_Decode()`,算法/阈值原样保留)。
|
||||||
|
* P3.6 边沿中断 (`RF_HandleEdgeInterrupt()`) 同时保持使能,但只做纯计数 (`dbg_isr_edge_count++`),不参与任何解码判断。
|
||||||
|
* 轮询解码一旦成功,立即打印本次解码尝试开始前、成功那一刻的中断边沿计数快照及差值 (`[RF-ISR-CHECK] edges_before=.. edges_after=.. delta=..`),与 EV1527 协议一帧的理论边沿数 (同步头 2 个 + 24 位数据每位 2 个 = 50) 比对。
|
||||||
|
* 若 `delta` 稳定等于 50 (或在合理误差范围内):说明真实硬件产生的边沿是干净、可信的,此前中断状态机解码失败的原因不在"信号本身有毛刺",而在状态机代码逻辑本身;应转向复查状态机实现而非继续调整过滤阈值。
|
||||||
|
* 若 `delta` 明显偏离 50 (尤其是远大于 50):则支持此前"存在真实高频毛刺/回响,被边沿中断如实捕获,但被轮询的每次循环开销意外滤除"的分析,说明中断驱动方案需要额外做硬件或软件去抖,才能达到轮询版本的可靠性。
|
||||||
|
|
||||||
|
改动仅限 `Drivers/rf.c`(`Drivers/rf.h` 同步更新函数注释):
|
||||||
|
|
||||||
|
* `GetPulseDuration()`:算法/阈值与原始版本一致,但测量方式改为对 `Drivers/timer.c` 提供的自由运行 `Timer0_GetTimestamp()` 做快照差值,而不是原始版本那样直接停表/清零/重启 `TR0`——因为现在 Timer0 已经是 `RF_Delay_us()`(TX 发射计时) 共用的自由运行时间戳源,若在这里停表清零会导致解码尝试之后的 TX 发射永远等不到时间戳前进而卡死。这是相对原始实现唯一的必要改动,不影响被验证的解码算法本身。
|
||||||
|
* `EV1527_Decode()`:算法与原始版本一致,新增开始前/成功后的边沿计数快照打印。
|
||||||
|
* `RF_HandleEdgeInterrupt()`:从三态状态机简化为纯计数器 (`dbg_isr_edge_count++`)。
|
||||||
|
* `RF_PrintDebugCounters()`:简化为只打印 `isr_edge_count`。
|
||||||
|
* 本实验阶段之前为诊断中断状态机新增的大量 `dbg_*` 计数器/`RF_CheckDecodeWatchdog`/状态机相关代码已随之整体移除;若后续证实需要回到中断驱动方案,需要从 git 历史重新引入或重新设计,不建议直接依赖本次删除前的调参结果。
|
||||||
|
|
||||||
|
状态:代码已完成,待实机烧录,触发一次真实发射并观察 `[RF-ISR-CHECK]` 日志的 `delta` 是否稳定等于 50。
|
||||||
|
|
||||||
|
**实测结果**:连续 4 次独立发射,`delta` 稳定在 6963~7043 之间(约理论值 50 的 140 倍,四次误差仅约 1%),远超"偶发毛刺"能解释的量级,且波动极小,不像随机噪声,更像由代码本身某个确定性行为决定。用户随后用示波器直接实测 P3.6 波形,**确认没有对应的高频噪声/振铃**,彻底排除了 2.6/2.7 节以来"信号有毛刺、需要软件去抖"的整个分析方向。
|
||||||
|
|
||||||
|
### 2.9 真正根因定位:`PxIM0/PxIM1` 没有"双边沿触发"模式,`(1,1)` 实际是"高电平中断"
|
||||||
|
|
||||||
|
在排除毛刺假设后,用户质疑"会不会中断接错了引脚"。核对 `App/isr_jump.asm`/`P3IM0`/`P3IM1`/`P3INTE` 全仓库唯一赋值点、原理图 Pin19=P3.6=RF_RX_DATA 网络标签,确认引脚本身没有接错。随后查阅《STC32G 系列技术手册》"15.1.3 端口中断模式配置寄存器 (PxIM0,PxIM1)"真值表,发现关键事实:
|
||||||
|
|
||||||
|
| PnIM1.x | PnIM0.x | 中断模式 | 掉电唤醒支持 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0 | 0 | 下降沿中断 | 支持 |
|
||||||
|
| 0 | 1 | 上升沿中断 | 支持 |
|
||||||
|
| 1 | 0 | 低电平中断 | 不支持 |
|
||||||
|
| 1 | 1 | 高电平中断 | 不支持 |
|
||||||
|
|
||||||
|
这颗芯片的端口中断硬件**只有下降沿/上升沿/低电平/高电平四选一,根本没有"双边沿都触发"这个模式**。而 `RF_Init()` 里一直写的是 `P3IM1|=0x40; P3IM0|=0x40;`——对照表格,这实际配置的是**高电平中断**,跟代码注释"双边沿触发"完全对不上:只要 P3.6 处于高电平,中断就会在整段高电平期间被硬件持续、大量地重新触发,这才是边沿计数远超协议理论值(50)、且四次测量高度稳定(由代码执行速度决定,而非随机噪声)的真正原因,从一开始就与信号质量无关。用户随后做了接地实验直接验证:P3.6 直接短接到地,`isr_edge_count` 恒为 0(满足不了"高电平"这个触发条件);正常收码时该值持续变化——与"高电平中断"模型完全吻合。
|
||||||
|
|
||||||
|
同时发现一个关联问题:`P3.6` 还兼职做掉电唤醒源,而上表显示"高电平中断"**不支持掉电唤醒**,说明之前 `P3.6` 唤醒功能大概率也没有真正按预期工作。
|
||||||
|
|
||||||
|
### 2.10 修复:软件乒乓单边沿切换模拟双边沿触发 + P3.6 内部下拉
|
||||||
|
|
||||||
|
由于硬件不提供真正的双边沿模式,改为标准的软件模拟方案:`RF_Init()` 里初始配置成单一边沿(上升沿)触发;`RF_HandleEdgeInterrupt()` 每次触发后,先根据触发前的 `P3IM0` 值判断刚刚发生的是上升沿还是下降沿(乒乓切换保证了高/低电平段严格交替出现,不需要再读引脚电平猜测方向),再立即把 `P3IM0` 翻转到相反边沿,为下一次真实翻转做准备。这样每次真实电平翻转都会精确触发一次中断,不多不少,不再需要任何毛刺过滤器。
|
||||||
|
|
||||||
|
顺带修正一个关联的悬空钳位问题:`P3.6` 内部上拉 (`P3PU`) 改为内部下拉 (`P3PD`),在 `RF_Init()` 里一次性永久开启——`SHUT=1`(芯片关断)时 `DATA` 脚高阻悬空,配合"高电平中断"误配置,之前的上拉方案正好会把悬空态钳在会触发中断的那一侧,是当年开机中断风暴/背光闪烁问题的更深层成因;改用下拉后悬空态落在"不触发"一侧。同时清理了 `App/system.c` 里 `Wakeup_Restore()` 遗留的、每次唤醒都重新打开 P3.6 上拉的代码——留着会导致上拉和下拉同时使能、相互打架,现已移除(下拉在 `RF_Init()` 一次性配置好,Power Down 期间 SFR 状态不丢失,休眠/唤醒不需要重新配置)。
|
||||||
|
|
||||||
|
改动范围:`Drivers/rf.c`(重写 `RF_Init` 引脚上下拉与初始边沿配置、重写 `RF_HandleEdgeInterrupt` 为乒乓单边沿版本、恢复三态增量解码状态机 `RF_HandleHighSegmentEnd`/`RF_HandleLowSegmentEnd`、`RF_CheckDecodeWatchdog`、非阻塞 `EV1527_Decode()`,移除本次调试实验用的 `GetPulseDuration`/阻塞轮询版 `EV1527_Decode`)、`Drivers/rf.h`(同步更新函数注释)、`App/main.c`(`Port3_Isr` 注释同步)、`App/system.c`(`Wakeup_Restore()` 移除上拉恢复)。
|
||||||
|
|
||||||
|
状态:**实机验证通过**。连续多次真实发射(Data=08/04/02/01,对应遥控器不同按键)均被稳定解码,`[RF_Monitor] Decoded Addr=00037A86, Data=xx` 持续正确输出,未见中断风暴或崩溃。至此阶段 2(RF 接收中断化改造)的核心解码问题已定位并修复完成。
|
||||||
|
|
||||||
|
## 六、阶段 1 实现备注
|
||||||
|
|
||||||
|
* 语言字节与传感器数据库共用同一 Flash 扇区(偏移地址为 `sizeof(sensor_list)`,当前为 352,扇区剩余空间足够),复用现有 `Load_Database`/`Save_Database`,未新增独立扇区。
|
||||||
|
* 未做二级选择子菜单:主菜单第 1 项选中"语言"时直接按■原地切换 FR/EN,立即保存并用新语言重绘当前菜单。
|
||||||
|
* 首次开机(Flash 从未写入过语言字节,读到擦除态 `0xFF`)时 `Lang_Init` 会自动回退为 `LANG_FR`,不会出现非法枚举值。
|
||||||
|
* 本阶段仅替换界面文案与新增语言状态管理,未改动任何按键状态机、事件队列或射频协议相关代码逻辑。
|
||||||
|
* 尚未实机编译烧录验证,第四节验收标准仍需在真实硬件上逐项确认。
|
||||||
82
work-order/wo-05
Normal file
82
work-order/wo-05
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# 工单 WO-05:细节问题修复清单
|
||||||
|
|
||||||
|
**优先级**:中
|
||||||
|
**状态**:代码已完成 (6 项全部实现),待实机烧录逐项验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题列表
|
||||||
|
|
||||||
|
1. 开机启动瞬间背光应保持关闭,等所有初始化都完成、即将进入 `while(1)` 主循环前才点亮,避免开机闪烁。
|
||||||
|
2. 长按 KEY_CONFIRM 打开主菜单后,10 秒内无任何按键操作,自动关闭菜单返回时钟界面。
|
||||||
|
3. 按 App 重新梳理菜单导航结构:menu_app 只保留 4 个入口(语言开关内置于 menu_app,其余 3 项各自跳转到独立的 App),每个子 App 统一用长按 CONFIRM 退出、回到自己的父 App,而不是像现在这样有的退回上一级、有的直接跳回主菜单/首页,规则不统一。
|
||||||
|
4. 默认菜单语言改为英文(目前首次开机/Flash 擦除态默认是法语)。
|
||||||
|
5. 主菜单上下键切换菜单项允许循环切换(到顶/底后可以绕到另一端)。
|
||||||
|
6. P3.6 进入休眠前改成电平触发方式作为唤醒源,现在经常唤醒不了处理器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现思路
|
||||||
|
|
||||||
|
### 1. 背光延迟点亮
|
||||||
|
|
||||||
|
- `Drivers/lcd.c` 的 `LCD_Init()` 第 89 行 `SGM_CTRL = 0;`(点亮背光)在整个 ST7789V 寄存器初始化序列**最开头**就执行了,这就是闪烁的直接原因——去掉这一行(或改成 `SGM_CTRL = 1` 熄灭)。
|
||||||
|
- `App/system.c` 的 `GPIO_Init()` 只把 P1.7 配置成推挽输出,没有写初始电平,上电瞬间电平不确定;补一行 `SGM_CTRL = 1;`(熄灭),让背光从上电第一刻就确定是关的。
|
||||||
|
- `App/main.c` 的 `main()` 里,在 `AppManager_StartApp(APP_ID_HOME)`(画出第一帧时钟界面,若第 3 条改名重构先落地)之后、`while(1)` 之前,加一行 `SGM_CTRL = 0;` 点亮背光。
|
||||||
|
|
||||||
|
### 2. 菜单 10 秒无操作自动关闭
|
||||||
|
|
||||||
|
- 现有的 `inactivity_timer`(`App/main.c`)是全局 60 秒深度休眠计时,粒度和用途都不一样,不能直接复用。
|
||||||
|
- 在 `App/app_menu.c` 里新增一个 `static u16 menu_idle_enter_ms`,仿照 `Drivers/rf.c` 的 `RF_CheckDecodeWatchdog()`/`app_menu.c` 里已有的 `last_bind_tx_ms` 那种 `ms_tick` 环绕安全减法模式(`ms_tick` 是 0~999 循环的 1ms 计时)。
|
||||||
|
- `MenuApp_onEvent()` 每收到一次按键事件就刷新这个时间戳。
|
||||||
|
- 需要一个周期性检查点:目前 AppManager 框架没有 `OnTick`/`OnUpdate` 回调,需要确认要不要新增一个,或者简单地在 `main.c` 的 `while(1)` 里,当前台 App 是 `menu_app` 时调用一个新增的 `MenuApp_CheckIdleTimeout()`。超时后调用 `AppManager_SwitchToForeground(&home_app)`(若第 3 条改名重构先落地,否则仍是 `&clock_app`)。
|
||||||
|
|
||||||
|
### 3. 按 App 重新梳理菜单导航结构
|
||||||
|
|
||||||
|
**现状问题**:`app_menu.c` 用一个 `menu_level`(0/1/2)在单个 App 内部模拟出三层菜单,其中 `menu_level=1` 短按 CONFIRM 会切换到独立的 `pair_app`;`pair_app` 退出时又统一调用 `AppManager_SwitchToForeground(&menu_app)`,而 `MenuApp_OnStart()`/`MenuApp_onClose()` 都会无条件把 `menu_level` 清零——导致不管从哪个子菜单退出,最后都被强制拉回顶层主菜单,退出规则不统一,也不符合"退出到上一级"的预期。时间设置(`Start_Time_Edit()`)则是另一套模式:混在 `clock_app` 内部用 `time_edit_active` 标志位实现,进入时还要先 `AppManager_SwitchToForeground(&clock_app)` 闪一下正常时钟画面再叠加编辑界面,也是一处潜在的画面闪烁点。
|
||||||
|
|
||||||
|
**改造方向 —— 完全去掉 `menu_level` 这个概念,统一按 App 组织,父子关系一致、退出手势一致**:`clock_app` 已经确认是开机默认显示的 App(`App/main.c:280` 开机调用 `AppManager_StartApp(APP_ID_CLOCK)`),直接更名为 `home_app`;从中新建一个专门的 `set_clock_app` 承载时间设置。
|
||||||
|
|
||||||
|
| App | 现状 | 改造后 |
|
||||||
|
|---|---|---|
|
||||||
|
| `home_app`(时钟主页) | 即现有 `clock_app`(`App/app_clock.c`),兼管时间编辑(`time_edit_active`) | 整体更名:文件 `App/app_clock.c`→`App/app_home.c`,结构体实例 `clock_app`→`home_app`,`APP_ID_CLOCK`→`APP_ID_HOME`,函数前缀 `ClockApp_*`→`HomeApp_*`;剥离时间编辑逻辑,只保留待机时钟显示与布撤防 |
|
||||||
|
| `menu_app`(主菜单) | 内部 `menu_level` 0/1/2 三态,语言开关 + 传感器子菜单 + 摄像头绑定全挤在一个 App 里 | 不再有 `menu_level`,只保留顶层 4 项列表(`menu_select` 0~3,语言开关内置处理,不跳转);其余 3 项短按 CONFIRM 各自切到独立 App |
|
||||||
|
| `set_clock_app`(时间设置) | 不是独立 App,是 `clock_app` 内的一个模式标志(`time_edit_active`) | 新建独立 App(`App/app_set_clock.c`),从 `app_clock.c` 抽出 `Start_Time_Edit`/`time_edit_*` 相关代码 |
|
||||||
|
| `pair_app`(传感器配对) | 已经是独立 App,但传感器类型选择这一步(门磁/PIR/烟感/气感/水浸)现在留在 `menu_app` 的 `menu_level=1` 里,选完才切到 `pair_app` 开始搜索 | 把类型选择合并成 `pair_app` 的新初始状态(比如 `PAIR_STATE_SELECT_TYPE`,排在现有 `PAIR_STATE_WAIT` 之前),`menu_app` 上"传感器配对"这一项短按 CONFIRM 直接整体切到 `pair_app`;`pair_app` 不管在哪个状态退出,都统一回到 `menu_app` |
|
||||||
|
| `ipc_bind_app`(摄像头绑定) | 不是独立 App,是 `menu_app` 内 `menu_level=2` 的一段代码(呼吸灯+定时发射绑定帧) | 新建独立 App(`App/app_ipc_bind.c`),仿照 `pair_app` 的结构抽出来 |
|
||||||
|
|
||||||
|
**统一导航规则**:
|
||||||
|
- `home_app` 长按 CONFIRM → 进入 `menu_app`。
|
||||||
|
- `menu_app` 长按 CONFIRM → 回到 `home_app`(现有行为,不变,只是改了名字)。
|
||||||
|
- `menu_app` 顶层短按 CONFIRM 选中某一项 → 进入对应子 App(`set_clock_app`/`pair_app`/`ipc_bind_app`)。
|
||||||
|
- 三个子 App 无论是长按 CONFIRM 主动退出、还是完成/超时自动退出(`pair_app` 的成功/失败/取消),都统一 → 回到 `menu_app`(它们共同的父 App),不再有"直接跳回首页"或"退到某个具体层级"的特殊情况。
|
||||||
|
|
||||||
|
**需要新增/修改的文件**:
|
||||||
|
- `App/app_clock.c` → 更名为 `App/app_home.c`:`clock_app`→`home_app`、`APP_ID_CLOCK`→`APP_ID_HOME`、`ClockApp_*`→`HomeApp_*` 函数全部改名;去掉时间编辑相关代码(`time_edit_active`/`time_edit_field`/`time_edit_blink_on`/`Start_Time_Edit`),长按 CONFIRM 改为切到 `menu_app`(不变)。
|
||||||
|
- 新建 `App/app_set_clock.c`:承接从 `app_clock.c` 抽出来的时间编辑逻辑,退出(短按 CONFIRM 保存 或 长按退出)统一改为 `AppManager_SwitchToForeground(&menu_app)`。
|
||||||
|
- 新建 `App/app_ipc_bind.c`:把 `app_menu.c` 里 `menu_level==2` 的呼吸灯效果、`EV1527_Transmit` 绑定广播、`last_bind_tx_ms` 计时都搬过去,退出统一改为 `AppManager_SwitchToForeground(&menu_app)`。
|
||||||
|
- 精简 `App/app_menu.c`:彻底去掉 `menu_level` 变量和三态分支,只保留 `menu_select` 这一个 0~3 的顶层索引;短按 CONFIRM 按 `menu_select` 分支跳转到三个子 App 或原地切语言。
|
||||||
|
- `App/app_manager.h`(或对应头文件):`APP_ID_CLOCK`→`APP_ID_HOME`,新增 `APP_ID_SET_CLOCK`/`APP_ID_IPC_BIND`;`extern WristbandApp clock_app` 等声明一并改名/新增。
|
||||||
|
- 全仓库搜索 `clock_app`/`APP_ID_CLOCK` 的引用点(至少 `App/main.c`、`App/app_menu.c`、`App/app_pair.c`、`App/event.c` 中出现过)统一改名,避免漏改。
|
||||||
|
- `wristband.uvproj`:文件改名(`app_clock.c`→`app_home.c`)与两个新文件(`app_set_clock.c`、`app_ipc_bind.c`)都要同步维护工程文件列表,不能像之前 `isr_jump.asm` 那样漏加。
|
||||||
|
|
||||||
|
**待确认的行为变化**:现在"时间设置"短按 CONFIRM 保存后,是直接回到时钟主页;改造后按统一规则会回到 `menu_app`(主菜单)而不是直接回时钟——这是一个和现状不一样的地方,需要确认是否符合预期,还是希望 `set_clock_app` 作为特例、保存后也直接回 `home_app`。
|
||||||
|
|
||||||
|
### 4. 默认语言改英文
|
||||||
|
|
||||||
|
- `App/language.c`:`static Language current_lang = LANG_FR;` 改成 `LANG_EN`。
|
||||||
|
- 同文件 `Lang_Init()` 里的越界回退分支 `(lang < LANG_COUNT) ? lang : LANG_FR` 改成 `: LANG_EN`。
|
||||||
|
- 两处都要改,否则开机静态初始值和 Flash 擦除态回退值会不一致。
|
||||||
|
|
||||||
|
### 5. 菜单上下循环切换
|
||||||
|
|
||||||
|
- 涉及两处 UP/DOWN 边界判断(若第 3 条的 App 化重构先落地,第二处会搬到 `pair_app` 里,但改法一样):
|
||||||
|
- `App/app_menu.c` 顶层菜单(现第 106-121 行,4 项,索引 0~3)
|
||||||
|
- 传感器类型选择(现在是 `app_menu.c` 的 `menu_level==1`,第 158-172 行;重构后是 `pair_app` 的 `PAIR_STATE_SELECT_TYPE`,5 项,索引 0~4)
|
||||||
|
- 现在是 `if (menu_select > 0) menu_select--;` / `if (menu_select < MAX) menu_select++;`,到边界就不动;改成到边界时绕到另一端,例如 UP 在 `menu_select==0` 时跳到 `MAX`,DOWN 在 `menu_select==MAX` 时跳回 `0`。
|
||||||
|
|
||||||
|
### 6. P3.6 休眠前唤醒触发方式修复
|
||||||
|
|
||||||
|
- 原因:`RF_HandleEdgeInterrupt()` 用软件乒乓单边沿切换模拟双边沿触发,`P3IM0` 会在上升沿/下降沿之间动态翻转;`Enter_Low_Power_Sleep()` 进入休眠前完全没有重新设置 `P3IM0`/`P3IM1`,导致进入 Power Down 那一刻 `P3IM0` 具体停在哪个方向是不确定的,如果停在"等下降沿"而实际先来的是上升沿,就可能唤醒不了或唤醒延迟。
|
||||||
|
- **已实现方案与最初需求不同,供确认**:根据 WO-04 §2.9 查到的《STC32G 技术手册》15.1.3 节真值表,`PxIM1/PxIM0` 的"高电平中断"和"低电平中断"两种模式**都标注为不支持掉电唤醒**,只有下降沿/上升沿两种边沿模式支持。既然手册明确说电平模式不支持掉电唤醒,改成电平触发大概率解决不了"唤醒不了"的问题,所以最终没有采用"改成电平触发",而是实现了手册认可的方案:`App/system.c` 的 `Enter_Low_Power_Sleep()` 里,`P3WKUE |= 0x40;` 之前,把 `P3IM1`/`P3IM0` 强制固定为上升沿 (`P3IM1&=~0x40; P3IM0|=0x40;`,与 `RF_Init()` 开机初始配置一致),不再依赖乒乓切换留下的随机状态。`Wakeup_Restore()` 不需要改动,因为固定后的值就是开机时的初始值,唤醒后解码状态机自然从已知状态开始。
|
||||||
|
- 状态:代码已完成,待实机验证休眠后 P3.6 (RF 信号) 能否稳定唤醒;如果实测仍不能唤醒,再考虑真的去试电平触发(即便手册说不支持)。
|
||||||
21
work-order/wo-06
Normal file
21
work-order/wo-06
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# 工单 WO-06:时间设置界面整屏闪烁改成局部刷新
|
||||||
|
|
||||||
|
**优先级**:中
|
||||||
|
**状态**:代码已完成,待实机验证只有数字局部闪烁、其它元素不受影响
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题列表
|
||||||
|
|
||||||
|
1. 时间设置界面(`set_clock_app`)调整小时/分钟时的闪烁效果,现在是每次闪烁状态翻转就整屏 `LCD_Clear()` 再全部重画,视觉上是整屏黑一下亮一下,而不是只有正在调节的那个数字在闪。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现思路
|
||||||
|
|
||||||
|
- 根因:`App/ui.c` 的 `UI_ShowSetTimePage()` 函数开头无条件 `LCD_Clear(COLOR_BLACK)`,然后重画锁头图标、电池、小时、冒号、分钟、AM/PM、日期全部元素;而 `App/app_set_clock.c` 的 `SetClockApp_onRun()` 每 500ms 闪烁状态翻转一次就整个调用这个函数。
|
||||||
|
- 改法:拆成两个函数——
|
||||||
|
- 一个"整页绘制"函数,只在真正进入时间设置界面(`SetClockApp_OnStart()`)或调节字段切换(长按 CONFIRM 切换 小时/分钟 焦点)时调用一次,负责画锁头、电池、冒号、AM/PM、日期这些不变的静态内容,以及当前的小时、分钟数字。
|
||||||
|
- 一个"局部刷新"函数,只负责重画正在调节的那个数字所在的矩形区域(先 `LCD_FillRect` 成黑色,再按闪烁状态决定是否写回数字),`SetClockApp_onRun()` 里每次闪烁翻转只调用这个局部刷新函数,不再动其它区域。
|
||||||
|
- 需要确定小时/分钟数字在屏幕上的准确坐标和尺寸(参照现有 `LCD_ShowString16x32(23, 100, ...)` / `LCD_ShowString16x32(75, 100, ...)` 调用,16x32 字体两位数字宽度为 32px),局部刷新时精确擦除这一块,不多不少。
|
||||||
|
- 涉及文件:`App/ui.c`(拆分 `UI_ShowSetTimePage`)、`App/ui.h`(新增局部刷新函数声明)、`App/app_set_clock.c`(`OnStart`/字段切换调用整页绘制,`onRun` 调用局部刷新)。
|
||||||
50
work-order/wo-07
Normal file
50
work-order/wo-07
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# 工单 WO-07:完善日期功能 (年/月/日/星期)
|
||||||
|
|
||||||
|
**优先级**:中
|
||||||
|
**状态**:待处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题列表
|
||||||
|
|
||||||
|
1. 软时钟(`App/clock.c`)只有时/分/秒,没有年、月、日、星期。
|
||||||
|
2. `PCF8563_ReadTime`/`PCF8563_WriteTime` 只读写时分秒三个寄存器,芯片本身支持的日/星期/月/年寄存器(0x05~0x08)从没被用过。
|
||||||
|
3. `set_clock_app` 只能调小时和分钟,没有日期可调。
|
||||||
|
4. HOME APP 和时间设置界面上显示的"星期 日-月"是写死的占位字符串 `"VEN 10-07"`/`"FRI 10-07"`,不是真实日期。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现思路
|
||||||
|
|
||||||
|
### 1. 驱动层:`Drivers/pcf8563.c`/`.h`
|
||||||
|
|
||||||
|
PCF8563 的秒/分/时/日/星期/月/年寄存器地址是连续的(0x02~0x08,共 7 字节),可以一次 I2C 连续读写全部拿到/写入,不用分开多次收发:
|
||||||
|
|
||||||
|
- 新增 `PCF8563_ReadDateTime(u8 *hour, u8 *min, u8 *sec, u8 *day, u8 *month, u8 *year)`:从 0x02 开始连续读 7 字节,`day` 掩码 `0x3F`(BCD),`month` 掩码 `0x1F`(BCD,忽略 bit7 世纪位),`year` 直接 BCD 转十进制(0~99,代表 2000~2099)。
|
||||||
|
- 新增 `PCF8563_WriteDateTime(u8 hour, u8 min, u8 sec, u8 day, u8 month, u8 year, u8 weekday)`:从 0x02 开始连续写 7 字节,包含星期(0x06,取值 0~6,不需要 BCD 转换,直接写数值)。
|
||||||
|
- 旧的 `PCF8563_ReadTime`/`PCF8563_WriteTime` 保留,内部改成调用新函数并丢弃日期部分,避免改动现有调用点(`app_home.c`/`app_set_clock.c`)。
|
||||||
|
- 世纪位(月寄存器 bit7)不处理,固定按 2000~2099 年处理,这个产品的使用年限内不用考虑跨世纪。
|
||||||
|
|
||||||
|
### 2. 软时钟层:`App/clock.c`/`.h`
|
||||||
|
|
||||||
|
- 新增全局变量 `u8 current_day`、`u8 current_month`、`u8 current_year`(0~99,代表 2000~2099)、`u8 current_weekday`(0~6,周日=0 或周一=0,需要定一个约定,建议跟 Zeller 公式的定义保持一致)。
|
||||||
|
- `Clock_IncMS()` 现有的时/分/小时进位链条(秒→分→时)后面继续接上日/月/年进位:小时满 24 归零时,`current_day++`;根据"每月天数表"(`{31, 28或29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}`,2 月按闰年规则(能被 4 整除且(不能被 100 整除或能被 400 整除),2000~2099 区间内简化成"能被 4 整除"即可)判断是否要月进位、年进位(99 → 0,不处理世纪)。
|
||||||
|
- 日期发生变化(不管是自然进位还是手动调节)时,用 Zeller 公式或类似算法重新计算 `current_weekday`,不依赖 PCF8563 自己的星期寄存器自增(避免手动改日期后星期跟着错乱);写入 RTC 时把算出来的星期一起写进去,保持芯片内部数据一致。
|
||||||
|
- `HomeApp_OnStart()`/相关读取 RTC 的地方,从 `PCF8563_ReadTime` 换成 `PCF8563_ReadDateTime`,一并读回日期。
|
||||||
|
|
||||||
|
### 3. 界面层:`App/ui.c`/`.h`
|
||||||
|
|
||||||
|
- 新增星期缩写字符串表(法语/英语),参照现有占位文案风格:`LUN/MAR/MER/JEU/VEN/SAM/DIM` 对应 `MON/TUE/WED/THU/FRI/SAT/SUN`,放进 `App/strings.h` 的 `WRISTBAND_STRING_LIST`。
|
||||||
|
- `UI_ShowClockPage()`/`UI_ShowSetTimePage()` 里原来直接 `Lang_Get(STR_CLOCK_DATE)` 显示死文案的地方,改成拼接"星期缩写 + 日-月两位数字"的动态字符串,用真实的 `current_weekday`/`current_day`/`current_month` 现算现拼。
|
||||||
|
|
||||||
|
### 4. 时间设置 App:`App/app_set_clock.c`
|
||||||
|
|
||||||
|
- `time_edit_field` 从 1~2(时/分)扩展到 1~5(时/分/日/月/年),长按 CONFIRM 依次循环切换。
|
||||||
|
- 上下键调节日/月/年时要做合理的范围限制和循环(日 1~当月最大天数、月 1~12、年 0~99),月份变化时如果当前日超过新月份的最大天数要做截断处理(比如从 31 号切到 2 月要变成 28/29 号)。
|
||||||
|
- 局部刷新沿用 WO-06 刚做的模式,不要整屏重绘:日/月/年每个字段各自对应屏幕上一小块数字区域,只刷新当前正在调节的那一块。
|
||||||
|
- 保存(短按 CONFIRM)时用新的 `PCF8563_WriteDateTime()`,同时把算好的 `current_weekday` 一起写入。
|
||||||
|
|
||||||
|
### 5. 需要确认的点
|
||||||
|
|
||||||
|
- 星期数值约定(0=周日 还是 0=周一)以及对应的 Zeller 公式实现需要仔细算一遍避免算错,建议实现后用几个已知日期(比如今天)手动核对星期是否正确。
|
||||||
|
- 日期显示格式沿用现有"星期 日-月"(如 "MON 03-08")这种顺序,不显示年份(屏幕位置有限);如果需要年份显示,要另外找位置或者做成可选切换。
|
||||||
265
wristband.uvopt
265
wristband.uvopt
@@ -12,7 +12,7 @@
|
|||||||
<lExt>*.lib</lExt>
|
<lExt>*.lib</lExt>
|
||||||
<tExt>*.txt; *.h; *.inc; *.md</tExt>
|
<tExt>*.txt; *.h; *.inc; *.md</tExt>
|
||||||
<pExt>*.plm</pExt>
|
<pExt>*.plm</pExt>
|
||||||
<CppX>*.cpp</CppX>
|
<CppX>*.cpp; *.cc; *.cxx</CppX>
|
||||||
<nMigrate>0</nMigrate>
|
<nMigrate>0</nMigrate>
|
||||||
</Extensions>
|
</Extensions>
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<TargetOption>
|
<TargetOption>
|
||||||
<CLK251>35000000</CLK251>
|
<CLK251>35000000</CLK251>
|
||||||
<OPTTT>
|
<OPTTT>
|
||||||
<gFlags>1</gFlags>
|
<gFlags>0</gFlags>
|
||||||
<BeepAtEnd>1</BeepAtEnd>
|
<BeepAtEnd>1</BeepAtEnd>
|
||||||
<RunSim>1</RunSim>
|
<RunSim>1</RunSim>
|
||||||
<RunTarget>0</RunTarget>
|
<RunTarget>0</RunTarget>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<IsCurrentTarget>1</IsCurrentTarget>
|
<IsCurrentTarget>1</IsCurrentTarget>
|
||||||
</OPTFL>
|
</OPTFL>
|
||||||
<CpuCode>255</CpuCode>
|
<CpuCode>0</CpuCode>
|
||||||
<DebugOpt>
|
<DebugOpt>
|
||||||
<uSim>1</uSim>
|
<uSim>1</uSim>
|
||||||
<uTrg>0</uTrg>
|
<uTrg>0</uTrg>
|
||||||
@@ -116,13 +116,6 @@
|
|||||||
<tIfile></tIfile>
|
<tIfile></tIfile>
|
||||||
<pMon></pMon>
|
<pMon></pMon>
|
||||||
</DebugOpt>
|
</DebugOpt>
|
||||||
<TargetDriverDllRegistry>
|
|
||||||
<SetRegEntry>
|
|
||||||
<Number>0</Number>
|
|
||||||
<Key>DLGDC51</Key>
|
|
||||||
<Name>(94=-1,-1,-1,-1,0)(120=-1,-1,-1,-1,0)(119=-1,-1,-1,-1,0)(107=-1,-1,-1,-1,0)(103=-1,-1,-1,-1,0)(108=-1,-1,-1,-1,0)(114=-1,-1,-1,-1,0)(115=-1,-1,-1,-1,0)</Name>
|
|
||||||
</SetRegEntry>
|
|
||||||
</TargetDriverDllRegistry>
|
|
||||||
<Breakpoint/>
|
<Breakpoint/>
|
||||||
<Tracepoint>
|
<Tracepoint>
|
||||||
<THDelay>0</THDelay>
|
<THDelay>0</THDelay>
|
||||||
@@ -130,12 +123,12 @@
|
|||||||
<DebugFlag>
|
<DebugFlag>
|
||||||
<trace>0</trace>
|
<trace>0</trace>
|
||||||
<periodic>0</periodic>
|
<periodic>0</periodic>
|
||||||
<aLwin>1</aLwin>
|
<aLwin>0</aLwin>
|
||||||
<aCover>0</aCover>
|
<aCover>0</aCover>
|
||||||
<aSer1>0</aSer1>
|
<aSer1>0</aSer1>
|
||||||
<aSer2>0</aSer2>
|
<aSer2>0</aSer2>
|
||||||
<aPa>0</aPa>
|
<aPa>0</aPa>
|
||||||
<viewmode>1</viewmode>
|
<viewmode>0</viewmode>
|
||||||
<vrSel>0</vrSel>
|
<vrSel>0</vrSel>
|
||||||
<aSym>0</aSym>
|
<aSym>0</aSym>
|
||||||
<aTbox>0</aTbox>
|
<aTbox>0</aTbox>
|
||||||
@@ -171,7 +164,7 @@
|
|||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<GroupName>App</GroupName>
|
<GroupName>App</GroupName>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>1</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<cbSel>0</cbSel>
|
<cbSel>0</cbSel>
|
||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
@@ -187,17 +180,221 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<bShared>0</bShared>
|
<bShared>0</bShared>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>2</FileNumber>
|
||||||
|
<FileType>2</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\isr_jump.asm</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>isr_jump.asm</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>3</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\rgb.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>rgb.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>4</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\event.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>event.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>5</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_manager.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_manager.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>6</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\database.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>database.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>7</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\language.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>language.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>8</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\ui.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>ui.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>9</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\system.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>system.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>10</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\clock.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>clock.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>11</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_home.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_home.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>12</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_set_clock.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_set_clock.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>13</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_menu.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_menu.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>14</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_pair.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_pair.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>15</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_ipc_bind.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_ipc_bind.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>16</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_alarm.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_alarm.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>17</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_sos.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_sos.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>1</GroupNumber>
|
||||||
|
<FileNumber>18</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\App\app_rf_monitor.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>app_rf_monitor.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<GroupName>Drivers</GroupName>
|
<GroupName>Drivers</GroupName>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>1</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
<cbSel>0</cbSel>
|
<cbSel>0</cbSel>
|
||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>2</GroupNumber>
|
<GroupNumber>2</GroupNumber>
|
||||||
<FileNumber>2</FileNumber>
|
<FileNumber>19</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -209,7 +406,7 @@
|
|||||||
</File>
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<GroupNumber>2</GroupNumber>
|
<GroupNumber>2</GroupNumber>
|
||||||
<FileNumber>3</FileNumber>
|
<FileNumber>20</FileNumber>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<tvExp>0</tvExp>
|
<tvExp>0</tvExp>
|
||||||
<tvExpOptDlg>0</tvExpOptDlg>
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
@@ -219,6 +416,42 @@
|
|||||||
<RteFlg>0</RteFlg>
|
<RteFlg>0</RteFlg>
|
||||||
<bShared>0</bShared>
|
<bShared>0</bShared>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>2</GroupNumber>
|
||||||
|
<FileNumber>21</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\Drivers\timer.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>timer.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>2</GroupNumber>
|
||||||
|
<FileNumber>22</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\Drivers\adc.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>adc.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<GroupNumber>2</GroupNumber>
|
||||||
|
<FileNumber>23</FileNumber>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<tvExp>0</tvExp>
|
||||||
|
<tvExpOptDlg>0</tvExpOptDlg>
|
||||||
|
<bDave2>0</bDave2>
|
||||||
|
<PathWithFileName>.\Drivers\pcf8563.c</PathWithFileName>
|
||||||
|
<FilenameWithoutPath>pcf8563.c</FilenameWithoutPath>
|
||||||
|
<RteFlg>0</RteFlg>
|
||||||
|
<bShared>0</bShared>
|
||||||
|
</File>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
</ProjectOpt>
|
</ProjectOpt>
|
||||||
|
|||||||
@@ -329,6 +329,11 @@
|
|||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<FilePath>.\App\main.c</FilePath>
|
<FilePath>.\App\main.c</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>isr_jump.asm</FileName>
|
||||||
|
<FileType>2</FileType>
|
||||||
|
<FilePath>.\App\isr_jump.asm</FilePath>
|
||||||
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<FileName>rgb.c</FileName>
|
<FileName>rgb.c</FileName>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
@@ -349,6 +354,11 @@
|
|||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<FilePath>.\App\database.c</FilePath>
|
<FilePath>.\App\database.c</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>language.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\language.c</FilePath>
|
||||||
|
</File>
|
||||||
<File>
|
<File>
|
||||||
<FileName>ui.c</FileName>
|
<FileName>ui.c</FileName>
|
||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
@@ -359,6 +369,51 @@
|
|||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<FilePath>.\App\system.c</FilePath>
|
<FilePath>.\App\system.c</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>clock.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\clock.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_home.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_home.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_set_clock.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_set_clock.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_menu.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_menu.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_pair.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_pair.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_ipc_bind.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_ipc_bind.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_alarm.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_alarm.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_sos.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_sos.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>app_rf_monitor.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\App\app_rf_monitor.c</FilePath>
|
||||||
|
</File>
|
||||||
</Files>
|
</Files>
|
||||||
</Group>
|
</Group>
|
||||||
<Group>
|
<Group>
|
||||||
@@ -374,6 +429,21 @@
|
|||||||
<FileType>1</FileType>
|
<FileType>1</FileType>
|
||||||
<FilePath>.\Drivers\rf.c</FilePath>
|
<FilePath>.\Drivers\rf.c</FilePath>
|
||||||
</File>
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>timer.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\Drivers\timer.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>adc.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\Drivers\adc.c</FilePath>
|
||||||
|
</File>
|
||||||
|
<File>
|
||||||
|
<FileName>pcf8563.c</FileName>
|
||||||
|
<FileType>1</FileType>
|
||||||
|
<FilePath>.\Drivers\pcf8563.c</FilePath>
|
||||||
|
</File>
|
||||||
</Files>
|
</Files>
|
||||||
</Group>
|
</Group>
|
||||||
</Groups>
|
</Groups>
|
||||||
|
|||||||
Reference in New Issue
Block a user