docs: 为所有代码源文件添加详细中文注释,并在设计文档中纠正GPIO物理引脚映射表

This commit is contained in:
Antigravity IDE
2026-07-16 17:52:52 +08:00
parent afd59d28ee
commit 731115e09a
26 changed files with 32803 additions and 53495 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,56 @@
#include "config.h" #include "config.h"
// AppManager 核心接口 /* ====== 应用程序管理器 (AppManager) 核心接口 ====== */
/**
* @brief 初始化应用程序管理器
* @details 负责将应用管理器的系统注册表清零,随后逐一注册系统支持的五个前台应用:
* ClockApp (待机时钟), MenuApp (主菜单), PairApp (传感器对码),
* AlarmApp (传感器警报), SOSApp (主动求救呼叫)。最后默认加载并启动 ClockApp。
*/
void AppManager_Init(void); 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() 初始化回调以绘制新页面。
*/
void AppManager_StartApp(AppID app_id); void AppManager_StartApp(AppID app_id);
/**
* @brief 向当前活跃的前台应用程序派发事件
* @param evt 要分发派送的系统事件
*/
void AppManager_DispatchEvent(SystemEvent evt); void AppManager_DispatchEvent(SystemEvent evt);
/**
* @brief 在系统主循环中持续轮询运行活跃应用
* @details 负责从全局注册表中定位到 active_app并轮询执行其 onRun() 周期回调。
* 该接口同时集成了【CPU占用率限制机制】如果在调用 onRun 前后,检测到应用
* 占用 CPU 时间超过了 APP_ONRUN_MAX_TICKS (5ms),它将在下一次循环中暂时挂起
* 该应用,强制释放本次 CPU 占用以防止界面卡死或影响底层射频解调。
*/
void AppManager_RunActiveApp(void); void AppManager_RunActiveApp(void);
/**
* @brief 获取当前前台活跃应用的唯一应用程序 ID
* @return AppID 当前活跃前台应用的枚举ID
*/
AppID AppManager_GetActiveAppID(void); AppID AppManager_GetActiveAppID(void);
// 时间编辑状态机接口 (由 MenuApp 触发)
/* ====== 时间编辑状态机交互接口 ====== */
/**
* @brief 启动时钟数值调整编辑状态机
* @details 由 MenuApp 的 "HORLOGE" 项被确认点击后触发。用于直接将系统运行状态切入
* STATE_SET_TIME控制灯条常亮蓝色马达短振反馈并进入时间参数微调页。
*/
void Start_Time_Edit(void); void Start_Time_Edit(void);
#endif #endif // __APP_MANAGER_H__

View File

@@ -4,156 +4,171 @@
#include "stc32g.h" #include "stc32g.h"
#include "intrins.h" #include "intrins.h"
// 重新定义基础数据类型,便于跨平台和简化代码书写
typedef unsigned char u8; typedef unsigned char u8;
typedef unsigned int u16; typedef unsigned int u16;
typedef unsigned long u32; typedef unsigned long u32;
/* ====== 硬件引脚定义 (sbit) ====== */ /* ====== 硬件引脚定义 (sbit) ====== */
sbit MOTOR = P2^5; // 振动马达控制端 (推挽输出,与 SPI SCLK 物理共享引脚) // 振动马达控制端 (推挽输出,与 SPI SCLK 物理共享引脚)
sbit KEY_UP = P0^1; // 侧边按键 ▲ (高阻输入 + 上拉) sbit MOTOR = P2^5;
sbit KEY_CONFIRM = P0^2; // 侧边按键 (高阻输入 + 上拉) // 侧边按键 (高阻输入 + 上拉),按下为低电平
sbit KEY_DOWN = P0^3; // 侧边按键 ▼ (高阻输入 + 上拉) sbit KEY_UP = P0^1;
sbit KEY_SOS = P2^6; // 物理 SOS 求救按键 (高阻输入 + 上拉) // 侧边按键 (高阻输入 + 上拉),按下为低电平
sbit RGB_DIN = P2^3; // FCOB 幻彩灯条 DIN 控制线 sbit KEY_CONFIRM = P0^2;
sbit SHUT = P2^2; // LR690L 射频芯片休眠/工作控制引脚 (低使能开启工作) // 侧边按键 ▼ (高阻输入 + 上拉),按下为低电平
sbit SGM_CTRL = P3^4; // SGM3833 PMIC 屏幕负压供电使能脚 sbit KEY_DOWN = P0^3;
sbit LCD_RST = P1^0; // Truly AMOLED 屏幕复位脚 (RESET) // 物理 SOS 求救按键 (高阻输入 + 上拉),按下为低电平
sbit LCD_DCX = P1^1; // Truly AMOLED 屏幕数据/指令选择脚 (D/C) sbit KEY_SOS = P2^6;
sbit LCD_SDI = P1^4; // Truly AMOLED 屏幕 SPI 数据脚 (MOSI) // FCOB 幻彩灯条 DIN 数据控制线
sbit LCD_SCL = P1^5; // Truly AMOLED 屏幕 SPI 时钟脚 (SCLK) sbit RGB_DIN = P2^3;
sbit LCD_CS = P1^6; // Truly AMOLED 屏幕 SPI 片选脚 (CS) // LR690L 射频芯片休眠/工作控制引脚 (低电平使能开启工作)
sbit SHUT = P2^2;
// SGM3833 PMIC 屏幕负压供电使能脚 (1使能0关闭)
sbit SGM_CTRL = P3^4;
// Truly AMOLED 屏幕复位脚 (RESET),低电平复位
sbit LCD_RST = P1^0;
// Truly AMOLED 屏幕数据/指令选择脚 (D/C1:数据, 0:命令)
sbit LCD_DCX = P1^1;
// Truly AMOLED 屏幕 SPI 数据脚 (MOSI/SDA)
sbit LCD_SDI = P1^4;
// Truly AMOLED 屏幕 SPI 时钟脚 (SCLK)
sbit LCD_SCL = P1^5;
// Truly AMOLED 屏幕 SPI 片选脚 (CS),低电平有效
sbit LCD_CS = P1^6;
/* ====== 系统状态 ====== */ /* ====== 系统运行状态枚举 ====== */
typedef enum { typedef enum {
STATE_NORMAL, STATE_NORMAL, // 正常时钟待机状态
STATE_ARMED, STATE_ARMED, // 布防状态 (监测传感器警报)
STATE_DISARMED, STATE_DISARMED, // 撤防状态 (忽略普通传感器警报)
STATE_PAIR_MENU, STATE_PAIR_MENU, // 传感器对码/配置主菜单状态
STATE_PAIR_WAIT, STATE_PAIR_WAIT, // 等待传感器射频信号状态 (对码搜寻中)
STATE_PAIR_CONFIRM, STATE_PAIR_CONFIRM, // 捕获到信号,进入防区微调确认状态
STATE_PAIR_SUCCESS, STATE_PAIR_SUCCESS, // 对码成功反馈状态
STATE_PAIR_FAIL, STATE_PAIR_FAIL, // 对码失败 (超时等) 反馈状态
STATE_ALARM, STATE_ALARM, // 传感器入侵报警状态
STATE_SOS_EMITTED, STATE_SOS_EMITTED, // 处于 SOS 紧急求救发射状态
STATE_SET_TIME, // 新增:时间调整编辑状态 STATE_SET_TIME, // 时间调整编辑状态 (修改小时、分钟)
STATE_IPC_BIND, // 新增:摄像机绑定状态 STATE_IPC_BIND, // 摄像机/IPC 绑定发射信号状态
STATE_SLEEP // 新增:低功耗休眠挂起状态 STATE_SLEEP // 低功耗休眠挂起状态
} SystemState; } SystemState;
/* ====== 按键事件类型 ====== */ /* ====== 按键事件类型枚举 ====== */
typedef enum { typedef enum {
KEY_EVENT_NONE, KEY_EVENT_NONE, // 无按键事件
KEY_EVENT_UP_CLICK, KEY_EVENT_UP_CLICK, // ▲ 键短按释放
KEY_EVENT_DOWN_CLICK, KEY_EVENT_DOWN_CLICK, // ▼ 键短按释放
KEY_EVENT_SOS_CLICK, KEY_EVENT_SOS_CLICK, // SOS 键短按释放
KEY_EVENT_UP_LONG, KEY_EVENT_UP_LONG, // ▲ 键长按事件 (触发布撤防切换)
KEY_EVENT_DOWN_LONG, KEY_EVENT_DOWN_LONG, // ▼ 键长按事件 (菜单返回)
KEY_EVENT_SOS_LONG, KEY_EVENT_SOS_LONG, // SOS 键长按事件
KEY_EVENT_UP_DOWN_COMB, KEY_EVENT_UP_DOWN_COMB, // ▲ 与 ▼ 双键组合按下事件
KEY_EVENT_CONFIRM_CLICK, // 新增:确认键短按 KEY_EVENT_CONFIRM_CLICK, // 确认键短按事件 (保存设置或进入子菜单)
KEY_EVENT_CONFIRM_LONG // 新增:确认键长按 KEY_EVENT_CONFIRM_LONG // 确认键长按事件 (打开主菜单)
} KeyEvent; } KeyEvent;
/* ====== 事件优先级 (步骤4) ====== */ /* ====== 事件优先级枚举 ====== */
typedef enum { typedef enum {
EVENT_PRIORITY_LOW, // 0: 低优先级-系统内部事件 EVENT_PRIORITY_LOW, // 低优先级系统内部状态更新 (如时钟分钟更新)
EVENT_PRIORITY_NORMAL, // 1: 正常优先级-用户交互 EVENT_PRIORITY_NORMAL, // 正常优先级用户按键交互、菜单导航
EVENT_PRIORITY_HIGH, // 2: 高优先级-报警事件 EVENT_PRIORITY_HIGH, // 高优先级:无线传感器入侵警报
EVENT_PRIORITY_URGENT // 3: 紧急优先级-SOS EVENT_PRIORITY_URGENT // 紧急优先级SOS 主动求救
} EventPriority; } EventPriority;
/* ====== 系统事件 (步骤4) ====== */ /* ====== 系统事件结构体定义 ====== */
typedef struct { typedef struct {
KeyEvent key_event; // 按键事件类型 KeyEvent key_event; // 关联的按键事件类型
EventPriority priority; // 事件优先级 EventPriority priority; // 事件的处理优先级
u32 extra_data; // 额外数据(射频地址等 u32 extra_data; // 随事件携带的额外数据 (如 24 位射频地址等)
u8 extra_size; // 额外数据有效字节(0~4) u8 extra_size; // 额外数据有效字节大小 (0~4)
} SystemEvent; } SystemEvent;
/* ====== 事件处理返回值 (步骤5) ====== */ /* ====== 事件分发处理结果枚举 ====== */
typedef enum { typedef enum {
EVENT_HANDLED, // 事件已处理,停止分发 EVENT_HANDLED, // 事件已成功处理,不再继续向后传递
EVENT_IGNORED // 事件未处理,继续传递 EVENT_IGNORED // 事件未处理,允许继续传递给后台应用或丢弃
} EventResult; } EventResult;
/* ====== 应用ID枚举 (步骤2) ====== */ /* ====== 应用程序 ID 枚举 ====== */
typedef enum { typedef enum {
APP_ID_CLOCK, // 0: 待机时钟 APP_ID_CLOCK, // 待机时钟应用程序 (首屏)
APP_ID_MENU, // 1: 主菜单 APP_ID_MENU, // 系统设置与操作主菜单应用程序
APP_ID_PAIR, // 2: 配对 APP_ID_PAIR, // 无线传感器对码学习应用程序
APP_ID_ALARM, // 3: 传感器报警 APP_ID_ALARM, // 报警接收与波形反馈应用程序
APP_ID_SOS, // 4: 主动求救 APP_ID_SOS, // 紧急呼救与无线发射应用程序
APP_ID_MAX // 5: 应用数量上限 APP_ID_MAX // 应用程序最大数量上限,用于数组大小
} AppID; } AppID;
/* ====== 应用类型 (步骤5) ====== */ /* ====== 应用程序类型枚举 ====== */
typedef enum { typedef enum {
APP_TYPE_FOREGROUND, // 前台应用:独占屏幕 APP_TYPE_FOREGROUND, // 前台应用:独占 OLED 屏幕显示并优先接收按键/射频事件
APP_TYPE_BACKGROUND // 后台应用:无屏幕,监听事件 APP_TYPE_BACKGROUND // 后台应用:无屏幕显示,静默监听前台未处理的系统事件
} AppType; } AppType;
/* ====== 应用描述符 ====== */ /* ====== 应用程序描述符及函数指针类型定义 ====== */
struct WristbandApp; struct WristbandApp;
typedef struct WristbandApp WristbandApp; typedef struct WristbandApp WristbandApp;
typedef void (*AppStartFunc)(void); typedef void (*AppStartFunc)(void); // 应用启动函数指针类型
typedef void (*AppRunFunc)(void); typedef void (*AppRunFunc)(void); // 主循环轮询函数指针类型
typedef void (*AppCloseFunc)(void); typedef void (*AppCloseFunc)(void); // 应用关闭清理函数指针类型
typedef EventResult (*AppEventFunc)(SystemEvent *evt); typedef EventResult (*AppEventFunc)(SystemEvent *evt); // 事件接收处理函数指针类型
struct WristbandApp { struct WristbandApp {
AppID app_id; AppID app_id; // 应用程序唯一标识 ID
AppType app_type; AppType app_type; // 前后台类型标识
char *app_name; char *app_name; // 应用程序名称字符串 (供串口日志调试使用)
void (*OnStart)(void); void (*OnStart)(void); // 启动回调:应用加载时调用,用于初始化 UI 界面与硬件状态
void (*onRun)(void); void (*onRun)(void); // 循环回调:在主循环中持续轮询调用,用于非阻塞业务更新
void (*onClose)(void); void (*onClose)(void); // 关闭回调:应用退出时调用,用于现场清理与外设复位
EventResult (*onEvent)(SystemEvent *evt); EventResult (*onEvent)(SystemEvent *evt); // 事件回调:接收系统事件,返回处理结果以控制事件流向
u8 is_active; // 是否当前活跃 u8 is_active; // 标记该应用当前是否处于活跃运行状态 (1表示前台活跃)
u8 is_running; // 是否允许运行 onRun u8 is_running; // 标记应用是否处于运行态,用于 CPU 超时挂起机制控制
}; };
/* ====== 传感器存储结构 ====== */ /* ====== 无线传感器 Flash 存储槽位结构 ====== */
typedef struct { typedef struct {
unsigned char is_used; unsigned char is_used; // 槽位是否已使用标志 (0x01:已启用, 其他:空闲)
unsigned char addr[3]; unsigned char addr[3]; // 绑定的 24 位无线传感器 EV1527 硬件地址
unsigned char type; unsigned char type; // 传感器设备类型 (0:门磁, 1:红外PIR, 2:烟感, 3:燃气, 4:水浸)
unsigned char zone; unsigned char zone; // 防区编号 (例如 1~990 代表 24小时紧急防区)
unsigned char name_gbk[16]; unsigned char name_gbk[16];// 传感器的自定义名称 (GBK 编码,用于屏幕显示)
} Sensor_Slot; } Sensor_Slot;
/* ====== 全局变量 extern 声明 ====== */ /* ====== 全局变量 extern 外部声明 ====== */
extern SystemState current_state; extern SystemState current_state; // 系统当前整体运行状态
extern u8 current_hour; extern u8 current_hour; // 当前系统时间:小时
extern u8 current_min; extern u8 current_min; // 当前系统时间:分钟
extern u8 current_sec; extern u8 current_sec; // 当前系统时间:秒
extern volatile u8 clock_updated; extern volatile u8 clock_updated; // 时钟分钟更新标志 (1表示需要刷新待机页时间)
extern u8 menu_select; extern u8 menu_select; // 当前菜单高亮的选中项索引
extern u32 captured_addr; extern u32 captured_addr; // 对码或报警时,当前捕获到的传感器 24 位射频地址
extern u8 captured_type; extern u8 captured_type; // 正在配对的传感器类型
extern u8 alarm_sensor_slot; extern u8 alarm_sensor_slot; // 当前触发警报的传感器在数据库列表中的槽位索引
extern Sensor_Slot xdata sensor_list[16]; extern Sensor_Slot xdata sensor_list[16];// 传感器数据库数组 (存储在 xdata 区,共 16 个槽位)
extern char *code sensor_names_fr[5]; extern char *code sensor_names_fr[5]; // 法语传感器设备类型默认名称字串数组
// 系统滴答与定时计数器 (均为 1ms 中断服务中更新)
extern volatile u16 ms_tick; // 毫秒滴答计数器 (0~999循环)
extern volatile u16 inactivity_timer; // 用户闲置计时器 (用作无操作自动休眠单位10ms)
extern volatile u32 pair_timeout_ms; // 对码等待搜寻超时毫秒计数器
extern volatile u16 alarm_timer_ms; // 报警振动与闪烁定时计数器
extern volatile u16 motor_timer_ms; // 马达单次震动定时计时器
extern volatile bit alarm_flash_flag; // 报警屏幕红框闪烁的周期标志位 (0或1)
extern volatile u16 ms_tick; // 按键长按与消抖状态机计数器
extern volatile u16 inactivity_timer; // 无操作闲置计时 extern volatile u16 key_scan_timer; // 按键扫描 10ms 分频计数
extern volatile u32 pair_timeout_ms; extern volatile u16 key_up_hold; // ▲ 键持续按下计时器
extern volatile u16 alarm_timer_ms; extern volatile u16 key_down_hold; // ▼ 键持续按下计时器
extern volatile u16 motor_timer_ms; extern volatile u16 key_confirm_hold; // ■ 确认键持续按下计时器
extern volatile bit alarm_flash_flag; extern volatile u16 key_sos_hold; // SOS 键持续按下计时器
extern volatile u16 comb_hold; // 组合键持续按下计时器
extern volatile u16 key_scan_timer; // 常量定义
extern volatile u16 key_up_hold; #define MAIN_Fosc 24000000UL // STC32G 主频工作频率定义 (24.0 MHz)
extern volatile u16 key_down_hold; #define CLONED_ADDR 0x37A86UL // 手环自身克隆发射的默认 24 位射频出厂 ID 地址
extern volatile u16 key_confirm_hold;
extern volatile u16 key_sos_hold;
extern volatile u16 comb_hold;
#define MAIN_Fosc 24000000UL
#define CLONED_ADDR 0x37A86UL
#endif
#endif // __CONFIG_H__

View File

@@ -1,140 +1,191 @@
#include "database.h" #include "database.h"
#define IAP_CMD_READ 1 // 内部 IAP 命令寄存器指令定义
#define IAP_CMD_WRITE 2 #define IAP_CMD_READ 1 // IAP 字节读指令
#define IAP_CMD_ERASE 3 #define IAP_CMD_WRITE 2 // IAP 字节写指令
#define IAP_CMD_ERASE 3 // IAP 扇区擦除指令
// 定义全局常量数组 // 定义法语传感器默认名称常量数组,分别对应类型 0~4
char *code sensor_names_fr[5] = { char *code sensor_names_fr[5] = {
"PORTE", "PORTE", // 0: 门磁 (法语: 门)
"PIR", "PIR", // 1: 红外探测器
"FUMEE", "FUMEE", // 2: 烟雾传感器 (法语: 烟雾)
"GAZ", "GAZ", // 3: 燃气泄漏传感器 (法语: 燃气)
"EAU" "EAU" // 4: 漏水/水浸传感器 (法语: 水)
}; };
/**
* @brief 关闭 IAP 寄存器控制并清理地址
* @details 每次 IAP 闪存操作后调用,确保安全,防止意外指针写入损坏 EEPROM 中的关键配置数据。
*/
void IAP_Disable(void) void IAP_Disable(void)
{ {
IAP_CONTR = 0; IAP_CONTR = 0; // 关闭 IAP 功能,禁用触发
IAP_CMD = 0; IAP_CMD = 0; // 清除内部命令
IAP_TRIG = 0; IAP_TRIG = 0; // 清零触发寄存器
IAP_ADDRH = 0xff; IAP_ADDRH = 0xff; // 地址指针高八位置无效,防止意外指向
IAP_ADDRL = 0xff; IAP_ADDRL = 0xff; // 地址指针低八位置无效
} }
/**
* @brief 读取指定 EEPROM 偏置地址处的 1 字节数据
* @param addr IAP 操作的目标物理地址
* @return u8 读取到的数据
*/
u8 IAP_ReadByte(u16 addr) u8 IAP_ReadByte(u16 addr)
{ {
IAP_CONTR = 0x80; IAP_CONTR = 0x80; // 开启 IAP/EEPROM 功能,设置等待时间
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 设置闪存读写等待时间参数 (以MHz为单位例如24MHz即24)
IAP_CMD = IAP_CMD_READ; IAP_CMD = IAP_CMD_READ; // 发送读字节命令
IAP_ADDRL = (u8)addr; IAP_ADDRL = (u8)addr; // 加载目标低八位地址
IAP_ADDRH = (u8)(addr >> 8); IAP_ADDRH = (u8)(addr >> 8); // 加载目标高八位地址
IAP_ADDRE = 0; IAP_ADDRE = 0; // 重置拓展物理地址 (STC32G内部EEPROM通常不需要)
IAP_TRIG = 0x5a; IAP_TRIG = 0x5a; // 触发写序列第一步密码
IAP_TRIG = 0xa5; IAP_TRIG = 0xa5; // 触发写序列第二步密码 (必须连续送入,才能使能擦写)
_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); // CPU 等待 4 个周期直至读取操作完成
IAP_Disable(); IAP_Disable(); // 读完后立即关闭 IAP 控制
return IAP_DATA; return IAP_DATA; // 返回读取出的数据寄存器字节
} }
/**
* @brief 在 EEPROM 指定地址写入 1 字节数据
* @param addr 目标物理地址
* @param dat 要写入的字节
*/
void IAP_WriteByte(u16 addr, u8 dat) void IAP_WriteByte(u16 addr, u8 dat)
{ {
IAP_CONTR = 0x80; IAP_CONTR = 0x80; // 开启 IAP/EEPROM 功能
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 匹配工作频率对应的读写周期
IAP_CMD = IAP_CMD_WRITE; IAP_CMD = IAP_CMD_WRITE; // 发送写字节命令
IAP_ADDRL = (u8)addr; IAP_ADDRL = (u8)addr; // 送入低字节地址
IAP_ADDRH = (u8)(addr >> 8); IAP_ADDRH = (u8)(addr >> 8); // 送入高字节地址
IAP_ADDRE = 0; IAP_ADDRE = 0; // 送入拓展地址
IAP_DATA = dat; IAP_DATA = dat; // 装载待写入的字节数据
IAP_TRIG = 0x5a; IAP_TRIG = 0x5a; // 送入触发密码 0x5A
IAP_TRIG = 0xa5; IAP_TRIG = 0xa5; // 送入触发密码 0xA5 启动硬件写
_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); // 等待写操作完成
IAP_Disable(); IAP_Disable(); // 关闭 IAP 控制器
} }
/**
* @brief 擦除指定的 512 字节 EEPROM 物理扇区
* @param addr 扇区首地址
*/
void IAP_EraseSector(u16 addr) void IAP_EraseSector(u16 addr)
{ {
IAP_CONTR = 0x80; IAP_CONTR = 0x80; // 开启 IAP 功能
IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 匹配工作频率的擦除时间周期
IAP_CMD = IAP_CMD_ERASE; IAP_CMD = IAP_CMD_ERASE; // 发送扇区擦除指令
IAP_ADDRL = (u8)addr; IAP_ADDRL = (u8)addr; // 送入低字节地址
IAP_ADDRH = (u8)(addr >> 8); IAP_ADDRH = (u8)(addr >> 8); // 送入高字节地址
IAP_ADDRE = 0; IAP_ADDRE = 0; // 拓展地址清零
IAP_TRIG = 0x5a; IAP_TRIG = 0x5a; // 触发密码 0x5A
IAP_TRIG = 0xa5; IAP_TRIG = 0xa5; // 触发密码 0xA5 启动硬件擦除
_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); // 等待擦除完成 (扇区全部内容被初始化为 0xFF)
IAP_Disable(); IAP_Disable(); // 关闭 IAP 操作以保护数据
} }
/**
* @brief 从 EEPROM 的扇区 254 中加载数据库
* @details 逐字节读取 Flash 物理数据,并直接通过指针拷贝装入 RAM 中的全局 `sensor_list` 数组。
*/
void Load_Database(void) void Load_Database(void)
{ {
u16 addr = 0; u16 addr = 0; // EEPROM 内部偏移地址从 0 开始
u8 *ptr = (u8 *)&sensor_list; u8 *ptr = (u8 *)&sensor_list; // 获取传感器 RAM 数组的首地址指针
u16 size = sizeof(sensor_list); u16 size = sizeof(sensor_list); // 获取结构体数组的全部占用尺寸
u16 i; u16 i;
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
ptr[i] = IAP_ReadByte(addr++); ptr[i] = IAP_ReadByte(addr++); // 循环读取字节并还原到 RAM
} }
} }
/**
* @brief 将 RAM 中的最新传感器列表数据持久化保存到 EEPROM
* @details 因为 Flash 写操作不能把 0 写回 1所以必须先调用 `IAP_EraseSector` 擦除整个扇区,
* 接着遍历整个数据结构,逐字节写入最新的 RAM 内容。
*/
void Save_Database(void) void Save_Database(void)
{ {
u16 addr = 0; u16 addr = 0; // EEPROM 物理首地址
u8 *ptr = (u8 *)&sensor_list; u8 *ptr = (u8 *)&sensor_list; // RAM 首指针
u16 size = sizeof(sensor_list); u16 size = sizeof(sensor_list); // 总字节量
u16 i; u16 i;
IAP_EraseSector(0); IAP_EraseSector(0); // 擦除首扇区 (扇区 254大小 512 字节足够存放)
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
IAP_WriteByte(addr++, ptr[i]); IAP_WriteByte(addr++, ptr[i]); // 循环写入字节数据
} }
} }
/**
* @brief 新增或更新已对码的传感器,并保存数据库
* @param addr 24位射频地址
* @param type 传感器类型索引 (0~4)
* @param zone 自定义分配的防区号 (1~99, 0)
*/
void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone) void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone)
{ {
u8 i; u8 i;
u8 slot_to_use = 15; u8 slot_to_use = 15; // 默认使用最后一槽
// 遍历现有的 16 个槽位,寻找匹配的地址或者空闲的槽位
for (i = 0; i < 16; i++) { for (i = 0; i < 16; i++) {
if (sensor_list[i].is_used == 0x01) { if (sensor_list[i].is_used == 0x01) {
// 解析该槽位中存储的 24 位传感器地址
u32 existing_addr = ((u32)sensor_list[i].addr[0] << 16) | u32 existing_addr = ((u32)sensor_list[i].addr[0] << 16) |
((u32)sensor_list[i].addr[1] << 8) | ((u32)sensor_list[i].addr[1] << 8) |
sensor_list[i].addr[2]; sensor_list[i].addr[2];
// 如果地址已经存在,说明这是要修改现有槽位的防区或类型
if (existing_addr == addr) { if (existing_addr == addr) {
slot_to_use = i; slot_to_use = i; // 使用该槽位进行更新
break; break;
} }
} else { } else {
// 如果找到未启用的空闲槽位,暂定使用它
slot_to_use = i; slot_to_use = i;
break; break;
} }
} }
sensor_list[slot_to_use].is_used = 0x01;
sensor_list[slot_to_use].addr[0] = (u8)(addr >> 16);
sensor_list[slot_to_use].addr[1] = (u8)(addr >> 8);
sensor_list[slot_to_use].addr[2] = (u8)addr;
sensor_list[slot_to_use].type = type;
sensor_list[slot_to_use].zone = zone;
// 配置传感器槽位数据
sensor_list[slot_to_use].is_used = 0x01; // 标记槽位为已启用状态
sensor_list[slot_to_use].addr[0] = (u8)(addr >> 16); // 存入地址高字节
sensor_list[slot_to_use].addr[1] = (u8)(addr >> 8); // 存入地址中字节
sensor_list[slot_to_use].addr[2] = (u8)addr; // 存入地址低字节
sensor_list[slot_to_use].type = type; // 记录传感器硬件类型
sensor_list[slot_to_use].zone = zone; // 记录对应的防区号
// 生成该槽位传感器的法语名称
for (i = 0; i < 15; i++) { for (i = 0; i < 15; i++) {
// 从默认名称常量数组中复制对应名字的字符
sensor_list[slot_to_use].name_gbk[i] = sensor_names_fr[type][i]; sensor_list[slot_to_use].name_gbk[i] = sensor_names_fr[type][i];
if (sensor_names_fr[type][i] == '\0') break; if (sensor_names_fr[type][i] == '\0') break; // 遇零终止
} }
sensor_list[slot_to_use].name_gbk[15] = '\0'; sensor_list[slot_to_use].name_gbk[15] = '\0'; // 尾部安全截断,防止字符越界
Save_Database();
Save_Database(); // 调用保存函数,强制持久化写回 IAP Flash
} }
/**
* @brief 检测输入的 24 位射频地址是否已被当前绑定的任何槽位匹配
* @param addr 传入待匹配的 24 位射频地址
* @param out_slot_index 若匹配成功,输出槽位的索引号指针
* @return bit 1-匹配成功0-该地址尚未被本系统学习绑定
*/
bit Check_Sensor_ID(u32 addr, u8 *out_slot_index) bit Check_Sensor_ID(u32 addr, u8 *out_slot_index)
{ {
u8 i; u8 i;
for (i = 0; i < 16; i++) { for (i = 0; i < 16; i++) {
// 仅比对正在使用中的槽位
if (sensor_list[i].is_used == 0x01) { if (sensor_list[i].is_used == 0x01) {
u32 slot_addr = ((u32)sensor_list[i].addr[0] << 16) | u32 slot_addr = ((u32)sensor_list[i].addr[0] << 16) |
((u32)sensor_list[i].addr[1] << 8) | ((u32)sensor_list[i].addr[1] << 8) |
sensor_list[i].addr[2]; sensor_list[i].addr[2];
if (slot_addr == addr) { if (slot_addr == addr) {
*out_slot_index = i; *out_slot_index = i; // 写入输出变量
return 1; return 1; // 匹配成功返回真
} }
} }
} }
return 0; return 0; // 遍历结束未找到,返回假
} }

View File

@@ -3,16 +3,68 @@
#include "config.h" #include "config.h"
// 底层 IAP 接口 /* ====== 底层 IAP (在应用编程 Flash) 读写驱动接口 ====== */
/**
* @brief 禁用 IAP 模块,关闭 Flash 擦写控制器
* @details 每次完成 Flash 的读、写或擦除操作后,必须立即调用此函数,
* 清除寄存器状态以防止因外部强干扰触发 Flash 误擦写。
*/
void IAP_Disable(void); void IAP_Disable(void);
/**
* @brief 从指定 IAP Flash 地址读取一个字节
* @param addr 16位目标 Flash 内部偏移物理地址 (对应扇区内偏移)
* @return u8 读取到的 8 位数据字节
*/
u8 IAP_ReadByte(u16 addr); u8 IAP_ReadByte(u16 addr);
/**
* @brief 向指定 IAP Flash 地址写入一个字节数据
* @param addr 16位目标 Flash 内部偏移物理地址
* @param dat 要写入的 8 位数据字节
* @note STC32G 的 EEPROM/IAP 写入只能把 1 写为 0若要把 0 改为 1必须先执行扇区擦除操作。
*/
void IAP_WriteByte(u16 addr, u8 dat); void IAP_WriteByte(u16 addr, u8 dat);
/**
* @brief 擦除指定的 Flash 物理扇区 (512字节)
* @param addr 目标扇区内的任意物理字节地址 (传入首地址即可)
* @note STC32G EEPROM 的擦除以扇区 (512 字节) 为基本单位。擦除后,该扇区内所有字节的数据均恢复为 0xFF。
*/
void IAP_EraseSector(u16 addr); void IAP_EraseSector(u16 addr);
// 数据库持久化接口
/* ====== 传感器数据库业务持久化接口 ====== */
/**
* @brief 从 Flash 扇区 254 (0xFE0000) 载入全部传感器槽位配置至 RAM 变量
* @details 系统初始化时调用,将 EEPROM 中的传感器绑定数据拷贝到 RAM 中的 `sensor_list` 数组。
*/
void Load_Database(void); void Load_Database(void);
/**
* @brief 将 RAM 中的全部传感器配置保存/写入到 Flash 扇区中
* @details 在完成对码、添加新传感器或微调防区号后调用,该函数会先擦除第 254 扇区,然后逐字节写入当前最新的 `sensor_list` 数组。
*/
void Save_Database(void); void Save_Database(void);
/**
* @brief 添加一个新的传感器或更新已有的传感器防区、类型和名称
* @param addr 捕获到的 24 位无线传感器 EV1527 射频地址
* @param type 传感器类型 (0:门磁, 1:红外PIR, 2:烟感, 3:燃气, 4:水浸)
* @param zone 自定义分配的防区号 (1~99, 0代表24小时紧急防区)
* @details 如果检测到该地址已在列表,则覆盖更新防区号并保存;否则在首个空闲槽位插入该传感器,
* 自动生成其法语名称字符串,并调用 `Save_Database()` 写入 Flash 中。
*/
void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone); void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone);
/**
* @brief 检测特定射频地址是否已被绑定在传感器数据库中
* @param addr 接收到的 24 位无线传感器射频地址
* @param out_slot_index 输出参数指针:如果匹配成功,将写入该传感器在 `sensor_list` 数组中的索引槽位号 (0~15)
* @return bit 返回匹配结果。1 表示数据库中已存在该传感器0 表示该传感器未登记 (未知设备)
*/
bit Check_Sensor_ID(u32 addr, u8 *out_slot_index); bit Check_Sensor_ID(u32 addr, u8 *out_slot_index);
#endif #endif // __DATABASE_H__

View File

@@ -3,13 +3,18 @@
#include "database.h" #include "database.h"
#include "system.h" #include "system.h"
// 定义事件环形队列的最大深度为 8
#define EVENT_QUEUE_DEPTH 8 #define EVENT_QUEUE_DEPTH 8
// 环形队列存储数组 (存放在单片机 idata 快速RAM区中)
static SystemEvent idata event_queue[EVENT_QUEUE_DEPTH]; static SystemEvent idata event_queue[EVENT_QUEUE_DEPTH];
static u8 queue_head = 0; static u8 queue_head = 0; // 队首读指针
static u8 queue_tail = 0; static u8 queue_tail = 0; // 队尾写指针
static u8 queue_count = 0; static u8 queue_count = 0; // 当前队列内有效事件总数
/**
* @brief 初始化/清空事件队列
*/
void EventQueue_Init(void) void EventQueue_Init(void)
{ {
queue_head = 0; queue_head = 0;
@@ -17,163 +22,217 @@ void EventQueue_Init(void)
queue_count = 0; queue_count = 0;
} }
/**
* @brief 将普通事件压入队尾
* @param evt 输入的系统事件
* @return bit 1-成功0-失败
*/
bit EventQueue_Push(SystemEvent evt) bit EventQueue_Push(SystemEvent evt)
{ {
if (queue_count >= EVENT_QUEUE_DEPTH) return 0; if (queue_count >= EVENT_QUEUE_DEPTH) return 0; // 队列满,丢弃事件
EA = 0; EA = 0; // 关闭全局中断,保护临界区操作的原子性
event_queue[queue_tail] = evt; event_queue[queue_tail] = evt; // 存入队尾
queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH; queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH; // 尾指针向后移动,回环
queue_count++; queue_count++; // 有效计数加 1
EA = 1; EA = 1; // 恢复全局中断
return 1; return 1;
} }
/**
* @brief 将高优先级/紧急事件强行插到队首
* @param evt 输入的系统事件
* @return bit 1-成功0-失败
*/
bit EventQueue_InsertFront(SystemEvent evt) bit EventQueue_InsertFront(SystemEvent evt)
{ {
if (queue_count >= EVENT_QUEUE_DEPTH) return 0; if (queue_count >= EVENT_QUEUE_DEPTH) return 0; // 队列满,插队失败
EA = 0; EA = 0; // 屏蔽中断
// 头指针向前移动一位(带回环防溢出)
queue_head = (queue_head - 1 + EVENT_QUEUE_DEPTH) % EVENT_QUEUE_DEPTH; queue_head = (queue_head - 1 + EVENT_QUEUE_DEPTH) % EVENT_QUEUE_DEPTH;
event_queue[queue_head] = evt; event_queue[queue_head] = evt; // 存入腾出的队首位置
queue_count++; queue_count++; // 计数加 1
EA = 1; EA = 1; // 恢复中断
return 1; return 1;
} }
/**
* @brief 从队首弹出一个缓冲事件
* @return SystemEvent 返回弹出的事件。若空返回空闲事件
*/
SystemEvent EventQueue_Pop(void) SystemEvent EventQueue_Pop(void)
{ {
SystemEvent evt; SystemEvent evt;
// 默认空事件属性
evt.key_event = KEY_EVENT_NONE; evt.key_event = KEY_EVENT_NONE;
evt.priority = EVENT_PRIORITY_LOW; evt.priority = EVENT_PRIORITY_LOW;
evt.extra_data = 0; evt.extra_data = 0;
evt.extra_size = 0; evt.extra_size = 0;
if (queue_count == 0) return evt;
EA = 0; if (queue_count == 0) return evt; // 队空,直接返回空闲事件
evt = event_queue[queue_head];
queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH; EA = 0; // 屏蔽中断
queue_count--; evt = event_queue[queue_head]; // 读取队首数据
EA = 1; queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH; // 头指针向后移动,回环
queue_count--; // 有效计数减 1
EA = 1; // 恢复中断
return evt; return evt;
} }
/**
* @brief 判断队列是否为空
*/
bit EventQueue_IsEmpty(void) bit EventQueue_IsEmpty(void)
{ {
return (queue_count == 0) ? 1 : 0; return (queue_count == 0) ? 1 : 0;
} }
/**
* @brief 判断队列是否已满
*/
bit EventQueue_IsFull(void) bit EventQueue_IsFull(void)
{ {
return (queue_count >= EVENT_QUEUE_DEPTH) ? 1 : 0; return (queue_count >= EVENT_QUEUE_DEPTH) ? 1 : 0;
} }
/**
* @brief 获取当前队列中的事件总数
*/
u8 EventQueue_GetCount(void) u8 EventQueue_GetCount(void)
{ {
return queue_count; return queue_count;
} }
/**
* @brief 事件分发器核心轮询逻辑
* @details 逐一处理队列中的事件,并根据系统当前的运行模式进行拦截、跳转或下发派发。
*/
void Event_Dispatcher_Loop(void) void Event_Dispatcher_Loop(void)
{ {
SystemEvent evt = EventQueue_Pop(); SystemEvent evt = EventQueue_Pop(); // 弹出一个事件
// 如果是无事发生的空事件,直接退出
if (evt.key_event == KEY_EVENT_NONE && evt.extra_size == 0) { if (evt.key_event == KEY_EVENT_NONE && evt.extra_size == 0) {
return; return;
} }
inactivity_timer = 0; // 收到有效按键或无线事件,重置闲置计时器
inactivity_timer = 0; // 捕获到有效事件,清零休眠计时器,重置闲置超时
// 串口打印调试信息
Uart_SendString("[EVENT] key="); Uart_SendString("[EVENT] key=");
Uart_SendHex8((u8)evt.key_event); Uart_SendHex8((u8)evt.key_event);
Uart_SendString(", pri="); Uart_SendString(", pri=");
Uart_SendHex8((u8)evt.priority); Uart_SendHex8((u8)evt.priority);
Uart_SendString("\r\n"); Uart_SendString("\r\n");
// SOS 按键:仅在当前不处于 SOS 求求模式时触发进入 SOS 状态,否则作为普通按键进行事件分发 // SOS 按键紧急处理:若非处于 SOS 呼救状态下,接收到 SOS 键动作,立即强行切入到 SOS_APP 并执行求救
if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) && if ((evt.key_event == KEY_EVENT_SOS_CLICK || evt.key_event == KEY_EVENT_SOS_LONG) &&
AppManager_GetActiveAppID() != APP_ID_SOS) { AppManager_GetActiveAppID() != APP_ID_SOS) {
AppManager_StartApp(APP_ID_SOS); AppManager_StartApp(APP_ID_SOS);
return; return;
} }
// 传感器入侵警报:当前不处于 SOS 求救状态时方可跳转到报警界面 // 传感器入侵报警过滤与跳转处理:
// 当接收到高优先级且带有 4 字节射频地址的数据包,说明有传感器触发了发射
if (evt.priority == EVENT_PRIORITY_HIGH && evt.extra_size >= 4) { if (evt.priority == EVENT_PRIORITY_HIGH && evt.extra_size >= 4) {
u8 slot; u8 slot;
// 在本地数据库中检索该传感器地址是否合法登记过
if (Check_Sensor_ID(evt.extra_data, &slot)) { if (Check_Sensor_ID(evt.extra_data, &slot)) {
// 在不处于 SOS 的情况下,可正常强行切入 ALARM_APP 触发声光振警报
if (AppManager_GetActiveAppID() != APP_ID_SOS) { if (AppManager_GetActiveAppID() != APP_ID_SOS) {
captured_addr = evt.extra_data; captured_addr = evt.extra_data; // 缓存报警地址
alarm_sensor_slot = slot; alarm_sensor_slot = slot; // 缓存报警槽位
AppManager_StartApp(APP_ID_ALARM); AppManager_StartApp(APP_ID_ALARM); // 强制切入报警前台
return; return;
} }
} }
} }
// 无线对码捕获:处于 PAIR 模式下时,将射频事件分发给 PairApp 处理并退出分发 // 无线对码捕获处理
// 若系统正处于对码应用 (APP_ID_PAIR),遇到普通优先级无线射频包,则将其派发给该应用进行匹配与登记
if (evt.priority == EVENT_PRIORITY_NORMAL && evt.extra_size >= 4) { if (evt.priority == EVENT_PRIORITY_NORMAL && evt.extra_size >= 4) {
captured_addr = evt.extra_data; captured_addr = evt.extra_data; // 缓存捕获的待配对地址
captured_type = menu_select; captured_type = menu_select; // 将主菜单选择的类型传给配对缓存
if (AppManager_GetActiveAppID() == APP_ID_PAIR) { if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
AppManager_DispatchEvent(evt); AppManager_DispatchEvent(evt); // 派发给对码应用
return; return;
} }
} }
// 常规事件(如普通的按键点击)直接透传分发给当前处于前台活跃的应用
AppManager_DispatchEvent(evt); AppManager_DispatchEvent(evt);
} }
/**
* @brief 定时器 1 初始化,设定 1ms 中断周期
*/
void Timer1_Init(void) void Timer1_Init(void)
{ {
AUXR |= 0x40; AUXR |= 0x40; // 设定定时器 1 为 1T 模式 (无分频,主频直接输入)
TMOD &= 0x0F; TMOD &= 0x0F; // 设置定时器 1 模式寄存器,保留低四位 (定时器0配置)
{ {
// 按照系统主频 (MAIN_Fosc) 计算 1ms 对应的重装载值
u16 reload = (u16)(65536UL - (MAIN_Fosc / 1000UL)); u16 reload = (u16)(65536UL - (MAIN_Fosc / 1000UL));
TL1 = (u8)reload; TL1 = (u8)reload; // 载入低 8 位
TH1 = (u8)(reload >> 8); TH1 = (u8)(reload >> 8);// 载入高 8 位
} }
ET1 = 1; ET1 = 1; // 开启定时器 1 中断允许
TR1 = 1; TR1 = 1; // 启动定时器 1 计数
EA = 1; EA = 1; // 开启全局中断允许
} }
/**
* @brief 定时器 1 中断服务程序 (每 1ms 触发一次)
* @details 承担了系统软时钟计算、报警与求救下的马达控制状态机以及 10ms 周期性按键消抖扫描的功能。
*/
void Timer1_Isr(void) interrupt 3 void Timer1_Isr(void) interrupt 3
{ {
SystemEvent evt; SystemEvent evt;
// 1ms 递增滴答
ms_tick++; ms_tick++;
if (ms_tick >= 1000) { if (ms_tick >= 1000) {
ms_tick = 0; ms_tick = 0; // 1秒时间已到归零
current_sec++; current_sec++;
if (current_sec >= 60) { if (current_sec >= 60) {
current_sec = 0; current_sec = 0; // 1分钟时间已到
current_min++; current_min++;
if (current_min >= 60) { if (current_min >= 60) {
current_min = 0; current_min = 0; // 1小时时间已到
current_hour++; current_hour++;
if (current_hour >= 24) current_hour = 0; if (current_hour >= 24) current_hour = 0; // 小时循环
} }
clock_updated = 1; clock_updated = 1; // 标志位置1提示前台时钟应用刷新显示
} }
} }
// 如果处于对码等待状态,对码超时计时器增加 (30秒超时判断)
if (current_state == STATE_PAIR_WAIT) { if (current_state == STATE_PAIR_WAIT) {
pair_timeout_ms++; pair_timeout_ms++;
} }
/* 振动马达定时状态机(仅控制马达,不再控制已被挪用/废弃的 LED */ /* 振动马达定时状态机逻辑控制 */
if (current_state == STATE_SOS_EMITTED) { if (current_state == STATE_SOS_EMITTED) {
// SOS 模式:交替规律震动 (震动 1000ms静音 200ms)
alarm_timer_ms++; alarm_timer_ms++;
if (alarm_timer_ms >= 1200) alarm_timer_ms = 0; if (alarm_timer_ms >= 1200) alarm_timer_ms = 0;
if (alarm_timer_ms < 1000) MOTOR = 1; else MOTOR = 0; if (alarm_timer_ms < 1000) MOTOR = 1; else MOTOR = 0;
} else if (current_state == STATE_ALARM) { } else if (current_state == STATE_ALARM) {
// 报警状态:根据触发警报的传感器类型,输出对应的差异化震动波形以便触觉分辨
u8 stype = sensor_list[alarm_sensor_slot].type; u8 stype = sensor_list[alarm_sensor_slot].type;
alarm_timer_ms++; alarm_timer_ms++;
if (alarm_timer_ms >= 5000) alarm_timer_ms = 0; if (alarm_timer_ms >= 5000) alarm_timer_ms = 0; // 5秒一周期
if (stype == 0) { if (stype == 0) {
// 门磁警报振动波形 // 门磁警报波形:单次震动 300ms停 4700ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0; if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
} else if (stype == 1) { } else if (stype == 1) {
// PIR 警报振动波形 // PIR 警报波形:震动 300ms停 200ms再震动 300ms停 4200ms
if (alarm_timer_ms < 300) MOTOR = 1; if (alarm_timer_ms < 300) MOTOR = 1;
else if (alarm_timer_ms >= 300 && alarm_timer_ms < 500) MOTOR = 0; 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 if (alarm_timer_ms >= 500 && alarm_timer_ms < 800) MOTOR = 1;
else MOTOR = 0; else MOTOR = 0;
} else if (stype == 2) { } else if (stype == 2) {
// 烟感警报振动波形 // 烟感警报波形:急促短闪三下 200ms-150ms-200ms-150ms-200ms
if (alarm_timer_ms < 200) MOTOR = 1; if (alarm_timer_ms < 200) MOTOR = 1;
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 350) MOTOR = 0; 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 >= 350 && alarm_timer_ms < 550) MOTOR = 1;
@@ -181,7 +240,7 @@ void Timer1_Isr(void) interrupt 3
else if (alarm_timer_ms >= 700 && alarm_timer_ms < 900) MOTOR = 1; else if (alarm_timer_ms >= 700 && alarm_timer_ms < 900) MOTOR = 1;
else MOTOR = 0; else MOTOR = 0;
} else if (stype == 3) { } else if (stype == 3) {
// 气感警报振动波形 // 燃气报警波形:快频连续四下
if (alarm_timer_ms < 200) MOTOR = 1; if (alarm_timer_ms < 200) MOTOR = 1;
else if (alarm_timer_ms >= 200 && alarm_timer_ms < 300) MOTOR = 0; 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 >= 300 && alarm_timer_ms < 500) MOTOR = 1;
@@ -191,75 +250,85 @@ void Timer1_Isr(void) interrupt 3
else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1100) MOTOR = 1; else if (alarm_timer_ms >= 900 && alarm_timer_ms < 1100) MOTOR = 1;
else MOTOR = 0; else MOTOR = 0;
} else if (stype == 4) { } else if (stype == 4) {
// 水浸警报振动波形 // 水浸警报波形:慢速双长震波 600ms 和 600ms
if (alarm_timer_ms < 600) MOTOR = 1; if (alarm_timer_ms < 600) MOTOR = 1;
else if (alarm_timer_ms >= 600 && alarm_timer_ms < 900) MOTOR = 0; 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 if (alarm_timer_ms >= 900 && alarm_timer_ms < 1500) MOTOR = 1;
else MOTOR = 0; else MOTOR = 0;
} else { } else {
// 缺省状态默认单次 300ms
if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0; if (alarm_timer_ms < 300) MOTOR = 1; else MOTOR = 0;
} }
} else { } else {
// 其他常态下彻底关闭震动马达并归零计时器
MOTOR = 0; MOTOR = 0;
alarm_timer_ms = 0; alarm_timer_ms = 0;
motor_timer_ms = 0; motor_timer_ms = 0;
} }
// 报警屏幕红边框以 100ms 周期亮灭,进行颜色爆闪指示
alarm_flash_flag = (ms_tick / 100) % 2; alarm_flash_flag = (ms_tick / 100) % 2;
/* 按键消抖扫描 (每10ms) */ /* 物理按键扫描及消抖状态机逻辑 (每 10ms 轮询一次) */
key_scan_timer++; key_scan_timer++;
if (key_scan_timer >= 10) { if (key_scan_timer >= 10) {
// 读取按键的电平信号 (由于有上拉电阻,未按下为高 1按下为低 0所以取反做正逻辑)
u8 raw_up = !KEY_UP; u8 raw_up = !KEY_UP;
u8 raw_down = !KEY_DOWN; u8 raw_down = !KEY_DOWN;
u8 raw_confirm = !KEY_CONFIRM; u8 raw_confirm = !KEY_CONFIRM;
u8 raw_sos = !KEY_SOS; u8 raw_sos = !KEY_SOS;
key_scan_timer = 0; key_scan_timer = 0; // 重置扫描分频计数器
// 如果检测到有任按键按下,清零闲置计时器;否则在非休眠非警报下累加 // 检测到有任按键处于下压状态,立即将无操作闲置休眠计时器归零
if (raw_up || raw_down || raw_confirm || raw_sos) { if (raw_up || raw_down || raw_confirm || raw_sos) {
inactivity_timer = 0; inactivity_timer = 0;
} else { } else {
// 如果在常态下(非休眠、非报警、非紧急求救),累加闲置时间
if (current_state != STATE_SLEEP && current_state != STATE_ALARM && current_state != STATE_SOS_EMITTED) { if (current_state != STATE_SLEEP && current_state != STATE_ALARM && current_state != STATE_SOS_EMITTED) {
inactivity_timer++; inactivity_timer++;
} }
} }
// 默认将派发事件重置为空
evt.key_event = KEY_EVENT_NONE; evt.key_event = KEY_EVENT_NONE;
evt.extra_data = 0; evt.extra_data = 0;
evt.extra_size = 0; evt.extra_size = 0;
// 双键组合判定:▲ 键和 ▼ 键同时按下的消抖与生成
if (raw_up && raw_down) { if (raw_up && raw_down) {
comb_hold++; comb_hold++;
if (comb_hold == 300) { if (comb_hold == 300) { // 持续按下达 3 秒 (300 * 10ms = 3s)
evt.key_event = KEY_EVENT_UP_DOWN_COMB; evt.key_event = KEY_EVENT_UP_DOWN_COMB;
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); EventQueue_Push(evt); // 压入队列
} }
key_up_hold = 0; key_up_hold = 0; // 清零单个按键计数,防止双击事件干扰
key_down_hold = 0; key_down_hold = 0;
} else { } else {
comb_hold = 0; comb_hold = 0; // 组合键松开归零
// ▲ (KEY_UP) 按键状态机
if (raw_up) { if (raw_up) {
key_up_hold++; key_up_hold++;
if (key_up_hold == 300) { if (key_up_hold == 300) { // 达到 3 秒判定为长按
evt.key_event = KEY_EVENT_UP_LONG; evt.key_event = KEY_EVENT_UP_LONG;
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); EventQueue_Push(evt);
} }
} else { } else {
// 如果松开时按下计数在消抖阈值20ms至长按阈值3s之间则判定为一次短按
if (key_up_hold >= 2 && key_up_hold < 300) { if (key_up_hold >= 2 && key_up_hold < 300) {
evt.key_event = KEY_EVENT_UP_CLICK; evt.key_event = KEY_EVENT_UP_CLICK;
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); EventQueue_Push(evt);
} }
key_up_hold = 0; key_up_hold = 0; // 释放归零
} }
// ▼ (KEY_DOWN) 按键状态机
if (raw_down) { if (raw_down) {
key_down_hold++; key_down_hold++;
if (key_down_hold == 300) { if (key_down_hold == 300) { // 长按 3 秒
evt.key_event = KEY_EVENT_DOWN_LONG; evt.key_event = KEY_EVENT_DOWN_LONG;
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); EventQueue_Push(evt);
@@ -270,12 +339,13 @@ void Timer1_Isr(void) interrupt 3
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); EventQueue_Push(evt);
} }
key_down_hold = 0; key_down_hold = 0; // 释放归零
} }
// ■ (KEY_CONFIRM) 确认按键状态机
if (raw_confirm) { if (raw_confirm) {
key_confirm_hold++; key_confirm_hold++;
if (key_confirm_hold == 300) { if (key_confirm_hold == 300) { // 长按 3 秒打开主菜单
evt.key_event = KEY_EVENT_CONFIRM_LONG; evt.key_event = KEY_EVENT_CONFIRM_LONG;
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); EventQueue_Push(evt);
@@ -286,23 +356,24 @@ void Timer1_Isr(void) interrupt 3
evt.priority = EVENT_PRIORITY_NORMAL; evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); EventQueue_Push(evt);
} }
key_confirm_hold = 0; key_confirm_hold = 0; // 释放归零
} }
// SOS 物理求救按键状态机
if (raw_sos) { if (raw_sos) {
key_sos_hold++; key_sos_hold++;
if (key_sos_hold == 300) { if (key_sos_hold == 300) { // 长按 3 秒,发出 URGENT 优先级求救,插入队首
evt.key_event = KEY_EVENT_SOS_LONG; evt.key_event = KEY_EVENT_SOS_LONG;
evt.priority = EVENT_PRIORITY_URGENT; evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt); EventQueue_InsertFront(evt);
} }
} else { } else {
if (key_sos_hold >= 2 && key_sos_hold < 300) { if (key_sos_hold >= 2 && key_sos_hold < 300) { // 短按即呼救,插入队首
evt.key_event = KEY_EVENT_SOS_CLICK; evt.key_event = KEY_EVENT_SOS_CLICK;
evt.priority = EVENT_PRIORITY_URGENT; evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt); EventQueue_InsertFront(evt);
} }
key_sos_hold = 0; key_sos_hold = 0; // 释放归零
} }
} }
} }

View File

@@ -3,17 +3,69 @@
#include "config.h" #include "config.h"
// 事件队列初始化与操作 /* ====== 事件环形缓冲区 (队列) 接口 ====== */
/**
* @brief 初始化事件队列
* @details 将队列的头指针、尾指针以及当前事件计数器全部归零,清空缓冲区。
*/
void EventQueue_Init(void); void EventQueue_Init(void);
/**
* @brief 将一个普通事件压入队列的末尾 (入队)
* @param evt 要入队的系统事件实例
* @return bit 1-入队成功0-队列已满 (入队失败,丢弃事件)
* @note 此函数操作时会自动禁用全局中断 (EA = 0),操作完成后恢复以防止重入冲突。
*/
bit EventQueue_Push(SystemEvent evt); bit EventQueue_Push(SystemEvent evt);
/**
* @brief 将一个紧急/高优先级事件强行插入到队列的首部 (插队)
* @param evt 要优先处理的系统事件
* @return bit 1-插队成功0-队列已满 (插队失败)
* @note 用于处理如 SOS 紧急求救和无线防区报警等需要立即响应的事件。操作受全局中断屏蔽保护。
*/
bit EventQueue_InsertFront(SystemEvent evt); bit EventQueue_InsertFront(SystemEvent evt);
/**
* @brief 从队列首部弹出一个事件 (出队)
* @return SystemEvent 被取出的系统事件。如果队列为空,将返回一个 key_event 为 KEY_EVENT_NONE 的空事件。
* @note 操作时受全局中断屏蔽保护。
*/
SystemEvent EventQueue_Pop(void); SystemEvent EventQueue_Pop(void);
/**
* @brief 检测事件队列当前是否为空
* @return bit 1-队列为空0-队列中仍有事件未处理
*/
bit EventQueue_IsEmpty(void); bit EventQueue_IsEmpty(void);
/**
* @brief 检测事件队列当前是否已满 (即达到最大深度 8)
* @return bit 1-队列已满0-队列仍有剩余空闲槽位
*/
bit EventQueue_IsFull(void); bit EventQueue_IsFull(void);
/**
* @brief 获取事件队列中当前已缓冲的事件总个数
* @return u8 当前事件的缓存个数
*/
u8 EventQueue_GetCount(void); u8 EventQueue_GetCount(void);
// 事件分发与定时器/按键扫描初始化
/* ====== 事件分发环与定时器驱动初始化 ====== */
/**
* @brief 事件分发轮询主处理程序
* @details 负责从事件队列中弹出当前最优先的事件,解析其内容,处理全局重置逻辑(如重置闲置休眠计时器),
* 并根据事件的类型与当前活跃的前台 APP 状态将事件分发到具体的应用程序中去处理。
*/
void Event_Dispatcher_Loop(void); void Event_Dispatcher_Loop(void);
/**
* @brief 初始化 Timer1 定时器,生成系统基准时钟 (1ms 中断)
* @details 配置定时器 1 为 16 位自动重装载模式,设定中断周期为 1.0 毫秒,开启 Timer1 中断并启动计数。
*/
void Timer1_Init(void); void Timer1_Init(void);
#endif #endif // __EVENT_H__

View File

@@ -1,17 +1,34 @@
; 端口中断 0 / 2 的硬件中转绝对定位汇编 (A251) ; =========================================================================
; 目的Keil C251 编译器只支持 interrupt 0~31 ; STC32G 端口中断硬件中转绝对定位汇编模块 (A251 语法格式)
; 而 STC32G 的端口中断硬件向量号为 37(Port0) 和 39(Port2) ; =========================================================================
; 超出范围无法用 C 直接声明。 ; 【技术原理分析】:
; 本文件在绝对地址处放置 LJMP 指令,将硬件向量重定向到 ; 1. Keil C251 编译器为了节省硬件资源,默认的 `interrupt` 关键字只支持 0 ~ 31 范围的软件中断向量号。
; Keil 可以生成的 13 号和 15 号中断服务函数入口。 ; 2. 然而STC32G 单片机拥有大量的扩展硬件外设,其 Port0 端口中断物理向量号为 37Port2 端口中断物理向量号为 39
NAME ISR_JUMP ; 超出了编译器的直接定义范畴。如果直接在 C 语言中声明 `interrupt 37`,编译器会报错。
; 3. 为此,本汇编模块采用“绝对地址跳转重定向”技术:
; - 直接在硬件物理中断向量入口地址Port0: 0x00012B, Port2: 0x00013B处放置一条长跳转 `LJMP` 汇编指令。
; - 将触发的物理中断强行重定向引导至 Keil C251 所能合法编译生成的较小编译向量入口地址(例如 13 号和 15 号中断服务程序入口)。
; - 由此,我们在 C 语言中声明 `interrupt 13` 和 `interrupt 15`,就能够成功被硬件 Port0 和 Port2 端口中断触发调用。
; =========================================================================
; 端口 0 中断 (向量 37, 硬件绝对地址 00012BH) 跳转到 13 号中断 (地址 00006BH) NAME ISR_JUMP ; 定义当前汇编模块的名字为 ISR_JUMP
; -------------------------------------------------------------------------
; 端口 0 掉电唤醒中断跳转配置
; -------------------------------------------------------------------------
; 硬件 Port0 中断向量号 37 对应的绝对闪存入口地址为 0x00012B
CSEG AT 00012BH CSEG AT 00012BH
; 当硬件触发 Port0 中断时,程序计数器 (PC) 定位到 00012BH此处 LJMP 指令强行将 PC 指向 00006BH
; 0x00006B 即为 C 语言声明的 `interrupt 13` 所对应的中断服务函数入口地址
LJMP 00006BH LJMP 00006BH
; 端口 2 中断 (向量 39, 硬件绝对地址 00013BH) 跳转到 15 号中断 (地址 00007BH) ; -------------------------------------------------------------------------
; 端口 2 掉电唤醒中断跳转配置
; -------------------------------------------------------------------------
; 硬件 Port2 中断向量号 39 对应的绝对闪存入口地址为 0x00013B
CSEG AT 00013BH CSEG AT 00013BH
; 当硬件触发 Port2 中断时,程序计数器 (PC) 定位到 00013BH此处 LJMP 指令强行将 PC 指向 00007BH
; 0x00007B 即为 C 语言声明的 `interrupt 15` 所对应的中断服务函数入口地址
LJMP 00007BH LJMP 00007BH
END END ; 汇编文件结束标志

View File

@@ -11,47 +11,64 @@
/* ========================================================================= /* =========================================================================
* 系统全局变量定义 * 系统全局变量定义
* ========================================================================= */ * ========================================================================= */
SystemState current_state = STATE_NORMAL; SystemState current_state = STATE_NORMAL; // 系统整体的当前运行状态,默认为正常时钟待机页
u8 current_hour = 10; u8 current_hour = 10; // 系统软时钟:小时 (默认初始化为 10 点)
u8 current_min = 35; u8 current_min = 35; // 系统软时钟:分钟 (默认初始化为 35 分)
u8 current_sec = 0; u8 current_sec = 0; // 系统软时钟:秒
volatile u8 clock_updated = 0; volatile u8 clock_updated = 0; // 分钟跳变标志位,置位后用于驱动 UI 刷新时间显示
u8 menu_select = 0; u8 menu_select = 0; // 主菜单当前选中的行高亮索引 (0~2)
u32 captured_addr = 0; u32 captured_addr = 0; // 配对或报警时,捕获到的传感器 24 位 EV1527 射频物理地址
u8 captured_type = 0; u8 captured_type = 0; // 正在对码或触发警报的传感器设备类型 (0:门磁, 1:PIR...)
u8 alarm_sensor_slot = 0; u8 alarm_sensor_slot = 0; // 触发警报的传感器在 sensor_list 数组中的索引槽位号
// 传感器存储列表数据库,位于单片机外部 XDATA 存储空间,总共 16 个槽位
Sensor_Slot xdata sensor_list[16]; Sensor_Slot xdata sensor_list[16];
volatile u16 ms_tick = 0; // 全局滴答与定时中断计数变量,位于 volatile 防止被编译器优化
volatile u16 inactivity_timer = 0; // 无操作闲置计时 volatile u16 ms_tick = 0; // 定时中断 1ms 基准滴答累加
volatile u32 pair_timeout_ms = 0; volatile u16 inactivity_timer = 0; // 用户闲置计时器,用于无操作判定(单位: 10ms
volatile u16 alarm_timer_ms = 0; volatile u32 pair_timeout_ms = 0; // 传感器配对对码超时毫秒计时器 (上限 30000ms 即 30s)
volatile u16 motor_timer_ms = 0; volatile u16 alarm_timer_ms = 0; // 入侵报警马达震动时序计数器
volatile bit alarm_flash_flag = 0; volatile u16 motor_timer_ms = 0; // 独立马达震动时长计数器
volatile bit alarm_flash_flag = 0; // 报警时红框周期性闪烁亮灭标志位
volatile u16 key_scan_timer = 0; // 物理按键扫描与消抖长按计数器 (10ms 扫描一次)
volatile u16 key_up_hold = 0; volatile u16 key_scan_timer = 0; // 10ms 扫描周期分频器
volatile u16 key_down_hold = 0; volatile u16 key_up_hold = 0; // ▲ (KEY_UP) 按下时长累加
volatile u16 key_confirm_hold = 0; volatile u16 key_down_hold = 0; // ▼ (KEY_DOWN) 按下时长累加
volatile u16 key_sos_hold = 0; volatile u16 key_confirm_hold = 0; // ■ (KEY_CONFIRM) 按下时长累加
volatile u16 comb_hold = 0; volatile u16 key_sos_hold = 0; // SOS 按下时长累加
volatile u16 comb_hold = 0; // ▲ + ▼ 组合键按下时长累加
/* ========================================================================= /* =========================================================================
* 串口调试指令解析与事件派发封装 * 串口调试指令解析与事件派发封装
* ========================================================================= */ * ========================================================================= */
/**
* @brief 解析通过串口 1 发送来的键盘及测试模拟指令
* @param cmd 接收到的 ASCII 字符命令
* @details 负责将字符指令转化对应的系统按键或射频事件,并推入事件队列以测试软件架构:
* - 'u'/'U': 模拟短按/长按 ▲ 键。
* - 'd'/'D': 模拟短按/长按 ▼ 键。
* - 's'/'S': 模拟短按/长按 SOS 物理键(优先插入队首)。
* - 'c'/'C': 模拟组合键触发。
* - 'a'/'A': 模拟无线传感器入侵触发警报信号,地址固定为 0x123456zone=1。
* - 'p'/'P': 模拟对码期间捕获的 24 位无线对码信号,地址固定为 0x789ABC。
* - '1'/'2'/'3': 模拟系统的正常、已布防、已撤防状态切换。
* - 'z'/'Z': 模拟超时,强行使闲置计数器置 6000触发自动深度休眠。
* - 't'/'T': 触发屏幕与无线控制引脚测试。
*/
void Debug_ProcessCommand(char cmd) void Debug_ProcessCommand(char cmd)
{ {
SystemEvent debug_evt; SystemEvent debug_evt;
inactivity_timer = 0; // 调试命令重置闲置计时器 inactivity_timer = 0; // 收到指令,立即重置用户的无操作闲置计时器
debug_evt.extra_data = 0; debug_evt.extra_data = 0;
debug_evt.extra_size = 0; debug_evt.extra_size = 0;
if (cmd == 'u') { if (cmd == 'u') {
debug_evt.key_event = KEY_EVENT_UP_CLICK; debug_evt.key_event = KEY_EVENT_UP_CLICK;
debug_evt.priority = EVENT_PRIORITY_NORMAL; debug_evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(debug_evt); EventQueue_Push(debug_evt); // 压入队尾
Uart_SendString("[UART] KEY_UP Clicked\r\n"); Uart_SendString("[UART] KEY_UP Clicked\r\n");
} else if (cmd == 'U') { } else if (cmd == 'U') {
debug_evt.key_event = KEY_EVENT_UP_LONG; debug_evt.key_event = KEY_EVENT_UP_LONG;
@@ -71,7 +88,7 @@ void Debug_ProcessCommand(char cmd)
} else if (cmd == 's') { } else if (cmd == 's') {
debug_evt.key_event = KEY_EVENT_SOS_CLICK; debug_evt.key_event = KEY_EVENT_SOS_CLICK;
debug_evt.priority = EVENT_PRIORITY_URGENT; debug_evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(debug_evt); EventQueue_InsertFront(debug_evt); // 紧急事件插入队首
Uart_SendString("[UART] KEY_SOS Clicked\r\n"); Uart_SendString("[UART] KEY_SOS Clicked\r\n");
} else if (cmd == 'S') { } else if (cmd == 'S') {
debug_evt.key_event = KEY_EVENT_SOS_LONG; debug_evt.key_event = KEY_EVENT_SOS_LONG;
@@ -84,22 +101,24 @@ void Debug_ProcessCommand(char cmd)
EventQueue_Push(debug_evt); EventQueue_Push(debug_evt);
Uart_SendString("[UART] KEY_UP+DOWN Combined\r\n"); Uart_SendString("[UART] KEY_UP+DOWN Combined\r\n");
} else if (cmd == 'a' || cmd == 'A') { } else if (cmd == 'a' || cmd == 'A') {
u32 mock_addr = 0x123456; u32 mock_addr = 0x123456; // 设定模拟的报警射频源地址
u8 mock_slot; u8 mock_slot;
// 如果本地数据库中没有匹配该射频,则强制登记进 1 号防区
if (!Check_Sensor_ID(mock_addr, &mock_slot)) { if (!Check_Sensor_ID(mock_addr, &mock_slot)) {
Add_Sensor_With_Zone(mock_addr, 0, 1); Add_Sensor_With_Zone(mock_addr, 0, 1);
} }
debug_evt.key_event = KEY_EVENT_NONE; debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_HIGH; debug_evt.priority = EVENT_PRIORITY_HIGH; // 入侵报警设为 HIGH 优先级
debug_evt.extra_data = mock_addr; debug_evt.extra_data = mock_addr;
debug_evt.extra_size = 4; debug_evt.extra_size = 4;
EventQueue_InsertFront(debug_evt); EventQueue_InsertFront(debug_evt); // 高优先级插队
Uart_SendString("[UART] Mock RF Alarm\r\n"); Uart_SendString("[UART] Mock RF Alarm\r\n");
} else if (cmd == 'p' || cmd == 'P') { } else if (cmd == 'p' || cmd == 'P') {
// 只有当手环切入 Pair 模式时,模拟对码信号才有效
if (AppManager_GetActiveAppID() == APP_ID_PAIR) { if (AppManager_GetActiveAppID() == APP_ID_PAIR) {
debug_evt.key_event = KEY_EVENT_NONE; debug_evt.key_event = KEY_EVENT_NONE;
debug_evt.priority = EVENT_PRIORITY_NORMAL; debug_evt.priority = EVENT_PRIORITY_NORMAL;
debug_evt.extra_data = 0x789ABC; debug_evt.extra_data = 0x789ABC; // 设定模拟对码硬件地址
debug_evt.extra_size = 4; debug_evt.extra_size = 4;
EventQueue_Push(debug_evt); EventQueue_Push(debug_evt);
Uart_SendString("[UART] Mock RF Pair Signal\r\n"); Uart_SendString("[UART] Mock RF Pair Signal\r\n");
@@ -107,26 +126,32 @@ void Debug_ProcessCommand(char cmd)
Uart_SendString("[UART] Not in PAIR mode\r\n"); Uart_SendString("[UART] Not in PAIR mode\r\n");
} }
} else if (cmd == '1') { } else if (cmd == '1') {
// 模拟切换至 正常撤防 (NORMAL) 状态并刷新时钟
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_NORMAL; current_state = STATE_NORMAL;
UI_ShowClockPage(current_state, current_hour, current_min); UI_ShowClockPage(current_state, current_hour, current_min);
} }
Uart_SendString("[UART] State: NORMAL\r\n"); Uart_SendString("[UART] State: NORMAL\r\n");
} else if (cmd == '2') { } else if (cmd == '2') {
// 模拟切换至 已布防 (ARMED) 状态,普通门磁可触发警报
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_ARMED; current_state = STATE_ARMED;
UI_ShowClockPage(current_state, current_hour, current_min); UI_ShowClockPage(current_state, current_hour, current_min);
} }
Uart_SendString("[UART] State: ARMED\r\n"); Uart_SendString("[UART] State: ARMED\r\n");
} else if (cmd == '3') { } else if (cmd == '3') {
// 模拟切换至 已撤防 (DISARMED) 状态,忽略普通报警
if (AppManager_GetActiveAppID() == APP_ID_CLOCK) { if (AppManager_GetActiveAppID() == APP_ID_CLOCK) {
current_state = STATE_DISARMED; current_state = STATE_DISARMED;
UI_ShowClockPage(current_state, current_hour, current_min); UI_ShowClockPage(current_state, current_hour, current_min);
} }
Uart_SendString("[UART] State: DISARMED\r\n"); Uart_SendString("[UART] State: DISARMED\r\n");
} else if (cmd == 'z' || cmd == 'Z') { } else if (cmd == 'z' || cmd == 'Z') {
inactivity_timer = 1000; // 模拟闲置超时,强制触发休眠 inactivity_timer = 6000; // 模拟闲置 60 秒超时,强制触发主循环深度休眠
Uart_SendString("[UART] Force Entering Sleep mode!\r\n"); Uart_SendString("[UART] Force Entering Sleep mode!\r\n");
} else if (cmd == 't' || cmd == 'T') {
Loopback_Test(); // 调用回环测试验证屏幕
RF_Test_Pin(); // 无线射频测试
} }
} }
@@ -137,72 +162,89 @@ void main(void)
{ {
char cmd; char cmd;
WTST = 0; // ====== STC32G 特殊功能寄存器内核基础初始化 ======
EAXFR = 1; WTST = 0; // 设置程序 Flash 访问等待时间为 0 (最高速运行)
CKCON = 0; EAXFR = 1; // 允许访问扩展特殊功能寄存器 (XSFR)
WDT_CONTR = 0x00; CKCON = 0; // 外部总线时钟设定为最快
WDT_CONTR = 0x00; // 关闭看门狗定时器
GPIO_Init(); // ====== 硬件各模块基础初始化 ======
RF_Init(); GPIO_Init(); // 配置各个引脚的推挽输出/准双向模式及内部上拉
Delay_ms(500); RF_Init(); // 初始化射频芯片各引脚控制
SGM_SendPulse(27); Delay_ms(500); // 软启动延时,等待供电电容稳定
Delay_ms(50); SGM_SendPulse(27); // 通过脉冲给 SGM3833 供电芯片发指令,稳压开启 LCD 正负电压轨
LCD_Init(); Delay_ms(50); // 等待电压充盈稳定,避开浪涌
Uart1_Init(); LCD_Init(); // 初始化 Truly AMOLED 屏幕控制寄存器序列
Uart1_Init(); // 初始化串口 1 波特率 115200 供调试日志
Uart_SendString("Wristband System Initialized!\r\n"); Uart_SendString("Wristband System Initialized!\r\n");
Load_Database();
/* 步骤1: 初始化事件队列 */ Load_Database(); // 从 IAP Flash 第 254 扇区读取所有已绑定的传感器数据至 RAM
/* 步骤 1: 初始化事件环形缓冲区队列 */
EventQueue_Init(); EventQueue_Init();
/* 步骤2+5: 初始化应用管理器 */ /* 步骤 2+5: 初始化应用管理器并登记所有前台应用,默认加载 ClockApp */
AppManager_Init(); AppManager_Init();
/* 启动1ms定时器中断 */ /* 启动 1ms 系统基准定时器 Timer1 */
Timer1_Init(); Timer1_Init();
Uart_SendString("[SYS] Event-driven framework ready\r\n"); Uart_SendString("[SYS] Event-driven framework ready\r\n");
// ====== 主循环 ======
while (1) { while (1) {
/* 串口调试命令 → 以事件方式入队 */ /* 1. 串口非阻塞指令捕获 -> 装配为事件方式入队 */
cmd = Uart_RxChar(); cmd = Uart_RxChar();
if (cmd != '\0') { if (cmd != '\0') {
Debug_ProcessCommand(cmd); Debug_ProcessCommand(cmd);
} }
/* 步骤3: 事件分发 — 循环处理队列中所有事件 */ /* 2. 事件分发总线:循环轮询并处理队列中所有有效事件 */
while (!EventQueue_IsEmpty()) { while (!EventQueue_IsEmpty()) {
Event_Dispatcher_Loop(); Event_Dispatcher_Loop();
} }
/* 步骤5: 运行活跃应用 (含CPU占用控制) */ /* 3. 应用调度中心:运行当前处于前台活跃状态的应用(集成了 CPU 运行时间超时挂起保护) */
AppManager_RunActiveApp(); AppManager_RunActiveApp();
/* 步骤6: 超时自动进入深度休眠 */ /* 4. 自动休眠判定:常态无操作闲置达到 60 秒 (6000 * 10ms = 60s) 时,切入低功耗停机休眠 */
if (inactivity_timer >= 1000) { if (inactivity_timer >= 6000) {
inactivity_timer = 0; inactivity_timer = 0;
current_state = STATE_SLEEP; current_state = STATE_SLEEP; // 系统置为休眠状态
Enter_Low_Power_Sleep(); Enter_Low_Power_Sleep(); // 该函数是阻塞的MCU 将在此挂起。唤醒后将自动从其后继续执行
} }
} }
} }
// 唤醒源事件标志,由对应的外部中断 ISR 写入
volatile u8 p0_wakeup_flag = 0; // P0 端口按键唤醒标志
volatile u8 p2_wakeup_flag = 0; // P2 端口按键唤醒标志
volatile u8 rf_wakeup_flag = 0; // P2.1 射频中断唤醒标志
/* ========================================================================= /* =========================================================================
* STC32G 端口中断 ISR * STC32G 端口掉电外部中断唤醒中断服务程序 (ISR)
* ========================================================================= */ * ========================================================================= */
// Port0 中断服务函数 (由 isr_jump.asm 从向量 37 @ 0x012BH 跳转至此) /**
* @brief Port0 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
* @details 负责处理 P0.1, P0.2, P0.3 按键在休眠模式下的物理触发,并立刻禁用中断防止反复执行。
*/
void Port0_Isr(void) interrupt 13 void Port0_Isr(void) interrupt 13
{ {
EAXFR = 1; EAXFR = 1; // 使能访问扩展寄存区
P0INTF = 0x00; // 清除 P0 端口中断悬挂标志 p0_wakeup_flag = P0INTF | 0x01;// 读取并备份 P0 中断标志寄存器值 (0x01作安全掩码)
P0INTE = 0x00; // 立即禁用,防止低电平保持期间反复触发 P0INTF = 0x00; // 清除 P0 端口中断悬挂标志位
P0INTE = 0x00; // 【关键保护】:立即关闭 P0 中断允许,防止按键处于低电平期间反复触发 ISR 导致堆栈溢出
} }
// Port2 中断服务函数 (由 isr_jump.asm 从向量 39 @ 0x013BH 跳转至此) /**
* @brief Port2 端口掉电中断服务函数 (由 isr_jump.asm 引导定位至此)
* @details 负责处理 P2.6 (SOS按键) 与 P2.1 (射频数据脚) 在休眠模式下的物理跳变唤醒。
*/
void Port2_Isr(void) interrupt 15 void Port2_Isr(void) interrupt 15
{ {
EAXFR = 1; EAXFR = 1; // 使能扩展寄存区
P2INTF = 0x00; // 清除 P2 端口中断悬挂标志 p2_wakeup_flag = P2INTF | 0x01;// 读取并备份 P2 中断标志寄存区的值
P2INTE = 0x00; // 立即禁用,防止低电平保持期间反复触发 P2INTF = 0x00; // 清除 P2 端口中断悬挂标志位
P2INTE = 0x00; // 立即关闭 P2 中断通道,防止低电平期间反复重入
} }

View File

@@ -1,47 +1,77 @@
#include "rgb.h" #include "rgb.h"
/* ====== SPI 寄存器定义 (如果 stc32g.h 定义) ====== */ /* ====== SPI 寄存器映射地址定义 (防止 stc32g.h 库版本中未显式定义) ====== */
#ifndef P_SW1 #ifndef P_SW1
sfr P_SW1 = 0xA2; sfr P_SW1 = 0xA2; // 外设端口切换寄存器 1
sfr SPCTL = 0xCE; sfr SPCTL = 0xCE; // SPI 控制寄存器
sfr SPSTAT = 0xCD; sfr SPSTAT = 0xCD; // SPI 状态寄存器
sfr SPDAT = 0xCF; sfr SPDAT = 0xCF; // SPI 数据寄存器
#endif #endif
/**
* @brief 初始化内核 SPI 控制器
* @details 将 SPI 的物理管脚切换至 P2 端口的第二组外设映射位置SCLK=P2.5, MOSI=P2.3
* 并配置 SPI 传输速率,使其产生的脉冲宽满足 WS2812B/FCOB 的 1 码和 0 码时序。
*/
void SPI_Init(void) void SPI_Init(void)
{ {
P_SW1 = (P_SW1 & ~0x0C) | 0x04; // 映射 SPI 至第二组 (P2.2~P2.5) // 将 P_SW1 寄存器的第 2、3 位 (SPI_S) 配置为 01即将 SPI 外设映射至 P2.2~P2.5 端口上
SPCTL = 0xD1; // SSIG=1, SPEN=1, MSTR=1, SPR0=1 (3.0 MHz) P_SW1 = (P_SW1 & ~0x0C) | 0x04;
SPSTAT = 0xC0; // 清除标志
// 配置 SPCTL (SPI Control Register):
// SSIG = 1 (忽略SS引脚置为主机)
// SPEN = 1 (启用SPI模块)
// MSTR = 1 (配置为主机模式)
// SPR0 = 1 (3MHz communication speed @ 24MHz System Clock)
SPCTL = 0xD1;
// 清除 SPI 状态标志 (写入1清除 SPIF 和 WCOL 标志位)
SPSTAT = 0xC0;
} }
/**
* @brief 字节数据转 3-bit 编码的 WS2812B 驱动波形数据
* @param val 要转码的一个 8 位色彩数值 (如R、G、B颜色分量)
* @param out 转换后存储目标的高速缓冲缓冲区首地址1 字节拆分成 3 字节表示物理波形
* @details WS2812B 通讯协议需要通过高低电平的持续脉冲时间比来判断 "1" 码或 "0" 码。
* 通过将 SPI 速率设为 3.0 Mbps即每个 SPI bit 占 ~333ns
* - 一个 WS2812B 协议的 "1" 码800ns高, 450ns低可用二进制 `110` (高高低,~666ns高, ~333ns低) 来模拟;
* - 一个 WS2812B 协议的 "0" 码400ns高, 850ns低可用二进制 `100` (高低低,~333ns高, ~666ns低) 来模拟;
* 因此1 个字节8 bits的颜色深度数据被完美重映射编码为 24 个 SPI 数据 bits共需 3 个 SPI 字节发送)。
*/
void Encode_Byte(u8 val, u8 *out) void Encode_Byte(u8 val, u8 *out)
{ {
u32 spi_val = 0; u32 spi_val = 0; // 用于拼接 24 位 SPI 映射串
u8 i; u8 i;
for (i = 0; i < 8; i++) for (i = 0; i < 8; i++)
{ {
spi_val <<= 3; spi_val <<= 3; // 每次左移 3 位,为下一个像素的波形让出空间
if (val & (0x80 >> i)) if (val & (0x80 >> i))
{ {
spi_val |= 6; // 1码 -> 110 spi_val |= 6; // 该 bit 为 1对应 WS2812B "1" 码时序,用二进制 '110' (即数值6) 填充
} }
else else
{ {
spi_val |= 4; // 0码 -> 100 spi_val |= 4; // 该 bit 为 0对应 WS2812B "0" 码时序,用二进制 '100' (即数值4) 填充
} }
} }
// 将拼接好的 24 位波形数据(前高位后低位)拆分装载进输出缓冲区中
out[0] = (u8)(spi_val >> 16); out[0] = (u8)(spi_val >> 16);
out[1] = (u8)(spi_val >> 8); out[1] = (u8)(spi_val >> 8);
out[2] = (u8)spi_val; out[2] = (u8)spi_val;
} }
/**
* @brief 发送两颗级联 RGB 灯珠的驱动命令
* @details 负责将两组 GRB 格式色彩数据g1, r1, b1 和 g2, r2, b2转换为 WS2812B 的 SPI 脉冲编码,
* 并在发送期间实施硬件引脚复用隔离,发送完成后发出复位复位电平。
*/
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2) void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2)
{ {
u8 idata spi_buf[18]; u8 idata spi_buf[18]; // 2 颗灯 * 3 个基色通道 * 3 字节编码 = 18 字节缓冲
u8 i; u8 i;
// 转码 3-bit 编码 // 分别转码两颗灯珠的 G、R、B 通道,顺序填充进 SPI 缓冲区
Encode_Byte(g1, &spi_buf[0]); Encode_Byte(g1, &spi_buf[0]);
Encode_Byte(r1, &spi_buf[3]); Encode_Byte(r1, &spi_buf[3]);
Encode_Byte(b1, &spi_buf[6]); Encode_Byte(b1, &spi_buf[6]);
@@ -49,36 +79,44 @@ void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2)
Encode_Byte(r2, &spi_buf[12]); Encode_Byte(r2, &spi_buf[12]);
Encode_Byte(b2, &spi_buf[15]); Encode_Byte(b2, &spi_buf[15]);
EA = 0; // 关中断 EA = 0; // 关闭全局中断,防止 SPI 数据发送被高频中断打断,造成 WS2812B 的时序畸变
// 临时将 P2.5 (MOTOR) 配置为高阻输入以隔离时钟信号 // 【硬件复用隔离机制】:
// 物理引脚 P2.5 同时被用作“振动马达控制端 (MOTOR)”和“SPI SCLK 时钟信号输出”。
// 在 SPI 发送 RGB 期间SCLK 将高速振荡,这会导致马达发生异常高频振动甚至噪音。
// 因此在启动 SPI 发送前,必须先将 P2.5 (MOTOR) 临时配置为高阻输入模式M1=1, M0=0
// 从而与 SPI 时钟引脚在电气上实现物理隔离,防止杂散时钟信号驱动马达。
P2M1 |= (1 << 5); P2M1 |= (1 << 5);
P2M0 &= ~(1 << 5); P2M0 &= ~(1 << 5);
SPI_Init(); // 启动 SPI SPI_Init(); // 配置并启动 SPI 外设控制器
// 循环发送 18 字节的 WS2812B 脉冲编码数据
for (i = 0; i < 18; i++) for (i = 0; i < 18; i++)
{ {
SPDAT = spi_buf[i]; SPDAT = spi_buf[i]; // 装载待发数据到数据寄存器
while (!(SPSTAT & 0x80)); // 等待发送完毕 while (!(SPSTAT & 0x80)); // 等待 SPI 状态寄存器第 7 位 (SPIF) 变为 1代表发送结束
SPSTAT = 0xC0; SPSTAT = 0xC0; // 往状态寄存器写入1以清零 SPIF 和 WCOL 标志
} }
SPCTL = 0; // 关闭 SPI SPCTL = 0; // 彻底关闭 SPI 控制器,释放总线
// 配置 P2.3 (RGB_DIN) 为推挽输出并拉低 100us 做 Reset // 【发送 RESET 复位低电平帧】:
// WS2812B 驱动芯片要求在所有色彩帧数据发完后DIN 必须维持大于 80us 的低电平以将数据刷入锁存。
// 首先将 P2.3 (RGB_DIN) 重新配置为推挽输出模式 (M1=0, M0=1)
P2M1 &= ~(1 << 3); P2M1 &= ~(1 << 3);
P2M0 |= (1 << 3); P2M0 |= (1 << 3);
RGB_DIN = 0; RGB_DIN = 0; // 强行拉低 DIN 信号线
for (i = 0; i < 200; i++) for (i = 0; i < 200; i++)
{ {
_nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); // 软件延时,保持低电平时间大于 100微秒
} }
// 恢复 P2.5 (MOTOR) 为推挽输出以进行振动控制 // 恢复复用引脚功能】:
// 恢复 P2.5 (MOTOR) 引脚为普通的推挽输出模式 (M1=0, M0=1),以便重新由定时器或应用控制振动马达
P2M1 &= ~(1 << 5); P2M1 &= ~(1 << 5);
P2M0 |= (1 << 5); P2M0 |= (1 << 5);
MOTOR = 0; MOTOR = 0; // 默认拉低马达引脚,防止发声
EA = 1; // 开中断 EA = 1; // 恢复全局中断使能
} }

View File

@@ -3,10 +3,26 @@
#include "config.h" #include "config.h"
// SPI 及 RGB 发送初始化 /* ====== SPI 控制与级联 WS2812B (FCOB 幻彩灯条) 驱动接口 ====== */
/**
* @brief 初始化 SPI 控制器以驱动 RGB 幻彩灯条
* @details 将 SPI 引脚切换映射到第二组物理引脚 (P2.2~P2.5),并配置 SPI 模块为
* 主机模式 (Master),将通讯时钟速率设置为 3.0 MHz以利用 SPI 字节发送功能
* 高效产生 WS2812B 所需的精确脉冲宽度时序。
*/
void SPI_Init(void); void SPI_Init(void);
// 向 FCOB 幻彩灯条发送两颗 LED 的 GRB 数据 /**
* @brief 向 FCOB 幻彩灯条发送两颗级联 LED 灯珠的 GRB 三原色数据
* @param g1 LED 1 的绿色亮度 (0~255)
* @param r1 LED 1 的红色亮度 (0~255)
* @param b1 LED 1 的蓝色亮度 (0~255)
* @param g2 LED 2 的绿色亮度 (0~255)
* @param r2 LED 2 的红色亮度 (0~255)
* @param b2 LED 2 的蓝色亮度 (0~255)
* @note 由于 WS2812B 内部芯片使用 GRB 顺序,此函数将色彩数据组合编码后通过 SPI 发送出去。
*/
void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2); void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2);
#endif #endif // __RGB_H__

View File

@@ -6,80 +6,102 @@
#include "app_manager.h" #include "app_manager.h"
#include "database.h" #include "database.h"
// 外部声明的中断唤醒标志 (在 main.c 中定义,用于在中断中记录是谁唤醒了系统)
extern volatile u8 p0_wakeup_flag;
extern volatile u8 p2_wakeup_flag;
extern volatile u8 rf_wakeup_flag;
/**
* @brief 初始化 STC32G 单片机的 GPIO 管脚模式及初始电平
* @details 按照电路原理图对各个端口的输入输出模式、内部上拉、外设扩展功能进行配置。
*/
void GPIO_Init(void) void GPIO_Init(void)
{ {
IE = 0x00; IE = 0x00; // 禁用 CPU 全局中断及常规中断
IE2 = 0x00; IE2 = 0x00; // 禁用扩展中断
TCON = 0x00; TCON = 0x00; // 清零定时器控制寄存器,关闭 Timer0/1 的常规控制
TMOD &= 0xF0; TMOD &= 0xF0; // 清零 Timer0 工作模式
TR0 = 0; TR0 = 0; // 停止 Timer0 运行
ET0 = 0; ET0 = 0; // 禁用 Timer0 中断允许
AUXR = 0x00; AUXR = 0x00; // 辅助寄存器清零,时钟分频恢复默认
INTCLKO = 0x00; INTCLKO = 0x00; // 禁用时钟输出与固定外部中断触发
P_SW1 = 0x00; P_SW1 = 0x00; // 重置外设映射 1 (串口1、SPI、I2C等)
P_SW2 = 0x80; // 开启 EAXFR=1,允许访问 P0PU/P2PU/P0INTE/P2INTE 等扩展 SFR 寄存器 P_SW2 = 0x80; // 开启 EAXFR (Extended Access Flag Register),允许访问 P0PU/P2PU/P0INTE/P2INTE 等扩展 SFR 寄存器
P_SW3 = 0x00; P_SW3 = 0x00; // 重置外设映射 3
// 配置 P0.0 (BAT_ADC) 为高阻输入模式 // 1. 配置 P0.0 (BAT_ADC) 电池电量采集管脚为高阻输入模式 (M1=1, M0=0)
P0M1 |= (1 << 0); P0M1 |= (1 << 0);
P0M0 &= ~(1 << 0); P0M0 &= ~(1 << 0);
// 配置 P0.1 (KEY_UP), P0.2 (KEY_CONFIRM), P0.3 (KEY_DOWN) 为准双向口模式并置 1 // 2. 配置 P0.1 (KEY_UP), P0.2 (KEY_CONFIRM), P0.3 (KEY_DOWN) 按键引脚为准双向口模式并置 1 (M1=0, M0=0)
P0M1 &= ~((1 << 1) | (1 << 2) | (1 << 3)); P0M1 &= ~((1 << 1) | (1 << 2) | (1 << 3));
P0M0 &= ~((1 << 1) | (1 << 2) | (1 << 3)); P0M0 &= ~((1 << 1) | (1 << 2) | (1 << 3));
KEY_UP = 1; KEY_UP = 1;
KEY_CONFIRM = 1; KEY_CONFIRM = 1;
KEY_DOWN = 1; KEY_DOWN = 1;
// 配置 P2.3 (RGB_DIN) 为推挽输出模式 // 3. 配置 P2.3 (RGB_DIN) 幻彩灯条控制线为推挽输出模式 (M1=0, M0=1)
P2M1 &= ~(1 << 3); P2M1 &= ~(1 << 3);
P2M0 |= (1 << 3); P2M0 |= (1 << 3);
RGB_DIN = 0; RGB_DIN = 0; // 默认拉低,保持复位时序状态
// 配置 P2.5 (MOTOR) 为推挽输出模式 // 4. 配置 P2.5 (MOTOR) 振动马达控制端为推挽输出模式 (M1=0, M0=1)
P2M1 &= ~(1 << 5); P2M1 &= ~(1 << 5);
P2M0 |= (1 << 5); P2M0 |= (1 << 5);
MOTOR = 0; MOTOR = 0; // 默认不震动
// 配置 P2.6 (KEY_SOS) 为准双向口模式并置 1 // 5. 配置 P2.6 (KEY_SOS) 物理求救按键引脚为准双向口模式并置 1 (M1=0, M0=0)
P2M1 &= ~(1 << 6); P2M1 &= ~(1 << 6);
P2M0 &= ~(1 << 6); P2M0 &= ~(1 << 6);
KEY_SOS = 1; KEY_SOS = 1;
// 配置 P2.2 (SHUT) 为推挽输出模式 // 6. 配置 P2.2 (SHUT) 射频芯片使能引脚为推挽输出模式 (M1=0, M0=1)
P2M1 &= ~(1 << 2); P2M1 &= ~(1 << 2);
P2M0 |= (1 << 2); P2M0 |= (1 << 2);
SHUT = 1; // 默认拉高开启射频接收芯片工作 SHUT = 1; // 默认拉高使能开启,允许无线芯片运行
// 开启按键引脚 P0.1, P0.2, P0.3 和 P2.6 的内部上拉电阻,避免引脚抖动误唤醒 // 7. 配置 P0.1, P0.2, P0.3 和 P2.6 的内部上拉电阻,避免引脚因浮空抖动误唤醒系统
P0PU |= 0x0E; P0PU |= 0x0E; // 开启 P0.1/P0.2/P0.3 上拉
P2PU |= 0x40; P2PU |= 0x40; // 开启 P2.6 上拉
// 配置 P2.7 (DET) 为高阻输入模式 // 8. 配置 P2.7 (DET) 充电检测脚为高阻输入模式 (M1=1, M0=0)
P2M1 |= (1 << 7); P2M1 |= (1 << 7);
P2M0 &= ~(1 << 7); P2M0 &= ~(1 << 7);
// 配置 P3.0 (TXD), P3.1 (RXD) 和 P3.4 (SGM_CTRL) // 9. 配置 P3.0 (UART1_RXD - 输入)、P3.1 (UART1_TXD - 输出) 以及 P3.4 (SGM_CTRL - 屏幕电源控制,输出)
P3M1 &= ~((1 << 0) | (1 << 1) | (1 << 4)); P3M1 &= ~((1 << 0) | (1 << 1) | (1 << 4)); // 清除 M1 相应位
P3M0 &= ~(1 << 0); P3M0 &= ~(1 << 0); // 准双向 / 输入模式
P3M0 |= ((1 << 1) | (1 << 4)); P3M0 |= ((1 << 1) | (1 << 4)); // 推挽输出模式 (TXD 和 SGM_CTRL)
} }
/**
* @brief 阻塞式毫秒级延时函数 (基于 CPU 频率估算环路次数)
* @param ms 延迟毫秒数
*/
void Delay_ms(u16 ms) void Delay_ms(u16 ms)
{ {
u16 i, j; u16 i, j;
for (i = 0; i < ms; i++) for (i = 0; i < ms; i++)
// 24MHz 晶振下,双重循环实现约 1ms 延迟
for (j = 12000; j > 0; j--); for (j = 12000; j > 0; j--);
} }
/**
* @brief 精确的 10 微秒延时函数 (利用 8051 指令循环时间)
*/
void Delay10us(void) void Delay10us(void)
{ {
unsigned char data i; unsigned char data i;
_nop_(); _nop_();
i = 30; i = 30; // 循环常数
while (--i); while (--i);
} }
/**
* @brief 规避 16 位寄存器在读取瞬间发生进位导致的读错问题
* @details 采用双重读取比对法,读取 TH0 -> TL0 -> 再读一次 TH0如果两次高 8 位一致,代表没有发生高低字节间的溢出进位,是安全的。
* @return u16 当前 Timer0 的 16 位计数值
*/
u16 GetTimer0_Safe(void) u16 GetTimer0_Safe(void)
{ {
u8 h1, l, h2; u8 h1, l, h2;
@@ -87,64 +109,88 @@ u16 GetTimer0_Safe(void)
h1 = TH0; h1 = TH0;
l = TL0; l = TL0;
h2 = TH0; h2 = TH0;
} while (h1 != h2); } while (h1 != h2); // 当两次 TH0 值相同时说明读取安全,退出循环
return ((u16)h1 << 8) | l; return ((u16)h1 << 8) | l;
} }
/**
* @brief 基于 Timer0 的精确微秒延时函数
* @param us 延时微秒数
*/
void Delay_us(u16 us) void Delay_us(u16 us)
{ {
// 根据系统时钟频率及 T0 运行在 12T 模式下计算时钟 Tick 计数量
u16 ticks = (u16)((u32)us * (MAIN_Fosc / 1000000UL) / 12UL); u16 ticks = (u16)((u32)us * (MAIN_Fosc / 1000000UL) / 12UL);
TL0 = 0; TL0 = 0; // 清零 Timer0 低字节
TH0 = 0; TH0 = 0; // 清零 Timer0 高字节
TR0 = 1; TR0 = 1; // 启动 Timer0 计数
while (GetTimer0_Safe() < ticks); while (GetTimer0_Safe() < ticks); // 等待计数达到目标值
TR0 = 0; TR0 = 0; // 停止 Timer0
} }
/**
* @brief 初始化串口 1 (UART1) 为 115200 波特率,主要用于调试输出
*/
void Uart1_Init(void) void Uart1_Init(void)
{ {
// 计算 115200 波特率在定时器 2 (1T模式) 的重装载值
u16 reload = (u16)(65536UL - (MAIN_Fosc / 4 / 115200UL)); u16 reload = (u16)(65536UL - (MAIN_Fosc / 4 / 115200UL));
SCON = 0x50; SCON = 0x50; // 8位数据位允许串口接收 (REN=1)
AUXR |= 0x01; AUXR |= 0x01; // 串口 1 波特率发生器选择定时器 2
AUXR |= 0x04; AUXR |= 0x04; // 定时器 2 工作在 1T 模式 (无分频)
T2L = (u8)reload; T2L = (u8)reload; // 载入重装载低字节
T2H = (u8)(reload >> 8); T2H = (u8)(reload >> 8);// 载入重装载高字节
AUXR |= 0x10; AUXR |= 0x10; // 启动定时器 2
TI = 0; TI = 0; // 清除串口发送标志位
} }
/**
* @brief 串口 1 发送单字节数据,并在发送时屏蔽接收以防自收自发杂波干扰
* @param dat 待发送的数据字节
*/
void Uart_SendByte(u8 dat) void Uart_SendByte(u8 dat)
{ {
REN = 0; REN = 0; // 临时关闭串口接收,避免发送杂波反灌被接收中断误认
SBUF = dat; SBUF = dat; // 送入串口数据寄存器启动发送
while (!TI); while (!TI);// 阻塞等待 TI (Transmit Interrupt) 变为 1表示发送完毕
TI = 0; TI = 0; // 清除发送中断标志位
RI = 0; RI = 0; // 顺便清零接收标志,确保没有误动作
REN = 1; REN = 1; // 重新开启接收允许
} }
/**
* @brief 串口发送字符串,直到遇到 '\0' 结束
* @param s 字符串首地址
*/
void Uart_SendString(char *s) void Uart_SendString(char *s)
{ {
REN = 0; REN = 0; // 临时关闭接收
while (*s) { while (*s) {
SBUF = *s++; SBUF = *s++;
while (!TI); while (!TI); // 等待发送结束
TI = 0; TI = 0;
} }
RI = 0; RI = 0;
REN = 1; REN = 1; // 重新开启接收
} }
/**
* @brief 非阻塞接收串口字符
* @return char 接收到的字符,若无则返回 0
*/
char Uart_RxChar(void) char Uart_RxChar(void)
{ {
if (RI) { if (RI) {
char c = SBUF; char c = SBUF; // 读取接收数据
RI = 0; RI = 0; // 手动清零接收标志位
return c; return c; // 返回字符
} }
return 0; return 0;
} }
/**
* @brief 串口输出 4 位十六进制数 (半字节0x0~0xF)
*/
void Uart_SendHex4(u8 val) void Uart_SendHex4(u8 val)
{ {
val &= 0x0F; val &= 0x0F;
@@ -154,12 +200,18 @@ void Uart_SendHex4(u8 val)
Uart_SendByte((u8)('A' + (val - 10))); Uart_SendByte((u8)('A' + (val - 10)));
} }
/**
* @brief 串口输出 8 位十六进制数 (单字节0x00~0xFF)
*/
void Uart_SendHex8(u8 val) void Uart_SendHex8(u8 val)
{ {
Uart_SendHex4(val >> 4); Uart_SendHex4(val >> 4); // 输出高四位
Uart_SendHex4(val); Uart_SendHex4(val); // 输出低四位
} }
/**
* @brief 串口输出 20 位十六进制数 (射频 24 位地址的有效 20 位表达)
*/
void Uart_SendHex20(u32 val) void Uart_SendHex20(u32 val)
{ {
Uart_SendHex4((u8)(val >> 16)); Uart_SendHex4((u8)(val >> 16));
@@ -167,6 +219,9 @@ void Uart_SendHex20(u32 val)
Uart_SendHex8((u8)val); Uart_SendHex8((u8)val);
} }
/**
* @brief 串口输出 32 位十六进制数
*/
void Uart_SendHex32(u32 val) void Uart_SendHex32(u32 val)
{ {
Uart_SendHex8((u8)(val >> 24)); Uart_SendHex8((u8)(val >> 24));
@@ -175,17 +230,27 @@ void Uart_SendHex32(u32 val)
Uart_SendHex8((u8)val); Uart_SendHex8((u8)val);
} }
/**
* @brief 串口输出 16 位十六进制数
*/
void Uart_SendHex16(u16 val) void Uart_SendHex16(u16 val)
{ {
Uart_SendHex8((u8)(val >> 8)); Uart_SendHex8((u8)(val >> 8));
Uart_SendHex8((u8)val); Uart_SendHex8((u8)val);
} }
/**
* @brief 将数值格式化为 8 位十六进制形式的 "ADDR: 0xXXXXX" 字符串格式并清零尾部
* @param val 十六进制数值
* @param buf 输出接收缓冲区
*/
void FormatHex(u32 val, char *buf) void FormatHex(u32 val, char *buf)
{ {
u8 i; u8 i;
// 拷贝固定前缀 "ADDR: 0x"
buf[0] = 'A'; buf[1] = 'D'; buf[2] = 'D'; buf[3] = 'R'; buf[4] = ':'; buf[0] = 'A'; buf[1] = 'D'; buf[2] = 'D'; buf[3] = 'R'; buf[4] = ':';
buf[5] = ' '; buf[6] = '0'; buf[7] = 'x'; buf[5] = ' '; buf[6] = '0'; buf[7] = 'x';
// 循环提取 20 位射频地址对应的 5 个半字节字符
for (i = 0; i < 5; i++) { for (i = 0; i < 5; i++) {
u8 nibble = (val >> (4 * (4 - i))) & 0x0F; u8 nibble = (val >> (4 * (4 - i))) & 0x0F;
if (nibble < 10) if (nibble < 10)
@@ -193,233 +258,180 @@ void FormatHex(u32 val, char *buf)
else else
buf[8 + i] = 'A' + (nibble - 10); buf[8 + i] = 'A' + (nibble - 10);
} }
buf[13] = '\0'; buf[13] = '\0'; // 尾部安全零字符
} }
/**
* @brief 系统进入低功耗深度休眠 (Power-Down) 核心逻辑流程
* @details 实施对屏幕、电源芯片、以及各个总线管脚的低功耗关断,配置相应的端口外部唤醒中断,
* 然后将 MCU 的置入 PCON 的 PD 模式,直至被外部硬件动作唤醒。
*/
void Enter_Low_Power_Sleep(void) void Enter_Low_Power_Sleep(void)
{ {
// 串口打印进入休眠提示
Uart_SendString("[SYS] Entering low power sleep...\r\n"); Uart_SendString("[SYS] Entering low power sleep...\r\n");
// 1. 熄灭 FCOB 双幻彩灯条 // 1. 关闭/熄灭 FCOB 灯条
RGB_Send(0, 0, 0, 0, 0, 0); RGB_Send(0, 0, 0, 0, 0, 0);
// 2. 向屏幕发送 Display OFF (0x28) 彻底关闭像素发光显示 // 2. 向 AMOLED 屏幕控制芯片 RM69310 写入 Display OFF 指令 (0x28),关闭屏幕发光
WriteComm(0x28); WriteComm(0x28);
Delay_ms(20); Delay_ms(20);
// 3. AMOLED 屏控制器写入 Sleep In (0x10) 睡眠指令 // 3. 屏幕芯片写入 Sleep In 指令 (0x10),使其内部数字逻辑单元进入停机低损耗模式
WriteComm(0x10); WriteComm(0x10);
Delay_ms(20); Delay_ms(20);
// 4. 拉低 SGM_CTRL 彻底断开 SGM3833 负压升压芯片的供 // 4. 将 P3.4 (SGM_CTRL) 拉低,切断 SGM3833 升压/负压电源芯片的使能,屏幕完全断
SGM_CTRL = 0; SGM_CTRL = 0;
// 5. 将 AMOLED 控制总线所有 IO 拉低,以防由于 IO 寄生二极管对屏幕倒灌电导致常亮 // 5. 【屏幕接口防漏电配置】:
LCD_CS = 0; // 当屏幕控制电源 (SGM3833) 切断后,屏幕内部处于无电状态。
LCD_RST = 0; // 如果单片机引脚 (SCL, SDA 等) 依然保持高电平,电流将通过屏幕保护二极管倒灌漏电,不仅会引发数毫安漏电,还会导致屏幕芯片损坏。
// - CS (片选) 必须拉高 (1) 以释放屏幕片选并禁用其输入缓冲器;
// - RST (复位) 必须保持高电平 (1),防止屏幕芯片被持续按在复位状态产生 ~130uA 的数字偏置电流;
// - 时钟 SCL、数据 SDI、命令/数据选择 DCX 拉低 (0) 以防浮空输入。
LCD_CS = 1;
LCD_RST = 1;
LCD_DCX = 0; LCD_DCX = 0;
LCD_SCL = 0; LCD_SCL = 0;
LCD_SDI = 0; LCD_SDI = 0;
// 6. 休眠状态下开启接收芯片并接通天线 (SHUT=0, RF_RX=1)此时屏幕关闭没有 SGM3833 噪底干扰 // 6. 休眠模式下关闭射频发射、接通接收 (SHUT=0, RF_RX=1)用来在休眠中监听射频警报。
RF_SetMode(1); // 此时屏幕和升压负压芯片已被切断,系统内部不存在高频开关纹波的噪底干扰,射频接收灵敏度达到最佳状态。
RF_SetMode(0);
// 确保开启扩展寄存器访问 (EAXFR = 1),以便能正确配置端口唤醒使能扩展寄存器 // 开启扩展 SFR 访问权限 (EAXFR = 1)
P_SW2 |= 0x80; P_SW2 |= 0x80;
// 7. 配置 P0.1, P0.2, P0.3 低电平触发 唤醒 // 7. 配置 P0.1, P0.2, P0.3 (KEY_UP/CONFIRM/DOWN) 端口下降沿低电平触发唤醒
P0IM1 |= 0x0E; P0IM1 |= 0x0E;
P0IM0 &= ~0x0E; P0IM0 &= ~0x0E;
P0INTE |= 0x0E; P0INTE |= 0x0E; // 开启 P0 端口的中断允许
// 8. 配置 P2.6 (KEY_SOS) 为 低电平触发 唤醒 // 8. 配置 P2.6 (KEY_SOS) 为低电平触发P2.1 (RF_RX_DATA) 为双沿跳变触发唤醒
P2IM1 |= 0x40; // P2.6: IM1=1, IM0=0 (低电平)
// P2.1: IM1=1, IM0=1 (双沿跳变触发,保障任何射频脉冲上升或下降沿都能立刻叫醒 MCU)
P2IM1 |= 0x42;
P2IM0 &= ~0x40; P2IM0 &= ~0x40;
P2INTE |= 0x40; P2IM0 |= 0x02;
P2INTE |= 0x42; // 开启 P2 端口中断允许
// ===== 配置端口掉电唤醒使能寄存器 PxWKUE ===== // 9. 【掉电唤醒核心扩展配置】:
P0WKUE |= 0x0E; // 使使能 P0.1/P0.2/P0.3 掉电唤醒 P0WKUE |= 0x0E; // 使能 P0.1/P0.2/P0.3 掉电唤醒 (WakeUp Enable)
P2WKUE |= 0x40; // 使使能 P2.6 掉电唤醒 P2WKUE |= 0x42; // 使能 P2.6 和 P2.1 的掉电唤醒
// 9. 开启外部中断 2 (RF_RX_DATA) 沿触发唤醒源 // 【漏电控制】P2.1 (RF_RX_DATA) 接入的是射频芯片的数据引脚。在休眠中,
EX2 = 1; // 若开启了 P2.1 的上拉电阻,当射频芯片拉低引脚时,会产生高达 ~660uA 的强倒灌漏电流。
// 因此这里特地仅开启 P2.6 (SOS) 上拉,而彻底关闭 P2.1 上拉以实现极致微安级待机。
P2PU |= 0x40;
P2IE |= 0x42; // 强制使能 P2.6/P2.1 的内部数字输入通道,确保电平跳变能被硬件捕捉
// 10. 清除端口中断的悬挂/标志位,防误触 // 10. 禁用无用的外部中断 2 (因为 STC32G 硬件 INT2 绑定在 P3.6,该物理引脚没有连接射频数据端,我们改由更灵活的 Port2 端口中断处理唤醒)
EX2 = 0;
// 11. 清除 P0 和 P2 的全部中断标志,防止进入休眠瞬间直接触发唤醒
P0INTF = 0x00; P0INTF = 0x00;
P2INTF = 0x00; P2INTF = 0x00;
// 11. 关闭 Timer1 中断,保持 EA=1 让端口/射频中断能唤醒 // 清零内存暂存的唤醒源事件标志
p0_wakeup_flag = 0;
p2_wakeup_flag = 0;
rf_wakeup_flag = 0;
// 12. 关闭 Timer1 (系统滴答) 中断。全局中断保持开启 (EA=1) 用作掉电唤醒条件
ET1 = 0; ET1 = 0;
EA = 1; EA = 1;
// 12. 写入 PCON 掉电模式位,使 MCU 进入深度 Power-Down 挂起状态 // 13. 向电源控制寄存器 PCON 写入 0x02,使 MCU 核心进入 Power-Down 模式CPU 停止运转
// 如果由于杂波伪唤醒,我们在循环中检测并重新进入休眠
while (1)
{
Uart_SendString("[SYS] Entering Power-Down mode...\r\n"); Uart_SendString("[SYS] Entering Power-Down mode...\r\n");
PCON |= 0x02; // PD = 1 PCON |= 0x02; // 设定 PD 位,硬件立刻休眠挂起
// 唤醒后必须跟 _nop_() 空操作以占位和刷新指令流水线
_nop_(); _nop_();
_nop_(); _nop_();
_nop_(); _nop_();
_nop_(); _nop_();
// 13. 唤醒并执行完 ISR 后CPU 从这里继续执行 // 14. 当有按键按下或无线信号到来时CPU 被唤醒进入对应的 ISR (在 main.c 中执行中断标志保存)
if (Wakeup_Restore()) // 中断处理完返回后CPU 接着从本行执行,调用 Wakeup_Restore() 唤醒恢复流程。
Wakeup_Restore();
}
/**
* @brief 系统被中断唤醒后的运行环境恢复逻辑
* @details 负责重新开启屏幕供电、配置 GPIO、重新初始化 LCD 芯片寄存器、重置时间,最终回到 Clock 主界面。
*/
void Wakeup_Restore(void)
{ {
break; // 真实唤醒(按键或合法射频报警),退出休眠循环 EAXFR = 1; // 确保允许访问扩展寄存区
}
// 14. 伪唤醒,重新配置唤醒源寄存器,准备下一次 Power-Down // 1. 立即关断所有的唤醒源和端口中断,防止应用运行期间反复因电平抖动误触发中断服务程序
EAXFR = 1;
RF_SetMode(1); // 重新开启接收芯片并接通天线
EX2 = 1; // 重新使能外部中断 2
P0INTF = 0x00;
P2INTF = 0x00;
P0INTE = 0x0E;
P2INTE = 0x40;
P0WKUE = 0x0E;
P2WKUE = 0x40;
ET1 = 0; // 关闭定时器 1
EA = 1;
}
}
bit Wakeup_Restore(void)
{
u8 p0_flag;
u8 p2_flag;
u16 rf_check_ms = 0;
bit rf_valid = 0;
u32 rx_addr;
u8 rx_data;
// 确保 EAXFR=1 才能访问扩展 SFR (P0INTF/P2INTF/P0INTE/P2INTE)
EAXFR = 1;
// [诊断] 在清除标志位之前先读取,判断唤醒来源
p0_flag = P0INTF;
p2_flag = P2INTF;
// 1. 立即禁用端口中断和唤醒,防止继续触发
P0INTE = 0x00; P0INTE = 0x00;
P2INTE = 0x00; P2INTE = 0x00;
P0WKUE = 0x00; // 清除 P0 唤醒允许 P0WKUE = 0x00; // 清除掉电唤醒源使能
P2WKUE = 0x00; // 清除 P2 唤醒允许 P2WKUE = 0x00;
P0INTF = 0x00; P0INTF = 0x00; // 清空标志位
P2INTF = 0x00; P2INTF = 0x00;
EX2 = 0; // 唤醒后禁用射频中断,运行模式下不受噪底杂波干扰 EX2 = 0; // 禁用外部 INT2 中断
// 1b. 关闭掉电唤醒定时器 // 2. 清零掉电专用定时器 (防止定时唤醒)
WKTCH = 0x00; WKTCH = 0x00;
WKTCL = 0x00; WKTCL = 0x00;
// 2. 开启全局中断,但暂时保持 Timer1 关闭以防中断影响射频比值软件解码 // 3. 恢复 Timer1 定时器中断允许,重新开启系统基准时基轮询
EA = 1; EA = 1;
ET1 = 0; ET1 = 1;
// 3. 判断是否为按键唤醒 // 4. 重置射频工作模式:进入模式 1 (接收模式) 并开启天线,恢复监听外部射频
if ((p0_flag & 0x0E) || (p2_flag & 0x40)) RF_SetMode(1);
// 打印日志,判断并告知调试串口具体是哪个物理源将手环唤醒的
if ((p0_wakeup_flag & 0x0E) || (p2_wakeup_flag & 0x40))
{ {
if (p0_flag & 0x0E) { Uart_SendString("[WR] Woken by KEY (P0=");
Uart_SendString("[WR] Woken by KEY P0 (P0INTF=0x"); Uart_SendHex8(p0_wakeup_flag);
Uart_SendHex8(p0_flag); Uart_SendString(", P2=");
Uart_SendString(")\r\n"); Uart_SendHex8(p2_wakeup_flag);
} else {
Uart_SendString("[WR] Woken by KEY P2 (P2INTF=0x");
Uart_SendHex8(p2_flag);
Uart_SendString(")\r\n"); Uart_SendString(")\r\n");
} }
else if ((p2_wakeup_flag & 0x02) || rf_wakeup_flag)
{
rf_wakeup_flag = 1; // 确认标记为射频解调唤醒
Uart_SendString("[WR] Woken by RF (P2.1)!\r\n");
}
else
{
Uart_SendString("[WR] Woken by TIMER/Other\r\n");
}
// 运行模式屏幕开启有噪底干扰,关闭射频接收,防止无信号时在后台乱输出电平 // 5. 【屏幕供电软启动】:
RF_SetMode(0); // 重新拉高 P3.4 (SGM_CTRL),并为负压电源芯片发送 27 个脉冲将输出稳压开启。
// 启动 SGM3833 PMIC 并发送脉冲使能升压
SGM_CTRL = 1; SGM_CTRL = 1;
SGM_SendPulse(27); SGM_SendPulse(27);
// 等待负压电轨充分稳定 // 6. 阻塞延时 200ms。
// 这期间用于避开电源电磁浪涌冲击,等待屏幕正负电压电轨彻底稳定,防止屏幕在初始化时画面出现撕裂或逻辑卡死。
Delay_ms(200); Delay_ms(200);
// 重做屏控制器 RM69310 寄存器组 of 初始化 // 7. 重新调用 LCD_Init() 初始化序列,写入控制偏置、伽马等命令,唤醒 Truly OLED 屏幕控制 IC RM69310
LCD_Init(); LCD_Init();
// 重新开启 Timer1 // 8. 重置用户的无操作闲置计时器为零
ET1 = 1;
inactivity_timer = 0; inactivity_timer = 0;
// 绘制时钟界面 // 9. 更新运行状态为普通时钟,并强制调用时钟界面函数刷屏,返回工作状态
current_state = STATE_NORMAL; current_state = STATE_NORMAL;
UI_ShowClockPage(current_state, current_hour, current_min); UI_ShowClockPage(current_state, current_hour, current_min);
Uart_SendString("[SYS] Wakeup restored by Key!\r\n"); Uart_SendString("[SYS] Wakeup restored!\r\n");
return 1; // 真实唤醒
}
else
{
// 射频外部中断或者 WKT 唤醒,保持屏幕 SGM_CTRL=0 关闭状态,避免升压干扰射频
Uart_SendString("[WR] Woken by RF/WKT, decoding...\r\n");
SGM_CTRL = 0;
// 尝试解码 150ms 射频包
for (rf_check_ms = 0; rf_check_ms < 150; rf_check_ms++)
{
if (EV1527_Decode(&rx_addr, &rx_data))
{
u8 slot;
if (Check_Sensor_ID(rx_addr, &slot))
{
// 找到了匹配的已配对传感器
if (sensor_list[slot].zone == 0 || current_state == STATE_ARMED)
{
rf_valid = 1;
captured_addr = rx_addr;
alarm_sensor_slot = slot;
break;
}
}
}
Delay_ms(1);
} }
// 采集完/超时后,立即关闭射频接收芯片,杜绝乱输出的噪底电平 /**
RF_SetMode(0); * @brief 掉电休眠用外部中断 2 中断服务子程序
* @details 当配置为 INT2 (P3.6) 跳变触发唤醒时,本函数将捕获唤醒标志,随后迅速关闭中断,
if (rf_valid) * 防止运行时产生的噪底高频跳变引发中断堆栈溢出。
{ */
// 真实报警信号唤醒:开启屏幕,显示报警画面
Uart_SendString("[WR] Valid Alarm RF Decoded! Address=0x");
Uart_SendHex32(captured_addr);
Uart_SendString("\r\n");
// 启动 SGM3833 并使能升压
SGM_CTRL = 1;
SGM_SendPulse(27);
Delay_ms(200);
LCD_Init();
// 重新开启 Timer1
ET1 = 1;
inactivity_timer = 0;
// 进入报警 App
AppManager_StartApp(APP_ID_ALARM);
return 1; // 真实唤醒
}
else
{
// 伪唤醒(杂波引起的误触发,或未注册传感器):不开启屏幕,返回 0 以便主循环重新进入休眠
Uart_SendString("[WR] RF Noise or Unregistered signal, back to sleep...\r\n");
// 重新开启 Timer1
ET1 = 1;
return 0; // 假唤醒,重新进入 Power-Down
}
}
}
// STC32G INT2 外部中断服务子程序 (向量 10)
// 用于从 Power-Down 模式中由射频数据脚下降沿唤醒 CPU
void INT2_Isr(void) interrupt 10 void INT2_Isr(void) interrupt 10
{ {
EX2 = 0; // 唤醒后立即关闭外部中断,避免噪底电平在唤醒后反复触发中断 rf_wakeup_flag = 1; // 中断唤醒标志置位,标明由无线射频触发唤醒
EX2 = 0; // 保护机制:进入 ISR 后立即切断中断通道
} }

View File

@@ -3,29 +3,118 @@
#include "config.h" #include "config.h"
// GPIO 与系统外设初始化 /* ====== GPIO 与外设初始化接口 ====== */
/**
* @brief 系统 GPIO 端口模式初始化配置
* @details 将按键引脚 (KEY_UP, KEY_DOWN, KEY_CONFIRM, KEY_SOS) 配置为高阻输入模式,
* 并将输出控制引脚 (MOTOR, RGB_DIN, SGM_CTRL, SHUT, 以及 LCD 屏幕 SPI 管脚)
* 配置为推挽输出模式,建立系统初始电气环境。
*/
void GPIO_Init(void); void GPIO_Init(void);
/**
* @brief 初始化 UART1 (串口 1)
* @details 设定波特率为 115200 bps (使用定时器 2 做波特率发生器1T 模式,系统时钟为 24.0 MHz)
* 主要用于输出系统调试日志、监控按键与射频接收状态。
*/
void Uart1_Init(void); void Uart1_Init(void);
// 延时与时间工具函数
/* ====== 阻塞式精确延时工具函数 ====== */
/**
* @brief 阻塞毫秒级延时函数
* @param ms 延时时长 (单位: 毫秒)
*/
void Delay_ms(u16 ms); void Delay_ms(u16 ms);
/**
* @brief 阻塞 10 微秒延时函数 (汇编级精确延时)
*/
void Delay10us(void); void Delay10us(void);
/**
* @brief 阻塞微秒级延时函数
* @param us 延时时长 (单位: 微秒)
*/
void Delay_us(u16 us); void Delay_us(u16 us);
/**
* @brief 获取安全的 Timer0 计数器值 (暂未使用)
*/
u16 GetTimer0_Safe(void); u16 GetTimer0_Safe(void);
// 串口数据打印函数
/* ====== 串口数据打印与日志辅助函数 ====== */
/**
* @brief 串口 1 发送 1 个字节数据
* @param dat 待发送的字节数据
*/
void Uart_SendByte(u8 dat); void Uart_SendByte(u8 dat);
/**
* @brief 串口 1 发送以 '\0' 结尾的字符串
* @param s 目标字符串指针
*/
void Uart_SendString(char *s); void Uart_SendString(char *s);
/**
* @brief 串口 1 非阻塞接收字符
* @return char 接收到的字符。若接收缓冲区为空,则立即返回 '\0'。
*/
char Uart_RxChar(void); char Uart_RxChar(void);
/**
* @brief 串口 1 打印输出 4 位十六进制数 (半字节0x0~0xF)
*/
void Uart_SendHex4(u8 val); void Uart_SendHex4(u8 val);
/**
* @brief 串口 1 打印输出 8 位十六进制数 (单字节0x00~0xFF)
*/
void Uart_SendHex8(u8 val); void Uart_SendHex8(u8 val);
/**
* @brief 串口 1 打印输出 16 位十六进制数 (双字节0x0000~0xFFFF)
*/
void Uart_SendHex16(u16 val); void Uart_SendHex16(u16 val);
/**
* @brief 串口 1 打印输出 20 位十六进制数 (主要用于射频解码输出5位十六进制格式)
*/
void Uart_SendHex20(u32 val); void Uart_SendHex20(u32 val);
/**
* @brief 串口 1 打印输出 32 位十六进制数 (双字8位十六进制格式)
*/
void Uart_SendHex32(u32 val); void Uart_SendHex32(u32 val);
/**
* @brief 格式化 32 位数为 8 位十六进制字符串
* @param val 要格式化的 32 位数值
* @param buf 输出目标字符数组指针 (至少保留 9 字节空间)
*/
void FormatHex(u32 val, char *buf); void FormatHex(u32 val, char *buf);
// 电源管理与休眠唤醒
void Enter_Low_Power_Sleep(void);
bit Wakeup_Restore(void);
#endif /* ====== 电源管理与休眠/唤醒流程接口 ====== */
/**
* @brief 系统切入低功耗深度休眠 (Power-Down Sleep)
* @details 负责关闭马达与灯光,保存当前运行的 App 现场,关闭屏幕的供电与复位,
* 并将 GPIO 端口的按键外部中断唤醒源以及射频中断唤醒源进行配置,
* 最终拉低 SHUT置位单片机的 PCON 寄存器切入停机低功耗状态。
*/
void Enter_Low_Power_Sleep(void);
/**
* @brief 从低功耗休眠被中断唤醒后的系统恢复流程
* @details 当检测到外部中断触发硬件唤醒后首先将供电芯片使能拉高SHUT=1
* 阻塞延时 50ms 避开冲击电流浪涌,随后重新初始化屏幕 RM69310 的全部控制寄存器,
* 并重置闲置休眠计数器,启动时钟应用恢复主界面的正常显示与事件分发。
*/
void Wakeup_Restore(void);
#endif // __SYSTEM_H__

242
App/ui.c
View File

@@ -1,69 +1,104 @@
#include "ui.h" #include "ui.h"
#include "../Drivers/lcd.h" #include "../Drivers/lcd.h"
// 引入外部菜单项定义 // 引入外部菜单对码项名称定义 (例如 "DETEC. PORTE" 等,法语表达防区类型名称)
extern char *code menu_items[5]; extern char *code menu_items[5];
/**
* @brief 渲染并显示系统待机时钟主页面
* @param state 当前的系统布防状态 (STATE_ARMED: 已布防, 否则为正常或撤防)
* @param hour 当前待显示的小时数 (0~23)
* @param min 当前待显示的分钟数 (0~59)
* @details 该页面被划分为三大功能视觉区:
* 1. 顶部状态栏:左侧绘制布撤防指示锁,右侧显示 "85%" 剩余电量以及精细电池单色位图。
* 2. 中部核心栏:垂直居中以 16x32 大号粗字体绘制格式化时间 "HH:MM",在其下方标注 AM/PM 标识。
* 3. 底部信息栏:使用灰色小字模绘制法语星期日期 "VEN 10-07" (星期五 7月10日)。
*/
void UI_ShowClockPage(SystemState state, u8 hour, u8 min) void UI_ShowClockPage(SystemState state, u8 hour, u8 min)
{ {
char time_str[6]; char time_str[6];
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK); // 使用纯黑色底色,充分利用 AMOLED 黑色像素不发光的省电特性
// 左上角锁图标 // ====== 绘制左上角布撤防锁头状态图标 ======
if (state == STATE_ARMED) if (state == STATE_ARMED)
{ {
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED); // 状态: ARMED (布防) -> 绘制红色闭合锁头
LCD_FillRect(15, 15, 2, 8, COLOR_RED); LCD_DrawRectBorder(10, 23, 20, 15, COLOR_RED); // 锁身矩形框
LCD_FillRect(17, 15, 10, 2, COLOR_RED); LCD_FillRect(15, 15, 2, 8, COLOR_RED); // 左半锁环
LCD_FillRect(25, 15, 2, 8, COLOR_RED); LCD_FillRect(17, 15, 10, 2, COLOR_RED); // 锁环顶梁
LCD_FillRect(25, 15, 2, 8, COLOR_RED); // 右半锁环
} }
else else
{ {
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN); // 状态: NORMAL / DISARMED (非布防) -> 绘制绿色开启锁头
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN); LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN); // 锁身矩形
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN); LCD_FillRect(15, 15, 2, 8, COLOR_GREEN); // 左锁环
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); LCD_FillRect(17, 15, 8, 2, COLOR_GREEN); // 锁环顶
LCD_FillRect(25, 17, 2, 6, COLOR_GREEN); // 开启状态的偏置锁环
} }
// 右上角电量 // ====== 绘制右上角电池电量 ======
LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK); LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK); // 静态测试显示 85% 字符
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); // 渲染 20x10 电池外廓和格数
// 中间时间 // ====== 绘制中部时间 "HH:MM" ======
time_str[0] = '0' + (hour / 10); time_str[0] = '0' + (hour / 10); // 小时十位
time_str[1] = '0' + (hour % 10); time_str[1] = '0' + (hour % 10); // 小时个位
time_str[2] = ':'; time_str[2] = ':'; // 冒号分隔符
time_str[3] = '0' + (min / 10); time_str[3] = '0' + (min / 10); // 分钟十位
time_str[4] = '0' + (min % 10); time_str[4] = '0' + (min % 10); // 分钟个位
time_str[5] = '\0'; time_str[5] = '\0'; // 字符串终止字符
LCD_ShowString16x32Centered(100, time_str, COLOR_WHITE, COLOR_BLACK); LCD_ShowString16x32Centered(100, time_str, COLOR_WHITE, COLOR_BLACK); // 使用 16x32 特大字体居中绘制时间
// AM/PM // ====== 绘制 AM/PM 状态 ======
LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK);
// 日期 (法语) // ====== 绘制日期星期栏 ======
LCD_ShowStringCentered(195, "VEN 10-07", COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(195, "VEN 10-07", COLOR_GRAY, COLOR_BLACK);
} }
/**
* @brief 渲染显示传感器对码二级菜单页面
* @param selected_index 当前聚焦选中的菜单行索引 (0~4分别对应 5 种传感器)
* @details 采用典型的纵向菜单滚轮特效:
* - 中间 110 行高度为当前选中行,使用青色框高亮圈起并用青色绘制名称。
* - 上下邻近行显示为暗灰色,且无边框保护,营造层次感。
*/
void UI_ShowPairMenuPage(u8 selected_index) void UI_ShowPairMenuPage(u8 selected_index)
{ {
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 绘制标题 "MENU APPAI." (法语: 配对/对码设置菜单)
LCD_ShowStringCentered(30, "MENU APPAI.", COLOR_AMBER, COLOR_BLACK); LCD_ShowStringCentered(30, "MENU APPAI.", COLOR_AMBER, COLOR_BLACK);
// 渲染上方相邻项 (如果当前选择的不是第一行)
if (selected_index > 0) { if (selected_index > 0) {
LCD_ShowStringCentered(78, menu_items[selected_index - 1], COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(78, menu_items[selected_index - 1], COLOR_GRAY, COLOR_BLACK);
} }
LCD_DrawRectBorder(8, 110, 104, 28, COLOR_CYAN);
// 渲染当前选中的高亮中间项
LCD_DrawRectBorder(8, 110, 104, 28, COLOR_CYAN); // 青色对齐外框
LCD_ShowStringCentered(118, menu_items[selected_index], COLOR_CYAN, COLOR_BLACK); LCD_ShowStringCentered(118, menu_items[selected_index], COLOR_CYAN, COLOR_BLACK);
// 渲染下方相邻项 (如果当前选择的不是最后一行)
if (selected_index < 4) { if (selected_index < 4) {
LCD_ShowStringCentered(158, menu_items[selected_index + 1], COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(158, menu_items[selected_index + 1], COLOR_GRAY, COLOR_BLACK);
} }
} }
/**
* @brief 渲染正在搜索配对信号的雷达扫描等待页面
* @param frame_index 当前的雷达波纹帧索引号 (0~2 帧循环)
* @details 在中央以青色渲染雷达同心圆扩散动图,并在底部提示 "DECLENCH. CAPT" (请触发传感器发射)
*/
void UI_ShowPairWaitPage(u8 frame_index) void UI_ShowPairWaitPage(u8 frame_index)
{ {
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 绘制头部 "APPAIRAGE" (对码中)
LCD_ShowStringCentered(35, "APPAIRAGE", COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(35, "APPAIRAGE", COLOR_GRAY, COLOR_BLACK);
// 根据 Timer1 节拍算得的帧号,切换渲染雷达的 3 种点阵图形,形成扩散动态视觉
if (frame_index == 0) if (frame_index == 0)
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK); LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f1, COLOR_CYAN, COLOR_BLACK);
else if (frame_index == 1) else if (frame_index == 1)
@@ -71,72 +106,103 @@ void UI_ShowPairWaitPage(u8 frame_index)
else else
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK); LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
LCD_ShowStringCentered(165, "RECHERCHE", COLOR_WHITE, COLOR_BLACK); // 绘制状态描述
LCD_ShowStringCentered(200, "DECLENCH. CAPT", COLOR_CYAN, COLOR_BLACK); LCD_ShowStringCentered(165, "RECHERCHE", COLOR_WHITE, COLOR_BLACK); // 法语: 搜寻中
LCD_ShowStringCentered(200, "DECLENCH. CAPT", COLOR_CYAN, COLOR_BLACK); // 法语: 请触发传感器以发射射频信号
} }
/**
* @brief 渲染捕获到传感器地址后的微调确认页面
* @param type 已解码出的传感器硬件类型 (0:门磁, 1:PIR, 2:烟感, 3:燃气, 4:水浸)
* @param zone_index 当前配置的防区编号 (1~99),由用户上下键修改微调
*/
void UI_ShowPairConfirmPage(u8 type, u8 zone_index) void UI_ShowPairConfirmPage(u8 type, u8 zone_index)
{ {
char zone_str[3]; char zone_str[3];
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 顶部显示捕获回馈标志 "v RECU" (法语: 信号已接收)
LCD_ShowStringCentered(32, "v RECU", COLOR_GREEN, COLOR_BLACK); LCD_ShowStringCentered(32, "v RECU", COLOR_GREEN, COLOR_BLACK);
// 在上部居中绘制设备类别所对应的图形
if (type == 0) { if (type == 0) {
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK); LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_door_32x32, COLOR_WHITE, COLOR_BLACK); // 门磁图标
} else if (type == 2 || type == 3) { } else if (type == 2 || type == 3) {
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK); LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_siren_32x32, COLOR_WHITE, COLOR_BLACK); // 警号喇叭图标
} else { } else {
LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK); LCD_DrawMonoBitmap(44, 55, 32, 32, bmp_confirm_lock_32x32, COLOR_WHITE, COLOR_BLACK); // 锁型常规图标
} }
// 渲染传感器类型的文字
LCD_ShowStringCentered(105, sensor_names_fr[type], COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(105, sensor_names_fr[type], COLOR_WHITE, COLOR_BLACK);
// 绘制一个青色边框,在中间以 16x32 大号字体展示当前防区号 (如 "01", "12")
LCD_DrawRectBorder(15, 140, 90, 44, COLOR_CYAN); LCD_DrawRectBorder(15, 140, 90, 44, COLOR_CYAN);
zone_str[0] = '0' + (zone_index / 10); zone_str[0] = '0' + (zone_index / 10);
zone_str[1] = '0' + (zone_index % 10); zone_str[1] = '0' + (zone_index % 10);
zone_str[2] = '\0'; zone_str[2] = '\0';
LCD_ShowString16x32(44, 146, zone_str, COLOR_CYAN, COLOR_BLACK); LCD_ShowString16x32(44, 146, zone_str, COLOR_CYAN, COLOR_BLACK); // 渲染两位防区编号
LCD_ShowStringCentered(215, "CONFIRMER", COLOR_GRAY, COLOR_BLACK); // 底部提示按键动作
LCD_ShowStringCentered(215, "CONFIRMER", COLOR_GRAY, COLOR_BLACK); // 法语: 短按以确认保存
} }
/**
* @brief 渲染显示配对成功反馈页
*/
void UI_ShowPairSuccessPage(void) void UI_ShowPairSuccessPage(void)
{ {
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 渲染 32x32 对勾图标
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK); LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_checkmark_32x32, COLOR_GREEN, COLOR_BLACK);
LCD_ShowStringCentered(145, "SUCCES", COLOR_GREEN, COLOR_BLACK); LCD_ShowStringCentered(145, "SUCCES", COLOR_GREEN, COLOR_BLACK); // 法语: 成功
LCD_ShowStringCentered(180, "ENREGISTRE", COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(180, "ENREGISTRE", COLOR_GRAY, COLOR_BLACK); // 法语: 配置已保存
} }
/**
* @brief 渲染显示配对失败页面
* @param is_timeout 1表示超时退出的失败0表示其他对码错误
*/
void UI_ShowPairFailPage(u8 is_timeout) void UI_ShowPairFailPage(u8 is_timeout)
{ {
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 渲染 32x32 红色叉号图标
LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK); LCD_DrawMonoBitmap(44, 75, 32, 32, bmp_cross_32x32, COLOR_RED, COLOR_BLACK);
LCD_ShowStringCentered(145, "ECHEC", COLOR_RED, COLOR_BLACK); LCD_ShowStringCentered(145, "ECHEC", COLOR_RED, COLOR_BLACK); // 法语: 失败
if (is_timeout) { if (is_timeout) {
LCD_ShowStringCentered(180, "DELAI DEPASSE", COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(180, "DELAI DEPASSE", COLOR_GRAY, COLOR_BLACK); // 法语: 对码超时
} else { } else {
LCD_ShowStringCentered(180, "TYPE INCORRECT", COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(180, "TYPE INCORRECT", COLOR_GRAY, COLOR_BLACK); // 法语: 传感器类型不符
} }
} }
/**
* @brief 渲染显示传感器入侵警报页面
* @param type 触发报警的传感器类型 (0~4)
* @param zone_index 报警传感器的防区号 (1~99, 0)
* @details 在界面外侧四周圈起醒目的双重红色警告边框 (COLOR_RED)
* 中间绘制设备对应的大警告图标,并在下方用特大红字标明防区及设备类型(如 "PORTE 02"
* 最下方渲染白色粗体 "INTRUSION!" (入侵警告) 提示。
*/
void UI_ShowAlarmPage(u8 type, u8 zone_index) void UI_ShowAlarmPage(u8 type, u8 zone_index)
{ {
char zone_info[20]; char zone_info[20];
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 绘制双重红色爆闪粗边界框 (配合 ms_tick 在 AlarmApp 中亮灭闪烁)
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED); LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED); LCD_DrawRectBorder(6, 6, 108, 228, COLOR_RED);
// 绘制报警设备的图形
if (type == 0) { if (type == 0) {
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK); LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_door_32x32, COLOR_RED, COLOR_BLACK); // 门磁报警
} else if (type == 2 || type == 3) { } else if (type == 2 || type == 3) {
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK); LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_siren_32x32, COLOR_RED, COLOR_BLACK); // 烟感/燃气报警
} else { } else {
LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK); LCD_DrawMonoBitmap(44, 50, 32, 32, bmp_confirm_lock_32x32, COLOR_RED, COLOR_BLACK); // 其他防区报警
} }
// 拼接字符串: "设备类型名 两位防区号"
{ {
u8 len = 0; u8 len = 0;
char *p = sensor_names_fr[type]; char *p = sensor_names_fr[type];
@@ -148,46 +214,59 @@ void UI_ShowAlarmPage(u8 type, u8 zone_index)
zone_info[len++] = '0' + (zone_index % 10); zone_info[len++] = '0' + (zone_index % 10);
zone_info[len] = '\0'; zone_info[len] = '\0';
} }
LCD_ShowStringCentered(135, zone_info, COLOR_RED, COLOR_BLACK); LCD_ShowStringCentered(135, zone_info, COLOR_RED, COLOR_BLACK); // 红字绘制设备防区
LCD_ShowStringCentered(175, "INTRUSION!", COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(175, "INTRUSION!", COLOR_WHITE, COLOR_BLACK); // 白字入侵提示
} }
/**
* @brief 渲染显示 SOS 主动求救发送状态页面
* @details 绘制红、白双色三层粗边界警告,中央绘制细致的警徽警灯图案,
* 绘制红色闪烁求助状态字样 "SOS ENVOI" (求救发送中...) 和 "APPEL..." (呼叫中)。
*/
void UI_ShowSosPage(void) void UI_ShowSosPage(void)
{ {
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 绘制红白双层紧急警示框
LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED); LCD_DrawRectBorder(4, 4, 112, 232, COLOR_RED);
LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED); LCD_DrawRectBorder(5, 5, 110, 230, COLOR_RED);
LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE); LCD_DrawRectBorder(8, 8, 104, 224, COLOR_WHITE);
// 警灯组合绘制 // ====== 用画点和填充矩形函数像素级绘制精细的警徽灯架 ======
LCD_FillRect(44, 45, 32, 2, COLOR_RED); LCD_FillRect(44, 45, 32, 2, COLOR_RED); // 警灯顶部
LCD_FillRect(48, 47, 24, 12, COLOR_RED); LCD_FillRect(48, 47, 24, 12, COLOR_RED); // 警灯红色灯罩
LCD_FillRect(40, 59, 40, 4, COLOR_GRAY); LCD_FillRect(40, 59, 40, 4, COLOR_GRAY); // 警灯灰色底座
LCD_FillRect(57, 51, 6, 6, COLOR_WHITE); LCD_FillRect(57, 51, 6, 6, COLOR_WHITE); // 中间高亮光影
LCD_FillRect(58, 36, 4, 6, COLOR_RED); LCD_FillRect(58, 36, 4, 6, COLOR_RED); // 发射的垂直红色闪光
LCD_FillRect(46, 38, 4, 4, COLOR_RED); LCD_FillRect(46, 38, 4, 4, COLOR_RED); // 左侧散射闪光
LCD_FillRect(70, 38, 4, 4, COLOR_RED); LCD_FillRect(70, 38, 4, 4, COLOR_RED); // 右侧散射闪光
// 绘制求救中法文字样
LCD_ShowStringCentered(120, "SOS", COLOR_RED, COLOR_BLACK); LCD_ShowStringCentered(120, "SOS", COLOR_RED, COLOR_BLACK);
LCD_ShowStringCentered(150, "ENVOI", COLOR_RED, COLOR_BLACK); LCD_ShowStringCentered(150, "ENVOI", COLOR_RED, COLOR_BLACK); // 法语: 求救中
LCD_ShowStringCentered(195, "APPEL...", COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(195, "APPEL...", COLOR_WHITE, COLOR_BLACK); // 法语: 紧急呼叫中
} }
/**
* @brief 渲染显示与网络摄像机 (IPC) 进行一键绑定配对的进度画面
* @param addr 手环自身的 24 位克隆出厂硬件地址
*/
void UI_ShowBindingPage(u32 addr) void UI_ShowBindingPage(u32 addr)
{ {
char id_str[20]; char id_str[20];
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 摄像机组合绘制 // ====== 像素级拼装绘制一个青色网络摄像头简图 ======
LCD_DrawRectBorder(36, 46, 32, 26, COLOR_CYAN); LCD_DrawRectBorder(36, 46, 32, 26, COLOR_CYAN); // 相机主体框
LCD_FillRect(44, 56, 6, 6, COLOR_CYAN); LCD_FillRect(44, 56, 6, 6, COLOR_CYAN); // 镜头光圈点
LCD_FillRect(68, 48, 2, 22, COLOR_CYAN); LCD_FillRect(68, 48, 2, 22, COLOR_CYAN); // 右侧红外指示圈
LCD_FillRect(70, 50, 2, 18, COLOR_CYAN); LCD_FillRect(70, 50, 2, 18, COLOR_CYAN); // 镜头固定翼 1
LCD_FillRect(72, 52, 2, 14, COLOR_CYAN); LCD_FillRect(72, 52, 2, 14, COLOR_CYAN); // 镜头固定翼 2
LCD_ShowStringCentered(120, "LIAISON", COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(120, "LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 连接/绑定
LCD_ShowStringCentered(155, "ENVOI EN COURS", COLOR_CYAN, COLOR_BLACK); LCD_ShowStringCentered(155, "ENVOI EN COURS", COLOR_CYAN, COLOR_BLACK); // 法语: 发送对码帧中
// 拼接要对外广播的手环 24 位克隆物理 ID 十六进制字符串并显示在最下方
id_str[0] = 'I'; id_str[1] = 'D'; id_str[2] = ':'; id_str[3] = ' '; id_str[0] = 'I'; id_str[1] = 'D'; id_str[2] = ':'; id_str[3] = ' ';
id_str[4] = '0'; id_str[5] = 'x'; id_str[4] = '0'; id_str[5] = 'x';
{ {
@@ -211,45 +290,65 @@ void UI_ShowBindingPage(u32 addr)
LCD_ShowStringCentered(205, id_str, COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(205, id_str, COLOR_GRAY, COLOR_BLACK);
} }
/**
* @brief 渲染显示设置主菜单界面
* @param selected_index 当前菜单高亮行索引 (0:时钟修改, 1:对码匹配, 2:摄像机绑定)
* @details 在顶部和底部绘制向上/向下箭头提示滑轮,中部绘制选中项的大几何图形,
* 并在下方打印对应的法文字符串描述。
*/
void UI_ShowMainMenu(u8 selected_index) void UI_ShowMainMenu(u8 selected_index)
{ {
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// ====== 绘制顶部的向上箭头三角标记 ======
LCD_FillRect(58, 20, 4, 2, COLOR_GRAY); LCD_FillRect(58, 20, 4, 2, COLOR_GRAY);
LCD_FillRect(56, 22, 8, 2, COLOR_GRAY); LCD_FillRect(56, 22, 8, 2, COLOR_GRAY);
LCD_FillRect(54, 24, 12, 2, COLOR_GRAY); LCD_FillRect(54, 24, 12, 2, COLOR_GRAY);
if (selected_index == 0) if (selected_index == 0)
{ {
LCD_DrawRectBorder(44, 70, 32, 32, COLOR_CYAN); // 菜单 0时间修改设置。中部绘制一个青色闹钟样式
LCD_FillRect(59, 76, 2, 10, COLOR_CYAN); LCD_DrawRectBorder(44, 70, 32, 32, COLOR_CYAN); // 闹钟外圈
LCD_FillRect(59, 85, 8, 2, COLOR_CYAN); LCD_FillRect(59, 76, 2, 10, COLOR_CYAN); // 闹钟时针
LCD_ShowStringCentered(150, "1. HORLOGE", COLOR_WHITE, COLOR_BLACK); LCD_FillRect(59, 85, 8, 2, COLOR_CYAN); // 闹钟分针
LCD_ShowStringCentered(150, "1. HORLOGE", COLOR_WHITE, COLOR_BLACK); // 法语: 时钟/时间修改
} }
else if (selected_index == 1) else if (selected_index == 1)
{ {
// 菜单 1无线对码匹配。中部绘制一个青色同心圆雷达波纹图形
LCD_DrawMonoBitmap(44, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK); LCD_DrawMonoBitmap(44, 70, 32, 32, bmp_radar_32x32_f3, COLOR_CYAN, COLOR_BLACK);
LCD_ShowStringCentered(150, "2. APPAIRAGE", COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(150, "2. APPAIRAGE", COLOR_WHITE, COLOR_BLACK); // 法语: 配对
} }
else if (selected_index == 2) else if (selected_index == 2)
{ {
// 菜单 2网络摄像机绑定。中部绘制一个相机/联通关系图
LCD_DrawRectBorder(44, 74, 20, 16, COLOR_CYAN); LCD_DrawRectBorder(44, 74, 20, 16, COLOR_CYAN);
LCD_FillRect(52, 80, 4, 4, COLOR_CYAN); LCD_FillRect(52, 80, 4, 4, COLOR_CYAN);
LCD_FillRect(64, 76, 2, 12, COLOR_CYAN); LCD_FillRect(64, 76, 2, 12, COLOR_CYAN);
LCD_FillRect(66, 78, 2, 8, COLOR_CYAN); LCD_FillRect(66, 78, 2, 8, COLOR_CYAN);
LCD_FillRect(68, 80, 2, 4, COLOR_CYAN); LCD_FillRect(68, 80, 2, 4, COLOR_CYAN);
LCD_ShowStringCentered(150, "3. LIAISON", COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(150, "3. LIAISON", COLOR_WHITE, COLOR_BLACK); // 法语: 摄像机绑定
} }
// ====== 绘制底部的向下箭头三角标记 ======
LCD_FillRect(54, 210, 12, 2, COLOR_GRAY); LCD_FillRect(54, 210, 12, 2, COLOR_GRAY);
LCD_FillRect(56, 212, 8, 2, COLOR_GRAY); LCD_FillRect(56, 212, 8, 2, COLOR_GRAY);
LCD_FillRect(58, 214, 4, 2, COLOR_GRAY); LCD_FillRect(58, 214, 4, 2, COLOR_GRAY);
} }
/**
* @brief 渲染修改时间闪烁调节页面 (调节小时或分钟)
* @param hour 修改状态中的小时
* @param min 修改状态中的分钟
* @param selection 当前正在编辑聚焦的属性位 (1: 修改小时, 2: 修改分钟)
* @param blink_on 当前 500ms 控制隐现的闪烁周期标志位 (1-绘制正常字0-以空格消隐该数值字符)
*/
void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on) void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
{ {
char time_str[6]; char time_str[6];
LCD_Clear(COLOR_BLACK); LCD_Clear(COLOR_BLACK);
// 绘制绿锁头等背景静态信息
LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN); LCD_DrawRectBorder(10, 23, 20, 15, COLOR_GREEN);
LCD_FillRect(15, 15, 2, 8, COLOR_GREEN); LCD_FillRect(15, 15, 2, 8, COLOR_GREEN);
LCD_FillRect(17, 15, 8, 2, COLOR_GREEN); LCD_FillRect(17, 15, 8, 2, COLOR_GREEN);
@@ -258,8 +357,10 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK); LCD_ShowString(62, 24, "85%", COLOR_GRAY, COLOR_BLACK);
LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK); LCD_DrawMonoBitmap(92, 27, 20, 10, bmp_battery_20x10, COLOR_GRAY, COLOR_BLACK);
// ====== 处理调节项一:小时的消隐/显示逻辑 ======
if (selection == 1 && !blink_on) if (selection == 1 && !blink_on)
{ {
// 聚焦在小时且处于灭周期,用空格隐藏数值,形成数值跳变闪烁调节感
time_str[0] = ' '; time_str[0] = ' ';
time_str[1] = ' '; time_str[1] = ' ';
} }
@@ -269,12 +370,15 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
time_str[1] = '0' + (hour % 10); time_str[1] = '0' + (hour % 10);
} }
time_str[2] = '\0'; time_str[2] = '\0';
LCD_ShowString16x32(12, 100, time_str, COLOR_CYAN, COLOR_BLACK); LCD_ShowString16x32(12, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘小时
// 绘制中间冒号
LCD_ShowString16x32(48, 98, ":", COLOR_WHITE, COLOR_BLACK); LCD_ShowString16x32(48, 98, ":", COLOR_WHITE, COLOR_BLACK);
// ====== 处理调节项二:分钟的消隐/显示逻辑 ======
if (selection == 2 && !blink_on) if (selection == 2 && !blink_on)
{ {
// 聚焦在分钟且处于灭周期,用空格隐藏数值
time_str[0] = ' '; time_str[0] = ' ';
time_str[1] = ' '; time_str[1] = ' ';
} }
@@ -284,7 +388,9 @@ void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on)
time_str[1] = '0' + (min % 10); time_str[1] = '0' + (min % 10);
} }
time_str[2] = '\0'; time_str[2] = '\0';
LCD_ShowString16x32(64, 100, time_str, COLOR_CYAN, COLOR_BLACK); LCD_ShowString16x32(64, 100, time_str, COLOR_CYAN, COLOR_BLACK); // 采用青色特大字描绘分钟
// 补齐底部的固定辅助元素
LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK); LCD_ShowStringCentered(150, "AM", COLOR_WHITE, COLOR_BLACK);
LCD_ShowStringCentered(195, "VEN 10-07", COLOR_GRAY, COLOR_BLACK); LCD_ShowStringCentered(195, "VEN 10-07", COLOR_GRAY, COLOR_BLACK);
} }

View File

@@ -3,16 +3,83 @@
#include "config.h" #include "config.h"
/* ====== OLED 屏幕 UI (用户界面) 渲染层接口 ====== */
/**
* @brief 渲染显示待机时钟主页面
* @param state 当前的系统运行状态 (STATE_NORMAL/STATE_ARMED/STATE_DISARMED)
* @param hour 当前待显示的小时
* @param min 当前待显示的分钟
* @details 负责在 120x240 大小的 AMOLED 屏幕上居中绘制巨大的时钟字样,并在顶部
* 绘制对应的状态图标 (如一把打开的锁代表已撤防、闭合的锁代表已布防等)。
*/
void UI_ShowClockPage(SystemState state, u8 hour, u8 min); void UI_ShowClockPage(SystemState state, u8 hour, u8 min);
/**
* @brief 渲染显示传感器对码的二级设备选择菜单页面
* @param selected_index 当前正在选中的传感器类型索引 (0:门磁, 1:PIR, 2:烟感, 3:燃气, 4:水浸)
*/
void UI_ShowPairMenuPage(u8 selected_index); void UI_ShowPairMenuPage(u8 selected_index);
/**
* @brief 动态刷新显示正在搜寻无线传感器信号的等待对码动态图画
* @param frame_index 当前雷达扫描波纹动画的帧索引 (0~2 帧循环)
*/
void UI_ShowPairWaitPage(u8 frame_index); void UI_ShowPairWaitPage(u8 frame_index);
/**
* @brief 渲染显示捕获到无线信号后的传感器类型确认与自定义防区微调界面
* @param type 已捕获的传感器类型 (0~4)
* @param zone_index 当前设定的防区数值 (1~99),允许通过按键增加或减小
*/
void UI_ShowPairConfirmPage(u8 type, u8 zone_index); void UI_ShowPairConfirmPage(u8 type, u8 zone_index);
/**
* @brief 渲染显示对码学习成功反馈页
* @details 屏幕中央显示巨大对勾图标,并绘制文字 "APPAIRAGE OK" (配对成功)。
*/
void UI_ShowPairSuccessPage(void); void UI_ShowPairSuccessPage(void);
/**
* @brief 渲染显示对码失败 (或30秒超时) 反馈页
* @param is_timeout 1表示超时失败0表示其他错误
* @details 屏幕显示红色大叉图标,并提示 "ECHEC" (配对失败)。
*/
void UI_ShowPairFailPage(u8 is_timeout); void UI_ShowPairFailPage(u8 is_timeout);
/**
* @brief 渲染显示传感器入侵警报报警页面
* @param type 触发入侵的传感器类型 (0:门磁, 1:PIR, 2:烟感, 3:燃气, 4:水浸)
* @param zone_index 报警传感器所绑定的防区号 (1~99, 0)
* @details 屏幕爆闪红色粗边框,并交替绘制代表紧急入侵的巨大传感器特定警示大图标。
*/
void UI_ShowAlarmPage(u8 type, u8 zone_index); void UI_ShowAlarmPage(u8 type, u8 zone_index);
/**
* @brief 渲染显示 SOS 主动求救发送状态页面
* @details 爆闪红白双层边框,在屏幕上绘制求救警示图,并闪烁显示法语 "SOS EMIS" (求救已发送)。
*/
void UI_ShowSosPage(void); void UI_ShowSosPage(void);
/**
* @brief 渲染显示与网络摄像机 (IPC) 进行一键绑定配对的进度画面
* @param addr 手环克隆发送的 24 位出厂 ID 地址 (十六进制字符串格式展示)
*/
void UI_ShowBindingPage(u32 addr); void UI_ShowBindingPage(u32 addr);
/**
* @brief 渲染显示设置主菜单界面
* @param selected_index 当前菜单高亮行索引 (0:HORLOGE时间, 1:APPAIRAGE对码, 2:LIAISON绑定)
*/
void UI_ShowMainMenu(u8 selected_index); void UI_ShowMainMenu(u8 selected_index);
/**
* @brief 渲染显示修改时间页面 (时钟设置编辑状态)
* @param hour 待修改的小时
* @param min 待修改的分钟
* @param selection 当前选中的修改项 (1:修改小时, 2:修改分钟)
* @param blink_on 控制被选中数值以 500ms 周期进行隐现闪烁的周期标志位 (1-显示, 0-消隐)
*/
void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on); void UI_ShowSetTimePage(u8 hour, u8 min, u8 selection, u8 blink_on);
#endif #endif // __UI_H__

View File

@@ -19,24 +19,27 @@
| :--- | :--- | :--- | :--- | :--- | | :--- | :--- | :--- | :--- | :--- |
| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | Truly AMOLED 屏幕复位控制脚 (RESET) | | **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | Truly AMOLED 屏幕复位控制脚 (RESET) |
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | Truly AMOLED 屏幕 SPI 数据/指令选择脚 (D/C) | | **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | Truly AMOLED 屏幕 SPI 数据/指令选择脚 (D/C) |
| **Pin 3** | `P1.4` | `SPI_SDI` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输入 (MOSI/SDA) | | **Pin 3** | `P1.3` | `SPI_SDI` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输入 (MOSI/SDA) |
| **Pin 4** | `P1.5` |`SPI_CLK` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行时钟信号 (SCL) | | **Pin 4** | `P1.4` | `SPI_CLK` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行时钟信号 (SCL) |
| **Pin 5** | `P1.6` | `SPI_CS` | 推挽输出 | Truly AMOLED 屏幕 SPI 片选信号 (CS) | | **Pin 5** | `P1.5` | `SPI_CS` | 推挽输出 | Truly AMOLED 屏幕 SPI 片选信号 (CS) |
| **Pin 6** | `P1.7` | `SPI_SDO` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输出 (MISO) | | **Pin 6** | `P1.6` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
| **Pin 7** | `P1.7` | `LEDA` | 推挽输出 | 屏幕负压供电芯片使能控制端 (SGM_CTRL) |
| **Pin 8** | `UCAP` | `UCAP` | 电源 | 外接滤波电容至 GND (LDO 稳定输出端) |
| **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 | | **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
| **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 | | **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电参考基准 |
| **Pin 12** | `GND` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 | | **Pin 12** | `GND` | `GND` | 电源 | 系统地 (GND) |
| **Pin 13** |`P3.1` |`RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 | | **Pin 13** | `P3.0` | `RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 |
| **Pin 14** | `P3.0` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 | | **Pin 14** | `P3.1` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 |
| **Pin 19** | `P3.6` | `RF_TX_DAT` | 推挽输出 | LR690L 射频芯片发射调制数据输入引脚 | | **Pin 18** | `P3.5` | `SHUT` | 推挽输出 | LR690L 射频芯片关闭信号 (0:开启工作, 1:关闭休眠) |
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | LR690L 射频芯片接收使能控制引脚 | | **Pin 19** | `P3.6` | `RF_RX_DATA` | 高阻输入 | LR690L 接收解调接收数据输出引脚 (注:代码中被映射为 P2^1) |
| **Pin 21** | `P2.0` | `RF_TX` | 推挽输出 | LT4455 射频芯片发射使能控制引脚 | | **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | ATR5179 接收使能控制引脚 |
| **Pin 22** | `P2.1` | `RF_RX_DATA` | 输入中断 | ATR5179 射频开关控制芯片 | **Pin 21** | `P2.0` | `RF_TX` | 推挽输出 | ATR5179 发射使能控制引脚 |
| **Pin 23** | `P2.2` |`SHUT` | LR0L射频芯片关闭信号 | **Pin 22** | `P2.1` | `RF_TX_DAT` | 推挽输出 | LR690L 射频芯片发射调制数据输入引脚 (注:代码中被映射为 P3^6) |
| **Pin 23** | `P2.2` | `NC` | - | 悬空 |
| **Pin 24** | `P2.3` | `LED_RGB` | SPI MOSI | WS2812B 级联双幻彩灯珠 DIN 控制信号 | | **Pin 24** | `P2.3` | `LED_RGB` | SPI MOSI | WS2812B 级联双幻彩灯珠 DIN 控制信号 |
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 | | **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 | | **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
| **Pin 28** | `P2.7` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 | | **Pin 28** | `P2.7` | `NC` | - | 悬空 |
| **Pin 29** | `P0.0` | `BAT_ADC` | ADC 输入 | 电池电压采集 | | **Pin 29** | `P0.0` | `BAT_ADC` | ADC 输入 | 电池电压采集 |
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 | | **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 | | **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |

View File

@@ -3,149 +3,179 @@
#include "../App/config.h" #include "../App/config.h"
#include "intrins.h" #include "intrins.h"
// RM69310 屏幕接口引脚 // ====== RM69310 Truly AMOLED 屏幕 SPI 引脚映射 (8051 bit 寻址控制) ======
sbit LCD_RST = P1^0; // 硬件复位 sbit LCD_RST = P1^0; // 屏幕硬件复位引脚 (RESET),低电平复位
sbit LCD_DCX = P1^1; // 数据/命令选择 sbit LCD_DCX = P1^1; // 串行数据 / 命令选择控制线 (1:数据, 0:指令)
sbit LCD_SDI = P1^4; // SPI 数据输入 (MOSI) sbit LCD_SDI = P1^4; // SPI 主机输出从机输入数据线 (MOSI)
sbit LCD_SCL = P1^5; // SPI 时钟 (SCLK) sbit LCD_SCL = P1^5; // SPI 串行工作时钟线 (SCLK)
sbit LCD_CS = P1^6; // SPI 片选 (CS) sbit LCD_CS = P1^6; // SPI 物理片选选择端 (CS),低电平选通屏幕
// SGM3833 电源芯片控制脚 // ====== SGM3833 升压/负压三路 OLED 屏电源 PMIC 使能引脚 ======
sbit SGM_CTRL = P3^4; sbit SGM_CTRL = P3^4; // 单线脉冲通信控制线
// External delay functions defined in main.c // 引用外部延时函数 (定义在 system.c 中)
extern void Delay_ms(u16 ms); extern void Delay_ms(u16 ms);
extern void Delay10us(void); extern void Delay10us(void);
/**
* @brief 通过脉冲序列为 SGM3833 屏幕电源管理芯片写入开启配置
* @param pulse_cnt 要写入的脉冲个数 (本系统传入 27 次脉冲开启正负双电轨)
* @details SGM3833 采用单线边沿脉冲计数协议进行数字控制。
* - tOFF 必须大于 5ms 才能完全复位芯片进入指令模式。
* - 每次电平拉低低电平持续 10us 后拉高,形成一个脉冲计数值。
* - 发送完毕后需拉高并维持 5ms 以上,以便将计数值存入寄存器,锁定目标电压轨开启。
*/
void SGM_SendPulse(u8 pulse_cnt) void SGM_SendPulse(u8 pulse_cnt)
{ {
u8 i; u8 i;
SGM_CTRL = 0; SGM_CTRL = 0;
Delay_ms(5); // 确保进入关断/复位状态 (tOFF) Delay_ms(5); // 确保电源芯片进入关断/复位状态 (tOFF)
SGM_CTRL = 1; SGM_CTRL = 1;
Delay_ms(5); // 确保充分初始化 (tINIT >= 300us) Delay_ms(5); // 确保进入初始化状态 (tINIT >= 300us)
for(i = 0; i < pulse_cnt; i++) for(i = 0; i < pulse_cnt; i++)
{ {
SGM_CTRL = 0; SGM_CTRL = 0;
Delay10us(); Delay10us(); // 保持低电平 10us
SGM_CTRL = 1; SGM_CTRL = 1;
Delay10us(); Delay10us(); // 保持高电平 10us
} }
SGM_CTRL = 1; SGM_CTRL = 1;
Delay_ms(5); // 确保数据稳定存储 (tSTORE >= 80us) Delay_ms(5); // 确保数据稳定存储锁定 (tSTORE >= 80us)
} }
/**
* @brief 软件模拟 8 位串行 SPI 字节数据发送
* @param dat 待发送的 1 字节数据
* @details 采用典型的 bit-banging 模式:时钟 SCL 在发送前置低,将数据 MSB 依次提取后赋值给
* 数据线 SDI随后拉高 SCL 完成一个 bit 的沿采样,循环 8 次发送完成。
*/
void SPI_SendData(u8 dat) void SPI_SendData(u8 dat)
{ {
u8 i; u8 i;
for(i = 0; i < 8; i++) for(i = 0; i < 8; i++)
{ {
LCD_SCL = 0; LCD_SCL = 0; // 拉低时钟,准备更新数据
if(dat & 0x80) if(dat & 0x80)
LCD_SDI = 1; LCD_SDI = 1;
else else
LCD_SDI = 0; LCD_SDI = 0;
_nop_(); _nop_();
LCD_SCL = 1; LCD_SCL = 1; // 拉高时钟,触发屏幕在上升沿采样数据位
_nop_(); _nop_();
dat <<= 1; dat <<= 1; // 左移一位,准备发送下一位
} }
} }
/**
* @brief 向屏幕控制器写入单字节控制指令 (DCX=0)
*/
void WriteComm(u8 cmd) void WriteComm(u8 cmd)
{ {
LCD_CS = 0; LCD_CS = 0; // 开启片选
LCD_DCX = 0; // 命令模式 LCD_DCX = 0; // 切换为指令传输模式
SPI_SendData(cmd); SPI_SendData(cmd);
LCD_CS = 1; LCD_CS = 1; // 关闭片选
} }
/**
* @brief 向屏幕控制器写入单字节显示数据 (DCX=1)
*/
void WriteData(u8 dat) void WriteData(u8 dat)
{ {
LCD_CS = 0; LCD_CS = 0; // 开启片选
LCD_DCX = 1; // 数据模式 LCD_DCX = 1; // 切换为数据传输模式
SPI_SendData(dat); SPI_SendData(dat);
LCD_CS = 1; LCD_CS = 1; // 关闭片选
} }
/**
* @brief 按照 RM69310 物理时序执行屏幕硬件复位
*/
void LCD_Reset(void) void LCD_Reset(void)
{ {
LCD_RST = 1; LCD_RST = 1;
Delay_ms(10); Delay_ms(10);
LCD_RST = 0; LCD_RST = 0; // 拉低复位引脚进入硬件复位状态
Delay_ms(20); Delay_ms(20);
LCD_RST = 1; LCD_RST = 1; // 重新拉高引脚
Delay_ms(120); Delay_ms(120); // 必须等待至少 120ms 才能执行后续的初始化
} }
/**
* @brief 初始化 Truly AMOLED 屏幕控制器 RM69310
* @details 先配置控制 GPIO 为推挽输出,接着根据 Truly 信利原厂提供的初始化指令序列,
* 完成偏置电压、刷新周期、Gamma 参数、跳帧逻辑及色彩空间模式等一系列初始化工作。
*/
void LCD_Init(void) void LCD_Init(void)
{ {
// Configure LCD pins as push-pull output // 1. 配置 P1 端口的 LCD_RST/DCX/SDI/SCL/CS 引脚全部为推挽输出模式 (M1=0, M0=1)
P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6)); P1M1 &= ~((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6)); P1M0 |= ((1<<0) | (1<<1) | (1<<4) | (1<<5) | (1<<6));
// Configure P3.4 (SGM_CTRL) as push-pull output // 2. 配置 P3.4 (SGM_CTRL) 为推挽输出模式
P3M1 &= ~(1 << 4); P3M1 &= ~(1 << 4);
P3M0 |= (1 << 4); P3M0 |= (1 << 4);
// 设置各控制引脚初始默认电平
SGM_CTRL = 1; SGM_CTRL = 1;
LCD_CS = 1; LCD_CS = 1;
LCD_SCL = 1; LCD_SCL = 1;
LCD_RST = 1; LCD_RST = 1;
LCD_DCX = 1; LCD_DCX = 1;
LCD_Reset(); LCD_Reset(); // 硬件复位屏幕
// 寄存器配置(源自 Truly 信利官方初始化配置) // ====== 写入 RM69310 原厂寄存器初始化指令序列 ======
WriteComm(0xFE);WriteData(0x01); WriteComm(0xFE);WriteData(0x01); // 切换至扩展 Page 1 寄存器页
WriteComm(0x04);WriteData(0xa0); // SPI write ram enable WriteComm(0x04);WriteData(0xa0); // 开启 SPI 写入显存 RAM 允许
WriteComm(0x05);WriteData(0x70); // 128RGB WriteComm(0x05);WriteData(0x70); // 配置屏幕尺寸规格为 128RGB
WriteComm(0x06);WriteData(0x3C); // NL = 240 line WriteComm(0x06);WriteData(0x3C); // 设定垂直有效扫描线 NL = 240
WriteComm(0x25);WriteData(0x06); // normal mode: gamma1 WriteComm(0x25);WriteData(0x06); // 正常模式伽马设定Gamma1
WriteComm(0x26);WriteData(0x80); // T1A = 280 WriteComm(0x26);WriteData(0x80); // T1A 时序常数 = 280
WriteComm(0x27);WriteData(0x12); // normal mode VBP = 12 WriteComm(0x27);WriteData(0x12); // 正常模式消隐后肩 VBP = 12
WriteComm(0x28);WriteData(0x12); // normal mode VFP = 8 WriteComm(0x28);WriteData(0x12); // 正常模式消隐前肩 VFP = 8
WriteComm(0x2A);WriteData(0x06); // idle mode: gamma1 WriteComm(0x2A);WriteData(0x06); // 空闲模式伽马设定 Gamma1
WriteComm(0x2B);WriteData(0x80); // T1B = 280 WriteComm(0x2B);WriteData(0x80); // T1B 时序常数 = 280
WriteComm(0x2D);WriteData(0x12); // idle mode VBP = 12 WriteComm(0x2D);WriteData(0x12); // 空闲消隐后肩 = 12
WriteComm(0x2F);WriteData(0x12); // idle mode VFP = 8 WriteComm(0x2F);WriteData(0x12); // 空闲消隐前肩 = 8
WriteComm(0x37);WriteData(0x0C); // precharge to VGSP WriteComm(0x37);WriteData(0x0C); // 预充电控制电压 VGSP
WriteComm(0x6D);WriteData(0x18); // skip frame VGMP and VGSP regulator off WriteComm(0x6D);WriteData(0x18); // 关闭跳帧 VGMP VGSP 稳压调节
WriteComm(0x29);WriteData(0x01); // normal mode skip frame off WriteComm(0x29);WriteData(0x01); // 关闭正常模式的跳帧
WriteComm(0x30);WriteData(0x43); // idle mode skip frame = 60/2 = 30Hz WriteComm(0x30);WriteData(0x43); // 设定空闲模式跳帧使刷新频率降为 30Hz
WriteComm(0x0E);WriteData(0x83); // AVDD = 5.6V normal mode WriteComm(0x0E);WriteData(0x83); // 正常模式 AVDD 电压 = 5.6V
WriteComm(0x0F);WriteData(0x83); // AVDD = 5.6V idle mode WriteComm(0x0F);WriteData(0x83); // 空闲模式 AVDD = 5.6V
WriteComm(0x10);WriteData(0x71); // AVDD = 3xVCI WriteComm(0x10);WriteData(0x71); // AVDD 三倍 VCI 升压控制
WriteComm(0x11);WriteData(0xb3); // VCL = -2xVCI WriteComm(0x11);WriteData(0xb3); // VCL = -2VCI 产生负压
WriteComm(0x12);WriteData(0xb3); // VCL = -2xVCI WriteComm(0x12);WriteData(0xb3);
WriteComm(0x13);WriteData(0x80); // VGH = AVDD normal mode WriteComm(0x13);WriteData(0x80); // 正常模式下 VGH 等于 AVDD
WriteComm(0x14);WriteData(0x80); // VGH = AVDD idle mode WriteComm(0x14);WriteData(0x80); // 空闲模式下 VGH 等于 AVDD
WriteComm(0x15);WriteData(0x81); // VGL = VCL - VCI normal mode WriteComm(0x15);WriteData(0x81); // 正常模式下低侧门电压 VGL = VCL - VCI
WriteComm(0x16);WriteData(0x81); // VGL = VCL - VCI idle mode WriteComm(0x16);WriteData(0x81);
WriteComm(0x18);WriteData(0x66); // VGHR = 6V normal/idle mode WriteComm(0x18);WriteData(0x66); // 偏置电平 VGHR = 6V
WriteComm(0x19);WriteData(0x44); // VGLR = -6V normal/idle mode WriteComm(0x19);WriteData(0x44); // 偏置电平 VGLR = -6V
WriteComm(0x1E);WriteData(0x02); // Switch EQ on WriteComm(0x1E);WriteData(0x02); // 开启电荷平衡 EQ 调制
WriteComm(0x5B);WriteData(0x10); // VREFN5 on WriteComm(0x5B);WriteData(0x10); // 启用参考负压 VREFN5
WriteComm(0x62);WriteData(0x19); // VREFN5 = -3V normal mode WriteComm(0x62);WriteData(0x19); // 正常模式 VREFN5 电平 = -3V
WriteComm(0x63);WriteData(0x19); // VREFN5 = -3V idle mode WriteComm(0x63);WriteData(0x19); // 空闲模式 VREFN5 = -3V
WriteComm(0x70);WriteData(0x55); // display off SD to AVDD WriteComm(0x70);WriteData(0x55); // 关闭显示时源极驱动器至 AVDD
WriteComm(0x1D);WriteData(0x02); // Switch EQ on WriteComm(0x1D);WriteData(0x02); // 启用 EQ 平衡
WriteComm(0x89);WriteData(0x18); // VGMP = 5.5V WriteComm(0x89);WriteData(0x18); // 控制偏置 VGMP = 5.5V
WriteComm(0x8A);WriteData(0xb9); // VGSP = 2V WriteComm(0x8A);WriteData(0xb9); // 控制偏置 VGSP = 2V
WriteComm(0x8B);WriteData(0x01); // VGMP VGSP high byte WriteComm(0x8B);WriteData(0x01); // 偏置高字节
WriteComm(0x8C);WriteData(0x10); // VGMP = 5.4V WriteComm(0x8C);WriteData(0x10); // VGMP 设为 5.4V
WriteComm(0x8D);WriteData(0x90); // VGSP = 2V WriteComm(0x8D);WriteData(0x90); // VGSP 设为 2V
WriteComm(0x8E);WriteData(0x01); // VGMP VGSP high byte WriteComm(0x8E);WriteData(0x01);
WriteComm(0x6E);WriteData(0x0A); // MIPI interface off WriteComm(0x6E);WriteData(0x0A); // 关闭 MIPI 高速数据接口以选择 SPI 驱动方式
WriteComm(0x6A);WriteData(0x05); // MUX WriteComm(0x6A);WriteData(0x05); // 配置 MUX 选择器
WriteComm(0x3A);WriteData(0x08); // T1_sd WriteComm(0x3A);WriteData(0x08); // 行延迟常数 T1_sd
WriteComm(0x3B);WriteData(0x00); // Tp_sd WriteComm(0x3B);WriteData(0x00); // 帧延迟常数 Tp_sd
WriteComm(0x3D);WriteData(0x16); // Th_sd WriteComm(0x3D);WriteData(0x16); // 保持延迟常数 Th_sd
WriteComm(0x3F);WriteData(0x27); // Tsw_sd WriteComm(0x3F);WriteData(0x27); // 切换时序 Tsw_sd
WriteComm(0x40);WriteData(0x0F); // Thsw_sd WriteComm(0x40);WriteData(0x0F); // 切换保持 Thsw_sd
WriteComm(0x41);WriteData(0x0D); // Thsd_sd WriteComm(0x41);WriteData(0x0D); // 切换释放 Thsd_sd
WriteComm(0x42);WriteData(0x14); // Mux 142536 // 如下配置模拟通道多路选通时序
WriteComm(0x42);WriteData(0x14);
WriteComm(0x43);WriteData(0x41); WriteComm(0x43);WriteData(0x41);
WriteComm(0x44);WriteData(0x25); WriteComm(0x44);WriteData(0x25);
WriteComm(0x45);WriteData(0x52); WriteComm(0x45);WriteData(0x52);
@@ -157,7 +187,7 @@ void LCD_Init(void)
WriteComm(0x4B);WriteData(0x52); WriteComm(0x4B);WriteData(0x52);
WriteComm(0x4C);WriteData(0x36); WriteComm(0x4C);WriteData(0x36);
WriteComm(0x4D);WriteData(0x63); WriteComm(0x4D);WriteData(0x63);
WriteComm(0x4E);WriteData(0x14); // Data R1R2G1G2B1B2 WriteComm(0x4E);WriteData(0x14);
WriteComm(0x4F);WriteData(0x41); WriteComm(0x4F);WriteData(0x41);
WriteComm(0x50);WriteData(0x25); WriteComm(0x50);WriteData(0x25);
WriteComm(0x51);WriteData(0x52); WriteComm(0x51);WriteData(0x52);
@@ -170,15 +200,15 @@ void LCD_Init(void)
WriteComm(0x58);WriteData(0x36); WriteComm(0x58);WriteData(0x36);
WriteComm(0x59);WriteData(0x63); WriteComm(0x59);WriteData(0x63);
WriteComm(0x66);WriteData(0x90); // idle mode internal power WriteComm(0x66);WriteData(0x90); // 空闲模式使用内部集成供电
WriteComm(0x67);WriteData(0x40); // internal power delay 1 frame off WriteComm(0x67);WriteData(0x40); // 内部延迟 1 帧切断电源
WriteComm(0x72);WriteData(0x1A); // internal OVDD = 4.6V WriteComm(0x72);WriteData(0x1A); // 内部核心 OVDD 电压 = 4.6V
WriteComm(0x73);WriteData(0x07); // internal OVSS = -2V WriteComm(0x73);WriteData(0x07); // 内部核心 OVSS 电压 = -2V
WriteComm(0x74);WriteData(0x0C); // OVDD power from AVDD WriteComm(0x74);WriteData(0x0C); // OVDD 的最终输入来源设为 AVDD
WriteComm(0x6A);WriteData(0x3D); // swire 61=0x3D pulse WriteComm(0x6A);WriteData(0x3D); // 向单线接口发送脉冲,配置升压
WriteComm(0x6B);WriteData(0x29); // swire 41=0x29 pulse WriteComm(0x6B);WriteData(0x29);
WriteComm(0xFE);WriteData(0x04); WriteComm(0xFE);WriteData(0x04); // 切换至 Page 4
WriteComm(0x5E);WriteData(0x01); WriteComm(0x5E);WriteData(0x01);
WriteComm(0x5F);WriteData(0xB8); WriteComm(0x5F);WriteData(0xB8);
WriteComm(0x60);WriteData(0xBB); WriteComm(0x60);WriteData(0xBB);
@@ -190,7 +220,7 @@ void LCD_Init(void)
WriteComm(0x79);WriteData(0xBB); WriteComm(0x79);WriteData(0xBB);
WriteComm(0x7A);WriteData(0xBB); WriteComm(0x7A);WriteData(0xBB);
WriteComm(0x00);WriteData(0xDC); // SN_CK1 WriteComm(0x00);WriteData(0xDC); // 扫描时钟 CK1 寄存器配置
WriteComm(0x01);WriteData(0x00); WriteComm(0x01);WriteData(0x00);
WriteComm(0x02);WriteData(0x02); WriteComm(0x02);WriteData(0x02);
WriteComm(0x03);WriteData(0x00); WriteComm(0x03);WriteData(0x00);
@@ -200,7 +230,7 @@ void LCD_Init(void)
WriteComm(0x07);WriteData(0x0A); WriteComm(0x07);WriteData(0x0A);
WriteComm(0x08);WriteData(0x00); WriteComm(0x08);WriteData(0x00);
WriteComm(0x09);WriteData(0xDC); // SN_CK2 WriteComm(0x09);WriteData(0xDC); // 扫描时钟 CK2 寄存器配置
WriteComm(0x0a);WriteData(0x00); WriteComm(0x0a);WriteData(0x00);
WriteComm(0x0b);WriteData(0x02); WriteComm(0x0b);WriteData(0x02);
WriteComm(0x0C);WriteData(0x00); WriteComm(0x0C);WriteData(0x00);
@@ -210,7 +240,7 @@ void LCD_Init(void)
WriteComm(0x10);WriteData(0x0A); WriteComm(0x10);WriteData(0x0A);
WriteComm(0x11);WriteData(0x00); WriteComm(0x11);WriteData(0x00);
WriteComm(0x12);WriteData(0xCC); // EM_CK1 WriteComm(0x12);WriteData(0xCC); // 发光逻辑 EM_CK1 配置
WriteComm(0x13);WriteData(0x00); WriteComm(0x13);WriteData(0x00);
WriteComm(0x14);WriteData(0x02); WriteComm(0x14);WriteData(0x02);
WriteComm(0x15);WriteData(0x00); WriteComm(0x15);WriteData(0x00);
@@ -220,7 +250,7 @@ void LCD_Init(void)
WriteComm(0x19);WriteData(0x26); WriteComm(0x19);WriteData(0x26);
WriteComm(0x1A);WriteData(0x00); WriteComm(0x1A);WriteData(0x00);
WriteComm(0x1B);WriteData(0xCC); // EM_CK2 WriteComm(0x1B);WriteData(0xCC); // 发光逻辑 EM_CK2 配置
WriteComm(0x1C);WriteData(0x00); WriteComm(0x1C);WriteData(0x00);
WriteComm(0x1D);WriteData(0x02); WriteComm(0x1D);WriteData(0x02);
WriteComm(0x1E);WriteData(0x00); WriteComm(0x1E);WriteData(0x00);
@@ -230,7 +260,7 @@ void LCD_Init(void)
WriteComm(0x22);WriteData(0x26); WriteComm(0x22);WriteData(0x26);
WriteComm(0x23);WriteData(0x00); WriteComm(0x23);WriteData(0x00);
WriteComm(0x4C);WriteData(0x89); // SN_STV WriteComm(0x4C);WriteData(0x89); // 扫描起始信号 SN_STV 配置
WriteComm(0x4D);WriteData(0x00); WriteComm(0x4D);WriteData(0x00);
WriteComm(0x4E);WriteData(0x01); WriteComm(0x4E);WriteData(0x01);
WriteComm(0x4F);WriteData(0x00); WriteComm(0x4F);WriteData(0x00);
@@ -238,43 +268,48 @@ void LCD_Init(void)
WriteComm(0x51);WriteData(0xBB); WriteComm(0x51);WriteData(0xBB);
WriteComm(0x52);WriteData(0xBB); WriteComm(0x52);WriteData(0xBB);
WriteComm(0x53);WriteData(0xCA); // EM_STV WriteComm(0x53);WriteData(0xCA); // 发光起始信号 EM_STV 配置
WriteComm(0x54);WriteData(0x00); WriteComm(0x54);WriteData(0x00);
WriteComm(0x55);WriteData(0x03); WriteComm(0x55);WriteData(0x03);
WriteComm(0x56);WriteData(0x00); WriteComm(0x56);WriteData(0x00);
WriteComm(0x58);WriteData(0x00); WriteComm(0x58);WriteData(0x00);
WriteComm(0x59);WriteData(0x00); WriteComm(0x59);WriteData(0x00);
WriteComm(0x65);WriteData(0x45); // 04 WriteComm(0x65);WriteData(0x45);
WriteComm(0x66);WriteData(0x0C); // 03 WriteComm(0x66);WriteData(0x0C);
WriteComm(0x67);WriteData(0x00); // 10 WriteComm(0x67);WriteData(0x00);
WriteComm(0xFE);WriteData(0x01); // ID WriteComm(0xFE);WriteData(0x01); // 切换至 Page 1 验证芯片 ID
WriteComm(0xE5);WriteData(0x00); WriteComm(0xE5);WriteData(0x00);
WriteComm(0xE6);WriteData(0x10); WriteComm(0xE6);WriteData(0x10);
WriteComm(0xE7);WriteData(0x31); WriteComm(0xE7);WriteData(0x31);
WriteComm(0xFE);WriteData(0x00); // 切换 Page 0 WriteComm(0xFE);WriteData(0x00); // 切换 Page 0 进行正常显示操作
WriteComm(0xC4);WriteData(0x80); WriteComm(0xC4);WriteData(0x80);
WriteComm(0x2A);WriteData(0x00);WriteData(0x04);WriteData(0x00);WriteData(0x7B); // 设置列 WriteComm(0x2A);WriteData(0x00);WriteData(0x04);WriteData(0x00);WriteData(0x7B); // 设定列像素物理边界 (4~123宽120像素)
// 显式设置 16 位像素格式 (COLMOD = 0x75) // ====== 显式设置屏幕像素色彩格式为 16 位像素格式 (COLMOD = 0x75即RGB565格式) ======
WriteComm(0x3A);WriteData(0x75); WriteComm(0x3A);WriteData(0x75);
WriteComm(0x11);WriteData(0x00); // 退出睡眠 WriteComm(0x11);WriteData(0x00); // 唤醒屏芯片:发送 Sleep Out 退出睡眠指令
Delay_ms(150); Delay_ms(150); // 退出睡眠状态后,必须等待 150ms 以便驱动电荷泵稳定
WriteComm(0x29);WriteData(0x00); // 开启显示 WriteComm(0x29);WriteData(0x00); // 开启屏幕显示:发送 Display ON 指令,像素层开始发光
Delay_ms(20); Delay_ms(20);
} }
/**
* @brief 设置屏幕绘制显存窗口区域
*/
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end) void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
{ {
// 写入 Column Address Set 列地址设置寄存器命令 (0x2A)
WriteComm(0x2A); WriteComm(0x2A);
WriteData((u8)(x_start >> 8)); WriteData((u8)(x_start >> 8));
WriteData((u8)(x_start & 0xFF)); WriteData((u8)(x_start & 0xFF));
WriteData((u8)(x_end >> 8)); WriteData((u8)(x_end >> 8));
WriteData((u8)(x_end & 0xFF)); WriteData((u8)(x_end & 0xFF));
// 写入 Row Address Set 行地址设置寄存器命令 (0x2B)
WriteComm(0x2B); WriteComm(0x2B);
WriteData((u8)(y_start >> 8)); WriteData((u8)(y_start >> 8));
WriteData((u8)(y_start & 0xFF)); WriteData((u8)(y_start & 0xFF));
@@ -282,29 +317,41 @@ void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
WriteData((u8)(y_end & 0xFF)); WriteData((u8)(y_end & 0xFF));
} }
/**
* @brief 用指定色彩刷写清空全屏幕
* @param color 16位 RGB565 颜色填充值
*/
void LCD_Clear(u16 color) void LCD_Clear(u16 color)
{ {
u16 i, j; u16 i, j;
// 【物理引脚偏移说明】:
// 该 AMOLED 屏在物理层面上水平方向的列像素地址为 4 ~ 123总宽 120 像素)。
// 因此调用 LCD_SetWindow 时X 轴的物理起始和终止坐标必须偏置 +4 才能与物理屏幕像素完全对齐。
LCD_SetWindow(4, 123, 0, 239); LCD_SetWindow(4, 123, 0, 239);
WriteComm(0x2C); WriteComm(0x2C); // 发送 RAM Write (0x2C) 显存写入命令
for(i = 0; i < 120; i++) for(i = 0; i < 120; i++)
{ {
for(j = 0; j < 240; j++) for(j = 0; j < 240; j++)
{ {
WriteData((u8)(color >> 8)); WriteData((u8)(color >> 8)); // 发送 RGB565 高 8 位字节
WriteData((u8)(color & 0xFF)); WriteData((u8)(color & 0xFF)); // 发送 RGB565 低 8 位字节
} }
} }
} }
/**
* @brief 在屏幕指定视口绘制实心矩形
*/
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color) void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
{ {
u16 i, count; u16 i, count;
u8 ch = (u8)(color >> 8); u8 ch = (u8)(color >> 8);
u8 cl = (u8)(color & 0xFF); u8 cl = (u8)(color & 0xFF);
// X 坐标应用物理偏置偏移 +4 像素
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1); LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1);
WriteComm(0x2C); WriteComm(0x2C); // 写入显存
count = w * h; count = w * h; // 实心矩形包含的总像素数
for(i = 0; i < count; i++) for(i = 0; i < count; i++)
{ {
WriteData(ch); WriteData(ch);
@@ -312,63 +359,90 @@ void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
} }
} }
/**
* @brief 绘制空心矩形边界框
* @details 通过组合调用四次 LCD_FillRect 实心线来绘制矩形的上、下、左、右四条边。
*/
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color) void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
{ {
LCD_FillRect(x, y, w, 1, color); LCD_FillRect(x, y, w, 1, color); // 上边框线 (厚度 1 像素)
LCD_FillRect(x, y + h - 1, w, 1, color); LCD_FillRect(x, y + h - 1, w, 1, color); // 下边框线
LCD_FillRect(x, y, 1, h, color); LCD_FillRect(x, y, 1, h, color); // 左边框线
LCD_FillRect(x + w - 1, y, 1, h, color); LCD_FillRect(x + w - 1, y, 1, h, color); // 右边框线
} }
/**
* @brief 渲染显示一个单色字模位图资源
* @param x 渲染起始坐标 X
* @param y 渲染起始坐标 Y
* @param w 位图宽度 (单位: 像素点数)
* @param h 位图高度 (单位: 像素点数)
* @param bmp FLASH 中的单色像素数组指针 (1表示画前景色0表示画背景色)
* @param color 16位前景色
* @param bg_color 16位背景色
* @details 会逐行、逐列解析单色图的字节数组,每一行占 (w+7)/8 个字节,
* 利用位操作提取指定 bit 的状态并发送色彩数据写入屏幕。
*/
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color) void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color)
{ {
u16 r, c; u16 r, c;
u16 byte_width = (w + 7) / 8; u16 byte_width = (w + 7) / 8; // 计算在单色编码中,一行图像占用的实际字节数
u8 ch = (u8)(color >> 8); u8 ch = (u8)(color >> 8);
u8 cl = (u8)(color & 0xFF); u8 cl = (u8)(color & 0xFF);
u8 bgh = (u8)(bg_color >> 8); u8 bgh = (u8)(bg_color >> 8);
u8 bgl = (u8)(bg_color & 0xFF); u8 bgl = (u8)(bg_color & 0xFF);
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1); LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1); // 限制绘制显存窗口并应用偏移
WriteComm(0x2C); WriteComm(0x2C);
// 嵌套循环,从第 0 行第 0 列开始遍历每个像素位置
for(r = 0; r < h; r++) for(r = 0; r < h; r++)
{ {
for(c = 0; c < w; c++) for(c = 0; c < w; c++)
{ {
u16 byte_idx = r * byte_width + (c / 8); u16 byte_idx = r * byte_width + (c / 8); // 定位当前像素所在单色图数组中的字节索引
u8 bit_shift = 7 - (c % 8); u8 bit_shift = 7 - (c % 8); // 定位该像素在该字节中对应的 bit 位置 (高位在前)
if(bmp[byte_idx] & (1 << bit_shift)) if(bmp[byte_idx] & (1 << bit_shift))
{ {
WriteData(ch); WriteData(ch); // 该位为 1发送前景色
WriteData(cl); WriteData(cl);
} }
else else
{ {
WriteData(bgh); WriteData(bgh); // 该位为 0发送背景色
WriteData(bgl); WriteData(bgl);
} }
} }
} }
} }
/**
* @brief 在指定坐标渲染输出一个 8x16 尺寸的普通英文 ASCII 字符
* @param ch ASCII 字符本身
* @details 遍历 8x16 点阵字库,将索引对应的 16 个字符编码字节依次提取,按位发送。
*/
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color) void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{ {
u8 r, c; u8 r, c;
u8 char_idx = ch - 0x20; u8 char_idx = ch - 0x20; // 偏移计算ASCII 可打印字符从 0x20 (空格) 开始
u8 f_h = (u8)(color >> 8); u8 f_h = (u8)(color >> 8);
u8 f_l = (u8)(color & 0xFF); u8 f_l = (u8)(color & 0xFF);
u8 b_h = (u8)(bg_color >> 8); u8 b_h = (u8)(bg_color >> 8);
u8 b_l = (u8)(bg_color & 0xFF); u8 b_l = (u8)(bg_color & 0xFF);
if(ch < 0x20 || ch > 0x7E) return; if(ch < 0x20 || ch > 0x7E) return; // 过滤不可见控制字符
LCD_SetWindow(x + 4, x + 8 - 1 + 4, y, y + 16 - 1); LCD_SetWindow(x + 4, x + 8 - 1 + 4, y, y + 16 - 1); // 应用 +4 像素偏移
WriteComm(0x2C); WriteComm(0x2C);
// 循环 16 行,绘制 8x16 字型
for(r = 0; r < 16; r++) for(r = 0; r < 16; r++)
{ {
u8 line = FONT_8x16[char_idx][r]; u8 line = FONT_8x16[char_idx][r]; // 提取当前行对应的 8 位单色像素信息
for(c = 0; c < 8; c++) for(c = 0; c < 8; c++)
{ {
// 从高到低依次提取单行中的像素点
if(line & (0x80 >> c)) if(line & (0x80 >> c))
{ {
WriteData(f_h); WriteData(f_h);
@@ -383,27 +457,43 @@ void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
} }
} }
/**
* @brief 在指定坐标打印一行普通英文 ASCII 字符串 (左对齐)
*/
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{ {
while(*str) while(*str)
{ {
LCD_ShowChar8x16(x, y, *str, color, bg_color); LCD_ShowChar8x16(x, y, *str, color, bg_color); // 渲染当前字符
x += 8; x += 8; // X 轴起始位置偏移 8 像素,准备绘制下一个字符
str++; str++; // 移向下一个字符
} }
} }
/**
* @brief 在指定 Y 轴高度上,自动计算偏移以实现字符串水平居中显示
*/
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color)
{ {
u16 len = 0; u16 len = 0;
char *p = str; char *p = str;
while(*p++) len++; while(*p++) len++; // 计算字符串长度
if(len * 8 >= 120) if(len * 8 >= 120)
// 若超出或等同于屏幕有效宽度 (120像素),强制从最左侧 0 开始左对齐绘制
LCD_ShowString(0, y, str, color, bg_color); LCD_ShowString(0, y, str, color, bg_color);
else else
// 否则通过公式 (120 - 字符宽度) / 2 计算出水平居中的首字起始偏移 X 坐标并渲染
LCD_ShowString((120 - len * 8) / 2, y, str, color, bg_color); LCD_ShowString((120 - len * 8) / 2, y, str, color, bg_color);
} }
/**
* @brief 渲染大号特粗 16x32 尺寸的英文/数字字符
* @details 【字库双重放大技术】:
* 由于 8051 FLASH 容量有限,手环未附带特大型号的 16x32 大字库。
* 此函数通过算法对 8x16 基础字库点阵数据在水平方向(列)和垂直方向(行)各重复绘制 2 遍
* (即单个点扩充绘制为 2x2 的像素团),从而实现对原字型双重放大渲染为 16x32 规格,节省空间。
*/
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color) void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{ {
u8 r, c, i, j; u8 r, c, i, j;
@@ -415,16 +505,22 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
if(ch < 0x20 || ch > 0x7E) return; if(ch < 0x20 || ch > 0x7E) return;
LCD_SetWindow(x + 4, x + 16 - 1 + 4, y, y + 32 - 1); LCD_SetWindow(x + 4, x + 16 - 1 + 4, y, y + 32 - 1); // 锁定 16x32 的绘图视窗
WriteComm(0x2C); WriteComm(0x2C);
// 遍历字型的 16 个点阵行数据
for(r = 0; r < 16; r++) for(r = 0; r < 16; r++)
{ {
u8 line = FONT_8x16[char_idx][r]; u8 line = FONT_8x16[char_idx][r];
// 纵向插点翻倍:将当前行重复绘制两次 (i = 0 和 i = 1)
for(i = 0; i < 2; i++) for(i = 0; i < 2; i++)
{ {
// 遍历单行内的 8 个 bit 像素点
for(c = 0; c < 8; c++) for(c = 0; c < 8; c++)
{ {
u8 val_bit = (line & (0x80 >> c)); u8 val_bit = (line & (0x80 >> c));
// 横向插点翻倍:单个像素点重复发送写入 2 次 (j = 0 和 j = 1)
for(j = 0; j < 2; j++) for(j = 0; j < 2; j++)
{ {
if(val_bit) if(val_bit)
@@ -443,23 +539,32 @@ void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
} }
} }
/**
* @brief 在指定坐标绘制特大 16x32 规格的数字/英文字符串
*/
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{ {
while(*str) while(*str)
{ {
LCD_ShowChar16x32(x, y, *str, color, bg_color); LCD_ShowChar16x32(x, y, *str, color, bg_color);
x += 16; x += 16; // 每次起始坐标偏移 16 像素
str++; str++;
} }
} }
/**
* @brief 在指定 Y 轴高度上,水平居中渲染显示特大 16x32 规格字符串
*/
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color) void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color)
{ {
u16 len = 0; u16 len = 0;
char *p = str; char *p = str;
while(*p++) len++; while(*p++) len++; // 计算字数
if(len * 16 >= 120) if(len * 16 >= 120)
// 超出屏幕宽度,左对齐
LCD_ShowString16x32(0, y, str, color, bg_color); LCD_ShowString16x32(0, y, str, color, bg_color);
else else
// 水平居中计算
LCD_ShowString16x32((120 - len * 16) / 2, y, str, color, bg_color); LCD_ShowString16x32((120 - len * 16) / 2, y, str, color, bg_color);
} }

View File

@@ -4,49 +4,157 @@
#include "../App/config.h" #include "../App/config.h"
#include "stc32g.h" #include "stc32g.h"
// Color Constants (RGB565) /* ====== Truly AMOLED RM69310 屏幕驱动 (RGB565格式) 常数定义 ====== */
#define COLOR_BLACK 0x0000
#define COLOR_WHITE 0xFFFF
#define COLOR_CYAN 0x073F
#define COLOR_RED 0xF800
#define COLOR_GREEN 0x07E0
#define COLOR_BLUE 0x001F
#define COLOR_GRAY 0x5AEB
#define COLOR_AMBER 0xFDC0
// Public screen functions // 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 // 琥珀色/橙黄色
/* ====== 屏幕电源控制与基本驱动接口 ====== */
/**
* @brief 通过脉冲序列为 SGM3833 屏幕负压供电芯片发送开启配置脉冲
* @param pulse_cnt 发送的脉冲个数 (例如 27 次脉冲开启并稳压屏幕输出)
* @details 采用单线脉冲边沿计数调制技术,为 RM69310 控制芯片提供其正常显示所需的高质量偏置正负电压。
*/
void SGM_SendPulse(u8 pulse_cnt); void SGM_SendPulse(u8 pulse_cnt);
/**
* @brief 执行 Truly AMOLED 屏幕的硬件复位
* @details 将 LCD_RST 引脚拉低保持一定时间后拉高,强制复位屏幕控制器,使其恢复到初始逻辑状态。
*/
void LCD_Reset(void); void LCD_Reset(void);
/**
* @brief 初始化 Truly AMOLED 屏幕控制器 RM69310
* @details 执行屏幕硬件复位,通过软件 SPI 总线依次向屏幕控制器写入寄存器偏置、显示周期、
* 通道选择、关闭休眠 (Sleep Out: 0x11)、开启显示 (Display On: 0x29) 等一系列
* 出厂初始化命令序列,使屏幕准备好接收显示像素帧。
*/
void LCD_Init(void); void LCD_Init(void);
/**
* @brief 向屏幕写入 1 字节控制命令
* @param cmd 8 位命令字节
*/
void WriteComm(u8 cmd); 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 限定后续的数据流写入范围,实现局部页面刷新或全部页面刷新。
*/
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end); void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end);
/**
* @brief 以指定纯色清除整个屏幕显示内容
* @param color 16位 RGB565 颜色填充值
*/
void LCD_Clear(u16 color); void LCD_Clear(u16 color);
// Drawing Primitives
/* ====== 像素级图形几何算法与字符渲染接口 ====== */
/**
* @brief 在屏幕指定位置绘制一个单色填充的实心矩形
* @param x 矩形左上角 X 轴坐标
* @param y 矩形左上角 Y 轴坐标
* @param w 矩形宽度
* @param h 矩形高度
* @param color 16位 RGB565 填充色彩值
*/
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color); void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color);
/**
* @brief 绘制一个无填充的空心矩形边界框
* @param x 矩形左上角 X 轴坐标
* @param y 矩形左上角 Y 轴坐标
* @param w 边框总宽度
* @param h 边框总高度
* @param color 16位 RGB565 边框线条色彩
*/
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color); void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color);
/**
* @brief 绘制渲染一个自定义宽高的单色单色位图
* @param x 绘制的左上角 X 轴坐标
* @param y 绘制的左上角 Y 轴坐标
* @param w 单色图宽度 (单位: 像素点数)
* @param h 单色图高度
* @param bmp 单色位图所在的 FLASH code 区指针 (1位对应1像素1画前景色0画背景色)
* @param color 位图前景色 (RGB565)
* @param bg_color 位图背景色
*/
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color); void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color);
/**
* @brief 渲染显示一个 8x16 尺寸的普通英文 ASCII 字符
*/
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color); void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color);
/**
* @brief 渲染显示一行普通的英文 ASCII 字符串 (左对齐)
* @param x 绘制行起始 X 坐标
* @param y 绘制行 Y 坐标
* @param str 字符串头指针
* @param color 字符颜色 (RGB565)
* @param bg_color 字符背景背景填充色
*/
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color); void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color);
/**
* @brief 在指定 Y 轴高度上,水平居中对齐显示一行字符串
* @param y 渲染的 Y 轴高度 (0 ~ 239)
* @param str 字符串内容
* @param color 颜色值
* @param bg_color 背景色
*/
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color); void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color);
/**
* @brief 渲染显示一个 16x32 大号特粗尺寸的英文/数字字符
*/
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color); void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color);
/**
* @brief 显示大号特粗 16x32 规格的数字/英文字符串 (如时钟大字显示)
*/
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color); void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color);
/**
* @brief 在指定 Y 轴高度上,居中显示 16x32 特大规格字体的字符串
* @param y 渲染的 Y 轴高度 (0 ~ 239)
* @param str 大字字符串内容
* @param color 大字前景色
* @param bg_color 大字背景色
*/
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color); void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color);
/* ====== 图标与点阵字模外部数据声明 ====== */
// 这些数据实体均定义在底层字库 lcd_font.h 文件中
// Font and Bitmap externs (defined in lcd_font.h via lcd.c) extern unsigned char code FONT_8x16[95][16]; // 8x16 英文 ASCII 点阵字库
extern unsigned char code FONT_8x16[95][16]; extern unsigned char code bmp_battery_20x10[]; // 20x10 电池电量指示位图
extern unsigned char code bmp_battery_20x10[]; extern unsigned char code bmp_lock_16x16[]; // 16x16 闭合锁头位图
extern unsigned char code bmp_lock_16x16[]; extern unsigned char code bmp_unlock_16x16[]; // 16x16 开启锁头位图
extern unsigned char code bmp_unlock_16x16[]; extern unsigned char code bmp_radar_32x32_f1[]; // 32x32 雷达扩散帧 1
extern unsigned char code bmp_radar_32x32_f1[]; extern unsigned char code bmp_radar_32x32_f2[]; // 32x32 雷达扩散帧 2
extern unsigned char code bmp_radar_32x32_f2[]; extern unsigned char code bmp_radar_32x32_f3[]; // 32x32 雷达扩散帧 3
extern unsigned char code bmp_radar_32x32_f3[]; extern unsigned char code bmp_confirm_lock_32x32[]; // 32x32 传感器类型锁头大图
extern unsigned char code bmp_confirm_lock_32x32[]; extern unsigned char code bmp_checkmark_32x32[]; // 32x32 配对成功大对勾
extern unsigned char code bmp_checkmark_32x32[]; extern unsigned char code bmp_cross_32x32[]; // 32x32 配对失败大红叉
extern unsigned char code bmp_cross_32x32[]; extern unsigned char code bmp_door_32x32[]; // 32x32 门磁大警告图标
extern unsigned char code bmp_door_32x32[]; extern unsigned char code bmp_siren_32x32[]; // 32x32 烟感喇叭警示图标
extern unsigned char code bmp_siren_32x32[];
#endif #endif // __LCD_H__

View File

@@ -1,6 +1,19 @@
#ifndef __FONT_H__ #ifndef __FONT_H__
#define __FONT_H__ #define __FONT_H__
/**************************************************************************
* @file lcd_font.h
* @brief 屏幕显示字库与单色位图数据定义文件
* @details 本文件存储了手环系统在 AMOLED 屏幕上渲染所使用的各类静态图形资源,
* 包含以下内容:
* 1. 电池电量单色图 (bmp_battery_20x10)
* 2. 布防/撤防指示用 16x16 锁图标 (bmp_lock, bmp_unlock)
* 3. 对码动画所需的 32x32 雷达动态扫描帧 (bmp_radar_32x32_f1 ~ f3)
* 4. 设备类型确认与保存界面用的 32x32 锁、成功对勾、失败红叉位图
* 5. 报警入侵指示用的 32x32 门磁大图 (bmp_door_32x32) 与警笛喇叭大图
* 6. 标准 8x16 英文 ASCII 字符点阵字库 (FONT_8x16),用于文本渲染
* @note 所有数据均使用 `code` 关键字修饰,强制存储在单片机 Flash 空间中以节省 RAM。
**************************************************************************/
// 1. Horizontal Battery Icon (20x10 pixels) // 1. Horizontal Battery Icon (20x10 pixels)
// Width: 20 pixels (3 bytes per row), Height: 10 rows // Width: 20 pixels (3 bytes per row), Height: 10 rows

View File

@@ -1,18 +1,17 @@
#include "rf.h" #include "rf.h"
#include "../App/config.h" #include "../App/config.h"
#include "intrins.h" #include "intrins.h"
// ATR5179 射频开关控制脚 // ATR5179 射频前端天线开关控制引脚定义
sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到 TX 路径) sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到发射 TX 路径)
sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到 RX 路径) sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到接收 RX 路径)
// 射频数据与控制引脚 // 射频芯片数据与电源控制引脚定义
sbit RF_TX_DAT = P3^6; // LT4455 数据输入 (DIN) sbit RF_TX_DAT = P3^6; // LT4455 发射调制数据输入引脚 (DIN)
sbit RF_RX_DATA = P2^1; // LR690L 数据输出 (DATA) sbit RF_RX_DATA = P2^1; // LR690L 接收解调数据输出引脚 (DATA),连接单片机中断管脚
sbit SHUT = P2^2; // LR690L 休眠脚 (0:工作, 1:休眠) sbit SHUT = P2^2; // LR690L 芯片休眠控制引脚 (1:休眠0:工作)
// External functions defined in main.c // 声明外部延时及串口打印函数 (在 system.c 中定义)
extern void Delay_us(u16 us); extern void Delay_us(u16 us);
extern void Delay_ms(u16 ms); extern void Delay_ms(u16 ms);
extern u16 GetTimer0_Safe(void); extern u16 GetTimer0_Safe(void);
@@ -23,61 +22,80 @@ extern void Uart_SendHex16(u16 val);
extern void Uart_SendHex20(u32 val); extern void Uart_SendHex20(u32 val);
extern void Uart_SendByte(u8 dat); extern void Uart_SendByte(u8 dat);
/**
* @brief 初始化无线射频芯片相关的 GPIO 引脚方向与初始状态
*/
void RF_Init(void) void RF_Init(void)
{ {
// P2.0 (RF_TX) 和 P2.2 (SHUT) 配置为推挽输出P2.1 (RF_RX_DATA) 配置为双向口/输入 // 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)); P2M1 &= ~((1 << 0) | (1 << 1) | (1 << 2));
P2M0 &= ~(1 << 1); P2M0 &= ~(1 << 1);
P2M0 |= ((1 << 0) | (1 << 2)); P2M0 |= ((1 << 0) | (1 << 2));
// P3.6 (RF_TX_DAT), P3.7 (RF_RX) 配置为推挽输出 // 2. 配置 P3.6 (RF_TX_DAT) P3.7 (RF_RX) 为推挽输出模式 (M1=0, M0=1)
P3M1 &= ~((1 << 6) | (1 << 7)); P3M1 &= ~((1 << 6) | (1 << 7));
P3M0 |= ((1 << 6) | (1 << 7)); P3M0 |= ((1 << 6) | (1 << 7));
RF_TX = 0; // 3. 设置系统默认的初始电平
SHUT = 1; // 默认关闭接收芯片 RF_TX = 0; // 断开发射天线通路
RF_RX = 0; SHUT = 1; // 默认拉高 SHUT使 LR690L 接收芯片处于低功耗休眠关闭状态
RF_TX_DAT = 0; RF_RX = 0; // 断开接收天线通路
RF_TX_DAT = 0; // 调制发送数据线置低
} }
/**
* @brief 配置无线射频收发系统的运行模式与天线切换
* @param mode 运行模式 (0-休眠隔离, 1-接收工作, 2-发射工作)
*/
void RF_SetMode(u8 mode) void RF_SetMode(u8 mode)
{ {
if (mode == 0) // Sleep/Idle if (mode == 0) // ====== 模式 0: 休眠隔离模式 (Sleep/Idle) ======
{ {
SHUT = 1; // LR690L Sleep SHUT = 1; // 强制拉高 SHUT 引脚,使 LR690L 彻底进入低功耗待机
RF_RX = 0; // 断开接收天线 RF_RX = 0; // 关闭接收天线开关,防止外界电磁杂波串入接收电路
RF_TX = 0; // 断开发射天线 RF_TX = 0; // 关闭发射天线开关
} }
else if (mode == 1) // Rx Mode else if (mode == 1) // ====== 模式 1: 射频接收模式 (Rx Mode) ======
{ {
SHUT = 0; // LR690L 工作 SHUT = 0; // 拉低 SHUT启动并使能 LR690L 接收解调芯片工作
RF_RX = 1; // 天线切至接收端 RF_RX = 1; // 选通天线切换开关到 RX 路径,连通物理接收天线
RF_TX = 0; RF_TX = 0; // 断开 TX 发射路径,防止回环烧毁前端
} }
else if (mode == 2) // Tx Mode else if (mode == 2) // ====== 模式 2: 射频发射模式 (Tx Mode) ======
{ {
SHUT = 1; // LR690L 睡眠 SHUT = 1; // 关闭 LR690L 接收芯片,避免发射大功率波形串入接收机
RF_RX = 0; RF_RX = 0; // 断开接收路径天线
RF_TX = 1; // 天线切发射 RF_TX = 1; // 选通天线切换开关至 TX 路径,连通物理天线进行发射
} }
} }
/**
* @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)。
*/
void EV1527_TxFrame(u32 addr, u8 dat) void EV1527_TxFrame(u32 addr, u8 dat)
{ {
u8 i; u8 i;
// 1. 同步脉冲: 1T 高电平 + 31T 低电平 // ====== 1. 发送同步引导脉冲 ======
RF_TX_DAT = 1; RF_TX_DAT = 1;
Delay_us(320); Delay_us(320); // 1T 高电平
RF_TX_DAT = 0; RF_TX_DAT = 0;
Delay_us(9920); Delay_us(9920); // 31T 低电平
// 2. 20位地址码 (MSB first) // ====== 2. 依次调制发送 20 位物理地址码 (高位 MSB 优先发送) ======
for (i = 0; i < 20; i++) for (i = 0; i < 20; i++)
{ {
if ((addr >> (19 - i)) & 1) if ((addr >> (19 - i)) & 1)
{ {
// 逻辑 1: 3T 高电平 + 1T 低电平 // 数据 bit 1: 3T + 1T
RF_TX_DAT = 1; RF_TX_DAT = 1;
Delay_us(960); Delay_us(960);
RF_TX_DAT = 0; RF_TX_DAT = 0;
@@ -85,7 +103,7 @@ void EV1527_TxFrame(u32 addr, u8 dat)
} }
else else
{ {
// 逻辑 0: 1T 高电平 + 3T 低电平 // 数据 bit 0: 1T + 3T
RF_TX_DAT = 1; RF_TX_DAT = 1;
Delay_us(320); Delay_us(320);
RF_TX_DAT = 0; RF_TX_DAT = 0;
@@ -93,12 +111,12 @@ void EV1527_TxFrame(u32 addr, u8 dat)
} }
} }
// 3. 4位数据码 (MSB first) // ====== 3. 依次调制发送 4 位指令状态数据码 (高位 MSB 优先) ======
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++)
{ {
if ((dat >> (3 - i)) & 1) if ((dat >> (3 - i)) & 1)
{ {
// 逻辑 1: 3T 高电平 + 1T 低电平 // 数据 bit 1: 3T + 1T
RF_TX_DAT = 1; RF_TX_DAT = 1;
Delay_us(960); Delay_us(960);
RF_TX_DAT = 0; RF_TX_DAT = 0;
@@ -106,7 +124,7 @@ void EV1527_TxFrame(u32 addr, u8 dat)
} }
else else
{ {
// 逻辑 0: 1T 高电平 + 3T 低电平 // 数据 bit 0: 1T + 3T
RF_TX_DAT = 1; RF_TX_DAT = 1;
Delay_us(320); Delay_us(320);
RF_TX_DAT = 0; RF_TX_DAT = 0;
@@ -115,33 +133,62 @@ void EV1527_TxFrame(u32 addr, u8 dat)
} }
} }
/**
* @brief 循环发射多帧无线 EV1527 信号以保证数据通信的完整性
* @param addr 20位源地址
* @param dat 4位命令码
*/
void EV1527_Transmit(u32 addr, u8 dat) void EV1527_Transmit(u32 addr, u8 dat)
{ {
u8 r; u8 r;
for (r = 0; r < 25; r++) // 增加到 25 帧(约 340ms // 循环发射 25 次相同的射频帧 (累计耗时约 340 毫秒),抵御外界瞬间同频杂波干扰
for (r = 0; r < 25; r++)
{ {
EV1527_TxFrame(addr, dat); EV1527_TxFrame(addr, dat);
} }
} }
/**
* @brief 使用定时器 0 (Timer0) 高精度的测量引脚指定电平状态的持续宽度时间
* @param state 待测电平状态 (1-测量高电平持续宽度, 0-测量低电平持续宽度)
* @param timeout_us 最大等待超时微秒数上限,超时自动退出防锁死
* @return u16 最终测量的实际电平持续微秒数。若发生超时则直接返回 0。
*/
u16 GetPulseDuration(u8 state, u16 timeout_us) u16 GetPulseDuration(u8 state, u16 timeout_us)
{ {
// 根据系统晶振频率计算在 12T 模式下目标超时的 Timer0 Tick 数值上限
u16 timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL); u16 timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL);
TL0 = 0; TL0 = 0; // 定时器 0 计数值清零
TH0 = 0; TH0 = 0;
TR0 = 1; // 启动定时器0 TR0 = 1; // 启动定时器 0 硬件计数
// 轮询引脚:只要电平仍旧等于目标待测状态,就继续保持循环
while (RF_RX_DATA == state) while (RF_RX_DATA == state)
{ {
// 软件防卡死判定:如果当前计数值超过了设定的最大允许超时 Tick 值
if (GetTimer0_Safe() > timeout_ticks) if (GetTimer0_Safe() > timeout_ticks)
{ {
TR0 = 0; TR0 = 0; // 关断定时器
return 0; // 超时 return 0; // 判定超时,返回 0
} }
} }
TR0 = 0; TR0 = 0; // 关断定时器
return (u16)((u32)GetTimer0_Safe() * 12UL / (MAIN_Fosc / 1000000UL)); // 返回微秒数
// 将最终读取的 Timer0 硬件 Tick 数值,重新换算成微秒数返回 (1 Tick = 12 / 24MHz = 0.5us)
return (u16)((u32)GetTimer0_Safe() * 12UL / (MAIN_Fosc / 1000000UL));
} }
/**
* @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。
*/
bit EV1527_Decode(u32 *out_addr, u8 *out_data) bit EV1527_Decode(u32 *out_addr, u8 *out_data)
{ {
u16 high_time, low_time; u16 high_time, low_time;
@@ -149,68 +196,97 @@ bit EV1527_Decode(u32 *out_addr, u8 *out_data)
u32 addr = 0; u32 addr = 0;
u8 dat = 0; u8 dat = 0;
u16 T; u16 T;
u16 idata raw_h[24]; u16 idata raw_h[24]; // 高电平时间寄存数组 (置于 idata 区)
u16 idata raw_l[24]; u16 idata raw_l[24]; // 低电平时间寄存数组
// 1. 同步头捕获: 必须是低电平状态之后变高电平开始 // ====== 1. 同步头引导脉冲捕获 (定位上升沿) ======
if (RF_RX_DATA == 0)
{ {
u16 wait_cnt = 0; u16 wait_cnt = 0;
while (RF_RX_DATA == 0) // 如果当前射频解调输出是高电平,等待其变低
while (RF_RX_DATA == 1)
{ {
Delay_us(10); Delay_us(5);
wait_cnt++; wait_cnt++;
if (wait_cnt > 3000) // 30ms 超时 if (wait_cnt > 2000) // 10 毫秒超时保护
return 0;
}
}
// 测量同步头高电平时间 (放宽至 2500us)
high_time = GetPulseDuration(1, 2500);
if (high_time < 50 || high_time > 2500)
return 0;
// 测量同步头低电平时间 (放宽至 60000us)
low_time = GetPulseDuration(0, 60000);
if (low_time < 1500 || low_time > 60000)
return 0;
// 计算高低电平时间之比。对于标准 EV1527低电平是高电平的 31 倍。
// 放宽比例范围为 15 到 48以应对各种不同的遥控器电阻。
{
u32 ratio = (u32)low_time / high_time;
if (ratio < 15 || ratio > 48)
return 0; return 0;
} }
// 估算基准脉冲周期 T = low_time / 31 // 当引脚维持在低电平后,阻塞等待它跳变为高电平(捕获到同步引导信号的上升沿)
T = low_time / 31; wait_cnt = 0;
if (T == 0) T = 1;
// 2. 连续抓取 24 个数据脉冲
for (i = 0; i < 24; i++)
{
u16 wait_cnt = 0;
// 等待下一个数据脉冲的上升沿 (如果当前是低电平,等待它变高)
while (RF_RX_DATA == 0) while (RF_RX_DATA == 0)
{ {
Delay_us(5); Delay_us(5);
wait_cnt++; wait_cnt++;
if (wait_cnt > 1000) // 5ms 超时 if (wait_cnt > 4000) // 20 毫秒超时保护
{ return 0;
return 0; // 解码失败
} }
} }
// 测量高电平持续时间和低电平持续时间 // 捕获引导头高电平时间 (放宽测量阈值上限至 2500 微秒)
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 毫秒)
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;
}
}
// 自适应估算出基准脉冲时值 T
T = low_time / 31;
if (T == 0) T = 1; // 零安全防护
// ====== 2. 依次采集接下来的 24 个数据 bit 脉冲 ======
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); raw_h[i] = GetPulseDuration(1, 5000);
if (raw_h[i] == 0) return 0; if (raw_h[i] == 0) return 0; // 超时错误直接退出
raw_l[i] = GetPulseDuration(0, 5000); raw_l[i] = GetPulseDuration(0, 5000);
if (raw_l[i] == 0) return 0; if (raw_l[i] == 0) return 0;
} }
// 3. 将抓到的全部 24 位脉冲数据无条件输出到串口 (缩短输出信息以防止阻塞接收) // ====== 3. 串口输出捕获的时序参数 ======
Uart_SendString("Captured: Sync H="); Uart_SendString("Captured: Sync H=");
Uart_SendHex16(high_time); Uart_SendHex16(high_time);
Uart_SendString("us, L="); Uart_SendString("us, L=");
@@ -219,13 +295,16 @@ bit EV1527_Decode(u32 *out_addr, u8 *out_data)
Uart_SendHex16(T); Uart_SendHex16(T);
Uart_SendString("us\r\n"); Uart_SendString("us\r\n");
// 4. 组合成 24 位地址数据,使用自适应阈值:如果高电平时间比低电平时间长,判定为 1反之为 0 // ====== 4. 自适应决策:组合成地址数据位 ======
for (i = 0; i < 24; i++) for (i = 0; i < 24; i++)
{ {
// 比较本位高低电平持续宽度:
// 如果高电平大于低电平,说明宽脉冲在高,判定该位为 1 (3T高+1T低)。
// 否则高电平短,判定为 0 (1T高+3T低)。
if (raw_h[i] > raw_l[i]) if (raw_h[i] > raw_l[i])
{ {
if (i < 20) addr = (addr << 1) | 1; if (i < 20) addr = (addr << 1) | 1; // 高 20 位写入物理地址
else dat = (dat << 1) | 1; else dat = (dat << 1) | 1; // 低 4 位写入状态码
} }
else else
{ {
@@ -237,28 +316,33 @@ bit EV1527_Decode(u32 *out_addr, u8 *out_data)
*out_addr = addr; *out_addr = addr;
*out_data = dat; *out_data = dat;
// 串口打印解码结果方便现场调试 // 串口打印解码最终结果
Uart_SendString("Decoded ADDR: 0x"); Uart_SendString("Decoded ADDR: 0x");
Uart_SendHex20(addr); Uart_SendHex20(addr);
Uart_SendString(", DATA: 0x"); Uart_SendString(", DATA: 0x");
Uart_SendHex4(dat); Uart_SendHex4(dat);
Uart_SendString("\r\n"); Uart_SendString("\r\n");
return 1; return 1; // 成功解析
} }
/**
* @brief 软硬件环路自检回环测试
* @details 将发射引脚置位并同时采样接收引脚,用于测试收发芯片控制连通性。
*/
void Loopback_Test(void) void Loopback_Test(void)
{ {
u16 match_count = 0; u16 match_count = 0;
u16 i; u16 i;
SHUT = 0; // 使能接收芯片 SHUT = 0; // 强行开启接收芯片供电
RF_RX = 1; // 选通天线到接收端 RF_RX = 1; // 选通天线到 RX
RF_TX = 0; // 发送侧断开 RF_TX = 0; // 发送引脚隔离
Delay_ms(100); Delay_ms(100);
Uart_SendString("Starting RF Loopback Self-Test...\r\n"); Uart_SendString("Starting RF Loopback Self-Test...\r\n");
// 高频翻转模拟发送引脚,同步读取接收解调出的管脚信号,统计匹配率
for (i = 0; i < 100; i++) for (i = 0; i < 100; i++)
{ {
RF_TX_DAT = 1; RF_TX_DAT = 1;
@@ -283,6 +367,62 @@ void Loopback_Test(void)
Uart_SendString("Result: FAIL! No RF signal detected. Check hardware.\r\n"); Uart_SendString("Result: FAIL! No RF signal detected. Check hardware.\r\n");
} }
SHUT = 1; SHUT = 1; // 关闭接收芯片
RF_RX = 0; RF_RX = 0; // 断开接收通道
}
/**
* @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++)
{
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);
}
}
// 输出本秒的电平跳转总数,若非发射期该数值很大,表明空中有丰富噪声噪底跳变,属接收机正常表现
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");
}
Uart_SendString("=== RF Pin Test End ===\r\n");
} }

View File

@@ -4,13 +4,66 @@
#include "../App/config.h" #include "../App/config.h"
#include "stc32g.h" #include "stc32g.h"
// Public RF functions /* ====== LR690L / EV1527 无线射频编解码驱动接口 ====== */
/**
* @brief 初始化无线射频芯片相关的 GPIO 引脚
* @details 配置 RF_TX_DAT (P3.6) 为推挽输出,配置 RF_TX (P2.0)、RF_RX (P3.7)、SHUT (P2.2) 等引脚,
* 使能 ATR5179 射频前端开关,为无线射频收发建立硬件基础。
*/
void RF_Init(void); void RF_Init(void);
void RF_SetMode(u8 mode); // mode: 0=Sleep, 1=Rx, 2=Tx
/**
* @brief 配置无线射频收发系统的运行模式
* @param mode 0-休眠模式 (Sleep)1-接收模式 (Rx)2-发射模式 (Tx)
* @details 负责联合调度 SHUT 控制引脚、天线切换使能引脚以及 ATR5179 射频开关:
* - 模式 0 (Sleep): 拉高 SHUT 以关断 LR690L 芯片电源,实现零电流待机。
* - 模式 1 (Rx): 拉低 SHUT 启动 LR690L 芯片,接通天线并配置前端为接收放大状态。
* - 模式 2 (Tx): 切换天线前端开关为发射直通状态,准备调制发送。
*/
void RF_SetMode(u8 mode);
/**
* @brief 按照 EV1527 协议单次调制发送一帧 24 位射频数据
* @param addr 24 位发射源地址
* @param dat 4 位命令状态数据码
* @details 会生成由 1 个同步码 (Sync Code) 和 24 个数据编码(由 addr + dat 组成)组成的完整帧脉冲。
*/
void EV1527_TxFrame(u32 addr, u8 dat); void EV1527_TxFrame(u32 addr, u8 dat);
/**
* @brief 连续多次发送 EV1527 射频帧,防止单帧丢失
* @param addr 24 位源地址
* @param dat 4 位命令状态码
* @details 连续发送多次以保证无线通信在嘈杂噪声环境下的接收可靠性。
*/
void EV1527_Transmit(u32 addr, u8 dat); void EV1527_Transmit(u32 addr, u8 dat);
/**
* @brief 获取特定引脚电平状态的持续微秒时间
* @param state 待检测的电平目标状态 (0-低电平, 1-高电平)
* @param timeout_us 最大等待超时上限 (单位: 微秒)
* @return u16 最终测得的脉冲宽度时长。如果超出 timeout_us 仍未改变电平,则直接返回超时值。
*/
u16 GetPulseDuration(u8 state, u16 timeout_us); u16 GetPulseDuration(u8 state, u16 timeout_us);
/**
* @brief 阻塞式检测并解码一个合法的 EV1527 射频信号数据帧
* @param out_addr 输出参数指针:若解码成功,将写入解析得到的 20 位发送地址 (即 24 位帧除去 4 位数据位后的剩余高 20 位)
* @param out_data 输出参数指针:若解码成功,将写入解析出的 4 位命令状态数据码 (例如报警/SOS等)
* @return bit 1-成功解析出一帧有效数据0-当前未捕获到合法脉冲或校验失败
* @note 此函数通过对 P2.1 (RF_RX_DATA) 输入脉冲宽度进行软件精密测量,定位同步头,依次采集 24 个数据位,并进行 3 次重复性去噪校验。
*/
bit EV1527_Decode(u32 *out_addr, u8 *out_data); bit EV1527_Decode(u32 *out_addr, u8 *out_data);
/**
* @brief 串口与射频功能的回环测试函数 (调试使用)
*/
void Loopback_Test(void); void Loopback_Test(void);
#endif /**
* @brief 射频控制引脚的高低电平输出极性测试 (硬件排查使用)
*/
void RF_Test_Pin(void);
#endif // __RF_H__

View File

@@ -1,15 +1,23 @@
#ifndef __STC32G_H__ #ifndef __STC32G_H__
#define __STC32G_H__ #define __STC32G_H__
/**************************************************************************
* @file stc32g.h
* @brief STC32G 系列单片机官方特殊功能寄存器 (SFR) 定义头文件
* @details 本文件包含了 STC32G12K128 单片机内核与所有外设寄存器地址的宏定义。
* 为保持与官方标准库及编译器的最大兼容性,本文件不对寄存器宏进行逐行改动,
* 仅在此处添加中文引导说明。
**************************************************************************/
///////////////////////////////////////////////// /////////////////////////////////////////////////
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͷ<EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>,<2C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٰ<EFBFBD><D9B0><EFBFBD>"REG51.H" //包含本头文件后,不用另外再包含"REG51.H"
#include "stdio.h" #include "stdio.h"
#include "intrins.h" #include "intrins.h"
#include "string.h" #include "string.h"
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>LCALL 0x0000<EFBFBD> //修正编译器的LCALL 0x0000异常
extern void _m(void); extern void _m(void);
#define main(x) main(x){_m();sprintf(0, "0");strlen("0");}; void _m(x) #define main(x) main(x){_m();sprintf(0, "0");strlen("0");}; void _m(x)
@@ -529,10 +537,10 @@ sfr RSTCFG = 0xff;
sbit ENLVR = RSTCFG^6; sbit ENLVR = RSTCFG^6;
sbit P54RST = RSTCFG^4; sbit P54RST = RSTCFG^4;
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܼĴ<EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>չRAM<EFBFBD><EFBFBD><EFBFBD><EFBFBD> //如下特殊功能寄存器位于扩展RAM区域
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Щ<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>,<2C><><EFBFBD>Ƚ<EFBFBD>EAXFR<46><52><EFBFBD><EFBFBD>Ϊ1,<2C>ſ<EFBFBD><C5BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д //访问这些寄存器,需先将EAXFR设置为1,才可正常读写
// EAXFR = 1; // EAXFR = 1;
//<EFBFBD><EFBFBD><EFBFBD><EFBFBD> //或者
// P_SW2 |= 0x80; // P_SW2 |= 0x80;
///////////////////////////////////////////////// /////////////////////////////////////////////////
@@ -949,14 +957,14 @@ typedef struct TAG_PWM_STRUCT
#define HSSPI_CFG2 (*(unsigned char volatile far *)0x7efbf9) #define HSSPI_CFG2 (*(unsigned char volatile far *)0x7efbf9)
#define HSSPI_STA (*(unsigned char volatile far *)0x7efbfa) #define HSSPI_STA (*(unsigned char volatile far *)0x7efbfa)
//ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĺ<EFBFBD>,<2C><><EFBFBD>Ƚ<EFBFBD>EAXFR<46><52><EFBFBD><EFBFBD>Ϊ1 //使用下面的宏,需先将EAXFR设置为1
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>: //使用方法:
// char val; // char val;
// //
// EAXFR = 1; //ʹ<EFBFBD>ܷ<EFBFBD><EFBFBD><EFBFBD>XFR // EAXFR = 1; //使能访问XFR
// READ_HSPWMA(PWMA_CR1, val); //<EFBFBD><EFBFBD><EFBFBD>PWMA<EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // READ_HSPWMA(PWMA_CR1, val); //异步读PWMA组寄存器
// val |= 0x01; // val |= 0x01;
// WRITE_HSPWMA(PWMA_CR1, val); //<EFBFBD>첽дPWMA<EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // WRITE_HSPWMA(PWMA_CR1, val); //异步写PWMA组寄存器
#define READ_HSPWMA(reg, dat) \ #define READ_HSPWMA(reg, dat) \
{ \ { \
@@ -1168,13 +1176,13 @@ typedef struct TAG_PWM_STRUCT
//#define CANAR (*(unsigned char volatile far *)0x7efebb) //#define CANAR (*(unsigned char volatile far *)0x7efebb)
//#define CANDR (*(unsigned char volatile far *)0x7efebc) //#define CANDR (*(unsigned char volatile far *)0x7efebc)
//ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĺ<EFBFBD>,<2C><><EFBFBD>Ƚ<EFBFBD>EAXFR<46><52><EFBFBD><EFBFBD>Ϊ1 //使用下面的宏,需先将EAXFR设置为1
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>: //使用方法:
// char dat; // char dat;
// //
// EAXFR = 1; //ʹ<EFBFBD>ܷ<EFBFBD><EFBFBD><EFBFBD>XFR // EAXFR = 1; //使能访问XFR
// dat = READ_CAN(RX_BUF0); //<EFBFBD><EFBFBD>CAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // dat = READ_CAN(RX_BUF0); //CAN寄存器
// WRITE_CAN(TX_BUF0, 0x55); //дCAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // WRITE_CAN(TX_BUF0, 0x55); //CAN寄存器
#define READ_CAN(reg) (CANAR = (reg), CANDR) #define READ_CAN(reg) (CANAR = (reg), CANDR)
#define WRITE_CAN(reg, dat) (CANAR = (reg), CANDR = (dat)) #define WRITE_CAN(reg, dat) (CANAR = (reg), CANDR = (dat))
@@ -1218,11 +1226,11 @@ typedef struct TAG_PWM_STRUCT
//sfr LINAR = 0xfa; //sfr LINAR = 0xfa;
//sfr LINDR = 0xfb; //sfr LINDR = 0xfb;
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>: //使用方法:
// char dat; // char dat;
// //
// dat = READ_LIN(LBUF); //<EFBFBD><EFBFBD>CAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // dat = READ_LIN(LBUF); //CAN寄存器
// WRITE_LIN(LBUF, 0x55); //дCAN<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // WRITE_LIN(LBUF, 0x55); //CAN寄存器
#define READ_LIN(reg) (LINAR = (reg), LINDR) #define READ_LIN(reg) (LINAR = (reg), LINDR)
#define WRITE_LIN(reg, dat) (LINAR = (reg), LINDR = (dat)) #define WRITE_LIN(reg, dat) (LINAR = (reg), LINDR = (dat))
@@ -1249,11 +1257,11 @@ typedef struct TAG_PWM_STRUCT
//sfr USBCON = 0xf4; //sfr USBCON = 0xf4;
//sfr USBADR = 0xfc; //sfr USBADR = 0xfc;
//ʹ<EFBFBD>÷<EFBFBD><EFBFBD><EFBFBD>: //使用方法:
// char dat; // char dat;
// //
// READ_USB(CSR0, dat); //<EFBFBD><EFBFBD>USB<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // READ_USB(CSR0, dat); //USB寄存器
// WRITE_USB(FADDR, 0x00); //дUSB<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD> // WRITE_USB(FADDR, 0x00); //USB寄存器
#define READ_USB(reg, dat) \ #define READ_USB(reg, dat) \
{ \ { \
@@ -1435,8 +1443,8 @@ typedef struct TAG_PWM_STRUCT
#define DMA_I2SR_VECTOR 64 //0203H #define DMA_I2SR_VECTOR 64 //0203H
///////////////////////////////////////////////// /////////////////////////////////////////////////
#define EAXSFR() EAXFR = 1 /* MOVX A,@DPTR/MOVX @DPTR,Aָ<EFBFBD><EFBFBD><EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>չSFR(XSFR) */ #define EAXSFR() EAXFR = 1 /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展SFR(XSFR) */
#define EAXRAM() EAXFR = 0 /* MOVX A,@DPTR/MOVX @DPTR,Aָ<EFBFBD><EFBFBD><EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>չRAM(XRAM) */ #define EAXRAM() EAXFR = 0 /* MOVX A,@DPTR/MOVX @DPTR,A指令的操作对象为扩展RAM(XRAM) */
///////////////////////////////////////////////// /////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"hidden_nets": [ "hidden_nets": [
"GND" "GND"
], ],
"high_contrast_mode": 1, "high_contrast_mode": 0,
"net_color_mode": 1, "net_color_mode": 1,
"opacity": { "opacity": {
"images": 0.6, "images": 0.6,
@@ -33,7 +33,6 @@
}, },
"visible_items": [ "visible_items": [
"vias", "vias",
"footprint_text",
"footprint_anchors", "footprint_anchors",
"ratsnest", "ratsnest",
"grid", "grid",
@@ -53,7 +52,7 @@
"board_outline_area", "board_outline_area",
"ly_points" "ly_points"
], ],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff", "visible_layers": "ffffffff_ffffffff_ffffffff_fffffffb",
"zone_display_mode": 0 "zone_display_mode": 0
}, },
"git": { "git": {

View File

@@ -39,9 +39,9 @@
"other_text_thickness": 0.15, "other_text_thickness": 0.15,
"other_text_upright": false, "other_text_upright": false,
"pads": { "pads": {
"drill": 0.0, "drill": 1.1,
"height": 0.310008, "height": 2.0,
"width": 0.529997 "width": 2.0
}, },
"silk_line_width": 0.1, "silk_line_width": 0.1,
"silk_text_italic": false, "silk_text_italic": false,
@@ -210,7 +210,10 @@
} }
], ],
"track_widths": [ "track_widths": [
0.0 0.0,
0.15,
0.25,
0.4
], ],
"tuning_pattern_settings": { "tuning_pattern_settings": {
"diff_pair_defaults": { "diff_pair_defaults": {
@@ -279,12 +282,7 @@
"equivalence_files": [] "equivalence_files": []
}, },
"erc": { "erc": {
"erc_exclusions": [ "erc_exclusions": [],
[
"power_pin_not_driven|3200400|1346200|270b45f3-22a3-458a-a43c-db627535af63|00000000-0000-0000-0000-000000000000|/72df0bee-e55e-4d81-989a-168573031a0a|/72df0bee-e55e-4d81-989a-168573031a0a|",
""
]
],
"meta": { "meta": {
"version": 0 "version": 0
}, },
@@ -470,9 +468,9 @@
"endpoint_off_grid": "warning", "endpoint_off_grid": "warning",
"extra_units": "error", "extra_units": "error",
"field_name_whitespace": "warning", "field_name_whitespace": "warning",
"footprint_filter": "ignore", "footprint_filter": "warning",
"footprint_link_issues": "warning", "footprint_link_issues": "warning",
"four_way_junction": "ignore", "four_way_junction": "warning",
"ground_pin_not_ground": "warning", "ground_pin_not_ground": "warning",
"hier_label_mismatch": "error", "hier_label_mismatch": "error",
"isolated_pin_label": "warning", "isolated_pin_label": "warning",
@@ -488,7 +486,7 @@
"net_not_bus_member": "warning", "net_not_bus_member": "warning",
"no_connect_connected": "warning", "no_connect_connected": "warning",
"no_connect_dangling": "warning", "no_connect_dangling": "warning",
"pin_not_connected": "error", "pin_not_connected": "warning",
"pin_not_driven": "error", "pin_not_driven": "error",
"pin_to_pin": "warning", "pin_to_pin": "warning",
"power_pin_not_driven": "error", "power_pin_not_driven": "error",
@@ -496,8 +494,8 @@
"similar_label_and_power": "warning", "similar_label_and_power": "warning",
"similar_labels": "warning", "similar_labels": "warning",
"similar_power": "warning", "similar_power": "warning",
"simulation_model_issue": "ignore", "simulation_model_issue": "warning",
"single_global_label": "ignore", "single_global_label": "warning",
"stacked_pin_name": "warning", "stacked_pin_name": "warning",
"unannotated": "error", "unannotated": "error",
"unconnected_wire_endpoint": "warning", "unconnected_wire_endpoint": "warning",
@@ -622,14 +620,50 @@
"label": "Datasheet", "label": "Datasheet",
"name": "Datasheet", "name": "Datasheet",
"show": true "show": true
},
{
"group_by": false,
"label": "Sim.Device",
"name": "Sim.Device",
"show": false
},
{
"group_by": false,
"label": "Sim.Pins",
"name": "Sim.Pins",
"show": false
},
{
"group_by": false,
"label": "Sim.Type",
"name": "Sim.Type",
"show": false
},
{
"group_by": false,
"label": "LCSC",
"name": "LCSC",
"show": false
},
{
"group_by": false,
"label": "描述",
"name": "Description",
"show": false
},
{
"group_by": false,
"label": "#",
"name": "${ITEM_NUMBER}",
"show": false
} }
], ],
"filter_string": "", "filter_string": "",
"group_symbols": true, "group_symbols": true,
"include_excluded_from_bom": true, "include_excluded_from_bom": true,
"name": "Default Editing", "name": "",
"sort_asc": true, "sort_asc": true,
"sort_field": "位号" "sort_field": "${EXCLUDE_FROM_BOM}"
}, },
"bus_aliases": {}, "bus_aliases": {},
"connection_grid_size": 50.0, "connection_grid_size": 50.0,
@@ -660,6 +694,14 @@
"meta": { "meta": {
"version": 1 "version": 1
}, },
"ngspice": {
"fix_include_paths": true,
"meta": {
"version": 0
},
"model_mode": 4,
"workbook_filename": ""
},
"page_layout_descr_file": "", "page_layout_descr_file": "",
"plot_directory": "", "plot_directory": "",
"reuse_designators": true, "reuse_designators": true,
@@ -672,7 +714,7 @@
"uuid": "72df0bee-e55e-4d81-989a-168573031a0a" "uuid": "72df0bee-e55e-4d81-989a-168573031a0a"
} }
], ],
"used_designators": "#PWR28,#PWR57-58", "used_designators": "#PWR59,U15",
"variants": [] "variants": []
}, },
"sheets": [ "sheets": [

File diff suppressed because it is too large Load Diff