Initialize repository and implement low-power sleep wakeup

This commit is contained in:
Antigravity IDE
2026-07-15 15:57:37 +08:00
commit 30a2e1680b
70 changed files with 86332 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# Keil compiler build outputs
Build/
# Keil user-specific options and GUI layouts
*.uvgui.*
*.uvguix.*
*.dep
# Temporary/Backup files
*~
*.bak
*.tmp
*.crf
*.lst
*.obj
*.lnp
*.map
*.build_log.htm
.history/
**/_history/
Hardware/.history/
session-*.md
build.log

110
App/config.h Normal file
View File

@@ -0,0 +1,110 @@
#ifndef __CONFIG_H__
#define __CONFIG_H__
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
/* ====== 系统状态 ====== */
typedef enum {
STATE_NORMAL,
STATE_ARMED,
STATE_DISARMED,
STATE_PAIR_MENU,
STATE_PAIR_WAIT,
STATE_PAIR_CONFIRM,
STATE_PAIR_SUCCESS,
STATE_PAIR_FAIL,
STATE_ALARM,
STATE_SOS_EMITTED,
STATE_SET_TIME, // 新增:时间调整编辑状态
STATE_IPC_BIND, // 新增:摄像机绑定状态
STATE_SLEEP // 新增:低功耗休眠挂起状态
} SystemState;
/* ====== 按键事件类型 ====== */
typedef enum {
KEY_EVENT_NONE,
KEY_EVENT_UP_CLICK,
KEY_EVENT_DOWN_CLICK,
KEY_EVENT_SOS_CLICK,
KEY_EVENT_UP_LONG,
KEY_EVENT_DOWN_LONG,
KEY_EVENT_SOS_LONG,
KEY_EVENT_UP_DOWN_COMB,
KEY_EVENT_CONFIRM_CLICK, // 新增:确认键短按
KEY_EVENT_CONFIRM_LONG // 新增:确认键长按
} KeyEvent;
/* ====== 事件优先级 (步骤4) ====== */
typedef enum {
EVENT_PRIORITY_LOW, // 0: 低优先级-系统内部事件
EVENT_PRIORITY_NORMAL, // 1: 正常优先级-用户交互
EVENT_PRIORITY_HIGH, // 2: 高优先级-报警事件
EVENT_PRIORITY_URGENT // 3: 紧急优先级-SOS
} EventPriority;
/* ====== 系统事件 (步骤4) ====== */
typedef struct {
KeyEvent key_event; // 按键事件类型
EventPriority priority; // 事件优先级
u32 extra_data; // 额外数据(射频地址等)
u8 extra_size; // 额外数据有效字节数(0~4)
} SystemEvent;
/* ====== 事件处理返回值 (步骤5) ====== */
typedef enum {
EVENT_HANDLED, // 事件已处理,停止分发
EVENT_IGNORED // 事件未处理,继续传递
} EventResult;
/* ====== 应用ID枚举 (步骤2) ====== */
typedef enum {
APP_ID_CLOCK, // 0: 待机时钟
APP_ID_MENU, // 1: 主菜单
APP_ID_PAIR, // 2: 配对
APP_ID_ALARM, // 3: 传感器报警
APP_ID_SOS, // 4: 主动求救
APP_ID_MAX // 5: 应用数量上限
} AppID;
/* ====== 应用类型 (步骤5) ====== */
typedef enum {
APP_TYPE_FOREGROUND, // 前台应用:独占屏幕
APP_TYPE_BACKGROUND // 后台应用:无屏幕,监听事件
} AppType;
/* ====== 应用描述符 ====== */
struct WristbandApp;
typedef struct WristbandApp WristbandApp;
typedef void (*AppStartFunc)(void);
typedef void (*AppRunFunc)(void);
typedef void (*AppCloseFunc)(void);
typedef EventResult (*AppEventFunc)(SystemEvent *evt);
struct WristbandApp {
AppID app_id;
AppType app_type;
char *app_name;
void (*OnStart)(void);
void (*onRun)(void);
void (*onClose)(void);
EventResult (*onEvent)(SystemEvent *evt);
u8 is_active; // 是否当前活跃
u8 is_running; // 是否允许运行 onRun
};
/* ====== 传感器存储结构 ====== */
typedef struct {
unsigned char is_used;
unsigned char addr[3];
unsigned char type;
unsigned char zone;
unsigned char name_gbk[16];
} Sensor_Slot;
#define MAIN_Fosc 24000000UL
#define CLONED_ADDR 0x37A86UL
#endif

17
App/isr_jump.asm Normal file
View File

@@ -0,0 +1,17 @@
; 端口中断 0 / 2 的硬件中转绝对定位汇编 (A251)
; 目的Keil C251 编译器只支持 interrupt 0~31
; 而 STC32G 的端口中断硬件向量号为 37(Port0) 和 39(Port2)
; 超出范围无法用 C 直接声明。
; 本文件在绝对地址处放置 LJMP 指令,将硬件向量重定向到
; Keil 可以生成的 13 号和 15 号中断服务函数入口。
NAME ISR_JUMP
; 端口 0 中断 (向量 37, 硬件绝对地址 00012BH) 跳转到 13 号中断 (地址 00006BH)
CSEG AT 00012BH
LJMP 00006BH
; 端口 2 中断 (向量 39, 硬件绝对地址 00013BH) 跳转到 15 号中断 (地址 00007BH)
CSEG AT 00013BH
LJMP 00007BH
END

2242
App/main.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
# 总体需求 (Docs/10_requirements-overview/main.md)
本项目主要实现随身手环终端的底层驱动、核心业务管理以及低功耗待机逻辑。
## 1. 项目系统拓扑与边界
* **业务流向**:手环作为智能家居安防的便携式告警与求救终端,与无线传感器以及 IPC 摄像头(网络摄像机)构成以下星型网络架构。
* **设计边界**:本项目**仅开发手环端的软件代码**,传感器和 IPC 摄像头侧的软件仅作为对接调试的外界仿真源。
```mermaid
graph TD
Sensor[433MHz 传感器] -->|433MHz 报警信号| Wristband[随身手环]
Wristband -->|433MHz SOS 呼救信号| IPC[IPC 摄像头 (仿真对接)]
```
---
## 2. 总体功能需求
手环系统应具备以下六大核心总体功能:
### 2.1 显示时间日期与状态栏
* 手环应支持软时钟走时,在主页面清晰直观地显示当前的小时、分钟、星期及日期信息。
* 主页面状态栏应实时指示当前电池电量、USB 充电状态,以及当前的系统安防运行状态(布防/撤防)。
### 2.2 系统安防布防与撤防控制
* 系统支持两种安防警戒模式:
* **布防状态**:对所有类型已配对的传感器报警信号(如门磁、红外等)均执行本地声光振动告警。
* **撤防状态**:对普通盗防类传感器信号予以忽略,仅对危及安全和紧急求救的传感器信号进行报警响应。
### 2.3 传感器无线对码配对
* 手环能捕获周围 433MHz 传感器发出的 EV1527 对码射频包,将其地址码与类型绑定记录在非易失存储区(掉电不丢失)。
* 手环支持多达 16 个传感器防区槽位的配对与法语前缀加数字序号的命名保存。
### 2.4 主动呼救与 IPC 绑定配对
* **主动求救**:当遇到紧急情况时,用户可一键快速触发主动 SOS 求救广播,向外以 2 秒为周期循环发射带有手环出厂固定唯一 ID 的 433MHz 载波信号。
* **IPC 绑定**:手环提供主动向 IPC发射对码信号的功能以便 IPC 摄像头将该手环地址绑定至特定用户,识别求救来源。
### 2.5 多维告警响应反馈
* 手环根据捕获到报警传感器的类型,执行差异化的硬件声光、马达振动和屏幕图标文字反馈,提供直观的物理告警。
### 2.6 低功耗待机与中断秒级唤醒
* 手环必须具备优秀的低功耗休眠机制,在连续无按键操作 10 秒后自动关闭屏幕并切断屏幕供电电路使单片机进入深度休眠Power-Down 挂起状态)。
* 手环在休眠状态下,支持任何物理按键按下瞬间被唤醒,以及接收到 433MHz 无线射频载波信号瞬间被唤醒,唤醒后应在极短时间内恢复系统运行与屏幕显示。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->

View File

@@ -0,0 +1,67 @@
# 细化需求 (Docs/20_requirements-detail/main.md)
本章节定义手环终端的物理按键交互、报警矩阵(可编程 RGB 灯颜色与振动)以及防区划分详细需求。
## 1. 物理按键与菜单交互定义
手环共配备 **5 个物理按键**:侧边 3 个(▲ 上移、▼ 下移、■ 确认/CONFIRM上部 2 个(相同的 SOS1、SOS2具备完全一致的并联电气逻辑
### 1.1 页面与状态定义
* **时钟待机状态 (STATE_CLOCK)**:正常显示时间、日期、电量及布撤防状态(大字版)。
* **低功耗休眠状态 (STATE_SLEEP)**:无按键操作 10 秒后自动进入。屏幕完全关闭,拉低 `SGM_CTRL = 0` 关闭 SGM3833 供电,单片机进入 Power-Down 挂起状态。
* **主选择菜单 (STATE_MENU)**:在 `STATE_CLOCK` 下长按 **■ 确认键** 3 秒进入。单屏只显示 1 个功能,通过 ▲/▼ 键来回翻页CONFIRM 键确认进入:
1. `1. DATE/HEURE` (进入时间日期修改界面 `STATE_SET_TIME`)
2. `2. APPAIRAGE` (进入对码配对搜索界面 `STATE_PAIR_SEARCH`)
3. `3. ARM/DISARM` (进入系统布撤防手动设定界面 `STATE_ARM_DISARM_SELECT`)
4. `4. LIAISON IPC` (进入 IPC 摄像头绑定发送界面 `STATE_IPC_BIND`)
### 1.2 按键动作状态机映射
| 当前状态 | ▲ 上移键动作 | ▼ 下移键动作 | ■ 确认键 (CONFIRM) 动作 | SOS1 / SOS2 按键动作 |
| :--- | :--- | :--- | :--- | :--- |
| **时钟待机 (STATE_CLOCK)** | 无动作 | 无动作 | 长按 3 秒:进入主选择菜单 | 短按/长按:立即跳转至发送 SOS 报警 (`STATE_SOS_EMITTED`) |
| **低功耗休眠 (STATE_SLEEP)** | 短按:唤醒并恢复至时钟待机 | 短按:唤醒并恢复至时钟待机 | 短按:唤醒并恢复至时钟待机 | 短按/长按:唤醒系统并立即触发 SOS 报警 |
| **主选择菜单 (STATE_MENU)** | 短按:上一个菜单条目(切换大图标与大字) | 短按:下一个菜单条目(切换大图标与大字) | 短按:确认进入选中分支;长按 3 秒:退出菜单返回时钟 | 短按/长按:立即跳转至发送 SOS 报警 |
| **时间修改 (STATE_SET_TIME)** | 短按:增加当前选中数值 | 短按:减少当前选中数值 | 短按:保存并切换到下一个修改参数;长按 3 秒:放弃修改返回时钟 | 短按/长按:立即跳转至发送 SOS 报警 |
| **对码配对 (STATE_PAIR_SEARCH)** | 无动作 | 长按 2 秒:放弃对码返回主菜单 | 无动作 | 短按/长按:立即跳转至发送 SOS 报警 |
| **对码确认微调 (STATE_PAIR_CONFIRM)** | 短按:自增后缀序号 (01->99) | 短按:自减后缀序号 (99->01) | 短按:确认保存配置并存入 Flash返回时钟长按 3 秒:放弃保存返回时钟 | 短按/长按:立即跳转至发送 SOS 报警 |
| **布撤防手动设定 (STATE_ARM_DISARM_SELECT)** | 短按:切换布撤防高亮项 | 短按:切换布撤防高亮项 | 短按:保存选定状态返回时钟 | 短按/长按:立即跳转至发送 SOS 报警 |
| **IPC 绑定发送 (STATE_IPC_BIND)** | 无动作 | 长按 2 秒:放弃绑定返回时钟 | 短按:立刻再次发射绑定射频包;长按 3 秒:退出返回时钟 | 短按/长按:立即跳转至发送 SOS 报警 |
| **警报触发状态 (STATE_ALARMING)** | 短按:解除报警并退回时钟 | 短按:解除报警并退回时钟 | 短按:解除报警并退回时钟 | 短按/长按:解除报警并退回时钟 |
| **本地主动 SOS 发送 (STATE_SOS_EMITTED)** | 短按:解除 SOS 发送退回时钟 | 短按:解除 SOS 发送退回时钟 | 短按:解除 SOS 发送退回时钟 | 短按/长按:解除 SOS 发送退回时钟 |
> [!NOTE]
> 1. **双 SOS 按键冗余**:上部的两个 SOS 键在电路与软件上完全等价。按下其中任意一个按键,均会触发相同的 SOS 信号采集与求救动作。
> 2. **一键报警优先级**:除了在报警状态和本地 SOS 发送状态下按 SOS 键为解除报警外,在其它任意状态下,短按或长按 SOS 键均拥有最高优先级,立刻强行中止当前业务并跳转至 SOS 呼救状态。
> 3. **对码自动类型识别与后缀选择**
> * 手环在接收到 433MHz 射频对码信号时,**自动根据数据码Data Code识别传感器类型**,并直接跳转到对码微调页面。
> * 在微调确认页面中,传感器类型和图标以大字只读显示,用户仅需通过 ▲/▼ 键增减微调该传感器的超大数字后缀序号01~99。按 CONFIRM 键即可一键保存并写入 Flash 数据库。
> 4. **低功耗休眠与唤醒**:在待机、菜单或非报警页面下,若 **10秒内无任何按键操作**,系统将自动关闭 OLED 屏幕、切断 SGM3833 PMIC 电源,并将 MCU 挂起进入 Power-Down 停机模式。任何按键的按下或射频信号的接收都会通过中断将 CPU 瞬间唤醒。
## 2. 传感器对码与报警映射矩阵
手环接收到 433MHz 传感器射频数据后,通过匹配预设数据码(高 4 位为类型)和地址码,根据下表驱动多色 LED、马达振动波形并更新 LCD 显示。为了方便视障及老年用户识别,显示均采用超大图标与闪烁红边框。
| 传感器类型 | 数据码标识 | 预设名前缀 | LED 颜色 | 默认防区类型 | 马达振动波形 (周期 5 秒) |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **🚪 门磁/窗磁** | `0x01` | `PORTE` | ● 红色 | ZONE 1 (普通) | 1 次短振 300ms |
| **👤 PIR 人体红外** | `0x02` | `PIR` | ● 橙色 | ZONE 1 (普通) | 2 次短振 300ms (间隔 200ms) |
| **🔥 烟雾传感器** | `0x03` | `FUMEE` | ● 绿色 | ZONE 0 (24h) | 3 次短振 200ms (间隔 150ms) |
| **🆘 紧急按钮** | `0x04` | `URGENCE` | ● 蓝色 | ZONE 0 (24h) | 1 次长振 800ms |
| **💨 气体泄漏** | `0x05` | `GAZ` | ● 紫色 | ZONE 0 (24h) | 4 次短振 200ms (间隔 100ms) |
| **💧 水浸传感器** | `0x06` | `INOND.` | ● 黄色 | ZONE 0 (24h) | 2 次长振 600ms (间隔 300ms) |
| **📳 振动传感器** | `0x07` | `VIBRA.` | ● 青色 | ZONE 1 (普通) | 连续3次 150ms (间隔 100ms) |
| **🚨 本地主动 SOS** | 手环触发 | `SOS` | ● 红色闪烁 | 独立运行 | 持续长振 (振 1000ms, 停 200ms) |
> [!IMPORTANT]
> **手环主动发射 SOS 射频数据格式**
> 当触发“本地主动 SOS”时手环通过内置发射芯片以 **2 秒** 为周期循环发射 EV1527 协议数据:
> * **源地址**:手环的 **20位出厂固定唯一 ID (Factory ID)**。
> * **数据码**:固定为 **`0x08`** (即 `1000`)。
> * **目的**:使 IPC 摄像头能接收解码,将其与配对列表中该手环 of Factory ID 匹对,从而识别求救来源。
## 3. ZONE 防区划分
手环支持将配对的传感器划分至不同的防区:
* **ZONE 0 (永久防区)**:烟雾、气体、水浸等危及安全的传感器。不论手环是否处于布防状态,一旦捕获信号即刻报警。
* **ZONE 1~N (可撤布防区)**:门磁、红外等防盗传感器。只有当手环接收到布防广播(系统进入 ARMED时才会报警在撤防DISARMED状态下被动忽略。

View File

@@ -0,0 +1,362 @@
# 架构设计说明书 (Docs/30_architecture/main.md)
本章节定义手环的系统软硬件架构设计,包括引脚分配、基于事件驱动的 APP 框架微内核设计、事件生命周期流转机制以及持久化数据存储结构。
---
## 1. 硬件原理图与引脚分配架构
手环的完整电路连接设计参见以下 KiCad 硬件工程文件:
* **硬件原理图**[text.kicad_sch](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_sch)
* **PCB 布局图**[text.kicad_pcb](file:///c:/workfile/105/stc32g12k128/Hardware/text.kicad_pcb)
主控芯片采用 **STC32G12K128**(工作频率 24.0 MHz单周期 8051 内核32位乘除法器
显示端采用 **Truly 0.95寸 AMOLED 屏幕**,物理分辨率为 **120x240 像素**,控制芯片为 **RM69310**
### 1.1 GPIO 物理映射表
| 芯片引脚号 | 引脚名称 | 逻辑网络标号 | IO 模式 | 连接外设与功能描述 |
| :--- | :--- | :--- | :--- | :--- |
| **Pin 1** | `P1.0` | `DIS_RST` | 推挽输出 | Truly AMOLED 屏幕复位控制脚 (RESET) |
| **Pin 2** | `P1.1` | `SPI_DCX` | 推挽输出 | Truly AMOLED 屏幕 SPI 数据/指令选择脚 (D/C) |
| **Pin 3** | `P1.4` | `SPI_SDI` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输入 (MOSI/SDA) |
| **Pin 4** | `P1.5` |`SPI_CLK` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行时钟信号 (SCL) |
| **Pin 5** | `P1.6` | `SPI_CS` | 推挽输出 | Truly AMOLED 屏幕 SPI 片选信号 (CS) |
| **Pin 6** | `P1.7` | `SPI_SDO` | 推挽输出 | Truly AMOLED 屏幕 SPI 串行数据输出 (MISO) |
| **Pin 10** | `Vcc` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
| **Pin 11** | `VREF+` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
| **Pin 12** | `GND` | `+3.3V` | 电源 | 屏幕/系统 3.3V 逻辑供电 |
| **Pin 13** |`P3.1` |`RXD` | 高阻输入 | 串口 1 (UART1) 接收脚 |
| **Pin 14** | `P3.0` | `TXD` | 推挽输出 | 串口 1 (UART1) 发送脚 |
| **Pin 19** | `P3.6` | `RF_TX_DAT` | 推挽输出 | LR690L 射频芯片发射调制数据输入引脚 |
| **Pin 20** | `P3.7` | `RF_RX` | 推挽输出 | LR690L 射频芯片接收使能控制引脚 |
| **Pin 21** | `P2.0` | `RF_TX` | 推挽输出 | LT4455 射频芯片发射使能控制引脚 |
| **Pin 22** | `P2.1` | `RF_RX_DATA` | 输入中断 | ATR5179 射频开关控制芯片
| **Pin 23** | `P2.2` |`SHUT` | LR0L射频芯片关闭信号
| **Pin 24** | `P2.3` | `LED_RGB` | SPI MOSI | WS2812B 级联双幻彩灯珠 DIN 控制信号 |
| **Pin 26** | `P2.5` | `MOTOR_PWM` | 推挽输出 | 连接振动马达驱动电路。高电平震动,低电平静止 |
| **Pin 27** | `P2.6` | `KEY_SOS` | 高阻输入 + 上拉 | 上部并联 SOS 求救按键,按下为低电平 |
| **Pin 28** | `P2.7` | `DET` | 高阻输入 | USB 插入充电检测引脚,高电平代表正在充电 |
| **Pin 29** | `P0.0` | `BAT_ADC` | ADC 输入 | 电池电压采集 |
| **Pin 30** | `P0.1` | `KEY_UP` | 高阻输入 + 上拉 | 侧边按键 ▲ (上移),按下为低电平 |
| **Pin 31** | `P0.2` | `KEY_CONFIRM` | 高阻输入 + 上拉 | 侧边按键 ■ (确认/菜单),按下为低电平 |
| **Pin 32** | `P0.3` | `KEY_DOWN` | 高阻输入 + 上拉 | 侧边按键 ▼ (下移),按下为低电平 |
---
## 2. 软件事件驱动 APP 框架 (Event-Driven App-Framework)
手环软件系统摒弃了传统的单线程前后台大循环设计,采用分层明确的 **事件驱动式 APP 框架** 架构。
### 2.1 五层软件架构模型
```mermaid
graph BT
%% 自下而上的 5 层软件依赖拓扑
subgraph Layer5 ["5. 应用业务层 Applications"]
FG_Apps["前台应用 (Clock/Menu/Pair/Alarm/SOS)"]
BG_Apps["后台应用 (射频监测)"]
end
subgraph Layer4 ["4. 应用管理层 APP Management"]
AppManager["APP 管理器"]
end
subgraph Layer3 ["3. 事件管理与总线层 Event Layer"]
EventQueue["事件环形缓冲区"]
end
subgraph Layer2 ["2. 中断与驱动层 Drivers"]
ExtInt["外部中断 (KEY/RF)"]
TimeInt["时钟中断 (Timer1 1ms)"]
LCD_Drv["LCD 屏幕绘制"]
ADC_Drv["ADC 电量采集"]
end
subgraph Layer1 ["1. 硬件实体层 STC32 HW"]
MCU["STC32G 单片机核心"]
end
%% 强制同一层节点水平对齐
ExtInt --- TimeInt
TimeInt --- LCD_Drv
LCD_Drv --- ADC_Drv
%% 事件产生:中断产生事件,推入队列
ExtInt -->|普通事件入队| EventQueue
ExtInt -->|紧急事件插队| EventQueue
TimeInt -->|定时事件入队| EventQueue
%% 事件队列出队
EventQueue -->|出队| AppManager
%% APP管理器分发事件给前台应用
AppManager -->|优先发给前台| FG_Apps
%% 前台应用:处理事件,调用驱动
FG_Apps -->|不处理| BG_Apps
FG_Apps -->|调用显示| LCD_Drv
FG_Apps -->|调用电量采集| ADC_Drv
%% 后台应用:前台不处理时接收事件
BG_Apps -->|不处理| Discard["事件丢弃"]
%% 硬件驱动交互
MCU ===> ExtInt
MCU ===> TimeInt
MCU ===> LCD_Drv
MCU ===> ADC_Drv
```
### 2.2 应用类型划分
系统将应用分为两大类:
| 应用类型 | 标识 | 特性 | 典型应用 |
| :--- | :--- | :--- | :--- |
| **前台应用** | `APP_TYPE_FOREGROUND` | 独占屏幕显示,响应用户交互,一次只有一个活跃,优先接收事件 | 时钟、菜单、配对、报警、SOS |
| **后台应用** | `APP_TYPE_BACKGROUND` | 无屏幕显示,持续运行监听特定事件,前台不处理时接收事件 | 射频监测 |
### 2.3 事件优先级体系
系统定义四级事件优先级,通过队列插队机制实现优先处理:
| 优先级 | 标识 | 描述 | 处理方式 | 典型事件 |
| :--- | :--- | :--- | :--- | :--- |
| **低** | `EVENT_PRIORITY_LOW` | 系统内部事件,如定时更新 | 入队等待处理 | 时钟分钟更新 |
| **正常** | `EVENT_PRIORITY_NORMAL` | 普通用户交互事件 | 入队等待处理 | 按键单击、菜单导航 |
| **高** | `EVENT_PRIORITY_HIGH` | 报警相关事件 | 插队到队首 | 传感器入侵警报 |
| **紧急** | `EVENT_PRIORITY_URGENT` | 最高优先级事件 | 插队到队首 | SOS求救、紧急按键 |
---
## 3. 应用生命周期机制 (APP Lifecycle)
手环中运行的各个子功能页面(如时钟、菜单、对码、布防)均被抽象为独立的 **App 实例**。每个 App 必须注册并实现以下回调接口,这些回调由 APP 管理器在应用状态变化时自动调用:
### 3.1 应用描述符结构
```c
typedef struct {
AppID app_id; // 应用程序 ID (APP_ID_CLOCK/MENU/PAIR/ALARM/SOS/MONITOR)
char *app_name; // 应用名称(用于调试日志)
AppType app_type; // 应用类型 (APP_TYPE_FOREGROUND / APP_TYPE_BACKGROUND)
AppStartFunc OnStart; // 启动回调:应用切入前台时调用,加载资源、初始化显示
AppRunFunc onRun; // 循环回调:主循环中持续调用,执行非阻塞业务逻辑
AppCloseFunc onClose; // 关闭回调:应用退出前台时调用,保存状态、清理外设
AppEventFunc onEvent; // 事件回调:接收到事件时调用,处理用户交互或系统事件
bit is_active; // 当前是否活跃(前台应用是否为当前运行的应用)
bit is_running; // 应用是否处于运行状态用于CPU占用控制
} WristbandApp;
```
### 3.2 回调接口说明
| 回调函数 | 调用时机 | 职责描述 |
| :--- | :--- | :--- |
| `OnStart()` | 应用被启动时 | 初始化显示界面、加载配置数据、启动定时器等 |
| `onRun()` | 主循环中持续调用 | 执行非阻塞的周期性任务(如动画刷新、超时检测),应用自主控制是否需要运行 |
| `onClose()` | 应用被关闭时 | 保存用户配置、停止定时器、关闭外设、清理资源 |
| `onEvent(evt)` | 接收到事件时 | 处理按键、射频等事件,返回 `EVENT_HANDLED``EVENT_IGNORED` |
### 3.3 事件处理返回值
```c
typedef enum {
EVENT_HANDLED, // 事件已处理,停止分发
EVENT_IGNORED // 事件未处理,继续传递给后台应用
} EventResult;
```
### 3.4 活跃应用切换逻辑 (APP Switch)
1. 当调用 `AppManager_StartApp(target_app_id)` 时:
* 触发当前活跃应用的 `onClose()` 接口,执行现场保存及关屏/停振等外设清理。
* 更新活跃应用指针 `active_app` 指向新 App。
* 触发新活跃应用的 `OnStart()` 接口,执行初始画面的绘制。
2. 在主循环中,系统不断执行当前活跃应用的 `onRun()` 接口,以维护非阻塞的页面动画或等待超时逻辑。
### 3.5 CPU 占用控制机制
为防止单个应用长时间占用 CPU 导致系统响应迟钝,框架引入 **onRun 执行时间限制**
* **时间阈值**`APP_ONRUN_MAX_TICKS`(默认 5ms可配置
* **监控机制**:每次调用 `onRun()` 前记录当前 `ms_tick`,返回后计算执行耗时
* **超时处理**:若执行时间超过阈值,将应用的 `is_running` 标记为 0暂停其 `onRun()` 调用,允许下一次循环恢复
```
onRun() 执行流程:
1. 记录开始时间 start_tick = ms_tick
2. 执行应用的 onRun() 回调
3. 计算耗时 elapsed = ms_tick - start_tick
4. 若 elapsed > APP_ONRUN_MAX_TICKS → 设置 is_running = 0暂停
5. 否则保持 is_running = 1继续
```
---
## 4. 事件采集、缓冲与流转机制 (Event Bus)
事件总线层是实现驱动层与应用层解耦的核心,支持多级优先级、队列插队和事件丢弃机制。
### 4.1 事件流转完整架构
```mermaid
graph TD
%% 事件产生层
HW_Sig["物理引脚状态跳变"] -->|电平变化中断| Driver["驱动中断 ISR"]
Driver -->|消抖/解调| Packer["封装统一事件 SystemEvent"]
%% 优先级判断与队列操作
Packer -->|判断优先级| PriorityJudge{"优先级判断"}
%% 队列操作
PriorityJudge -->|HIGH/URGENT| QueueFront["EventQueue_InsertFront()"]
PriorityJudge -->|LOW/NORMAL| QueueBack["EventQueue_Push()"]
QueueFront --> Queue["EventQueue 环形缓冲区 (深度8)"]
QueueBack --> Queue
%% 主循环轮询
Queue -->|主循环轮询| MainLoop["MainLoop 轮询读取"]
MainLoop -->|事件分发| AppMgr["APP 管理器"]
%% 应用层处理
AppMgr -->|步骤1: 优先发给前台应用| FG_App["前台应用 active_app"]
FG_App -->|返回 EVENT_IGNORED| BG_App["步骤2: 后台应用"]
FG_App -->|返回 EVENT_HANDLED| Done["事件处理完成"]
BG_App -->|返回 EVENT_IGNORED| Discard["步骤3: 事件丢弃"]
BG_App -->|返回 EVENT_HANDLED| Done
```
### 4.2 事件分发规则
事件在应用层的分发遵循以下顺序:
```
事件分发流程:
┌─────────────────────────────────────────────────────────────┐
│ 1. 分发给当前活跃的前台应用 (active_app->onEvent()) │
│ ├── 返回 EVENT_HANDLED → 事件处理完成,结束分发 │
│ └── 返回 EVENT_IGNORED → 继续下一步 │
├─────────────────────────────────────────────────────────────┤
│ 2. 分发给后台应用 (遍历所有后台应用) │
│ ├── 逐个调用 bg_app->onEvent() │
│ ├── 返回 EVENT_HANDLED → 事件处理完成,结束分发 │
│ └── 返回 EVENT_IGNORED → 继续下一步 │
├─────────────────────────────────────────────────────────────┤
│ 3. 事件被丢弃(前台和后台应用均不处理) │
└─────────────────────────────────────────────────────────────┘
```
### 4.3 扩展事件结构体
```c
typedef struct {
KeyEvent key_event; // 原始按键事件
EventPriority priority; // 事件优先级 (LOW/NORMAL/HIGH/URGENT)
u32 extra_data; // 额外数据(如射频地址、时间信息等)
u8 extra_size; // 额外数据有效字节数
} SystemEvent;
```
### 4.4 环形事件缓冲区 (EventQueue)
为防止高速按键或多输入源并发导致事件丢失,系统建立一个深度为 **8 级** 的环形事件队列。事件层仅通过队列完成所有事件处理,提供三个核心函数:
* **队列深度**`EVENT_QUEUE_DEPTH = 8`
* **入队操作**`EventQueue_Push(evt)` — 将事件追加到队尾,非阻塞,队列满时返回 0丢弃事件
* **出队操作**`EventQueue_Pop()` — 从队首取出事件,队空时返回空事件
* **插队操作**`EventQueue_InsertFront(evt)` — 将事件插入到队首,实现高优先级事件优先处理,队列满时返回 0丢弃事件
* **中断保护**:所有队列操作时禁用全局中断 (`EA=0`),操作完成后恢复 (`EA=1`),确保原子性
#### 队列内部结构
```c
static SystemEvent event_queue[EVENT_QUEUE_DEPTH]; // 环形缓冲区存储
static u8 queue_head = 0; // 读指针:指向即将读取的位置
static u8 queue_tail = 0; // 写指针:指向即将写入的位置
static u8 queue_count = 0; // 当前队列中的事件数量
```
#### 队列操作流程
```
入队 (Push) - 追加到队尾:
1. 检查队列是否已满 → 满则返回失败
2. 禁用中断 (EA=0)
3. event_queue[queue_tail] = evt
4. queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH
5. queue_count++
6. 恢复中断 (EA=1)
7. 返回成功
出队 (Pop) - 从队首取出:
1. 检查队列是否为空 → 空则返回空事件
2. 禁用中断 (EA=0)
3. evt = event_queue[queue_head]
4. queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH
5. queue_count--
6. 恢复中断 (EA=1)
7. 返回事件
插队 (InsertFront) - 插入到队首:
1. 检查队列是否已满 → 满则返回失败
2. 禁用中断 (EA=0)
3. queue_head = (queue_head - 1 + EVENT_QUEUE_DEPTH) % EVENT_QUEUE_DEPTH
4. event_queue[queue_head] = evt
5. queue_count++
6. 恢复中断 (EA=1)
7. 返回成功
```
### 4.5 事件产生机制
#### 按键事件产生
* Timer1 定时器每 10ms 中断扫描物理引脚
* 判定有效按键后根据优先级执行 `EventQueue_Push()`(低/正常优先级)或 `EventQueue_InsertFront()`(高/紧急优先级如SOS事件
#### RF 事件产生
* LR690L 接收中断触发,解码线程还原 24 位载波包
* 识别到配对传感器动作或入侵警报,根据优先级推送对应事件(配对事件 Push警报事件 InsertFront
#### 系统事件产生
* 定时器分钟变化时推送时钟更新事件低优先级Push入队
---
## 5. 持久化配置数据库架构
手环配对防区数据存储在 STC32G 内置 Flash 的第 254 扇区:
* **物理存储地址**`0xFE0000` (末尾 512 字节)。
* **数据库容量**:共 16 组传感器配置槽位。
* **存储结构定义**
```c
typedef struct {
u8 flags_suffix; // 属性字节Bit 7 代表有效标志 (1: 有效, 0: 空闲)Bit 0~6 代表法语防区名称的后缀序号 (01~99)
u8 addr[3]; // 24位射频物理地址 (EV1527 地址 + 4位状态数据码)
} Sensor_Slot;
```
---
## 6. 核心业务事件输入输出 (I/O) 架构明细表
手环核心事件在整个框架中的端到端流动及消耗链路如下:
| 事件名称 | 输入源 (触发条件与参数) | 事件优先级 | 流转方式 | 活跃 APP 消费处理 (逻辑响应与动作) |
| :--- | :--- | :--- | :--- | :--- |
| **按键操作事件**<br>`KEY_EVENT` | **源**Pin 30/31/32 (Port 0) 下降沿中断。<br>**参数**:▲/▼/■ (短按/长按)。 | NORMAL | **入队**`EventQueue_Push` 压入队列 | **待机应用**:长按确认键 3 秒,调用 `StartApp(APP_MENU)`<br>**菜单应用**:短按 ▲/▼ 键移动高亮焦点;短按确认键调用 `StartApp` 进入对应子设置应用。 |
| **主动求救事件**<br>`KEY_EVENT_SOS_CLICK` / `KEY_EVENT_SOS_LONG` | **源**Pin 27 (`P2.6`) SOS 物理键拉低。<br>**参数**:短按触发 `KEY_EVENT_SOS_CLICK`,长按触发 `KEY_EVENT_SOS_LONG`。 | **URGENT** | **插队**`EventQueue_InsertFront` 插入到队首优先处理 | **SOS报警应用**<br>1. 显示OLED 屏亮起 `SOS EMIS`<br>2. 灯效WS2812B 红色交替闪烁 (50ms)<br>3. 马达:开启间歇长振 (振1000ms停200ms)<br>4. 射频启动发送线程周期性2s发射手环 Factory ID 及 `0x08` (SOS) 包。 |
| **无线对码捕获事件**<br>`RF_PAIR_RECV_EVENT` | **源**Pin 22 (`P2.1`) 沿变化中断。<br>**参数**EV1527 24位对码信号。 | NORMAL | **入队**`EventQueue_Push` 压入队列 | **配对应用**<br>1. 状态:跳转进入 `STATE_PAIR_CONFIRM` (微调保存页)<br>2. 显示:只读显示自动识别传感器类型及大图标;<br>3. 马达:短振 300ms 提醒成功;<br>4. 交互:通过按键微调序号并写入 Flash 数据库。 |
| **传感器入侵警报事件**<br>`RF_ALARM_RECV_EVENT` | **源**Pin 22 (`P2.1`) 沿变化中断。<br>**参数**:传感器物理地址 + 4位状态码。 | HIGH | **插队**`EventQueue_InsertFront` 插入到队首优先处理 | **警报应用**:若该防区为 ZONE 0 (24h) 或处于已布防APP管理器启动 `StartApp(APP_ALARM)`<br>1. 显示OLED 刷红边框与对应大图标,显示入侵警报文字;<br>2. 灯效:同步闪烁该防区专属色彩;<br>3. 马达:输出对应防区的特定异步振动波形。 |
| **系统自动休眠事件**<br>`AUTO_SLEEP_EVENT` | **源**:软件时钟计时 `inactivity_timer >= 1000` (10s)。 | LOW | **入队**`EventQueue_Push` 压入队列 | **所有非警报应用**:执行 `onClose()` → 调用 PM 模块拉低 `SHUT=0`Pin 23彻底切断屏幕供电 → 配置 GPIO 变化中断和无线唤醒源 → 执行 `PCON|=0x02` 进入停机休眠。 |
| **中断唤醒系统事件**<br>`SYSTEM_WAKEUP_EVENT` | **源**GPIO 按键下降沿或无线信号变化触发硬件唤醒。 | HIGH | **插队**`EventQueue_InsertFront` 插入到队首优先处理 | **系统底层**:优先拉高 `SHUT=1` 重新开启 SGM3833 供电 → **阻塞强延时 50ms 避开浪涌** → 完整重新配置 RM69310 屏幕控制器 → 调用 `StartApp(APP_CLOCK)` 恢复时钟显示状态。 |
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Updated with foreground/background app separation, event priority system, queue front-insert, and CPU occupancy control -->

File diff suppressed because it is too large Load Diff

131
Docs/40_ui-design/main.md Normal file
View File

@@ -0,0 +1,131 @@
# UI 设计 (Docs/40_ui-design/main.md)
本章节定义手环 120x240 Truly AMOLED 屏幕的画面布局、各页面坐标位置、中文字体/ASCII 字符对齐基线以及局部刷新逻辑。
针对**特殊人群(如视力障碍者、老年人)**的需求,本界面采取了**大字版与大图标无障碍Accessibility / A11y设计系统**。
---
## 1. 无障碍A11y大字设计系统规范
为确保特殊人群能清晰阅读,且文本在 120 像素宽的屏幕内**不超出边界**,设计遵循以下准则:
1. **文本宽度与换行原则**
* 在 120 像素宽的屏幕上,单行 16x32px 大字符最多显示 **7 个字符**
* 如果提示语较长(如 `APPEL EN COURS`),必须将其**缩短或分成多行显示**(如第一行 `APPEL`,第二行 `SOS`)。
* 菜单项名称进行合理缩短,如 `1. DATE/HEURE` 缩短为 `1. HORLOGE``2. APPAIRAGE` 缩短为 `2. APPAIR.`,以保证在 16x32px 字体下不溢出。
2. **最小字号限制**:取消所有 8x16px 或更小的微缩文本。基础信息字号不小于 **16x32px**
3. **核心电量与状态图标放大**
* 为了使用户能清晰看清剩余电量,电池电量数字放大为 **16x32px** 粗体。
* 电池图标放大为 **24x12px** 粗边框,放置在状态栏右上角。
* 状态锁图标放大为 **32x32px**,放置在状态栏左上角。
4. **超大核心数据**:时间数字、设定参数、传感器序号等使用 **32x64px**(超大点阵字模)呈现。
5. **大图标辅助**:所有页面均配备 **48x48px 或 64x64px 的超大单色位图图标**,结合高对比度色彩(红/绿/黄/黑),方便用户一眼识别功能。
6. **单页单焦点排版**:避免多行数据堆叠。菜单、时间设定、对码等页面在屏幕中**每次只显示当前编辑或选中的一个条目**,把所有的屏幕空间留给最大化的字体和图标。
7. **坐标偏移修正** Truly AMOLED 屏幕写屏窗口渲染必须应用 **X 轴偏移量 +4Y 轴偏移量 +0**。即:`LCD_SetWindow(x1 + 4, x2 + 4, y1, y2)`
---
## 2. 页面布局与坐标坐标规范
### 2.1 时钟待机界面 (STATE_CLOCK)
时钟页面作为常态显示,包括状态栏、数字时钟和日期行。
```
+-----------------------------------------+
| [🔒 32x32] 🔋 85%+ | <- 状态栏 (Y=15~35) (电池图标与16x32px电量)
| |
| |
| 10:35 | <- 居中超大时间 (Y=70~134) (32x64px字模)
| |
| |
| VEN. 07-10 | <- 底部大日期 (Y=180~212) (16x32px字模)
| |
+-----------------------------------------+
```
* **状态栏电量显示(极重要)**
* **电池图标** 放大绘制在 `X=86, Y=19, W=24, H=13`
* **电量百分比** 采用 `16x32px` 字模,右对齐。充电时显示 `+` 号。
* **安防状态显示**
* 状态栏左侧绘制 **32x32px** 的超大布撤防锁图标:红色闭锁 `🔒`(布防),绿色开锁 `🔓`(撤防),不再显示小文字,使用户看图即懂。
### 2.2 主选择菜单页面 (STATE_MENU)
由时钟待机长按 CONFIRM 键 3 秒进入。显示四个功能条目,供滚动选择:
```
+-----------------------------------------+
| - MENU - | <- 标题 (Y=20) (16x32px)
| |
| [⏰ 48x48] | <- 选中项大图标 (Y=60~108)
| |
| 1. HORLOGE | <- 选中项大字文本 (Y=150~182) (16x32px)
| |
| ▲ / ▼ | <- 翻页提示符 (Y=205) (16x32px)
+-----------------------------------------+
```
* 屏幕中央显示当前选中功能的 **48x48px** 图标,下方以 **16x32px** 粗体显示功能名称。
* 用户按 ▲ 或 ▼ 键时,页面以幻灯片形式切换:
1. `1. HORLOGE` (对应 ⏰ 时钟图标)
2. `2. APPAIR.` (对应 📡 雷达对码图标)
3. `3. ALARME` (对应 🔒 防区盾牌图标)
4. `4. LIAISON` (对应 📷 摄像头图标)
* 短按 CONFIRM 键确认进入当前显示的功能。
### 2.3 功能设定与子界面设计
#### 2.3.1 时间日期修改界面 (STATE_SET_TIME)
为了避免小字,时间设置每次只微调一个数值:
* **页面指示**:顶部大字显示当前修改的字段,例如 `REGLER` (16x32px)。
* **中央数值**:正中央显示正在修改的两位数值(如小时 `10`),字号采用 **32x64px** 超大号。数值上下有明显的闪烁三角箭头指示。
* **交互**:按 ▲ / ▼ 调整该值。短按 CONFIRM 保存该值并切入下一项(分 $
ightarrow$ 秒 $
ightarrow$ 年 $
ightarrow$ 月 $
ightarrow$ 日)。
#### 2.3.2 传感器对码等待与配对界面 (STATE_PAIR_SEARCH)
* **顶部标题**:显示 `APPAIRAGE` (16x32px)。
* **中央图案**绘制占满屏幕中部的雷达搜索大图标64x64px并闪烁。
#### 2.3.3 对码确认与序号微调界面 (STATE_PAIR_CONFIRM)
当手环解码到传感器对码信号后,系统自动依据 4-bit 数据码解析出传感器类型,直接切入此确认页进行序号编辑:
```
+-----------------------------------------+
| ✓ RECU | <- 状态提示 (Y=20) (16x32px)
| |
| [🚪 48x48] | <- 传感器大图标 (Y=55~103)
| |
| PORTE | <- 类型名称 (Y=120) (16x32px)
| |
| [ 01 ] | <- 序号微调框 (Y=170~234) (32x64px)
+-----------------------------------------+
```
* **元素布局**
* **传感器图标** 使用 **48x48px** 超大单色位图。
* **类型前缀** 使用 **16x32px**
* **数字序号选框** 使用 **32x64px** 超大数字。用户按 ▲/▼ 直接微调序号CONFIRM 键保存。
#### 2.3.4 系统布撤防手动选择界面 (STATE_ARM_DISARM_SELECT)
* **页面显示**:屏幕正中央显示当前拟设定的状态:
* `[ ARME ]`(配 48x48px 盾牌图标)
* `[ DESARME ]`(配 48x48px 虚线开锁图标)
* 字体全部使用 **16x32px**。按 ▲/▼ 键来回切换,按 CONFIRM 键保存生效。
#### 2.3.5 IPC 绑定发射界面 (STATE_IPC_BIND)
* **图形指示**:正中央显示 **48x48px** 的摄像头绑定大图标,配合动态声波向外发射动画。
* **大字提示**:上方显示 `LIAISON` (16x32px),下方显示 `EN COURS` (16x32px)。
* **物理地址展示**:底部以 8x16px 显示手环地址 `ID: 0xXXXXX`
* **退出/返回**:长按 CONFIRM 键 3 秒 或 长按 ▼ 键 2 秒主动退出。
### 2.4 报警触发与本地 SOS 界面 (STATE_ALARMING / STATE_SOS_EMITTED)
手环发生报警时的显示要具有极强的视觉冲击力:
* **警告框**:全屏外围红绿交替闪烁的 4 像素宽粗线警告框。
* **中央图标**:根据传感器类型,居中绘制对应的 **64x64px** 的超大单色位图图标(如巨大的火苗 `🔥`、门锁 `🚪`)。
* **主动 SOS 界面**:大字显示 `SOS``EMIS` (16x32px 分行显示,防止单行溢出),中央显示巨大的 `🚨` 警灯图标 (64x64px)。
---
## 3. 文字居中对齐与字模基线规范
为保证排版美观,[Drivers/lcd_font.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd_font.h) 内的标准字模点阵需要满足如下基线对齐:

View File

@@ -0,0 +1,646 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Wristband Accessibility Big Font UI Gallery (A11y V3.4)</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #0A0D14;
color: #E2E8F0;
margin: 0;
padding: 40px 20px;
line-height: 1.6;
}
/* 页面居中大框容器 */
.container {
max-width: 1200px;
margin: 0 auto;
}
/* 页面大标题 */
h1 {
font-size: 2.2rem;
color: #00E5FF;
text-align: center;
margin-bottom: 8px;
letter-spacing: 1px;
}
/* 页面副标题 */
.subtitle {
text-align: center;
color: #94A3B8;
font-size: 1.1rem;
margin-bottom: 30px;
}
/* 主栏目分隔标题 */
.section-title {
font-size: 1.4rem;
color: #FFB74D;
border-left: 4px solid #FFB74D;
padding-left: 12px;
margin-top: 50px;
margin-bottom: 20px;
font-weight: bold;
}
/* 网格卡片布局 */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 40px;
justify-items: center;
}
/* 每一个卡片的样式 */
.card {
background-color: #161B22;
border-radius: 16px;
border: 1px solid #30363D;
padding: 24px;
text-align: center;
width: 270px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
transition: transform 0.2s, border-color 0.2s;
display: flex;
flex-direction: column;
}
.card:hover {
transform: translateY(-5px);
border-color: #00E5FF;
}
/* 卡片内标题 */
.card-title {
font-size: 1.1rem;
font-weight: bold;
color: #FFFFFF;
margin-bottom: 8px;
}
/* 卡片内简短描述 */
.card-desc {
font-size: 0.85rem;
color: #8B949E;
margin-bottom: 20px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
/* 智能手环 CSS 仿真外壳 */
.watch-device {
position: relative;
width: 160px;
height: 280px;
background: linear-gradient(135deg, #2D3748 0%, #1A202C 100%);
border-radius: 35px;
border: 5px solid #4A5568;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.8), 0 10px 20px rgba(0, 0, 0, 0.6);
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
}
/* 表带连接件仿真效果 (上) */
.watch-device::before {
content: '';
position: absolute;
top: -20px;
left: 45px;
width: 70px;
height: 20px;
background-color: #2D3748;
border-radius: 5px 5px 0 0;
z-index: -1;
}
/* 表带连接件仿真效果 (下) */
.watch-device::after {
content: '';
position: absolute;
bottom: -20px;
left: 45px;
width: 70px;
height: 20px;
background-color: #2D3748;
border-radius: 0 0 5px 5px;
z-index: -1;
}
/* 屏幕物理边框 */
.screen-bezel {
position: relative;
width: 130px;
height: 250px;
background-color: #000000;
border-radius: 25px;
border: 2px solid #0D1117;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
/* SVG 自动适应屏幕比例 */
.screen-bezel svg {
width: 120px;
height: 240px;
display: block;
}
/* 底部按键操作说明框 */
.card-ops {
font-size: 0.8rem;
color: #E2E8F0;
margin-top: 20px;
text-align: left;
background-color: #0D1117;
padding: 12px;
border-radius: 8px;
border: 1px solid #21262D;
min-height: 70px;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
/* 操作说明高亮前缀 */
.card-ops b {
color: #FFB74D;
display: block;
margin-bottom: 4px;
font-size: 0.85rem;
}
</style>
</head>
<body>
<div class="container">
<h1>Wristband Accessibility Big Font UI Gallery (A11y V3.4)</h1>
<div class="subtitle">Concentrated single-page display of large-font, large-icon UI screens and button
interaction instructions optimized for special groups (13 categories of complete UI screens)</div>
<div class="section-title">Part 1: Normal Standby Clock Screen (STATE_CLOCK)</div>
<div class="grid">
<div class="card">
<div class="card-title">1. Standby Clock (Disarmed)</div>
<div class="card-desc">Normal time display, large battery percentage, with a large unlock icon 🔓 on the
left</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<g transform="translate(10, 15)">
<rect x="0" y="8" width="20" height="15" rx="3" fill="none" stroke="#10B981"
stroke-width="2" />
<path d="M 5 8 L 5 4 C 5 2, 12 2, 15 4 L 15 10" fill="none" stroke="#10B981"
stroke-width="2" stroke-linecap="round" />
</g>
<text x="82" y="32" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="end">85%</text>
<rect x="86" y="19" width="24" height="13" rx="2" fill="none" stroke="#FFFFFF"
stroke-width="1.8" />
<rect x="89" y="22" width="15" height="7" rx="0.5" fill="#10B981" />
<rect x="110" y="23" width="1.5" height="5" rx="0.5" fill="#FFFFFF" />
<text x="60" y="115" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="34"
font-weight="bold" text-anchor="middle">10:35</text>
<text x="60" y="150" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="16"
font-weight="bold" text-anchor="middle">AM</text>
<text x="60" y="195" fill="#94A3B8" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">FRI 07-10</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Long press ■ confirm key for 3 seconds to enter main menu; <b>long press ▲ (UP) key to toggle
Arm/Disarm</b>; short or long press SOS key directly triggers [Local Active Alarm].
</div>
</div>
<div class="card">
<div class="card-title">2. Standby Clock (Armed)</div>
<div class="card-desc">Normal time display, large battery percentage, with a large red lock icon 🔒 on
the left</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<g transform="translate(10, 15)">
<rect x="0" y="8" width="20" height="15" rx="3" fill="none" stroke="#EF4444"
stroke-width="2" />
<path d="M 5 8 L 5 3 C 5 1, 15 1, 15 3 L 15 8" fill="none" stroke="#EF4444"
stroke-width="2" stroke-linecap="round" />
</g>
<text x="82" y="32" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="end">85%+</text>
<rect x="86" y="19" width="24" height="13" rx="2" fill="none" stroke="#FFFFFF"
stroke-width="1.8" />
<rect x="89" y="22" width="15" height="7" rx="0.5" fill="#10B981" />
<rect x="110" y="23" width="1.5" height="5" rx="0.5" fill="#FFFFFF" />
<text x="60" y="115" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="34"
font-weight="bold" text-anchor="middle">10:35</text>
<text x="60" y="150" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="16"
font-weight="bold" text-anchor="middle">PM</text>
<text x="60" y="195" fill="#94A3B8" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">FRI 07-10</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Long press ■ confirm key for 3 seconds to enter main menu; <b>long press ▲ (UP) key to toggle
Arm/Disarm</b>; short or long press SOS key directly triggers [Local Active Alarm].
</div>
</div>
</div>
<div class="section-title">Part 2: Main Menu Screen (STATE_MENU)</div>
<div class="grid">
<div class="card">
<div class="card-title">3. Main Menu: 1. Time Settings</div>
<div class="card-desc">Menu Item 1: Large clock icon with extra large menu text</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<path d="M 52 30 L 60 20 L 68 30 Z" fill="#64748B" />
<g transform="translate(36, 65)">
<circle cx="24" cy="24" r="20" stroke="#00E5FF" stroke-width="3" fill="none" />
<path d="M 24 10 L 24 24 L 34 24" stroke="#00E5FF" stroke-width="3"
stroke-linecap="round" fill="none" />
<path d="M 8 6 L 16 2 M 40 6 L 32 2" stroke="#00E5FF" stroke-width="3.5"
stroke-linecap="round" />
</g>
<text x="60" y="165" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">1. CLOCK</text>
<path d="M 52 210 L 60 220 L 68 210 Z" fill="#64748B" />
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Press ▲/▼ keys to browse menu items; short press ■ to confirm entry; long press ■ for 3 seconds to
return to clock.
</div>
</div>
<div class="card">
<div class="card-title">4. Main Menu: 2. Sensor Pairing</div>
<div class="card-desc">Menu Item 2: Large radar pairing icon with pairing text</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<path d="M 52 30 L 60 20 L 68 30 Z" fill="#64748B" />
<g transform="translate(36, 65)">
<path
d="M 24 38 A 12 12 0 0 1 36 28 M 24 38 A 18 18 0 0 1 42 22 M 24 38 A 24 24 0 0 1 48 16"
stroke="#00E5FF" stroke-width="3" fill="none" stroke-linecap="round" />
<circle cx="24" cy="38" r="4" fill="#00E5FF" />
<line x1="24" y1="38" x2="6" y2="44" stroke="#00E5FF" stroke-width="2.5" />
</g>
<text x="60" y="165" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">2. PAIR</text>
<path d="M 52 210 L 60 220 L 68 210 Z" fill="#64748B" />
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Press ▲/▼ keys to browse menu items; short press ■ to confirm entry to start pairing search; long
press ■ for 3 seconds to return to clock.
</div>
</div>
<div class="card">
<div class="card-title">5. Main Menu: 3. Camera Binding</div>
<div class="card-desc">Menu Item 3: Large camera icon with binding text</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<path d="M 52 30 L 60 20 L 68 30 Z" fill="#64748B" />
<g transform="translate(36, 65)">
<rect x="4" y="14" width="30" height="24" rx="3" fill="none" stroke="#00E5FF"
stroke-width="3" />
<circle cx="19" cy="26" r="6" stroke="#00E5FF" stroke-width="2.5" fill="none" />
<path d="M 34 20 L 44 14 L 44 38 L 34 32 Z" fill="#00E5FF" />
</g>
<text x="60" y="165" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">3. BIND</text>
<path d="M 52 210 L 60 220 L 68 210 Z" fill="#64748B" />
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Press ▲/▼ keys to browse menu items; short press ■ to confirm entry into camera binding page; long
press ■ for 3 seconds to return to clock.
</div>
</div>
</div>
<div class="section-title">Part 3: Sub-setting Flow 1 - Time Adjustment (STATE_SET_TIME)</div>
<div class="grid">
<div class="card">
<div class="card-title">6. Time Edit: Select Hours (Blinking)</div>
<div class="card-desc">The actively selected digits (e.g., hours '10') will continuously blink on and
off.</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<g transform="translate(10, 15)">
<rect x="0" y="8" width="20" height="15" rx="3" fill="none" stroke="#10B981"
stroke-width="2" />
<path d="M 5 8 L 5 4 C 5 2, 12 2, 15 4 L 15 10" fill="none" stroke="#10B981"
stroke-width="2" stroke-linecap="round" />
</g>
<text x="82" y="32" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="end">85%+</text>
<rect x="86" y="19" width="24" height="13" rx="2" fill="none" stroke="#FFFFFF"
stroke-width="1.8" />
<rect x="89" y="22" width="15" height="7" rx="0.5" fill="#10B981" />
<rect x="110" y="23" width="1.5" height="5" rx="0.5" fill="#FFFFFF" />
<text x="52" y="115" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="34"
font-weight="bold" text-anchor="end">10</text>
<text x="60" y="113" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="34"
font-weight="bold" text-anchor="middle">:</text>
<text x="68" y="115" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="34"
font-weight="bold" text-anchor="start">35</text>
<text x="60" y="150" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="16"
font-weight="bold" text-anchor="middle">AM</text>
<text x="60" y="195" fill="#94A3B8" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">FRI 07-10</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Long press ■ confirm key to enter edit mode; short press ▲/▼ to adjust the currently blinking
number; long press ■ to jump to the next field (Minutes blink); short press ■ to save and exit edit
mode.
</div>
</div>
</div>
<div class="section-title">Part 4: Sub-setting Flow 2 - Sensor Pairing (STATE_PAIR)</div>
<div class="grid">
<div class="card">
<div class="card-title">7. Radar Pairing Search</div>
<div class="card-desc">64x64px extra large radar flashing animation, waiting for pairing signal</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<text x="60" y="35" fill="#94A3B8" font-family="system-ui, sans-serif" font-size="14"
font-weight="bold" text-anchor="middle">PAIRING</text>
<g transform="translate(28, 65)">
<path
d="M 32 50 C 32 30, 48 10, 60 10 M 32 50 C 32 35, 42 20, 52 20 M 32 50 C 32 40, 36 30, 44 30"
stroke="#00E5FF" stroke-width="3" fill="none" stroke-linecap="round" />
<circle cx="32" cy="50" r="5" fill="#00E5FF" />
<line x1="32" y1="50" x2="8" y2="58" stroke="#00E5FF" stroke-width="3" />
</g>
<text x="60" y="165" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="14"
font-weight="bold" text-anchor="middle">SEARCHING</text>
<text x="60" y="200" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="13"
font-weight="bold" text-anchor="middle">TRIGGER DET.</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Trigger the physical sensor. After capturing data, the wristband will <b>automatically identify the
type by data code and jump to the confirmation page</b>. Long press ▼ for 2 seconds to exit.
</div>
</div>
<div class="card">
<div class="card-title">8. Identify Type and Adjust ID</div>
<div class="card-desc">Auto-display type and large icon, adjust index number inside large box directly
</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<text x="60" y="32" fill="#10B981" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">✓ RECEIVED</text>
<g transform="translate(36, 45)">
<rect x="2" y="2" width="24" height="42" rx="3" fill="none" stroke="#FFFFFF"
stroke-width="2.5" />
<rect x="30" y="8" width="10" height="30" rx="1.5" fill="none" stroke="#10B981"
stroke-width="2.5" />
<circle cx="8" cy="24" r="2.5" fill="#FFFFFF" />
</g>
<text x="60" y="130" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">DOOR</text>
<rect x="15" y="152" width="90" height="44" rx="6" fill="none" stroke="#00E5FF"
stroke-width="2" />
<text x="60" y="186" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="34"
font-weight="bold" text-anchor="middle">01</text>
<text x="60" y="225" fill="#64748B" font-family="system-ui, sans-serif" font-size="13"
font-weight="bold" text-anchor="middle">CONFIRM</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Press ▲ to increase, ▼ to decrease the index number (01-99); short press ■ to save; long press ■ for
3 seconds to cancel.
</div>
</div>
<div class="card">
<div class="card-title">9. Pairing Saved Successfully</div>
<div class="card-desc">Accessibility big font: circle with checkmark, green success indicator</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<circle cx="60" cy="90" r="24" stroke="#10B981" stroke-width="2.5" fill="none" />
<path d="M 48 90 L 56 98 L 72 82" stroke="#10B981" stroke-width="3.5" fill="none"
stroke-linecap="round" stroke-linejoin="round" />
<text x="60" y="150" fill="#10B981" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">SUCCESS</text>
<text x="60" y="185" fill="#94A3B8" font-family="system-ui, sans-serif" font-size="13"
font-weight="bold" text-anchor="middle">SAVED</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Automatically returns to standby clock after showing for 2 seconds, no buttons required.
</div>
</div>
<div class="card">
<div class="card-title">10. Pairing Failed</div>
<div class="card-desc">Accessibility big font: red cross circle, warning to re-activate pairing</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<circle cx="60" cy="90" r="24" stroke="#EF4444" stroke-width="2.5" fill="none" />
<path d="M 50 80 L 70 100 M 70 80 L 50 100" stroke="#EF4444" stroke-width="3.5" fill="none"
stroke-linecap="round" />
<text x="60" y="150" fill="#EF4444" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">FAILED</text>
<text x="60" y="185" fill="#94A3B8" font-family="system-ui, sans-serif" font-size="12"
font-weight="bold" text-anchor="middle">PAIRED</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Automatically returns to pairing menu after showing for 5 seconds, or press any key to return
immediately.
</div>
</div>
</div>
<div class="section-title">Part 5: Sub-setting Flow 3 - IPC Camera Binding (STATE_IPC_BIND)</div>
<div class="grid">
<div class="card">
<div class="card-title">11. IPC Binding Transmission</div>
<div class="card-desc">48x48px camera icon, large status text and ID indicator</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<g transform="translate(36, 40)">
<rect x="2" y="10" width="32" height="26" rx="4" fill="none" stroke="#00E5FF"
stroke-width="3" />
<circle cx="18" cy="23" r="6" stroke="#00E5FF" stroke-width="2.5" fill="none" />
<path d="M 34 16 L 44 10 L 44 36 L 34 30 Z" fill="#00E5FF" />
</g>
<text x="60" y="130" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">BIND</text>
<text x="60" y="165" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="15"
font-weight="bold" text-anchor="middle">PAIRING...</text>
<text x="60" y="215" fill="#64748B" font-family="system-ui, sans-serif" font-size="9"
text-anchor="middle">ID: 0x3F882</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Continuously sends pairing signal. Long press ■ for 3 seconds or long press ▼ for 2 seconds to exit
immediately and return to clock.
</div>
</div>
</div>
<div class="section-title">Part 6: Emergency Alarm Status (STATE_ALARMING / STATE_SOS_EMITTED)</div>
<div class="grid">
<div class="card">
<div class="card-title">12. Received Zone Alarm (DOOR)</div>
<div class="card-desc">Full screen flashing thick red border + 64x64px large sensor icon</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<rect x="4" y="4" width="112" height="232" rx="20" stroke="#EF4444" stroke-width="4.5"
fill="none" />
<g transform="translate(28, 40)">
<rect x="2" y="2" width="32" height="58" rx="4" fill="none" stroke="#EF4444"
stroke-width="3.5" />
<rect x="42" y="12" width="14" height="42" rx="2" fill="none" stroke="#EF4444"
stroke-width="3" />
<circle cx="10" cy="32" r="3.5" fill="#EF4444" />
</g>
<text x="60" y="145" fill="#EF4444" font-family="system-ui, sans-serif" font-size="16"
font-weight="bold" text-anchor="middle">DOOR 01</text>
<text x="60" y="185" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="16"
font-weight="bold" text-anchor="middle">INTRUSION!</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
High-decibel motor and light alarms activated. Short press any key on the side or top of the
wristband to instantly dismiss the alarm and reset.
</div>
</div>
<div class="card">
<div class="card-title">13. Local Active Help SOS</div>
<div class="card-desc">Huge alarm light, preventing overflow via word wraps and size allocation</div>
<div class="watch-device">
<div class="screen-bezel">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000" />
<rect x="4" y="4" width="112" height="232" rx="20" stroke="#EF4444" stroke-width="4.5"
fill="none" />
<g transform="translate(28, 25)">
<path d="M 32 10 L 32 18 M 14 16 L 22 22 M 50 16 L 42 22 M 6 32 L 15 34 M 58 32 L 49 34"
stroke="#EF4444" stroke-width="3" stroke-linecap="round" />
<path d="M 12 52 C 12 28, 52 28, 52 52 Z" fill="#EF4444" stroke="#EF4444"
stroke-width="2" />
<circle cx="32" cy="45" r="7" fill="#FFFFFF" />
<rect x="6" y="52" width="52" height="8" rx="2" fill="#64748B" />
</g>
<text x="60" y="125" fill="#EF4444" font-family="system-ui, sans-serif" font-size="16"
font-weight="bold" text-anchor="middle">SOS</text>
<text x="60" y="152" fill="#EF4444" font-family="system-ui, sans-serif" font-size="16"
font-weight="bold" text-anchor="middle">SENT</text>
<text x="60" y="195" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="14"
font-weight="bold" text-anchor="middle">CALLING...</text>
</svg>
</div>
</div>
<div class="card-ops">
<b>Button Operations:</b>
Continuously sends SOS distress signal. Short or long press any button to stop transmission and
clear the emergency distress.
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,410 @@
# 模块拆分 - 应用执行层 (Docs/50_module-breakdown/mod-app.md)
本模块描述手环的 App 调度管理器AppManager、前台/后台应用架构、生命周期钩子OnStart, onRun, onClose, onEvent、事件分发机制、CPU占用控制以及各功能模块的页面渲染与状态机调度设计。
---
## 1. 模块职责说明
### 1.1 核心职责
| 职责领域 | 描述 |
| :--- | :--- |
| **应用生命周期管理** | 维护应用注册表,管理应用的启动、切换、关闭流程 |
| **前台/后台应用分离** | 支持前台应用独占屏幕和后台应用持续监听的双轨运行模式 |
| **事件分发路由** | 实现前台优先、后台兜底、未处理丢弃的事件分发策略 |
| **CPU占用控制** | 监控应用 onRun() 执行时间防止单个应用长时间占用CPU |
| **应用状态维护** | 维护活跃应用指针、应用运行状态标记等核心数据结构 |
### 1.2 应用类型划分
系统将应用分为两大类,通过 `AppType` 枚举区分:
| 应用类型 | 标识 | 特性 | 生命周期 |
| :--- | :--- | :--- | :--- |
| **前台应用** | `APP_TYPE_FOREGROUND` | 独占屏幕显示,响应用户交互,一次只有一个活跃 | `OnStart``onRun`(循环) → `onClose` |
| **后台应用** | `APP_TYPE_BACKGROUND` | 无屏幕显示,持续运行监听特定事件,可多个同时运行 | 注册后自动运行,无显式启动/关闭 |
### 1.3 应用列表
| 应用ID | 应用名称 | 类型 | 职责描述 |
| :--- | :--- | :--- | :--- |
| `APP_ID_CLOCK` | ClockApp | 前台 | 待机时钟应用,中央以 32x64px 超大点阵渲染当前时分,右上角渲染 16x32px 电量百分比及充电指示;后台监听射频报警信号 |
| `APP_ID_MENU` | MenuApp | 前台 | 主菜单应用,单屏单条目显示交互,支持 ▲/▼ 键高亮切换,确认键进入对应子页面 |
| `APP_ID_PAIR` | PairApp | 前台 | 配对与微调应用,对码捕捉到未配对 24 位射频码时拉起,提供微调序号选框,确认后存入持久化 Flash |
| `APP_ID_ALARM` | AlarmApp | 前台 | 传感器报警应用,被后台监测唤醒拉起,屏幕闪烁红色粗边框,调度马达振动波形与 LED 警示颜色 |
| `APP_ID_SOS` | SOSApp | 前台 | 主动求救应用,短按/长按 SOS 物理按键拉起,周期性发射 0x08 (SOS) 无线电数据,触发最高优先级的持续马达震动及红色幻彩灯爆闪 |
---
## 2. 核心数据结构
### 2.1 应用描述符结构 (WristbandApp)
```c
typedef struct {
AppID app_id; // 应用唯一标识 (APP_ID_CLOCK/MENU/PAIR/ALARM/SOS)
AppType app_type; // 应用类型 (APP_TYPE_FOREGROUND / APP_TYPE_BACKGROUND)
char *app_name; // 应用名称(用于调试日志输出)
// 生命周期回调函数指针
void (*OnStart)(void); // 启动回调:应用切入前台,加载资源、初始化显示
void (*onRun)(void); // 循环回调:主循环轮询调用,执行非阻塞业务逻辑
void (*onClose)(void); // 关闭回调:应用退出前台,保存状态、清理外设
EventResult (*onEvent)(SystemEvent evt); // 事件回调:接收并处理系统事件
// 运行状态标记
bit is_active; // 当前是否活跃(仅对前台应用有效,标识是否在前台运行)
bit is_running; // 应用是否处于运行状态用于CPU占用控制标记是否允许调用onRun
} WristbandApp;
```
### 2.2 应用ID枚举
```c
typedef enum {
APP_ID_CLOCK, // 0: 待机时钟应用
APP_ID_MENU, // 1: 主菜单应用
APP_ID_PAIR, // 2: 配对与微调应用
APP_ID_ALARM, // 3: 传感器报警应用
APP_ID_SOS, // 4: 主动求救应用
APP_ID_MAX // 5: 应用数量上限(用于数组大小)
} AppID;
```
### 2.3 应用类型枚举
```c
typedef enum {
APP_TYPE_FOREGROUND, // 前台应用:独占屏幕和用户交互
APP_TYPE_BACKGROUND // 后台应用:无屏幕显示,仅监听事件
} AppType;
```
### 2.4 事件处理返回值
```c
typedef enum {
EVENT_HANDLED, // 事件已处理,停止继续分发
EVENT_IGNORED // 事件未处理,继续传递给下一个应用
} EventResult;
```
---
## 3. 应用管理器核心接口
### 3.1 初始化与注册接口
| 接口名称 | 函数签名 | 功能描述 |
| :--- | :--- | :--- |
| `AppManager_Init` | `void AppManager_Init(void)` | 初始化应用管理器,初始化应用注册表,设置默认活跃应用为 ClockApp |
| `AppManager_RegisterApp` | `void AppManager_RegisterApp(AppID app_id, AppType app_type, char *app_name, AppStartFunc on_start, AppRunFunc on_run, AppCloseFunc on_close, AppEventFunc on_event)` | 注册应用到注册表,绑定生命周期回调函数 |
### 3.2 应用切换接口
| 接口名称 | 函数签名 | 功能描述 |
| :--- | :--- | :--- |
| `AppManager_StartApp` | `void AppManager_StartApp(AppID app_id)` | 启动指定应用:关闭当前活跃应用 → 切换活跃应用指针 → 启动新应用 |
| `AppManager_CloseApp` | `void AppManager_CloseApp(void)` | 关闭当前活跃应用回退到默认应用ClockApp |
| `AppManager_GetActiveAppID` | `AppID AppManager_GetActiveAppID(void)` | 获取当前活跃应用的 ID |
### 3.3 事件分发接口
| 接口名称 | 函数签名 | 功能描述 |
| :--- | :--- | :--- |
| `AppManager_DispatchEvent` | `void AppManager_DispatchEvent(SystemEvent evt)` | 分发事件到应用层:前台优先 → 后台兜底 → 未处理丢弃 |
| `AppManager_RunActiveApp` | `void AppManager_RunActiveApp(void)` | 运行当前活跃应用的 onRun 回调(带 CPU 占用控制) |
---
## 4. 应用生命周期调度机制
### 4.1 生命周期状态转换
```mermaid
graph LR
subgraph 前台应用状态
Idle["未注册 / 空闲"] -->|注册| Registered["已注册"]
Registered -->|StartApp| Active["活跃中 (OnStart)"]
Active -->|onRun循环| Running["运行中"]
Running -->|StartApp(其他)| Closing["关闭中 (onClose)"]
Closing -->|切换完成| Active
Running -->|CloseApp| Closing
Closing -->|回退完成| Default["默认状态 (ClockApp)"]
end
subgraph 后台应用状态
BG_Idle["未注册"] -->|注册| BG_Running["持续运行"]
BG_Running -->|接收事件| BG_Handling["事件处理中"]
BG_Handling -->|处理完成| BG_Running
end
```
### 4.2 生命周期回调语义
| 回调函数 | 调用时机 | 职责要求 |
| :--- | :--- | :--- |
| `OnStart()` | 应用切入前台时 | 初始化显示、加载资源、重置状态变量 |
| `onRun()` | 主循环每轮调用 | 执行非阻塞业务逻辑(如动画更新、超时检测),必须控制执行时间 |
| `onClose()` | 应用退出前台时 | 保存状态、清理外设(关屏、停振、关闭射频等) |
| `onEvent(evt)` | 收到事件时 | 处理事件,返回 `EVENT_HANDLED``EVENT_IGNORED` |
### 4.3 应用切换流程
```
AppManager_StartApp(target_app_id) 执行流程:
1. 参数有效性检查
├── app_id >= APP_ID_MAX → 返回错误
├── 目标应用非前台应用 → 返回错误
└── 目标应用未注册 (OnStart == NULL) → 返回错误
2. 目标应用已是活跃应用 → 直接返回
3. 关闭当前活跃应用
└── active_app->onClose() → 清理外设、保存状态
4. 切换活跃应用指针
└── active_app = &app_registry[target_app_id]
5. 启动新应用
└── active_app->OnStart() → 初始化显示、加载资源
```
---
## 5. 事件分发机制
### 5.1 事件分发规则
事件在应用层的分发遵循以下顺序,前台应用优先处理,后台应用作为兜底:
```
事件分发流程 (AppManager_DispatchEvent):
┌──────────────────────────────────────────────────────────────┐
│ Step 1: 前台应用优先处理 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ if (active_app != NULL && active_app->is_active) │ │
│ │ { │ │
│ │ result = active_app->onEvent(evt); │ │
│ │ if (result == EVENT_HANDLED) │ │
│ │ return; // 前台应用已处理,结束分发 │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Step 2: 后台应用兜底处理 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ for (i = 0; i < APP_ID_MAX; i++) │ │
│ │ { │ │
│ │ bg_app = &app_registry[i]; │ │
│ │ // 仅处理后台应用 │ │
│ │ if (bg_app->app_type != APP_TYPE_BACKGROUND || │ │
│ │ bg_app->onEvent == NULL) │ │
│ │ continue; │ │
│ │ │ │
│ │ result = bg_app->onEvent(evt); │ │
│ │ if (result == EVENT_HANDLED) │ │
│ │ return; // 某后台应用已处理,结束分发 │ │
│ │ } │ │
│ └────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Step 3: 事件丢弃 │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ // 前台和后台应用均未处理,事件被丢弃 │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
### 5.2 事件处理示例
以下为各应用的典型事件处理行为:
| 事件类型 | ClockApp | MenuApp | PairApp | AlarmApp | SOSApp |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `KEY_EVENT_UP_CLICK` | IGNORED | HANDLED (上移) | IGNORED | HANDLED (清除) | IGNORED |
| `KEY_EVENT_DOWN_CLICK` | IGNORED | HANDLED (下移) | IGNORED | HANDLED (清除) | IGNORED |
| `KEY_EVENT_SOS_CLICK` | HANDLED (启动SOS) | IGNORED | IGNORED | HANDLED (清除) | IGNORED |
| `KEY_EVENT_UP_DOWN_COMB` | HANDLED (启动Menu) | IGNORED | IGNORED | IGNORED | IGNORED |
| `KEY_EVENT_SOS_LONG` | HANDLED (绑定IPC) | IGNORED | HANDLED (保存) | IGNORED | HANDLED (退出) |
| `KEY_EVENT_DOWN_LONG` | IGNORED | HANDLED (返回) | HANDLED (退出) | IGNORED | HANDLED (退出) |
---
## 6. CPU 占用控制机制
### 6.1 设计目的
为防止单个应用的 `onRun()` 回调长时间占用 CPU 导致系统响应迟钝或卡死,框架引入 **执行时间监控机制**
### 6.2 控制参数
| 参数名称 | 定义 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| `APP_ONRUN_MAX_TICKS` | onRun 最大执行时间阈值 | 5 (ms) | 超过此时间则暂停应用运行 |
### 6.3 监控机制
```
AppManager_RunActiveApp() 执行流程:
1. 检查应用状态
└── if (active_app == NULL || !active_app->is_active) → 直接返回
2. 记录开始时间
└── start_tick = ms_tick
3. 执行应用的 onRun() 回调
└── active_app->onRun()
4. 计算执行耗时
└── elapsed_ticks = ms_tick - start_tick
5. 判断是否超时
├── if (elapsed_ticks > APP_ONRUN_MAX_TICKS)
│ └── active_app->is_running = 0 // 暂停应用
└── else
└── active_app->is_running = 1 // 继续运行
```
### 6.4 超时恢复
* 超时后应用的 `is_running` 标记被置为 0下一次主循环将跳过该应用的 `onRun()` 调用
* 应用可在下一次循环中自动恢复运行(`is_running``onRun` 调用前被检查,调用后被更新)
* 此机制不会杀死应用,仅暂停其循环执行,允许应用在后续恢复
---
## 7. 应用实现细节
### 7.1 ClockApp时钟应用
**职责**:待机时钟显示、后台射频监测
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 初始化状态为 NORMAL设置重绘标志 |
| `onRun` | 分钟变化时重绘时钟;后台监听射频信号,匹配到已配对传感器触发报警 |
| `onClose` | 清理资源 |
| `onEvent` | 处理组合键进入菜单、SOS 按键进入求救模式、长按绑定 IPC |
### 7.2 MenuApp菜单应用
**职责**:主菜单导航
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 初始化菜单选择项为 0关闭射频显示菜单页面 |
| `onRun` | 无持续运行逻辑 |
| `onClose` | 重新开启射频接收 |
| `onEvent` | 处理上/下键切换菜单项、确认键进入配对、长按返回时钟 |
### 7.3 PairApp配对应用
**职责**:传感器配对与保存
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 进入等待状态,开启射频接收,显示雷达搜索页面 |
| `onRun` | 更新雷达动画;监听射频信号捕获对码;检测超时 |
| `onClose` | 关闭射频,重置配对状态 |
| `onEvent` | 处理长按退出、确认保存对码 |
### 7.4 AlarmApp报警应用
**职责**:入侵警报显示与响应
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 设置状态为 ALARM显示报警页面重置报警定时器 |
| `onRun` | 控制报警边框闪烁效果 |
| `onClose` | 停止报警效果马达、LED恢复正常状态 |
| `onEvent` | 任何按键清除报警 |
### 7.5 SOSApp求救应用
**职责**:主动求救模式
**生命周期实现**
| 回调 | 实现逻辑 |
| :--- | :--- |
| `OnStart` | 设置状态为 SOS_EMITTED显示 SOS 页面 |
| `onRun` | 每 500ms 发射一次射频求救帧;控制红色边框闪烁 |
| `onClose` | 停止求救效果马达、LED恢复正常状态 |
| `onEvent` | 长按任意键解除求救 |
---
## 8. 页面渲染接口
| 接口名称 | 函数签名 | 所属应用 | 功能描述 |
| :--- | :--- | :--- | :--- |
| `UI_ShowClockPage` | `void UI_ShowClockPage(SystemState state, u8 hour, u8 min)` | ClockApp | 绘制时钟页面,包含时间显示和电量指示 |
| `UI_ShowPairMenuPage` | `void UI_ShowPairMenuPage(u8 selected_item)` | MenuApp | 绘制配对菜单页面,显示选中项图标和名称 |
| `UI_ShowPairWaitPage` | `void UI_ShowPairWaitPage(u8 frame)` | PairApp | 绘制雷达搜索页面,包含动画帧 |
| `UI_ShowPairConfirmPage` | `void UI_ShowPairConfirmPage(u32 addr)` | PairApp | 绘制配对确认页面,显示捕获到的射频地址 |
| `UI_ShowPairSuccessPage` | `void UI_ShowPairSuccessPage(u32 addr)` | PairApp | 绘制配对成功页面 |
| `UI_ShowPairFailPage` | `void UI_ShowPairFailPage(u8 reason)` | PairApp | 绘制配对失败页面 |
| `UI_ShowAlarmPage` | `void UI_ShowAlarmPage(char *name, u8 severity)` | AlarmApp | 绘制报警页面,包含红色边框和传感器名称 |
| `UI_ShowSosPage` | `void UI_ShowSosPage(void)` | SOSApp | 绘制 SOS 求救页面 |
| `UI_ShowBindingPage` | `void UI_ShowBindingPage(void)` | ClockApp | 绘制 IPC 绑定页面 |
---
## 9. 应用注册表结构
应用管理器维护一个静态数组 `app_registry[APP_ID_MAX]`,存储所有已注册的应用:
```c
static WristbandApp app_registry[APP_ID_MAX];
static WristbandApp *active_app = NULL; // 当前活跃前台应用指针
#define DEFAULT_APP_ID APP_ID_CLOCK // 默认应用为时钟
```
注册流程:
1. 系统初始化时调用 `AppManager_Init()` 初始化注册表
2. 各应用模块调用 `AppManager_RegisterApp()` 注册自身
3. 最后调用 `AppManager_Init()` 设置默认活跃应用
---
## 10. 主循环集成
主循环中应用管理器的调用顺序:
```
while(1)
{
// 1. 处理串口调试输入
cmd = Uart_RxChar();
if (cmd != '\0')
// 解析命令,生成事件,调用 EventQueue_Push 或 EventQueue_InsertFront
// 2. 处理按键事件缓冲区
if (key_event_buf != KEY_EVENT_NONE)
// 生成事件,根据优先级选择入队(Push)或插队(InsertFront)
key_event_buf = KEY_EVENT_NONE;
// 3. 处理事件队列
while (!EventQueue_IsEmpty())
{
evt = EventQueue_Pop();
AppManager_DispatchEvent(evt);
}
// 4. 运行当前活跃应用带CPU占用控制
AppManager_RunActiveApp();
// 5. 其他系统监控如LED/马达状态输出)
}
```

View File

@@ -0,0 +1,597 @@
# 模块拆分 - 事件采集与总线分发管理 (Docs/50_module-breakdown/mod-event.md)
本模块描述系统输入事件采集(按键移位消抖滤波与 RF 自适应解调解码、事件优先级体系、事件环形队列EventQueue缓冲、紧急事件插队机制以及事件输出分配的整体流转规格。
---
## 1. 模块职责说明
### 1.1 核心职责
| 职责领域 | 描述 |
| :--- | :--- |
| **事件采集(按键输入)** | 在 Timer1 中断 (10ms 时基) 中扫描物理引脚,判定有效按键后封装为事件 |
| **事件采集(射频接收)** | 在外部中断中捕获射频信号,解调并还原 24 位载波包,封装为射频事件 |
| **事件优先级管理** | 根据事件类型分配优先级,支持四级优先级体系 |
| **事件队列缓冲** | 维护深度为 8 的环形事件队列,隔离高频中断和低频业务交互 |
| **紧急事件插队** | 提供将紧急事件插入队列头部的路径,确保紧急事件优先处理 |
| **事件输出路由分发** | 在主循环中轮询队列,将事件分发给应用管理器 |
### 1.2 事件来源与优先级映射
| 事件来源 | 典型事件 | 优先级 | 处理方式 |
| :--- | :--- | :--- | :--- |
| **SOS 按键** | `KEY_EVENT_SOS_CLICK`, `KEY_EVENT_SOS_LONG` | URGENT | **插队**到队首 |
| **射频报警** | `RF_EVENT_ALARM` | HIGH | **插队**到队首 |
| **普通按键** | `KEY_EVENT_UP_CLICK`, `KEY_EVENT_DOWN_CLICK`, `KEY_EVENT_UP_DOWN_COMB` | NORMAL | 入队等待处理 |
| **射频配对** | `RF_EVENT_PAIR` | NORMAL | 入队等待处理 |
| **系统定时** | 时钟更新、超时检测 | LOW | 入队等待处理 |
---
## 2. 核心数据结构
### 2.1 扩展事件结构体 (SystemEvent)
```c
typedef struct {
KeyEvent key_event; // 原始按键事件类型
EventPriority priority; // 事件优先级 (LOW/NORMAL/HIGH/URGENT)
u32 extra_data; // 额外数据(如射频地址、时间信息等)
u8 extra_size; // 额外数据有效字节数
} SystemEvent;
```
**字段说明**
| 字段 | 类型 | 描述 |
| :--- | :--- | :--- |
| `key_event` | `KeyEvent` | 按键事件的原始类型,如 `KEY_EVENT_UP_CLICK``KEY_EVENT_SOS_LONG` |
| `priority` | `EventPriority` | 事件优先级,决定事件的处理路径(入队或直通) |
| `extra_data` | `u32` | 额外携带的数据,用于传递射频地址、时间等信息 |
| `extra_size` | `u8` | `extra_data` 中有效数据的字节数0~4 |
### 2.2 事件优先级枚举
```c
typedef enum {
EVENT_PRIORITY_LOW, // 0: 低优先级:系统内部事件,如定时更新
EVENT_PRIORITY_NORMAL, // 1: 正常优先级:大部分用户交互事件
EVENT_PRIORITY_HIGH, // 2: 高优先级:报警相关事件,插队到队首
EVENT_PRIORITY_URGENT // 3: 紧急优先级SOS等插队到队首
} EventPriority;
```
### 2.3 按键事件类型枚举
```c
typedef enum {
KEY_EVENT_NONE, // 0: 无事件
KEY_EVENT_UP_CLICK, // 1: 上键短按
KEY_EVENT_UP_LONG, // 2: 上键长按
KEY_EVENT_DOWN_CLICK, // 3: 下键短按
KEY_EVENT_DOWN_LONG, // 4: 下键长按
KEY_EVENT_SOS_CLICK, // 5: SOS键短按
KEY_EVENT_SOS_LONG, // 6: SOS键长按
KEY_EVENT_UP_DOWN_COMB // 7: 上下键组合
} KeyEvent;
```
---
## 3. 事件队列模块
### 3.1 队列设计参数
| 参数名称 | 定义 | 默认值 | 说明 |
| :--- | :--- | :--- | :--- |
| `EVENT_QUEUE_DEPTH` | 队列深度(环形缓冲区大小) | 8 | 可配置,建议值为 4~16 |
| `queue_head` | 读指针 | 0 | 指向即将读取的位置 |
| `queue_tail` | 写指针 | 0 | 指向即将写入的位置 |
| `queue_count` | 当前事件数量 | 0 | 范围 0~EVENT_QUEUE_DEPTH |
### 3.2 队列内部结构
```c
static SystemEvent event_queue[EVENT_QUEUE_DEPTH]; // 环形缓冲区存储数组
static u8 queue_head = 0; // 读指针:指向即将读取的位置
static u8 queue_tail = 0; // 写指针:指向即将写入的位置
static u8 queue_count = 0; // 当前队列中的事件数量
```
### 3.3 队列操作接口
| 接口名称 | 函数签名 | 返回值 | 功能描述 |
| :--- | :--- | :--- | :--- |
| `EventQueue_Init` | `void EventQueue_Init(void)` | 无 | 初始化队列:重置读写指针和计数器 |
| `EventQueue_Push` | `bit EventQueue_Push(SystemEvent evt)` | `bit` (1=成功, 0=失败) | 非阻塞将事件推入队列尾部,队列满时返回 0 |
| `EventQueue_InsertFront` | `bit EventQueue_InsertFront(SystemEvent evt)` | `bit` (1=成功, 0=失败) | **插队**:将事件插入到队列头部,高/紧急优先级事件使用 |
| `EventQueue_Pop` | `SystemEvent EventQueue_Pop(void)` | `SystemEvent` | 从队列头部弹出一个事件,队空时返回空事件 |
| `EventQueue_IsEmpty` | `bit EventQueue_IsEmpty(void)` | `bit` (1=空) | 判断队列是否为空 |
| `EventQueue_IsFull` | `bit EventQueue_IsFull(void)` | `bit` (1=满) | 判断队列是否已满 |
| `EventQueue_GetCount` | `u8 EventQueue_GetCount(void)` | `u8` | 获取队列中当前事件数量 |
### 3.4 队列操作流程
#### 入队操作 (EventQueue_Push)
```
EventQueue_Push(evt) 执行流程:
1. 检查队列是否已满
└── if (queue_count >= EVENT_QUEUE_DEPTH) → 返回 0入队失败
2. 禁用全局中断(保护队列操作原子性)
└── EA = 0
3. 将事件写入环形缓冲区
└── event_queue[queue_tail] = evt
4. 更新写指针(循环递增)
└── queue_tail = (queue_tail + 1) % EVENT_QUEUE_DEPTH
5. 更新事件计数
└── queue_count++
6. 恢复全局中断
└── EA = 1
7. 返回成功
└── return 1
```
#### 出队操作 (EventQueue_Pop)
```
EventQueue_Pop() 执行流程:
1. 初始化返回的空事件
└── evt.key_event = KEY_EVENT_NONE
evt.priority = EVENT_PRIORITY_LOW
evt.extra_data = 0
evt.extra_size = 0
2. 检查队列是否为空
└── if (queue_count == 0) → 返回空事件
3. 禁用全局中断(保护队列操作原子性)
└── EA = 0
4. 从队列头部读取事件
└── evt = event_queue[queue_head]
5. 更新读指针(循环递增)
└── queue_head = (queue_head + 1) % EVENT_QUEUE_DEPTH
6. 更新事件计数
└── queue_count--
7. 恢复全局中断
└── EA = 1
8. 返回读取到的事件
└── return evt
```
#### 插队操作 (EventQueue_InsertFront)
```
EventQueue_InsertFront(evt) 执行流程:
1. 检查队列是否已满
└── if (queue_count >= EVENT_QUEUE_DEPTH) → 返回 0插队失败
2. 禁用全局中断(保护队列操作原子性)
└── EA = 0
3. 更新读指针(向前移动一位,循环递减)
└── queue_head = (queue_head - 1 + EVENT_QUEUE_DEPTH) % EVENT_QUEUE_DEPTH
4. 将事件写入队列头部
└── event_queue[queue_head] = evt
5. 更新事件计数
└── queue_count++
6. 恢复全局中断
└── EA = 1
7. 返回成功
└── return 1
注意:此函数将事件插入到队列头部,适用于高/紧急优先级事件,
确保此类事件优先于队列中已有的事件被处理。
```
### 3.5 中断保护机制
队列操作使用全局中断开关 `EA` 进行保护,确保在中断环境下的原子性:
```
中断保护原则:
┌─────────────────────────────────────────────────────────────┐
│ 1. 入队操作可能在中断中被调用按键扫描、RF接收
│ └── 需要保护队列数据结构不被并发访问 │
│ │
│ 2. 出队操作在主循环中被调用 │
│ └── 需要保护队列数据结构不被中断破坏 │
│ │
│ 3. 保护方式:禁用全局中断 → 执行操作 → 恢复全局中断 │
│ └── 最小化中断禁用时间,减少对系统响应性的影响 │
└─────────────────────────────────────────────────────────────┘
```
---
## 4. 事件采集机制
### 4.1 按键事件采集
#### 采集流程
```
按键事件采集流程Timer1 10ms 中断):
1. 读取物理引脚状态
└── P0.1 (KEY_UP), P0.2 (KEY_CONFIRM), P0.3 (KEY_DOWN), P2.6 (KEY_SOS)
2. 移位消抖滤波
└── 连续 3 次检测一致确认为有效电平变化
3. 判断按键类型
├── 短按:按下后 200ms 内释放
├── 长按:按下持续时间 >= 2000ms (DOWN键) 或 >= 3000ms (其他键)
└── 组合键UP 和 DOWN 同时按下
4. 将事件写入缓冲区(中断中仅标记事件,不直接处理)
└── key_event_buf = 对应的按键事件类型
注意中断中不直接调用事件处理函数避免在ISR中执行耗时操作如Delay_ms、LCD绘制
事件的优先级判断和分发由主循环完成。
```
#### 按键消抖时序
```
按键消抖时序图:
时间轴 →
├──────────────────────────────────────────────────────────────┐
│ 物理按键 ──────┐ │ │
│ (实际) LOW │───────────────── HIGH ────────────│──────│ │
├─────────────────┼────────────────────────────────────┼──────┤ │
│ 10ms采样 ───────┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──────┤ │
│ (抖动区) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
├─────────────────┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──┼──────┤ │
│ 消抖结果 ───────┼──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──────┤ │
│ (连续3次 │ LOW HIGH │ │
│ 一致才确认) └──────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ ↑ 抖动区结束,确认按键按下 │
└──────────────────────────────────────────────────────────────┘
```
### 4.2 射频事件采集
#### 采集流程
```
射频事件采集流程(外部中断 P2.1
1. 射频输入引脚 RF_RX_DATA (P2.1) 发生电平沿变化
└── 触发外部中断
2. 定时器捕获高低电平比值
└── 解调自适应基准 T
3. 连续采集 24 个数据位
└── 组合还原 20 位物理 ID 及 4 位状态码
4. 校验与识别
├── 解密通过
├── 判断是对码事件还是报警事件
5. 封装事件
└── SystemEvent evt;
evt.key_event = KEY_EVENT_NONE; // 射频事件无按键类型
evt.extra_data = 射频地址;
evt.extra_size = 4;
6. 设置优先级并入队
├── 报警事件 → evt.priority = EVENT_PRIORITY_HIGH
└── 配对事件 → evt.priority = EVENT_PRIORITY_NORMAL
→ EventQueue_Push(evt)
```
#### RF 解调参数
| 参数 | 值 | 说明 |
| :--- | :--- | :--- |
| **载波频率** | 433MHz | LR690L 射频芯片工作频率 |
| **编码方式** | EV1527 | 24 位曼彻斯特编码 |
| **数据位** | 24 位 | 20 位物理地址 + 4 位状态码 |
| **解调方式** | 自适应基准 T | 根据信号自动调整采样基准 |
### 4.3 系统定时事件
```
系统定时事件产生流程Timer1 1ms 中断):
1. 毫秒计数器累加
└── ms_tick++
2. 秒分频检测
└── if (ms_tick % 1000 == 0) → 秒计数加1
3. 分钟变化检测
└── if (分钟变化) → 产生时钟更新事件
4. 封装事件
└── SystemEvent evt;
evt.key_event = KEY_EVENT_NONE;
evt.priority = EVENT_PRIORITY_LOW;
evt.extra_data = ((u32)hour << 8) | min;
evt.extra_size = 2;
5. 入队处理
└── EventQueue_Push(evt)
```
---
## 5. 事件分发机制
### 5.1 事件流转完整架构
```mermaid
graph TD
%% 事件产生层
subgraph EventSources ["事件来源"]
KeyScan["按键扫描 (10ms)"]
RFRecv["射频接收 (中断)"]
Timer["定时事件 (1ms)"]
end
%% 事件封装层
KeyScan -->|封装| KeyEventPack["按键事件封装"]
RFRecv -->|封装| RFEventPack["射频事件封装"]
Timer -->|封装| TimerEventPack["定时事件封装"]
%% 优先级判断
KeyEventPack --> PriorityJudge{"优先级判断"}
RFEventPack --> PriorityJudge
TimerEventPack --> PriorityJudge
%% 分流处理
PriorityJudge -->|HIGH/URGENT| InsertFront["插队到队首"]
PriorityJudge -->|LOW/NORMAL| Push["入队"]
%% 队列处理
InsertFront --> Queue["EventQueue 环形缓冲区"]
Push --> Queue
Queue --> MainLoop["主循环轮询出队"]
MainLoop --> AppMgr["AppManager_DispatchEvent"]
%% 应用层处理
AppMgr --> FG_App["前台应用 (优先)"]
FG_App -->|IGNORED| BG_Apps["后台应用 (兜底)"]
FG_App -->|HANDLED| Done["处理完成"]
BG_Apps -->|IGNORED| Discard["事件丢弃"]
BG_Apps -->|HANDLED| Done
```
### 5.2 主循环事件处理流程
```
主循环事件处理流程:
while(1)
{
// 1. 处理串口调试输入
cmd = Uart_RxChar();
if (cmd != '\0')
{
// 解析命令生成事件
SystemEvent evt;
evt.key_event = 对应的按键事件;
evt.priority = 命令对应的优先级;
// 根据优先级选择入队方式
if (evt.priority >= EVENT_PRIORITY_HIGH)
EventQueue_InsertFront(evt); // 高/紧急优先级插队到队首
else
EventQueue_Push(evt); // 低/正常优先级入队
}
// 2. 处理按键事件缓冲区(来自定时器中断)
if (key_event_buf != KEY_EVENT_NONE)
{
SystemEvent evt;
evt.key_event = key_event_buf;
evt.extra_data = 0;
evt.extra_size = 0;
// 判断事件优先级
if (key_event_buf == KEY_EVENT_SOS_CLICK ||
key_event_buf == KEY_EVENT_SOS_LONG)
{
evt.priority = EVENT_PRIORITY_URGENT;
EventQueue_InsertFront(evt); // 紧急事件插队到队首
}
else
{
evt.priority = EVENT_PRIORITY_NORMAL;
EventQueue_Push(evt); // 普通事件入队
}
key_event_buf = KEY_EVENT_NONE;
}
// 3. 处理事件队列中的所有事件
while (!EventQueue_IsEmpty())
{
SystemEvent evt = EventQueue_Pop();
// 跳过空事件
if (evt.key_event == KEY_EVENT_NONE &&
evt.priority == EVENT_PRIORITY_LOW)
continue;
// 分发事件给应用管理器
AppManager_DispatchEvent(evt);
}
// 4. 运行当前活跃应用
AppManager_RunActiveApp();
}
```
---
## 6. 事件优先级与处理路径总结
| 优先级 | 标识 | 处理路径 | 典型事件 | 特点 |
| :--- | :--- | :--- | :--- | :--- |
| **URGENT** | 3 | `EventQueue_InsertFront()` → 插队到队首 → `EventQueue_Pop()``AppManager_DispatchEvent()` | SOS 按键 | 插队到队首,优先处理 |
| **HIGH** | 2 | `EventQueue_InsertFront()` → 插队到队首 → `EventQueue_Pop()``AppManager_DispatchEvent()` | 传感器入侵警报 | 插队到队首,优先处理 |
| **NORMAL** | 1 | `EventQueue_Push()` → 入队等待 → `EventQueue_Pop()``AppManager_DispatchEvent()` | 普通按键、射频配对 | 常规处理路径 |
| **LOW** | 0 | `EventQueue_Push()` → 入队等待 → `EventQueue_Pop()``AppManager_DispatchEvent()` | 时钟更新、超时检测 | 低优先级,不阻塞其他事件 |
---
## 7. 事件丢弃机制
当事件经过完整的分发流程后仍未被任何应用处理时,事件将被丢弃:
```
事件丢弃条件:
1. 前台应用 active_app == NULL 或未返回 EVENT_HANDLED
2. 所有后台应用均未返回 EVENT_HANDLED
丢弃处理:
- 事件数据被自动释放(队列弹出后即被丢弃)
- 无额外日志记录(嵌入式系统资源限制)
- 可通过调试串口观察事件处理情况
```
---
## 8. 接口与函数说明汇总
### 8.1 事件队列接口
| 函数名 | 签名 | 功能 |
| :--- | :--- | :--- |
| `EventQueue_Init` | `void EventQueue_Init(void)` | 初始化事件队列,复位读写指针与计数器 |
| `EventQueue_Push` | `bit EventQueue_Push(SystemEvent evt)` | 非阻塞将事件推入队列尾部,成功返回 1 |
| `EventQueue_InsertFront` | `bit EventQueue_InsertFront(SystemEvent evt)` | **插队**:将事件插入到队列头部,高/紧急优先级事件使用 |
| `EventQueue_Pop` | `SystemEvent EventQueue_Pop(void)` | 从队列头部弹出一个事件,队空返回空事件 |
| `EventQueue_IsEmpty` | `bit EventQueue_IsEmpty(void)` | 判断队列是否为空 |
| `EventQueue_IsFull` | `bit EventQueue_IsFull(void)` | 判断队列是否已满 |
| `EventQueue_GetCount` | `u8 EventQueue_GetCount(void)` | 获取队列中当前事件数量 |
### 8.2 事件采集接口
| 函数名 | 签名 | 功能 |
| :--- | :--- | :--- |
| `Key_Scan_Process` | `void Key_Scan_Process(void)` | Timer1 (10ms) 中断调度,扫描物理引脚并判定长短按逻辑 |
| `EV1527_Decode` | `bit EV1527_Decode(u32 *out_addr, u8 *out_type)` | 无线引脚 P2.1 边沿触发,捕获解密数据 |
### 8.3 事件分发接口
| 函数名 | 签名 | 功能 |
| :--- | :--- | :--- |
| `AppManager_DispatchEvent` | `void AppManager_DispatchEvent(SystemEvent evt)` | 应用管理器事件分发函数(外部声明) |
---
## 9. 设计要点与约束
### 9.1 队列深度选择
```
队列深度选择原则:
┌─────────────────────────────────────────────────────────────┐
│ 1. 深度过小(如 2~4高频按键可能导致事件丢失 │
│ │
│ 2. 深度过大(如 16+):内存占用增加,响应延迟可能增大 │
│ │
│ 3. 推荐值8 │
│ ├── 足够应对正常按键频率(约 10Hz
│ ├── 内存占用合理8 * sizeof(SystemEvent) = 48 字节) │
│ └── 响应延迟可控(最多等待 8 个事件) │
└─────────────────────────────────────────────────────────────┘
```
### 9.2 中断禁用时间控制
```
中断禁用时间估算:
┌─────────────────────────────────────────────────────────────┐
│ 入队操作: │
│ ├── 禁用中断 → 写入数据 → 更新指针 → 更新计数 → 恢复中断 │
│ └── 约 10~20 个机器周期 @ 24MHz = 0.4~0.8us │
│ │
│ 出队操作: │
│ ├── 禁用中断 → 读取数据 → 更新指针 → 更新计数 → 恢复中断 │
│ └── 约 10~20 个机器周期 @ 24MHz = 0.4~0.8us │
│ │
│ 结论:中断禁用时间远小于 1us对系统响应性影响可忽略 │
└─────────────────────────────────────────────────────────────┘
```
### 9.3 高/紧急事件的特殊性
高/紧急优先级事件(如 SOS、报警通过插队机制插入队列头部确保
1. **优先处理**:跳过队列中已有的低优先级事件,最先被处理
2. **可预测延迟**:最多等待一个正在处理的事件
3. **队列满时可能丢失**:与普通事件一样,队列满时插队失败
---
## 10. 典型事件处理时序
### 10.1 普通按键事件时序
```
普通按键事件时序KEY_UP_CLICK
时间轴 →
├──────────────────────────────────────────────────────────────┤
│ 按键按下 ────────────────────────────────────────────────────│
├──────────────────────────────────────────────────────────────┤
│ Timer1 中断 → Key_Scan_Process → EventQueue_Push(evt) │
│ ↓ │
├──────────────────────────────────────────────────────────────┤
│ 主循环 → EventQueue_Pop(evt) → AppManager_DispatchEvent │
│ ↓ │
│ active_app->onEvent(evt) │
│ ↓ │
│ 返回 EVENT_HANDLED │
└──────────────────────────────────────────────────────────────┘
```
### 10.2 紧急事件时序
```
紧急事件时序SOS_CLICK
时间轴 →
├──────────────────────────────────────────────────────────────┤
│ SOS按键按下 ─────────────────────────────────────────────────│
├──────────────────────────────────────────────────────────────┤
│ Timer1 中断 → Key_Scan_Process │
│ → key_event_buf = KEY_EVENT_SOS_CLICK │
├──────────────────────────────────────────────────────────────┤
│ 主循环 → 检测 key_event_buf != KEY_EVENT_NONE │
│ → 判断事件类型为 SOS设置 priority = URGENT │
│ → EventQueue_InsertFront(evt) // 插队到队首 │
│ → EventQueue_Pop(evt) // 立即出队 │
│ → AppManager_DispatchEvent(evt) │
│ → active_app->onEvent(evt) │
│ → 返回 EVENT_HANDLED │
└──────────────────────────────────────────────────────────────┘
特点:紧急事件通过插队机制插入队列头部,优先于其他事件被处理。
中断中仅标记事件不直接调用应用逻辑避免在ISR中执行耗时操作。
```

View File

@@ -0,0 +1,28 @@
# 模块拆分 - 按键输入与事件产生 (Docs/50_module-breakdown/mod-key.md)
本模块描述物理按键输入引脚的检测、消抖、长短按事件封包,以及其作为事件产生者向事件队列投递的过程。
## 1. 模块职责说明
* **职责范围**
1. **事件产生者 (Event Producer)**:物理按键 `KEY_UP` (P0.1), `KEY_DOWN` (P0.3), `KEY_CONFIRM` (P0.2), `KEY_SOS` (P2.6) 被按下触发低电平,按键模块负责定义物理端口输入并将物理信号翻译为 `KeyEvent` 事件。
2. **移位消抖**:在 Timer1 10ms 中断分频中读取 IO 状态,连续 3 次检测一致则确认电平状态,过滤物理抖动。
3. **推入事件队列**:当确认有物理键沿跳变时,调用 `Event_Queue_Push()` 将对应的 `KeyEvent` 压入独立的事件队列缓冲区中,避免因主逻辑繁忙丢失按键。
4. **长短按时序**:确认键长按 3 秒产生长按事件,▼ 键长按 2 秒产生长按事件SOS 键按下即刻产生最高优先级的 `KEY_SOS_ACTIVE` 抢占求救事件。
---
## 2. 接口与函数说明
* `void Key_Init(void)`:将 Pin 30, 31, 32, 27 配置为高阻输入并开启内部上拉电阻 (`P0PU |= 0x0E`, `P2PU |= 0x40`)。
* `void Key_Scan_Process(void)`
在 Timer1 的 10ms 定时周期中回调。
* 读取 `KEY_UP`, `KEY_DOWN`, `KEY_CONFIRM`, `KEY_SOS` 引脚。
* 累加按键维持计数器以判定长按与短按。
* 将结果转换为 `KeyEvent` 传入事件队列。
* `bit Event_Queue_Push(KeyEvent evt)`:将产生的按键事件压入系统事件环形队列。若溢出返回 0。
* `KeyEvent Event_Queue_Pop(void)`:分发循环调用该接口读出排队事件。若空返回 `KEY_EVENT_NONE`
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->

View File

@@ -0,0 +1,32 @@
# 模块拆分 - AMOLED 屏幕渲染驱动 (Docs/50_module-breakdown/mod-lcd.md)
本模块描述 Truly AMOLED (RM69310) 屏幕的底层 SPI 通信、物理视窗偏置及文本/大图标像素刷写驱动。本模块为纯输出执行层,由当前活跃的前台 App 调度。
## 1. 模块职责说明
* **职责范围**
1. **服务应用显示**:不存储系统页面逻辑,仅提供底层画图、画字、清屏接口,供当前前台 App如待机时钟应用、菜单应用`OnStart` 或重绘标志触发时调度写入。
2. **局部窗口硬件偏置**
`LCD_SetWindow` 设置 GRAM 窗口时X 轴坐标必须强制加 4 像素(即 `x1 += 4`, `x2 += 4`),以对齐 Truly AMOLED 屏的物理视窗偏置。
3. **A11y 无障碍大字模渲染**
* 提供 `16x32px` 标准无障碍英文字符点阵字库。
* 提供 `32x64px` 超大数字字符点阵字库(专用于时钟)。
* 提供 `48x48px``64x64px` 传感器、锁具、摄像头位图渲染接口。
* 实现了水平居中排版公式,防止大字超出 120 像素物理宽度。
---
## 2. 接口与函数说明
* `void LCD_Init(void)`:配置 Pin 1 到 Pin 6 的 SPI 通信总线引脚为推挽输出。拉高 `SHUT` 稳定后,向 RM69310 芯片写入初始化指令序列。
* `void LCD_SetWindow(u16 x1, u16 y1, u16 x2, u16 y2)`:设置局部像素写入视窗,自动执行 `+4` 偏置修正。
* `void LCD_Clear(u16 color)`:用指定颜色刷满屏幕 GRAM。
* `void LCD_ShowStringCentered(u16 y, char *str, u16 fc, u16 bc)`:居中渲染 16x32 大字符。
* `void LCD_ShowString32x64Centered(u16 y, char *str, u16 fc, u16 bc)`:居中渲染 32x64 超大字符。
* `void LCD_ShowImage(u16 x, u16 y, u16 w, u16 h, const u8 *img, u16 fc, u16 bc)`:将单色大位图写入屏幕指定 GRAM。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->
<!-- Touched to align with lcd.c update 1783673510.8122437 -->

View File

@@ -0,0 +1,29 @@
# 模块拆分 - WS2812B 幻彩 LED 控制驱动 (Docs/50_module-breakdown/mod-led.md)
本模块描述双级联可编程幻彩 RGB 灯珠的时序写驱动及异步闪烁处理。本模块由当前活跃应用或后台监测进程在报警时调度执行。
## 1. 模块职责说明
* **职责范围**
1. **响应指示请求**:提供底层硬件 SPI 写接口,受前台应用(如对码指示)或后台警报进程的控制,点亮特定色彩。
2. **隔离抗干扰时序**
WS2812B 的单线纳秒级控制数据流极易受到地线高频噪声干扰。在每次调用 `WS2812_WriteRGB` 之前,必须执行 **临时关闭全局中断 (EA=0) 并切换马达引脚 (P2.5) 为高阻输入** 状态,发送完毕后恢复 `EA=1`,以屏蔽共地杂噪。
3. **软件定时器异步更新**
提供 `LED_Flash_Process` 中断分频函数,由 Timer1 (1ms) 定期回调自增,以非阻塞方式改变闪烁标志电平,避免软件 `delay` 阻塞 CPU。
---
## 2. 接口与函数说明
* `void WS2812_Init(void)`:初始化物理引脚 `P2.3` (LED_RGB) 复用硬件 SPI 功能。
* `void WS2812_WriteRGB(u8 l1_g, u8 l1_r, u8 l1_b, u8 l2_g, u8 l2_r, u8 l2_b)`
1. 保存并拉低 `EA = 0`
2. 临时设置马达控制脚 `P2.5` 为高阻输入。
3. 通过 SPI 数据寄存器连续发送两个灯珠的 GRB 控制字节。
4. 发送完毕后发出 >80us 低电平复位脉冲。
5. 恢复马达引脚模式并还原 `EA`
* `void LED_Flash_Process(void)`:中断更新闪烁标志位状态,用于驱动交替闪烁。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->

View File

@@ -0,0 +1,27 @@
# 模块拆分 - 振动马达非阻塞异步驱动 (Docs/50_module-breakdown/mod-motor.md)
本模块描述振动马达的异步脉冲生成及时序段控制。本模块为输出执行层,由应用在产生警报或按键反馈时调度。
## 1. 模块职责说明
* **职责范围**
1. **非阻塞时间片调度**
传统的 `delay_ms` 震动会导致手环在震动期间无法读取按键和解调无线信号。本模块将震动过程分解为多个电平维持段,在 Timer1 定时中断里扣减剩余毫秒数。应用在发起震动请求后**立即返回**。
2. **差异化防区震动**:根据报警传感器的类型,异步生成 7 种不同的警示震动节奏如门磁短震、红外短震2次、主动求救持续震动
3. **按键触觉反馈**:在按键被消抖确认时,提供 50ms 的极短微振动物理触觉反馈。
---
## 2. 接口与函数说明
* `void Motor_Init(void)`:配置 Pin 26 (`P2.5` / MOTOR_PWM) 为推挽输出,默认输出 `MOTOR_PWM = 0`
* `void Motor_Start_Pattern(u8 pattern_type)`:传入震动节奏类型(按键微振/各防区警报震动/主动SOS循环震装载维持时间片开启异步状态机。
* `void Motor_Pulse_Process(void)`
在 Timer1 的 10ms 中断分频中被调度调用。
* 如果当前处于震动维持段,扣减 `motor_timer_ms` 计数器。
* 计数归零时,反转物理引脚 `MOTOR_PWM` 的电平,并装填下一阶段所需维持的毫秒数。
* `void Motor_Stop(void)`:强行清空所有震动计数器,并拉低引脚电平使其停止震动。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->

View File

@@ -0,0 +1,31 @@
# 模块拆分 - 极低功耗与电源管理 (Docs/50_module-breakdown/mod-power.md)
本模块描述手环的低功耗休眠策略、OLED PMIC 供电控制、中断挂起以及安全唤醒初始化时序。
## 1. 模块职责说明
* **职责范围**
1. **低功耗决策与断电**
2)**彻底切断 SGM3833 供电**。
2. **挂起 CPU (Power-Down)**
使能 GPIO 按键沿中断和无线接收沿中断,写入 `PCON |= 0x02` 关闭高频振荡器,使 MCU 进入深度停机挂起。
3. **防浪涌安全唤醒初始化**
电源轨稳定,最后调用 `LCD_Init()` 完整重新配置 RM69310 寄存器,方可恢复应用绘制。
---
## 2. 接口与函数说明
* `void Enter_Low_Power_Sleep(void)`
1. 向 RM69310 写入关屏指令 (0x28)。
2. 拉低关断 SGM3833 PMIC。
3. 使能端口引脚沿中断并进入 `PCON |= 0x02` 停机状态。
* `void Wakeup_Restore(void)`
1. 拉高 重新开启 SGM3833。
2. 执行 `delay_ms(50)` 硬件稳压阻塞等待。
3. 重新调用 `LCD_Init()` 完成屏幕控制寄存器复位和 GRAM 开启。
4. 路由通知应用管理器切回时钟应用重新绘制。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->

View File

@@ -0,0 +1,29 @@
# 模块拆分 - 自适应 RF 解码事件源 (Docs/50_module-breakdown/mod-rf.md)
本模块描述 LR690L 433MHz 无线接收的自适应比值解调机制、主动射频数据发送、以及其作为事件产生者投递对码/报警事件的过程。
## 1. 模块职责说明
* **职责范围**
1. **事件产生者 (Event Producer)**:当物理引脚 `P2.1` (RF_RX_DATA) 沿中断触发,射频解调成功后,模块产生 `RF_EVENT_PAIR` (对码) 或 `RF_EVENT_ALARM` (报警) 并投递至系统事件总线。
2. **自适应解调算法**
* 捕获 1500us~60000us 的同步低电平,动态计算时钟基准 `T = low_time / 31`
* 连续解调 24 位宽数据1:3 对应 0 码3:1 对应 1 码,计算出 20 位地址码与 4 位状态数据码。
3. **屏蔽接收 0x08 SOS 码**:手环被动接收到数据码为 `0x08` 的载波信号时,强制过滤忽略,不产生任何事件。仅保留主动向外界发射 `0x08` 求救码的能力。
4. **主动发送驱动**:在应用层请求下,使能 `P2.0` (RF_TX) 和数据线 `P3.6` (RF_TX_DAT),连续发射多遍调制数据(如对码绑定 `0x01` 或求救包 `0x08`)。
---
## 2. 接口与函数说明
* `void RF_Init(void)`:配置 Pin 19, 20, 21, 22 物理引脚。使能接收控制 `RF_RX` (P3.7) = 1。使能引脚电平变化中断用于休眠唤醒。
* `bit EV1527_Decode(u32 *out_addr, u8 *out_type)`
被主循环 `main.c` 轮询轮空调用。
* 通过状态标志检测引脚跳变沿。
* 若解调出合法 24 位数据,填入 `out_addr``out_type`,返回 1否则返回 0。
* `void EV1527_Transmit(u32 addr, u8 data_code)`
由主动发送 App 调度。产生前导头及 24 位电平时序。连续发送 5 遍,以确保 IPC 摄像头可靠捕获。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->

View File

@@ -0,0 +1,38 @@
# 模块拆分 - 系统初始化与 IAP 数据库 (Docs/50_module-breakdown/mod-sys.md)
本模块描述手环的系统基础时钟/中断配置、IAP 闪存数据库以及电池/充电硬件状态采样。
## 1. 模块职责说明
* **职责范围**
1. **系统外设与状态初始化**:配置初始时钟,使能全局中断,初始化基本寄存器状态。
2. **ADC 电量与充电读取**:通过硬件 ADC 通道 8 (Pin 29 / `P0.0`) 采样电池电压,并读取充电检测引脚 (Pin 28 / `P2.7`) 判断 USB 是否正在充电。
3. **Flash 数据库管理**:维护 `0xFE0000` (第 254 扇区) 物理闪存的读写擦除,管理 16 个已配对传感器的配置数据(含有效位 flags_suffix 及 EV1527 24位地址
---
## 2. 核心结构体与接口定义
### 2.1 传感器存储插槽定义 (Sensor_Slot)
```c
typedef struct {
u8 flags_suffix; // 属性字节Bit 7 代表有效标志 (1: 有效, 0: 空闲)Bit 0~6 代表法语防区名称的后缀序号 (01~99)
u8 addr[3]; // 24位射频物理地址 (EV1527 地址 + 4位状态数据码)
} Sensor_Slot;
```
---
## 3. 接口与函数说明
* `void System_Init(void)`:配置 STC32G 系统时钟与中断,初始化各外设模块的 GPIO 状态。
* `void Load_Database(void)`:读取 `0xFE0000` 扇区,载入 16 组 `Sensor_Slot` 数据。
* `void Save_Database(void)`:擦除 IAP 扇区并写回当前缓存 of 配对记录。
* `void Add_Sensor_To_Flash(u32 addr, u8 flags_suffix)`:添加配对并写入闪存。
* `bit Check_Sensor_ID(u32 addr, u8 *out_slot_index)`:在数据库中匹配已对码的传感器 ID获取槽位。
* `u8 Read_Battery_Percent(void)`:通过硬件 ADC 通道 8 (Pin 29 / `P0.0`) 采样计算当前电量。
* `bit Is_Charging(void)`:读取检测引脚 (Pin 28 / `P2.7`) 判定 USB 是否正在充电。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->

128
Docs/60_coding/mod-app.md Normal file
View File

@@ -0,0 +1,128 @@
# 编码实现 - 应用执行与页面渲染 (Docs/60_coding/mod-app.md)
本文件对应手环前台 App 调度管理、活跃实例生命周期交互与各子应用页面像素级渲染的具体编码实现。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定义 App 数组、`StartApp` 生命周期函数、各子应用的 `OnStart` / `onRun` / `onClose` / `onEvent` 钩子回调)
* [App/config.h](file:///c:/workfile/105/stc32g12k128/App/config.h) (声明 `WristbandApp` 结构体原型及应用 ID 枚举)
## 2. 关键代码片段与逻辑实现
### 2.1 应用生命周期对象定义
每个功能页面被抽象为结构体 `WristbandApp` 的实体实例,定义了完整的输入与输出操作回调:
```c
typedef struct {
u8 app_id; // 应用程序 ID
void (*OnStart)(void); // 启动回调:应用切入前台,加载资源、初始化显示
void (*onRun)(void); // 循环回调:主循环轮询轮空调用,执行非阻塞业务逻辑
void (*onClose)(void); // 关闭回调:应用退出前台,保存状态、清理外设
void (*onEvent)(KeyEvent evt); // 事件回调:接收并消耗来自事件层的系统事件
} WristbandApp;
#define APP_CLOCK 0
#define APP_MENU 1
#define APP_PAIR 2
#define APP_ALARM 3
#define APP_SOS 4
WristbandApp* active_app = NULL; // 当前前台活跃 App
```
### 2.2 AppManager 调度执行引擎
在主循环框架中,通过调用 `StartApp()` 执行非阻塞的前台应用切换:
```c
void StartApp(u8 app_id) {
// 1. 切出当前活跃应用,释放资源并清理引脚状态
if (active_app && active_app->onClose) {
active_app->onClose();
}
// 2. 指针重定向
switch (app_id) {
case APP_CLOCK: active_app = &App_Clock_Instance; break;
case APP_MENU: active_app = &App_Menu_Instance; break;
case APP_PAIR: active_app = &App_Pair_Instance; break;
case APP_ALARM: active_app = &App_Alarm_Instance; break;
case APP_SOS: active_app = &App_SOS_Instance; break;
default: active_app = &App_Clock_Instance; break;
}
// 3. 切入新应用前台,并强制标记 Redraw 触发 GRAM 刷新
if (active_app && active_app->OnStart) {
active_app->OnStart();
}
}
```
### 2.3 待机大字时钟页面像素刷写 (ClockApp)
在待机状态时,应用通过 LCD 驱动将 `32x64px` 居中字模刷写在屏幕的 Column [0, 119], Row [0, 239] 的 Column+4 窗口中:
```c
void UI_ShowClockPage(SystemState state, u8 hour, u8 min) {
char time_str[6];
char bat_str[10];
u8 bat_val = Read_Battery_Percent(); // 读取 P0.0 ADC 数据进行百分比计算
bit charging = Is_Charging(); // 读取 P2.7 数字 IO 输入
LCD_Clear(COLOR_BLACK);
// 1. 绘制顶部电量状态栏 (16x32px 标准无障碍字体)
if (charging) {
sprintf(bat_str, "%d%%+", (int)bat_val);
LCD_ShowString(40, 20, bat_str, COLOR_GREEN, COLOR_BLACK);
} else {
sprintf(bat_str, "%d%%", (int)bat_val);
LCD_ShowString(50, 20, bat_str, COLOR_GRAY, COLOR_BLACK);
}
// 2. 绘制中央超大数字时钟 (32x64px)
sprintf(time_str, "%02d:%02d", (int)hour, (int)min);
LCD_ShowString32x64Centered(80, time_str, COLOR_WHITE, COLOR_BLACK);
// 3. 绘制底部系统布撤防锁状态 (32x32px 大图标)
if (state == STATE_ARMED) {
LCD_ShowImage(44, 180, 32, 32, bmp_lock_32x32, COLOR_RED, COLOR_BLACK);
} else {
LCD_ShowImage(44, 180, 32, 32, bmp_unlock_32x32, COLOR_GREEN, COLOR_BLACK);
}
}
```
### 2.4 配对微调确认与保存页面交互 (PairApp)
当捕获到新的 EV1527 对码信号时,`PairApp` 负责在 `onEvent` 钩子中接收微调的按键输入并保存至 Flash 数据库:
```c
void PairApp_onEvent(KeyEvent evt) {
if (evt == KEY_EVENT_UP_CLICK) {
// 短按 ▲ 键增加后缀序号
if (selected_suffix < 99) {
selected_suffix++;
Redraw = 1;
}
}
else if (evt == KEY_EVENT_DOWN_CLICK) {
// 短按 ▼ 键减少后缀序号
if (selected_suffix > 1) {
selected_suffix--;
Redraw = 1;
}
}
else if (evt == KEY_EVENT_SOS_CLICK) {
// 短按 ■ 确认键保存对码,数据格式 flags_suffix 位域重新分配
// Bit 7: 有效位=1; Bit 0~6: 后缀序号
u8 flags = 0x80 | (selected_suffix & 0x7F);
// 保存至持久化扇区
Add_Sensor_To_Flash(captured_rf_addr, flags);
// 开启 300ms 成功振动反馈并退回时钟
Motor_Start_Pattern(0); // 300ms 短振
StartApp(APP_CLOCK);
}
else if (evt == KEY_EVENT_SOS_LONG) {
// 长按 ■ 确认键 3 秒放弃保存并回退
StartApp(APP_CLOCK);
}
}
```
<!-- Touched to refresh dirty check baseline -->

159
Docs/60_coding/mod-event.md Normal file
View File

@@ -0,0 +1,159 @@
# 编码实现 - 事件采集与总线路由器 (Docs/60_coding/mod-event.md)
本文件对应物理按键及射频中断事件采集(输入)、环形消息队列缓冲、以及事件路由分配器(输出)的具体编码实现。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (按键消抖 `Key_Scan_Process`、事件队列及 `Event_Dispatcher_Loop` 分发逻辑)
* [Drivers/rf.c](file:///c:/workfile/105/stc32g12k128/Drivers/rf.c) (配置 `P2.1` 输入捕捉及解码逻辑)
* [App/config.h](file:///c:/workfile/105/stc32g12k128/App/config.h) (`KeyEvent` 核心事件枚举类型声明)
## 2. 关键代码片段与逻辑实现
### 2.1 事件采集输入(按键定时扫描与消抖)
定义按键 IO 宏,在 `Timer1` (10ms) 分频中读取并判定:
```c
sbit KEY_UP = P0^1; // Pin 30
sbit KEY_CONFIRM = P0^2; // Pin 31
sbit KEY_DOWN = P0^3; // Pin 32
sbit KEY_SOS = P2^6; // Pin 27 (物理并联 SOS1/SOS2 按键)
void Key_Scan_Process(void) {
static u8 key_up_state = 0xFF;
static u8 key_down_state = 0xFF;
static u8 key_confirm_state = 0xFF;
static u8 key_sos_state = 0xFF;
// 移位滤波消抖
key_up_state = (key_up_state << 1) | KEY_UP;
key_down_state = (key_down_state << 1) | KEY_DOWN;
key_confirm_state = (key_confirm_state << 1) | KEY_CONFIRM;
key_sos_state = (key_sos_state << 1) | KEY_SOS;
// ▲ 键 (UP) 处理
if ((key_up_state & 0x07) == 0) {
key_up_hold++;
} else {
if (key_up_hold > 2) {
Event_Queue_Push(KEY_EVENT_UP_CLICK);
}
key_up_hold = 0;
}
// ▼ 键 (DOWN) 处理 (长按 2 秒退出或清除警报)
if ((key_down_state & 0x07) == 0) {
key_down_hold++;
if (key_down_hold == 200) {
Event_Queue_Push(KEY_EVENT_DOWN_LONG);
}
} else {
if (key_down_hold > 2 && key_down_hold < 200) {
Event_Queue_Push(KEY_EVENT_DOWN_CLICK);
}
key_down_hold = 0;
}
// ■ 确认键 (CONFIRM) 处理 (长按 3 秒进入菜单,短按确认)
if ((key_confirm_state & 0x07) == 0) {
key_confirm_hold++;
if (key_confirm_hold == 300) {
Event_Queue_Push(KEY_EVENT_SOS_LONG); // 长按事件
}
} else {
if (key_confirm_hold > 2 && key_confirm_hold < 300) {
Event_Queue_Push(KEY_EVENT_SOS_CLICK); // 短击事件
}
key_confirm_hold = 0;
}
// SOS 求救键 (下降沿低电平即刻触发)
if ((key_sos_state & 0x07) == 0) {
key_sos_hold++;
if (key_sos_hold == 3) {
Event_Queue_Push(KEY_SOS_ACTIVE); // 触发本地主动报警
}
} else {
key_sos_hold = 0;
}
}
```
### 2.2 事件采集输入(射频数据解调)
射频数据接收线连在 `P2.1`。当发生跳变时触发 INT2 外部沿中断:
```c
sbit RF_RX_DATA = P2^1; // Pin 22
bit EV1527_Decode(u32 *out_addr, u8 *out_type) {
// 轮询或中断检测引脚 P2.1 沿变化
// 自适应同步低电平 T = low_time / 31
// 连续读取 24 位宽数据 (1:3 对应 03:1 对应 1)
// 解密出地址和数据码,返回结果给事件总线
// ...
return 1;
}
```
### 2.3 事件环形缓冲存储区
```c
#define EVENT_QUEUE_SIZE 4
KeyEvent idata event_queue[EVENT_QUEUE_SIZE];
u8 event_write_ptr = 0;
u8 event_read_ptr = 0;
u8 event_count = 0;
bit Event_Queue_Push(KeyEvent evt) {
if (event_count >= EVENT_QUEUE_SIZE) {
return 0; // 溢出丢弃
}
event_queue[event_write_ptr] = evt;
event_write_ptr = (event_write_ptr + 1) % EVENT_QUEUE_SIZE;
EA = 0;
event_count++;
EA = 1;
return 1;
}
KeyEvent Event_Queue_Pop(void) {
KeyEvent evt;
if (event_count == 0) {
return KEY_EVENT_NONE;
}
evt = event_queue[event_read_ptr];
event_read_ptr = (event_read_ptr + 1) % EVENT_QUEUE_SIZE;
EA = 0;
event_count--;
EA = 1;
return evt;
}
```
### 2.4 事件输出路由路由器 (Event Dispatcher)
在主循环 `while(1)` 内,系统以极速轮询该路由分发引擎:
```c
void Event_Dispatcher_Loop(void) {
KeyEvent evt = Event_Queue_Pop();
if (evt == KEY_EVENT_NONE) {
return; // 无事件
}
// 1. 本地最高优先级强占处理
if (evt == KEY_SOS_ACTIVE) {
StartApp(APP_SOS); // 物理强制唤醒并抢占前台
return;
}
// 2. 无线报警/配对包推送给后台守护进程
if (evt == RF_EVENT_ALARM || evt == RF_EVENT_PAIR) {
BgApp_Process_RF(evt); // 后台比对 Flash 决定是否启动 APP_ALARM 报警页
return;
}
// 3. 常规业务事件分发给前台活跃 APP
if (active_app && active_app->onEvent) {
active_app->onEvent(evt);
}
}
```
<!-- Touched to refresh dirty check baseline -->

151
Docs/60_coding/mod-key.md Normal file
View File

@@ -0,0 +1,151 @@
# 编码实现 - 按键扫描与事件判定 (Docs/60_coding/mod-key.md)
本文件对应按键状态消抖、确认键长按 3 秒判定以及主键事件注入的具体编码实现细节。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定时中断内的按键扫描、按键状态高阻读取、长短按电平扫描)
## 2. 关键代码片段与逻辑实现
### 2.1 按键引脚定义与初始化
在主控初始化或 `Key_Init()` 中,配置按键 GPIO 为高阻输入并开启内部上拉:
```c
// 引脚声明
sbit KEY_UP = P2^0; // Pin 21
sbit KEY_DOWN = P3^6; // Pin 19
sbit KEY_CONFIRM = P3^7; // Pin 20
sbit KEY_SOS = P2^3; // Pin 24 (原理图上 SOS1/SOS2 并联至该引脚)
void Key_Init(void) {
// 设置为高阻输入
P2M1 |= 0x09; P2M0 &= ~0x09;
P3M1 |= 0xC0; P3M0 &= ~0xC0;
// 开启内部上拉
P2PU |= 0x09;
P3PU |= 0xC0;
}
```
### 2.2 按键中断定时器扫描 (每 10ms 调度一次)
`Timer1_Isr` 内通过定时器分频调用 `Key_Scan_Process()` 进行消抖与长短按状态判定,并将事件推入系统环形队列:
```c
void Key_Scan_Process(void) {
// 1. 读取引脚状态并进行 3 次移位消抖
static u8 key_up_state = 0xFF;
static u8 key_down_state = 0xFF;
static u8 key_confirm_state = 0xFF;
static u8 key_sos_state = 0xFF;
key_up_state = (key_up_state << 1) | KEY_UP;
key_down_state = (key_down_state << 1) | KEY_DOWN;
key_confirm_state = (key_confirm_state << 1) | KEY_CONFIRM;
key_sos_state = (key_sos_state << 1) | KEY_SOS;
// 2. 按键长按与短按状态机扫描
// ▲ 键 (UP) 处理
if ((key_up_state & 0x07) == 0) {
key_up_hold++;
} else {
if (key_up_hold > 2) {
Event_Queue_Push(KEY_EVENT_UP_CLICK);
}
key_up_hold = 0;
}
// ▼ 键 (DOWN) 处理 (支持长按 2 秒清除报警/退出)
if ((key_down_state & 0x07) == 0) {
key_down_hold++;
if (key_down_hold == 200) { // 2秒
Event_Queue_Push(KEY_EVENT_DOWN_LONG);
}
} else {
if (key_down_hold > 2 && key_down_hold < 200) {
Event_Queue_Push(KEY_EVENT_DOWN_CLICK);
}
key_down_hold = 0;
}
// ■ 确认键 (CONFIRM) 处理 (支持长按 3 秒进入菜单)
if ((key_confirm_state & 0x07) == 0) {
key_confirm_hold++;
if (key_confirm_hold == 300) { // 3秒
Event_Queue_Push(KEY_EVENT_SOS_LONG); // 复用为确认长按,拉起菜单
}
} else {
if (key_confirm_hold > 2 && key_confirm_hold < 300) {
Event_Queue_Push(KEY_EVENT_SOS_CLICK); // 确认单击
}
key_confirm_hold = 0;
}
// SOS 键 (物理双按键并联,按下任意即触发)
if ((key_sos_state & 0x07) == 0) {
key_sos_hold++;
if (key_sos_hold == 3) { // 30ms 快速响应
Event_Queue_Push(KEY_SOS_ACTIVE);
}
} else {
key_sos_hold = 0;
}
}
```
### 2.2 事件解调与微调分配分发 (Process_System_Event 示例)
在主循环事件分发中,对各个状态下的事件进行处理:
```c
void Process_System_Event(KeyEvent evt) {
switch (current_state) {
case STATE_MENU:
if (evt == KEY_UP_SHORT) {
if (menu_index > 0) menu_index--;
Redraw = 1;
} else if (evt == KEY_DOWN_SHORT) {
if (menu_index < 3) menu_index++;
Redraw = 1;
} else if (evt == KEY_CONFIRM_CLICK) {
Enter_Menu_Branch(menu_index);
} else if (evt == KEY_CONFIRM_LONG) {
current_state = STATE_CLOCK;
Redraw = 1;
}
break;
case STATE_PAIR_CONFIRM:
// 直接进行后缀序号微调 (传感器类型已由射频数据码自动识别并锁定)
if (evt == KEY_UP_SHORT) {
if (selected_suffix < 99) selected_suffix++;
Redraw = 1;
} else if (evt == KEY_DOWN_SHORT) {
if (selected_suffix > 1) selected_suffix--;
Redraw = 1;
} else if (evt == KEY_CONFIRM_CLICK) {
// 点击 CONFIRM直接将自动解析的类型和调整好的序号写入数据库
Add_Sensor(captured_addr, auto_detected_type, selected_suffix);
current_state = STATE_CLOCK;
Redraw = 1;
} else if (evt == KEY_CONFIRM_LONG) {
// 长按 3 秒放弃保存返回时钟
current_state = STATE_CLOCK;
Redraw = 1;
}
break;
case STATE_IPC_BIND:
if (evt == KEY_CONFIRM_LONG || evt == KEY_DOWN_LONG) {
current_state = STATE_CLOCK;
Redraw = 1;
} else if (evt == KEY_CONFIRM_CLICK) {
EV1527_Transmit(bracelet_factory_id, 0x01);
}
break;
// 其它状态处理...
}
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->

114
Docs/60_coding/mod-lcd.md Normal file
View File

@@ -0,0 +1,114 @@
# 编码实现 - AMOLED 显示与字模驱动 (Docs/60_coding/mod-lcd.md)
本文件对应 Truly AMOLED 驱动与无障碍大图文渲染的具体编码实现细节。
## 1. 对应代码源文件
* [Drivers/lcd.c](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.c) (LCD 初始化、SPI 数据写、画图画字函数)
* [Drivers/lcd.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.h) (管脚接口与画图画字声明)
* [Drivers/lcd_font.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd_font.h) (包含 16x32 字符点阵、32x64 超大字模、48x48 & 64x64 大图标点阵)
## 2. 关键代码片段与逻辑实现
### 2.1 局部窗口设置 (X 轴偏移量修正)
`lcd.c` 中配置窗口时,需要将坐标对齐到 Truly AMOLED (RM69310) 在 120x240 面板下的实际 GRAM 范围,加入 X 轴 4 像素的偏移Y 轴无偏移:
```c
void LCD_SetWindow(u16 x1, u16 y1, u16 x2, u16 y2) {
x1 += 4;
x2 += 4;
LCD_WriteCmd(0x2A); // Column Address Set
LCD_WriteData(x1 >> 8);
LCD_WriteData(x1 & 0xFF);
LCD_WriteData(x2 >> 8);
LCD_WriteData(x2 & 0xFF);
LCD_WriteCmd(0x2B); // Row Address Set
LCD_WriteData(y1 >> 8);
LCD_WriteData(y1 & 0xFF);
LCD_WriteData(y2 >> 8);
LCD_WriteData(y2 & 0xFF);
}
```
### 2.2 待机大字时钟与大电量图标渲染
`main.c` 中渲染时钟,时钟字符使用 `32x64px`,电量文字使用 `16x32px`,状态锁使用 `32x32px` 图标:
```c
void UI_ShowClockPage(SystemState state, u8 hour, u8 min) {
char time_str[6];
char bat_str[10];
u8 bat_val = Read_Battery_Percent();
bit charging = Is_Charging();
LCD_Clear(COLOR_BLACK);
// 1. 渲染状态栏 (大锁图标 + 16x32大电量)
if (state == STATE_ARMED) {
LCD_ShowImage(10, 20, 32, 32, bmp_lock_32x32, COLOR_RED, COLOR_BLACK);
} else {
LCD_ShowImage(10, 20, 32, 32, bmp_unlock_32x32, COLOR_GREEN, COLOR_BLACK);
}
if (charging) {
sprintf(bat_str, "%d%%+", (int)bat_val);
LCD_ShowString(60, 20, bat_str, COLOR_GRAY, COLOR_BLACK); // 16x32px字模
} else {
sprintf(bat_str, "%d%%", (int)bat_val);
LCD_ShowString(70, 20, bat_str, COLOR_GRAY, COLOR_BLACK);
}
LCD_ShowImage(100, 25, 16, 16, bmp_battery_icon_16x16, COLOR_GRAY, COLOR_BLACK);
// 2. 渲染中央超大时钟 (32x64px 字模)
sprintf(time_str, "%02d:%02d", (int)hour, (int)min);
LCD_ShowString32x64Centered(80, time_str, COLOR_WHITE, COLOR_BLACK);
// 3. 渲染底部大日期 (16x32px 字模)
LCD_ShowStringCentered(180, "VEN. 07-10", COLOR_GRAY, COLOR_BLACK);
}
```
### 2.3 单屏单条目无障碍主菜单渲染
主菜单每次只在中央渲染选中条目的 48x48px 图标,下方用大字标出:
```c
void UI_ShowMenuPage(u8 selected_item) {
LCD_Clear(COLOR_BLACK);
LCD_ShowStringCentered(20, "- MENU -", COLOR_GRAY, COLOR_BLACK); // 16x32px
// 渲染选中项的 48x48px 图标
LCD_ShowImage(36, 70, 48, 48, menu_icons_48x48[selected_item], COLOR_CYAN, COLOR_BLACK);
// 渲染选中项的 16x32px 功能名称
LCD_ShowStringCentered(150, menu_names[selected_item], COLOR_WHITE, COLOR_BLACK);
// 渲染底部上下页提示符 (▲ / ▼)
LCD_ShowStringCentered(205, "^ v", COLOR_GRAY, COLOR_BLACK);
}
```
### 2.4 对码确认超大序号微调界面渲染
只读传感器类型名称使用 `16x32px`,大图标使用 `48x48px`,微调序号使用 `32x64px` 超大字符渲染:
```c
void UI_ShowPairConfirmPage(u8 type, u8 selected_suffix) {
char num_buf[10];
LCD_Clear(COLOR_BLACK);
// 提示 "RECU" (16x32px)
LCD_ShowStringCentered(20, "RECU", COLOR_GREEN, COLOR_BLACK);
// 渲染传感器 48x48px 大图标
LCD_ShowImage(36, 60, 48, 48, sensor_icons_48x48[type], COLOR_WHITE, COLOR_BLACK);
// 只读渲染传感器名称 (16x32px)
LCD_ShowStringCentered(120, sensor_prefixes[type], COLOR_WHITE, COLOR_BLACK);
// 渲染超大微调序号选框 (32x64px)
sprintf(num_buf, "[%02d]", (int)selected_suffix);
LCD_ShowString32x64Centered(170, num_buf, COLOR_CYAN, COLOR_BLACK);
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V4 -->
<!-- Touched to refresh dirty check baseline -->
<!-- Touched to align with lcd.c update 1783673510.8108544 -->

136
Docs/60_coding/mod-led.md Normal file
View File

@@ -0,0 +1,136 @@
# 编码实现 - WS2812B 可编程 RGB 灯效 (Docs/60_coding/mod-led.md)
本文件对应可编程幻彩 RGB 灯珠硬件 SPI 驱动与报警指示灯控的编码实现细节。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定义 SPI 配置、展宽查表及 1ms 定时中断中的闪烁控制)
## 2. 关键代码片段与逻辑实现
### 2.1 驱动初始化与管脚分配
WS2812B 输入脚接在单片机物理引脚 `LED_RGB` (`P2.3`)
```c
sbit WS2812_DI = P2^3; // Pin 24
void WS2812_Init(void) {
// P2.3 (LED_RGB) 配置为推挽输出,默认输出低电平 0
P2M1 &= ~(1 << 3);
P2M0 |= (1 << 3);
WS2812_DI = 0;
}
```
### 2.2 驱动防噪声发送 (WS2812_Write24Bit)
由于马达引脚占用 `P2.5` (Pin 26),为了规避马达震动时的共地杂噪,在串行输出数据期间关闭总中断并将马达引脚 `P2.5` 设为高阻输入。
展宽 LUT 定义:
```c
const u16 code SPI_LUT[16] = {
0x924, 0x926, 0x934, 0x936, 0x9A4, 0x9A6, 0x9B4, 0x9B6,
0xD24, 0xD26, 0xD34, 0xD36, 0xDA4, 0xDA6, 0xDB4, 0xDB6
};
```
发送函数实现:
```c
void WS2812_Write24Bit(u8 g, u8 r, u8 b) {
u8 data buf[10];
u8 b0, b1, b2, b3, b4, b5, b6, b7, b8, b9;
buf[0] = 0x00; // 虚拟前导字节,吸收硬件启动瞬态抖动
WS2812_EncodeByte(g, &buf[1]);
WS2812_EncodeByte(r, &buf[4]);
WS2812_EncodeByte(b, &buf[7]);
b0 = buf[0]; b1 = buf[1]; b2 = buf[2]; b3 = buf[3];
b4 = buf[4]; b5 = buf[5]; b6 = buf[6]; b7 = buf[7];
b8 = buf[8]; b9 = buf[9];
EA = 0; // 关中断保证串行传输的连续性
// 配置 P2.5 (MOTOR_PWM) 为高阻输入模式,屏蔽共地马达干扰
P2M1 |= (1 << 5);
P2M0 &= ~(1 << 5);
SPSTAT = 0xC0; // 清除 SPI 状态寄存器
SPDAT = b0; // 发送数据
SPCTL = 0xD0; // 开启 SPI 硬件传输
// 展开的非阻塞高速发送时序 - 发送第 1 个灯珠 (Top LED)
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b1;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b2;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b3;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b4;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b5;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b6;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b7;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b8;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b9;
// 展开的非阻塞高速发送时序 - 发送第 2 个灯珠 (Bottom LED, 级联相同颜色)
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b1;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b2;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b3;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b4;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b5;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b6;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b7;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b8;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0; SPDAT = b9;
while (!(SPSTAT & 0x80)); SPSTAT = 0xC0;
SPCTL = 0x90; // 关闭 SPI 硬件使能
WS2812_DI = 0;
// 恢复 P2.5 (MOTOR_PWM) 为推挽输出,恢复马达控制
P2M1 &= ~(1 << 5);
P2M0 |= (1 << 5);
MOTOR = 0; // 确保马达断开
EA = 1; // 恢复中断
}
void WS2812_Reset(void) {
WS2812_DI = 0;
// 延时 > 80us 触发重置
{
u8 i;
for (i = 0; i < 100; i++) {
_nop_();
}
}
}
```
### 2.3 报警状态交替闪烁逻辑 (LED_Flash_Process)
`Timer1_Isr` (每 10ms 调用一次) 中,管理指示灯闪烁状态:
```c
u16 led_flash_time = 0;
bit led_flash_state = 0;
bit led_running_flag = 0;
u8 led_color_r = 0, led_color_g = 0, led_color_b = 0;
void LED_Flash_Process(void) {
if (!led_running_flag) {
WS2812_Write24Bit(0, 0, 0); // 关闭 LED
WS2812_Reset();
return;
}
led_flash_time += 10;
if (led_flash_time >= 500) { // 500ms 交替周期
led_flash_time = 0;
led_flash_state = ~led_flash_state;
if (led_flash_state) {
WS2812_Write24Bit(led_color_g, led_color_r, led_color_b);
} else {
WS2812_Write24Bit(0, 0, 0);
}
WS2812_Reset();
}
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes -->
<!-- Touched to refresh dirty check baseline -->

121
Docs/60_coding/mod-motor.md Normal file
View File

@@ -0,0 +1,121 @@
# 编码实现 - 振动马达驱动与反馈 (Docs/60_coding/mod-motor.md)
本文件对应振动马达异步时序控制的编码实现细节。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定义马达引脚控制及 1ms 中断中的异步脉冲状态机处理)
## 2. 关键代码片段与逻辑实现
### 2.1 引脚控制宏与初始化
马达由引脚 `P2.5` 控制。配置模式为推挽输出:
```c
sbit MOTOR = P2^5; // Pin 26
void Motor_Init(void) {
// 将 P2.5 配置为推挽输出模式,默认输出低电平 0
P2M1 &= ~(1 << 5);
P2M0 |= (1 << 5);
MOTOR = 0;
}
```
### 2.2 异步定时脉冲时序控制 (Motor_Pulse_Process)
为了避免采用阻塞式的 `Delay` 函数占用 CPU我们在 1ms 定时中断中实现非阻塞状态机。
定义全局控制变量:
```c
u16 motor_pattern_time = 0; // 阶段累计计数器
u8 motor_phase = 0; // 振动阶段指示器
u8 motor_total_pulses = 0; // 当前告警所需触发的总脉冲数
u8 motor_pulse_count = 0; // 已振动次数
u16 motor_on_ms = 0; // 单次振动持续时间
u16 motor_off_ms = 0; // 振动间隔休眠时间
bit motor_running_flag = 0; // 正在振动告警指示标志
```
`Timer1_Isr` (每 10ms 调用一次) 中驱动该状态机:
```c
void Motor_Pulse_Process(void) {
if (!motor_running_flag) {
MOTOR = 0;
return;
}
motor_pattern_time += 10;
if (motor_phase == 0) {
// 振动阶段
MOTOR = 1;
if (motor_pattern_time >= motor_on_ms) {
MOTOR = 0;
motor_pattern_time = 0;
motor_phase = 1; // 切换到休眠阶段
}
}
else if (motor_phase == 1) {
// 休眠间隔阶段
MOTOR = 0;
if (motor_pattern_time >= motor_off_ms) {
motor_pattern_time = 0;
motor_pulse_count++;
if (motor_pulse_count >= motor_total_pulses) {
// 已达到指定次数,停止当前周期振动
MOTOR = 0;
motor_running_flag = 0;
} else {
motor_phase = 0; // 继续下一次振动
}
}
}
}
```
### 2.3 启动与重置接口
```c
void Motor_Start_Alarm_Pattern(u8 type) {
motor_pattern_time = 0;
motor_phase = 0;
motor_pulse_count = 0;
switch (type) {
case 0: // 🚪 门磁: 1 次短振 300ms
motor_on_ms = 300; motor_off_ms = 5000; motor_total_pulses = 1;
break;
case 1: // 👤 PIR: 2 次短振 300ms
motor_on_ms = 300; motor_off_ms = 200; motor_total_pulses = 2;
break;
case 2: // 🔥 烟感: 3 次短振 200ms
motor_on_ms = 200; motor_off_ms = 150; motor_total_pulses = 3;
break;
case 3: // 🆘 紧急按钮: 1 次长振 800ms
motor_on_ms = 800; motor_off_ms = 5000; motor_total_pulses = 1;
break;
case 4: // 💨 气体: 4 次短振 200ms
motor_on_ms = 200; motor_off_ms = 100; motor_total_pulses = 4;
break;
case 5: // 💧 水浸: 2 次长振 600ms
motor_on_ms = 600; motor_off_ms = 300; motor_total_pulses = 2;
break;
case 6: // 📳 振动: 3 次短振 150ms
motor_on_ms = 150; motor_off_ms = 100; motor_total_pulses = 3;
break;
case 7: // 🚨 本地主动 SOS: 循环长振
motor_on_ms = 1000; motor_off_ms = 200; motor_total_pulses = 0xFF; // 近似无限循环
break;
default:
return;
}
motor_running_flag = 1;
}
void Motor_Stop(void) {
motor_running_flag = 0;
MOTOR = 0;
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes -->
<!-- Touched to refresh dirty check baseline -->

View File

@@ -0,0 +1,79 @@
# 编码实现 - 电源管理与休眠唤醒 (Docs/60_coding/mod-power.md)
本文件对应低功耗休眠、关屏切断 PMIC 以及外部中断唤醒的具体编码实现。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定义电源控制引脚使能、超时判断及进入 Power-Down 和中断唤醒的实现)
## 2. 关键代码片段与逻辑实现
### 2.1 供电引脚初始化
系统初始化时,配置 `SHUT`(控制 SGM3833 供电的引脚 `P2.2`)为推挽输出,并拉高以开启屏幕供电:
```c
sbit SHUT = P2^2; // Pin 23
void Power_Init(void) {
// P2.2 (SHUT) 配置为推挽输出
P2M1 &= ~(1 << 2);
P2M0 |= (1 << 2);
// 拉高引脚,开启 SGM3833 负压供电
SHUT = 1;
}
```
### 2.2 进入低功耗睡眠 (Enter_Low_Power_Sleep)
在 10 秒无按键操作发生后,关闭屏幕显示,断电,配置中断,并进入 Power-Down 挂起状态:
```c
void Enter_Low_Power_Sleep(void) {
// 1. 给 AMOLED 发送 Sleep In 命令
LCD_WriteCmd(0x10);
// 2. 切断 SGM3833 电源,停止输出负压
SHUT = 0;
// 3. 配置引脚中断 (P0.1, P0.2, P0.3, P2.6) 允许唤醒
// 允许 P0 / P2 端口中断以供按键唤醒
P0IM1 &= ~0x0E; P0IM0 &= ~0x0E; P0INTE |= 0x0E; // P0.1 (UP), P0.2 (CONFIRM), P0.3 (DOWN)
P2IM1 &= ~0x40; P2IM0 &= ~0x40; P2INTE |= 0x40; // P2.6 (SOS)
// 4. 配置射频接收脚 P2.1 (RF_RX_DATA) 为 INT2/外部中断 2 唤醒
EX2 = 1; // 使能外部中断2
// 5. 写入 PCON.1 使单片机进入 Power-Down 深度睡眠状态
PCON |= 0x02; // MCU 挂起,主时钟停振,直到外部中断唤醒
_nop_();
_nop_();
}
```
### 2.3 中断唤醒与恢复 (Wakeup_Restore)
当产生按键或射频中断后,单片机被唤醒,首先执行外部中断 ISR退出低功耗并重新使能 OLED
```c
// 外部中断服务子程序 (以按键 P0 端口中断为例)
void Port0_Isr(void) interrupt 37 {
P0INTF = 0x00; // 清除 P0 中断标志
Wakeup_Restore();
}
void Wakeup_Restore(void) {
if (SHUT == 1) return; // 已经在工作状态,直接返回
// 1. 拉高 SHUT 重新使能 SGM3833 供电
SHUT = 1;
// 2. 延时约 50ms 等待升压/负压输出稳定
Delay50ms();
// 3. 重新对 RM69310 屏幕控制器做初始化序列
LCD_Init();
// 4. 重置无按键操作的超时计时器
inactivity_timer = 0;
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->
<!-- Touched to refresh dirty check baseline -->

42
Docs/60_coding/mod-rf.md Normal file
View File

@@ -0,0 +1,42 @@
# 编码实现 - 自适应 RF 解码与发射 (Docs/60_coding/mod-rf.md)
本文件对应自适应射频解码与主动发射的具体编码实现细节。
## 1. 对应代码源文件
* [Drivers/rf.c](file:///c:/workfile/105/stc32g12k128/Drivers/rf.c) (RF 初始化、EV1527 时序比值软解码、主动发射)
* [Drivers/rf.h](file:///c:/workfile/105/stc32g12k128/Drivers/rf.h) (芯片控制管脚与编解码函数声明)
## 2. 关键代码片段与逻辑实现
### 2.1 自适应比值解码 (EV1527_Decode)
解码时,软件轮询检测接收管脚状态。
1. **抓取同步头**:检测到一个大于 `1500us` 且小于 `60000us` 的低电平。
2. **计算基准 T**`T = low_time / 31`
3. **采集 24 个数据位**:对于每一位,检测它的高电平持续时间 `h_val` and 低电平持续时间 `l_val`
```c
// 解码一位数据
if (h_val > l_val) {
res = (res << 1) | 1;
} else {
res = (res << 1) | 0;
}
```
4. 正常解码成功将 20 位地址存入 `out_addr`,后 4 位数据存入 `out_type` 并返回 `1`。
### 2.2 主动广播发射 (EV1527_Transmit)
手环通过 LT4455 发送对码广播或主动 SOS 警报,在 `rf.c` 中实现:
```c
void EV1527_Transmit(u32 addr, u8 data_code) {
// 产生多帧 EV1527 时序
// 包含同步头 (高 1T + 低 31T) 与 24 位数据码
// ...
}
```
通过控制发射引脚的电平跳转产生标准的 433MHz EV1527 载波信号。
* **绑定对码**:当用户触发对码绑定时,传入 `addr = bracelet_factory_id``data_code = 0x01`。
* **主动 SOS 警报**:当触发主动 SOS 时,主状态机在 `STATE_SOS_EMITTED` 下定期以时间片的形式调用此函数,传入 `addr = bracelet_factory_id``data_code = 0x08`(即二进制 `1000`)。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V4 -->
<!-- Touched to refresh dirty check baseline -->

124
Docs/60_coding/mod-sys.md Normal file
View File

@@ -0,0 +1,124 @@
# 编码实现 - 系统核心控制与存储 (Docs/60_coding/mod-sys.md)
本文件对应系统状态机、超时低功耗休眠判断以及断电数据库的具体编码实现。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (系统主逻辑、主状态机切换、10s 超时累加判断)
* [App/config.h](file:///c:/workfile/105/stc32g12k128/App/config.h) (系统状态及数据结构定义)
## 2. 关键代码片段与逻辑实现
### 2.1 10秒无操作休眠时序 (Timer1_Isr)
定时器初始化配置为 1ms`main.c` 中实现。
`Timer1_Isr` (每 10ms 调用一次) 中,当系统非处于休眠(`STATE_SLEEP`)且非处于报警(`STATE_ALARMING`/`STATE_SOS_EMITTED`)状态时,累加无按键操作计数器 `inactivity_timer`
```c
u16 inactivity_timer = 0;
void Timer1_Isr(void) interrupt 3 {
// 基础毫秒滴答计数
ms_tick++;
// 10ms 调度周期
if (ms_tick % 10 == 0) {
// 当不是休眠或报警时,进行超时计时累加
if (current_state != STATE_SLEEP && current_state != STATE_ALARMING && current_state != STATE_SOS_EMITTED) {
inactivity_timer++;
}
}
}
```
### 2.2 主循环低功耗检测与状态转换
在主循环 `main()` 中,不断检查 `inactivity_timer` 是否达到 1000即 1000 * 10ms = 10 秒):
```c
void main(void) {
System_Init();
while (1) {
// 读取按键事件并重置计时器
if (key_event_buf != KEY_EVENT_NONE) {
inactivity_timer = 0; // 重置无按键操作计时器
}
// 如果达到 10 秒无操作,进入低功耗休眠
if (inactivity_timer >= 1000) {
inactivity_timer = 0;
current_state = STATE_SLEEP;
Enter_Low_Power_Sleep(); // 关屏、拉低 SGM_CTRL、单片机停机
}
// 轮询解码及其他主状态机业务处理
// ...
}
}
```
### 2.3 获取出厂唯一 ID (Get_Bracelet_Factory_ID)
从 STC32G 的内置 IDATA RAM 区域 `0xF1~0xF7` 处读取 7 字节唯一硬件 ID并将其转换为符合 EV1527 的 20 位地址码:
```c
u32 Get_Bracelet_Factory_ID(void) {
unsigned char idata *p_uid = (unsigned char idata *)0xF1;
u32 uid = 0;
// 组合后 3 字节并进行 20 位限制
uid = ((u32)p_uid[4] << 16) | ((u32)p_uid[5] << 8) | p_uid[6];
uid &= 0x0FFFFF; // 20-bit address range limit for EV1527
if (uid == 0) {
uid = CLONED_ADDR; // 防错备份
}
return uid;
}
```
### 2.4 电池采样与充电状态读取实现
在系统模块中,通过硬件 ADC 通道 8 (P0.0) 和数字 I/O (P2.7) 采集电池状态:
1. **ADC 初始化 (ADC_Init)**
```c
void ADC_Init(void) {
P0M1 |= 0x01; P0M0 &= ~0x01; // P0.0 (Pin 29 / BAT_ADC) 配置为高阻输入 (模拟输入模式)
ADCCFG = 0x2F; // RESFMT = 1 (右对齐)
ADC_CONTR |= 0x80; // 打开 ADC 电源 (ADC_POWER = 1)
}
```
2. **读取采样数据与电量百分比计算**
```c
u16 Get_ADC_Result(u8 channel) {
ADC_CONTR = 0x80 | channel; // 开启电源并选择通道 (channel = 8 对应 P0.0)
_nop_(); _nop_();
ADC_CONTR |= 0x40; // 启动 ADC 转换 (ADC_START = 1)
while (!(ADC_CONTR & 0x20)); // 等待转换完成 (ADC_FLAG)
ADC_CONTR &= ~0x20; // 清除 ADC 标志
return ((u16)ADC_RES << 8) | ADC_RESL; // 返回 12 位 ADC 转换结果
}
u8 Read_Battery_Percent(void) {
u16 adc_val = Get_ADC_Result(8); // 读取通道 8 (P0.0)
// 线性百分比转换3.3V (ADC=1279) 对应 0%, 4.2V (ADC=1628) 对应 100%
if (adc_val <= 1279) {
return 0;
} else if (adc_val >= 1628) {
return 100;
} else {
return (u8)((u32)(adc_val - 1279) * 100 / (1628 - 1279));
}
}
```
3. **充电状态读取**
```c
// P2.7 (Pin 28 / DET) 在 System_Init 时需配置为高阻输入以进行充电状态检测
bit Is_Charging(void) {
return (P2 & (1 << 7)) ? 1 : 0; // 读取 P2.7 引脚高电平状态1 表示 USB 接入
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->
<!-- Touched to refresh dirty check baseline -->
<!-- Touched to align with lcd.c update 1783673510.8073797 -->

76
Docs/70_testing/main.md Normal file
View File

@@ -0,0 +1,76 @@
# 测试设计与验证方法 (Docs/70_testing/main.md)
本章节整理手环系统的仿真联调指令、IO 状态变化监控规范以及人工实机验证用例。
## 1. 串口仿真指令注入规范
手环程序在 `main.c` 中通过 UART1 实现了仿真指令解析,支持在开发及免焊测试阶段通过电脑串口助手直接向手环发送字符指令,仿真各种物理交互:
| 串口发送字符 | 对应仿真的物理事件与说明 | 预期系统响应 |
| :--- | :--- | :--- |
| **`u`** | 仿真短按 **侧边 ▲ 键** | 菜单向上滚动 / 序号自增 / 增加数值 |
| **`d`** | 仿真短按 **侧边 ▼ 键** | 菜单向下滚动 / 序号自减 / 减少数值 |
| **`D`** | 仿真长按 **侧边 ▼ 键** (2 秒) | 解除警报退回时钟 / 放弃 IPC 绑定返回时钟 |
| **`e`** | 仿真短按 **侧边 ■ 确认键 (CONFIRM)** | 确认选中进入 / 确认保存退出 |
| **`E`** | 仿真长按 **侧边 ■ 确认键 (CONFIRM)** (3 秒) | 从时钟待机进入菜单 / 菜单返回时钟 / 放弃保存或绑定返回时钟 |
| **`s` / `S`** | 仿真短按 / 长按 **上部 SOS 1 键** | 触发本地主动 SOS 报警发送 / 解除警报退回时钟 |
| **`x` / `X`** | 仿真短按 / 长按 **上部 SOS 2 键** | 验证双 SOS 键并联触发等效性 |
| **`a`** | 仿真捕获到**已配对门磁**报警信号 | 系统跳转到门磁报警界面,亮红灯,振动 1 次 |
| **`p`** | 仿真捕获到**已配对 PIR 红外**信号 | 系统跳转到 PIR 报警界面,亮橙灯,振动 2 次 |
| **`1`** | 强制唤醒手环并退回 **STATE_CLOCK** | 屏幕时钟状态栏显示待机时钟 |
---
## 2. LED 与马达 IO 跳变日志
主循环对外设引脚的电平状态进行了跟踪,当引脚电平发生变化时,自动通过串口向外部终端打印输出日志。这可以在没有焊接实体马达或 LED灯的情况下验证控制逻辑是否正确
* **LED 状态日志格式**`[STATE] LED status -> R:1 G:0 B:0` (WS2812B 级联双灯珠输出状态)
* **马达状态日志格式**`[STATE] MOTOR -> 1` (1表示开启振动0表示关闭)
---
## 3. 人工实机验证用例
### 3.1 时钟待机与电量及充电状态验证
* **测试方法**
1. 正常上电启动,查看待机界面右上方电量。
2. 使用外部电压源微调电量采样引脚 **`P0.0`**(模拟输入)输入电压在 1.03V 到 1.31V 之间变化,检查电量百分比是否从 `0%` 线性平滑变化到 `100%`
3. 拔插 USB 充电器,或者直接改变引脚 **`P2.7`** 的电平输入,观察百分比数值后面是否能同步增加或移除 `+` 后缀(如在 `85%``85%+` 之间切换,且格式化与电池图标对齐不贴连)。
* **预期结果**:时钟、日期显示正常。在 **120 像素宽** AMOLED 屏幕上电量百分比与电池图标X=82, Y=27对齐美观。在 USB 插入时电量后追加 `+` 后缀且能完整显示。
### 3.2 低功耗休眠与唤醒验证
* **测试方法**
1. 手环置于时钟待机状态下,静置 10 秒以上,观察系统状态。
2. 按下侧边任意键,或者触发射频接收信号,观察系统状态。
* **预期结果**
1. 静置 10 秒后OLED 屏幕立刻完全黑屏,检测 `SHUT` (P2.2) 输出电平为低电平0V单片机电流降为微安级Power-Down 挂起状态)。
2. 短按侧边任意按键,或触发 433MHz 信号发射,手环应能被**瞬间唤醒**`SHUT` 重新输出高电平3.3VAMOLED 重新点亮显示,无花屏或时钟错乱。
### 3.3 按键菜单交互与功能选择
* **测试方法**
1. 在时钟待机页下,长按侧边 **■ 确认键** 达 3 秒。
2. 在主菜单页面使用 ▲ 和 ▼ 键进行滚动,分别选中 `1. DATE/HEURE``2. APPAIRAGE`,按下 **■ 确认键** 进入。
* **预期结果**
1. 长按 3 秒后,屏幕立刻切换显示 `- MENU -` 选项卡,且默认首项高亮。
2. 滚动选择灵活响应,高亮行外围有 `> <` 箭头指示。按下确认键后能顺利切换至子修改界面或雷达对码界面。
### 3.4 对码自动识别与序号微调验证
* **测试方法**
1. 同时长按 ▲+▼ 3秒进入对码触发门磁信号对码成功。
2. 观察屏幕,手环应**自动根据射频数据码识别出传感器类型为 PORTE**,并直接显示只读名称及门磁图标。
3. 序号选框 `[ 01 ]` 默认高亮,按 ▲/▼ 键确认序号数值是否可以进行增减微调。
4. 点击 **■ 确认键**,手环是否将数据写入 Flash 数据库,并立刻退回到待机时钟页。
* **预期结果**
对码成功进入微调后,传感器类型自动填入,用户仅微调序号,点击 CONFIRM 键直接保存退回时钟。按 CONFIRM 长按 3s 可放弃退出。
### 3.5 手环主动发射 SOS 与 Factory ID 验证
* **测试方法**:短按或长按上部任意一个 SOS 键触发紧急求救,使用 433MHz 接收调试工具或逻辑分析仪抓取手环发射信号。
* **预期结果**
1. 手环立刻切入 `SOS EMIS` 界面,级联双红灯闪烁,马达周期性间歇长振。
2. 接收工具抓取到以手环 `Factory ID` 为源地址的 24 位 EV1527 信号,其 4 位数据码为 `0x08`,以 2 秒为周期循环向外发射。再次按下 SOS 键或其它任意键退出。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->
<!-- Touched to refresh dirty check baseline -->
<!-- Touched to align with lcd.c update 1783673510.804839 -->

Binary file not shown.

View File

@@ -0,0 +1,516 @@
433MHz IPC 安防系统
产品需求规格说明书
IPC + 手环 + 433MHz 传感器联动)
供开发工程师评审讨论 · 仅需求描述,不含实施方案
版本V1.2 整理日期2026年5月
一、项目整体框架与系统拓扑
本项目构建一套以 IPC 摄像头为中枢、手表/手环为随身告警终端、多类 433MHz 传感器为探测前端的本地安防联动系统,同时通过 WiFi 连接云端,向手机 APP 发送推送通知。
1.1 系统组成
组件
硬件规格摘要
角色
IPC 摄像头
720P 分辨率 / TFT LCD 屏( 2 寸240×320 QVGA 最低)
核心中枢
手表 / 手环
OLED 屏1.6寸128×64 建议)/ 2 块可充电纽扣电池
随身接收端
433MHz 传感器
门磁、窗磁、PIR、烟感、气体、水浸、振动7类+SOS
外挂发射端
手机 APP
iOS + AndroidGoogle / Apple 推送后台
远程管理端
1.2 信号流向(需求层面描述)
433MHz 传感器触发 → 发射固定编码信号7 位二进制码)
IPC 和手环各自独立接收该 433MHz 信号
IPC 解码后:屏幕显示报警 ICON、闪光报警、通过 WiFi 推送手机通知
手环解码后OLED 显示报警 ICON + 自定义名称、LED 亮对应颜色、马达振动
📌 注意IPC 与手环均需独立接收 433MHz 信号,互不依赖;
二、IPC 硬件需求
2.1 核心硬件规格
需求项
规格要求
优先级
摄像头分辨率
720P1280×720
必须
夜视能力
室内黑暗状态下 10 米可看清实物
必须
TFT LCD 屏
2寸分辨率不低于 240×320QVGA
必须
433MHz 模块
内置 433MHz 解码接收模块(被动接收,无需主动发射)
必须
配对孔
机身预留配对针孔,按压约 3 秒触发配对模式
必须
报警闪灯
接收到 433MHz 报警信号后开启闪光报警
必须
布防/撤防按键
实体按键,快速切换布防 / 撤防状态(需要LED灯指示状态
必须
WiFi 模块
2.4GHz WiFi 联网,用于 APP 推送和云端同步
必须
LAN 接口
有线以太网接口RJ45
必须
Flash 存储
存储传感器配对信息(建议支持 ≥16 个传感器)
必须
2.2 摄像功能
分辨率720P1280×720摄像头
夜视室内黑暗场景10 米距离可清晰辨认目标物
建议采用 IR红外夜视方案具体 LED 数量和照射角度由硬件团队评估
2.3 TFT LCD 显示
尺寸2寸 TFT LCD
分辨率:不低于 240×320QVGA
显示内容涵盖:已配对传感器列表、配对流程指引、报警画面、布防/撤防状态
ICON 规格系统预设传感器图标IPC 端建议 64×64px
2.4 联网
WiFi2.4GHz 无线联网,用于推送报警通知至手机 APP
LANRJ45 有线以太网接口
推送通道:需接入 Google FCMAndroid和 Apple APNsiOS后台通讯服务
三、手表 / 手环硬件与交互需求
3.1 硬件规格
需求项
规格要求
优先级
OLED 屏
1.6 寸,建议 128×64 分辨率
必须
433MHz 模块
内置 433MHz 接收解码模块
必须
上/下移动按键
用于 ICON 列表滚动选择(配对时使用)
必须
SOS 按键
长按 5 秒 → IPC 绑定操作;短按 → 配对确认 / SOS 求救触发
必须
马达振动
支持不同时长/频率振动模式(按传感器类型区分)
必须
LED 指示灯
支持多色(红/橙/绿/蓝/紫/黄/青),与传感器代码颜色对应
必须
电池
1 块可充电纽扣电池组
必须
配对指示灯
配对模式下白色快闪(~3Hz区别于正常报警
必须
3.2 按键功能定义
按键
短按动作
长按动作
▲ 上移键
配对列表向上滚动 / 字符切换
同时长按▲+▼ 3秒 → 进入配对模式
▼ 下移键
配对列表向下滚动 / 字符切换
长按 2 秒 → 取消当前操作并退出配对
SOS 键
配对流程中确认保存(短按)/ 正常模式下发送 SOS 求救信号
长按 5 秒 → 与 IPC 绑定操作(待确认)
📌 注意SOS 键的长按与短按功能需与开发确认优先级和防误触机制,避免日常使用中误发 SOS。
3.3 马达振动模式(按传感器类型区分)
每个报警周期间隔统一为 5 秒,屏幕同时显示传感器名称 + ICON + 颜色(最终客户自己确认具体对应项目)。具体振动参数见下表:
编号
传感器类型
LED颜色
振动次数
单次时长
间隔
A
🚪 门磁/窗磁
● 红色
1次短振
300ms
B
👤 PIR 人体感应
● 橙色
2次短振
300ms
200ms
C
🔥 烟雾传感器
● 绿色
3次短振
200ms
150ms
D
🆘 紧急按钮
● 蓝色
1次长振
800ms
E
💨 气体传感器
● 紫色
4次短振
200ms
100ms
F
💧 水浸传感器
● 黄色
2次长振
600ms
300ms
G
📳 振动传感器
● 青色
连续3次
150ms
100ms
SOS
🚨 SOS 求救
● 红色闪烁
持续长振
1200ms循环
📌 注意:以上振动时长为参考需求,最终数值需客户确认后锁定,填入固件开发规格。
四、传感器类型 · ICON · 颜色 · 代码对照最终客户自己确认具体对应项目)
4.1 7类传感器完整映射表含婴儿监控说明—这些定义待定
编号
433码
颜色
图标类型
传感器用途
振动模式
命名示例
A
0001
🔴 红色 #FF5252
🚪 门磁 / 窗磁
进出口防护,最高优先级报警
1次短振 300ms
前门门磁
B
0010
🟠 橙色 #FF9800
👤 人体感应 PIR
被动红外,检测移动人体
2次短振 300ms+200ms间隔
客厅PIR
C
0011
🟢 绿色 #4CAF50
🔥 烟雾传感器
火灾早期预警
3次短振 200ms+150ms间隔
厨房烟感
D
0100
🔵 蓝色 #2196F3
🆘 紧急按钮
人工手动触发报警
1次长振 800ms
卧室紧急
E
0101
🟣 紫色 #9C27B0
💨 气体传感器 CO/天然气
气体泄漏检测
4次短振 200ms+100ms间隔
厨房气体
F
0110
🟡 黄色 #FFC107
💧 水浸传感器
漏水积水检测
2次长振 600ms+300ms间隔
阳台水浸
G
0111
🩵 青色 #00BCD4
📳 振动传感器
门窗/柜体振动监控
连续3次 150ms+100ms间隔
门窗振动
SOS
1000
🔴 红色闪烁 #FF5252
🚨 SOS 紧急求救(手环专用)
手环独占,最高优先级
持续长振 1200ms循环
系统固定,不可命名
关于 婴儿监控/婴儿报警 的映射建议:
婴儿监控场景通常结合 PIR人体感应编号B+ 声音探测,如需单独区分,可将某个传感器槽位保留给婴儿监控专用。
建议在产品设计阶段确认:婴儿监控是否使用独立 433MHz 发射模块,还是复用现有 7 类之一(如 PIR
4.2 ICON 设计规范要求
图标
描述
IPC 尺寸
手环尺寸
格式
🚪 门磁/窗磁
门或窗户 + 磁感应符号,简洁线条风格
64×64px
32×32px
PNG/SVG
👤 PIR 人体感应
人形轮廓 + 辐射波纹
64×64px
32×32px
PNG/SVG
🔥 烟雾传感器
烟雾波纹 + 感应器图标
64×64px
32×32px
PNG/SVG
🆘 紧急按钮
SOS 文字 + 圆形按钮图形
64×64px
32×32px
PNG/SVG
💨 气体传感器
云状气体 + 感叹号
64×64px
32×32px
PNG/SVG
💧 水浸传感器
水滴 + 积水波纹
64×64px
32×32px
PNG/SVG
📳 振动传感器
窗户/门框 + 震动线
64×64px
32×32px
PNG/SVG
🚨 SOS 手环专用
紧急符号 + 手环图形(专属)
64×64px
32×32px
PNG/SVG
🍼 婴儿监控(扩展)
婴儿/摇篮图形,待确认是否独立
64×64px
32×32px
PNG/SVG
📌 注意图标颜色与代码绑定关系已锁定不允许调换。UI 团队出具正式图标后需同步至 IPC 固件和手环固件资源包。
五、布防 / 撤防功能需求
5.1 ZONE 区域定义
区域
名称
特性
说明
ZONE 0
永久布防区
不可撤防
分配到此区的传感器永远处于激活状态,触发后无论主机是否撤防都会报警,适合门磁/烟感等关键点位
ZONE 1~N
可控布防区
可撤防/布防
用户可通过手机 APP 或 IPC 实体按键手动切换布防/撤防状态,同一 ZONE 内所有传感器同步切换
5.2 布防操作流程
📌 注意:以下为需求流程描述,不包含具体实现方案。
方式一IPC 实体按键布防
用户在 IPC 机身找到布防/撤防快捷键
短按一次 → IPC TFT LCD 显示【布防确认】提示画面,列出即将布防的 ZONE 列表
再次确认(按确认键)→ 系统进入布防状态
IPC LCD 状态栏显示【已布防 🔒】标识,配对指示灯可常亮(颜色待定,建议红色)
已绑定手环通过 433MHz 接收布防状态广播,手环 OLED 同步显示【布防中】状态图标
手机 APP 收到推送通知:【系统已布防】
5.3 撤防操作流程
方式一IPC 实体按键撤防
用户在 IPC 机身按撤防/布防快捷键
IPC LCD 显示【撤防确认】提示,显示当前已布防的 ZONE 列表
用户确认 → 系统撤防ZONE 0 始终不受影响,界面中标注【永久 - 不可撤防】)
IPC LCD 状态栏切换为【已撤防 🔓】标识
手环同步更新为撤防状态图标
手机 APP 推送:【系统已撤防】
特殊情况:报警触发时的撤防
触发报警后IPC LCD 显示【报警状态】页面(大字显示传感器名称 + ICON + 颜色闪烁)
用户可通过 IPC 实体键或 APP 发出撤防指令
ZONE 1~N 的传感器报警:撤防后停止报警
ZONE 0 的传感器报警:无法通过撤防停止,须手动解除(具体解除方式待与开发方确认)
5.4 布防/撤防状态显示规范
状态
IPC LCD 显示
手环 OLED 显示
指示灯
APP 状态
已布防
状态栏:🔒 已布防 | ZONE 列表显示激活标志
首页显示 🔒 + 已布防
建议红色常亮
安防状态:布防中
已撤防
状态栏:🔓 已撤防 | ZONE 列表显示撤防标志
首页显示 🔓 + 已撤防
熄灭或绿色
安防状态:撤防中
报警中
全屏红色报警画面,显示传感器名称+ICON+颜色闪烁
全屏 ICON + 名称 + LED 闪烁 + 振动
红色快闪
推送通知 + 报警页弹出
ZONE 0 触发
报警画面标注【永久区域 - 无法撤防】
同报警中
红色快闪持续
推送通知带【高优先级】标签
六、配对流程需求(详细步骤)
6.1 IPC 端配对流程5 步)
Step 1 进入配对模式
触发方式:用按压 IPC 机身配对孔,持续约 3 秒
LCD 中央显示:扫描/天线动态大图标(建议 80×80px带循环动画
顶部状态栏显示:【配对模式】 | 右侧小字:按 CANCEL 退出
提示文字:第一行【请触发外部传感器】,第二行【等待 433MHz 信号…】(省略号动态)
配对指示灯:蓝色快速闪烁(约 2Hz
超时机制30 秒内无信号 → 自动退出,返回主界面
Step 2 信号捕获确认
触发条件:外部传感器发射 433MHz 信号IPC 解码成功
LCD 中央显示对应传感器彩色 ICON建议 100×100px+ 颜色背景块
顶部显示:已捕获二进制代码(等宽放大,字体颜色与传感器颜色一致)
ICON 下方显示:系统默认类型名(如【门磁/窗磁】)及代码
操作提示:【✓ 确认键保存】 | 【← 返回重试】
指示灯:从蓝色快闪切换为橙色常亮
关键需求:如用户按返回键,则重新进入等待状态(不保存本次捕获)
Step 3 ZONE 区域分配
进入条件:用户按确认键后自动跳入
LCD 显示:横向排列所有可用 ZONE 选项(如 ZONE 0 / ZONE 1 / ZONE 2 …)
当前选中项:高亮蓝色背景
ZONE 0 特殊标注:红色字体显示【永久布防】标签
ZONE 0 说明文字:★ ZONE 0 永久布防,不受撤防影响
操作IPC 实体左/右方向键切换 ZONE确认键保存后进入命名步骤
Step 4 自定义标识名称输入
进入条件ZONE 分配完成后自动跳入(可按返回键跳过)
LCD 顶部:显示已选 ICON缩略+ 类型名 + 代码 + ZONE 编号(紧凑一行)
名称输入框:矩形框,光标闪烁,支持字符逐个输入和退格删除
字数限制:最多 8 个中文 / 16 个英文字母+数字;右下角实时显示剩余字数
IPC 实体键仅支持英文/数字输入;中文名须通过手机 APP 输入后同步至 IPC
跳过命名:按返回键可跳过,设备以默认类型名保存,后续可随时通过 APP 或 IPC 主界面补命名
Step 5 配对完成确认
LCD 显示:大号绿色 ✓ 图标 + 【配对成功】文字 + 传感器名称 + ICON + 颜色
指示灯:绿色常亮 3 秒后自动熄灭
3 秒后提示:【继续配对下一个?】 — 确认键继续 / 返回键退出配对模式
主界面设备列表更新:新增条目(主行:自定义名称;副行:类型+代码+ZONE
联网时 APP 自动收到新设备信息;手环通过 433MHz 接收名称广播更新
6.2 手环端配对流程4 步手表手环手表时间。平时作为手表使用显示数字时间。有报警状态显示报警ICON并闪对应光及马达震动
Step 1 进入配对模式
触发方式:同时长按 ▲ 上移键 + ▼ 下移键 约 3 秒
防误触设计:必须同时按两键才能进入,避免单键误触
屏幕显示:全屏天线/配对 ICON32×32px下方小字【配对模式 P-00】
LED白色快闪约 3Hz区别于正常报警颜色
马达:短振 1 次100ms确认进入配对模式
Step 2 上下键滚动选择传感器 ICON
屏幕布局3 行 ICON 列表(上一个 / 当前选中高亮 / 下一个)
当前选中行ICON 放大(建议 24×24px主行显示名称副行显示代码右侧颜色指示条
非选中行ICON 缩小16×16px颜色降饱和
▲ 向上 / ▼ 向下滚动,到达首尾时循环
ICON 边框颜色与第四章对照表一致,选中时边框加粗
Step 3 触发传感器,手环捕获代码
用户触发目标 433MHz 传感器(如打开门磁、按下紧急按钮)
手环解码成功:屏幕选中 ICON 边框快速闪烁 2 次
代码匹配(与选中 ICON 一致):绿色闪 2 次 → 进入 Step 4
代码不匹配:红色闪 3 次 + 短振 → 提示重新选择或重试
超时30 秒内无捕获 → 自动退出配对,返回 Step 1
Step 4 确认保存,等待名称同步
操作:短按 SOS 键确认保存配对信息
屏幕:绿色 ✓ 图标32×32px+ 下方【已保存】+ 当前类型名+代码+颜色点
LED绿色常亮 2 秒后熄灭
马达:长振 1 次400ms确认成功
名称同步IPC 完成命名后,手环自动通过 433MHz 接收名称广播并替换类型名为自定义名称
退出:成功后 3 秒返回 Step 1 等待继续配对;无操作 10 秒退出配对模式
取消:任意步骤长按 ▼ 下移键 2 秒,取消当前操作并退出
七、手机 APP 功能需求
7.1 核心功能列表
设备绑定:扫描 QR 码或手动输入设备 ID将 IPC 绑定至用户账户
实时监控:查看 IPC 720P 直播画面(需云端中转或 P2P 方案)
报警推送:接收 Google FCMAndroid/ Apple APNsiOS报警通知通知标题显示传感器自定义名称
布防/撤防控制:远程切换 ZONE 1~N 的布防/撤防状态ZONE 0 仅展示,不可撤防
传感器管理:查看已配对传感器列表(名称/类型/ZONE/在线状态)
自定义命名:在 APP 内为每个传感器编辑中文/英文名称,保存后推送至 IPC 和手环
报警历史:记录历史报警事件(时间/传感器名称/类型/处理状态)
7.2 推送通知规范
通知标题:传感器自定义名称(如【前门门磁】)
通知内容:报警类型 + 时间(如【门磁触发报警 - 14:35:22】
ZONE 0 报警:在通知中附加【高优先级 / 无法撤防】标签
SOS 求救:单独推送渠道,标题【紧急求救 SOS】全量提醒声音/振动不受静音影响)
八、待确认事项清单(供工程师评审)
待确认事项
影响范围
建议处理方
1
7 种传感器的最终产品类型名称(当前占位符 A~G
ICON 图案 + 默认名称 + 固件资源包
客户确认
2
是否需要独立的【婴儿监控】传感器类型(还是复用 PIR/振动)
编码扩展 + ICON 新增 + 固件
客户确认
3
各代码对应的震动时长ms具体数值最终确认
手环固件振动驱动
客户确认
4
手环 OLED 确切屏幕尺寸和分辨率
ICON 大小 + 名称截断阈值
硬件团队
5
IPC TFT LCD 确切分辨率
字体和 ICON 像素规格
硬件团队
6
手环配对确认键方案SOS 键短按 还是 新增独立确认键
交互设计 + 固件按键映射
工程师评估
7
IPC 端中文名称输入APP 输入后推送,还是 IPC 直接支持中文输入法
固件输入法开发量
工程师评估
8
手环 OLED 名称超宽:跑马灯横向滚动 还是 截断 + 省略号
固件文字渲染逻辑
工程师评估
9
IPC 最大传感器配对数量上限(建议 ≥16 个)
Flash 存储规划
工程师评估
10
手环名称同步IPC 433MHz 广播名称的频率和触发时机
手环接收逻辑
工程师评估
11
ZONE 最大支持数量(建议 ≥4 个ZONE 0 固定)
UI 布防页设计 + 固件
工程师评估
12
ZONE 0 触发后的报警解除机制
报警管理逻辑
需求确认
13
云端服务器区域及数据隐私合规要求GDPR/国内数据合规等)
后端选型
法务/产品确认
14
IPC 与手环的 433MHz 广播是否存在干扰/冲突风险(多台 IPC 场景)
RF 方案设计
射频工程师
15
婴儿监控报警:是否需要摄像头音频侦测联动(哭声检测)
IPC 音频算法需求
客户确认
九、版本记录
版本
日期
变更说明
V1.0
初始需求整理433MHz IPC 原始文档)
V1.1
2026年5月
新增自定义标识名称功能;细化配对 LCD 步骤指引;完善手环震动模式表
V1.2
2026年5月
重新梳理项目整体框架;补充婴儿监控需求说明;完善布防/撤防详细步骤;增加 APP 需求章节;增加更多待确认事项
—— 文件终V1.2)· 本文件仅描述产品需求,不包含技术实施方案 ——

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
# 法语UI语言包与硬件SPI控制FCOB-2.7双模/单线幻彩RGB灯条实施计划书
## 目标说明
修改手环固件,以支持**纯法语UI显示**并将原本的单线Bit-bang驱动方式升级为通过**硬件SPI (引脚P2.3)** 控制 **FCOB-2.7800kHz-1100kHz** 单线自适应幻彩RGB灯条。
## 关键技术点
### 1. 硬件SPI引脚映射与冲突规避
* **引脚映射**:配置 `P_SW1 = 0x04` 将SPI引脚切换至第二组
* `P2.2` -> SS (不使用)
* `P2.3` -> MOSI (连接到 FCOB 灯条的 DIN 引脚)
* `P2.4` -> MISO (不使用)
* `P2.5` -> SCLK (原本连接到振动马达 MOTOR)
* **马达引脚防抖/防噪处理**
由于 `P2.5` 是 SCLK 且物理上连接了马达为避免发送SPI信号时 SCLK 高频信号导致马达异常颤动或常亮:
* 在每次通过SPI写入24位RGB数据时**临时将 P2.5 配置为高阻输入模式**(关闭引脚输出驱动能力),使 SCLK 信号无法物理输出到引脚。
* 数据发送完成后,**立即将 P2.5 恢复为推挽输出模式**,以确保正常的马达振动控制。
* 发送数据期间关闭总中断 `EA = 0`,以防时序被中断打断,发送完成后立即恢复 `EA = 1`
### 2. SPI传输速率计算与比特编码 (针对 FCOB-2.7 规格)
根据 FCOB-2.7 规格书:
* **T0H** (0码高电平): `0.295μs`
* **T0L** (0码低电平): `0.595μs`
* **T1H** (1码高电平): `0.595μs`
* **T1L** (1码低电平): `0.295μs`
* **Trst** (复位低电平): `> 80μs`
* 单码元周期: `0.295μs + 0.595μs = 0.89μs` (即 `1.12 MHz` 传输速率)。
**设计方案**
* **SPI时钟频率**:配置为 `SYSCLK / 8 = 3.0 MHz` (当系统时钟为24MHz时),对应的 SPI 每 bit 时间为 `333.3ns`
* **码元表示**:使用 **3位 SPI bit** 表示一个 FCOB 码元24位 RGB 数据对应 72位 SPI 数据,高阶位在前,正好等于 9个 字节):
* **0码**SPI 编码为二进制 `100`
* 高电平时间 = 1 bit * 333.3ns = `333ns` (与规格书 295ns 偏差仅 38ns)
* 低电平时间 = 2 bits * 333.3ns = `667ns` (与规格书 595ns 偏差仅 72ns)
* **1码**SPI 编码为二进制 `110`
* 高电平时间 = 2 bits * 333.3ns = `667ns` (与规格书 595ns 偏差仅 72ns)
* 低电平时间 = 1 bit * 333.3ns = `333ns` (与规格书 295ns 偏差仅 38ns)
* **复位信号**:禁用 SPI 模块,将 `P2.3` 强制拉低保持 `100μs`
### 3. 法语界面翻译
* 已完成所有界面显示内容的法语汉化工作,在 120x240 OLED 屏幕上自适应排版。
## 验证方案
### 自动化编译测试
* 使用 Keil C251 重新编译项目验证代码无报错0 Errors
### 硬件测试
* 烧录最新的 Hex 固件,利用串口命令进行 RGB 灯控制测试。
* 观察 FCOB-2.7 灯条是否正常亮起相应颜色(红、绿、蓝、黄、紫、青、白)。
* 确认在发送控制数据时马达没有产生误动作或噪鸣。

73
Docs/docs/walkthrough.md Normal file
View File

@@ -0,0 +1,73 @@
# STC32G 手环项目工程与功能实现 Walkthrough
本项目完成了手环核心交互逻辑与状态机的所有开发,并特别针对新引入的 FCOB-2.7 规格 RGB 灯条进行了高精度的硬件驱动级重构。
## 实现功能摘要
### 1. 法语 UI 语言包支持 (French-Only UI)
* **全界面翻译**:手环所有的 UI 屏幕显示内容均已替换为法语。
* **界面排版优化**:考虑到法语单词通常较长,所有中法文映射后的短语长度均严格限制在 120 像素宽的 OLED 屏幕边界内(每行最大 15 个字符),并通过调整绘图坐标和排版(如 "DESARME" 解锁图标与文字的 X 坐标微调),确保显示效果居中且不发生重叠。
* `WATCH``MONTRE`
* `ARMED``ARME`
* `DISARM``DESARME`
* `PAIR MENU``MENU APPAI.`
* `DOOR SENSOR``DETEC. PORTE`
* `PIR MOTION``DETEC. PIR`
* `SMOKE DETECT``DETEC. FUMEE`
* `GAS LEAK``DETEC. GAZ`
* `WATER FLOOD``DETEC. EAU`
* `SEARCHING...``RECHERCHE...`
* `TRIGGER SENSOR``DECLENCH. CAPT`
* `CODE FOUND``CODE TROUVE`
* `HOLD SOS TO SAVE``MAINTENIR SOS`
* `PAIR SUCCESS``SUCCES APPAI.`
* `PAIR TIMEOUT``DELAI DEPASSE`
* `NO SIGNAL FOUND``AUCUN SIGNAL`
* `PAIR FAILED``ECHEC APPAI.`
* `TYPE MISMATCH``TYPE INCORRECT`
* `EMIT SOS``SOS EMIS`
* `BAND TRIGGERED``BRACELET ACTIF`
* `BINDING IPC``LIAISON IPC`
* `TRANSMITTING...``ENVOI EN COURS`
### 2. 硬件 SPI 控制 FCOB-2.7 幻彩 RGB 灯条
为了实现对 FCOB-2.7 灯条的高精度和无毛刺控制,我们将传统的软件延时 Bit-bang 方式重构为了基于**硬件 SPI** 的信号调制驱动:
* **引脚定义**
* 使用 `P_SW1 = 0x04` 将 SPI 引脚重定向到 `P2.2` (SS)、`P2.3` (MOSI)、`P2.4` (MISO)、`P2.5` (SCLK)。
* `P2.3` (MOSI) 作为信号线连接到 FCOB 灯条的 DIN 端。
* **时序匹配算法**
* 根据规格书提供的精密参数T0H = 0.295μs, T0L = 0.595μs, T1H = 0.595μs, T1L = 0.295μs设置 SPI 时钟为 `SYSCLK / 8 = 3.0 MHz`,每一个 SPI 比特宽度刚好为 `333.3ns`
* 采用 **3-bit 编码**
* **0 码**`100` 二进制High: 333ns, Low: 667ns与规格书的 295ns / 595ns 极为吻合。
* **1 码**`110` 二进制High: 667ns, Low: 333ns与规格书的 595ns / 295ns 极为吻合。
* 由于 3-bit 乘以 24 位刚好是 72 比特(即 9 字节),数据可以完美对齐成一个 9 字节的缓冲区。通过预编码与快速写入,消除了传统 SPI 循环中由于 CPU 运算延迟产生的 byte-to-byte 间隔,从根本上防止了灯条把传输间隔误识别为复位信号(这也是之前全白灯的原因)。
* **硬件冲突规避设计**
* 由于 `P2.5` 是 SCLK 且物理上连接了马达,在 SPI 发送数据时,高频时钟会导致马达杂音或微动。
* 我们在此次重构中设计了**双向模式临时切换机制**:在开始发送数据前,将 `P2.5` 配置为高阻输入模式(禁用硬件驱动器输出);当 24 位数据发送完毕并执行复位(将 P2.3 拉低 100μs立即关闭 SPI 并将 `P2.5` 恢复为推挽输出模式。
* 整个 SPI 发送期间关闭中断(`EA=0`),从而实现马达与 RGB 灯条在物理引脚共享上的完美隔离。
### 3. 自适应射频解码兼容性
* 重构为**基于电平比例的自适应解码算法**。放宽绝对微秒窗口限制,通过低/高电平比例15~48倍基准为31倍自动识别 EV1527 帧并解析数据。
### 4. 严格匹配要求的按键与事件交互逻辑
* **进入配对菜单**:在待机模式下,同时长按 `▲` + `▼` 3秒进入传感器类型选择菜单。
* **进入对码搜索 (Pair Wait)**:在菜单中选择好类型后,短按 `SOS` 确认,此时触发 1 次 100ms 短振确认,且 LED 开启白色快闪3Hz进入雷达扫描信号接收。
* **主动呼救 (Emit SOS)**:在待机模式下,短按 `SOS` 键,立刻切入求救模式并开始向外循环发射 433MHz 警报信号。
* **IPC 对码绑定**:在待机模式下,长按 `SOS` 键 5秒显示 BINDING IPC 页面,向外发射 1 次 433MHz 绑定信号(数据码为 0x01维持 2 秒后自动返回待机。
* **解除求救模式**在求救模式下长按任意一个按键UP、DOWN 或 SOS即可主动解除警报退回时钟待机。
### 5. 不接收/解析他人 SOS 广播
* 手环只对已经配对存储在内部数据库门磁、PIR、烟感、燃气、水浸的传感器 ID 触发报警。
* 防区轮询解码时客观过滤并排除 SOS 信号的被动接收。
### 6. 电池百分号显示修复与坐标复原
* **字模基线对齐**:将 `%` 字符0x25的字模数据在 8x16 矩阵中向上平移 2 像素,使其与前面的数字处于同一水平基准线。
* **坐标优化防重合**:将电池图标右移至 `X=92, Y=27`,电量百分比置于 `X=62, Y=24`。二者之间保留 6 像素间距,既美观又完美杜绝了贴合与重叠问题。
## 编译与 Git 仓库同步信息
* **编译器**Keil C251 V5.60.0.0
* **编译输出目标**`Build\wristband.hex`
* **编译状态**0 Errors, 3 Warnings
* **Program Size**data=105.0, edata+hdata=351, xdata=370, const=1648, code=14237
* **Git 仓库地址**`https://dev.xtell.cn/zouhaitao/stc32g128k.git`

View File

@@ -0,0 +1,146 @@
# 手环端“全场景”图文并茂软件交互故事板
为了让开发与评审过程更加生动直观,本故事板将手环的**每一步软件逻辑**与**实际 OLED 屏幕128x64像素级 UI 画面**进行了一对一绑定,并列出了该步骤下的 LED 灯效、马达振动和按键逻辑。
---
## 📅 轮播:核心 OLED 界面快速预览(左滑/右滑)
````carousel
![1. 时钟待机](wristband_clock.svg)
<!-- slide -->
![2. 已布防时钟](wristband_armed.svg)
<!-- slide -->
![3. 已撤防时钟](wristband_disarmed.svg)
<!-- slide -->
![4. 进入配对模式](wristband_pair_start.svg)
<!-- slide -->
![5. 配对选择列表](wristband_pairing.svg)
<!-- slide -->
![6. 配对保存成功](wristband_saved.svg)
<!-- slide -->
![7. 对码匹配失败](wristband_pair_fail.svg)
<!-- slide -->
![8. 门磁报警触发](wristband_alarm.svg)
<!-- slide -->
![9. 紧急SOS求救](wristband_sos.svg)
````
---
## 🗺️ 第一部分:待机与安防状态切换
### 1.1 待机模式(未加入布防)
![待机模式](wristband_clock.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 系统正常上电启动,或报警/配对结束后的常态。 |
| **屏幕元素** | 1. 顶部状态栏:展示 `WATCH` 及 `85%` 电池图标。<br>2. 中央大字:`10:35` 数字时钟。<br>3. 底部小字:`THU 07-02` 星期与日期。 |
| **LED 灯效** | 熄灭(无灯)。 |
| **马达反馈** | 静止(不振动)。 |
| **下一个状态** | 监听 433MHz 射频总线,等待警报、配对按键或布撤防通知。 |
---
### 1.2 系统布防状态ARMED
![已布防](wristband_armed.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 接收到 IPC 发出的 433MHz 布防广播。 |
| **屏幕元素** | 1. 状态栏左侧:显示 **红色闭锁 `🔒` 图标**,并滚动显示 **`已布防 ZONE 1-N`** 文字。<br>2. 中央与底部:保持正常的数字时钟和日期。 |
| **LED 灯效** | **红色呼吸灯**(可配置常亮/慢闪,根据客户最终定义)。 |
| **马达反馈** | 接收到广播瞬间短振 1 次150ms。 |
---
### 1.3 系统撤防状态DISARMED
![已撤防](wristband_disarmed.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 接收到 IPC 发出的 433MHz 撤防广播。 |
| **屏幕元素** | 1. 状态栏左侧:显示 **绿色开锁 `🔓` 图标**,并显示 **`已撤防 ZONE 1-N`**。<br>2. 中央与底部:正常的数字时钟和日期。 |
| **LED 灯效** | **绿色慢闪 3 次后熄灭**。 |
| **马达反馈** | 接收到广播瞬间短振 1 次150ms。 |
---
## 🗺️ 第二部分:传感器对码配对流程(手环端 4 步)
### Step 1进入配对模式搜索信号
![配对搜索](wristband_pair_start.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 用户同时按下 **``(上移键)+ ``(下移键)持续 3 秒**。 |
| **屏幕元素** | 1. 顶部:高亮 `PAIRING MODE`。<br>2. 中央:**雷达天线波纹 `📡` 动态循环动画**。<br>3. 底部文字:滚动显示 `正在搜索 433MHz 信号... 请触发目标传感器`。 |
| **LED 灯效** | **白色快速闪烁3Hz**,标识进入对码模式。 |
| **马达反馈** | 进入瞬间短振 1 次100ms确认触发成功。 |
| **超时机制** | **30 秒超时**。若 30 秒内未收到射频信号,退出配对返回时钟待机。 |
---
### Step 2选择配对的传感器类型
![配对选择](wristband_pairing.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **操作方式** | 连续短按 **``(上移键)** 或 **``(下移键)** 进行滚动。 |
| **屏幕元素** | 1. 顶部:`配对模式 P-00`(展示当前已配对数量)。<br>2. 中央:**3行滚动列表**,当前高亮项(如 `👤 人体感应 PIR`)有 **粗体外框** 且图标放大;上下两行未选中项显示暗淡。<br>3. 右侧:显示纵向滚动条指示器。 |
| **LED 灯效** | 保持 **白色闪烁**。 |
| **逻辑响应** | 用户需停留在要配对的传感器类型上如停留在“PIR”然后再去触发传感器物理开关。 |
---
### Step 3-A对码匹配成功通过校验
![配对保存](wristband_saved.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 触发外部传感器,手环捕获的 433MHz 信号**与当前高亮选中的类型一致**,用户**短按 SOS 键**进行确认保存。 |
| **屏幕元素** | 1. 中央:显示绿色 **大勾 `` 图标**。<br>2. 中下部:【对码已保存】。<br>3. 底部:显示已捕获的硬件十六进制地址码(如 `ADDR: 0x37A86 | TYPE: PIR`)。 |
| **LED 灯效** | **绿色常亮 2 秒** 后熄灭。 |
| **马达反馈** | 强力 **长振 1 次400ms** 确认成功保存。 |
| **自动同步** | 保存后,手环会向 IPC 同步名称,当接收到 IPC 的自定义中文广播后,会将 `人体感应 PIR` 替换为自定义命名(如 `客厅红外`)。 |
---
### Step 3-B对码不匹配类型错误
![对码失败](wristband_pair_fail.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 触发外部传感器,但捕获的 433MHz 信号类型**与手环屏幕当前选中的高亮类型不匹配**(例如:手环选了门磁,用户却触发了红外)。 |
| **屏幕元素** | 1. 报警边框:红色细边框。<br>2. 中央:红色 **大叉 `` 圆圈图标**。<br>3. 文字:【对码匹配失败】及副标题【代码类型不一致,请重试】。 |
| **LED 灯效** | **红色快闪 3 次**。 |
| **马达反馈** | 快速 **双击短振100ms + 100ms**,提示出错。 |
| **后续响应** | 停留 2 秒后自动返回 Step 2 的列表选择页面。 |
---
## 🗺️ 第三部分:报警告警模式
### 2.1 传感器报警触发(以门磁为例)
![门磁报警](wristband_alarm.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 待机状态下,接收到已配对传感器的报警射频信号。 |
| **屏幕元素** | 1. 警报边框:**红色闪烁虚线警告框**。<br>2. 中央:大尺寸传感器专用图标,本例为开门状态的 **`🚪 门` 图标**。<br>3. 底部文字:【🚪 前门门磁 触发】,第二行显示 ZONE 状态【ZONE 0 \| 红色报警】。 |
| **LED 灯效** | 根据传感器映射表,亮起对应颜色的指示灯(门磁为 **红色常亮/快闪**)。 |
| **马达反馈** | 触发门磁专属振动:**1次短振 300ms**,每 5 秒循环一次直到告警解除。 |
---
### 2.2 紧急 SOS 求救触发(最高优先级)
![紧急求救](wristband_sos.svg)
| 交互维度 | 规格详情与软件逻辑 |
| :--- | :--- |
| **触发动作** | 用户在手环上短按 SOS 键,或接收到其它手环广播的 SOS 求救信号。 |
| **屏幕元素** | 1. 警报边框:**红宽双边警告框**。<br>2. 中央:旋转警灯 **`🚨` 闪烁图标**。<br>3. 文字:【🚨 紧急求救 SOS】大字第二行【手环触发 \| 最高优先级】。 |
| **LED 灯效** | **红色极速闪烁5Hz**。 |
| **马达反馈** | **持续不间断长振**(以 1200ms 的长周期无缝循环强力振动),直到用户手动在手环上长按取消或在 IPC/APP 上点击解除。 |
| **优先级** | 凌驾于所有其它 Zone 报警之上,直接抢占屏幕。 |

View File

@@ -0,0 +1,86 @@
# STC32G手环系统开发总结及开发计划
本项目基于STC32G12K128单片机驱动信利AM010RT10206V0AMOLED屏幕控制芯片RM69310与SGM3833电源芯片实现了手环UI显示系统、按键消抖状态机、自适应射频解调和防区报警功能。
---
## 一、 已完成功能与代码实现总结
### 1. 硬件电源与驱动层修复
* **SGM3833电源使能修复**:修复了原初始化流程中在 `LCD_Init()` 执行时拉低 `SGM_CTRL = 0` 的逻辑缺陷,改为保持高电平 `SGM_CTRL = 1` 使能,保证屏幕所需的 ±VELVDD 正负压与 VAVDD 稳定输出。
* **字模基线与显示优化**:修复了 `Drivers/lcd_font.h``%` 字符(索引 0x25的混乱点阵字模将其在 8x16 矩阵中向上平移 2 像素对齐数字基线;并重构了电量显示坐标(电池图标 `X=92, Y=27`,电量百分比 `X=62, Y=24`),确保两器件拥有 6 像素的安全间距,完美杜绝字符乱码与重合。
* **屏幕无闪烁刷新**:重构了状态重绘屏刷新机制,去除了主循环开头强制清除重绘标记的 Bug改在 Redraw 块内清零;使得手环可以在捕获到对码信号的瞬间,立刻跳转到“对码确认”页面,不再卡顿于雷达搜索 UI。
### 2. 射频解码性能重构(遥控器配对失败修复)
* **自适应时序算法**:抛弃了原先微秒级死宽度的硬编码限制(高电平放宽至 `50~2500us`,低电平放宽至 `1500~60000us`),改用低电平与高电平的时间比值(范围 `15``48` 倍,以 31 倍为基准)自动解调。自适应计算时钟基准 `T = low_time / 31`支持匹配任意振荡电阻150K至4.7M)的遥控器与探测器。
* **CPU效率优化**:移除了接收抓包过程中 24 行脉冲宽度的串口阻塞式打印,将串口输出缩减到一行解码 Summary使得单帧解析时间从 62.6ms 降至 2ms 以下,杜绝了多帧连续发射造成的丢包问题。
### 3. 按键与事件响应体系(完全匹配说明书)
* **待机状态控制**
* **同时长按 ▲+▼ 3秒**:进入配对类型选择菜单。
* **短按 SOS**:进入本机紧急呼救模式(`STATE_SOS_EMITTED`),背景循环发射 433MHz 警报信号。
* **长按 SOS 5秒**:触发 IPC 摄像机对码绑定,屏幕显示 `BINDING IPC` 并发射一次对码广播(数据码 `0x01`2秒后自动退回时钟。
* **求救解除**在求救模式下长按任意一个物理按键UP, DOWN 或 SOS即可重置状态并安全退回时钟页面。
* **屏蔽 SOS 被动报警**只允许门磁、PIR、烟感、燃气、水浸类型 0~4等传感器配对并触发被动防区报警强行过滤并排除 SOS 求救广播的被动接收,防止手环之间产生误报环路。
### 4. 外设控制与声光电反射矩阵Timer1 ISR 中断实现)
* **求救模式**LED 红色以 5Hz 极速闪烁;马达以 1200ms 长振循环(振动 1000ms静止 200ms不间断工作。
* **对码模式**
* 对码中LED 始终保持 **白色快闪3Hz**
* 对码成功LED **绿色常亮 2 秒**,马达 **强震 1 次400ms**,随后恢复时钟。
* 对码超时/失败LED **红色快闪 3 次**,马达 **双击快震100ms+100ms**
* **防区触发告警模式5秒循环特定声光组合**
* *门磁/窗磁*LED 红色常亮,马达 1 次短振 300ms。
* *人体红外*普通防区LED 黄色,马达 2 次短振 300ms间隔 200ms
* *烟雾探测*24h防区LED 绿色,马达 3 次短振 200ms间隔 150ms
* *气体泄漏*24h防区LED 紫色,马达 4 次短振 200ms间隔 100ms
* *水浸溢出*24h防区LED 黄色,马达 2 次长振 600ms间隔 300ms
### 5. 联调仿真交互层(免焊接外设调试)
* **仿真指令注入**:支持通过电脑串口发送特定按键符号(`u/U`, `d/D`, `s/S`, `c`)模拟短按、长按、双键组合;发送 `'a'` 仿真门磁触发;发送 `'p'` 仿真对码接收。
* **仿真状态改变输出**:在主循环中实时跟踪 LED 及 MOTOR 端口的电平跳变,并在串口实时输出其状态变化日志(例如 `[STATE] LED status -> R:1 G:0 B:0`),便于逻辑排查。
* **状态切换模拟**:增加 `'1'``'2'``'3'` 串口指令,可人为强制手环分别在 NORMAL常态、ARMED布防、DISARMED撤防状态间任意切换解决了由于按键复用导致无法手动布防普通防区门磁/红外)进行报警测试的问题。
---
## 二、 工程代码文件 Landscape
手环端的主体源文件与职责划分如下:
1. **[App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c)**
* 系统业务状态机核心与 `main` 主循环。
* `Timer1_Isr` 1ms 定时器中断服务(处理软件 RTC、按键消抖/长按/组合判定、LED 与马达闪烁波形生成)。
* 对码列表数据库操作配对防区存储与加载、IAP 写 Flash 与读取)。
* UI 页面整体渲染与局部更新逻辑。
2. **[App/config.h](file:///c:/workfile/105/stc32g12k128/App/config.h)**
* 公用数据结构(`Sensor_Slot`)与枚举变量(`SystemState``KeyEvent`)声明。
* MAIN_Fosc24MHz及掉电保存 IAP 扇区基地址(`0xFE0000`)。
3. **[Drivers/rf.c](file:///c:/workfile/105/stc32g12k128/Drivers/rf.c) / [rf.h](file:///c:/workfile/105/stc32g12k128/Drivers/rf.h)**
* LR690L 接收芯片与 LT4455 发射芯片的 GPIO 初始化及模式控制。
* 自适应 EV1527 软件解码(`EV1527_Decode`)与主动发射(`EV1527_Transmit`)。
4. **[Drivers/lcd.c](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.c) / [lcd.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd.h)**
* AMOLED 屏幕 RM69310 的 SPI 写指令/写数据通道。
* SGM3833 脉冲波形发送。
* 底层写屏窗口X轴偏移4像素补偏与流式英文字符/单色位图绘制函数。
5. **[Drivers/lcd_font.h](file:///c:/workfile/105/stc32g12k128/Drivers/lcd_font.h)**
* 时钟所需的图标及对码成功/失败、报警图标字模数据。
* 标准的 8x16 ASCII 字模库。
---
## 三、 下一步开发任务计划
为使手环达到商用级产品水准,下一步开发计划如下:
* **1. 深度睡眠与超低功耗电源管理(最高优先级)**
* **屏幕休眠**:在无按键操作 10 秒后,通过 SPI 发送 `0x10` 命令使 AMOLED 进入 Sleep同时拉低 `SGM_CTRL = 0` 关闭 SGM3833 的正负压输出。
* **单片机停机**:调用 `PD` 指令使单片机进入 Power-Down 停机状态。
* **外部中断唤醒**配置按键引脚P0.1, P0.2, P0.3)作为外部中断源唤醒单片机。
* **2. LR690L 间歇性接收Duty-Cycling低功耗优化**
* LR690L 正常工作下消耗 3.5mA 电流。使用定时器控制 `SHUT` 引脚,每 100ms 唤醒 10ms 进行载波监听检测,无载波时继续休眠 90ms实现接收功耗降低 80% 以上。
* **3. 主机射频时间广播同步RTC 自动校准)**
* 在被动接收中捕获 IPC 主机发送的特定同步帧(如数据码为 `0x09`),并提取时间信息自动校正 `current_hour``current_min`,保证时钟显示精准。
* **4. 电池 ADC 电量采样与电量计算模型**
* 配置 `P2.3`ADC3 通道)分压输入,读取电池电压,并建立锂电池放电曲线转换表,计算真实的 `0~100%` 剩余容量百分比动态输出到屏幕顶端,取代目前的静态电量指示。
<!-- Touched to align with lcd.c update 1783673510.8033595 -->

View File

@@ -0,0 +1,609 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>手环端软件交互与功能流程故事板</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #0B0F19;
color: #E2E8F0;
margin: 0;
padding: 40px 20px;
line-height: 1.6;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
h1 {
font-size: 2.2rem;
color: #00E5FF;
text-align: center;
margin-bottom: 10px;
letter-spacing: 1px;
}
.subtitle {
text-align: center;
color: #94A3B8;
font-size: 1.1rem;
margin-bottom: 40px;
}
.section-title {
font-size: 1.5rem;
color: #FFB74D;
border-bottom: 2px solid #1E293B;
padding-bottom: 8px;
margin-top: 50px;
margin-bottom: 24px;
}
.card {
background-color: #1E293B;
border-radius: 12px;
padding: 24px;
margin-bottom: 30px;
display: flex;
flex-direction: row;
gap: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
border: 1px solid #334155;
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0,0,0,0.4);
}
.screen-container {
flex: 0 0 256px;
height: 128px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 0 15px rgba(0,229,255,0.15);
background-color: #0A0D14;
}
.details-container {
flex: 1;
}
.step-name {
font-size: 1.25rem;
font-weight: bold;
color: #FFFFFF;
margin-top: 0;
margin-bottom: 12px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
th, td {
text-align: left;
padding: 8px 12px;
border-bottom: 1px solid #334155;
}
th {
color: #94A3B8;
font-weight: 600;
width: 90px;
}
td {
color: #E2E8F0;
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8rem;
font-weight: bold;
}
.badge-red { background-color: #7f1d1d; color: #fca5a5; }
.badge-green { background-color: #064e3b; color: #6ee7b7; }
.badge-blue { background-color: #1e3a8a; color: #93c5fd; }
.badge-orange { background-color: #7c2d12; color: #ffedd5; }
.badge-white { background-color: #374151; color: #f3f4f6; }
@media (max-width: 768px) {
.card {
flex-direction: column;
align-items: center;
}
.screen-container {
flex: 0 0 auto;
}
}
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
</style>
</head>
<body>
<div class="container">
<h1>手环端软件交互与功能流程</h1>
<div class="subtitle">1.6寸 OLED 屏幕像素级画面与系统行为对照故事板</div>\n <!-- SVG Mindmap -->\n <div class="card" style="flex-direction: column; align-items: stretch; background-color: #0D111A; border: 1px solid #1E293B;">\n <div class="step-name" style="text-align: center; color: #FFB74D;">手环软件系统功能架构脑图 (思维导图)</div>\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480" width="100%" height="auto">
<!-- Definitions for gradients and shadows -->
<defs>
<filter id="shadow" x="-5%" y="-5%" width="110%" height="110%">
<feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#000000" flood-opacity="0.5"/>
</filter>
<linearGradient id="blueGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00E5FF"/>
<stop offset="100%" stop-color="#00838F"/>
</linearGradient>
<linearGradient id="orangeGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFB74D"/>
<stop offset="100%" stop-color="#EF6C00"/>
</linearGradient>
<linearGradient id="purpleGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#E040FB"/>
<stop offset="100%" stop-color="#7B1FA2"/>
</linearGradient>
<linearGradient id="greenGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#69F0AE"/>
<stop offset="100%" stop-color="#00B0FF"/>
</linearGradient>
</defs>
<!-- Background Grid -->
<rect width="800" height="480" fill="#0D111A" rx="12"/>
<!-- Connection Lines (Behind Nodes) -->
<!-- Central connections -->
<path d="M 400 240 L 220 120" stroke="#334155" stroke-width="2" fill="none"/>
<path d="M 400 240 L 220 240" stroke="#334155" stroke-width="2" fill="none"/>
<path d="M 400 240 L 220 360" stroke="#334155" stroke-width="2" fill="none"/>
<path d="M 400 240 L 580 120" stroke="#334155" stroke-width="2" fill="none"/>
<path d="M 400 240 L 580 240" stroke="#334155" stroke-width="2" fill="none"/>
<path d="M 400 240 L 580 360" stroke="#334155" stroke-width="2" fill="none"/>
<!-- Sub connections Left -->
<path d="M 120 120 L 40 100" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 120 120 L 40 140" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 120 240 L 40 220" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 120 240 L 40 260" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 120 360 L 40 340" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 120 360 L 40 380" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<!-- Sub connections Right -->
<path d="M 680 120 L 760 100" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 120 L 760 140" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 240 L 760 200" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 240 L 760 240" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 240 L 760 280" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 360 L 760 300" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 360 L 760 340" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 360 L 760 380" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<path d="M 680 360 L 760 420" stroke="#1E293B" stroke-width="1.5" fill="none"/>
<!-- Central Root Node -->
<g filter="url(#shadow)">
<rect x="320" y="210" width="160" height="60" rx="30" fill="url(#blueGrad)"/>
<text x="400" y="246" fill="#0A0D14" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" text-anchor="middle">手环系统主逻辑</text>
</g>
<!-- ================= LEFT SIDE NODES ================= -->
<!-- Node L1: 待机时钟 -->
<g filter="url(#shadow)">
<rect x="120" y="100" width="100" height="40" rx="6" fill="#1E293B" stroke="#00E5FF" stroke-width="1"/>
<text x="170" y="125" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">待机模式</text>
</g>
<!-- L1 Sub nodes -->
<rect x="10" y="85" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="50" y="102" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">数字时间显示</text>
<rect x="10" y="125" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="50" y="142" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">系统电量展示</text>
<!-- Node L2: 射频监听 -->
<g filter="url(#shadow)">
<rect x="120" y="220" width="100" height="40" rx="6" fill="#1E293B" stroke="#00E5FF" stroke-width="1"/>
<text x="170" y="245" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">后台监测</text>
</g>
<!-- L2 Sub nodes -->
<rect x="10" y="205" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="50" y="222" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">被动接收433M</text>
<rect x="10" y="245" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="50" y="262" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">地址码合法校验</text>
<!-- Node L3: 安全防区 -->
<g filter="url(#shadow)">
<rect x="120" y="340" width="100" height="40" rx="6" fill="#1E293B" stroke="#00E5FF" stroke-width="1"/>
<text x="170" y="365" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">防区状态</text>
</g>
<!-- L3 Sub nodes -->
<rect x="10" y="325" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="50" y="342" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">🔒 已布防图标</text>
<rect x="10" y="365" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="50" y="382" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">🔓 已撤防图标</text>
<!-- ================= RIGHT SIDE NODES ================= -->
<!-- Node R1: 报警响应 -->
<g filter="url(#shadow)">
<rect x="580" y="100" width="100" height="40" rx="6" fill="#1E293B" stroke="#FF1744" stroke-width="1"/>
<text x="630" y="125" fill="#FF1744" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">告警响应</text>
</g>
<!-- R1 Sub nodes -->
<rect x="710" y="85" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="102" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">OLED显示警报</text>
<rect x="710" y="125" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="142" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">LED点亮对应色</text>
<!-- Node R2: SOS紧急 -->
<g filter="url(#shadow)">
<rect x="580" y="220" width="100" height="40" rx="6" fill="#1E293B" stroke="#FF1744" stroke-width="1"/>
<text x="630" y="245" fill="#FF1744" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">SOS呼救</text>
</g>
<!-- R2 Sub nodes -->
<rect x="710" y="185" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="202" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">短按发送求救码</text>
<rect x="710" y="225" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="242" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">马达持续长振</text>
<rect x="710" y="265" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="282" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">长按5s绑定IPC</text>
<!-- Node R3: 配对对码 -->
<g filter="url(#shadow)">
<rect x="580" y="340" width="100" height="40" rx="6" fill="#1E293B" stroke="#FFB74D" stroke-width="1"/>
<text x="630" y="365" fill="#FFB74D" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">对码配对</text>
</g>
<!-- R3 Sub nodes -->
<rect x="710" y="285" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="302" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="9" text-anchor="middle">同时按▲+▼进入</text>
<rect x="710" y="325" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="342" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="9" text-anchor="middle">按▲/▼滚动选类型</text>
<rect x="710" y="365" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="382" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="9" text-anchor="middle">物理触发捕获码</text>
<rect x="710" y="405" width="80" height="26" rx="4" fill="#0F172A" stroke="#334155" stroke-width="1"/>
<text x="750" y="422" fill="#E2E8F0" font-family="system-ui, sans-serif" font-size="9" text-anchor="middle">按SOS保存并同步</text>
</svg>\n </div>
<!-- Section 1 -->
<div class="section-title">一、 待机与安全防区模式</div>
<!-- Card 1.1 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<text x="12" y="20" fill="#5A6275" font-family="system-ui, sans-serif" font-size="10" font-weight="bold">WATCH</text>
<rect x="210" y="11" width="22" height="10" rx="2" fill="none" stroke="#5A6275" stroke-width="1"/>
<rect x="212" y="13" width="14" height="6" rx="1" fill="#00E5FF"/>
<rect x="233" y="14" width="2" height="4" rx="1" fill="#5A6275"/>
<text x="186" y="20" fill="#5A6275" font-family="system-ui, sans-serif" font-size="9">85%</text>
<text x="128" y="85" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="48" font-weight="bold" text-anchor="middle" letter-spacing="1">10:35</text>
<text x="128" y="112" fill="#5A6275" font-family="system-ui, sans-serif" font-size="11" text-anchor="middle">THU 07-02</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">1.1 待机模式(未加入布防)</div>
<table>
<tr><th>触发动作</th><td>系统正常上电启动,或报警/配对结束后的常态。</td></tr>
<tr><th>屏幕显示</th><td>顶部显示 WATCH 及电池百分比,中央大字显示数字时钟,底部显示星期日期。</td></tr>
<tr><th>LED 灯效</th><td>关闭(无灯)</td></tr>
<tr><th>马达反馈</th><td>静止(不振动)</td></tr>
<tr><th>逻辑状态</th><td>433MHz 接收管使能,持续监听总线信号。</td></tr>
</table>
</div>
</div>
<!-- Card 1.2 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<g transform="translate(12, 10)">
<rect x="0" y="5" width="8" height="6" rx="1" fill="#FF1744"/>
<path d="M2,5 L2,3 C2,1.5 6,1.5 6,3 L6,5" fill="none" stroke="#FF1744" stroke-width="1.5"/>
<text x="14" y="11" fill="#FF1744" font-family="system-ui, sans-serif" font-size="9" font-weight="bold">已布防 ZONE 1-N</text>
</g>
<rect x="210" y="11" width="22" height="10" rx="2" fill="none" stroke="#5A6275" stroke-width="1"/>
<rect x="212" y="13" width="14" height="6" rx="1" fill="#00E5FF"/>
<rect x="233" y="14" width="2" height="4" rx="1" fill="#5A6275"/>
<text x="128" y="85" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="48" font-weight="bold" text-anchor="middle" letter-spacing="1">10:35</text>
<text x="128" y="112" fill="#5A6275" font-family="system-ui, sans-serif" font-size="11" text-anchor="middle">THU 07-02</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">1.2 系统布防状态ARMED</div>
<table>
<tr><th>触发动作</th><td>接收到 IPC 发出的 433MHz 布防广播。</td></tr>
<tr><th>屏幕显示</th><td>状态栏左侧显示 <span class="badge badge-red">🔒</span> 并滚动展示“已布防 ZONE 1-N”其余保持时钟。</td></tr>
<tr><th>LED 灯效</th><td>红色呼吸灯慢闪(根据客户要求配置)</td></tr>
<tr><th>马达反馈</th><td>接收广播瞬间短振 1 次150ms</td></tr>
<tr><th>逻辑状态</th><td>进入布防状态,后续除 ZONE 0 外ZONE 1-N 的触发也会引发手环报警。</td></tr>
</table>
</div>
</div>
<!-- Card 1.3 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<g transform="translate(12, 10)">
<rect x="0" y="5" width="8" height="6" rx="1" fill="#00E676"/>
<path d="M2,5 L2,3 C2,1.5 5,1.5 5,3 L5,4" fill="none" stroke="#00E676" stroke-width="1.5"/>
<text x="14" y="11" fill="#00E676" font-family="system-ui, sans-serif" font-size="9" font-weight="bold">已撤防 ZONE 1-N</text>
</g>
<rect x="210" y="11" width="22" height="10" rx="2" fill="none" stroke="#5A6275" stroke-width="1"/>
<rect x="212" y="13" width="14" height="6" rx="1" fill="#00E5FF"/>
<rect x="233" y="14" width="2" height="4" rx="1" fill="#5A6275"/>
<text x="128" y="85" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="48" font-weight="bold" text-anchor="middle" letter-spacing="1">10:35</text>
<text x="128" y="112" fill="#5A6275" font-family="system-ui, sans-serif" font-size="11" text-anchor="middle">THU 07-02</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">1.3 系统撤防状态DISARMED</div>
<table>
<tr><th>触发动作</th><td>接收到 IPC 发出的 433MHz 撤防广播。</td></tr>
<tr><th>屏幕显示</th><td>状态栏左侧显示 <span class="badge badge-green">🔓</span> 并展示“已撤防 ZONE 1-N”其余保持时钟。</td></tr>
<tr><th>LED 灯效</th><td>绿色慢闪 3 次后熄灭</td></tr>
<tr><th>马达反馈</th><td>接收广播瞬间短振 1 次150ms</td></tr>
<tr><th>逻辑状态</th><td>系统处于撤防ZONE 1-N 的探测触发将被忽略,但 ZONE 0 永久布防区仍能触发报警。</td></tr>
</table>
</div>
</div>
<!-- Section 2 -->
<div class="section-title">二、 传感器配对流程(手环端四步)</div>
<!-- Card 2.1 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<text x="128" y="20" fill="#5A6275" font-family="system-ui, sans-serif" font-size="10" font-weight="bold" text-anchor="middle">PAIRING MODE</text>
<g transform="translate(128, 56)" stroke="#FFB74D" fill="none" stroke-width="2">
<circle cx="0" cy="0" r="4" fill="#FFB74D"/>
<path d="M-8,-8 A12,12 0 0,1 8,-8" stroke-linecap="round"/>
<path d="M-15,-15 A22,22 0 0,1 15,-15" stroke-linecap="round"/>
<path d="M-22,-22 A32,32 0 0,1 22,-22" stroke-linecap="round" stroke-dasharray="2 2"/>
</g>
<text x="128" y="96" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">正在搜索 433MHz 信号...</text>
<text x="128" y="114" fill="#FFB74D" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">请触发目标传感器 | P-00</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">Step 1进入配对模式搜索信号</div>
<table>
<tr><th>触发动作</th><td>用户同时长按 <span class="badge badge-white"></span> + <span class="badge badge-white"></span> 按键 3 秒。</td></tr>
<tr><th>屏幕显示</th><td>顶部展示 PAIRING MODE中央显示天线雷达 <span class="badge badge-orange">📡</span> 波纹动态动画,底部提示等待信号。</td></tr>
<tr><th>LED 灯效</th><td>白色快速闪烁3Hz以示配对工作状态</td></tr>
<tr><th>马达反馈</th><td>短振 1 次100ms作为物理按键确认反馈</td></tr>
<tr><th>超时机制</th><td>30 秒内若无信号输入,自动退出配对返回时钟待机。</td></tr>
</table>
</div>
</div>
<!-- Card 2.2 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<text x="128" y="18" fill="#FFB74D" font-family="system-ui, sans-serif" font-size="10" font-weight="bold" text-anchor="middle" letter-spacing="1">配对模式 P-00</text>
<text x="24" y="44" fill="#374151" font-family="system-ui, sans-serif" font-size="11">🚪 门磁/窗磁</text>
<rect x="12" y="54" width="232" height="28" rx="4" fill="none" stroke="#00E5FF" stroke-width="1.5"/>
<rect x="220" y="62" width="12" height="12" rx="6" fill="#FF9800"/>
<text x="24" y="72" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="13" font-weight="bold">👤 人体感应 PIR</text>
<text x="24" y="102" fill="#374151" font-family="system-ui, sans-serif" font-size="11">🔥 烟雾传感器</text>
<path d="M244,48 L244,96" stroke="#1F2937" stroke-width="2" stroke-linecap="round"/>
<path d="M244,64 L244,80" stroke="#00E5FF" stroke-width="4" stroke-linecap="round"/>
</svg>
</div>
<div class="details-container">
<div class="step-name">Step 2滚动选择配对类型</div>
<table>
<tr><th>操作方式</th><td>短按 <span class="badge badge-white"></span><span class="badge badge-white"></span> 进行列表滚动。</td></tr>
<tr><th>屏幕显示</th><td>展示 3 行滚动列表,当前选中行(如 PIR加粗并加边框右侧有滚动条指示。</td></tr>
<tr><th>LED 灯效</th><td>持续白色快闪</td></tr>
<tr><th>选中指示</th><td>选中类型的边框会变粗,并在右侧提示对应的专属颜色块。</td></tr>
</table>
</div>
</div>
<!-- Card 2.3 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<circle cx="128" cy="42" r="22" fill="none" stroke="#00E676" stroke-width="3"/>
<path d="M118,42 L125,49 L138,34" fill="none" stroke="#00E676" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<text x="128" y="88" fill="#00E676" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" text-anchor="middle">对码已保存</text>
<text x="128" y="110" fill="#B9F6CA" font-family="system-ui, sans-serif" font-size="12" text-anchor="middle">ADDR: 0x37A86 | TYPE: PIR</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">Step 3-A配对保存成功</div>
<table>
<tr><th>触发动作</th><td>触发目标传感器,发射码与选择类型相符,短按 <span class="badge badge-white">SOS</span> 保存。</td></tr>
<tr><th>屏幕显示</th><td>中央大号绿色 <span class="badge badge-green"></span> 图标,文字展示“对码已保存”及捕获的 24位地址码。</td></tr>
<tr><th>LED 灯效</th><td>绿色常亮 2 秒后熄灭</td></tr>
<tr><th>马达反馈</th><td>强力长振 1 次400ms确认写入成功</td></tr>
<tr><th>名称同步</th><td>接收来自 IPC 摄像头的广播,自动用自定义中文名称替换默认传感器类型名称。</td></tr>
</table>
</div>
</div>
<!-- Card 2.4 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<rect x="4" y="4" width="248" height="120" rx="6" fill="none" stroke="#FF1744" stroke-width="1.5"/>
<circle cx="128" cy="42" r="22" fill="none" stroke="#FF1744" stroke-width="3"/>
<path d="M118,32 L138,52 M138,32 L118,52" fill="none" stroke="#FF1744" stroke-width="4" stroke-linecap="round"/>
<text x="128" y="88" fill="#FF1744" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" text-anchor="middle">对码匹配失败</text>
<text x="128" y="110" fill="#FF8A80" font-family="system-ui, sans-serif" font-size="11" text-anchor="middle">代码类型不一致,请重试</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">Step 3-B对码匹配失败类型不一致</div>
<table>
<tr><th>触发动作</th><td>触发了传感器,但其发射的代码类型与手环当前选中的菜单类型不符。</td></tr>
<tr><th>屏幕显示</th><td>红色大圆圈 <span class="badge badge-red"></span> 图标,大字“对码匹配失败”及提示“代码类型不一致”。</td></tr>
<tr><th>LED 灯效</th><td>红色快闪 3 次</td></tr>
<tr><th>马达反馈</th><td>双击快速短振100ms + 100ms</td></tr>
<tr><th>自动恢复</th><td>界面停留 2 秒后,自动退回到 Step 2 传感器列表选择页面。</td></tr>
</table>
</div>
</div>
<!-- Section 3 -->
<div class="section-title">三、 报警告警模式</div>
<!-- Card 3.1 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<rect x="4" y="4" width="248" height="120" rx="6" fill="none" stroke="#FF3D00" stroke-width="2" stroke-dasharray="8 4"/>
<g transform="translate(116, 20)">
<path d="M4,28 L4,2 L18,2 L18,28 Z" fill="none" stroke="#FF3D00" stroke-width="2"/>
<path d="M4,2 L18,5 L18,25 L4,28 Z" fill="#FF3D00" opacity="0.8"/>
<circle cx="14" cy="15" r="1.5" fill="#0A0D14"/>
</g>
<text x="128" y="82" fill="#FF3D00" font-family="system-ui, sans-serif" font-size="16" font-weight="bold" text-anchor="middle">🚪 前门门磁 触发</text>
<text x="128" y="105" fill="#FF8A80" font-family="system-ui, sans-serif" font-size="12" text-anchor="middle">ZONE 0 | 红色报警</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">3.1 传感器报警触发(以门磁为例)</div>
<table>
<tr><th>触发动作</th><td>待机状态下,接收到已配对传感器发送的报警 433MHz 数据帧。</td></tr>
<tr><th>屏幕显示</th><td>全屏红色闪烁虚线框,中央显示开门状态的 <span class="badge badge-red">🚪</span> 图标,底部显示自定义中文名称。</td></tr>
<tr><th>LED 灯效</th><td>对应传感器专属颜色门磁对应红色PIR对应橙色水浸对应黄色</td></tr>
<tr><th>马达反馈</th><td>对应传感器特定振动模式门磁为1次短振 300ms每 5 秒循环一次)</td></tr>
<tr><th>自动退出</th><td>报警发射停止后5 秒自动超时退出,恢复时钟待机。</td></tr>
</table>
</div>
</div>
<!-- Card 3.2 -->
<div class="card">
<div class="screen-container">
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="128" viewBox="0 0 256 128">
<rect width="256" height="128" rx="8" fill="#0A0D14"/>
<rect x="4" y="4" width="248" height="120" rx="6" fill="none" stroke="#FF1744" stroke-width="2"/>
<rect x="8" y="8" width="240" height="112" rx="4" fill="none" stroke="#FF1744" stroke-width="1" stroke-dasharray="6 3"/>
<g transform="translate(116, 20)" fill="#FF1744">
<rect x="2" y="18" width="20" height="6" rx="1"/>
<path d="M4,18 C4,8 20,8 20,18 Z"/>
<line x1="-4" y1="6" x2="2" y2="12" stroke="#FF1744" stroke-width="2" stroke-linecap="round"/>
<line x1="12" y1="-4" x2="12" y2="4" stroke="#FF1744" stroke-width="2" stroke-linecap="round"/>
<line x1="28" y1="6" x2="22" y2="12" stroke="#FF1744" stroke-width="2" stroke-linecap="round"/>
</g>
<text x="128" y="85" fill="#FF1744" font-family="system-ui, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" letter-spacing="2">🚨 紧急求救 SOS</text>
<text x="128" y="108" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">手环触发 | 最高优先级</text>
</svg>
</div>
<div class="details-container">
<div class="step-name">3.2 紧急 SOS 触发(最高优先级)</div>
<table>
<tr><th>触发动作</th><td>用户在手环上短按 SOS 键,或接收到其它手环广播的 SOS 求救信号。</td></tr>
<tr><th>屏幕显示</th><td>红色宽警告边框,中央闪烁旋转警灯 <span class="badge badge-red">🚨</span>,全屏显示“紧急求救 SOS”。</td></tr>
<tr><th>LED 灯效</th><td>红色极速闪烁5Hz</td></tr>
<tr><th>马达反馈</th><td>持续不断的长周期振动1200ms 长振循环),用于在危急状态下唤醒睡眠或提醒用户。</td></tr>
<tr><th>解除机制</th><td>长按手环按键或在 IPC 主机/手机 APP 上点击“解除警报”。</td></tr>
</table>
</div>
</div>
<!-- Section 4 -->
<div class="section-title">四、 传感器与振动灯效映射表</div>
<div class="card" style="flex-direction: column; background-color: #0F172A; border: 1px solid #334155;">
<div class="step-name" style="color: #FFB74D;">7类传感器及手环动作参数汇总</div>
<table style="font-size: 0.95rem; line-height: 1.8;">
<thead>
<tr style="border-bottom: 2px solid #334155; color: #94A3B8;">
<th>代码</th>
<th>类型</th>
<th>LED 颜色</th>
<th>振动模式</th>
<th>单次振动时长</th>
<th>振动间隔</th>
<th>应用场景说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><b>0001 (A)</b></td>
<td>🚪 门磁/窗磁</td>
<td><span class="badge" style="background-color: #7f1d1d; color: #fca5a5;">● 红色</span></td>
<td>1 次短振</td>
<td>300 ms</td>
<td></td>
<td>进出口边界安全防护(最高优先级)</td>
</tr>
<tr>
<td><b>0010 (B)</b></td>
<td>👤 人体感应 PIR</td>
<td><span class="badge" style="background-color: #7c2d12; color: #ffedd5;">● 橙色</span></td>
<td>2 次短振</td>
<td>300 ms</td>
<td>200 ms</td>
<td>室内移动探测及婴儿防翻身/攀爬监控</td>
</tr>
<tr>
<td><b>0011 (C)</b></td>
<td>🔥 烟雾传感器</td>
<td><span class="badge" style="background-color: #064e3b; color: #6ee7b7;">● 绿色</span></td>
<td>3 次短振</td>
<td>200 ms</td>
<td>150 ms</td>
<td>火灾烟雾探测与厨房安全监控</td>
</tr>
<tr>
<td><b>0100 (D)</b></td>
<td>🆘 紧急按钮</td>
<td><span class="badge" style="background-color: #1e3a8a; color: #93c5fd;">● 蓝色</span></td>
<td>1 次长振</td>
<td>800 ms</td>
<td></td>
<td>人工手动紧急一键求助呼叫</td>
</tr>
<tr>
<td><b>0101 (E)</b></td>
<td>💨 气体传感器</td>
<td><span class="badge" style="background-color: #581c87; color: #e9d5ff;">● 紫色</span></td>
<td>4 次短振</td>
<td>200 ms</td>
<td>100 ms</td>
<td>一氧化碳/天然气泄漏高危报警</td>
</tr>
<tr>
<td><b>0110 (F)</b></td>
<td>💧 水浸传感器</td>
<td><span class="badge" style="background-color: #78350f; color: #fef3c7;">● 黄色</span></td>
<td>2 次长振</td>
<td>600 ms</td>
<td>300 ms</td>
<td>水管漏水/积水外溢高湿防护</td>
</tr>
<tr>
<td><b>0111 (G)</b></td>
<td>📳 振动传感器</td>
<td><span class="badge" style="background-color: #134e5e; color: #e0f7fa;">● 青色</span></td>
<td>连续 3 次</td>
<td>150 ms</td>
<td>100 ms</td>
<td>保险柜/主门防撬外力振动监测</td>
</tr>
<tr>
<td><b>1000 (SOS)</b></td>
<td>🚨 手环紧急求救</td>
<td><span class="badge" style="background-color: #7f1d1d; color: #fca5a5; animation: flash 1s infinite;">● 红色闪烁</span></td>
<td>持续长振</td>
<td>1200 ms 循环</td>
<td></td>
<td>手环端独立发起最高优先级报警凌驾所有Zone</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>

BIN
Docs/docs/架构.odg Normal file

Binary file not shown.

465
Drivers/lcd.c Normal file
View File

@@ -0,0 +1,465 @@
#include "lcd.h"
#include "lcd_font.h"
#include "../App/config.h"
#include "intrins.h"
// RM69310 屏幕接口引脚
sbit LCD_RST = P1^0; // 硬件复位
sbit LCD_DCX = P1^1; // 数据/命令选择
sbit LCD_SDI = P1^4; // SPI 数据输入 (MOSI)
sbit LCD_SCL = P1^5; // SPI 时钟 (SCLK)
sbit LCD_CS = P1^6; // SPI 片选 (CS)
// SGM3833 电源芯片控制脚
sbit SGM_CTRL = P3^4;
// External delay functions defined in main.c
extern void Delay_ms(u16 ms);
extern void Delay10us(void);
void SGM_SendPulse(u8 pulse_cnt)
{
u8 i;
SGM_CTRL = 0;
Delay_ms(5); // 确保进入关断/复位状态 (tOFF)
SGM_CTRL = 1;
Delay_ms(5); // 确保充分初始化 (tINIT >= 300us)
for(i = 0; i < pulse_cnt; i++)
{
SGM_CTRL = 0;
Delay10us();
SGM_CTRL = 1;
Delay10us();
}
SGM_CTRL = 1;
Delay_ms(5); // 确保数据稳定存储 (tSTORE >= 80us)
}
void SPI_SendData(u8 dat)
{
u8 i;
for(i = 0; i < 8; i++)
{
LCD_SCL = 0;
if(dat & 0x80)
LCD_SDI = 1;
else
LCD_SDI = 0;
_nop_();
LCD_SCL = 1;
_nop_();
dat <<= 1;
}
}
void WriteComm(u8 cmd)
{
LCD_CS = 0;
LCD_DCX = 0; // 命令模式
SPI_SendData(cmd);
LCD_CS = 1;
}
void WriteData(u8 dat)
{
LCD_CS = 0;
LCD_DCX = 1; // 数据模式
SPI_SendData(dat);
LCD_CS = 1;
}
void LCD_Reset(void)
{
LCD_RST = 1;
Delay_ms(10);
LCD_RST = 0;
Delay_ms(20);
LCD_RST = 1;
Delay_ms(120);
}
void LCD_Init(void)
{
// Configure LCD pins as push-pull output
P1M1 &= ~((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
P3M1 &= ~(1 << 4);
P3M0 |= (1 << 4);
SGM_CTRL = 1;
LCD_CS = 1;
LCD_SCL = 1;
LCD_RST = 1;
LCD_DCX = 1;
LCD_Reset();
// 寄存器配置(源自 Truly 信利官方初始化配置)
WriteComm(0xFE);WriteData(0x01);
WriteComm(0x04);WriteData(0xa0); // SPI write ram enable
WriteComm(0x05);WriteData(0x70); // 128RGB
WriteComm(0x06);WriteData(0x3C); // NL = 240 line
WriteComm(0x25);WriteData(0x06); // normal mode: gamma1
WriteComm(0x26);WriteData(0x80); // T1A = 280
WriteComm(0x27);WriteData(0x12); // normal mode VBP = 12
WriteComm(0x28);WriteData(0x12); // normal mode VFP = 8
WriteComm(0x2A);WriteData(0x06); // idle mode: gamma1
WriteComm(0x2B);WriteData(0x80); // T1B = 280
WriteComm(0x2D);WriteData(0x12); // idle mode VBP = 12
WriteComm(0x2F);WriteData(0x12); // idle mode VFP = 8
WriteComm(0x37);WriteData(0x0C); // precharge to VGSP
WriteComm(0x6D);WriteData(0x18); // skip frame VGMP and VGSP regulator off
WriteComm(0x29);WriteData(0x01); // normal mode skip frame off
WriteComm(0x30);WriteData(0x43); // idle mode skip frame = 60/2 = 30Hz
WriteComm(0x0E);WriteData(0x83); // AVDD = 5.6V normal mode
WriteComm(0x0F);WriteData(0x83); // AVDD = 5.6V idle mode
WriteComm(0x10);WriteData(0x71); // AVDD = 3xVCI
WriteComm(0x11);WriteData(0xb3); // VCL = -2xVCI
WriteComm(0x12);WriteData(0xb3); // VCL = -2xVCI
WriteComm(0x13);WriteData(0x80); // VGH = AVDD normal mode
WriteComm(0x14);WriteData(0x80); // VGH = AVDD idle mode
WriteComm(0x15);WriteData(0x81); // VGL = VCL - VCI normal mode
WriteComm(0x16);WriteData(0x81); // VGL = VCL - VCI idle mode
WriteComm(0x18);WriteData(0x66); // VGHR = 6V normal/idle mode
WriteComm(0x19);WriteData(0x44); // VGLR = -6V normal/idle mode
WriteComm(0x1E);WriteData(0x02); // Switch EQ on
WriteComm(0x5B);WriteData(0x10); // VREFN5 on
WriteComm(0x62);WriteData(0x19); // VREFN5 = -3V normal mode
WriteComm(0x63);WriteData(0x19); // VREFN5 = -3V idle mode
WriteComm(0x70);WriteData(0x55); // display off SD to AVDD
WriteComm(0x1D);WriteData(0x02); // Switch EQ on
WriteComm(0x89);WriteData(0x18); // VGMP = 5.5V
WriteComm(0x8A);WriteData(0xb9); // VGSP = 2V
WriteComm(0x8B);WriteData(0x01); // VGMP VGSP high byte
WriteComm(0x8C);WriteData(0x10); // VGMP = 5.4V
WriteComm(0x8D);WriteData(0x90); // VGSP = 2V
WriteComm(0x8E);WriteData(0x01); // VGMP VGSP high byte
WriteComm(0x6E);WriteData(0x0A); // MIPI interface off
WriteComm(0x6A);WriteData(0x05); // MUX
WriteComm(0x3A);WriteData(0x08); // T1_sd
WriteComm(0x3B);WriteData(0x00); // Tp_sd
WriteComm(0x3D);WriteData(0x16); // Th_sd
WriteComm(0x3F);WriteData(0x27); // Tsw_sd
WriteComm(0x40);WriteData(0x0F); // Thsw_sd
WriteComm(0x41);WriteData(0x0D); // Thsd_sd
WriteComm(0x42);WriteData(0x14); // Mux 142536
WriteComm(0x43);WriteData(0x41);
WriteComm(0x44);WriteData(0x25);
WriteComm(0x45);WriteData(0x52);
WriteComm(0x46);WriteData(0x36);
WriteComm(0x47);WriteData(0x63);
WriteComm(0x48);WriteData(0x14);
WriteComm(0x49);WriteData(0x41);
WriteComm(0x4A);WriteData(0x25);
WriteComm(0x4B);WriteData(0x52);
WriteComm(0x4C);WriteData(0x36);
WriteComm(0x4D);WriteData(0x63);
WriteComm(0x4E);WriteData(0x14); // Data R1R2G1G2B1B2
WriteComm(0x4F);WriteData(0x41);
WriteComm(0x50);WriteData(0x25);
WriteComm(0x51);WriteData(0x52);
WriteComm(0x52);WriteData(0x36);
WriteComm(0x53);WriteData(0x63);
WriteComm(0x54);WriteData(0x14);
WriteComm(0x55);WriteData(0x41);
WriteComm(0x56);WriteData(0x25);
WriteComm(0x57);WriteData(0x52);
WriteComm(0x58);WriteData(0x36);
WriteComm(0x59);WriteData(0x63);
WriteComm(0x66);WriteData(0x90); // idle mode internal power
WriteComm(0x67);WriteData(0x40); // internal power delay 1 frame off
WriteComm(0x72);WriteData(0x1A); // internal OVDD = 4.6V
WriteComm(0x73);WriteData(0x07); // internal OVSS = -2V
WriteComm(0x74);WriteData(0x0C); // OVDD power from AVDD
WriteComm(0x6A);WriteData(0x3D); // swire 61=0x3D pulse
WriteComm(0x6B);WriteData(0x29); // swire 41=0x29 pulse
WriteComm(0xFE);WriteData(0x04);
WriteComm(0x5E);WriteData(0x01);
WriteComm(0x5F);WriteData(0xB8);
WriteComm(0x60);WriteData(0xBB);
WriteComm(0x61);WriteData(0xBB);
WriteComm(0x62);WriteData(0xBB);
WriteComm(0x76);WriteData(0xBB);
WriteComm(0x77);WriteData(0x3B);
WriteComm(0x78);WriteData(0x92);
WriteComm(0x79);WriteData(0xBB);
WriteComm(0x7A);WriteData(0xBB);
WriteComm(0x00);WriteData(0xDC); // SN_CK1
WriteComm(0x01);WriteData(0x00);
WriteComm(0x02);WriteData(0x02);
WriteComm(0x03);WriteData(0x00);
WriteComm(0x04);WriteData(0x08);
WriteComm(0x05);WriteData(0x01);
WriteComm(0x06);WriteData(0x70);
WriteComm(0x07);WriteData(0x0A);
WriteComm(0x08);WriteData(0x00);
WriteComm(0x09);WriteData(0xDC); // SN_CK2
WriteComm(0x0a);WriteData(0x00);
WriteComm(0x0b);WriteData(0x02);
WriteComm(0x0C);WriteData(0x00);
WriteComm(0x0D);WriteData(0x08);
WriteComm(0x0E);WriteData(0x00);
WriteComm(0x0F);WriteData(0x70);
WriteComm(0x10);WriteData(0x0A);
WriteComm(0x11);WriteData(0x00);
WriteComm(0x12);WriteData(0xCC); // EM_CK1
WriteComm(0x13);WriteData(0x00);
WriteComm(0x14);WriteData(0x02);
WriteComm(0x15);WriteData(0x00);
WriteComm(0x16);WriteData(0x20);
WriteComm(0x17);WriteData(0x00);
WriteComm(0x18);WriteData(0x28);
WriteComm(0x19);WriteData(0x26);
WriteComm(0x1A);WriteData(0x00);
WriteComm(0x1B);WriteData(0xCC); // EM_CK2
WriteComm(0x1C);WriteData(0x00);
WriteComm(0x1D);WriteData(0x02);
WriteComm(0x1E);WriteData(0x00);
WriteComm(0x1F);WriteData(0x20);
WriteComm(0x20);WriteData(0x01);
WriteComm(0x21);WriteData(0x28);
WriteComm(0x22);WriteData(0x26);
WriteComm(0x23);WriteData(0x00);
WriteComm(0x4C);WriteData(0x89); // SN_STV
WriteComm(0x4D);WriteData(0x00);
WriteComm(0x4E);WriteData(0x01);
WriteComm(0x4F);WriteData(0x00);
WriteComm(0x50);WriteData(0x01);
WriteComm(0x51);WriteData(0xBB);
WriteComm(0x52);WriteData(0xBB);
WriteComm(0x53);WriteData(0xCA); // EM_STV
WriteComm(0x54);WriteData(0x00);
WriteComm(0x55);WriteData(0x03);
WriteComm(0x56);WriteData(0x00);
WriteComm(0x58);WriteData(0x00);
WriteComm(0x59);WriteData(0x00);
WriteComm(0x65);WriteData(0x45); // 04
WriteComm(0x66);WriteData(0x0C); // 03
WriteComm(0x67);WriteData(0x00); // 10
WriteComm(0xFE);WriteData(0x01); // ID
WriteComm(0xE5);WriteData(0x00);
WriteComm(0xE6);WriteData(0x10);
WriteComm(0xE7);WriteData(0x31);
WriteComm(0xFE);WriteData(0x00); // 切换到 Page 0
WriteComm(0xC4);WriteData(0x80);
WriteComm(0x2A);WriteData(0x00);WriteData(0x04);WriteData(0x00);WriteData(0x7B); // 设置列
// 显式设置 16 位像素格式 (COLMOD = 0x75)
WriteComm(0x3A);WriteData(0x75);
WriteComm(0x11);WriteData(0x00); // 退出睡眠
Delay_ms(150);
WriteComm(0x29);WriteData(0x00); // 开启显示
Delay_ms(20);
}
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end)
{
WriteComm(0x2A);
WriteData((u8)(x_start >> 8));
WriteData((u8)(x_start & 0xFF));
WriteData((u8)(x_end >> 8));
WriteData((u8)(x_end & 0xFF));
WriteComm(0x2B);
WriteData((u8)(y_start >> 8));
WriteData((u8)(y_start & 0xFF));
WriteData((u8)(y_end >> 8));
WriteData((u8)(y_end & 0xFF));
}
void LCD_Clear(u16 color)
{
u16 i, j;
LCD_SetWindow(4, 123, 0, 239);
WriteComm(0x2C);
for(i = 0; i < 120; i++)
{
for(j = 0; j < 240; j++)
{
WriteData((u8)(color >> 8));
WriteData((u8)(color & 0xFF));
}
}
}
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color)
{
u16 i, count;
u8 ch = (u8)(color >> 8);
u8 cl = (u8)(color & 0xFF);
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1);
WriteComm(0x2C);
count = w * h;
for(i = 0; i < count; i++)
{
WriteData(ch);
WriteData(cl);
}
}
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color)
{
LCD_FillRect(x, y, w, 1, color);
LCD_FillRect(x, y + h - 1, w, 1, color);
LCD_FillRect(x, y, 1, h, color);
LCD_FillRect(x + w - 1, y, 1, h, color);
}
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color)
{
u16 r, c;
u16 byte_width = (w + 7) / 8;
u8 ch = (u8)(color >> 8);
u8 cl = (u8)(color & 0xFF);
u8 bgh = (u8)(bg_color >> 8);
u8 bgl = (u8)(bg_color & 0xFF);
LCD_SetWindow(x + 4, x + w - 1 + 4, y, y + h - 1);
WriteComm(0x2C);
for(r = 0; r < h; r++)
{
for(c = 0; c < w; c++)
{
u16 byte_idx = r * byte_width + (c / 8);
u8 bit_shift = 7 - (c % 8);
if(bmp[byte_idx] & (1 << bit_shift))
{
WriteData(ch);
WriteData(cl);
}
else
{
WriteData(bgh);
WriteData(bgl);
}
}
}
}
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{
u8 r, c;
u8 char_idx = ch - 0x20;
u8 f_h = (u8)(color >> 8);
u8 f_l = (u8)(color & 0xFF);
u8 b_h = (u8)(bg_color >> 8);
u8 b_l = (u8)(bg_color & 0xFF);
if(ch < 0x20 || ch > 0x7E) return;
LCD_SetWindow(x + 4, x + 8 - 1 + 4, y, y + 16 - 1);
WriteComm(0x2C);
for(r = 0; r < 16; r++)
{
u8 line = FONT_8x16[char_idx][r];
for(c = 0; c < 8; c++)
{
if(line & (0x80 >> c))
{
WriteData(f_h);
WriteData(f_l);
}
else
{
WriteData(b_h);
WriteData(b_l);
}
}
}
}
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{
while(*str)
{
LCD_ShowChar8x16(x, y, *str, color, bg_color);
x += 8;
str++;
}
}
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color)
{
u16 len = 0;
char *p = str;
while(*p++) len++;
if(len * 8 >= 120)
LCD_ShowString(0, y, str, color, bg_color);
else
LCD_ShowString((120 - len * 8) / 2, y, str, color, bg_color);
}
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color)
{
u8 r, c, i, j;
u8 char_idx = ch - 0x20;
u8 f_h = (u8)(color >> 8);
u8 f_l = (u8)(color & 0xFF);
u8 b_h = (u8)(bg_color >> 8);
u8 b_l = (u8)(bg_color & 0xFF);
if(ch < 0x20 || ch > 0x7E) return;
LCD_SetWindow(x + 4, x + 16 - 1 + 4, y, y + 32 - 1);
WriteComm(0x2C);
for(r = 0; r < 16; r++)
{
u8 line = FONT_8x16[char_idx][r];
for(i = 0; i < 2; i++)
{
for(c = 0; c < 8; c++)
{
u8 val_bit = (line & (0x80 >> c));
for(j = 0; j < 2; j++)
{
if(val_bit)
{
WriteData(f_h);
WriteData(f_l);
}
else
{
WriteData(b_h);
WriteData(b_l);
}
}
}
}
}
}
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color)
{
while(*str)
{
LCD_ShowChar16x32(x, y, *str, color, bg_color);
x += 16;
str++;
}
}
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color)
{
u16 len = 0;
char *p = str;
while(*p++) len++;
if(len * 16 >= 120)
LCD_ShowString16x32(0, y, str, color, bg_color);
else
LCD_ShowString16x32((120 - len * 16) / 2, y, str, color, bg_color);
}

52
Drivers/lcd.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef __LCD_H__
#define __LCD_H__
#include "../App/config.h"
#include "stc32g.h"
// Color Constants (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
void SGM_SendPulse(u8 pulse_cnt);
void LCD_Reset(void);
void LCD_Init(void);
void WriteComm(u8 cmd);
void LCD_SetWindow(u16 x_start, u16 x_end, u16 y_start, u16 y_end);
void LCD_Clear(u16 color);
// Drawing Primitives
void LCD_FillRect(u16 x, u16 y, u16 w, u16 h, u16 color);
void LCD_DrawRectBorder(u16 x, u16 y, u16 w, u16 h, u16 color);
void LCD_DrawMonoBitmap(u16 x, u16 y, u16 w, u16 h, unsigned char code *bmp, u16 color, u16 bg_color);
void LCD_ShowChar8x16(u16 x, u16 y, char ch, u16 color, u16 bg_color);
void LCD_ShowString(u16 x, u16 y, char *str, u16 color, u16 bg_color);
void LCD_ShowStringCentered(u16 y, char *str, u16 color, u16 bg_color);
void LCD_ShowChar16x32(u16 x, u16 y, char ch, u16 color, u16 bg_color);
void LCD_ShowString16x32(u16 x, u16 y, char *str, u16 color, u16 bg_color);
void LCD_ShowString16x32Centered(u16 y, char *str, u16 color, u16 bg_color);
// Font and Bitmap externs (defined in lcd_font.h via lcd.c)
extern unsigned char code FONT_8x16[95][16];
extern unsigned char code bmp_battery_20x10[];
extern unsigned char code bmp_lock_16x16[];
extern unsigned char code bmp_unlock_16x16[];
extern unsigned char code bmp_radar_32x32_f1[];
extern unsigned char code bmp_radar_32x32_f2[];
extern unsigned char code bmp_radar_32x32_f3[];
extern unsigned char code bmp_confirm_lock_32x32[];
extern unsigned char code bmp_checkmark_32x32[];
extern unsigned char code bmp_cross_32x32[];
extern unsigned char code bmp_door_32x32[];
extern unsigned char code bmp_siren_32x32[];
#endif

284
Drivers/lcd_font.h Normal file
View File

@@ -0,0 +1,284 @@
#ifndef __FONT_H__
#define __FONT_H__
// 1. Horizontal Battery Icon (20x10 pixels)
// Width: 20 pixels (3 bytes per row), Height: 10 rows
unsigned char code bmp_battery_20x10[30] = {
0xFF, 0xFF, 0x00, // ####################..
0x80, 0x00, 0x80, // #..................#
0xBF, 0xFB, 0x80, // ##.###############.#
0xBF, 0xFB, 0xE0, // ##.###############.###
0xBF, 0xFB, 0xE0, // ##.###############.###
0xBF, 0xFB, 0xE0, // ##.###############.###
0xBF, 0xFB, 0xE0, // ##.###############.###
0xBF, 0xFB, 0x80, // ##.###############.#
0x80, 0x00, 0x80, // #..................#
0xFF, 0xFF, 0x00 // ####################..
};
// 2. Lock Icon (16x16 pixels)
// Width: 16 pixels (2 bytes per row), Height: 16 rows
unsigned char code bmp_lock_16x16[32] = {
0x07, 0xE0, // ######
0x0C, 0x30, // ## ##
0x18, 0x18, // ## ##
0x18, 0x18, // ## ##
0x18, 0x18, // ## ##
0xFF, 0xFF, // ################
0xFF, 0xFF, // ################
0xE7, 0xE7, // ### ###### ###
0xE7, 0xE7, // ### ###### ###
0xE1, 0x87, // ### #### ###
0xE1, 0x87, // ### #### ###
0xE7, 0xE7, // ### ###### ###
0xE7, 0xE7, // ### ###### ###
0xFF, 0xFF, // ################
0xFF, 0xFF, // ################
0x00, 0x00 //
};
// 3. Unlock Icon (16x16 pixels)
// Width: 16 pixels (2 bytes per row), Height: 16 rows
unsigned char code bmp_unlock_16x16[32] = {
0x07, 0xE0, // ######
0x0C, 0x30, // ## ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0x18, 0x00, // ##
0xFF, 0xFF, // ################
0xFF, 0xFF, // ################
0xE7, 0xE7, // ### ###### ###
0xE7, 0xE7, // ### ###### ###
0xE1, 0x87, // ### #### ###
0xE1, 0x87, // ### #### ###
0xE7, 0xE7, // ### ###### ###
0xE7, 0xE7, // ### ###### ###
0xFF, 0xFF, // ################
0xFF, 0xFF, // ################
0x00, 0x00 //
};
// 4. Radar Icon Frame 1 (32x32 pixels, 4 bytes per row)
unsigned char code bmp_radar_32x32_f1[128] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x10, 0x08, 0x00,
0x00, 0x10, 0x08, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x07, 0xE0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 5. Radar Icon Frame 2
unsigned char code bmp_radar_32x32_f2[128] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0xE0, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x02, 0x00,
0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
0x00, 0x40, 0x02, 0x00, 0x00, 0x47, 0xE2, 0x00, 0x00, 0x48, 0x12, 0x00, 0x00, 0x50, 0x0A, 0x00,
0x00, 0x50, 0x0A, 0x00, 0x00, 0x50, 0x0A, 0x00, 0x00, 0x48, 0x12, 0x00, 0x00, 0x47, 0xE2, 0x00,
0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
0x00, 0x40, 0x02, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x07, 0xE0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 6. Radar Icon Frame 3 (Complete circles)
unsigned char code bmp_radar_32x32_f3[128] = {
0x00, 0x07, 0xE0, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x02, 0x00,
0x00, 0x87, 0xE1, 0x00, 0x01, 0x18, 0x18, 0x80, 0x02, 0x20, 0x04, 0x40, 0x04, 0x40, 0x02, 0x20,
0x04, 0x40, 0x02, 0x20, 0x04, 0x40, 0x02, 0x20, 0x04, 0x40, 0x02, 0x20, 0x04, 0x40, 0x02, 0x20,
0x04, 0x40, 0x02, 0x20, 0x04, 0x47, 0xE2, 0x20, 0x04, 0x48, 0x12, 0x20, 0x04, 0x50, 0x0A, 0x20,
0x04, 0x50, 0x0A, 0x20, 0x04, 0x50, 0x0A, 0x20, 0x04, 0x48, 0x12, 0x20, 0x04, 0x47, 0xE2, 0x20,
0x04, 0x40, 0x02, 0x20, 0x04, 0x40, 0x02, 0x20, 0x04, 0x40, 0x02, 0x20, 0x04, 0x40, 0x02, 0x20,
0x04, 0x40, 0x02, 0x20, 0x02, 0x20, 0x04, 0x40, 0x01, 0x18, 0x18, 0x80, 0x00, 0x87, 0xE1, 0x00,
0x00, 0x40, 0x02, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x07, 0xE0, 0x00
};
// 7. Checkmark Icon (32x32 pixels)
unsigned char code bmp_checkmark_32x32[128] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0xF0, 0x00, 0x00, 0x3F, 0xFC, 0x00,
0x00, 0xF8, 0x1F, 0x00, 0x01, 0xC0, 0x03, 0x80,
0x03, 0x80, 0x01, 0xC0, 0x07, 0x00, 0x00, 0xE0,
0x0E, 0x00, 0x00, 0x70, 0x0C, 0x00, 0x00, 0x30,
0x18, 0x00, 0x02, 0x18, 0x18, 0x00, 0x07, 0x18,
0x38, 0x00, 0x0E, 0x1C, 0x30, 0x00, 0x1C, 0x0C,
0x30, 0x20, 0x38, 0x0C, 0x30, 0x70, 0x70, 0x0C,
0x30, 0x38, 0xE0, 0x0C, 0x30, 0x1D, 0xC0, 0x0C,
0x30, 0x0F, 0x80, 0x0C, 0x38, 0x07, 0x00, 0x1C,
0x18, 0x02, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18,
0x0C, 0x00, 0x00, 0x30, 0x0E, 0x00, 0x00, 0x70,
0x07, 0x00, 0x00, 0xE0, 0x03, 0x80, 0x01, 0xC0,
0x01, 0xC0, 0x03, 0x80, 0x00, 0xF8, 0x1F, 0x00,
0x00, 0x3F, 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 8. Cross Icon (32x32 pixels)
unsigned char code bmp_cross_32x32[128] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0xF0, 0x00, 0x00, 0x3F, 0xFC, 0x00,
0x00, 0xF8, 0x1F, 0x00, 0x01, 0xC0, 0x03, 0x80,
0x03, 0x80, 0x01, 0xC0, 0x07, 0x00, 0x00, 0xE0,
0x0E, 0x00, 0x00, 0x70, 0x0C, 0x00, 0x00, 0x30,
0x18, 0x10, 0x08, 0x18, 0x18, 0x38, 0x1C, 0x18,
0x38, 0x1C, 0x38, 0x1C, 0x30, 0x0E, 0x70, 0x0C,
0x30, 0x07, 0xE0, 0x0C, 0x30, 0x03, 0xC0, 0x0C,
0x30, 0x03, 0xC0, 0x0C, 0x30, 0x07, 0xE0, 0x0C,
0x30, 0x0E, 0x70, 0x0C, 0x38, 0x1C, 0x38, 0x1C,
0x18, 0x38, 0x1C, 0x18, 0x18, 0x10, 0x08, 0x18,
0x0C, 0x00, 0x00, 0x30, 0x0E, 0x00, 0x00, 0x70,
0x07, 0x00, 0x00, 0xE0, 0x03, 0x80, 0x01, 0xC0,
0x01, 0xC0, 0x03, 0x80, 0x00, 0xF8, 0x1F, 0x00,
0x00, 0x3F, 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 9. Door Icon (32x32 pixels)
unsigned char code bmp_door_32x32[128] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0,
0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0,
0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0,
0x0F, 0x00, 0x7C, 0xF0, 0x0F, 0x00, 0x7C, 0xF0, 0x0F, 0x00, 0x7C, 0xF0, 0x0F, 0x00, 0x00, 0xF0,
0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0,
0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0,
0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 10. Siren Icon (32x32 pixels)
unsigned char code bmp_siren_32x32[128] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00,
0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00,
0x06, 0x01, 0x80, 0x60, 0x07, 0x01, 0x80, 0xE0,
0x03, 0x81, 0x81, 0xC0, 0x01, 0xC0, 0x03, 0x80,
0x00, 0xE0, 0x07, 0x00, 0x00, 0x60, 0x06, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7C, 0x0F, 0xF0, 0x3E, 0x00, 0x1F, 0xF8, 0x00,
0x00, 0x3F, 0xFC, 0x00, 0x00, 0x7F, 0xFE, 0x00,
0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00,
0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x80,
0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0,
0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x03, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 11. Unlocked Padlock Icon (32x32 pixels, for confirmation page)
unsigned char code bmp_confirm_lock_32x32[128] = {
0x00, 0x03, 0xC0, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x38, 0x1C, 0x00,
0x00, 0x30, 0x0C, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
0x00, 0x30, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0,
0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x03, 0xC0, 0xF0,
0x0F, 0x03, 0xC0, 0xF0, 0x0F, 0x03, 0xC0, 0xF0, 0x0F, 0x03, 0xC0, 0xF0, 0x0F, 0x01, 0x80, 0xF0,
0x0F, 0x01, 0x80, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xF0,
0x0F, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xE0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Standard ASCII 8x16 Font Library
// 95 printable characters (0x20 to 0x7E)
// Each character takes 16 bytes.
unsigned char code FONT_8x16[95][16] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x20 Space
{0x00,0x00,0x0C,0x1C,0x1C,0x1C,0x0C,0x0C,0x08,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00}, // 0x21 !
{0x00,0x00,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x22 "
{0x00,0x00,0x24,0x24,0x7E,0x24,0x24,0x7E,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00}, // 0x23 #
{0x00,0x00,0x08,0x1C,0x2A,0x0A,0x0A,0x1C,0x28,0x28,0x2A,0x1C,0x08,0x00,0x00,0x00}, // 0x24 $
{0x62,0x94,0x98,0x68,0x10,0x26,0x49,0x49,0x26,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x25 % (向上平移2像素对齐)
{0x00,0x00,0x00,0x0C,0x12,0x12,0x0C,0x15,0x22,0x22,0x1D,0x00,0x00,0x00,0x00,0x00}, // 0x26 &
{0x00,0x00,0x0C,0x0C,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x27 '
{0x00,0x00,0x02,0x04,0x08,0x08,0x10,0x10,0x10,0x10,0x08,0x08,0x04,0x02,0x00,0x00}, // 0x28 (
{0x00,0x00,0x10,0x08,0x04,0x04,0x02,0x02,0x02,0x02,0x04,0x04,0x08,0x10,0x00,0x00}, // 0x29 )
{0x00,0x00,0x00,0x08,0x2A,0x1C,0x08,0x1C,0x2A,0x08,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2A *
{0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x7F,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00}, // 0x2B +
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x08,0x10,0x00,0x00}, // 0x2C ,
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2D -
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00}, // 0x2E .
{0x00,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x00,0x00}, // 0x2F /
{0x00,0x00,0x1C,0x22,0x22,0x26,0x2A,0x32,0x22,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x30 0
{0x00,0x00,0x08,0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x31 1
{0x00,0x00,0x1C,0x22,0x22,0x04,0x08,0x10,0x20,0x22,0x3F,0x00,0x00,0x00,0x00,0x00}, // 0x32 2
{0x00,0x00,0x1C,0x22,0x02,0x06,0x18,0x02,0x02,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x33 3
{0x00,0x00,0x02,0x06,0x0A,0x12,0x22,0x7F,0x02,0x02,0x07,0x00,0x00,0x00,0x00,0x00}, // 0x34 4
{0x00,0x00,0x3F,0x20,0x20,0x3C,0x22,0x02,0x02,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x35 5
{0x00,0x00,0x1C,0x22,0x20,0x3C,0x22,0x22,0x22,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x36 6
{0x00,0x00,0x3F,0x22,0x02,0x04,0x08,0x08,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00}, // 0x37 7
{0x00,0x00,0x1C,0x22,0x22,0x1C,0x22,0x22,0x22,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x38 8
{0x00,0x00,0x1C,0x22,0x22,0x22,0x22,0x1E,0x02,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x39 9
{0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00,0x00,0x00}, // 0x3A :
{0x00,0x00,0x00,0x0C,0x0C,0x00,0x00,0x00,0x00,0x0C,0x0C,0x08,0x10,0x00,0x00,0x00}, // 0x3B ;
{0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x10,0x08,0x04,0x02,0x00,0x00,0x00,0x00,0x00}, // 0x3C <
{0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x3D =
{0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x00,0x00,0x00,0x00,0x00}, // 0x3E >
{0x00,0x00,0x1C,0x22,0x22,0x02,0x04,0x08,0x08,0x00,0x08,0x08,0x00,0x00,0x00,0x00}, // 0x3F ?
{0x00,0x00,0x1C,0x22,0x4A,0x56,0x52,0x52,0x4C,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x40 @
{0x00,0x00,0x08,0x14,0x22,0x22,0x3E,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x41 A
{0x00,0x00,0x3C,0x22,0x22,0x3C,0x22,0x22,0x22,0x22,0x3C,0x00,0x00,0x00,0x00,0x00}, // 0x42 B
{0x00,0x00,0x1C,0x22,0x20,0x20,0x20,0x20,0x20,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x43 C
{0x00,0x00,0x38,0x24,0x22,0x22,0x22,0x22,0x22,0x24,0x38,0x00,0x00,0x00,0x00,0x00}, // 0x44 D
{0x00,0x00,0x3E,0x20,0x20,0x3C,0x20,0x20,0x20,0x20,0x3E,0x00,0x00,0x00,0x00,0x00}, // 0x45 E
{0x00,0x00,0x3E,0x20,0x20,0x3C,0x20,0x20,0x20,0x20,0x20,0x00,0x00,0x00,0x00,0x00}, // 0x46 F
{0x00,0x00,0x1C,0x22,0x20,0x20,0x27,0x22,0x22,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x47 G
{0x00,0x00,0x22,0x22,0x22,0x22,0x3E,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x48 H
{0x00,0x00,0x1C,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x49 I
{0x00,0x00,0x0E,0x02,0x02,0x02,0x02,0x02,0x02,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x4A J
{0x00,0x00,0x22,0x24,0x28,0x30,0x20,0x30,0x28,0x24,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x4B K
{0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3E,0x00,0x00,0x00,0x00,0x00}, // 0x4C L
{0x00,0x00,0x22,0x36,0x2A,0x2A,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x4D M
{0x00,0x00,0x22,0x32,0x2A,0x26,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x4E N
{0x00,0x00,0x1C,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x4F O
{0x00,0x00,0x3C,0x22,0x22,0x3C,0x20,0x20,0x20,0x20,0x20,0x00,0x00,0x00,0x00,0x00}, // 0x50 P
{0x00,0x00,0x1C,0x22,0x22,0x22,0x22,0x2A,0x24,0x22,0x1D,0x00,0x00,0x00,0x00,0x00}, // 0x51 Q
{0x00,0x00,0x3C,0x22,0x22,0x3C,0x28,0x24,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x52 R
{0x00,0x00,0x1C,0x22,0x20,0x1C,0x02,0x02,0x02,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x53 S
{0x00,0x00,0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x54 T
{0x00,0x00,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x55 U
{0x00,0x00,0x22,0x22,0x22,0x22,0x22,0x14,0x14,0x08,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x56 V
{0x00,0x00,0x22,0x22,0x22,0x2A,0x2A,0x2A,0x2A,0x14,0x14,0x00,0x00,0x00,0x00,0x00}, // 0x57 W
{0x00,0x00,0x22,0x22,0x14,0x08,0x08,0x08,0x14,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x58 X
{0x00,0x00,0x22,0x22,0x22,0x14,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x59 Y
{0x00,0x00,0x3E,0x02,0x04,0x08,0x10,0x20,0x20,0x20,0x3E,0x00,0x00,0x00,0x00,0x00}, // 0x5A Z
{0x00,0x00,0x1E,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x1E,0x00,0x00,0x00,0x00,0x00}, // 0x5B [
{0x00,0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01,0x00,0x00,0x00,0x00,0x00}, // 0x5C Backslash
{0x00,0x00,0x3C,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x3C,0x00,0x00,0x00,0x00,0x00}, // 0x5D ]
{0x00,0x00,0x08,0x14,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x5E ^
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00}, // 0x5F _
{0x00,0x00,0x08,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x60 `
{0x00,0x00,0x00,0x00,0x1C,0x02,0x1E,0x22,0x22,0x26,0x1F,0x00,0x00,0x00,0x00,0x00}, // 0x61 a
{0x00,0x00,0x20,0x20,0x3C,0x22,0x22,0x22,0x22,0x22,0x3C,0x00,0x00,0x00,0x00,0x00}, // 0x62 b
{0x00,0x00,0x00,0x00,0x1C,0x22,0x20,0x20,0x20,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x63 c
{0x00,0x00,0x02,0x02,0x1E,0x22,0x22,0x22,0x22,0x22,0x1E,0x00,0x00,0x00,0x00,0x00}, // 0x64 d
{0x00,0x00,0x00,0x00,0x1C,0x22,0x3E,0x20,0x20,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x65 e
{0x00,0x00,0x0E,0x10,0x10,0x3C,0x10,0x10,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00}, // 0x66 f
{0x00,0x00,0x00,0x00,0x1E,0x22,0x22,0x22,0x1E,0x02,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x67 g
{0x00,0x00,0x20,0x20,0x3C,0x22,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x68 h
{0x00,0x00,0x08,0x00,0x18,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x69 i
{0x00,0x00,0x02,0x00,0x06,0x02,0x02,0x02,0x02,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x6A j
{0x00,0x00,0x20,0x20,0x22,0x24,0x28,0x30,0x28,0x24,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x6B k
{0x00,0x00,0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x6C l
{0x00,0x00,0x00,0x00,0x3A,0x2A,0x2A,0x2A,0x2A,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x6D m
{0x00,0x00,0x00,0x00,0x2E,0x22,0x22,0x22,0x22,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x6E n
{0x00,0x00,0x00,0x00,0x1C,0x22,0x22,0x22,0x22,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x6F o
{0x00,0x00,0x00,0x00,0x3C,0x22,0x22,0x3C,0x20,0x20,0x20,0x20,0x20,0x00,0x00,0x00}, // 0x70 p
{0x00,0x00,0x00,0x00,0x1E,0x22,0x22,0x1E,0x02,0x02,0x02,0x02,0x02,0x00,0x00,0x00}, // 0x71 q
{0x00,0x00,0x00,0x00,0x2E,0x12,0x10,0x10,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00}, // 0x72 r
{0x00,0x00,0x00,0x00,0x1E,0x20,0x1C,0x02,0x02,0x22,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x73 s
{0x00,0x00,0x08,0x08,0x3E,0x08,0x08,0x08,0x08,0x08,0x04,0x00,0x00,0x00,0x00,0x00}, // 0x74 t
{0x00,0x00,0x00,0x00,0x22,0x22,0x22,0x22,0x22,0x26,0x1F,0x00,0x00,0x00,0x00,0x00}, // 0x75 u
{0x00,0x00,0x00,0x00,0x22,0x22,0x22,0x14,0x14,0x08,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x76 v
{0x00,0x00,0x00,0x00,0x22,0x22,0x2A,0x2A,0x2A,0x14,0x14,0x00,0x00,0x00,0x00,0x00}, // 0x77 w
{0x00,0x00,0x00,0x00,0x22,0x14,0x08,0x08,0x14,0x22,0x22,0x00,0x00,0x00,0x00,0x00}, // 0x78 x
{0x00,0x00,0x00,0x00,0x22,0x22,0x22,0x1E,0x02,0x1C,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x79 y
{0x00,0x00,0x00,0x00,0x3E,0x04,0x08,0x10,0x20,0x20,0x3E,0x00,0x00,0x00,0x00,0x00}, // 0x7A z
{0x00,0x00,0x06,0x08,0x08,0x08,0x10,0x08,0x08,0x08,0x06,0x00,0x00,0x00,0x00,0x00}, // 0x7B {
{0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x7C |
{0x00,0x00,0x60,0x10,0x10,0x10,0x08,0x10,0x10,0x10,0x60,0x00,0x00,0x00,0x00,0x00}, // 0x7D }
{0x00,0x00,0x00,0x12,0x2C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} // 0x7E ~
};
#endif

288
Drivers/rf.c Normal file
View File

@@ -0,0 +1,288 @@
#include "rf.h"
#include "../App/config.h"
#include "intrins.h"
// ATR5179 射频开关控制脚
sbit RF_TX = P2^0; // V1 控制端 (1: 选通天线到 TX 路径)
sbit RF_RX = P3^7; // V2 控制端 (1: 选通天线到 RX 路径)
// 射频数据与控制引脚
sbit RF_TX_DAT = P3^6; // LT4455 数据输入 (DIN)
sbit RF_RX_DATA = P2^1; // LR690L 数据输出 (DATA)
sbit SHUT = P2^2; // LR690L 休眠脚 (0:工作, 1:休眠)
// External functions defined in main.c
extern void Delay_us(u16 us);
extern void Delay_ms(u16 ms);
extern u16 GetTimer0_Safe(void);
extern void Uart_SendString(char *s);
extern void Uart_SendHex4(u8 val);
extern void Uart_SendHex8(u8 val);
extern void Uart_SendHex16(u16 val);
extern void Uart_SendHex20(u32 val);
extern void Uart_SendByte(u8 dat);
void RF_Init(void)
{
// P2.0 (RF_TX) 和 P2.2 (SHUT) 配置为推挽输出P2.1 (RF_RX_DATA) 配置为双向口/输入
P2M1 &= ~((1 << 0) | (1 << 1) | (1 << 2));
P2M0 &= ~(1 << 1);
P2M0 |= ((1 << 0) | (1 << 2));
// P3.6 (RF_TX_DAT), P3.7 (RF_RX) 配置为推挽输出
P3M1 &= ~((1 << 6) | (1 << 7));
P3M0 |= ((1 << 6) | (1 << 7));
RF_TX = 0;
SHUT = 1; // 默认关闭接收芯片
RF_RX = 0;
RF_TX_DAT = 0;
}
void RF_SetMode(u8 mode)
{
if (mode == 0) // Sleep/Idle
{
SHUT = 1; // LR690L Sleep
RF_RX = 0; // 断开接收天线
RF_TX = 0; // 断开发射天线
}
else if (mode == 1) // Rx Mode
{
SHUT = 0; // LR690L 工作
RF_RX = 1; // 天线切至接收端
RF_TX = 0;
}
else if (mode == 2) // Tx Mode
{
SHUT = 1; // LR690L 睡眠
RF_RX = 0;
RF_TX = 1; // 天线切至发射端
}
}
void EV1527_TxFrame(u32 addr, u8 dat)
{
u8 i;
// 1. 同步脉冲: 1T 高电平 + 31T 低电平
RF_TX_DAT = 1;
Delay_us(320);
RF_TX_DAT = 0;
Delay_us(9920);
// 2. 20位地址码 (MSB first)
for (i = 0; i < 20; i++)
{
if ((addr >> (19 - i)) & 1)
{
// 逻辑 1: 3T 高电平 + 1T 低电平
RF_TX_DAT = 1;
Delay_us(960);
RF_TX_DAT = 0;
Delay_us(320);
}
else
{
// 逻辑 0: 1T 高电平 + 3T 低电平
RF_TX_DAT = 1;
Delay_us(320);
RF_TX_DAT = 0;
Delay_us(960);
}
}
// 3. 4位数据码 (MSB first)
for (i = 0; i < 4; i++)
{
if ((dat >> (3 - i)) & 1)
{
// 逻辑 1: 3T 高电平 + 1T 低电平
RF_TX_DAT = 1;
Delay_us(960);
RF_TX_DAT = 0;
Delay_us(320);
}
else
{
// 逻辑 0: 1T 高电平 + 3T 低电平
RF_TX_DAT = 1;
Delay_us(320);
RF_TX_DAT = 0;
Delay_us(960);
}
}
}
void EV1527_Transmit(u32 addr, u8 dat)
{
u8 r;
for (r = 0; r < 25; r++) // 增加到 25 帧(约 340ms
{
EV1527_TxFrame(addr, dat);
}
}
u16 GetPulseDuration(u8 state, u16 timeout_us)
{
u16 timeout_ticks = (u16)((u32)timeout_us * (MAIN_Fosc / 1000000UL) / 12UL);
TL0 = 0;
TH0 = 0;
TR0 = 1; // 启动定时器0
while (RF_RX_DATA == state)
{
if (GetTimer0_Safe() > timeout_ticks)
{
TR0 = 0;
return 0; // 超时
}
}
TR0 = 0;
return (u16)((u32)GetTimer0_Safe() * 12UL / (MAIN_Fosc / 1000000UL)); // 返回微秒数
}
bit EV1527_Decode(u32 *out_addr, u8 *out_data)
{
u16 high_time, low_time;
u8 i;
u32 addr = 0;
u8 dat = 0;
u16 T;
u16 idata raw_h[24];
u16 idata raw_l[24];
// 1. 同步头捕获: 必须是低电平状态之后变高电平开始
if (RF_RX_DATA == 0)
{
u16 wait_cnt = 0;
while (RF_RX_DATA == 0)
{
Delay_us(10);
wait_cnt++;
if (wait_cnt > 3000) // 30ms 超时
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;
}
// 估算基准脉冲周期 T = low_time / 31
T = low_time / 31;
if (T == 0) T = 1;
// 2. 连续抓取 24 个数据脉冲
for (i = 0; i < 24; i++)
{
u16 wait_cnt = 0;
// 等待下一个数据脉冲的上升沿 (如果当前是低电平,等待它变高)
while (RF_RX_DATA == 0)
{
Delay_us(5);
wait_cnt++;
if (wait_cnt > 1000) // 5ms 超时
{
return 0; // 解码失败
}
}
// 测量高电平持续时间和低电平持续时间
raw_h[i] = GetPulseDuration(1, 5000);
if (raw_h[i] == 0) return 0;
raw_l[i] = GetPulseDuration(0, 5000);
if (raw_l[i] == 0) return 0;
}
// 3. 将抓到的全部 24 位脉冲数据无条件输出到串口 (缩短输出信息以防止阻塞接收)
Uart_SendString("Captured: Sync H=");
Uart_SendHex16(high_time);
Uart_SendString("us, L=");
Uart_SendHex16(low_time);
Uart_SendString("us, T=");
Uart_SendHex16(T);
Uart_SendString("us\r\n");
// 4. 组合成 24 位地址与数据,使用自适应阈值:如果高电平时间比低电平时间长,判定为 1反之为 0
for (i = 0; i < 24; i++)
{
if (raw_h[i] > raw_l[i])
{
if (i < 20) addr = (addr << 1) | 1;
else dat = (dat << 1) | 1;
}
else
{
if (i < 20) addr = (addr << 1);
else dat = (dat << 1);
}
}
*out_addr = addr;
*out_data = dat;
// 串口打印解码结果方便现场调试
Uart_SendString("Decoded ADDR: 0x");
Uart_SendHex20(addr);
Uart_SendString(", DATA: 0x");
Uart_SendHex4(dat);
Uart_SendString("\r\n");
return 1;
}
void Loopback_Test(void)
{
u16 match_count = 0;
u16 i;
SHUT = 0; // 使能接收芯片
RF_RX = 1; // 选通天线到接收端
RF_TX = 0; // 发送侧断开
Delay_ms(100);
Uart_SendString("Starting RF Loopback Self-Test...\r\n");
for (i = 0; i < 100; i++)
{
RF_TX_DAT = 1;
Delay_us(500);
if (RF_RX_DATA == 1) match_count++;
RF_TX_DAT = 0;
Delay_us(500);
if (RF_RX_DATA == 0) match_count++;
}
Uart_SendString("Loopback Match Count: ");
Uart_SendHex16(match_count);
Uart_SendString("/200\r\n");
if (match_count > 150)
{
Uart_SendString("Result: PASS! Transmitter is emitting RF, and Receiver is picking it up!\r\n");
}
else
{
Uart_SendString("Result: FAIL! No RF signal detected. Check hardware.\r\n");
}
SHUT = 1;
RF_RX = 0;
}

16
Drivers/rf.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef __RF_H__
#define __RF_H__
#include "../App/config.h"
#include "stc32g.h"
// Public RF functions
void RF_Init(void);
void RF_SetMode(u8 mode); // mode: 0=Sleep, 1=Rx, 2=Tx
void EV1527_TxFrame(u32 addr, u8 dat);
void EV1527_Transmit(u32 addr, u8 dat);
u16 GetPulseDuration(u8 state, u16 timeout_us);
bit EV1527_Decode(u32 *out_addr, u8 *out_data);
void Loopback_Test(void);
#endif

1487
Drivers/stc32g.h Normal file

File diff suppressed because it is too large Load Diff

47181
Hardware/text.kicad_pcb Normal file

File diff suppressed because it is too large Load Diff

138
Hardware/text.kicad_prl Normal file
View File

@@ -0,0 +1,138 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [
"GND"
],
"high_contrast_mode": 1,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"drc_exclusions",
"locked_item_shadows",
"conflict_shadows",
"shapes",
"board_outline_area",
"ly_points"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 0
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "text.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [
174,
159,
100,
63,
100,
100,
100,
63,
100,
100
],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": 0
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": false,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true
}
}
}

691
Hardware/text.kicad_pro Normal file
View File

@@ -0,0 +1,691 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 0.310008,
"width": 0.529997
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"border_display_style": 2,
"border_hatch_pitch": 0.5,
"corner_radius": 0.0,
"corner_smoothing": 0,
"fill_mode": 0,
"hatch_gap": 1.5,
"hatch_orientation": 0.0,
"hatch_smoothing_level": 0,
"hatch_smoothing_value": 0.1,
"hatch_thickness": 1.0,
"min_clearance": 0.5,
"min_island_area": 10.0,
"min_thickness": 0.25,
"pad_connection": 1,
"remove_islands": 0,
"thermal_relief_gap": 0.5,
"thermal_relief_spoke_width": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "error",
"padstack": "warning",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 1,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.1,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": true
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [
{
"bottomLayer": 2,
"enabled": true,
"topLayer": 0
}
],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"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": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "text.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.15,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"idf": "",
"netlist": "",
"plot": "",
"specctra_dsn": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "位号"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"page_layout_descr_file": "",
"plot_directory": "",
"reuse_designators": true,
"subpart_first_id": 65,
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "text.kicad_sch",
"name": "text",
"uuid": "72df0bee-e55e-4d81-989a-168573031a0a"
}
],
"used_designators": "#PWR28,#PWR57-58",
"variants": []
},
"sheets": [
[
"72df0bee-e55e-4d81-989a-168573031a0a",
"text"
]
],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}

25670
Hardware/text.kicad_sch Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<rect x="4" y="4" width="112" height="232" rx="20" stroke="#EF4444" stroke-width="2.5" fill="none" stroke-dasharray="6 4"/>
<g transform="translate(0, 10)">
<rect x="52" y="55" width="16" height="30" rx="1" fill="none" stroke="#EF4444" stroke-width="2.5"/>
<circle cx="64" cy="70" r="1.5" fill="#EF4444"/>
</g>
<text x="60" y="130" fill="#EF4444" font-family="system-ui, sans-serif" font-size="11" font-weight="bold" text-anchor="middle">FRONT DOOR</text>
<text x="60" y="165" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">ZONE 0 | ALARM</text>
</svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<!-- Status Bar (Right: Battery Icon) -->
<rect x="82" y="27" width="20" height="10" rx="2" fill="none" stroke="#64748B" stroke-width="1.2"/>
<rect x="84" y="29" width="13" height="6" rx="1" fill="#10B981"/>
<rect x="103" y="30" width="1.5" height="4" rx="0.5" fill="#64748B"/>
<text x="76" y="36" fill="#64748B" font-family="system-ui, sans-serif" font-size="9" text-anchor="end">85%</text>
<!-- Time -->
<text x="60" y="115" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="24" font-weight="bold" text-anchor="middle">10:35</text>
<!-- Date -->
<text x="60" y="165" fill="#64748B" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">THU 07-02</text>
<!-- Armed Status at the very bottom (Y=205) -->
<g transform="translate(36, 192)">
<rect x="0" y="0" width="12" height="12" rx="2" fill="none" stroke="#EF4444" stroke-width="1.2"/>
<circle cx="6" cy="4" r="2.5" fill="none" stroke="#EF4444" stroke-width="1.2"/>
<rect x="5" y="6" width="2" height="3" fill="#EF4444"/>
</g>
<text x="60" y="202" fill="#EF4444" font-family="system-ui, sans-serif" font-size="9" font-weight="bold" text-anchor="middle"> ARMED</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<!-- Status Bar (Right: Battery Icon) -->
<rect x="82" y="27" width="20" height="10" rx="2" fill="none" stroke="#64748B" stroke-width="1.2"/>
<rect x="84" y="29" width="13" height="6" rx="1" fill="#10B981"/>
<rect x="103" y="30" width="1.5" height="4" rx="0.5" fill="#64748B"/>
<text x="76" y="36" fill="#64748B" font-family="system-ui, sans-serif" font-size="9" text-anchor="end">85%</text>
<!-- Time -->
<text x="60" y="115" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="24" font-weight="bold" text-anchor="middle">10:35</text>
<!-- Date -->
<text x="60" y="165" fill="#64748B" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">THU 07-02</text>
</svg>

After

Width:  |  Height:  |  Size: 854 B

View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<!-- Status Bar (Right: Battery Icon) -->
<rect x="82" y="27" width="20" height="10" rx="2" fill="none" stroke="#64748B" stroke-width="1.2"/>
<rect x="84" y="29" width="13" height="6" rx="1" fill="#10B981"/>
<rect x="103" y="30" width="1.5" height="4" rx="0.5" fill="#64748B"/>
<text x="76" y="36" fill="#64748B" font-family="system-ui, sans-serif" font-size="9" text-anchor="end">85%</text>
<!-- Time -->
<text x="60" y="115" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="24" font-weight="bold" text-anchor="middle">10:35</text>
<!-- Date -->
<text x="60" y="165" fill="#64748B" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">THU 07-02</text>
<!-- Disarmed Status at the very bottom (Y=205) -->
<g transform="translate(32, 192)">
<rect x="0" y="3" width="12" height="9" rx="1.5" fill="none" stroke="#10B981" stroke-width="1.2"/>
<path d="M 3 3 L 3 1.5 C 3 0.7, 6 0.7, 8 1.5 L 8 4" fill="none" stroke="#10B981" stroke-width="1.2"/>
</g>
<text x="60" y="202" fill="#10B981" font-family="system-ui, sans-serif" font-size="9" font-weight="bold" text-anchor="middle"> DISARM</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<circle cx="60" cy="120" r="58" stroke="#334155" stroke-width="1" fill="none"/>
<g transform="translate(0, 10)">
<circle cx="60" cy="80" r="28" stroke="#FFB74D" stroke-width="1.2" stroke-dasharray="3 3" fill="none"/>
<rect x="46" y="74" width="28" height="20" rx="3" fill="none" stroke="#FFB74D" stroke-width="2.5"/>
<circle cx="60" cy="81" r="2.5" fill="#FFB74D"/>
<path d="M 60 83.5 L 60 88" stroke="#FFB74D" stroke-width="2" stroke-linecap="round"/>
<path d="M 52 74 L 52 64 A 8 8 0 0 1 68 64 L 68 70" fill="none" stroke="#FFB74D" stroke-width="2.5" stroke-linecap="round"/>
</g>
<text x="60" y="145" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="11" font-weight="bold" text-anchor="middle">CODE FOUND</text>
<text x="60" y="170" fill="#FFB74D" font-family="system-ui, sans-serif" font-size="10" font-weight="bold" text-anchor="middle">HOLD SOS TO SAVE</text>
<text x="60" y="192" fill="#64748B" font-family="system-ui, sans-serif" font-size="9" text-anchor="middle">ADDR: 0x37A86</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<!-- Symmetrical Circular Fail Badge -->
<circle cx="60" cy="90" r="24" stroke="#EF4444" stroke-width="2.5" fill="none"/>
<path d="M 50 80 L 70 100 M 70 80 L 50 100" stroke="#EF4444" stroke-width="3.5" fill="none" stroke-linecap="round"/>
<text x="60" y="155" fill="#EF4444" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">PAIR FAILED</text>
<text x="60" y="180" fill="#64748B" font-family="system-ui, sans-serif" font-size="9" text-anchor="middle">TYPE MISMATCH</text>
</svg>

After

Width:  |  Height:  |  Size: 673 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<g transform="translate(0, 10)">
<path d="M 60 70 L 60 90 M 52 75 A 10 10 0 0 1 68 75 M 48 70 A 15 15 0 0 1 72 70 M 44 65 A 20 20 0 0 1 76 65" stroke="#00E5FF" stroke-width="2" fill="none" stroke-linecap="round"/>
<circle cx="60" cy="92" r="2" fill="#00E5FF"/>
</g>
<text x="60" y="145" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="11" font-weight="bold" text-anchor="middle">SEARCHING...</text>
<text x="60" y="170" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="9" text-anchor="middle">TRIGGER SENSOR</text>
</svg>

After

Width:  |  Height:  |  Size: 702 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<text x="60" y="35" fill="#FFB74D" font-family="system-ui, sans-serif" font-size="10" font-weight="bold" text-anchor="middle">PAIR MENU P-00</text>
<text x="60" y="80" fill="#64748B" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">🚪 DOOR SENSOR</text>
<rect x="10" y="105" width="100" height="30" rx="6" fill="none" stroke="#00E5FF" stroke-width="1.5"/>
<text x="60" y="124" fill="#00E5FF" font-family="system-ui, sans-serif" font-size="10" font-weight="bold" text-anchor="middle">👤 PIR MOTION</text>
<text x="60" y="165" fill="#64748B" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">🔥 SMOKE DETECT</text>
</svg>

After

Width:  |  Height:  |  Size: 818 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<!-- Symmetrical Circular Success Badge -->
<circle cx="60" cy="90" r="24" stroke="#10B981" stroke-width="2.5" fill="none"/>
<path d="M 48 90 L 56 98 L 72 82" stroke="#10B981" stroke-width="3.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
<text x="60" y="145" fill="#10B981" font-family="system-ui, sans-serif" font-size="12" font-weight="bold" text-anchor="middle">PAIR SUCCESS</text>
<text x="60" y="175" fill="#64748B" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">ADDR: 0x37A86</text>
</svg>

After

Width:  |  Height:  |  Size: 692 B

View File

@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="240" viewBox="0 0 120 240">
<rect width="120" height="240" fill="#000000"/>
<rect x="4" y="4" width="112" height="232" rx="20" stroke="#EF4444" stroke-width="2.5" fill="none"/>
<rect x="8" y="8" width="104" height="224" rx="17" stroke="#FFFFFF" stroke-width="1" fill="none"/>
<!-- High-Fidelity Siren Icon -->
<g transform="translate(0, -3)">
<!-- Light Beams -->
<path d="M 60 48 L 60 54 M 45 52 L 50 57 M 75 52 L 70 57 M 40 65 L 46 67 M 80 65 L 74 67" stroke="#EF4444" stroke-width="2" stroke-linecap="round"/>
<!-- Siren Glass Dome -->
<path d="M 46 82 C 46 64, 74 64, 74 82 Z" fill="#EF4444" stroke="#EF4444" stroke-width="1.5"/>
<!-- Bulb inside -->
<circle cx="60" cy="77" r="4.5" fill="#FFFFFF"/>
<!-- Metallic Base -->
<rect x="42" y="82" width="36" height="6" rx="1.5" fill="#64748B"/>
</g>
<!-- Centered SOS Alarm Texts safely inside circular active area -->
<text x="60" y="138" fill="#EF4444" font-family="system-ui, sans-serif" font-size="11" font-weight="bold" text-anchor="middle">🆘 EMIT SOS</text>
<text x="60" y="165" fill="#FFFFFF" font-family="system-ui, sans-serif" font-size="10" text-anchor="middle">BAND TRIGGERED</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

224
wristband.uvopt Normal file
View File

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

353
wristband.uvproj Normal file
View File

@@ -0,0 +1,353 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<Project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="project_proj.xsd">
<SchemaVersion>1.1</SchemaVersion>
<Header>### uVision Project, (C) Keil Software</Header>
<Targets>
<Target>
<TargetName>Wristband</TargetName>
<ToolsetNumber>0x1</ToolsetNumber>
<ToolsetName>MCS-251</ToolsetName>
<uAC6>0</uAC6>
<TargetOption>
<TargetCommonOption>
<Device>STC32G12K128 Series</Device>
<Vendor>STC</Vendor>
<Cpu>IRAM(0-0xFFF) XRAM(0x10000-0x11FFF) IROM(0xFE0000-0xFFFFFF) CLOCK(35000000)</Cpu>
<FlashUtilSpec></FlashUtilSpec>
<StartupFile>"LIB\STARTUP251.ASM" ("80251 Startup Code")</StartupFile>
<FlashDriverDll></FlashDriverDll>
<DeviceId>63457</DeviceId>
<RegisterFile>STC16F.H</RegisterFile>
<MemoryEnv></MemoryEnv>
<Cmp></Cmp>
<Asm></Asm>
<Linker></Linker>
<OHString></OHString>
<InfinionOptionDll></InfinionOptionDll>
<SLE66CMisc></SLE66CMisc>
<SLE66AMisc></SLE66AMisc>
<SLE66LinkerMisc></SLE66LinkerMisc>
<SFDFile></SFDFile>
<bCustSvd>0</bCustSvd>
<UseEnv>0</UseEnv>
<BinPath></BinPath>
<IncludePath></IncludePath>
<LibPath></LibPath>
<RegisterFilePath>STC\</RegisterFilePath>
<DBRegisterFilePath>STC\</DBRegisterFilePath>
<TargetStatus>
<Error>0</Error>
<ExitCodeStop>0</ExitCodeStop>
<ButtonStop>0</ButtonStop>
<NotGenerated>0</NotGenerated>
<InvalidFlash>1</InvalidFlash>
</TargetStatus>
<OutputDirectory>.\Build\</OutputDirectory>
<OutputName>wristband</OutputName>
<CreateExecutable>1</CreateExecutable>
<CreateLib>0</CreateLib>
<CreateHexFile>1</CreateHexFile>
<DebugInformation>1</DebugInformation>
<BrowseInformation>1</BrowseInformation>
<ListingPath>.\Build\</ListingPath>
<HexFormatSelection>1</HexFormatSelection>
<Merge32K>0</Merge32K>
<CreateBatchFile>0</CreateBatchFile>
<BeforeCompile>
<RunUserProg1>0</RunUserProg1>
<RunUserProg2>0</RunUserProg2>
<UserProg1Name></UserProg1Name>
<UserProg2Name></UserProg2Name>
<UserProg1Dos16Mode>0</UserProg1Dos16Mode>
<UserProg2Dos16Mode>0</UserProg2Dos16Mode>
<nStopU1X>0</nStopU1X>
<nStopU2X>0</nStopU2X>
</BeforeCompile>
<BeforeMake>
<RunUserProg1>0</RunUserProg1>
<RunUserProg2>0</RunUserProg2>
<UserProg1Name></UserProg1Name>
<UserProg2Name></UserProg2Name>
<UserProg1Dos16Mode>0</UserProg1Dos16Mode>
<UserProg2Dos16Mode>0</UserProg2Dos16Mode>
<nStopB1X>0</nStopB1X>
<nStopB2X>0</nStopB2X>
</BeforeMake>
<AfterMake>
<RunUserProg1>0</RunUserProg1>
<RunUserProg2>0</RunUserProg2>
<UserProg1Name></UserProg1Name>
<UserProg2Name></UserProg2Name>
<UserProg1Dos16Mode>0</UserProg1Dos16Mode>
<UserProg2Dos16Mode>0</UserProg2Dos16Mode>
<nStopA1X>0</nStopA1X>
<nStopA2X>0</nStopA2X>
</AfterMake>
<SelectedForBatchBuild>0</SelectedForBatchBuild>
<SVCSIdString></SVCSIdString>
</TargetCommonOption>
<CommonProperty>
<UseCPPCompiler>0</UseCPPCompiler>
<RVCTCodeConst>0</RVCTCodeConst>
<RVCTZI>0</RVCTZI>
<RVCTOtherData>0</RVCTOtherData>
<ModuleSelection>0</ModuleSelection>
<IncludeInBuild>1</IncludeInBuild>
<AlwaysBuild>0</AlwaysBuild>
<GenerateAssemblyFile>0</GenerateAssemblyFile>
<AssembleAssemblyFile>0</AssembleAssemblyFile>
<PublicsOnly>0</PublicsOnly>
<StopOnExitCode>3</StopOnExitCode>
<CustomArgument></CustomArgument>
<IncludeLibraryModules></IncludeLibraryModules>
<ComprImg>1</ComprImg>
<BankNo>65535</BankNo>
</CommonProperty>
<DllOption>
<SimDllName>S251.DLL</SimDllName>
<SimDllArguments></SimDllArguments>
<SimDlgDll>DCORE51.DLL</SimDlgDll>
<SimDlgDllArguments>-p251</SimDlgDllArguments>
<TargetDllName>S251.DLL</TargetDllName>
<TargetDllArguments></TargetDllArguments>
<TargetDlgDll>TCORE51.DLL</TargetDlgDll>
<TargetDlgDllArguments>-p251</TargetDlgDllArguments>
</DllOption>
<DebugOption>
<OPTHX>
<HexSelection>1</HexSelection>
<HexRangeLowAddress>0</HexRangeLowAddress>
<HexRangeHighAddress>0</HexRangeHighAddress>
<HexOffset>0</HexOffset>
<Oh166RecLen>16</Oh166RecLen>
</OPTHX>
<Simulator>
<UseSimulator>1</UseSimulator>
<LoadApplicationAtStartup>1</LoadApplicationAtStartup>
<RunToMain>1</RunToMain>
<RestoreBreakpoints>1</RestoreBreakpoints>
<RestoreWatchpoints>1</RestoreWatchpoints>
<RestoreMemoryDisplay>1</RestoreMemoryDisplay>
<RestoreFunctions>1</RestoreFunctions>
<RestoreToolbox>1</RestoreToolbox>
<LimitSpeedToRealTime>0</LimitSpeedToRealTime>
<RestoreSysVw>1</RestoreSysVw>
</Simulator>
<Target>
<UseTarget>0</UseTarget>
<LoadApplicationAtStartup>1</LoadApplicationAtStartup>
<RunToMain>0</RunToMain>
<RestoreBreakpoints>1</RestoreBreakpoints>
<RestoreWatchpoints>1</RestoreWatchpoints>
<RestoreMemoryDisplay>1</RestoreMemoryDisplay>
<RestoreFunctions>0</RestoreFunctions>
<RestoreToolbox>1</RestoreToolbox>
<RestoreTracepoints>1</RestoreTracepoints>
<RestoreSysVw>1</RestoreSysVw>
</Target>
<RunDebugAfterBuild>0</RunDebugAfterBuild>
<TargetSelection>-1</TargetSelection>
<SimDlls>
<CpuDll></CpuDll>
<CpuDllArguments></CpuDllArguments>
<PeripheralDll></PeripheralDll>
<PeripheralDllArguments></PeripheralDllArguments>
<InitializationFile></InitializationFile>
</SimDlls>
<TargetDlls>
<CpuDll></CpuDll>
<CpuDllArguments></CpuDllArguments>
<PeripheralDll></PeripheralDll>
<PeripheralDllArguments></PeripheralDllArguments>
<InitializationFile></InitializationFile>
<Driver></Driver>
</TargetDlls>
</DebugOption>
<Utilities>
<Flash1>
<UseTargetDll>1</UseTargetDll>
<UseExternalTool>0</UseExternalTool>
<RunIndependent>0</RunIndependent>
<UpdateFlashBeforeDebugging>0</UpdateFlashBeforeDebugging>
<Capability>0</Capability>
<DriverSelection>-1</DriverSelection>
</Flash1>
<bUseTDR>0</bUseTDR>
<Flash2></Flash2>
<Flash3></Flash3>
<Flash4></Flash4>
<pFcarmOut></pFcarmOut>
<pFcarmGrp></pFcarmGrp>
<pFcArmRoot></pFcArmRoot>
<FcArmLst>0</FcArmLst>
</Utilities>
<Target251>
<Target251Misc>
<MemoryModel>3</MemoryModel>
<RTOS>0</RTOS>
<RomSize>3</RomSize>
<NearDataHold>0</NearDataHold>
<XDataHold>0</XDataHold>
<FarDataHold>0</FarDataHold>
<uocRom>0</uocRom>
<uocXRAM>0</uocXRAM>
<uSrcBin>1</uSrcBin>
<uFrame4>0</uFrame4>
<hadIRAM>1</hadIRAM>
<hadXRAM>1</hadXRAM>
<hadIROM>1</hadIROM>
<Use_Code_Banking>0</Use_Code_Banking>
<uCC7>0</uCC7>
<fp_hp>0</fp_hp>
<CBANKS2>0</CBANKS2>
<OnChipMemories>
<RCB>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x10000</Size>
</RCB>
<IROM>
<Type>1</Type>
<StartAddress>0xfe0000</StartAddress>
<Size>0x20000</Size>
</IROM>
<IRAM>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x1000</Size>
</IRAM>
<XRAM>
<Type>0</Type>
<StartAddress>0x10000</StartAddress>
<Size>0x2000</Size>
</XRAM>
<Ocm1>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x0</Size>
</Ocm1>
<Ocm2>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x0</Size>
</Ocm2>
<Ocm3>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x0</Size>
</Ocm3>
<Ocm4>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x0</Size>
</Ocm4>
<Ocm5>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x0</Size>
</Ocm5>
<Ocm6>
<Type>0</Type>
<StartAddress>0x0</StartAddress>
<Size>0x0</Size>
</Ocm6>
</OnChipMemories>
</Target251Misc>
<C251>
<RegColor>0</RegColor>
<uOrder>0</uOrder>
<uAlias>1</uAlias>
<uRentF>0</uRentF>
<uUch>0</uUch>
<uFlt64>0</uFlt64>
<Fuzzy>3</Fuzzy>
<Optim>7</Optim>
<wLevel>2</wLevel>
<SizSpd>1</SizSpd>
<AcaOpt>0</AcaOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
<Undefine></Undefine>
<IncludePath></IncludePath>
</VariousControls>
</C251>
<Ax51>
<UseMpl>0</UseMpl>
<UseStandard>1</UseStandard>
<UseCase>0</UseCase>
<UseMod51>0</UseMod51>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
<Undefine></Undefine>
<IncludePath></IncludePath>
</VariousControls>
</Ax51>
<Lx51>
<useFile>0</useFile>
<linkonly>0</linkonly>
<UseMemoryFromTarget>1</UseMemoryFromTarget>
<CaseSensitiveSymbols>1</CaseSensitiveSymbols>
<WarningLevel>2</WarningLevel>
<DataOverlaying>1</DataOverlaying>
<OverlayString></OverlayString>
<MiscControls></MiscControls>
<DisableWarningNumbers></DisableWarningNumbers>
<LinkerCmdFile></LinkerCmdFile>
<Assign></Assign>
<ReserveString></ReserveString>
<CClasses></CClasses>
<UserClasses></UserClasses>
<CSection></CSection>
<UserSection></UserSection>
<CodeBaseAddress></CodeBaseAddress>
<XDataBaseAddress></XDataBaseAddress>
<PDataBaseAddress></PDataBaseAddress>
<BitBaseAddress></BitBaseAddress>
<DataBaseAddress></DataBaseAddress>
<IDataBaseAddress></IDataBaseAddress>
<Precede></Precede>
<Stack></Stack>
<CodeSegmentName></CodeSegmentName>
<XDataSegmentName></XDataSegmentName>
<BitSegmentName></BitSegmentName>
<DataSegmentName></DataSegmentName>
<IDataSegmentName></IDataSegmentName>
</Lx51>
</Target251>
</TargetOption>
<Groups>
<Group>
<GroupName>App</GroupName>
<Files>
<File>
<FileName>main.c</FileName>
<FileType>1</FileType>
<FilePath>.\App\main.c</FilePath>
</File>
</Files>
</Group>
<Group>
<GroupName>Drivers</GroupName>
<Files>
<File>
<FileName>lcd.c</FileName>
<FileType>1</FileType>
<FilePath>.\Drivers\lcd.c</FilePath>
</File>
<File>
<FileName>rf.c</FileName>
<FileType>1</FileType>
<FilePath>.\Drivers\rf.c</FilePath>
</File>
</Files>
</Group>
</Groups>
</Target>
</Targets>
</Project>