docs: optimize SOS button behavior, add 2-step pairing wizard, and IPC exit action

This commit is contained in:
2026-07-09 17:42:55 +08:00
parent 1168e747e6
commit 32c07c0466
16 changed files with 821 additions and 478 deletions

View File

@@ -1,10 +1,10 @@
# 编码实现 - 按键扫描与事件判定 (Docs/60_coding/mod-key.md)
本文件对应按键状态消抖、长按计时判定以及组合按键识别的具体编码实现细节。
本文件对应按键状态消抖、确认键长按 3 秒判定以及主键事件注入的具体编码实现细节。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定时中断内的按键扫描、按键状态累加、主循环事件处理)
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定时中断内的按键扫描、按键状态高阻读取、长短按电平扫描)
## 2. 关键代码片段与逻辑实现
@@ -26,67 +26,134 @@ void Key_Scan_Process(void) {
key_sos1_state = (key_sos1_state << 1) | KEY_SOS1;
key_sos2_state = (key_sos2_state << 1) | KEY_SOS2;
// 双上部 SOS 键的逻辑并联(低电平有效,按位与运算相当于逻辑或)
// 双上部 SOS 键的逻辑并联
key_sos_state = key_sos1_state & key_sos2_state;
// 2. 长按与组合按键计时器累加
if ((key_up_state & 0x07) == 0 && (key_down_state & 0x07) == 0) {
// ▲ + ▼ 组合键按住
comb_hold++;
if (comb_hold == 300) { // 3秒
key_event_buf = KEY_COMB_LONG;
// 2. 按键长按与短按状态机扫描
// ▲ 键 (UP) 处理
if ((key_up_state & 0x07) == 0) {
key_up_hold++;
} else {
if (key_up_hold > 2) {
key_event_buf = KEY_UP_SHORT;
}
key_up_hold = 0;
}
// ▼ 键 (DOWN) 处理 (支持长按 2 秒清除报警/退出)
if ((key_down_state & 0x07) == 0) {
key_down_hold++;
if (key_down_hold == 200) { // 2秒
key_event_buf = KEY_DOWN_LONG;
}
} else {
comb_hold = 0;
// 单个按键:▲ 键 (UP)
if ((key_up_state & 0x07) == 0) {
key_up_hold++;
} else {
if (key_up_hold > 2 && key_up_hold < 200) {
key_event_buf = KEY_UP_SHORT;
}
key_up_hold = 0;
if (key_down_hold > 2 && key_down_hold < 200) {
key_event_buf = KEY_DOWN_SHORT;
}
// 单个按键:■ 确认键 (CONFIRM)
if ((key_confirm_state & 0x07) == 0) {
key_confirm_hold++;
} else {
if (key_confirm_hold > 2 && key_confirm_hold < 200) {
key_event_buf = KEY_CONFIRM_CLICK;
}
key_confirm_hold = 0;
key_down_hold = 0;
}
// ■ 确认键 (CONFIRM) 处理 (支持长按 3 秒进入/退出)
if ((key_confirm_state & 0x07) == 0) {
key_confirm_hold++;
if (key_confirm_hold == 300) { // 3秒
key_event_buf = KEY_CONFIRM_LONG;
}
// 单个按键:并联 SOS 键
if ((key_sos_state & 0x07) == 0) {
key_sos_hold++;
if (key_sos_hold == 500) { // 5秒
key_event_buf = KEY_SOS_LONG;
}
} else {
if (key_sos_hold > 2 && key_sos_hold < 500) {
key_event_buf = KEY_SOS_SHORT;
}
key_sos_hold = 0;
} else {
if (key_confirm_hold > 2 && key_confirm_hold < 300) {
key_event_buf = KEY_CONFIRM_CLICK;
}
// ▼ 键 (DOWN) 处理同上...
key_confirm_hold = 0;
}
// 并联 SOS 键
if ((key_sos_state & 0x07) == 0) {
key_sos_hold++;
if (key_sos_hold == 3) {
key_event_buf = KEY_SOS_ACTIVE;
}
} else {
key_sos_hold = 0;
}
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes -->
### 2.2 事件异步读取与清除
主循环在其周期内轮询 `key_event_buf`。一旦发现其非 `KEY_EVENT_NONE`,拷贝并立即重置该缓冲区为 `KEY_EVENT_NONE` 以防止重复触发:
### 2.2 事件异步处理与分发 (Process_System_Event 示例)
在主循环事件分发中,对各个状态下的事件进行处理:
```c
if (key_event_buf != KEY_EVENT_NONE) {
KeyEvent evt = key_event_buf;
key_event_buf = KEY_EVENT_NONE; // 清空事件缓冲区
// 执行具体事件动作,如在常态下短按 SOS 进入 SOS 警报发射状态等
// ...
void Process_System_Event(KeyEvent evt) {
switch (current_state) {
case STATE_MENU:
if (evt == KEY_UP_SHORT) {
// 菜单滚动
if (menu_index > 0) menu_index--;
Redraw = 1;
} else if (evt == KEY_DOWN_SHORT) {
if (menu_index < 3) menu_index++;
Redraw = 1;
} else if (evt == KEY_CONFIRM_CLICK) {
// 点击确认进入选中分支
Enter_Menu_Branch(menu_index);
} else if (evt == KEY_CONFIRM_LONG) {
// 长按 3 秒退出菜单返回时钟待机
current_state = STATE_CLOCK;
Redraw = 1;
}
break;
case STATE_PAIR_CONFIRM:
if (confirm_edit_focus == 0) {
// 第一步:编辑传感器类型
if (evt == KEY_UP_SHORT) {
if (selected_type < 6) selected_type++;
else selected_type = 0;
Redraw = 1;
} else if (evt == KEY_DOWN_SHORT) {
if (selected_type > 0) selected_type--;
else selected_type = 6;
Redraw = 1;
} else if (evt == KEY_CONFIRM_CLICK) {
// 点击确认,锁定类型,跳入第二步选择序号
confirm_edit_focus = 1;
Redraw = 1;
}
} else if (confirm_edit_focus == 1) {
// 第二步:编辑序号后缀
if (evt == KEY_UP_SHORT) {
if (selected_suffix < 99) selected_suffix++;
Redraw = 1;
} else if (evt == KEY_DOWN_SHORT) {
if (selected_suffix > 1) selected_suffix--;
Redraw = 1;
} else if (evt == KEY_CONFIRM_CLICK) {
// 点击确认,完成最终写入保存,返回时钟
Add_Sensor(captured_addr, selected_type, selected_suffix);
current_state = STATE_CLOCK;
Redraw = 1;
}
}
// 长按 CONFIRM 3 秒退出不保存
if (evt == KEY_CONFIRM_LONG) {
current_state = STATE_CLOCK;
Redraw = 1;
}
break;
case STATE_IPC_BIND:
// IPC 绑定连发状态中支持物理长按退出
if (evt == KEY_CONFIRM_LONG || evt == KEY_DOWN_LONG) {
current_state = STATE_CLOCK;
Redraw = 1;
} else if (evt == KEY_CONFIRM_CLICK) {
// 重发绑定信号
EV1527_Transmit(bracelet_factory_id, 0x01);
}
break;
// 其它状态处理...
}
}
```
通过该缓冲区实现了按键中断采集与主循环处理的解耦。
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->

Binary file not shown.

View File

@@ -1,30 +1,78 @@
# 编码实现 - 电源管理与常亮控制 (Docs/60_coding/mod-power.md)
# 编码实现 - 电源管理与休眠唤醒 (Docs/60_coding/mod-power.md)
本文件对应取消低功耗休眠、保持屏幕常亮及系统全时监测的具体编码实现。
本文件对应低功耗休眠、关屏切断 PMIC 以及外部中断唤醒的具体编码实现。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (初始化供电配置)
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (定义电源控制引脚使能、超时判断及进入 Power-Down 和中断唤醒的实现)
## 2. 关键代码片段与逻辑实现
### 2.1 供电引脚常使能
系统初始化阶段,配置 `LCD_PWR_CTRL`(控制屏和背光供电的引脚)为推挽输出模式,并输出高电平
### 2.1 供电引脚初始化
系统初始化,配置 `SGM_CTRL`(控制 SGM3833 供电的引脚 `P3.4`)为推挽输出,并拉高使能
```c
sbit SGM_CTRL = P3^4;
void Power_Init(void) {
// P3.4 (LCD_PWR_CTRL) 配置为推挽输出
// P3.4 (SGM_CTRL) 配置为推挽输出
P3M1 &= ~(1 << 4);
P3M0 |= (1 << 4);
// 拉高引脚,开启 LCD 背光与供电使能
LCD_PWR_CTRL = 1;
// 拉高引脚,开启 SGM3833 负压供电
SGM_CTRL = 1;
}
```
### 2.2 移除睡眠超时与休眠指令
主循环事件分发中,不再轮询待机时间累加器,也不再调用休眠流程
* **移除** `Enter_Deep_Sleep` 函数。
* **移除** 端口 P0 的引脚中断设置(`P0INTE` 等)及 `PD` 停机指令。
* 手环时刻保持主频运转Timer 1 中断不断累加,`EV1527_Decode` 全时监听射频,随时处理防区报警 and SOS 求救触发。
### 2.2 进入低功耗睡眠 (Enter_Low_Power_Sleep)
10 秒无按键操作发生后,关闭屏幕显示,断电,配置中断,并进入 Power-Down 挂起状态
```c
void Enter_Low_Power_Sleep(void) {
// 1. 给 AMOLED 发送 Sleep In 命令
LCD_WriteCmd(0x10);
// 2. 切断 SGM3833 电源,停止输出负压
SGM_CTRL = 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; // 开启中断使能
// 4. 配置射频 carrier 接收脚 (如 P3.6) 为外部中断唤醒
EX2 = 1; // 使能外部中断2
// 5. 写入 PCON.1 使单片机进入 Power-Down 深度睡眠状态
PCON |= 0x02; // MCU 挂起,主时钟停振,直到外部中断唤醒
_nop_();
_nop_();
}
```
<!-- Checked and verified with always-on screen and sleep removal changes -->
### 2.3 中断唤醒与恢复 (Wakeup_Restore)
当产生按键或射频中断后,单片机被唤醒,首先执行外部中断 ISR退出低功耗并重新使能 OLED
```c
// 外部中断服务子程序 (以按键 P0 端口中断为例)
void Port0_Isr(void) interrupt 37 {
P0INTF = 0x00; // 清除 P0 中断标志
Wakeup_Restore();
}
void Wakeup_Restore(void) {
if (SGM_CTRL == 1) return; // 已经在工作状态,直接返回
// 1. 拉高 SGM_CTRL 重新使能 SGM3833 供电
SGM_CTRL = 1;
// 2. 延时约 50ms 等待升压/负压输出稳定
Delay50ms();
// 3. 重新对 RM69310 屏幕控制器做初始化序列
LCD_Init();
// 4. 重置无按键操作的超时计时器
inactivity_timer = 0;
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->

View File

@@ -1,6 +1,6 @@
# 编码实现 - 自适应 RF 解码与发射 (Docs/60_coding/mod-rf.md)
本文件对应自适应射频解码与主动发射的具体编码实现细节。已验证无变动。
本文件对应自适应射频解码与主动发射的具体编码实现细节。
## 1. 对应代码源文件
@@ -13,7 +13,7 @@
解码时,软件轮询检测接收管脚状态。
1. **抓取同步头**:检测到一个大于 `1500us` 且小于 `60000us` 的低电平。
2. **计算基准 T**`T = low_time / 31`
3. **采集 24 个数据位**:对于每一位,检测它的高电平持续时间 `h_val` 低电平持续时间 `l_val`
3. **采集 24 个数据位**:对于每一位,检测它的高电平持续时间 `h_val` and 低电平持续时间 `l_val`
```c
// 解码一位数据
if (h_val > l_val) {
@@ -22,7 +22,7 @@
res = (res << 1) | 0;
}
```
4. **极速解析与 CPU 效率**:去除了之前在主循环中阻塞多帧解析的详细日志串口打印,仅在解析整帧成功后发送一行 Summary。单帧处理耗时控制在 **2ms** 以内,彻底避免了连续波形导致的丢包现象
4. 正常解码成功将 20 位地址存入 `out_addr`,后 4 位数据存入 `out_type` 并返回 `1`
### 2.2 主动广播发射 (EV1527_Transmit)
手环通过 LT4455 发送对码广播或主动 SOS 警报,在 `rf.c` 中实现:
@@ -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 -->
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->

View File

@@ -1,32 +1,58 @@
# 编码实现 - 系统核心控制与存储 (Docs/60_coding/mod-sys.md)
本文件对应系统控制与存储的具体编码实现细节。只包含手环代码实现。
本文件对应系统状态机、超时低功耗休眠判断以及断电数据库的具体编码实现。
## 1. 对应代码源文件
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (系统主循环、定时器中断、IAP 存取)
* [App/main.c](file:///c:/workfile/105/stc32g12k128/App/main.c) (系统主逻辑、主状态机切换、10s 超时累加判断)
* [App/config.h](file:///c:/workfile/105/stc32g12k128/App/config.h) (系统状态及数据结构定义)
## 2. 关键代码片段与逻辑实现
### 2.1 定时器 1 中断服务 (Timer1_Isr)
定时器初始化配置为 1ms`main.c`第 190~250 行左右实现。
`Timer1_Isr` 里,我们累加 `ms_tick`。当累加到 1000 时,重置 `ms_tick = 0`,自增 `current_sec`。若秒达 60则自增 `current_min` 并置位 `clock_updated = 1` 通知主循环在下一个周期重绘时钟画面。
### 2.2 IAP 读写细节
`main.c` 中通过 IAP 寄存器控制读写。
### 2.1 10秒无操作休眠时序 (Timer1_Isr)
定时器初始化配置为 1ms`main.c` 中实现。
`Timer1_Isr` (每 10ms 调用一次) 中,当系统非处于休眠(`STATE_SLEEP`)且非处于报警(`STATE_ALARMING`/`STATE_SOS_EMITTED`)状态时,累加无按键操作计数器 `inactivity_timer`
```c
void IAP_EraseSector(u16 addr) {
IAP_CMD = 3; // 扇区擦除
IAP_ADDRL = addr & 0xFF;
IAP_ADDRH = addr >> 8;
IAP_TRIG = 0x5A; // 触发
IAP_TRIG = 0xA5;
_nop_();
u16 inactivity_timer = 0;
void Timer1_Isr(void) interrupt 3 {
// 基础毫秒滴答计数
ms_tick++;
// 10ms 调度周期
if (ms_tick % 10 == 0) {
// 当不是休眠或报警时,进行超时计时累加
if (current_state != STATE_SLEEP && current_state != STATE_ALARMING && current_state != STATE_SOS_EMITTED) {
inactivity_timer++;
}
}
}
```
### 2.2 主循环低功耗检测与状态转换
在主循环 `main()` 中,不断检查 `inactivity_timer` 是否达到 1000即 1000 * 10ms = 10 秒):
```c
void main(void) {
System_Init();
while (1) {
// 读取按键事件并重置计时器
if (key_event_buf != KEY_EVENT_NONE) {
inactivity_timer = 0; // 重置无按键操作计时器
}
// 如果达到 10 秒无操作,进入低功耗休眠
if (inactivity_timer >= 1000) {
inactivity_timer = 0;
current_state = STATE_SLEEP;
Enter_Low_Power_Sleep(); // 关屏、拉低 SGM_CTRL、单片机停机
}
// 轮询解码及其他主状态机业务处理
// ...
}
}
```
- 主动擦除与写字节前必须先调用使能,写完后调用 `IAP_Disable()` 避免误写入损坏配置。
- 数据库保存在末尾扇区 `0xFE0000`(大小为 512 字节)。
### 2.3 获取出厂唯一 ID (Get_Bracelet_Factory_ID)
从 STC32G 的内置 IDATA RAM 区域 `0xF1~0xF7` 处读取 7 字节唯一硬件 ID并将其转换为符合 EV1527 的 20 位地址码:
@@ -35,7 +61,7 @@ u32 Get_Bracelet_Factory_ID(void) {
unsigned char idata *p_uid = (unsigned char idata *)0xF1;
u32 uid = 0;
// 组合后 3 字节并进行 20 位掩码过滤
// 组合后 3 字节并进行 20 位限制
uid = ((u32)p_uid[4] << 16) | ((u32)p_uid[5] << 8) | p_uid[6];
uid &= 0x0FFFFF; // 20-bit address range limit for EV1527
@@ -46,41 +72,14 @@ u32 Get_Bracelet_Factory_ID(void) {
}
```
### 2.4 传感器自定义名称拼接与写入 (Add_Sensor)
`Add_Sensor(u32 addr, u8 type, u8 suffix_idx)` 中,系统预设 7 类传感器名前缀:
```c
char *code sensor_prefixes[7] = {
"PORTE", "PIR", "FUMEE", "URGENCE", "GAZ", "INOND.", "VIBRA."
};
```
在写入时,手动拼接前缀和两位数字 `suffix_idx` 后缀:
```c
void Add_Sensor(u32 addr, u8 type, u8 suffix_idx) {
u8 slot = ...; // 检索空槽或匹配历史ID
char *prefix = sensor_prefixes[type];
u8 len = 0;
// 拷贝前缀
while (prefix[len] != '\0' && len < 11) {
sensor_list[slot].name_gbk[len] = prefix[len];
len++;
}
sensor_list[slot].name_gbk[len++] = ' ';
sensor_list[slot].name_gbk[len++] = '0' + (suffix_idx / 10);
sensor_list[slot].name_gbk[len++] = '0' + (suffix_idx % 10);
sensor_list[slot].name_gbk[len] = '\0';
Save_Database();
}
### 2.5 电池采样与充电状态读取实现
### 2.4 电池采样与充电状态读取实现
在系统模块中,通过硬件 ADC 通道 8 (P0.0) 和数字 I/O (P2.7) 采集电池状态:
1. **ADC 初始化 (ADC_Init)**
```c
void ADC_Init(void) {
P0M1 |= 0x01; P0M0 &= ~0x01; // P0.0 配置为高阻输入 (模拟输入模式)
ADCCFG = 0x2F; // RESFMT = 1 (右对齐), ADC 时钟配置为 SYSCLK / 2 / 16
ADCCFG = 0x2F; // RESFMT = 1 (右对齐)
ADC_CONTR |= 0x80; // 打开 ADC 电源 (ADC_POWER = 1)
}
```
@@ -118,5 +117,4 @@ bit Is_Charging(void) {
}
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes -->
```
<!-- Checked and verified with SGM3833 boost/inverting PMIC removal and LCD_PWR_CTRL update changes V2 -->