Files
stc32g128k/Docs/50_module-breakdown/mod-event.md
2026-07-15 15:57:37 +08:00

598 lines
24 KiB
Markdown
Raw Permalink 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缓冲、紧急事件插队机制以及事件输出分配的整体流转规格。
---
## 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中执行耗时操作。
```