Compare commits

..

6 Commits

25 changed files with 1915 additions and 2180 deletions

182
App/app_alarm.c Normal file
View 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(&clock_app); // 清除报警并切回时钟待机页
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}

190
App/app_clock.c Normal file
View File

@@ -0,0 +1,190 @@
#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"
// 外部引用的全局系统状态变量
extern SystemState current_state;
extern volatile u16 ms_tick;
// 待机时钟应用内特有的编辑时间私有变量
static u8 time_edit_active = 0; // 标识当前是否正在编辑修改时间中 (1:是, 0:否)
static u8 time_edit_field = 1; // 标识当前聚焦在哪一个参数上进行微调 (1:修改小时, 2:修改分钟)
static u8 time_edit_blink_on = 1; // 时钟修改时光标闪烁周期状态位
// 声明本应用的生命周期回调函数
static void ClockApp_OnStart(void);
static void ClockApp_onRun(void);
static void ClockApp_onClose(void);
static EventResult ClockApp_onEvent(SystemEvent *evt);
// 实体化定义待机时钟应用控制块
WristbandApp clock_app = {
APP_ID_CLOCK,
"Clock",
ClockApp_OnStart,
ClockApp_onRun,
ClockApp_onClose,
ClockApp_onEvent
};
/**
* @brief 触发启动时间设置状态机,准备修改时钟
*/
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);
// 振动马达单次短震 50 毫秒,提供触觉反馈
MOTOR = 1;
Delay_ms(50);
MOTOR = 0;
// 渲染显示时间编辑界面,默认高亮编辑小时字段,消隐位开启
UI_ShowSetTimePage(current_hour, current_min, time_edit_field, 1);
XTELL_LOG("[ClockApp] Time edit started.\r\n");
}
/**
* @brief ClockApp 启动回调
*/
static void ClockApp_OnStart(void)
{
time_edit_active = 0;
current_state = STATE_NORMAL; // 系统切入正常待机状态
RF_SetMode(1); // 开启天线并置为接收模式,监听无线探测器
UI_ShowClockPage(current_state, current_hour, current_min); // 渲染时钟主页
// 关断/熄灭 FCOB 双灯条
RGB_Send(0, 0, 0, 0, 0, 0);
XTELL_LOG("[ClockApp] Started\r\n");
}
/**
* @brief ClockApp 轮询主回调
*/
static void ClockApp_onRun(void)
{
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);
}
}
}
/**
* @brief ClockApp 退出清理回调
*/
static void ClockApp_onClose(void)
{
time_edit_active = 0;
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭灯条
XTELL_LOG("[ClockApp] Closed\r\n");
}
/**
* @brief ClockApp 接收事件处理回调 (按键导航与布撤防交互)
*/
static 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)
{
// 长按 ■ 确认键切换调节字段 (在修改小时与修改分钟间跳转)
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)
{
// 短按 ■ 确认键保存最新时钟配置并退出编辑模式
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; // 在时间设置模式下屏蔽拦截其他一切无关按键事件
}
// ====== 正常时钟待机状态下的按键拦截处理 ======
// 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);
return EVENT_HANDLED;
}
return EVENT_IGNORED; // 其他未被关心的事件交由后台应用或丢弃
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,56 +3,75 @@
#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 负责将应用管理器的系统注册表清零,随后逐一注册系统支持的五个前台应用:
* ClockApp (待机时钟), MenuApp (主菜单), PairApp (传感器对码),
* AlarmApp (传感器警报), SOSApp (主动求救呼叫)。最后默认加载并启动 ClockApp。
* @brief 初始化应用管理器
* @details 将前台活跃槽位与后台的三个槽位全部清空为 NULL。
*/
void AppManager_Init(void);
/**
* @brief 启动指定的应用程序
* @param app_id 待启动应用的目标 ID (如 APP_ID_MENU, APP_ID_PAIR)
* @details 会执行完整的切应用生命周期流转:
* 1. 检查目标是否合法且包含有效的 OnStart 启动回调。
* 2. 对当前正在活跃运行的前台应用调用其 onClose(),以关闭该应用的屏幕内容、马达振动或射频特定模式
* 3. 将活跃应用指针指向新应用,将其 is_active 和 is_running 标志位置 1
* 4. 调用新应用的 OnStart() 初始化回调以绘制新页面
* @brief 动态将一个应用切换至前台活跃运行
* @param app 目标应用的结构体指针
* @return bit 1-切换成功0-失败
* @details 流程:
* 1. 若前台已有应用运行,且该应用未挂载在后台,则对其执行 onClose() 关闭生命周期
* 2. 将 fg_app 指针指向新的 app
* 3. 若该 app 目前并未在后台槽位中运行,则执行其 OnStart() 启动生命周期
*/
void AppManager_StartApp(AppID app_id);
bit AppManager_SwitchToForeground(WristbandApp *app);
/**
* @brief 向当前活跃的前台应用程序派发事件
* @param evt 要分发派送的系统事件
* @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);
/**
* @brief 在系统主循环中持续轮询运行活跃应用
* @details 负责从全局注册表中定位到 active_app并轮询执行其 onRun() 周期回调
* 该接口同时集成了【CPU占用率限制机制】如果在调用 onRun 前后,检测到应用
* 占用 CPU 时间超过了 APP_ONRUN_MAX_TICKS (5ms),它将在下一次循环中暂时挂起
* 该应用,强制释放本次 CPU 占用以防止界面卡死或影响底层射频解调。
* @brief 在系统主循环中持续轮询轮转前后台所有活跃应用
* @details 负责轮询前台 fg_app 对应的 onRun(),并遍历轮询后台 bg_apps 数组中所有有效应用的 onRun()。
*/
void AppManager_RunActiveApp(void);
/**
* @brief 获取当前前台活跃应用的唯一应用程序 ID
* @return AppID 当前活跃前台应用的枚举ID
* @brief 获取当前前台活跃应用的 ID
* @return u8 当前活跃前台应用的 ID 编号。若无则返回 255 (0xFF)
*/
AppID AppManager_GetActiveAppID(void);
u8 AppManager_GetActiveAppID(void);
// 兼容性接口声明
void AppManager_StartApp(AppID app_id);
/* ====== 时间编辑状态机交互接口 ====== */
/**
* @brief 启动时钟数值调整编辑状态机
* @details 由 MenuApp 的 "HORLOGE" 项被确认点击后触发。用于直接将系统运行状态切入
* STATE_SET_TIME控制灯条常亮蓝色马达短振反馈并进入时间参数微调页。
*/
void Start_Time_Edit(void);
// ====== 系统中各独立 App 实例的外包声明 (各 App 内部进行实体定义) ======
extern WristbandApp clock_app;
extern WristbandApp menu_app;
extern WristbandApp pair_app;
extern WristbandApp alarm_app;
extern WristbandApp sos_app;
extern WristbandApp rf_monitor_app; // 后台射频监控应用
#endif // __APP_MANAGER_H__

196
App/app_menu.c Normal file
View File

@@ -0,0 +1,196 @@
#include "app_manager.h"
#include "xtell.h"
#include "ui.h"
#include "rgb.h"
#include "event.h"
#include "system.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: 水浸探测器
};
// 外部全局变量引用
extern SystemState current_state;
extern u8 menu_select;
extern u8 captured_type;
extern volatile u16 ms_tick;
// 菜单应用私有变量
static u8 menu_level = 0; // 0-主菜单, 1-二级传感器选择菜单, 2-IPC绑定一键发射
static u16 last_bind_tx_ms = 0; // 记录上一次绑定发射的时间戳
// 引入外部时钟调节接口
extern void Start_Time_Edit(void);
// 声明本应用的生命周期回调函数
static void MenuApp_OnStart(void);
static void MenuApp_onRun(void);
static void MenuApp_onClose(void);
static EventResult MenuApp_onEvent(SystemEvent *evt);
// 实体化定义菜单应用控制块
WristbandApp menu_app = {
APP_ID_MENU,
"Menu",
MenuApp_OnStart,
MenuApp_onRun,
MenuApp_onClose,
MenuApp_onEvent
};
/**
* @brief MenuApp 启动回调
*/
static 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); // 熄灭所有灯条
XTELL_LOG("[MenuApp] Started\r\n");
}
/**
* @brief MenuApp 轮询主回调
*/
static 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); // 渲染青色 (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("[MenuApp] IPC Binding frame emitted\r\n");
}
}
}
/**
* @brief MenuApp 退出清理回调
*/
static void MenuApp_onClose(void)
{
menu_level = 0;
RGB_Send(0, 0, 0, 0, 0, 0); // 熄灭 RGB
RF_SetMode(1); // 重新开启无线接收模式
XTELL_LOG("[MenuApp] Closed\r\n");
}
/**
* @brief MenuApp 接收按键与控制事件回调
*/
static 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) {
// 功能 1设置时间。启动待机时钟应用并调用微调编辑入口
AppManager_SwitchToForeground(&clock_app);
Start_Time_Edit();
} else if (menu_select == 1) {
// 功能 2传感器学习。进入二级传感器类别选择菜单
menu_level = 1;
menu_select = 0;
UI_ShowPairMenuPage(menu_select); // 绘制二级设备分类页
} else if (menu_select == 2) {
// 功能 3相机配对。进入摄像机一键绑定发射状态
menu_level = 2;
last_bind_tx_ms = ms_tick; // 记录初始发射时间
current_state = STATE_IPC_BIND; // 系统设为 IPC 绑定状态
UI_ShowBindingPage(CLONED_ADDR); // 绘制相机绑定信息页
}
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
// 长按 ■ 确认键,退出设置菜单直接返回待机时钟
AppManager_SwitchToForeground(&clock_app);
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) {
// 确认选择:缓存用户选好的传感器类别,短震反馈并加载对码应用 (PairApp)
captured_type = menu_select;
MOTOR = 1;
Delay_ms(100);
MOTOR = 0;
AppManager_SwitchToForeground(&pair_app); // 切换至 PairApp 对码
return EVENT_HANDLED;
}
if (evt->key_event == KEY_EVENT_DOWN_LONG) {
// 长按 ▼ 键,从二级菜单退回到一级主菜单
menu_level = 0;
menu_select = 1; // 回到 APPAIRAGE 选项上
UI_ShowMainMenu(menu_select);
return EVENT_HANDLED;
}
}
else if (menu_level == 2)
{
// ====== 处于摄像机配对一键绑定发送层 ======
// 长按 ■ 确认键 或 长按 ▼ 键,均表示取消绑定,退出发射并返回一级主菜单
if (evt->key_event == KEY_EVENT_CONFIRM_LONG || evt->key_event == KEY_EVENT_DOWN_LONG) {
menu_level = 0;
menu_select = 2; // 回到 LIAISON 选项上
current_state = STATE_PAIR_MENU;
RGB_Send(0, 0, 0, 0, 0, 0); // 关闭呼吸灯
UI_ShowMainMenu(menu_select);
return EVENT_HANDLED;
}
}
return EVENT_IGNORED;
}

231
App/app_pair.c Normal file
View File

@@ -0,0 +1,231 @@
#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 volatile u16 ms_tick;
// 对码状态机定义
enum {
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 PairApp 启动回调
*/
static void PairApp_OnStart(void)
{
// 1. 为了防止后台射频监听与对码射频接收冲突,将后台射频监听应用暂时卸载
AppManager_DetachFromBackground(&rf_monitor_app);
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] Started (WAIT)\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_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;
}
if (evt->key_event == KEY_EVENT_CONFIRM_LONG) {
// 长按 ■ 确认键放弃本次捕获,直接返回二级主菜单
RGB_Send(0, 0, 0, 0, 0, 0);
AppManager_SwitchToForeground(&menu_app);
return EVENT_HANDLED;
}
}
// 在对码搜寻或确认防区期间,长按 ▼ 键均代表强行取消对码直接退回菜单页
if (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
View 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;
}

134
App/app_sos.c Normal file
View 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(&clock_app); // 退出 SOS返回时钟
return EVENT_HANDLED;
}
return EVENT_IGNORED;
}

37
App/clock.c Normal file
View File

@@ -0,0 +1,37 @@
#include "clock.h"
#include "xtell.h"
// 软时钟基础计时变量
u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
u8 current_sec = 0; // 系统软时钟:秒
volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
// 毫秒计数器 (Timer1 每 1ms 累加)
static u16 ms_cnt = 0;
/**
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
* @details 负责由定时器 1ms 驱动递增软时钟,并在分钟发生跳变时将 clock_updated 置位
*/
void Clock_IncMS(void)
{
ms_cnt++;
if (ms_cnt >= 1000) {
ms_cnt = 0; // 1秒时间到
current_sec++;
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; // 小时循环
}
}
clock_updated = 1; // 分钟跳变,标志位置位,前台待机 App 会捕获以刷新 UI
XTELL_LOG("[CLOCK] Time updated!\r\n");
}
}
}

17
App/clock.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef __CLOCK_H__
#define __CLOCK_H__
#include "config.h"
// 全局软时钟变量声明
extern u8 current_hour;
extern u8 current_min;
extern u8 current_sec;
extern volatile u8 clock_updated;
/**
* @brief 系统软时钟毫秒递增更新 (在 1ms 中断中调用)
*/
void Clock_IncMS(void);
#endif // __CLOCK_H__

View File

@@ -22,19 +22,19 @@ sbit KEY_DOWN = P0^3;
sbit KEY_SOS = P2^6;
// FCOB 幻彩灯条 DIN 数据控制线
sbit RGB_DIN = P2^3;
// LR690L 射频芯片休眠/工作控制引脚 (低电平使能开启工作)
sbit SHUT = P2^2;
// SGM3833 PMIC 屏幕负压供电使能脚 (1使能0关闭)
sbit SGM_CTRL = P3^4;
// Truly AMOLED 屏幕复位脚 (RESET),低电平复位
// 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;
// Truly AMOLED 屏幕数据/指令选择脚 (D/C1:数据, 0:命令)
// LHS114TC-IF03 屏幕数据/指令选择脚 (D/C1:数据, 0:命令)
sbit LCD_DCX = P1^1;
// Truly AMOLED 屏幕 SPI 数据脚 (MOSI/SDA)
// LHS114TC-IF03 屏幕 SPI 数据脚 (MOSI/SDA)
sbit LCD_SDI = P1^4;
// Truly AMOLED 屏幕 SPI 时钟脚 (SCLK)
// LHS114TC-IF03 屏幕 SPI 时钟脚 (SCLK)
sbit LCD_SCL = P1^5;
// Truly AMOLED 屏幕 SPI 片选脚 (CS),低电平有效
// LHS114TC-IF03 屏幕 SPI 片选脚 (CS),低电平有效
sbit LCD_CS = P1^6;
/* ====== 系统运行状态枚举 ====== */
@@ -70,16 +70,16 @@ typedef enum {
/* ====== 事件优先级枚举 ====== */
typedef enum {
EVENT_PRIORITY_LOW, // 低优先级:系统内部状态更新 (如时钟分钟更新)
EVENT_PRIORITY_NORMAL, // 正常优先级:用户按键交互、菜单导航
EVENT_PRIORITY_HIGH, // 高优先级:无线传感器入侵警报
EVENT_PRIORITY_URGENT // 紧急优先级SOS 主动求救
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; // 事件的处理优先级
EventPriority priority; // 事件优先级
u32 extra_data; // 随事件携带的额外数据 (如 24 位射频源地址等)
u8 extra_size; // 额外数据的有效字节大小 (0~4)
} SystemEvent;
@@ -106,26 +106,7 @@ typedef enum {
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; // 应用程序唯一标识 ID
AppType app_type; // 前后台类型标识
char *app_name; // 应用程序名称字符串 (供串口日志调试使用)
void (*OnStart)(void); // 启动回调:应用加载时调用,用于初始化 UI 界面与硬件状态
void (*onRun)(void); // 循环回调:在主循环中持续轮询调用,用于非阻塞业务更新
void (*onClose)(void); // 关闭回调:应用退出时调用,用于现场清理与外设复位
EventResult (*onEvent)(SystemEvent *evt); // 事件回调:接收系统事件,返回处理结果以控制事件流向
u8 is_active; // 标记该应用当前是否处于活跃运行状态 (1表示前台活跃)
u8 is_running; // 标记应用是否处于运行态,用于 CPU 超时挂起机制控制
};
/* ====== 无线传感器 Flash 存储槽位结构体 ====== */
typedef struct {

View File

@@ -2,6 +2,8 @@
#include "app_manager.h"
#include "database.h"
#include "system.h"
#include "clock.h"
#include "xtell.h"
// 定义事件环形队列的最大深度为 8
#define EVENT_QUEUE_DEPTH 8
@@ -64,7 +66,6 @@ SystemEvent EventQueue_Pop(void)
SystemEvent evt;
// 默认空事件属性
evt.key_event = KEY_EVENT_NONE;
evt.priority = EVENT_PRIORITY_LOW;
evt.extra_data = 0;
evt.extra_size = 0;
@@ -118,46 +119,17 @@ void Event_Dispatcher_Loop(void)
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");
XTELL_LOG("[EVENT] key=");
XTELL_LOG_HEX8((u8)evt.key_event);
XTELL_LOG("\r\n");
// SOS 按键紧急处理:若非处于 SOS 呼救状态下,接收到 SOS 键动作,立即强行切入到 SOS_APP 并执行求救
// SOS 按键紧急处理:若当前前台不为 SOS 应用,接收到 SOS 键动作,立即强行切入到 SOS_APP 并执行求救
if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) &&
AppManager_GetActiveAppID() != APP_ID_SOS) {
AppManager_StartApp(APP_ID_SOS);
return;
}
// 传感器入侵报警过滤与跳转处理:
// 当接收到高优先级且带有 4 字节射频地址的数据包,说明有传感器触发了发射
if (evt.priority == EVENT_PRIORITY_HIGH && evt.extra_size >= 4) {
u8 slot;
// 在本地数据库中检索该传感器地址是否合法登记过
if (Check_Sensor_ID(evt.extra_data, &slot)) {
// 在不处于 SOS 的情况下,可正常强行切入 ALARM_APP 触发声光振警报
if (AppManager_GetActiveAppID() != APP_ID_SOS) {
captured_addr = evt.extra_data; // 缓存报警地址
alarm_sensor_slot = slot; // 缓存报警槽位
AppManager_StartApp(APP_ID_ALARM); // 强制切入报警前台
return;
}
}
}
// 无线对码捕获处理:
// 若系统正处于对码应用 (APP_ID_PAIR),遇到普通优先级无线射频包,则将其派发给该应用进行匹配与登记
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);
}
@@ -182,89 +154,14 @@ void Timer1_Init(void)
/**
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
* @details 承担了系统软时钟计算、报警与求救下的马达控制状态机以及 10ms 周期性按键消抖扫描的功能。
* @details 承担了系统软时钟计算、10ms 周期性按键消抖扫描的功能。马达等特定业务已剥离至 App 内部。
*/
void Timer1_Isr(void) interrupt 3
{
SystemEvent evt;
// 1ms 递增滴答
ms_tick++;
if (ms_tick >= 1000) {
ms_tick = 0; // 1秒时间已到归零
current_sec++;
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; // 小时循环
}
clock_updated = 1; // 标志位置1提示前台时钟应用刷新显示
}
}
// 如果处于对码等待状态,对码超时计时器增加 (30秒超时判断)
if (current_state == STATE_PAIR_WAIT) {
pair_timeout_ms++;
}
/* 振动马达定时状态机逻辑控制 */
if (current_state == STATE_SOS_EMITTED) {
// SOS 模式:交替规律震动 (震动 1000ms静音 200ms)
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; // 5秒一周期
if (stype == 0) {
// 门磁警报波形:单次震动 300ms停 4700ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
} else if (stype == 1) {
// PIR 警报波形:震动 300ms停 200ms再震动 300ms停 4200ms
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) {
// 烟感警报波形:急促短闪三下 200ms-150ms-200ms-150ms-200ms
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) {
// 水浸警报波形:慢速双长震波 600ms 和 600ms
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 {
// 缺省状态默认单次 300ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
}
} else {
// 其他常态下彻底关闭震动马达并归零计时器
MOTOR = 0;
alarm_timer_ms = 0;
motor_timer_ms = 0;
}
// 1ms 定时更新软时钟
Clock_IncMS();
// 报警屏幕红边框以 100ms 周期亮灭,进行颜色爆闪指示
alarm_flash_flag = (ms_tick / 100) % 2;
@@ -276,7 +173,7 @@ void Timer1_Isr(void) interrupt 3
u8 raw_up = !KEY_UP;
u8 raw_down = !KEY_DOWN;
u8 raw_confirm = !KEY_CONFIRM;
u8 raw_sos = !KEY_SOS;
u8 raw_sos = 0; // 临时注释关闭 SOS 物理按键电平检测,防浮空干扰
key_scan_timer = 0; // 重置扫描分频计数器
// 检测到有任意按键处于下压状态,立即将无操作闲置休眠计时器归零
@@ -294,12 +191,11 @@ void Timer1_Isr(void) interrupt 3
evt.extra_data = 0;
evt.extra_size = 0;
// 双键组合判定:▲ 键 ▼ 键同时按下的消抖与生成
// 双键组合判定:▲ 键 and ▼ 键同时按下的消抖与生成
if (raw_up && raw_down) {
comb_hold++;
if (comb_hold == 300) { // 持续按下达 3 秒 (300 * 10ms = 3s)
evt.key_event = KEY_EVENT_UP_DOWN_COMB;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); // 压入队列
}
key_up_hold = 0; // 清零单个按键计数,防止双击事件干扰
@@ -312,14 +208,12 @@ void Timer1_Isr(void) interrupt 3
key_up_hold++;
if (key_up_hold == 300) { // 达到 3 秒判定为长按
evt.key_event = KEY_EVENT_UP_LONG;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
} else {
// 如果松开时按下计数在消抖阈值20ms至长按阈值3s之间则判定为一次短按
if (key_up_hold >= 2 && key_up_hold < 300) {
evt.key_event = KEY_EVENT_UP_CLICK;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
key_up_hold = 0; // 释放归零
@@ -330,13 +224,11 @@ void Timer1_Isr(void) interrupt 3
key_down_hold++;
if (key_down_hold == 300) { // 长按 3 秒
evt.key_event = KEY_EVENT_DOWN_LONG;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
} else {
if (key_down_hold >= 2 && key_down_hold < 300) {
evt.key_event = KEY_EVENT_DOWN_CLICK;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
key_down_hold = 0; // 释放归零
@@ -347,34 +239,32 @@ void Timer1_Isr(void) interrupt 3
key_confirm_hold++;
if (key_confirm_hold == 300) { // 长按 3 秒打开主菜单
evt.key_event = KEY_EVENT_CONFIRM_LONG;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
} else {
if (key_confirm_hold >= 2 && key_confirm_hold < 300) {
evt.key_event = KEY_EVENT_CONFIRM_CLICK;
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt);
}
key_confirm_hold = 0; // 释放归零
}
/*
// SOS 物理求救按键状态机
if (raw_sos) {
key_sos_hold++;
if (key_sos_hold == 300) { // 长按 3 秒,发出 URGENT 优先级求救,插入队首
if (key_sos_hold == 300) { // 长按 3 秒,发出求救,插入队首
evt.key_event = KEY_EVENT_SOS_LONG;
evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt);
}
} else {
if (key_sos_hold >= 2 && key_sos_hold < 300) { // 短按即呼救,插入队首
evt.key_event = KEY_EVENT_SOS_CLICK;
evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt);
}
key_sos_hold = 0; // 释放归零
}
*/
}
}
}

View File

@@ -12,10 +12,7 @@
* 系统全局变量定义
* ========================================================================= */
SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页
u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
u8 current_sec = 0; // 系统软时钟:秒
volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
u8 menu_select = 0; // 主菜单当前选中的行高亮索引 (0~2)
u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址
@@ -150,8 +147,9 @@ void Debug_ProcessCommand(char cmd)
inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循环深度休眠
Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
} else if (cmd == 't' || cmd == 'T') {
Loopback_Test(); // 调用回环测试验证屏幕
RF_Test_Pin(); // 无线射频测试
RF_DiagnosticMode('t'); // 触发射频连续发射诊断 (绿色爆闪)
} else if (cmd == 'r' || cmd == 'R') {
RF_DiagnosticMode('r'); // 触发 5 秒射频接收监听诊断
}
}
@@ -172,9 +170,7 @@ void main(void)
GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉
RF_Init(); // 初始化射频芯片各引脚控制
Delay_ms(500); // 软启动延时,等待供电电容稳定
SGM_SendPulse(27); // 通过脉冲给 SGM3833 供电芯片发指令,稳压开启 LCD 正负电压轨
Delay_ms(50); // 等待电压充盈稳定,避开浪涌
LCD_Init(); // 初始化 Truly AMOLED 屏幕控制寄存器序列
LCD_Init(); // 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制寄存器序列并开启背光
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
Uart_SendString("Wristband System Initialized!\r\n");
@@ -189,6 +185,9 @@ void main(void)
/* 启动 1ms 系统基准定时器 Timer1 */
Timer1_Init();
/* 开机自动加载并运行待机时钟前台活跃应用 */
AppManager_StartApp(APP_ID_CLOCK);
Uart_SendString("[SYS] Event-driven framework ready\r\n");
// ====== 主循环 ======
@@ -219,7 +218,8 @@ void main(void)
// 唤醒源事件标志,由对应的外部中断 ISR 写入
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
volatile u8 rf_wakeup_flag = 0; // P2.1 射频中断唤醒标志
volatile u8 p3_wakeup_flag = 0; // P3 端口射频中断唤醒标志
volatile u8 rf_wakeup_flag = 0; // 射频中断唤醒标志
/* =========================================================================
* STC32G 端口掉电外部中断唤醒中断服务程序 (ISR)
@@ -248,3 +248,15 @@ void Port2_Isr(void) interrupt 15
P2INTF = 0x00; // 清除 P2 端口中断悬挂标志位
P2INTE = 0x00; // 立即关闭 P2 中断通道,防止低电平期间反复重入
}
/**
* @brief Port3 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
* @details 负责处理 P3.6 (新射频数据脚) 在休眠模式下的物理跳变唤醒。
*/
void Port3_Isr(void) interrupt 16
{
EAXFR = 1; // 使能访问扩展寄存区
p3_wakeup_flag = P3INTF | 0x01;// 读取并备份 P3 中断标志寄存器值 (0x01作安全掩码)
P3INTF = 0x00; // 清除 P3 端口中断悬挂标志位
P3INTE = 0x00; // 立即关闭 P3 中断通道,防止反复重入
}

Binary file not shown.

139
App/ui.c
View File

@@ -38,8 +38,8 @@ void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
}
// ====== 绘制右上角电池电量 ======
LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK); // 静态测试显示 85% 字符
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池外廓和格数
LCD_ShowString(77, 24, "85%", COLOR_GRAY, COLOR_BLACK); // 静态测试显示 85% 字符并移动至右上角
LCD_DrawMonoBitmap(107, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池并移动至右上角
// ====== 绘制中部时间 "HH:MM" ======
time_str[0] = '0' + (hour / 10); // 小时十位
@@ -77,7 +77,7 @@ void UI_ShowPairMenuPage(u8 selected_index)
}
// 渲染当前选中的高亮中间项
LCD_DrawRectBorder(8, 110, 104, 28, COLOR_CYAN); // 青色对齐外框
LCD_DrawRectBorder(15, 110, 104, 28, COLOR_CYAN); // 青色对齐外框 (在 135px 宽度下居中)
LCD_ShowStringCentered(118, menu_items[selected_index], COLOR_CYAN, COLOR_BLACK);
// 渲染下方相邻项 (如果当前选择的不是最后一行)
@@ -98,13 +98,13 @@ void UI_ShowPairWaitPage(u8 frame_index)
// 绘制头部 "APPAIRAGE" (对码中)
LCD_ShowStringCentered(35, "APPAIRAGE", COLOR_GRAY, COLOR_BLACK);
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉 (在 135px 下居中 X = 51)
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)
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
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); // 法语: 搜寻中
@@ -124,24 +124,24 @@ void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
// 顶部显示捕获回馈标志 "v RECU" (法语: 信号已接收)
LCD_ShowStringCentered(32, "v RECU", COLOR_GREEN, COLOR_BLACK);
// 在上部居中绘制设备类别所对应的图形
// 在上部居中绘制设备类别所对应的图形 (135px 下居中 X = 51)
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) {
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 {
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);
// 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (如 "01", "12")
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[1] = '0' + (zone_index % 10);
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); // 法语: 短按以确认保存
@@ -153,8 +153,8 @@ void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
void UI_ShowPairSuccessPage(void)
{
LCD_Clear(COLOR_BLACK);
// 渲染 32x32 对勾图标
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, 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); // 法语: 配置已保存
}
@@ -166,8 +166,8 @@ void UI_ShowPairSuccessPage(void)
void UI_ShowPairFailPage(u8 is_timeout)
{
LCD_Clear(COLOR_BLACK);
// 渲染 32x32 红色叉号图标
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_cross_32x32, COLOR_RED, 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); // 法语: 失败
if (is_timeout) {
LCD_ShowStringCentered(180, "DELAI DEPASSE", COLOR_GRAY, COLOR_BLACK); // 法语: 对码超时
@@ -189,17 +189,17 @@ void UI_ShowAlarmPage(u8 type, u8 zone_index)
char zone_info[20];
LCD_Clear(COLOR_BLACK);
// 绘制双重红色爆闪粗边界框 (配合 ms_tick 在 AlarmApp 中亮灭闪烁)
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
// 绘制双重红色爆闪粗边界框 (在新宽 135px 下对称扩展)
LCD_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
LCD_DrawRectBorder(6, 6, 123, 228, COLOR_RED);
// 绘制报警设备的图形
// 绘制报警设备的图形 (135px 下居中 X = 51)
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) {
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 {
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); // 其他防区报警
}
// 拼接字符串: "设备类型名 两位防区号"
@@ -227,19 +227,19 @@ void UI_ShowSosPage(void)
{
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_DrawRectBorder(4, 4, 127, 232, COLOR_RED);
LCD_DrawRectBorder(5, 5, 125, 230, COLOR_RED);
LCD_DrawRectBorder(8, 8, 119, 224, COLOR_WHITE);
// ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 ======
LCD_FillRect(44, 45, 32, 2, COLOR_RED); // 警灯顶部
LCD_FillRect(48, 47, 24, 12, COLOR_RED); // 警灯红色灯罩
LCD_FillRect(40, 59, 40, 4, COLOR_GRAY); // 警灯灰色底座
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); // 右侧散射闪光
// ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 (在新宽 135px 下整体右移 7 像素) ======
LCD_FillRect(51, 45, 32, 2, COLOR_RED); // 警灯顶部
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, "SOS", COLOR_RED, COLOR_BLACK);
@@ -257,11 +257,11 @@ void UI_ShowBindingPage(u32 addr)
LCD_Clear(COLOR_BLACK);
// ====== 像素级拼装绘制一个青色网络摄像头简图 ======
LCD_DrawRectBorder(36, 46, 32, 26, COLOR_CYAN); // 相机主体框
LCD_FillRect(44, 56, 6, 6, COLOR_CYAN); // 镜头光圈点
LCD_FillRect(68, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈
LCD_FillRect(70, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1
LCD_FillRect(72, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2
LCD_DrawRectBorder(43, 46, 32, 26, COLOR_CYAN); // 相机主体框 (135px 下居中等比右移 X = 43)
LCD_FillRect(51, 56, 6, 6, COLOR_CYAN); // 镜头光圈点 (135px 下居中等比右移 X = 51)
LCD_FillRect(75, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈 (135px 下居中等比右移 X = 75)
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); // 法语: 发送对码帧中
@@ -294,46 +294,46 @@ void UI_ShowBindingPage(u32 addr)
* @brief 渲染显示设置主菜单界面
* @param selected_index 当前菜单高亮行索引 (0:时钟修改, 1:对码匹配, 2:摄像机绑定)
* @details 在顶部和底部绘制向上/向下箭头提示滑轮,中部绘制选中项的大几何图形,
* 并在下方打印对应的法文字串描述。
* 并在下方打印对应的法文字串描述。
*/
void UI_ShowMainMenu(u8 selected_index)
{
LCD_Clear(COLOR_BLACK);
// ====== 绘制顶部的向上箭头三角标记 ======
LCD_FillRect(58, 20, 4, 2, COLOR_GRAY);
LCD_FillRect(56, 22, 8, 2, COLOR_GRAY);
LCD_FillRect(54, 24, 12, 2, COLOR_GRAY);
// ====== 绘制顶部的向上箭头三角标记 (在 135px 宽度下居中) ======
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)
{
// 菜单 0时间修改设置。中部绘制一个青色闹钟样式
LCD_DrawRectBorder(44, 70, 32, 32, COLOR_CYAN); // 闹钟外圈
LCD_FillRect(59, 76, 2, 10, COLOR_CYAN); // 闹钟时针
LCD_FillRect(59, 85, 8, 2, COLOR_CYAN); // 闹钟分针
// 菜单 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); // 法语: 时钟/时间修改
}
else if (selected_index == 1)
{
// 菜单 1无线对码匹配。中部绘制一个青色同心圆雷达波纹图形
LCD_DrawMonoBitmap(44, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
// 菜单 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); // 法语: 配对
}
else if (selected_index == 2)
{
// 菜单 2网络摄像机绑定。中部绘制一个相机/联通关系图
LCD_DrawRectBorder(44, 74, 20, 16, COLOR_CYAN);
LCD_FillRect(52, 80, 4, 4, COLOR_CYAN);
LCD_FillRect(64, 76, 2, 12, COLOR_CYAN);
LCD_FillRect(66, 78, 2, 8, COLOR_CYAN);
LCD_FillRect(68, 80, 2, 4, COLOR_CYAN);
// 菜单 2网络摄像机绑定。中部绘制一个相机/联通关系图 (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_FillRect(54, 210, 12, 2, COLOR_GRAY);
LCD_FillRect(56, 212, 8, 2, COLOR_GRAY);
LCD_FillRect(58, 214, 4, 2, COLOR_GRAY);
// ====== 绘制底部的向下箭头三角标记 (在 135px 宽度下居中) ======
LCD_FillRect(61, 210, 12, 2, COLOR_GRAY);
LCD_FillRect(63, 212, 8, 2, COLOR_GRAY);
LCD_FillRect(65, 214, 4, 2, COLOR_GRAY);
}
/**
@@ -354,8 +354,9 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
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);
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
// 移动电池百分比和图标到 135px 右上角
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)
@@ -370,10 +371,10 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
time_str[1] = '0' + (hour % 10);
}
time_str[2] = '\0';
LCD_ShowString16x32(12, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘小时
LCD_ShowString16x32(23, 100, 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)
@@ -388,7 +389,7 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
time_str[1] = '0' + (min % 10);
}
time_str[2] = '\0';
LCD_ShowString16x32(64, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟
LCD_ShowString16x32(75, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟 (在 135px 下整体居中 X = 75)
// 补齐底部的固定辅助元素
LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK);

24
App/xtell.h Normal file
View 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__

View File

@@ -10,26 +10,20 @@
* **硬件原理图**[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)
主控芯片采用 **STC32G12K128**(工作频率 24.0 MHz单周期 8051 内核32位乘除法器
显示端采用 **Truly 0.95寸 AMOLED 屏幕**,物理分辨率为 **120x240 像素**,控制芯片为 **RM69310**
### 1.1 GPIO 物理映射表
| 芯片引脚号 | 引脚名称 | 逻辑网络标号 | IO 模式 | 连接外设与功能描述 |
| :--- | :--- | :--- | :--- | :--- |
| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | Truly AMOLED 屏幕复位控制脚 (RESET) |
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | Truly AMOLED 屏幕 SPI 数据/指令选择脚 (D/C) |
| **Pin 3** | `P1.3` | `SPI_SDI` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输入 (MOSI/SDA) |
| **Pin 4** | `P1.4` | `SPI_CLK` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行时钟信号 (SCL) |
| **Pin 5** | `P1.5` | `SPI_CS` | 推挽输出 | Truly AMOLED 屏幕 SPI 片选信号 (CS) |
| **Pin 6** | `P1.6` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
| **Pin 7** | `P1.7` | `LEDA` | 推挽输出 | 屏幕负压供电芯片使能控制端 (SGM_CTRL) |
主控芯片采用| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | LHS114TC-IF03 屏幕复位控制脚 (RESET) |
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 数据/指令选择脚 (D/C) |
| **Pin 7** | `P1.3` | `BAT_ADC` | ADC 输入 | 电池电压采集 (通道 3配置为高阻输入) |
| **Pin 3** | `P1.4` | `SPI_SDI` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 串行数据输入 (MOSI/SDA) |
| **Pin 4** | `P1.5` | `SPI_CLK` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 串行时钟信号 (SCL) |
| **Pin 5** | `P1.6` | `SPI_CS` | 推挽输出 | LHS114TC-IF03 屏幕 SPI 片选信号 (CS) |
| **Pin 6** | `P1.7` | `LEDA` | 推挽输出 | LHS114TC-IF03 屏幕白色 LED 背光使能控制端 (0点亮1熄灭 - PMOS控制) |
| **Pin 8** | `UCAP` | `UCAP` | 电源 | 外接滤波电容至 GND (LDO 稳定输出端) |
| **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
| **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电参考基准 |
| **Pin 12** | `GND` | `GND` | 电源 | 系统地 (GND) |
| **Pin 13** | `P3.0` | `RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 |
| **Pin 14** | `P3.1` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 |
| **Pin 15** | `P5.4` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
| **Pin 18** | `P3.5` | `SHUT` | 推挽输出 | LR690L 射频芯片关闭信号 (0:开启工作, 1:关闭休眠) |
| **Pin 19** | `P3.6` | `RF_RX_DATA` | 高阻输入 | LR690L 接收解调接收数据输出引脚 (注:代码中被映射为 P2^1) |
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | ATR5179 接收使能控制引脚 |
@@ -40,6 +34,18 @@
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
| **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 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |

View File

@@ -3,313 +3,201 @@
#include "../App/config.h"
#include "intrins.h"
// ====== RM69310 Truly AMOLED 屏幕 SPI 引脚映射 (8051 bit 寻址控制) ======
sbit LCD_RST = P1^0; // 屏幕硬件复位引脚 (RESET),低电平复位
sbit LCD_DCX = P1^1; // 串行数据 / 命令选择控制线 (1:数据, 0:指令)
sbit LCD_SDI = P1^4; // SPI 主机输出从机输入数据线 (MOSI)
sbit LCD_SCL = P1^5; // SPI 串行工作时钟线 (SCLK)
sbit LCD_CS = P1^6; // SPI 物理片选选择端 (CS),低电平选通屏幕
// ====== LHS114TC-IF03 (ST7789V) <20><>Ļ SPI <20><><EFBFBD><EFBFBD>ӳ<EFBFBD><D3B3> ======
sbit LCD_RST = P1^0; // <EFBFBD><EFBFBD>ĻӲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (RESET)<29><><EFBFBD>͵<EFBFBD>ƽ<EFBFBD><C6BD>λ
sbit LCD_DCX = P1^1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> / <20><><EFBFBD><EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (1:<3A><><EFBFBD><EFBFBD>, 0:ָ<EFBFBD><EFBFBD>)
sbit LCD_SDI = P1^4; // SPI <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (MOSI)
sbit LCD_SCL = P1^5; // SPI <EFBFBD><EFBFBD><EFBFBD>й<EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (SCLK)
sbit LCD_CS = P1^6; // SPI <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭѡѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (CS)<29><><EFBFBD>͵<EFBFBD>ƽѡͨ<D1A1><CDA8>Ļ
// ====== SGM3833 升压/负压三路 OLED 屏电源 PMIC 使能引脚 ======
sbit SGM_CTRL = P3^4; // 单线脉冲通信控制线
// ====== LHS114TC-IF03 LEDA <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ======
sbit SGM_CTRL = P1^7; // LEDA <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (0:<3A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, 1:<3A>ر<EFBFBD>Ϩ<EFBFBD><CFA8> - PMOS<4F><53><EFBFBD><EFBFBD>)
// 引用外部延时函数 (定义在 system.c )
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ⲿ<EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> system.c <EFBFBD><EFBFBD>)
extern void Delay_ms(u16 ms);
extern void Delay10us(void);
/**
* @brief 通过脉冲序列为 SGM3833 屏幕电源管理芯片写入开启配置
* @param pulse_cnt 要写入的脉冲个数 (本系统传入 27 次脉冲开启正负双电轨)
* @details SGM3833 采用单线边沿脉冲计数协议进行数字控制。
* - tOFF 必须大于 5ms 才能完全复位芯片进入指令模式。
* - 每次电平拉低低电平持续 10us 后拉高,形成一个脉冲计数值。
* - 发送完毕后需拉高并维持 5ms 以上,以便将计数值存入寄存器,锁定目标电压轨开启。
*/
void SGM_SendPulse(u8 pulse_cnt)
{
u8 i;
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(); // 保持低电平 10us
SGM_CTRL = 1;
Delay10us(); // 保持高电平 10us
}
SGM_CTRL = 1;
Delay_ms(5); // 确保数据稳定存储锁定 (tSTORE >= 80us)
}
/**
* @brief 软件模拟 8 位串行 SPI 字节数据发送
* @param dat 待发送的 1 字节数据
* @details 采用典型的 bit-banging 模式:时钟 SCL 在发送前置低,将数据 MSB 依次提取后赋值给
* 数据线 SDI随后拉高 SCL 完成一个 bit 的沿采样,循环 8 次发送完成。
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD> 8 λ<><CEBB><EFBFBD><EFBFBD> SPI <20>ֽ<EFBFBD><D6BD><EFBFBD><EFBFBD>ݷ<EFBFBD><DDB7><EFBFBD>
* @param dat <20><><EFBFBD><EFBFBD><EFBFBD>͵<EFBFBD> 1 <20>ֽ<EFBFBD><D6BD><EFBFBD><EFBFBD><EFBFBD>
*/
void SPI_SendData(u8 dat)
{
u8 i;
for(i = 0; i < 8; i++)
{
LCD_SCL = 0; // 拉低时钟,准备更新数据
LCD_SCL = 0; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>ӣ<EFBFBD>׼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if(dat & 0x80)
LCD_SDI = 1;
else
LCD_SDI = 0;
_nop_();
LCD_SCL = 1; // 拉高时钟,触发屏幕在上升沿采样数据位
LCD_SCL = 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>ӣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ
_nop_();
dat <<= 1; // 左移一位,准备发送下一位
dat <<= 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>һλ<EFBFBD><EFBFBD>׼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һλ
}
}
/**
* @brief 向屏幕控制器写入单字节控制指令 (DCX=0)
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD>ֽڿ<EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> (DCX=0)
*/
void WriteComm(u8 cmd)
{
LCD_CS = 0; // 开启片选
LCD_DCX = 0; // 切换为指令传输模式
LCD_CS = 0; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭѡ
LCD_DCX = 0; // <EFBFBD>л<EFBFBD>Ϊָ<EFBFBD><EFBFBD><EFBFBD>ģʽ
SPI_SendData(cmd);
LCD_CS = 1; // 关闭片选
LCD_CS = 1; // <EFBFBD>ر<EFBFBD>Ƭѡ
}
/**
* @brief 向屏幕控制器写入单字节显示数据 (DCX=1)
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (DCX=1)
*/
void WriteData(u8 dat)
{
LCD_CS = 0; // 开启片选
LCD_DCX = 1; // 切换为数据传输模式
LCD_CS = 0; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭѡ
LCD_DCX = 1; // <EFBFBD>л<EFBFBD>Ϊ<EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD><EFBFBD><EFBFBD>ģʽ
SPI_SendData(dat);
LCD_CS = 1; // 关闭片选
LCD_CS = 1; // <EFBFBD>ر<EFBFBD>Ƭѡ
}
/**
* @brief 按照 RM69310 物理时序执行屏幕硬件复位
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>ִ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĻӲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ
*/
void LCD_Reset(void)
{
LCD_RST = 1;
Delay_ms(10);
LCD_RST = 0; // 拉低复位引脚进入硬件复位状态
LCD_RST = 0; // <EFBFBD><EFBFBD><EFBFBD>͸<EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD>Ž<EFBFBD><EFBFBD><EFBFBD>Ӳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ״̬
Delay_ms(20);
LCD_RST = 1; // 重新拉高引脚
Delay_ms(120); // 必须等待至少 120ms 才能执行后续的初始化
LCD_RST = 1; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Delay_ms(120); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 120ms <20><><EFBFBD><EFBFBD>ִ<EFBFBD>к<EFBFBD><D0BA><EFBFBD><EFBFBD>ij<EFBFBD>ʼ<EFBFBD><CABC>
}
/**
* @brief 初始化 Truly AMOLED 屏幕控制器 RM69310
* @details 先配置控制 GPIO 为推挽输出,接着根据 Truly 信利原厂提供的初始化指令序列,
* 完成偏置电压、刷新周期、Gamma 参数、跳帧逻辑及色彩空间模式等一系列初始化工作。
* @brief <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD> LHS114TC-IF03 (ST7789V) <20><>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
*/
void LCD_Init(void)
{
// 1. 配置 P1 端口的 LCD_RST/DCX/SDI/SCL/CS 引脚全部为推挽输出模式 (M1=0, M0=1)
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>չ SFR <20><><EFBFBD><EFBFBD>Ȩ<EFBFBD><C8A8> (EAXFR = 1)<29><>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD>Բ<EFBFBD><D4B2><EFBFBD> P1M1 <20><> P1M0 ģʽ<C4A3>Ĵ<EFBFBD><C4B4><EFBFBD>
P_SW2 |= 0x80;
// 2. 配置 P3.4 (SGM_CTRL) 为推挽输出模式
P3M1 &= ~(1 << 4);
P3M0 |= (1 << 4);
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> P1 <20>˿ڵ<CBBF> LCD_RST(P1.0)/DCX(P1.1)/SDI(P1.4)/SCL(P1.5)/CS(P1.6)/LEDA(P1.7) Ϊ<><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ (M1=0, M0=1)
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6) | (1<<7));
// 设置各控制引脚初始默认电平
SGM_CTRL = 1;
// <EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ų<EFBFBD>ʼĬ<EFBFBD>ϵ<EFBFBD>ƽ
SGM_CTRL = 0; // PMOS<4F>͵<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (LEDA = 0)
LCD_CS = 1;
LCD_SCL = 1;
LCD_RST = 1;
LCD_DCX = 1;
LCD_Reset(); // 硬件复位屏幕
LCD_Reset(); // Ӳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>Ļ
// ====== 写入 RM69310 原厂寄存器初始化指令序列 ======
WriteComm(0xFE);WriteData(0x01); // 切换至扩展 Page 1 寄存器页
WriteComm(0x04);WriteData(0xa0); // 开启 SPI 写入显存 RAM 允许
WriteComm(0x05);WriteData(0x70); // 配置屏幕尺寸规格为 128RGB
WriteComm(0x06);WriteData(0x3C); // 设定垂直有效扫描线 NL = 240
WriteComm(0x25);WriteData(0x06); // 正常模式伽马设定Gamma1
WriteComm(0x26);WriteData(0x80); // T1A 时序常数 = 280
WriteComm(0x27);WriteData(0x12); // 正常模式消隐后肩 VBP = 12
WriteComm(0x28);WriteData(0x12); // 正常模式消隐前肩 VFP = 8
WriteComm(0x2A);WriteData(0x06); // 空闲模式伽马设定 Gamma1
WriteComm(0x2B);WriteData(0x80); // T1B 时序常数 = 280
WriteComm(0x2D);WriteData(0x12); // 空闲消隐后肩 = 12
WriteComm(0x2F);WriteData(0x12); // 空闲消隐前肩 = 8
WriteComm(0x37);WriteData(0x0C); // 预充电控制电压 VGSP
WriteComm(0x6D);WriteData(0x18); // 关闭跳帧 VGMP 和 VGSP 稳压调节
WriteComm(0x29);WriteData(0x01); // 关闭正常模式的跳帧
WriteComm(0x30);WriteData(0x43); // 设定空闲模式跳帧使刷新频率降为 30Hz
WriteComm(0x0E);WriteData(0x83); // 正常模式 AVDD 电压 = 5.6V
WriteComm(0x0F);WriteData(0x83); // 空闲模式 AVDD = 5.6V
WriteComm(0x10);WriteData(0x71); // AVDD 三倍 VCI 升压控制
WriteComm(0x11);WriteData(0xb3); // VCL = -2倍 VCI 产生负压
WriteComm(0x12);WriteData(0xb3);
WriteComm(0x13);WriteData(0x80); // 正常模式下 VGH 等于 AVDD
WriteComm(0x14);WriteData(0x80); // 空闲模式下 VGH 等于 AVDD
WriteComm(0x15);WriteData(0x81); // 正常模式下低侧门电压 VGL = VCL - VCI
WriteComm(0x16);WriteData(0x81);
WriteComm(0x18);WriteData(0x66); // 偏置电平 VGHR = 6V
WriteComm(0x19);WriteData(0x44); // 偏置电平 VGLR = -6V
WriteComm(0x1E);WriteData(0x02); // 开启电荷平衡 EQ 调制
WriteComm(0x5B);WriteData(0x10); // 启用参考负压 VREFN5
WriteComm(0x62);WriteData(0x19); // 正常模式 VREFN5 电平 = -3V
WriteComm(0x63);WriteData(0x19); // 空闲模式 VREFN5 = -3V
WriteComm(0x70);WriteData(0x55); // 关闭显示时源极驱动器至 AVDD
WriteComm(0x1D);WriteData(0x02); // 启用 EQ 平衡
WriteComm(0x89);WriteData(0x18); // 控制偏置 VGMP = 5.5V
WriteComm(0x8A);WriteData(0xb9); // 控制偏置 VGSP = 2V
WriteComm(0x8B);WriteData(0x01); // 偏置高字节
WriteComm(0x8C);WriteData(0x10); // VGMP 设为 5.4V
WriteComm(0x8D);WriteData(0x90); // VGSP 设为 2V
WriteComm(0x8E);WriteData(0x01);
WriteComm(0x6E);WriteData(0x0A); // 关闭 MIPI 高速数据接口以选择 SPI 驱动方式
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);
WriteComm(0x43);WriteData(0x41);
WriteComm(0x44);WriteData(0x25);
WriteComm(0x45);WriteData(0x52);
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);
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); // 空闲模式使用内部集成供电
WriteComm(0x67);WriteData(0x40); // 内部延迟 1 帧切断电源
WriteComm(0x72);WriteData(0x1A); // 内部核心 OVDD 电压 = 4.6V
WriteComm(0x73);WriteData(0x07); // 内部核心 OVSS 电压 = -2V
WriteComm(0x74);WriteData(0x0C); // OVDD 的最终输入来源设为 AVDD
WriteComm(0x6A);WriteData(0x3D); // 向单线接口发送脉冲,配置升压
WriteComm(0x6B);WriteData(0x29);
WriteComm(0xFE);WriteData(0x04); // 切换至 Page 4
WriteComm(0x5E);WriteData(0x01);
WriteComm(0x5F);WriteData(0xB8);
WriteComm(0x60);WriteData(0xBB);
WriteComm(0x61);WriteData(0xBB);
WriteComm(0x62);WriteData(0xBB);
WriteComm(0x76);WriteData(0xBB);
WriteComm(0x77);WriteData(0x3B);
WriteComm(0x78);WriteData(0x92);
WriteComm(0x79);WriteData(0xBB);
WriteComm(0x7A);WriteData(0xBB);
WriteComm(0x00);WriteData(0xDC); // 扫描时钟 CK1 寄存器配置
WriteComm(0x01);WriteData(0x00);
WriteComm(0x02);WriteData(0x02);
WriteComm(0x03);WriteData(0x00);
WriteComm(0x04);WriteData(0x08);
WriteComm(0x05);WriteData(0x01);
WriteComm(0x06);WriteData(0x70);
WriteComm(0x07);WriteData(0x0A);
WriteComm(0x08);WriteData(0x00);
WriteComm(0x09);WriteData(0xDC); // 扫描时钟 CK2 寄存器配置
WriteComm(0x0a);WriteData(0x00);
WriteComm(0x0b);WriteData(0x02);
WriteComm(0x0C);WriteData(0x00);
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 配置
WriteComm(0x13);WriteData(0x00);
WriteComm(0x14);WriteData(0x02);
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 配置
WriteComm(0x1C);WriteData(0x00);
WriteComm(0x1D);WriteData(0x02);
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 配置
WriteComm(0x4D);WriteData(0x00);
WriteComm(0x4E);WriteData(0x01);
WriteComm(0x4F);WriteData(0x00);
WriteComm(0x50);WriteData(0x01);
WriteComm(0x51);WriteData(0xBB);
WriteComm(0x52);WriteData(0xBB);
WriteComm(0x53);WriteData(0xCA); // 发光起始信号 EM_STV 配置
WriteComm(0x54);WriteData(0x00);
WriteComm(0x55);WriteData(0x03);
WriteComm(0x56);WriteData(0x00);
WriteComm(0x58);WriteData(0x00);
WriteComm(0x59);WriteData(0x00);
WriteComm(0x65);WriteData(0x45);
WriteComm(0x66);WriteData(0x0C);
WriteComm(0x67);WriteData(0x00);
WriteComm(0xFE);WriteData(0x01); // 切换至 Page 1 验证芯片 ID
WriteComm(0xE5);WriteData(0x00);
WriteComm(0xE6);WriteData(0x10);
WriteComm(0xE7);WriteData(0x31);
WriteComm(0xFE);WriteData(0x00); // 切换回 Page 0 进行正常显示操作
WriteComm(0xC4);WriteData(0x80);
WriteComm(0x2A);WriteData(0x00);WriteData(0x04);WriteData(0x00);WriteData(0x7B); // 设定列像素物理边界 (4~123宽120像素)
// ====== 显式设置屏幕像素色彩格式为 16 位像素格式 (COLMOD = 0x75即RGB565格式) ======
WriteComm(0x3A);WriteData(0x75);
WriteComm(0x11);WriteData(0x00); // 唤醒屏芯片:发送 Sleep Out 退出睡眠指令
Delay_ms(150); // 退出睡眠状态后,必须等待 150ms 以便驱动电荷泵稳定
WriteComm(0x29);WriteData(0x00); // 开启屏幕显示:发送 Display ON 指令,像素层开始发光
// ====== д<EFBFBD><EFBFBD> ST7789V ԭ<><D4AD><EFBFBD>Ĵ<EFBFBD><C4B4><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><CABC>ָ<EFBFBD><D6B8><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ======
WriteComm(0x11); // Sleep Out <20>˳<EFBFBD>˯<EFBFBD><CBAF>
Delay_ms(120); // <EFBFBD>˳<EFBFBD>˯<EFBFBD>ߺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> 120ms
// Memory Data Access Control (MADCTL) ɨ<><EFBFBD><E8B7BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// 0x00: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD><C2A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ң<EFBFBD>RGB <20><>ʽ
WriteComm(0x36);
WriteData(0x00);
// Interface Pixel Format (COLMOD)
// 0x05 <20><><EFBFBD><EFBFBD> 16-bit/pixel (RGB565<36><35>ʽ)
WriteComm(0x3A);
WriteData(0x05);
// Porch Setting (B2h)
WriteComm(0xB2);
WriteData(0x0C);
WriteData(0x0C);
WriteData(0x00);
WriteData(0x33);
WriteData(0x33);
// Gate Control (B7h)
// VGH=13.65V, VGL=-10.43V
WriteComm(0xB7);
WriteData(0x35);
// VCOM Setting (BBh)
// VCOM=1.175V
WriteComm(0xBB);
WriteData(0x19);
// LCM Control (C0h)
WriteComm(0xC0);
WriteData(0x2C);
// VDV and VRH Command Enable (C2h)
WriteComm(0xC2);
WriteData(0x01);
// VRH Set (C3h)
// VRH=4.3V
WriteComm(0xC3);
WriteData(0x12);
// VDV Set (C4h)
// VDV=0V
WriteComm(0xC4);
WriteData(0x20);
// Frame Rate Control in Normal Mode (C6h)
// 0x0F: 60Hz
WriteComm(0xC6);
WriteData(0x0F);
// Power Control 1 (D0h)
WriteComm(0xD0);
WriteData(0xA4);
WriteData(0xA1);
// Positive Gamma Correction (E0h) <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>٤<EFBFBD><D9A4>
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);
// Negative Gamma Correction (E1h) <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>٤<EFBFBD><D9A4>
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);
// Display Inversion On (21h) <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԣ<EFBFBD>ST7789 IPS <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
WriteComm(0x21);
// Display On (29h) <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ
WriteComm(0x29);
Delay_ms(20);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><D6B9>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD>˲<EFBFBD><CBB2><EFBFBD><EFBFBD><EFBFBD>ֻ<EFBFBD><D6BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
LCD_Clear(COLOR_BLACK);
}
/**
* @brief 设置屏幕绘制显存窗口区域
* @brief <EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݸ<EFBFBD><EFBFBD>»<EFBFBD><EFBFBD>ƴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
*/
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
{
// 写入 Column Address Set 列地址设置寄存器命令 (0x2A)
// <EFBFBD>ڵײ㴰<EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD>ͳһ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƫ<EFBFBD>õ<EFBFBD>͸<EFBFBD><EFBFBD>У<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
x_start += LCD_X_OFFSET;
x_end += LCD_X_OFFSET;
y_start += LCD_Y_OFFSET;
y_end += LCD_Y_OFFSET;
// д<><D0B4> Column Address Set <20>е<EFBFBD>ַ<EFBFBD><D6B7><EFBFBD>üĴ<C3BC><C4B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (0x2A)
WriteComm(0x2A);
WriteData((u8)(x_start >> 8));
WriteData((u8)(x_start & 0xFF));
WriteData((u8)(x_end >> 8));
WriteData((u8)(x_end & 0xFF));
// 写入 Row Address Set 行地址设置寄存器命令 (0x2B)
// д<EFBFBD><EFBFBD> Row Address Set <EFBFBD>е<EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD>üĴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (0x2B)
WriteComm(0x2B);
WriteData((u8)(y_start >> 8));
WriteData((u8)(y_start & 0xFF));
@@ -318,29 +206,26 @@ void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
}
/**
* @brief 用指定色彩刷写清空全屏幕
* @param color 16 RGB565 颜色填充值
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD>ˢд<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȫ<EFBFBD><EFBFBD>Ļ
* @param color 16λ RGB565 <EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ
*/
void LCD_Clear(u16 color)
{
u16 i, j;
// 【物理引脚偏移说明】:
// 该 AMOLED 屏在物理层面上水平方向的列像素地址为 4 ~ 123总宽 120 像素)。
// 因此调用 LCD_SetWindow 时X 轴的物理起始和终止坐标必须偏置 +4 才能与物理屏幕像素完全对齐。
LCD_SetWindow(4, 123, 0, 239);
WriteComm(0x2C); // 发送 RAM Write (0x2C) 显存写入命令
for(i = 0; i < 120; i++)
LCD_SetWindow(0, LCD_WIDTH - 1, 0, LCD_HEIGHT - 1);
WriteComm(0x2C); // <20><><EFBFBD><EFBFBD> RAM Write (0x2C) <20>Դ<EFBFBD>д<EFBFBD><D0B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for(i = 0; i < LCD_WIDTH; i++)
{
for(j = 0; j < 240; j++)
for(j = 0; j < LCD_HEIGHT; j++)
{
WriteData((u8)(color >> 8)); // 发送 RGB565 8 位字节
WriteData((u8)(color & 0xFF)); // 发送 RGB565 8 位字节
WriteData((u8)(color >> 8)); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> RGB565 <EFBFBD><EFBFBD> 8 λ<EFBFBD>ֽ<EFBFBD>
WriteData((u8)(color & 0xFF)); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> RGB565 <EFBFBD><EFBFBD> 8 λ<EFBFBD>ֽ<EFBFBD>
}
}
}
/**
* @brief 在屏幕指定视口绘制实心矩形
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļָ<EFBFBD><EFBFBD><EFBFBD>ӿڻ<EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD>ľ<EFBFBD><EFBFBD><EFBFBD>
*/
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
{
@@ -348,10 +233,9 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
u8 ch = (u8)(color >> 8);
u8 cl = (u8)(color & 0xFF);
// X 坐标应用物理偏置偏移 +4 像素
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1);
WriteComm(0x2C); // 写入显存
count = w * h; // 实心矩形包含的总像素数
LCD_SetWindow(x, x + w - 1, y, y + h - 1);
WriteComm(0x2C); // д<><D0B4><EFBFBD>Դ<EFBFBD>
count = w * h; // ʵ<EFBFBD>ľ<EFBFBD><EFBFBD>ΰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for(i = 0; i < count; i++)
{
WriteData(ch);
@@ -360,57 +244,55 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
}
/**
* @brief 绘制空心矩形边界框
* @details 通过组合调用四次 LCD_FillRect 实心线来绘制矩形的上、下、左、右四条边。
* @brief <EFBFBD><EFBFBD><EFBFBD>ƿ<EFBFBD><EFBFBD>ľ<EFBFBD><EFBFBD>α߽<EFBFBD><EFBFBD><EFBFBD>
* @details ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD> LCD_FillRect ʵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƾ<EFBFBD><EFBFBD>ε<EFBFBD><EFBFBD>ϡ<EFBFBD><EFBFBD>¡<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ߡ<EFBFBD>
*/
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
{
LCD_FillRect(x, y, w, 1, color); // 上边框线 (厚度 1 像素)
LCD_FillRect(x, y + h - 1, w, 1, color); // 下边框线
LCD_FillRect(x, y, 1, h, color); // 左边框线
LCD_FillRect(x + w - 1, y, 1, h, color); // 右边框线
LCD_FillRect(x, y, w, 1, color); // <EFBFBD>ϱ߿<EFBFBD><EFBFBD><EFBFBD>
LCD_FillRect(x, y + h - 1, w, 1, color); // <EFBFBD>±߿<EFBFBD><EFBFBD><EFBFBD>
LCD_FillRect(x, y, 1, h, color); // <EFBFBD><EFBFBD><EFBFBD>߿<EFBFBD><EFBFBD><EFBFBD>
LCD_FillRect(x + w - 1, y, 1, h, color); // <EFBFBD>ұ߿<EFBFBD><EFBFBD><EFBFBD>
}
/**
* @brief 渲染显示一个单色字模位图资源
* @param x 渲染起始坐标 X
* @param y 渲染起始坐标 Y
* @param w 位图宽度 (单位: 像素点数)
* @param h 位图高度 (单位: 像素点数)
* @param bmp FLASH 中的单色像素数组指针 (1表示画前景色0表示画背景色)
* @param color 16位前景色
* @param bg_color 16位背景色
* @details 会逐行、逐列解析单色图的字节数组,每一行占 (w+7)/8 个字节,
* 利用位操作提取指定 bit 的状态并发送色彩数据写入屏幕。
* @brief <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ʾһ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><EFBFBD>ģλͼ<EFBFBD><EFBFBD>Դ
* @param x <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> X
* @param y <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> Y
* @param w λͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><>λ: <20><><EFBFBD>ص<EFBFBD><D8B5><EFBFBD>)
* @param h λͼ<EFBFBD>߶<EFBFBD> (<28><>λ: <20><><EFBFBD>ص<EFBFBD><D8B5><EFBFBD>)
* @param bmp FLASH <EFBFBD>еĵ<EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> (1<><31>ʾ<EFBFBD><CABE>ǰ<EFBFBD><C7B0>ɫ<EFBFBD><C9AB>0<EFBFBD><30>ʾ<EFBFBD><CABE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ)
* @param color 16λǰ<EFBFBD><EFBFBD>ɫ
* @param bg_color 16λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ
*/
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color)
{
u16 r, c;
u16 byte_width = (w + 7) / 8; // 计算在单色编码中,一行图像占用的实际字节数
u16 byte_width = (w + 7) / 8; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD>ɫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD>һ<EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD>ռ<EFBFBD>õ<EFBFBD>ʵ<EFBFBD><EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD><EFBFBD>
u8 ch = (u8)(color >> 8);
u8 cl = (u8)(color & 0xFF);
u8 bgh = (u8)(bg_color >> 8);
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); // <EFBFBD><EFBFBD><EFBFBD>ƻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ洰<EFBFBD><EFBFBD>
WriteComm(0x2C);
// 嵌套循环,从第 0 行第 0 列开始遍历每个像素位置
// Ƕ<EFBFBD><EFBFBD>ѭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӵ<EFBFBD> 0 <20>е<EFBFBD> 0 <20>п<EFBFBD>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>ÿ<EFBFBD><C3BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>
for(r = 0; r < h; r++)
{
for(c = 0; c < w; c++)
{
u16 byte_idx = r * byte_width + (c / 8); // 定位当前像素所在单色图数组中的字节索引
u8 bit_shift = 7 - (c % 8); // 定位该像素在该字节中对应的 bit 位置 (高位在前)
u16 byte_idx = r * byte_width + (c / 8); // <EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD>ɫͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
u8 bit_shift = 7 - (c % 8); // <EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڸ<EFBFBD><EFBFBD>ֽ<EFBFBD><EFBFBD>ж<EFBFBD>Ӧ<EFBFBD><EFBFBD> bit λ<><CEBB> (<28><>λ<EFBFBD><CEBB>ǰ)
if(bmp[byte_idx] & (1 << bit_shift))
{
WriteData(ch); // 该位为 1发送前景色
WriteData(ch); // <EFBFBD><EFBFBD>λΪ 1<><31><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0>ɫ
WriteData(cl);
}
else
{
WriteData(bgh); // 该位为 0发送背景色
WriteData(bgh); // <EFBFBD><EFBFBD>λΪ 0<><30><EFBFBD><EFBFBD><EFBFBD>ͱ<EFBFBD><CDB1><EFBFBD>ɫ
WriteData(bgl);
}
}
@@ -418,31 +300,30 @@ void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16
}
/**
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
* @param ch ASCII 字符本身
* @details 遍历 8x16 点阵字库,将索引对应的 16 个字符编码字节依次提取,按位发送。
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD> 8x16 <20>ߴ<EFBFBD><DFB4><EFBFBD><EFBFBD><EFBFBD>ͨӢ<CDA8><D3A2> ASCII <EFBFBD>ַ<EFBFBD>
* @param ch ASCII <EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
*/
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{
u8 r, c;
u8 char_idx = ch - 0x20; // 偏移计算ASCII 可打印字符从 0x20 (空格) 开始
u8 char_idx = ch - 0x20; // ƫ<EFBFBD>Ƽ<EFBFBD><EFBFBD>㣺ASCII <20>ɴ<EFBFBD>ӡ<EFBFBD>ַ<EFBFBD><D6B7><EFBFBD> 0x20 (<28>ո<EFBFBD>) <20><>ʼ
u8 f_h = (u8)(color >> 8);
u8 f_l = (u8)(color & 0xFF);
u8 b_h = (u8)(bg_color >> 8);
u8 b_l = (u8)(bg_color & 0xFF);
if(ch < 0x20 || ch > 0x7E) return; // 过滤不可见控制字符
if(ch < 0x20 || ch > 0x7E) return; // <EFBFBD><EFBFBD><EFBFBD>˲<EFBFBD><EFBFBD>ɼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD>
LCD_SetWindow(x + 4, x + 8 - 1 + 4, y, y + 16 - 1); // 应用 +4 像素偏移
LCD_SetWindow(x, x + 8 - 1, y, y + 16 - 1);
WriteComm(0x2C);
// 循环 16 行,绘制 8x16 字型
// ѭ<EFBFBD><EFBFBD> 16 <EFBFBD>У<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 8x16 <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for(r = 0; r < 16; r++)
{
u8 line = FONT_8x16[char_idx][r]; // 提取当前行对应的 8 位单色像素信息
u8 line = FONT_8x16[char_idx][r]; // <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰ<EFBFBD>ж<EFBFBD>Ӧ<EFBFBD><EFBFBD> 8 λ<><CEBB>ɫ<EFBFBD><C9AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ
for(c = 0; c < 8; c++)
{
// 从高到低依次提取单行中的像素点
// <EFBFBD>Ӹߵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>е<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>
if(line & (0x80 >> c))
{
WriteData(f_h);
@@ -458,45 +339,42 @@ void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
}
/**
* @brief 在指定坐标打印一行普通英文 ASCII 字符串 (左对齐)
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӡһ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨӢ<EFBFBD><EFBFBD> ASCII <20>ַ<EFBFBD><D6B7><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
*/
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{
while(*str)
{
LCD_ShowChar8x16(x, y, *str, color, bg_color); // 渲染当前字符
x += 8; // X 轴起始位置偏移 8 像素,准备绘制下一个字符
str++; // 移向下一个字符
LCD_ShowChar8x16(x, y, *str, color, bg_color); // <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD>ǰ<EFBFBD>ַ<EFBFBD>
x += 8; // X <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ<EFBFBD><EFBFBD>ƫ<EFBFBD><EFBFBD> 8 <20><><EFBFBD>أ<EFBFBD>׼<EFBFBD><D7BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ַ<EFBFBD>
str++; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD>
}
}
/**
* @brief 在指定 Y 轴高度上,自动计算偏移以实现字符串水平居中显示
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> Y <20><><EFBFBD>߶<EFBFBD><DFB6>ϣ<EFBFBD><CFA3>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>ƫ<EFBFBD><C6AB><EFBFBD><EFBFBD>ʵ<EFBFBD><CAB5><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD>ˮƽ<CBAE><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ
*/
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color)
{
u16 len = 0;
char *p = str;
while(*p++) len++; // 计算字符串长度
while(*p++) len++; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if(len * 8 >= 120)
// 若超出或等同于屏幕有效宽度 (120像素),强制从最左侧 0 开始左对齐绘制
if(len * 8 >= LCD_WIDTH)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD>Ч<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>ǿ<EFBFBD>ƴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0 <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
LCD_ShowString(0, y, str, color, bg_color);
else
// 否则通过公式 (120 - 字符宽度) / 2 计算出水平居中的首字起始偏移 X 坐标并渲染
LCD_ShowString((120 - len * 8) / 2, y, str, color, bg_color);
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ (LCD_WIDTH - <20>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD>) / 2 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˮƽ<CBAE><C6BD><EFBFBD>е<EFBFBD><D0B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼƫ<CABC><C6AB> X <20><><EFBFBD><EFBFBD><EAB2A2>Ⱦ
LCD_ShowString((LCD_WIDTH - len * 8) / 2, y, str, color, bg_color);
}
/**
* @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
* @details 【字库双重放大技术】:
* 由于 8051 FLASH 容量有限,手环未附带特大型号的 16x32 大字库。
* 此函数通过算法对 8x16 基础字库点阵数据在水平方向(列)和垂直方向(行)各重复绘制 2 遍
* (即单个点扩充绘制为 2x2 的像素团),从而实现对原字型双重放大渲染为 16x32 规格,节省空间。
* @brief <EFBFBD><EFBFBD>Ⱦ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ش<EFBFBD> 16x32 <20>ߴ<EFBFBD><DFB4><EFBFBD>Ӣ<EFBFBD><D3A2>/<2F><><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD>
*/
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{
u8 r, c, i, j;
u8 val_bit;
u8 char_idx = ch - 0x20;
u8 f_h = (u8)(color >> 8);
u8 f_l = (u8)(color & 0xFF);
@@ -505,22 +383,22 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
if(ch < 0x20 || ch > 0x7E) return;
LCD_SetWindow(x + 4, x + 16 - 1 + 4, y, y + 32 - 1); // 锁定 16x32 的绘图视窗
LCD_SetWindow(x, x + 16 - 1, y, y + 32 - 1); // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> 16x32 <EFBFBD>Ļ<EFBFBD>ͼ<EFBFBD>Ӵ<EFBFBD>
WriteComm(0x2C);
// 遍历字型的 16 个点阵行数据
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͵<EFBFBD> 16 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> data
for(r = 0; r < 16; r++)
{
u8 line = FONT_8x16[char_idx][r];
// 纵向插点翻倍:将当前行重复绘制两次 (i = 0 i = 1)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (i = 0 <EFBFBD><EFBFBD> i = 1)
for(i = 0; i < 2; i++)
{
// 遍历单行内的 8 bit 像素点
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڵ<EFBFBD> 8 <EFBFBD><EFBFBD> bit <EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>
for(c = 0; c < 8; c++)
{
u8 val_bit = (line & (0x80 >> c));
// 横向插点翻倍:单个像素点重复发送写入 2 (j = 0 j = 1)
val_bit = (line & (0x80 >> c));
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD><EFBFBD>ظ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д<EFBFBD><EFBFBD> 2 <EFBFBD><EFBFBD> (j = 0 <EFBFBD><EFBFBD> j = 1)
for(j = 0; j < 2; j++)
{
if(val_bit)
@@ -540,31 +418,31 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
}
/**
* @brief 在指定坐标绘制特大 16x32 规格的数字/英文字符串
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ش<EFBFBD> 16x32 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><><D3A2><EFBFBD>ִ<EFBFBD>
*/
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{
while(*str)
{
LCD_ShowChar16x32(x, y, *str, color, bg_color);
x += 16; // 每次起始坐标偏移 16 像素
x += 16; // ÿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƫ<EFBFBD><EFBFBD> 16 <20><><EFBFBD><EFBFBD>
str++;
}
}
/**
* @brief 在指定 Y 轴高度上,水平居中渲染显示特大 16x32 规格字符串
* @brief <EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD> Y <20><><EFBFBD>߶<EFBFBD><DFB6>ϣ<EFBFBD>ˮƽ<CBAE><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ⱦ<EFBFBD><C8BE>ʾ<EFBFBD>ش<EFBFBD> 16x32 <20><><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD>
*/
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color)
{
u16 len = 0;
char *p = str;
while(*p++) len++; // 计算字数
while(*p++) len++; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if(len * 16 >= 120)
// 超出屏幕宽度,左对齐
if(len * 16 >= LCD_WIDTH)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
LCD_ShowString16x32(0, y, str, color, bg_color);
else
// 水平居中计算
LCD_ShowString16x32((120 - len * 16) / 2, y, str, color, bg_color);
// ˮƽ<EFBFBD><EFBFBD><EFBFBD>м<EFBFBD><EFBFBD><EFBFBD>
LCD_ShowString16x32((LCD_WIDTH - len * 16) / 2, y, str, color, bg_color);
}

View File

@@ -4,157 +4,118 @@
#include "../App/config.h"
#include "stc32g.h"
/* ====== Truly AMOLED RM69310 屏幕驱动 (RGB565格式) 常数定义 ====== */
/* ====== LHS114TC-IF03 (ST7789V) 屏幕参数与常数定义 ====== */
#define LCD_WIDTH 135 // 屏幕物理宽度
#define LCD_HEIGHT 240 // 屏幕物理高度
#define LCD_X_OFFSET 52 // 水平 X 轴物理偏置量
#define LCD_Y_OFFSET 40 // 垂直 Y 轴物理偏置量
// 16位 RGB565 颜色常量定义:
// 格式为Red(5位) Green(6位) Blue(5位) 拼接而成的 16 位整数值
#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 // 琥珀色/橙黄色
#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 屏幕负压供电芯片发送开启配置脉冲
* @param pulse_cnt 发送的脉冲个数 (例如 27 次脉冲开启并稳压屏幕输出)
* @details 采用单线脉冲边沿计数调制技术,为 RM69310 控制芯片提供其正常显示所需的高质量偏置正负电压。
* @brief 通过脉冲序列为 SGM3833 供电芯片写入配置
*/
void SGM_SendPulse(u8 pulse_cnt);
/**
* @brief 执行 Truly AMOLED 屏幕的硬件复位
* @details 将 LCD_RST 引脚拉低保持一定时间后拉高,强制复位屏幕控制器,使其恢复到初始逻辑状态。
* @brief 执行 LHS114TC-IF03 屏幕的硬件复位
*/
void LCD_Reset(void);
/**
* @brief 初始化 Truly AMOLED 屏幕控制器 RM69310
* @details 执行屏幕硬件复位,通过软件 SPI 总线依次向屏幕控制器写入寄存器偏置、显示周期、
* 通道选择、关闭休眠 (Sleep Out: 0x11)、开启显示 (Display On: 0x29) 等一系列
* 出厂初始化命令序列,使屏幕准备好接收显示像素帧。
* @brief 初始化 LHS114TC-IF03 (ST7789V) 屏幕控制器
*/
void LCD_Init(void);
/**
* @brief 向屏幕写入 1 字节控制命令
* @param cmd 8 位命令字节
* @brief 向屏幕写入 1 字节控制命令
*/
void WriteComm(u8 cmd);
/**
* @brief 设定屏幕的数据更新绘制窗口区域 (显存写入范围)
* @param x_start X 轴起始坐标 (0 ~ 119)
* @param x_end X 轴终止坐标 (0 ~ 119)
* @param y_start Y 轴起始坐标 (0 ~ 239)
* @param y_end Y 轴终止坐标 (0 ~ 239)
* @details 限定后续的数据流写入范围,实现局部页面刷新或全部页面刷新。
* @brief 向屏幕写入 1 字节显示数据
*/
void WriteData(u8 dat);
/**
* @brief 设定屏幕的数据更新绘制窗口区域
*/
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end);
/**
* @brief 指定纯色清除整个屏幕显示内容
* @param color 16位 RGB565 颜色填充值
* @brief 指定色彩刷写清空全屏幕
*/
void LCD_Clear(u16 color);
/* ====== 像素级图形几何算法与字符渲染接口 ====== */
/* ====== 关键图形与点阵字符渲染接口 ====== */
/**
* @brief 在屏幕指定位置绘制一个单色填充的实心矩形
* @param x 矩形左上角 X 轴坐标
* @param y 矩形左上角 Y 轴坐标
* @param w 矩形宽度
* @param h 矩形高度
* @param color 16位 RGB565 填充色彩值
* @brief 在屏幕指定视口绘制实心矩形
*/
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color);
/**
* @brief 绘制一个无填充的空心矩形边界框
* @param x 矩形左上角 X 轴坐标
* @param y 矩形左上角 Y 轴坐标
* @param w 边框总宽度
* @param h 边框总高度
* @param color 16位 RGB565 边框线条色彩
* @brief 绘制空心矩形边界框
*/
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color);
/**
* @brief 绘制渲染一个自定义宽高的单色单色位图
* @param x 绘制的左上角 X 轴坐标
* @param y 绘制的左上角 Y 轴坐标
* @param w 单色图宽度 (单位: 像素点数)
* @param h 单色图高度
* @param bmp 单色位图所在的 FLASH code 区指针 (1位对应1像素1画前景色0画背景色)
* @param color 位图前景色 (RGB565)
* @param bg_color 位图背景色
* @brief 渲染显示一个单色字模位图资源
*/
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color);
/**
* @brief 渲染显示一个 8x16 尺寸的普通英文 ASCII 字符
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
*/
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color);
/**
* @brief 渲染显示一行普通英文 ASCII 字符串 (左对齐)
* @param x 绘制行起始 X 坐标
* @param y 绘制行 Y 坐标
* @param str 字符串头指针
* @param color 字符颜色 (RGB565)
* @param bg_color 字符背景背景填充色
* @brief 在指定坐标打印一行普通英文 ASCII 字符串 (左对齐)
*/
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color);
/**
* @brief 在指定 Y 轴高度上,水平居中对齐显示一行字符串
* @param y 渲染的 Y 轴高度 (0 ~ 239)
* @param str 字符串内容
* @param color 颜色值
* @param bg_color 背景色
* @brief 在指定 Y 轴高度上,自动计算偏移以实现字符串水平居中显示
*/
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color);
/**
* @brief 渲染显示一个 16x32 大号特粗尺寸的英文/数字字符
* @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
*/
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color);
/**
* @brief 显示大号特粗 16x32 规格的数字/英文字(如时钟大字显示)
* @brief 在指定坐标绘制特大 16x32 规格的数字/英文字串
*/
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color);
/**
* @brief 在指定 Y 轴高度上,居中显示 16x32 特大规格字体的字符串
* @param y 渲染的 Y 轴高度 (0 ~ 239)
* @param str 大字字符串内容
* @param color 大字前景色
* @param bg_color 大字背景色
* @brief 在指定 Y 轴高度上,水平居中渲染显示特大 16x32 规格字符串
*/
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color);
/* ====== 图标与点阵字模外部数据声明 ====== */
// 这些数据实体均定义在底层字库 lcd_font.h 文件中
extern unsigned char code FONT_8x16[95][16]; // 8x16 英文 ASCII 点阵字库
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 烟感喇叭警示图标
/* ====== 图标外部点阵数据声明 ====== */
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__

View File

@@ -1,17 +1,17 @@
#include "rf.h"
#include "../App/config.h"
#include "../App/rgb.h"
#include "intrins.h"
// ATR5179 射频前端天线开关控制引脚定义
sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到发射 TX 路径)
sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到接收 RX 路径)
// ATR5179 <EFBFBD><EFBFBD>Ƶǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߿<EFBFBD><EFBFBD>ؿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŷ<EFBFBD><EFBFBD><EFBFBD>
sbit RF_TX = P2^0; // V1 <EFBFBD><EFBFBD><EFBFBD>ƶ<EFBFBD> (1: ѡͨ<D1A1><CDA8><EFBFBD>ߵ<EFBFBD><DFB5><EFBFBD><EFBFBD><EFBFBD> TX ·<EFBFBD><EFBFBD>)
sbit RF_RX = P3^7; // V2 <EFBFBD><EFBFBD><EFBFBD>ƶ<EFBFBD> (1: ѡͨ<D1A1><CDA8><EFBFBD>ߵ<EFBFBD><DFB5><EFBFBD><EFBFBD><EFBFBD> RX ·<EFBFBD><EFBFBD>)
// 射频芯片数据与电源控制引脚定义
sbit RF_TX_DAT = P3^6; // LT4455 发射调制数据输入引脚 (DIN)
sbit RF_RX_DATA = P2^1; // LR690L 接收解调数据输出引脚 (DATA),连接单片机中断管脚
sbit SHUT = P2^2; // LR690L 芯片休眠控制引脚 (1:休眠0:工作)
// <EFBFBD><EFBFBD>ƵоƬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŷ<EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD>°<EFBFBD> PCB <20><><EFBFBD>ŶԵ<C5B6><D4B5><EFBFBD><EFBFBD><EFBFBD>)
sbit RF_TX_DAT = P2^1; // LR690L <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (DIN) - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Pin 22 (P2.1)
sbit RF_RX_DATA = P3^6; // LR690L <EFBFBD><EFBFBD><EFBFBD>ս<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (DATA) - <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Pin 19 (P3.6)
// 声明外部延时及串口打印函数 (在 system.c 中定义)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ⲿ<EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>ӡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><> system.c <EFBFBD>ж<EFBFBD><EFBFBD><EFBFBD>)
extern void Delay_us(u16 us);
extern void Delay_ms(u16 ms);
extern u16 GetTimer0_Safe(void);
@@ -24,171 +24,156 @@ extern void Uart_SendHex32(u32 val);
extern void Uart_SendByte(u8 dat);
/**
* @brief 初始化无线射频芯片相关的 GPIO 引脚方向与初始状态
* @brief <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƵоƬ<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD> GPIO <20><><EFBFBD>ŷ<EFBFBD><C5B7><EFBFBD><EFBFBD><EFBFBD>״̬
*/
void RF_Init(void)
{
// 1. 配置 P2.0 (RF_TX) 和 P2.2 (SHUT) 为推挽输出模式 (M1=0, M0=1)
// 配置 P2.1 (RF_RX_DATA) 为准双向口/输入模式 (M1=0, M0=0)
P2M1 &= ~((1 << 0) | (1 << 1) | (1 << 2));
P2M0 &= ~(1 << 1);
P2M0 |= ((1 << 0) | (1 << 2));
// EAXFR = 1 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>չ<EFBFBD><D5B9><EFBFBD><EFBFBD>ܼĴ<DCBC><C4B4><EFBFBD> (XSFR)
EAXFR = 1;
// 2. 配置 P3.6 (RF_TX_DAT) 和 P3.7 (RF_RX) 为推挽输出模式 (M1=0, M0=1)
P3M1 &= ~((1 << 6) | (1 << 7));
P3M0 |= ((1 << 6) | (1 << 7));
// 1. <EFBFBD><EFBFBD><EFBFBD><EFBFBD> P2.0 (RF_TX) <20><> P2.1 (RF_TX_DAT) Ϊ<><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ (M1=0, M0=1)
P2M1 &= ~((1 << 0) | (1 << 1));
P2M0 |= ((1 << 0) | (1 << 1));
// 3. 设置系统默认的初始电平
RF_TX = 0; // 断开发射天线通路
SHUT = 1; // 默认拉高 SHUT使 LR690L 接收芯片处于低功耗休眠关闭状态
RF_RX = 0; // 断开接收天线通路
RF_TX_DAT = 0; // 调制发送数据线置低
// 2. <20><><EFBFBD><EFBFBD> P3.7 (RF_RX) <20><> P3.5 (SHUT) Ϊ<><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ (M1=0, M0=1)
// <20><><EFBFBD><EFBFBD> P3.6 (RF_RX_DATA) Ϊ<><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ (M1=1, M0=0)<29><>ƥ<EFBFBD><C6A5><EFBFBD><EFBFBD><EFBFBD>ս<EFBFBD><D5BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
P3M1 &= ~((1 << 5) | (1 << 7));
P3M1 |= (1 << 6);
P3M0 &= ~(1 << 6);
P3M0 |= ((1 << 5) | (1 << 7));
// 3. <20><><EFBFBD><EFBFBD>ϵͳĬ<CDB3>ϵij<CFB5>ʼ<EFBFBD><CABC>ƽ
RF_TX = 0; // <20>Ͽ<EFBFBD><CFBF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ·
SHUT = 1; // Ĭ<><C4AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SHUT<55><54>ʹ LR690L <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߹ر<DFB9>״̬
RF_RX = 0; // <20>Ͽ<EFBFBD><CFBF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ·
RF_TX_DAT = 0; // <20><><EFBFBD>Ʒ<EFBFBD><C6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD>
}
/**
* @brief 配置无线射频收发系统的运行模式与天线切换
* @param mode 运行模式 (0-休眠隔离, 1-接收工作, 2-发射工作)
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD>շ<EFBFBD>ϵͳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>л<EFBFBD>
* @param mode <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ (0-<2D><><EFBFBD>߸<EFBFBD><DFB8><EFBFBD>, 1-<2D><><EFBFBD>չ<EFBFBD><D5B9><EFBFBD>, 2-<2D><><EFBFBD><EFBFBD><E4B9A4>)
*/
void RF_SetMode(u8 mode)
{
if (mode == 0) // ====== 模式 0: 休眠隔离模式 (Sleep/Idle) ======
if (mode == 0) // ====== ģʽ 0: <EFBFBD><EFBFBD><EFBFBD>߸<EFBFBD><EFBFBD><EFBFBD>ģʽ (Sleep/Idle) ======
{
SHUT = 1; // 强制拉高 SHUT 引脚,使 LR690L 彻底进入低功耗待机
RF_RX = 0; // 关闭接收天线开关,防止外界电磁杂波串入接收电路
RF_TX = 0; // 关闭发射天线开关
SHUT = 1; // ǿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SHUT <20><><EFBFBD>ţ<EFBFBD>ʹ LR690L <20><><EFBFBD>׽<EFBFBD><D7BD><EFBFBD><EFBFBD>͹<EFBFBD><CDB9>Ĵ<EFBFBD><C4B4><EFBFBD>
RF_RX = 0; // <EFBFBD>رս<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߿<EFBFBD><EFBFBD>أ<EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>յ<EFBFBD>·
RF_TX = 0; // <EFBFBD>رշ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߿<EFBFBD><EFBFBD><EFBFBD>
}
else if (mode == 1) // ====== 模式 1: 射频接收模式 (Rx Mode) ======
else if (mode == 1) // ====== ģʽ 1: <EFBFBD><EFBFBD><EFBFBD>չ<EFBFBD><EFBFBD><EFBFBD>ģʽ (RX Mode) ======
{
SHUT = 0; // 拉低 SHUT,启动并使能 LR690L 接收解调芯片工作
RF_RX = 1; // 选通天线切换开关到 RX 路径,连通物理接收天线
RF_TX = 0; // 断开 TX 发射路径,防止回环烧毁前端
SHUT = 0; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD> SHUT ʹ<><CAB9> LR690L оƬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
RF_TX = 0; // <EFBFBD>رշ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
RF_RX = 1; // ѡͨ<EFBFBD><EFBFBD><EFBFBD>ߵ<EFBFBD> RX <20><><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD>ʹ<EFBFBD>ܽ<EFBFBD><DCBD><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7>Ƶ<EFBFBD>Ŵ<EFBFBD>
}
else if (mode == 2) // ====== 模式 2: 射频发射模式 (Tx Mode) ======
else if (mode == 2) // ====== ģʽ 2: <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ (TX Mode) ======
{
SHUT = 1; // 关闭 LR690L 接收芯片,避免发射大功率波形串入接收机
RF_RX = 0; // 断开接收路径天线
RF_TX = 1; // 选通天线切换开关至 TX 路径,连通物理天线进行发射
SHUT = 1; // <EFBFBD><EFBFBD><EFBFBD>ջ<EFBFBD>ͣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǿ<EFBFBD>źŶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD>
RF_RX = 0; // <EFBFBD>رս<EFBFBD><EFBFBD>տ<EFBFBD><EFBFBD><EFBFBD>
RF_TX = 1; // ѡͨ<EFBFBD><EFBFBD><EFBFBD>ߵ<EFBFBD> TX <20><><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD>߷<EFBFBD><DFB7><EFBFBD>
}
}
/**
* @brief EV1527 载波规范单次调制发送一帧 24 位无线射频数据
* @param addr 20 位的发射地址编码
* @param dat 4 位的命令状态码
* @details EV1527 是常见的无线编码格式:
* - 一个基本脉冲周期设为 T ≈ 320us。
* - 同步头脉冲: 1T 高电平 (320us) + 31T 低电平 (9920us)。
* - 数据位 '1' (逻辑1) 编码: 3T 高电平 (960us) + 1T 低电平 (320us)。
* - 数据位 '0' (逻辑0) 编码: 1T 高电平 (320us) + 3T 低电平 (960us)。
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD> EV1527 Э<EFBFBD><EFBFBD>ε<EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD>һ֡ 24 λ<><CEBB>Ƶ<EFBFBD><C6B5><EFBFBD><EFBFBD> (Sync + 24bit Data)
*/
void EV1527_TxFrame(u32 addr, u8 dat)
{
u8 i;
u32 tx_val;
// ====== 1. 发送同步引导脉冲 ======
// <EFBFBD>ϲ<EFBFBD> 20 λ<><CEBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7> 4 λ<><CEBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EBA3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 24 λ<>ز<EFBFBD><D8B2><EFBFBD>
tx_val = (addr << 4) | (dat & 0x0F);
// 1. ͬ<><CDAC>ͷ<EFBFBD><CDB7><EFBFBD>ͣ<EFBFBD><CDA3>ߵ<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD> 1T (Լ 350us)<29><><EFBFBD>͵<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD> 31T (Լ 10850us)
RF_TX_DAT = 1;
Delay_us(320); // 1T 高电平
Delay_us(350);
RF_TX_DAT = 0;
Delay_us(9920); // 31T 低电平
// ====== 2. 依次调制发送 20 位物理地址码 (高位 MSB 优先发送) ======
for (i = 0; i < 20; i++)
Delay_us(10850);
// 2. <20><><EFBFBD>ε<EFBFBD><CEB5>Ʒ<EFBFBD><C6B7><EFBFBD> 24 λ<><CEBB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ
for (i = 0; i < 24; i++)
{
if ((addr >> (19 - i)) & 1)
if (tx_val & (0x800000UL >> i))
{
// 数据 bit 1: 3T高 + 1T低
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> "1"<22><><EFBFBD>ߵ<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD> 3T (1050us)<29><><EFBFBD>͵<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD> 1T (350us)
RF_TX_DAT = 1;
Delay_us(960);
Delay_us(1050);
RF_TX_DAT = 0;
Delay_us(320);
Delay_us(350);
}
else
{
// 数据 bit 0: 1T高 + 3T低
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> "0"<22><><EFBFBD>ߵ<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD> 1T (350us)<29><><EFBFBD>͵<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD> 3T (1050us)
RF_TX_DAT = 1;
Delay_us(320);
Delay_us(350);
RF_TX_DAT = 0;
Delay_us(960);
}
}
// ====== 3. 依次调制发送 4 位指令状态数据码 (高位 MSB 优先) ======
for (i = 0; i < 4; i++)
{
if ((dat >> (3 - i)) & 1)
{
// 数据 bit 1: 3T高 + 1T低
RF_TX_DAT = 1;
Delay_us(960);
RF_TX_DAT = 0;
Delay_us(320);
}
else
{
// 数据 bit 0: 1T高 + 3T低
RF_TX_DAT = 1;
Delay_us(320);
RF_TX_DAT = 0;
Delay_us(960);
Delay_us(1050);
}
}
}
/**
* @brief 循环发射多帧无线 EV1527 信号以保证数据通信的完整性
* @param addr 20位源地址
* @param dat 4位命令码
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>η<EFBFBD><EFBFBD><EFBFBD> EV1527 <20><>Ƶ֡<C6B5><D6A1><EFBFBD><EFBFBD>ֹ<EFBFBD><D6B9>֡<EFBFBD><D6A1>ʧ
*/
void EV1527_Transmit(u32 addr, u8 dat)
{
u8 r;
// 循环发射 25 次相同的射频帧 (累计耗时约 340 毫秒),抵御外界瞬间同频杂波干扰
for (r = 0; r < 25; r++)
u8 i;
RF_SetMode(2); // <20>л<EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD>״̬<D7B4><CCAC><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ն<EFBFBD>
Delay_ms(5); // <20>ȶ<EFBFBD><C8B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶͨ·
// ѭ<><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 10 ֡<><D6A1>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD>ն<EFBFBD><D5B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1 ֡<><D6A1><EFBFBD>޲<EFBFBD><DEB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for (i = 0; i < 10; i++)
{
EV1527_TxFrame(addr, dat);
Delay_ms(10); // ֡<><EFBFBD><E4B1A3><EFBFBD><EFBFBD>ʱ
}
RF_TX_DAT = 0; // <20><><EFBFBD><EFBFBD><E3B7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
RF_SetMode(1); // <20><><EFBFBD>»ָ<C2BB>Ϊ<EFBFBD><CEAA><EFBFBD>ռ<EFBFBD><D5BC><EFBFBD><EFBFBD><EFBFBD>̬<EFBFBD><CCAC><EFBFBD>Ա<EFBFBD><D4B1><EFBFBD><EFBFBD><EFBFBD><E2B7B4>
}
/**
* @brief 使用定时器 0 (Timer0) 高精度的测量引脚指定电平状态的持续宽度时间
* @param state 待测电平状态 (1-测量高电平持续宽度, 0-测量低电平持续宽度)
* @param timeout_us 最大等待超时微秒数上限,超时自动退出防锁死
* @return u16 最终测量的实际电平持续微秒数。若发生超时则直接返回 0。
* @brief <EFBFBD><EFBFBD>ȡ<EFBFBD>ض<EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>״̬<EFBFBD>ij<EFBFBD><EFBFBD><EFBFBD>΢<EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD> Timer0 Ӳ<><D3B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>)
*/
u16 GetPulseDuration(u8 state, u16 timeout_us)
{
// 根据系统晶振频率计算在 12T 模式下目标超时的 Timer0 Tick 数值上限
u16 timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL);
TL0 = 0; // 定时器 0 计数值清零
TH0 = 0;
TR0 = 1; // 启动定时器 0 硬件计数
u16 timeout_ticks;
// 轮询引脚:只要电平仍旧等于目标待测状态,就继续保持循环
// ǿ<EFBFBD>ƹر<EFBFBD> Timer0 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD>㸴λ
TR0 = 0;
TMOD &= 0xF0; // <20><><EFBFBD><EFBFBD> Timer0 <20>Ĵ<EFBFBD><C4B4><EFBFBD>Ϊģʽ 0 (16λ<36>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>)
TL0 = 0x00;
TH0 = 0x00;
// <20><><EFBFBD><EFBFBD> 16 λ<><CEBB>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ticks <20><>ʱֵ (<28><> 1T ģʽ 24MHz <20>£<EFBFBD>1 Tick = 1 / 24M = 0.0416us)
// <20><><EFBFBD><EFBFBD>ϵͳ Timer0 Ĭ<><C4AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ 12T ģʽ (1 Tick = 12 / 24MHz = 0.5us)
timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL);
TR0 = 1; // <20><><EFBFBD><EFBFBD> Timer0 Ӳ<><D3B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// <20><><EFBFBD><EFBFBD>Ҫ<EFBFBD><D2AA><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD>ƽ״̬<D7B4><CCAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>жϣ<D0B6>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ת<EFBFBD><D7AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ
while (RF_RX_DATA == state)
{
// 软件防卡死判定:如果当前计数值超过了设定的最大允许超时 Tick 值
if (GetTimer0_Safe() > timeout_ticks)
{
TR0 = 0; // 关断定时器
return 0; // 判定超时,返回 0
TR0 = 0;
return 0; // <EFBFBD>ж<EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0
}
}
TR0 = 0; // 关断定时器
TR0 = 0; // <EFBFBD>ض϶<EFBFBD>ʱ<EFBFBD><EFBFBD>
// 将最终读取的 Timer0 硬件 Tick 数值,重新换算成微秒数返回 (1 Tick = 12 / 24MHz = 0.5us)
return (u16)((u32)GetTimer0_Safe() * 12UL / (MAIN_Fosc / 1000000UL));
}
// ģ<><C4A3><EFBFBD><EFBFBD>Ƶ<EFBFBD>ź<EFBFBD>ȫ<EFBFBD>ֱ<EFBFBD><D6B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
volatile u32 mock_rx_addr = 0;
volatile u8 mock_rx_data = 0;
volatile bit mock_rf_ready = 0;
/**
* @brief 软件自适应解码接收一帧无线 EV1527 信号
* @param out_addr 解码成功后,用于回传物理地址高 20 位的指针
* @param out_data 解码成功后,用于回传指令数据低 4 位的指针
* @return bit 1-解码成功且校验通过0-捕获失败或脉冲畸变
* @note 核心算法:
* 1. 等待并寻找合法的同步引导头脉冲 (1T高电平 + 31T低电平其中低/高比率约为 31本程序允许比率在 15~48 内变化以抗物理畸变)。
* 2. 依据捕获的同步低电平宽度估算基准周期 T = low_time / 31。
* 3. 随后依次高精度捕获 24 个数据位的高电平和低电平时间。
* 4. 自适应决策:对于每个 bit如果其高电平时间 raw_h > 低电平时间 raw_l则判定该位为 1否则判定该位为 0。
* @brief <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD>Ϸ<EFBFBD><EFBFBD><EFBFBD> EV1527 <20><>Ƶ<EFBFBD>ź<EFBFBD><C5BA><EFBFBD><EFBFBD><EFBFBD>֡
*/
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
{
@@ -197,229 +182,156 @@ bit EV1527_Decode(u32 *out_addr, u8 *out_data)
u32 addr = 0;
u8 dat = 0;
u16 T;
u16 idata raw_h[24]; // 高电平时间寄存数组 (置于 idata 区)
u16 idata raw_l[24]; // 低电平时间寄存数组
u16 idata raw_h[24];
u16 idata raw_l[24];
// ====== 1. 同步头引导脉冲捕获 (定位上升沿) ======
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (mock_rf_ready) {
mock_rf_ready = 0;
*out_addr = mock_rx_addr;
*out_data = mock_rx_data;
return 1;
}
// ====== 1. ͬ<><CDAC>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E5B2B6> ======
{
u16 wait_cnt = 0;
// 如果当前射频解调输出是高电平,等待其变低
while (RF_RX_DATA == 1)
{
Delay_us(5);
wait_cnt++;
if (wait_cnt > 2000) // 10 毫秒超时保护
if (wait_cnt > 2000) // 10 <EFBFBD><EFBFBD><EFBFBD>볬ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
return 0;
}
// 当引脚维持在低电平后,阻塞等待它跳变为高电平(捕获到同步引导信号的上升沿)
wait_cnt = 0;
while (RF_RX_DATA == 0)
{
Delay_us(5);
wait_cnt++;
if (wait_cnt > 4000) // 20 毫秒超时保护
if (wait_cnt > 4000) // 20 <EFBFBD><EFBFBD><EFBFBD>볬ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
return 0;
}
}
// 捕获引导头高电平时间 (放宽测量阈值上限至 2500 微秒)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͷ<EFBFBD>ߵ<EFBFBD>ƽʱ<EFBFBD><EFBFBD>
high_time = GetPulseDuration(1, 2500);
if (high_time < 50 || high_time > 2500) {
if (high_time > 10) { // 剔除高频毛刺噪声,防止调试串口满载
Uart_SendString("Sync H Err: ");
Uart_SendHex16(high_time);
Uart_SendString("us\r\n");
}
return 0;
}
// 捕获引导头后续的 31T 低电平时间 (设定上限 60 毫秒)
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 31T <20>͵<EFBFBD>ƽʱ<C6BD><CAB1>
low_time = GetPulseDuration(0, 60000);
if (low_time < 1500 || low_time > 60000) {
Uart_SendString("Sync L Err H=");
Uart_SendHex16(high_time);
Uart_SendString(" L=");
Uart_SendHex16(low_time);
Uart_SendString("us\r\n");
return 0;
}
// 计算低电平与高电平的比值,以检验是否满足 EV1527 的 31:1 特征
{
u32 ratio = (u32)low_time / high_time;
if (ratio < 15 || ratio > 48) { // 允许 50% 左右的空气无线传输畸变误差
Uart_SendString("Sync Ratio Err H=");
Uart_SendHex16(high_time);
Uart_SendString(" L=");
Uart_SendHex16(low_time);
Uart_SendString(" R=");
Uart_SendHex16((u16)ratio);
Uart_SendString("\r\n");
return 0;
}
}
// ͬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD>׼ʱ<EFBFBD><EFBFBD> T <20><><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ߵ<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD>ȼ<EFBFBD>Ϊ 1T)
T = high_time;
if (T < 100 || T > 800) return 0; // <20><><EFBFBD><EFBFBD>Ӧ<EFBFBD><D3A6>Χ<EFBFBD>޷<EFBFBD>: 100us ~ 800us
// 自适应估算出基准脉冲时值 T
T = low_time / 31;
if (T == 0) T = 1; // 零安全防护
// ====== 2. 依次采集接下来的 24 个数据 bit 脉冲 ======
// ====== 2. <20><><EFBFBD>ν<EFBFBD><CEBD><EFBFBD> 24 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><CEBB><EFBFBD><EFBFBD> ======
for (i = 0; i < 24; i++)
{
u16 wait_cnt = 0;
// 等待下一个数据位跳变产生的上升沿 (低电平变高)
while (RF_RX_DATA == 0)
{
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_h[i] = GetPulseDuration(1, 2000);
if (raw_h[i] == 0) return 0;
raw_l[i] = GetPulseDuration(0, 5000);
raw_l[i] = GetPulseDuration(0, 2000);
if (raw_l[i] == 0) return 0;
}
// ====== 3. 串口输出捕获的时序参数 ======
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 位完整射频数据帧 ======
// ====== 3. ˫<EFBFBD>˱<EFBFBD>ֵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ж<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ======
for (i = 0; i < 24; i++)
{
// 比较本位高低电平持续宽度:
// 如果高电平大于低电平,说明宽脉冲在高,判定该位为 1 (3T高+1T低)。
// 否则高电平短,判定为 0 (1T高+3T低)。
if (raw_h[i] > raw_l[i])
u16 total = raw_h[i] + raw_l[i];
if (total < T * 3 || total > T * 6) return 0; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><CEBB><EFBFBD>ڱ<EFBFBD><DAB1><EFBFBD><EFBFBD><EFBFBD> 3T~6T ֮<><D6AE>
if (raw_h[i] > raw_l[i] * 2)
{
addr = (addr << 1) | 1; // 写入 24 位物理地址与状态数据相结合的最终值
// <20>ߵ<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD>Գ<EFBFBD><D4B3>ڵ͵<DAB5>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߼<EFBFBD> "1"
if (i < 20)
addr = (addr << 1) | 1;
else
dat = (dat << 1) | 1;
}
else if (raw_l[i] > raw_h[i] * 2)
{
// <20>͵<EFBFBD>ƽ<EFBFBD><C6BD><EFBFBD>Գ<EFBFBD><D4B3>ڸߵ<DAB8>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߼<EFBFBD> "0"
if (i < 20)
addr = (addr << 1);
else
dat = (dat << 1);
}
else
{
addr = (addr << 1);
return 0; // <20><>ƽ<EFBFBD><C6BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><D6B5><EFBFBD>Ϲ<CFB9>ж<EFBFBD><D0B6><EFBFBD><EFBFBD><EFBFBD>֡<EFBFBD><D6A1><EFBFBD><EFBFBD><E4A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
}
}
*out_addr = addr; // 输出包含状态数据的 24 位完整射频编码作为传感器唯一 ID
*out_data = (u8)(addr & 0x0F); // 输出低 4 位作为指令/状态,保持调用兼容性
// 串口打印解码最终结果
Uart_SendString("Decoded 24bit ADDR: 0x");
Uart_SendHex32(addr);
Uart_SendString("\r\n");
return 1; // 成功解析
// У<><D0A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
*out_addr = addr;
*out_data = dat;
return 1;
}
/**
* @brief 软硬件环路自检回环测试
* @details 将发射引脚置位并同时采样接收引脚,用于测试收发芯片控制连通性。
* @brief RF <20>շ<EFBFBD><D5B7><EFBFBD><EFBFBD>ϲ<EFBFBD><CFB2>Թ<EFBFBD><D4B9><EFBFBD><>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD><D5BC><EFBFBD><EFBFBD><EFBFBD>ӡ)
*/
void Loopback_Test(void)
void RF_DiagnosticMode(char choice)
{
u16 match_count = 0;
u16 i;
SHUT = 0; // 强行开启接收芯片供电
RF_RX = 1; // 选通天线到 RX
RF_TX = 0; // 发送引脚隔离
Delay_ms(100);
Uart_SendString("Starting RF Loopback Self-Test...\r\n");
// 高频翻转模拟发送引脚,同步读取接收解调出的管脚信号,统计匹配率
for (i = 0; i < 100; i++)
if (choice == 't')
{
RF_TX_DAT = 1;
Delay_us(500);
if (RF_RX_DATA == 1) match_count++;
RF_TX_DAT = 0;
Delay_us(500);
if (RF_RX_DATA == 0) match_count++;
}
Uart_SendString("Loopback Match Count: ");
Uart_SendHex16(match_count);
Uart_SendString("/200\r\n");
if (match_count > 150)
{
Uart_SendString("Result: PASS! Transmitter is emitting RF, and Receiver is picking it up!\r\n");
}
else
{
Uart_SendString("Result: FAIL! No RF signal detected. Check hardware.\r\n");
}
SHUT = 1; // 关闭接收芯片
RF_RX = 0; // 断开接收通道
}
u8 i;
Uart_SendString("=== RF TX Test: Transmitting 20 test frames... ===\r\n");
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߿<EFBFBD><DFBF><EFBFBD>
RF_SetMode(2);
Delay_ms(5);
/**
* @brief 统计接收引脚在 5 秒内的电平翻转次数,用以评估物理噪声污染度或管脚短路
*/
void RF_Test_Pin(void)
{
u8 i;
u16 j;
u16 transitions = 0;
bit last_state = RF_RX_DATA;
Uart_SendString("=== RF RX Pin Data Test Start (5 seconds) ===\r\n");
Uart_SendString("SHUT pin state: ");
Uart_SendByte(SHUT ? '1' : '0');
Uart_SendString("\r\n");
// 强制设为接收模式
RF_SetMode(1);
Delay_ms(100);
Uart_SendString("SHUT pin state (after RF_SetMode(1)): ");
Uart_SendByte(SHUT ? '1' : '0');
Uart_SendString("\r\n");
Uart_SendString("Please trigger your 433MHz transmitter now...\r\n");
// 连续进行 5 秒的高频电平抖动采样
for (i = 0; i < 5; i++)
{
transitions = 0;
// 1秒循环
for (j = 0; j < 1000; j++)
for (i = 0; i < 20; i++)
{
u8 k;
// 每次内循环进行 5 次采样,每次间隔 200us
for (k = 0; k < 5; k++)
{
bit current_state = RF_RX_DATA;
if (current_state != last_state)
{
transitions++; // 统计电平边沿翻转次数
last_state = current_state;
}
Delay_us(200);
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͳ<EFBFBD><CDB2>԰<EFBFBD><D4B0><EFBFBD>ÿ<EFBFBD>η<EFBFBD><CEB7>Ͱ<EFBFBD><CDB0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ָʾ<D6B8><CABE><EFBFBD><EFBFBD>˸
MOTOR = 1;
RGB_Send(0, 150, 0, 0, 150, 0); // <20>̹<EFBFBD><CCB9><EFBFBD>˸
EV1527_TxFrame(0x37A86UL, 0x01);
MOTOR = 0;
RGB_Send(0, 0, 0, 0, 0, 0);
Delay_ms(150); // ֡<><D6A1><EFBFBD><EFBFBD>
}
// 输出本秒的电平跳转总数,若非发射期该数值很大,表明空中有丰富噪声噪底跳变,属接收机正常表现
Uart_SendString("Sampling... Transitions in last 1s: ");
Uart_SendHex16(transitions);
Uart_SendString(", current pin value: ");
Uart_SendByte(RF_RX_DATA ? '1' : '0');
Uart_SendString("\r\n");
RF_TX_DAT = 0;
RF_SetMode(1); // <20><><EFBFBD>»ָ<C2BB><D6B8><EFBFBD><EFBFBD><EFBFBD>ģʽ
Uart_SendString("[RF] Transmit Done!\r\n");
}
else if (choice == 'r')
{
u16 wait_ms;
u32 rx_addr;
u8 rx_data;
Uart_SendString("=== RF RX Test: Listening for 5 seconds... ===\r\n");
RF_SetMode(1); // <20><><EFBFBD>ý<EFBFBD><C3BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Delay_ms(10);
// 5<><35><EFBFBD>ķ<EFBFBD><C4B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
for (wait_ms = 0; wait_ms < 5000; wait_ms++)
{
if (EV1527_Decode(&rx_addr, &rx_data))
{
Uart_SendString("[RF] Decoded successfully! Addr: 0x");
Uart_SendHex32(rx_addr);
Uart_SendString(", Cmd/Data: ");
Uart_SendHex8(rx_data);
Uart_SendString("\r\n");
// <20>յ<EFBFBD><D5B5>źź<C5BA><C5BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𶯲<EFBFBD><F0B6AFB2><EFBFBD><EFBFBD><EFBFBD>ɫ<EFBFBD><C9AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
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");
}
Uart_SendString("=== RF Pin Test End ===\r\n");
}

View File

@@ -52,18 +52,13 @@ u16 GetPulseDuration(u8 state, u16 timeout_us);
* @param out_addr 输出参数指针:若解码成功,将写入解析得到的 20 位发送地址 (即 24 位帧除去 4 位数据位后的剩余高 20 位)
* @param out_data 输出参数指针:若解码成功,将写入解析出的 4 位命令状态数据码 (例如报警/SOS等)
* @return bit 1-成功解析出一帧有效数据0-当前未捕获到合法脉冲或校验失败
* @note 此函数通过对 P2.1 (RF_RX_DATA) 输入脉冲宽度进行软件精密测量,定位同步头,依次采集 24 个数据位,并进行 3 次重复性去噪校验。
* @note 此函数通过对 P3.6 (RF_RX_DATA) 输入脉冲宽度进行软件精密测量,定位同步头,依次采集 24 个数据位,并进行 3 次重复性去噪校验。
*/
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
/**
* @brief 串口与射频功能的回环测试函数 (调试使用)
* @brief RF 收发诊断测试功能 (支持发送与接收监听打印)
*/
void Loopback_Test(void);
/**
* @brief 射频控制引脚的高低电平输出极性测试 (硬件排查使用)
*/
void RF_Test_Pin(void);
void RF_DiagnosticMode(char choice);
#endif // __RF_H__

View File

@@ -1 +0,0 @@
{"hostname":"DESKTOP-0S8JI7H","username":"xtell"}

View File

@@ -1 +0,0 @@
{"hostname":"DESKTOP-0S8JI7H","username":"xtell"}

View File

@@ -1,296 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<ProjectOpt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="project_opt.xsd">
<SchemaVersion>1.0</SchemaVersion>
<Header>### uVision Project, (C) Keil Software</Header>
<Extensions>
<cExt>*.c</cExt>
<aExt>*.s*; *.src; *.a*</aExt>
<oExt>*.obj; *.o</oExt>
<lExt>*.lib</lExt>
<tExt>*.txt; *.h; *.inc; *.md</tExt>
<pExt>*.plm</pExt>
<CppX>*.cpp</CppX>
<nMigrate>0</nMigrate>
</Extensions>
<DaveTm>
<dwLowDateTime>0</dwLowDateTime>
<dwHighDateTime>0</dwHighDateTime>
</DaveTm>
<Target>
<TargetName>Wristband</TargetName>
<ToolsetNumber>0x1</ToolsetNumber>
<ToolsetName>MCS-251</ToolsetName>
<TargetOption>
<CLK251>35000000</CLK251>
<OPTTT>
<gFlags>1</gFlags>
<BeepAtEnd>1</BeepAtEnd>
<RunSim>1</RunSim>
<RunTarget>0</RunTarget>
<RunAbUc>0</RunAbUc>
</OPTTT>
<OPTHX>
<HexSelection>1</HexSelection>
<FlashByte>65535</FlashByte>
<HexRangeLowAddress>0</HexRangeLowAddress>
<HexRangeHighAddress>0</HexRangeHighAddress>
<HexOffset>0</HexOffset>
</OPTHX>
<OPTLEX>
<PageWidth>120</PageWidth>
<PageLength>65</PageLength>
<TabStop>8</TabStop>
<ListingPath>.\Build\</ListingPath>
</OPTLEX>
<ListingPage>
<CreateCListing>1</CreateCListing>
<CreateAListing>1</CreateAListing>
<CreateLListing>1</CreateLListing>
<CreateIListing>0</CreateIListing>
<AsmCond>1</AsmCond>
<AsmSymb>1</AsmSymb>
<AsmXref>0</AsmXref>
<CCond>1</CCond>
<CCode>0</CCode>
<CListInc>0</CListInc>
<CSymb>0</CSymb>
<LinkerCodeListing>0</LinkerCodeListing>
</ListingPage>
<OPTXL>
<LMap>1</LMap>
<LComments>1</LComments>
<LGenerateSymbols>1</LGenerateSymbols>
<LLibSym>1</LLibSym>
<LLines>1</LLines>
<LLocSym>1</LLocSym>
<LPubSym>1</LPubSym>
<LXref>0</LXref>
<LExpSel>0</LExpSel>
</OPTXL>
<OPTFL>
<tvExp>1</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<IsCurrentTarget>1</IsCurrentTarget>
</OPTFL>
<CpuCode>255</CpuCode>
<DebugOpt>
<uSim>1</uSim>
<uTrg>0</uTrg>
<sLdApp>1</sLdApp>
<sGomain>1</sGomain>
<sRbreak>1</sRbreak>
<sRwatch>1</sRwatch>
<sRmem>1</sRmem>
<sRfunc>1</sRfunc>
<sRbox>1</sRbox>
<tLdApp>1</tLdApp>
<tGomain>0</tGomain>
<tRbreak>1</tRbreak>
<tRwatch>1</tRwatch>
<tRmem>1</tRmem>
<tRfunc>0</tRfunc>
<tRbox>1</tRbox>
<tRtrace>1</tRtrace>
<sRSysVw>1</sRSysVw>
<tRSysVw>1</tRSysVw>
<sRunDeb>0</sRunDeb>
<sLrtime>0</sLrtime>
<bEvRecOn>1</bEvRecOn>
<bSchkAxf>0</bSchkAxf>
<bTchkAxf>0</bTchkAxf>
<nTsel>-1</nTsel>
<sDll></sDll>
<sDllPa></sDllPa>
<sDlgDll></sDlgDll>
<sDlgPa></sDlgPa>
<sIfile></sIfile>
<tDll></tDll>
<tDllPa></tDllPa>
<tDlgDll></tDlgDll>
<tDlgPa></tDlgPa>
<tIfile></tIfile>
<pMon></pMon>
</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/>
<Tracepoint>
<THDelay>0</THDelay>
</Tracepoint>
<DebugFlag>
<trace>0</trace>
<periodic>0</periodic>
<aLwin>1</aLwin>
<aCover>0</aCover>
<aSer1>0</aSer1>
<aSer2>0</aSer2>
<aPa>0</aPa>
<viewmode>1</viewmode>
<vrSel>0</vrSel>
<aSym>0</aSym>
<aTbox>0</aTbox>
<AscS1>0</AscS1>
<AscS2>0</AscS2>
<AscS3>0</AscS3>
<aSer3>0</aSer3>
<eProf>0</eProf>
<aLa>0</aLa>
<aPa1>0</aPa1>
<AscS4>0</AscS4>
<aSer4>0</aSer4>
<StkLoc>0</StkLoc>
<TrcWin>0</TrcWin>
<newCpu>0</newCpu>
<uProt>0</uProt>
</DebugFlag>
<LintExecutable></LintExecutable>
<LintConfigFile></LintConfigFile>
<bLintAuto>0</bLintAuto>
<bAutoGenD>0</bAutoGenD>
<LntExFlags>0</LntExFlags>
<pMisraName></pMisraName>
<pszMrule></pszMrule>
<pSingCmds></pSingCmds>
<pMultCmds></pMultCmds>
<pMisraNamep></pMisraNamep>
<pszMrulep></pszMrulep>
<pSingCmdsp></pSingCmdsp>
<pMultCmdsp></pMultCmdsp>
</TargetOption>
</Target>
<Group>
<GroupName>App</GroupName>
<tvExp>1</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel>
<RteFlg>0</RteFlg>
<File>
<GroupNumber>1</GroupNumber>
<FileNumber>1</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>.\App\main.c</PathWithFileName>
<FilenameWithoutPath>main.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>1</GroupNumber>
<FileNumber>2</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>3</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>4</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>5</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>6</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>7</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>
</Group>
<Group>
<GroupName>Drivers</GroupName>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<cbSel>0</cbSel>
<RteFlg>0</RteFlg>
<File>
<GroupNumber>2</GroupNumber>
<FileNumber>8</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>.\Drivers\lcd.c</PathWithFileName>
<FilenameWithoutPath>lcd.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
<File>
<GroupNumber>2</GroupNumber>
<FileNumber>9</FileNumber>
<FileType>1</FileType>
<tvExp>0</tvExp>
<tvExpOptDlg>0</tvExpOptDlg>
<bDave2>0</bDave2>
<PathWithFileName>.\Drivers\rf.c</PathWithFileName>
<FilenameWithoutPath>rf.c</FilenameWithoutPath>
<RteFlg>0</RteFlg>
<bShared>0</bShared>
</File>
</Group>
</ProjectOpt>

View File

@@ -359,6 +359,41 @@
<FileType>1</FileType>
<FilePath>.\App\system.c</FilePath>
</File>
<File>
<FileName>clock.c</FileName>
<FileType>1</FileType>
<FilePath>.\App\clock.c</FilePath>
</File>
<File>
<FileName>app_clock.c</FileName>
<FileType>1</FileType>
<FilePath>.\App\app_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_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>
</Group>
<Group>