docs: 重构系统层级为自下而上,剥离独立事件管理模块,全量校正GPIO引脚映射为原理图线标

This commit is contained in:
2026-07-10 10:04:40 +08:00
parent fdda10aadc
commit a04ea5fb51
28 changed files with 291 additions and 120 deletions

View File

@@ -0,0 +1,90 @@
# 编码实现 - 事件总线与缓冲路由 (Docs/60_coding/mod-event.md)
本文件对应环形事件缓冲区Event Queue与事件路由分发器的具体编码实现细节。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (事件队列定义、`Event_Queue_Push` / `Event_Queue_Pop` / `Event_Dispatcher_Loop` 的底层 C 语言实现)
* [App/config.h](file:///c:/workfile/105/stc32g12k128/App/config.h) (`KeyEvent` 枚举量定义)
## 2. 关键代码片段与逻辑实现
### 2.1 环形队列定义与入队出队函数
`main.c` 中,定义深度为 4 级的事件队列缓冲数组及读写位置指针:
```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;
// 初始化队列
void Event_Queue_Init(void) {
event_write_ptr = 0;
event_read_ptr = 0;
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.2 事件轮询分发器 (Event Dispatcher)
在主循环 `main()` 中,系统定时轮询事件总线,读取事件并路由给前后台应用:
```c
void Event_Dispatcher_Loop(void) {
KeyEvent evt = Event_Queue_Pop();
if (evt == KEY_EVENT_NONE) {
return; // 队列为空,直接跳过
}
// 1. 优先判定物理抢占型事件 (如 SOS 键)
if (evt == KEY_SOS_ACTIVE) {
StartApp(APP_SOS); // 强行强占前台,启动紧急求救页面
return;
}
// 2. 无线报警事件路由给后台进程进行防区匹配
if (evt == RF_EVENT_ALARM || evt == RF_EVENT_PAIR) {
BgApp_Process_RF(evt); // 后台比对数据库若匹配且在布防状态下唤醒强占报警App
return;
}
// 3. 常规按键事件分发给当前活跃的前台 APP
if (active_app && active_app->onEvent) {
active_app->onEvent(evt);
}
}
```

View File

@@ -8,26 +8,39 @@
## 2. 关键代码片段与逻辑实现
### 2.1 按键中断定时器扫描 (每 10ms 调度一次)
`Timer1_Isr` (1ms 滴答) 内,通过 `key_scan_timer` 进行 10ms 分频并调用 `Key_Scan_Process()`
### 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. 读取引脚状态并消抖
// 1. 读取引脚状态并进行 3 次移位消抖
static u8 key_up_state = 0xFF;
static u8 key_down_state = 0xFF;
static u8 key_confirm_state = 0xFF;
static u8 key_sos1_state = 0xFF;
static u8 key_sos2_state = 0xFF;
u8 key_sos_state;
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_sos1_state = (key_sos1_state << 1) | KEY_SOS1;
key_sos2_state = (key_sos2_state << 1) | KEY_SOS2;
// 双上部 SOS 键的逻辑并联
key_sos_state = key_sos1_state & key_sos2_state;
key_sos_state = (key_sos_state << 1) | KEY_SOS;
// 2. 按键长按与短按状态机扫描
@@ -36,7 +49,7 @@ void Key_Scan_Process(void) {
key_up_hold++;
} else {
if (key_up_hold > 2) {
key_event_buf = KEY_UP_SHORT;
Event_Queue_Push(KEY_EVENT_UP_CLICK);
}
key_up_hold = 0;
}
@@ -45,33 +58,33 @@ void Key_Scan_Process(void) {
if ((key_down_state & 0x07) == 0) {
key_down_hold++;
if (key_down_hold == 200) { // 2秒
key_event_buf = KEY_DOWN_LONG;
Event_Queue_Push(KEY_EVENT_DOWN_LONG);
}
} else {
if (key_down_hold > 2 && key_down_hold < 200) {
key_event_buf = KEY_DOWN_SHORT;
Event_Queue_Push(KEY_EVENT_DOWN_CLICK);
}
key_down_hold = 0;
}
// ■ 确认键 (CONFIRM) 处理 (支持长按 3 秒进入/退出)
// ■ 确认键 (CONFIRM) 处理 (支持长按 3 秒进入菜单)
if ((key_confirm_state & 0x07) == 0) {
key_confirm_hold++;
if (key_confirm_hold == 300) { // 3秒
key_event_buf = KEY_CONFIRM_LONG;
Event_Queue_Push(KEY_EVENT_SOS_LONG); // 复用为确认长按,拉起菜单
}
} else {
if (key_confirm_hold > 2 && key_confirm_hold < 300) {
key_event_buf = KEY_CONFIRM_CLICK;
Event_Queue_Push(KEY_EVENT_SOS_CLICK); // 确认单击
}
key_confirm_hold = 0;
}
// 并联 SOS 键
// SOS 键 (物理双按键并联,按下任意即触发)
if ((key_sos_state & 0x07) == 0) {
key_sos_hold++;
if (key_sos_hold == 3) {
key_event_buf = KEY_SOS_ACTIVE;
if (key_sos_hold == 3) { // 30ms 快速响应
Event_Queue_Push(KEY_SOS_ACTIVE);
}
} else {
key_sos_hold = 0;

View File

@@ -107,4 +107,4 @@ void UI_ShowPairConfirmPage(u8 type, u8 selected_suffix) {
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->

View File

@@ -9,20 +9,20 @@
## 2. 关键代码片段与逻辑实现
### 2.1 驱动初始化与管脚分配
WS2812B 输入脚接在单片机硬件 SPI 组管脚中的 MOSI (`P2.3`)
WS2812B 输入脚接在单片机物理引脚 `LED_RGB` (`P2.6`)
```c
sbit WS2812_DI = P2^3;
sbit WS2812_DI = P2^6; // Pin 27
void WS2812_Init(void) {
// P2.3 (MOSI) 配置为推挽输出,默认输出低电平 0
P2M1 &= ~(1 << 3);
P2M0 |= (1 << 3);
// P2.6 (LED_RGB) 配置为推挽输出,默认输出低电平 0
P2M1 &= ~(1 << 6);
P2M0 |= (1 << 6);
WS2812_DI = 0;
}
```
### 2.2 硬件 SPI 防噪声极速发送 (WS2812_Write24Bit)
由于 SPI 时钟 SCLK 与马达引脚`P2.5`,发送期间关闭总中断并将 `P2.5` 设为高阻输入以规避马达杂音,通过 SPDAT 展开 24bit 数据串行输出
### 2.2 驱动防噪声发送 (WS2812_Write24Bit)
由于马达引脚`P2.4` (Pin 25),为了规避马达震动时的共地杂噪,在串行输出数据期间关闭总中断并将马达引脚 `P2.4` 设为高阻输入。
展宽 LUT 定义:
```c
const u16 code SPI_LUT[16] = {
@@ -45,11 +45,11 @@ void WS2812_Write24Bit(u8 g, u8 r, u8 b) {
b4 = buf[4]; b5 = buf[5]; b6 = buf[6]; b7 = buf[7];
b8 = buf[8]; b9 = buf[9];
EA = 0; // 关中断保证 SPI 传输的连续性
EA = 0; // 关中断保证串行传输的连续性
// 配置 P2.5 为高阻输入模式,屏蔽马达噪声
P2M1 |= (1 << 5);
P2M0 &= ~(1 << 5);
// 配置 P2.4 (MOTOR_PWM) 为高阻输入模式,屏蔽共地马达干扰
P2M1 |= (1 << 4);
P2M0 &= ~(1 << 4);
SPSTAT = 0xC0; // 清除 SPI 状态寄存器
SPDAT = b0; // 发送数据
@@ -81,9 +81,9 @@ void WS2812_Write24Bit(u8 g, u8 r, u8 b) {
SPCTL = 0x90; // 关闭 SPI 硬件使能
WS2812_DI = 0;
// 恢复 P2.5 为推挽输出,控制马达常规功能
P2M1 &= ~(1 << 5);
P2M0 |= (1 << 5);
// 恢复 P2.4 (MOTOR_PWM) 为推挽输出,恢复马达控制
P2M1 &= ~(1 << 4);
P2M0 |= (1 << 4);
MOTOR = 0; // 确保马达断开
EA = 1; // 恢复中断

View File

@@ -9,14 +9,14 @@
## 2. 关键代码片段与逻辑实现
### 2.1 引脚控制宏与初始化
马达由引脚 `P2.5` 控制。配置模式为推挽输出:
马达由引脚 `P2.4` 控制。配置模式为推挽输出:
```c
sbit MOTOR = P2^5;
sbit MOTOR = P2^4; // Pin 25
void Motor_Init(void) {
// 将 P2.5 配置为推挽输出模式,默认输出低电平 0
P2M1 &= ~(1 << 5);
P2M0 |= (1 << 5);
// 将 P2.4 配置为推挽输出模式,默认输出低电平 0
P2M1 &= ~(1 << 4);
P2M0 |= (1 << 4);
MOTOR = 0;
}
```

View File

@@ -9,17 +9,17 @@
## 2. 关键代码片段与逻辑实现
### 2.1 供电引脚初始化
系统初始化时,配置 `SGM_CTRL`(控制 SGM3833 供电的引脚 `P3.4`)为推挽输出,并拉高使能
系统初始化时,配置 `SHUT`(控制 SGM3833 供电的引脚 `P2.7`)为推挽输出,并拉高以打开屏幕供电
```c
sbit SGM_CTRL = P3^4;
sbit SHUT = P2^7; // Pin 28
void Power_Init(void) {
// P3.4 (SGM_CTRL) 配置为推挽输出
P3M1 &= ~(1 << 4);
P3M0 |= (1 << 4);
// P2.7 (SHUT) 配置为推挽输出
P2M1 &= ~(1 << 7);
P2M0 |= (1 << 7);
// 拉高引脚,开启 SGM3833 负压供电
SGM_CTRL = 1;
SHUT = 1;
}
```
@@ -31,15 +31,14 @@ void Enter_Low_Power_Sleep(void) {
LCD_WriteCmd(0x10);
// 2. 切断 SGM3833 电源,停止输出负压
SGM_CTRL = 0;
SHUT = 0;
// 3. 配置引脚中断 (以 P0.4 确认键、P0.2/P0.5 SOS键为例)
// 允许 P0 口引脚下降沿中断以供按键唤醒
P0IM1 &= ~0x36; // P0.1, P0.2, P0.3, P0.4, P0.5 设为下降沿或电平触发
P0IM0 &= ~0x36;
P0INTE |= 0x36; // 开启中断使能
// 3. 配置引脚中断 (P2.0, P2.3, P3.6, P3.7) 允许唤醒
// 允许 P2 / P3 端口中断以供按键唤醒
P2IM1 &= ~0x09; P2IM0 &= ~0x09; P2INTE |= 0x09; // P2.0 (UP), P2.3 (SOS)
P3IM1 &= ~0xC0; P3IM0 &= ~0xC0; P3INTE |= 0xC0; // P3.6 (DOWN), P3.7 (CONFIRM)
// 4. 配置射频 carrier 接收脚 (如 P3.6) 为外部中断唤醒
// 4. 配置射频接收脚 P0.0 (RF_RX_DATA) 为 INT2/外部中断 2 唤醒
EX2 = 1; // 使能外部中断2
// 5. 写入 PCON.1 使单片机进入 Power-Down 深度睡眠状态
@@ -52,17 +51,17 @@ void Enter_Low_Power_Sleep(void) {
### 2.3 中断唤醒与恢复 (Wakeup_Restore)
当产生按键或射频中断后,单片机被唤醒,首先执行外部中断 ISR退出低功耗并重新使能 OLED
```c
// 外部中断服务子程序 (以按键 P0 端口中断为例)
void Port0_Isr(void) interrupt 37 {
P0INTF = 0x00; // 清除 P0 中断标志
// 外部中断服务子程序 (以按键 P3 端口中断为例)
void Port3_Isr(void) interrupt 38 {
P3INTF = 0x00; // 清除 P3 中断标志
Wakeup_Restore();
}
void Wakeup_Restore(void) {
if (SGM_CTRL == 1) return; // 已经在工作状态,直接返回
if (SHUT == 1) return; // 已经在工作状态,直接返回
// 1. 拉高 SGM_CTRL 重新使能 SGM3833 供电
SGM_CTRL = 1;
// 1. 拉高 SHUT 重新使能 SGM3833 供电
SHUT = 1;
// 2. 延时约 50ms 等待升压/负压输出稳定
Delay50ms();

View File

@@ -37,4 +37,4 @@ void EV1527_Transmit(u32 addr, u8 data_code) {
* **绑定对码**:当用户触发对码绑定时,传入 `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 V2 -->
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V3 -->

View File

@@ -73,12 +73,12 @@ u32 Get_Bracelet_Factory_ID(void) {
```
### 2.4 电池采样与充电状态读取实现
在系统模块中,通过硬件 ADC 通道 8 (P0.0) 和数字 I/O (P2.7) 采集电池状态:
在系统模块中,通过硬件 ADC 通道 9 (P2.1) 和数字 I/O (P2.2) 采集电池状态:
1. **ADC 初始化 (ADC_Init)**
```c
void ADC_Init(void) {
P0M1 |= 0x01; P0M0 &= ~0x01; // P0.0 配置为高阻输入 (模拟输入模式)
P2M1 |= 0x02; P2M0 &= ~0x02; // P2.1 (Pin 22 / BAT_ADC) 配置为高阻输入 (模拟输入模式)
ADCCFG = 0x2F; // RESFMT = 1 (右对齐)
ADC_CONTR |= 0x80; // 打开 ADC 电源 (ADC_POWER = 1)
}
@@ -87,7 +87,7 @@ void ADC_Init(void) {
2. **读取采样数据与电量百分比计算**
```c
u16 Get_ADC_Result(u8 channel) {
ADC_CONTR = 0x80 | channel; // 开启电源并选择通道
ADC_CONTR = 0x80 | channel; // 开启电源并选择通道 (channel = 9 对应 P2.1)
_nop_(); _nop_();
ADC_CONTR |= 0x40; // 启动 ADC 转换 (ADC_START = 1)
while (!(ADC_CONTR & 0x20)); // 等待转换完成 (ADC_FLAG)
@@ -96,7 +96,7 @@ u16 Get_ADC_Result(u8 channel) {
}
u8 Read_Battery_Percent(void) {
u16 adc_val = Get_ADC_Result(8); // 读取通道 8 (P0.0)
u16 adc_val = Get_ADC_Result(9); // 读取通道 9 (P2.1)
// 线性百分比转换3.3V (ADC=1279) 对应 0%, 4.2V (ADC=1628) 对应 100%
if (adc_val <= 1279) {
@@ -111,9 +111,9 @@ u8 Read_Battery_Percent(void) {
3. **充电状态读取**
```c
// P2.7 在 System_Init 时需配置为高阻输入以进行充电状态检测
// P2.2 (Pin 23 / DET) 在 System_Init 时需配置为高阻输入以进行充电状态检测
bit Is_Charging(void) {
return (P2 & (1 << 7)) ? 1 : 0; // 读取 P2.7 引脚高电平状态1 表示 USB 接入
return (P2 & (1 << 2)) ? 1 : 0; // 读取 P2.2 引脚高电平状态1 表示 USB 接入
}
```