This commit is contained in:
edisondeng
2026-07-31 13:57:06 +08:00
parent d8df5d49d3
commit a7a2cc9465
23 changed files with 902 additions and 328 deletions

View File

@@ -4,15 +4,17 @@
#include "rgb.h"
#include "event.h"
#include "system.h"
#include "database.h"
#include "language.h"
#include "../Drivers/rf.h"
// 二级传感器对码菜单中,各类传感器的法语显示名称定义
char *code menu_items[5] = {
"DETEC. PORTE", // 0: 门磁探测器 (法语: 门)
"DETEC. PIR", // 1: 红外探测器
"DETEC. FUMEE", // 2: 烟雾传感器
"DETEC. GAZ", // 3: 燃气探测器
"DETEC. EAU" // 4: 水浸探测器
// 二级传感器对码菜单中,各类传感器显示名称的字符串 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: 水浸探测器
};
// 外部全局变量引用
@@ -100,7 +102,7 @@ static EventResult MenuApp_onEvent(SystemEvent *evt)
{
if (menu_level == 0)
{
// ====== 处于一级主菜单层 (0-HORLOGE, 1-APPAIRAGE, 2-LIAISON) ======
// ====== 处于一级主菜单层 (0-语言切换, 1-HORLOGE, 2-APPAIRAGE, 3-LIAISON) ======
if (evt->key_event == KEY_EVENT_UP_CLICK) {
// 短按 ▲ 键向上移动焦点
if (menu_select > 0) {
@@ -111,7 +113,7 @@ static EventResult MenuApp_onEvent(SystemEvent *evt)
}
if (evt->key_event == KEY_EVENT_DOWN_CLICK) {
// 短按 ▼ 键向下移动焦点
if (menu_select < 2) {
if (menu_select < 3) {
menu_select++;
UI_ShowMainMenu(menu_select);
}
@@ -120,15 +122,22 @@ static EventResult MenuApp_onEvent(SystemEvent *evt)
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设置时间。启动待机时钟应用并调用微调编辑入口
AppManager_SwitchToForeground(&clock_app);
Start_Time_Edit();
} else if (menu_select == 1) {
} else if (menu_select == 2) {
// 功能 2传感器学习。进入二级传感器类别选择菜单
menu_level = 1;
menu_select = 0;
UI_ShowPairMenuPage(menu_select); // 绘制二级设备分类页
} else if (menu_select == 2) {
} else if (menu_select == 3) {
// 功能 3相机配对。进入摄像机一键绑定发射状态
menu_level = 2;
last_bind_tx_ms = ms_tick; // 记录初始发射时间
@@ -174,7 +183,7 @@ static EventResult MenuApp_onEvent(SystemEvent *evt)
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
// 长按 ▼ 键,从二级菜单退回到一级主菜单
menu_level = 0;
menu_select = 1; // 回到 APPAIRAGE 选项上
menu_select = 2; // 回到 APPAIRAGE 选项上
UI_ShowMainMenu(menu_select);
return EVENT_HANDLED;
}
@@ -185,7 +194,7 @@ static EventResult MenuApp_onEvent(SystemEvent *evt)
// 长按 ■ 确认键 或 长按 ▼ 键,均表示取消绑定,退出发射并返回一级主菜单
if (evt->key_event == KEY_EVENT_CONFIRM_LONG || evt->key_event == KEY_EVENT_DOWN_LONG) {
menu_level = 0;
menu_select = 2; // 回到 LIAISON 选项上
menu_select = 3; // 回到 LIAISON 选项上
current_state = STATE_PAIR_MENU;
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭呼吸灯
UI_ShowMainMenu(menu_select);

View File

@@ -146,7 +146,6 @@ extern u8 captured_type; // 正在配对的传感器类型
extern u8 alarm_sensor_slot; // 当前触发警报的传感器在数据库列表中的槽位索引
extern Sensor_Slot xdata sensor_list[16];// 传感器数据库数组 (存储在 xdata 区,共 16 个槽位)
extern char *code sensor_names_fr[5]; // 法语传感器设备类型默认名称字串数组
// 系统滴答与定时计数器 (均为 1ms 中断服务中更新)
extern volatile u16 ms_tick; // 毫秒滴答计数器 (0~999循环)

View File

@@ -1,19 +1,23 @@
#include "database.h"
#include "language.h"
// 内部 IAP 命令寄存器指令定义
#define IAP_CMD_READ 1 // IAP 字节读指令
#define IAP_CMD_WRITE 2 // IAP 字节写指令
#define IAP_CMD_ERASE 3 // IAP 扇区擦除指令
// 定义法语传感器默认名称常量数组,分别对应类型 0~4
char *code sensor_names_fr[5] = {
"PORTE", // 0: 门磁 (法语: 门)
"PIR", // 1: 红外探测器
"FUMEE", // 2: 烟雾传感器 (法语: 烟雾)
"GAZ", // 3: 燃气泄漏传感器 (法语: 燃气)
"EAU" // 4: 漏水/水浸传感器 (法语: 水)
// 传感器类型简称的字符串 ID分别对应类型 0~4实际文案由 Lang_Get() 按当前语言取出
const StringID sensor_type_name_ids[5] = {
STR_NAME_PORTE, // 0: 门磁
STR_NAME_PIR, // 1: 红外探测器
STR_NAME_FUMEE, // 2: 烟雾传感器
STR_NAME_GAZ, // 3: 燃气泄漏传感器
STR_NAME_EAU // 4: 漏水/水浸传感器
};
// 紧随 sensor_list 之后存放的 1 字节界面语言设置在 Flash 扇区内的偏移地址
#define LANGUAGE_FLASH_ADDR ((u16)sizeof(sensor_list))
/**
* @brief 关闭 IAP 寄存器控制并清理地址
* @details 每次 IAP 闪存操作后调用,确保安全,防止意外指针写入损坏 EEPROM 中的关键配置数据。
@@ -87,7 +91,8 @@ void IAP_EraseSector(u16 addr)
/**
* @brief 从 EEPROM 的扇区 254 中加载数据库
* @details 逐字节读取 Flash 物理数据,并直接通过指针拷贝装入 RAM 中的全局 `sensor_list` 数组
* @details 逐字节读取 Flash 物理数据,并直接通过指针拷贝装入 RAM 中的全局 `sensor_list` 数组
* 随后读取紧随其后的 1 字节界面语言设置,恢复上次保存的语言选择。
*/
void Load_Database(void)
{
@@ -95,15 +100,20 @@ void Load_Database(void)
u8 *ptr = (u8 *)&sensor_list; // 获取传感器 RAM 数组的首地址指针
u16 size = sizeof(sensor_list); // 获取结构体数组的全部占用尺寸
u16 i;
u8 lang_byte;
for (i = 0; i < size; i++) {
ptr[i] = IAP_ReadByte(addr++); // 循环读取字节并还原到 RAM
}
// 读取语言设置字节 (首次开机、从未保存过时 Flash 为擦除态 0xFFLang_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 内容。
* 接着遍历整个数据结构,逐字节写入最新的 RAM 内容,最后追加写入当前界面语言设置字节
*/
void Save_Database(void)
{
@@ -115,6 +125,9 @@ void Save_Database(void)
for (i = 0; i < size; i++) {
IAP_WriteByte(addr++, ptr[i]); // 循环写入字节数据
}
// 紧随传感器数据之后追加写入当前界面语言设置
IAP_WriteByte(LANGUAGE_FLASH_ADDR, (u8)Lang_Current());
}
/**
@@ -155,11 +168,14 @@ void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone)
sensor_list[slot_to_use].type = type; // 记录传感器硬件类型
sensor_list[slot_to_use].zone = zone; // 记录对应的防区号
// 生成该槽位传感器的法语名称
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; // 遇零终止
// 生成该槽位传感器的名称 (取当前界面语言下的简称)
{
const char *type_name = Lang_Get(sensor_type_name_ids[type]);
for (i = 0; i < 15; i++) {
// 从当前语言名称字符串中复制对应名字的字符
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'; // 尾部安全截断,防止字符越界

View File

@@ -2,6 +2,10 @@
#define __DATABASE_H__
#include "config.h"
#include "strings.h"
// 传感器类型简称的字符串 ID 表 (索引 0~4 对应门磁/PIR/烟感/燃气/水浸),供 UI 层按当前语言取用
extern const StringID sensor_type_name_ids[5];
/* ====== 底层 IAP (在应用编程 Flash) 读写驱动接口 ====== */
@@ -39,13 +43,15 @@ void IAP_EraseSector(u16 addr);
/**
* @brief 从 Flash 扇区 254 (0xFE0000) 载入全部传感器槽位配置至 RAM 变量
* @details 系统初始化时调用,将 EEPROM 中的传感器绑定数据拷贝到 RAM 中的 `sensor_list` 数组
* @details 系统初始化时调用,将 EEPROM 中的传感器绑定数据拷贝到 RAM 中的 `sensor_list` 数组
* 并读取紧随其后存放的 1 字节界面语言设置,调用 `Lang_Init()` 恢复上次选择的语言。
*/
void Load_Database(void);
/**
* @brief 将 RAM 中的全部传感器配置保存/写入到 Flash 扇区中
* @details 在完成对码、添加新传感器微调防区号后调用,该函数会先擦除第 254 扇区,然后逐字节写入当前最新的 `sensor_list` 数组。
* @details 在完成对码、添加新传感器微调防区号或切换界面语言后调用,该函数会先擦除第 254 扇区,
* 然后逐字节写入当前最新的 `sensor_list` 数组,并在其后追加写入当前界面语言 (`Lang_Current()`)。
*/
void Save_Database(void);

View File

@@ -2,7 +2,6 @@
#include "app_manager.h"
#include "database.h"
#include "system.h"
#include "clock.h"
#include "xtell.h"
#include "../Drivers/adc.h"
@@ -142,47 +141,6 @@ void Event_Dispatcher_Loop(void)
AppManager_DispatchEvent(evt);
}
/**
* @brief 定时器 1 初始化,设定 1ms 中断周期
*/
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; // 开启全局中断允许
}
// 全局按键扫描触发标志
volatile bit key_scan_flag = 0;
/**
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
* @details 承担系统软时钟计算,并每 10ms 置位主循环按键扫描标志,不直接在中断中进行 ADC 多次采样。
*/
void Timer1_Isr(void) interrupt 3
{
// 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; // 仅在中断里置起标志位,留待主循环中执行实际读取与消抖
}
}
/**
* @brief 主循环按键扫描与消抖处理函数
* @details 由主循环每 10ms 检查并执行,完全在主线程中运行,绝无中断重入冲突。

View File

@@ -53,7 +53,7 @@ bit EventQueue_IsFull(void);
u8 EventQueue_GetCount(void);
/* ====== 事件分发环与定时器驱动初始化 ====== */
/* ====== 事件分发环 ====== */
/**
* @brief 事件分发轮询主处理程序
@@ -62,13 +62,7 @@ u8 EventQueue_GetCount(void);
*/
void Event_Dispatcher_Loop(void);
/**
* @brief 初始化 Timer1 定时器,生成系统基准时钟 (1ms 中断)
* @details 配置定时器 1 为 16 位自动重装载模式,设定中断周期为 1.0 毫秒,开启 Timer1 中断并启动计数。
*/
void Timer1_Init(void);
// 主循环按键扫描定时标志 (由 1ms 中断置 1主循环响应清除)
// 主循环按键扫描定时标志 (由 Drivers/timer.c 的 Timer1 1ms 中断置 1主循环响应清除)
extern volatile bit key_scan_flag;
/**

48
App/language.c Normal file
View 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_FR;
void Lang_Init(Language lang)
{
current_lang = (lang < LANG_COUNT) ? lang : LANG_FR;
}
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
View 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__

View File

@@ -7,6 +7,7 @@
#include "system.h"
#include "../Drivers/rf.h"
#include "../Drivers/lcd.h"
#include "../Drivers/timer.h"
/* =========================================================================
* 系统全局变量定义
@@ -193,9 +194,46 @@ void Debug_ProcessCommand(char cmd)
}
}
// ====== 调试用最小化启动开关 ======
// 置 1 时 main() 只做点亮屏幕这一件事RF/自检/数据库/事件框架/Timer1 全部跳过,
// 用于定位"开机反复重启"问题的范围。定位完成后改回 0恢复正常启动流程。
#define DEBUG_MINIMAL_BOOT 1
/* =========================================================================
* 主函数 (入口)
* ========================================================================= */
#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 溢出中断与 P3.6 边沿中断真正开始工作
// 注意:已恢复到步骤 A不调用 RF_SetMode(1)RF 芯片保持关闭状态
LCD_ShowStringCentered(100, "STEP A OK", COLOR_WHITE, COLOR_BLACK);
LCD_ShowStringCentered(130, "RF_Init+Timer1", COLOR_GRAY, COLOR_BLACK);
while (1) {
Delay_ms(1);
}
}
#else
void main(void)
{
char cmd;
@@ -268,6 +306,8 @@ void main(void)
}
}
#endif // DEBUG_MINIMAL_BOOT
// 唤醒源事件标志,由对应的外部中断 ISR 写入
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
@@ -304,12 +344,15 @@ void Port2_Isr(void) interrupt 15
/**
* @brief Port3 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
* @details 负责处理 P3.6 (新射频数据脚) 在休眠模式下的物理跳变唤醒。
* @details P3.6 (RF_RX_DATA) 双边沿中断现在改为常态开启 (见 RF_Init()),身兼两职:
* 1. 休眠期间作为唤醒源,记录 p3_wakeup_flag 供 Wakeup_Restore() 判定唤醒来源;
* 2. 正常运行期间驱动 RF_HandleEdgeInterrupt() 增量解码 EV1527 信号。
* 不再像旧版那样在首次触发后自行关闭 P3INTE否则第 2 项功能会在第一次边沿后失效。
*/
void Port3_Isr(void) interrupt 16
{
EAXFR = 1; // 使能访问扩展寄存器
p3_wakeup_flag = P3INTF | 0x01;// 读取并备份 P3 中断标志寄存器值 (0x01作安全掩码)
P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
P3INTE = 0x00; // 立即关闭 P3 中断通道,防止反复重入
RF_HandleEdgeInterrupt(); // 驱动 EV1527 边沿增量解码状态机
}

66
App/strings.h Normal file
View File

@@ -0,0 +1,66 @@
#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") \
DEFINE_STRING(CLOCK_DATE, "VEN 10-07", "FRI 10-07")
typedef enum {
WRISTBAND_STRING_LIST
STR_COUNT
} StringID;
#endif // __STRINGS_H__

View File

@@ -342,11 +342,10 @@ void Enter_Low_Power_Sleep(void)
P2INTE |= 0x40; // 开启 P2.6 中断允许
P2WKUE |= 0x40; // 使能 P2.6 掉电唤醒
// 9. 配置 Port3 中断唤醒源 (RF_RX_DATA=P3.6)
P3IM1 |= 0x40;
P3IM0 |= 0x40; // 双边沿触发
// 9. 配置 Port3 掉电唤醒源 (RF_RX_DATA=P3.6)
// 双边沿触发与 P3INTE 中断允许现已在 RF_Init() 中常态开启 (供正常运行期间的边沿解码使用)
// 此处只需重新武装 P3WKUE (掉电唤醒使能,会被 Wakeup_Restore() 清零) 与清除挂起标志
P3INTF = 0x00; // 清除挂起标志
P3INTE |= 0x40; // 开启 P3.6 中断允许
P3WKUE |= 0x40; // 使能 P3.6 掉电唤醒
// 确保引脚上拉和输入使能,防止因浮空产生的功耗泄漏
@@ -365,7 +364,10 @@ void Enter_Low_Power_Sleep(void)
p3_wakeup_flag = 0;
rf_wakeup_flag = 0;
// 10. 关闭系统滴答定时器 Timer1 中断,防止休眠时被定时器误唤醒
// 10. 关闭系统滴答定时器 Timer1 与 RF 时间戳基准 Timer0 的中断,防止休眠时被定时器误唤醒
// (Power Down 模式下主振荡器本应整体停振Timer0/1 都会随之停止计数;这里仍显式关闭
// 中断允许作为防御性保险,与 ET1 的处理方式保持一致)
ET0 = 0;
ET1 = 0;
EA = 1; // 开启全局中断
@@ -383,10 +385,15 @@ void Enter_Low_Power_Sleep(void)
void Wakeup_Restore(void)
{
EAXFR = 1;
// 1. 立即关闭所有掉电外部中断允许与唤醒允许,防止重入
// 1. 立即关闭 P0/P2 掉电外部中断允许与全部唤醒允许,防止重入
// P3INTE 的 P3.6 位不清零 —— 该中断常态用于正常运行期间的 RF 边沿解码,唤醒后仍需保持开启
P0INTE = 0x00;
P2INTE = 0x00;
P3INTE = 0x00;
P3INTE = 0x40;
// 恢复休眠前被 Enter_Low_Power_Sleep() 关闭的 P3.6 内部上拉LR690L 在 SHUT=1 期间数据脚
// 高阻悬空,配合常态开启的 P3.6 中断会引发中断风暴,唤醒后必须立刻用上拉钳住电平
P3PU |= 0x40;
P0WKUE = 0x00;
P2WKUE = 0x00;
P3WKUE = 0x00;
@@ -398,8 +405,9 @@ void Wakeup_Restore(void)
WKTCH = 0x00;
WKTCL = 0x00;
// 3. 开启全局中断并重启系统滴答定时器
// 3. 开启全局中断并重启系统滴答定时器 Timer1 与 RF 时间戳基准 Timer0
EA = 1;
ET0 = 1;
ET1 = 1;
// 4. 恢复射频芯片工作模式

102
App/ui.c
View File

@@ -1,9 +1,11 @@
#include "ui.h"
#include "database.h"
#include "language.h"
#include "../Drivers/lcd.h"
#include "../Drivers/adc.h"
// 引入外部主菜单对码项名称定义 (例如 "DETEC. PORTE" 等,法语表达防区类型名称)
extern char *code menu_items[5];
// 引入外部主菜单对码项名称的字符串 ID 表 (例如门磁/PIR/烟感等防区类型)
extern const StringID device_menu_ids[5];
/**
* @brief 渲染并显示系统待机时钟主页面
@@ -70,10 +72,10 @@ void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
LCD_ShowString16x32Centered(100, time_str, COLOR_WHITE, COLOR_BLACK); // 使用 16x32 特大字体居中绘制时间
// ====== 绘制 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);
LCD_ShowStringCentered(195, Lang_Get(STR_CLOCK_DATE), COLOR_GRAY, COLOR_BLACK);
}
/**
@@ -87,21 +89,21 @@ void UI_ShowPairMenuPage(u8 selected_index)
{
LCD_Clear(COLOR_BLACK);
// 绘制标题 "MENU APPAI." (法语: 配对/对码设置菜单)
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) {
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(15, 110, 104, 28, COLOR_CYAN); // 青色对齐外框 (在 135px 宽度下居中)
LCD_ShowStringCentered(118, menu_items[selected_index], COLOR_CYAN, COLOR_BLACK);
LCD_ShowStringCentered(118, Lang_Get(device_menu_ids[selected_index]), COLOR_CYAN, COLOR_BLACK);
// 渲染下方相邻项 (如果当前选择的不是最后一行)
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);
}
}
@@ -115,7 +117,7 @@ void UI_ShowPairWaitPage(u8 frame_index)
LCD_Clear(COLOR_BLACK);
// 绘制头部 "APPAIRAGE" (对码中)
LCD_ShowStringCentered(35, "APPAIRAGE", COLOR_GRAY, COLOR_BLACK);
LCD_ShowStringCentered(35, Lang_Get(STR_PAIRING_TITLE), COLOR_GRAY, COLOR_BLACK);
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉 (在 135px 下居中 X = 51)
if (frame_index == 0)
@@ -126,8 +128,8 @@ void UI_ShowPairWaitPage(u8 frame_index)
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); // 请触发传感器以发射射频信号
}
/**
@@ -140,8 +142,8 @@ void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
char zone_str[3];
LCD_Clear(COLOR_BLACK);
// 顶部显示捕获回馈标志 "v RECU" (法语: 信号已接收)
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) {
@@ -153,7 +155,7 @@ void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
}
// 渲染传感器类型的文字
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);
// 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (在 135px 下居中 X=22, 宽度 90)
LCD_DrawRectBorder(22, 140, 90, 44, COLOR_CYAN);
@@ -163,7 +165,7 @@ void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
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); // 短按以确认保存
}
/**
@@ -174,8 +176,8 @@ void UI_ShowPairSuccessPage(void)
LCD_Clear(COLOR_BLACK);
// 渲染 32x32 对勾图标 (135px 下居中 X = 51)
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK);
LCD_ShowStringCentered(145, "SUCCES", 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); // 配置已保存
}
/**
@@ -187,11 +189,11 @@ void UI_ShowPairFailPage(u8 is_timeout)
LCD_Clear(COLOR_BLACK);
// 渲染 32x32 红色叉号图标 (135px 下居中 X = 51)
LCD_DrawMonoBitmap(51, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK);
LCD_ShowStringCentered(145, "ECHEC", COLOR_RED, COLOR_BLACK); // 法语: 失败
LCD_ShowStringCentered(145, Lang_Get(STR_PAIR_FAILED), COLOR_RED, COLOR_BLACK); // 失败
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 {
LCD_ShowStringCentered(180, "TYPE INCORRECT", COLOR_GRAY, COLOR_BLACK); // 法语: 传感器类型不符
LCD_ShowStringCentered(180, Lang_Get(STR_PAIR_WRONG_TYPE), COLOR_GRAY, COLOR_BLACK); // 传感器类型不符
}
}
@@ -224,7 +226,7 @@ void UI_ShowAlarmPage(u8 type, u8 zone_index)
// 拼接字符串: "设备类型名 两位防区号"
{
u8 len = 0;
char *p = sensor_names_fr[type];
char *p = Lang_Get(sensor_type_name_ids[type]);
while(*p) {
zone_info[len++] = *p++;
}
@@ -234,7 +236,7 @@ void UI_ShowAlarmPage(u8 type, u8 zone_index)
zone_info[len] = '\0';
}
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); // 白字入侵提示
}
/**
@@ -260,10 +262,10 @@ void UI_ShowSosPage(void)
LCD_FillRect(53, 38, 4, 4, COLOR_RED); // 左侧散射闪光
LCD_FillRect(77, 38, 4, 4, COLOR_RED); // 右侧散射闪光
// 绘制求救中文字样
LCD_ShowStringCentered(120, "SOS", COLOR_RED, COLOR_BLACK);
LCD_ShowStringCentered(150, "ENVOI", COLOR_RED, COLOR_BLACK); // 法语: 求救中
LCD_ShowStringCentered(195, "APPEL...", COLOR_WHITE, COLOR_BLACK); // 法语: 紧急呼叫中
// 绘制求救中文字样
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); // 紧急呼叫中
}
/**
@@ -282,8 +284,8 @@ void UI_ShowBindingPage(u32 addr)
LCD_FillRect(77, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1 (135px 下居中等比右移 X = 77)
LCD_FillRect(79, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2 (135px 下居中等比右移 X = 79)
LCD_ShowStringCentered(120, "LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 连接/绑定
LCD_ShowStringCentered(155, "ENVOI EN COURS", COLOR_CYAN, COLOR_BLACK); // 法语: 发送对码帧中
LCD_ShowStringCentered(120, Lang_Get(STR_LINK_TITLE), COLOR_WHITE, 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] = ' ';
@@ -311,14 +313,14 @@ void UI_ShowBindingPage(u32 addr)
/**
* @brief 渲染显示设置主菜单界面
* @param selected_index 当前菜单高亮行索引 (0:时钟修改, 1:对码匹配, 2:摄像机绑定)
* @param selected_index 当前菜单高亮行索引 (0:语言切换, 1:时钟修改, 2:对码匹配, 3:摄像机绑定)
* @details 在顶部和底部绘制向上/向下箭头提示滑轮,中部绘制选中项的大几何图形,
* 并在下方打印对应的文字描述。
* 并在下方打印对应当前语言的文字描述。
*/
void UI_ShowMainMenu(u8 selected_index)
{
LCD_Clear(COLOR_BLACK);
// ====== 绘制顶部的向上箭头三角标记 (在 135px 宽度下居中) ======
LCD_FillRect(65, 20, 4, 2, COLOR_GRAY);
LCD_FillRect(63, 22, 8, 2, COLOR_GRAY);
@@ -326,27 +328,33 @@ void UI_ShowMainMenu(u8 selected_index)
if (selected_index == 0)
{
// 菜单 0时间修改设置。中部绘制一个青色闹钟样式 (135px 下居中 X = 51)
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, "1. HORLOGE", COLOR_WHITE, COLOR_BLACK); // 法语: 时钟/时间修改
// 菜单 0语言切换。用当前语言的两字母缩写代替图标,直接展示切换后的效果
LCD_ShowString16x32Centered(75, (Lang_Current() == LANG_FR) ? "FR" : "EN", COLOR_CYAN, COLOR_BLACK);
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LANGUAGE), COLOR_WHITE, COLOR_BLACK);
}
else if (selected_index == 1)
{
// 菜单 1无线对码匹配。中部绘制一个青色同心圆雷达波纹图形 (135px 下居中 X = 51)
LCD_DrawMonoBitmap(51, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
LCD_ShowStringCentered(150, "2. APPAIRAGE", COLOR_WHITE, COLOR_BLACK); // 法语: 配对
// 菜单 1时间修改设置。中部绘制一个青色闹钟样式 (135px 下居中 X = 51)
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)
{
// 菜单 2网络摄像机绑定。中部绘制一个相机/联通关系图 (135px 下居中等比右移 X = 57)
// 菜单 2无线对码匹配。中部绘制一个青色同心圆雷达波纹图形 (135px 下居中 X = 51)
LCD_DrawMonoBitmap(51, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_APPAIRAGE), COLOR_WHITE, COLOR_BLACK); // 配对
}
else if (selected_index == 3)
{
// 菜单 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, "3. LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 摄像机绑定
LCD_ShowStringCentered(150, Lang_Get(STR_MENU_LIAISON), COLOR_WHITE, COLOR_BLACK); // 摄像机绑定
}
// ====== 绘制底部的向下箭头三角标记 (在 135px 宽度下居中) ======
@@ -411,6 +419,6 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
LCD_ShowString16x32(75, 100, 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);
LCD_ShowStringCentered(195, Lang_Get(STR_CLOCK_DATE), COLOR_GRAY, COLOR_BLACK);
}