Files
stc32g128k/Docs/50_module-breakdown/mod-event.md

581 lines
24 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/50_module-breakdown/mod-event.md)
本模块描述系统输入事件采集(按键移位消抖滤波与 RF 自适应解调解码、事件优先级体系、事件环形队列EventQueue缓冲、紧急事件直通机制以及事件输出分配EventDispatcher的整体流转规格。
---
## 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_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` | 获取队列中当前事件数量 |
| `EventQueue_EmergencyDispatch` | `void EventQueue_EmergencyDispatch(SystemEvent evt)` | 无 | **紧急事件直通**:跳过队列直接调用应用管理器分发 |
### 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_EmergencyDispatch)
```
EventQueue_EmergencyDispatch(evt) 执行流程:
1. 直接调用应用管理器的事件分发函数
└── AppManager_DispatchEvent(evt)
注意:此函数跳过队列,直接将事件发送给应用管理器,
适用于最高优先级的紧急事件如SOS确保立即响应。
```
### 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 -->|URGENT| EmergencyPath["紧急直通路径"]
PriorityJudge -->|非URGENT| NormalPath["正常队列路径"]
%% 正常路径
NormalPath --> Queue["EventQueue 环形缓冲区"]
Queue --> MainLoop["主循环轮询"]
MainLoop --> AppMgr["AppManager_DispatchEvent"]
%% 紧急路径
EmergencyPath --> AppMgr
%% 应用层处理
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_URGENT)
EventQueue_EmergencyDispatch(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_EmergencyDispatch(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_EmergencyDispatch()` → 直接调用 `AppManager_DispatchEvent()` | SOS 按键 | 跳过队列,立即响应,最高优先级 |
| **HIGH** | 2 | `EventQueue_Push()` → 入队等待 → `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_Pop` | `SystemEvent EventQueue_Pop(void)` | 从队列头部弹出一个事件,队空返回空事件 |
| `EventQueue_IsEmpty` | `bit EventQueue_IsEmpty(void)` | 判断队列是否为空 |
| `EventQueue_IsFull` | `bit EventQueue_IsFull(void)` | 判断队列是否已满 |
| `EventQueue_GetCount` | `u8 EventQueue_GetCount(void)` | 获取队列中当前事件数量 |
| `EventQueue_EmergencyDispatch` | `void EventQueue_EmergencyDispatch(SystemEvent evt)` | **紧急事件直通**:跳过队列直接分发 |
### 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_EmergencyDispatch(evt) │
│ → AppManager_DispatchEvent(evt) │
│ → active_app->onEvent(evt) │
│ → 返回 EVENT_HANDLED │
└──────────────────────────────────────────────────────────────┘
特点:紧急事件仍在主循环中处理,但跳过队列直接分发,减少延迟。
中断中仅标记事件不直接调用应用逻辑避免在ISR中执行耗时操作。
```