Files
stc32g128k/Docs/30_architecture/main.md
2026-07-15 15:57:37 +08:00

363 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 架构设计说明书 (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 -->