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

24 KiB
Raw Blame History

模块拆分 - 事件采集与总线分发管理 (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)

typedef struct {
    KeyEvent key_event;     // 原始按键事件类型
    EventPriority priority; // 事件优先级 (LOW/NORMAL/HIGH/URGENT)
    u32 extra_data;         // 额外数据(如射频地址、时间信息等)
    u8 extra_size;          // 额外数据有效字节数
} SystemEvent;

字段说明

字段 类型 描述
key_event KeyEvent 按键事件的原始类型,如 KEY_EVENT_UP_CLICKKEY_EVENT_SOS_LONG
priority EventPriority 事件优先级,决定事件的处理路径(入队或直通)
extra_data u32 额外携带的数据,用于传递射频地址、时间等信息
extra_size u8 extra_data 中有效数据的字节数0~4

2.2 事件优先级枚举

typedef enum {
    EVENT_PRIORITY_LOW,     // 0: 低优先级:系统内部事件,如定时更新
    EVENT_PRIORITY_NORMAL,  // 1: 正常优先级:大部分用户交互事件
    EVENT_PRIORITY_HIGH,    // 2: 高优先级:报警相关事件
    EVENT_PRIORITY_URGENT   // 3: 紧急优先级SOS等直接越过队列
} EventPriority;

2.3 按键事件类型枚举

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 队列内部结构

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 事件流转完整架构

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中执行耗时操作。