This commit is contained in:
edisondeng
2026-07-31 13:57:06 +08:00
parent d8df5d49d3
commit a7a2cc9465
23 changed files with 902 additions and 328 deletions

View File

@@ -0,0 +1,7 @@
1 屏幕显示
2 按键响应
3 射频收发
4 马达响应
5 RGB控制
6 电池电压检测和充电
7 板子各状态下的功耗 。

72
work-order/wo-01 Normal file
View File

@@ -0,0 +1,72 @@
# 📋 工单 wo-01: ADC按键检测、PWR_HOLD开关机控制、P1.3电池电量检测与 PCF8563 硬件 RTC 整合
**工单编号**: WO-01
**创建时间**: 2026-07-27
**模块组件**: 按键驱动 (ADC Key) / 电源管理 (Power Control) / 电池检测 (Battery ADC) / 实时时钟 (PCF8563 RTC) / 待机应用 (ClockApp & UI)
---
## 📌 一、 需求概述与按阶段分步规划
根据项目开发优先级与依赖关系,按以下四个顺序阶段分步完成:
### 阶段 1: P0.3 ADC 按键检测
- **硬件引脚**: `P0.3` 对应 STC32G 硬件 `ADC3` 通道。
- **电平特征**: 开机常态下 `KEY_DOWN` 节点电压约为 **2.0V**(未按下),按键按下后电压升至约为 **3.7V**。
- **算法与逻辑**:
- 配置 ADC 模块,引入滑动窗口均值/中值滤波消抖。
- 按键判定:未按下区间 (1.5V ~ 2.5V),按下区间 (3.2V ~ 4.2V)。
- 保留并兼容原本 KEY_DOWN 的短按功能(消抖释放后触发 `KEY_EVENT_DOWN_CLICK` 供 UI 菜单与时间编辑下翻数值)。
### 阶段 2: PWR_HOLD 开关机控制与上电自检
- **硬件引脚**: `PWR_HOLD` 控制引脚(默认分配为 `P0.0`)。
- **开关机流程**:
- **开机锁存**: 按下开机键硬件通电后,单片机在入口 `main()` **第一时间拉高 `PWR_HOLD` 引脚**,锁存电源保持供电。
- **长按关机**: 开机状态下长按 KEY_DOWN 按键 1 秒以上,触发关机流程 `KEY_EVENT_POWER_OFF`,拉低 `PWR_HOLD` 切断总电源。
- **上电自检 (Self-Test)**:
- 启动阶段自动执行硬件自检电源锁存状态、ADC 采样功能、串口通信校验等),并在串口 1 输出完整的格式化自检报告。
### 阶段 3: P1.3 电池电压检测与真实电量显示
- **硬件引脚**: `P1.3` 对应 STC32G 硬件 `ADC11` 通道(电池分压检测脚)。
- **计算与转换**:
- 读取 ADC11 通道计算电池输入电压 $V_{bat}$。
- 将电池电压(锂电池 3.3V ~ 4.2V 范围)线性/分段映射转换为 **0% ~ 100%** 百分比电量。
- **UI 真实电量刷新**:
- 重构 `ui.c` 的 `UI_ShowClockPage` 及待机界面电量渲染代码,替换原写死固定的静态 "85%" 字符。
- 动态显示真实电池百分比(如 "95%")及绘制对应的电量电池柱位图。
### 阶段 4: PCF8563 (D8563) 硬件 RTC 深度整合
- **硬件连接**: PCF8563 RTC 芯片通过 I2C 总线连接至 STC32G (`P3.2` -> SCL, `P3.3` -> SDA)。
- **深度整合**:
- **开机/唤醒即读**: 系统开机或从低功耗休眠唤醒时,自动通过 I2C 读取 PCF8563 芯片的时间,更新系统全局时钟变量。
- **前台编辑直接写入**: 在 `ClockApp` 编辑模式下按确认键保存时,同步将最新时间写入 PCF8563 芯片寄存器中,实现真正的硬件级掉电时间保存。
---
## 🛠️ 二、 硬件引脚配置汇总
| 功能名称 | 关联引脚 | 端口模式配置 | 采样/控制参数 |
| :--- | :--- | :--- | :--- |
| **KEY_DOWN** | `P0.3` / ADC3 | 高阻输入 | 常态 **2.0V**;按下 **3.7V**;消抖 50ms |
| **PWR_HOLD** | `P0.0` (可配置) | 推挽输出 | 上电第一时间置 **高电平 (1)**;关机置 **低电平 (0)** |
| **BATT_ADC** | `P1.3` / ADC11 | 高阻输入 | 电池分压输入,计算 3.3V(0%) ~ 4.2V(100%) 电量 |
| **RTC_SCL** | `P3.2` | 准双向 / 模拟 I2C | PCF8563 SCL 时钟线 |
| **RTC_SDA** | `P3.3` | 准双向 / 模拟 I2C | PCF8563 SDA 数据线 |
---
## 🧪 三、 分阶段验收标准
1. **阶段 1 验收 (ADC按键)**:
- [ ] 串口实时打印 P0.3 ADC 电压,常态 2.0V 不误触发。
- [ ] 按下 3.7V 稳定识别,短按能够正常执行 UI 菜单下翻和数值减小。
2. **阶段 2 验收 (开关机与自检)**:
- [ ] 长按按键 >1s 开机,系统完成自检后松开按键,`PWR_HOLD` 持续高电平保持供电。
- [ ] 开机状态下长按按键 >1s触发关机提示后 `PWR_HOLD` 拉低,系统断电。
- [ ] 开机串口打印完整自检日志。
3. **阶段 3 验收 (P1.3电池检测与UI)**:
- [ ] 可通过 P1.3 ADC 测量准确计算实际电池电压。
- [ ] 待机 UI 屏幕上实时绘制正确的百分比 (0%~100%) 与对应电量电池图标。
4. **阶段 4 验收 (PCF8563 RTC)**:
- [ ] 开机从 PCF8563 读取硬件时间。
- [ ] 前台编辑保存后直接同步写入 PCF8563 芯片,掉电重启时间不丢失。

75
work-order/wo-02 Normal file
View File

@@ -0,0 +1,75 @@
# 📋 工单 wo-02: 改动总结与硬件调试归档
**工单编号**: WO-02
**归档时间**: 2026-07-28
**处理状态**: 已完成 (Completed)
**涉及模块**: 电池检测 (Battery ADC) / 按键驱动 (ADC Key) / 实时时钟 (PCF8563 RTC) / 系统自检 (Self-Test)
---
## 📌 一、 软件改动与底层逻辑修正
在本次工单调试执行过程中,针对硬件表现的差异与异常,对底层驱动及自检模块进行了如下修正:
### 1. 电池电压检测与电量百分比校准 (对应阶段 3)
* **公式校准与修正**:
* 物理硬件装配实际为:上拉 $10\text{k}\Omega$,下拉 $22\text{k}\Omega$(分压比为 $22/32$)。
* 经用户万用表与 ADC 联合实测:当电池电压为 $4.0\text{V}$ (4000mV) 时,分压点物理电压为 $2.7\text{V}$,单片机读出的原始 ADC 过滤值为 `2357`。
* 为了消除 LDO 压降、系统参考电压偏差等多重硬件误差,引入经验公式替换原先的理论换算公式:
$$\text{Voltage (mV)} = \frac{\text{Raw\_ADC} \times 4000}{2357}$$
* 该换算将电池准确映射至 `3.3V (0%) ~ 4.2V (100%)`。在 [adc.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.c) 中已完全部署,并在前台时钟界面和自检中完美实时呈现。
### 2. ADC 按键与电池通道互换 (对应阶段 1 & 2)
* **故障定位**:
* 物理走线中,`P0.3` (按键) 实际对应单片机的 `ADC11` 通道;`P1.3` (电池) 实际对应 `ADC3` 通道。原先代码中将两个通道错调。
* **修正方案**:
* 在 [adc.h](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.h) 中对调了通道定义:
`#define ADC_CHANNEL_KEY_DOWN 11` (P0.3)
`#define ADC_CHANNEL_BATTERY 3` (P1.3)
* 修正后,按键常态值成功捕获为正常的 `2712` (约 2.2V),按下后正常达到 `4095` 饱和值。
### 3. PCF8563 (D8563) RTC 通信时序与机制重构 (对应阶段 4)
* **I2C 物理模式优化**: 移除了高频读写 XSFR 带来的推挽/开漏动态切换(避免产生高频暂态毛刺),完全使用准双向口硬件 of “过渡强上拉”机制。
* **死锁释放机制 (`I2C_ReleaseBus`)**: 在 PCF8563 通信或自检开始前,自动输出 9 个时钟脉冲配合 Stop 信号,强行迫使因常供电而进入错乱挂起状态的 RTC 芯片释放 I2C 总线,彻底避免死锁。
* **自适应引脚探测**: 内置支持运行时自动侦测 `P3.2` 与 `P3.3` 的连线是否在画板时反接(默认极性与互换极性自动轮询),增强了硬件兼容度。
* **双时延诊断测试**: 实现了 `PCF8563_TryReadDiagnostic()` 函数,同时使用常规速率 (30us延时) 与超低速时序 (200us延时2.5kHz) 进行试读测试。
### 4. RGB 幻彩灯条上电亮白灯与 4-bit SPI 时序重构
* **故障定位**:
1. 上电复位暂态:单片机复位期间引脚默认弱上拉输出 `1`这被灯珠移位寄存器误读为“全1”色彩白光。当单片机完成复位并拉低引脚时产生 RESET 信号迫使灯珠锁存该色彩,导致开机长时间亮白灯。
2. 3-bit 编码周期过短:在 3MHz 速率下3-bit 模拟波形100b/110b的整个脉冲周期仅有 1.0us,低于规格书规定的 1.2us 最小值,引发灯珠识别率低下。
* **软件重构方案**:
1. **4-bit 编码重构**: 将转码改为 4-bit 编码0码=1000b, 1码=1100b高电平分别保持 333ns 和 666ns完全处于规格书安全区间总周期为 4 bits = 1.33us,完美吻合典型值 1.25us,数据传输稳定度极高。
2. **上电显式关灯**: 在 `main()` 的 `GPIO_Init()` 执行完毕的第一个瞬间,强制调用 `RGB_Send(0, 0, 0, 0, 0, 0);` 送出关灯帧,在极速开机阶段的几微秒内强行熄灭白光。
---
## 🛠️ 二、 物理硬件排障记录
在联合调试期间,定位并解决了一项关键的物理硬件故障:
* **RTC 晶振不起振故障**:
* **现象**: MCU 引脚输出波形完美,但 RTC 芯片一直不回 ACK。
* **病因**: 32.768kHz 晶体两端的**旁路电容参数焊接错误**,导致晶振无法起振,从而使芯片的 I2C 接口内部控制块锁定并忽略全部寻址。
* **解决**: 修正贴片电容后晶振起振RTC 硬件通信与自检完全恢复正常。
* **充电检测引脚配置与校准**:
* **引脚定义**: 充电状态 DET 物理连接至 MCU 的 **`P1.2`** 引脚(对应 QFN32 第 10 脚,硬件为 **`ADC 通道 2`**)。
* **电气优化**: 硬件配置为高阻输入,且在 `system.c` 和 `adc.c` 初始化中**显式关闭了 P1.2 的内部上拉电阻**(通过置空寄存器位 `(*(unsigned char volatile xdata *)0xFE11) &= ~(1 << 2)`),彻底消除了上电引起的电平偏置。
* **阈值检测标准**:
- $V_{\text{DET}} < 0.5\text{V}$ ($<500\text{mV}$): 未插充电器
- $0.8\text{V} \le V_{\text{DET}} \le 1.8\text{V}$ ($800\text{mV} \sim 1800\text{mV}$): 正在充电 (充电指示闪烁)
- $2.2\text{V} \le V_{\text{DET}} \le 2.8\text{V}$ ($2200\text{mV} \sim 2800\text{mV}$): 已充满 (停止闪烁)
---
## 🧪 三、 交付与归档文件
* **最新源文件**:
* 电池与按键检测算法: [adc.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.c)
* 引脚定义配置表: [adc.h](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Drivers/adc.h)
* 自适应防死锁 RTC 驱动: [pcf8563.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20%281%29/stc32g128k/Drivers/pcf8563.c)
* 极速自检与诊断流程: [system.c](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/App/system.c)
* **烧录固件**:
* 归档 HEX 路径: [Build\wristband.hex](file:///c:/Users/xtell/Downloads/stc32g128k-master%20(1)/stc32g128k/Build/wristband.hex)

126
work-order/wo-03 Normal file
View File

@@ -0,0 +1,126 @@
# 工单 WO-03RGB 灯控故障排查 & 电池充电功能测试计划
**优先级**:高
**状态**:进行中
---
## 一、RGB 灯控 — 故障排查与修复
### 现象
开机后两颗 WS2812B 灯珠一直亮白灯,无法通过软件控制熄灭或变色。
### 1.1 硬件电路复查
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|---|---------|---------|---------|------|
| 1.1.1 | 用万用表测量 P2.3 对地电阻 | 断电测 | 非短路 | 待执行 |
| 1.1.2 | 上电测 P2.3 引脚电平 | 示波器捕获上电瞬间到 main() 执行前的波形 | 应快速被拉低,无长时间高电平 | 待执行 |
| 1.1.3 | 测量 TP4054 VCC (5V) 和 VSYS 供电 | 万用表直流档 | VSYS 约 3.7~4.2V 或 5V | 待执行 |
| 1.1.4 | 测量 WS2812B VDD (Pin4) 电压 | 万用表 | 与 VSYS 一致 | 待执行 |
| 1.1.5 | 测量 WS2812B DIN (Pin3) 静态电平 | 未发送数据时示波器/万用表 | 应接近 0V | 待执行 |
### 1.2 引脚初始化修复
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|---|---------|---------|------|------|
| 1.2.1 | GPIO_Init() 中增设 RGB_DIN = 0 | system.c:50 | 将 P2.3 初始输出拉低,消除上电不确定态 | ✔ 已修复 |
| 1.2.2 | main() 中发一次 RESET 脉冲确保 WS2812B 复位 | main.c | 确保 WS2812B 在上电后处于已知复位状态 | ✔ 已修复 |
| 1.2.3 | 验证 P_SW1 SPI 引脚映射 | rgb.c:19 | P_SW1 &= ~0x0C; P_SW1 |= 0x04 将 SPI 映射到 P2.3(MOSI)/P2.5(SCLK) | ✔ 已验证 |
### 1.3 SPI 时序验证
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|---|---------|---------|---------|------|
| 1.3.1 | 示波器探头接 P2.3 (MOSI/DIN) 和 GND | 调用 RGB_Send(0, 180, 0, 0, 0, 0) 只亮红灯 | 观察到 3MHz 时钟波形 | 待执行 |
| 1.3.2 | 示波器接 P2.5 (SCLK) 观察 | 同一调用 | 发送期间无 SCLK 信号(因已被设为高阻) | 待执行 |
| 1.3.3 | 测量 RESET 低电平持续时间 | 在 RGB_Send() 最后 RGB_DIN=0 后测 | >80μs | 待执行 |
| 1.3.4 | 验证 SPCTL = 0xD1 配置 | 核对 STC32G 数据手册 | SSIG=1, SPEN=1, MSTR=1, CPOL=0, CPHA=0, SPR0=1 → 3MHz @24MHz | ✔ 已验证 |
### 1.4 代码逻辑复查
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|---|---------|---------|------|------|
| 1.4.1 | 检查 Encode_Byte 编码 | rgb.c:42-62 | 每个 WS2812B 的 8bit 展开为 3 字节 SPI 数据 = 24 SPI bits | ✔ 已验证 |
| 1.4.2 | 检查 RGB_Send 中 SPI 收发循环 | rgb.c:94-100 | 18 字节全部通过 SPDAT 发出,含等待 SPIF | ✔ 已验证 |
| 1.4.3 | SPI_Init 调用频次 | rgb.c:92 | 每次 RGB_Send 都重新 init SPI不影响功能 | ✔ 已验证 |
| 1.4.4 | RGB_Send(0,0,0,0,0,0) 能否关灯 | 调试指令 | 需实测验证 | 待执行 |
### 1.5 功能验证
| # | 测试用例 | 操作 | 预期结果 | 状态 |
|---|---------|------|---------|------|
| 1.5.1 | 开机默认灭灯 | 上电 | 两颗灯全灭 | 待执行 |
| 1.5.2 | 对码模式白灯 | 进入 PairApp | 两颗灯亮白灯G=R=B=150 | 待执行 |
| 1.5.3 | 对码成功绿灯 | PairApp 确认保存 | 两颗亮绿灯G=200 | 待执行 |
| 1.5.4 | 报警红灯 | 触发门磁报警 | 红灯闪烁 | 待执行 |
| 1.5.5 | SOS 红蓝交替 | 触发 SOS | 灯1红灯、灯2蓝灯交替闪烁 | 待执行 |
| 1.5.6 | 时间设置蓝灯 | 长按确认进入时间设置 | 双蓝灯常亮 | 待执行 |
| 1.5.7 | 马达无异常振动 | 任何 RGB_Send 调用时 | 马达不应发出异常噪音或振动 | 待执行 |
---
## 二、电池充电功能测试计划
### 2.1 硬件充电回路验证
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|---|---------|---------|---------|------|
| 2.1.1 | 万用表测 TP4054 的 VCC(Pin4) 对 GND | 插入 USB 5V 供电 | 5.0V ±0.25V | 待执行 |
| 2.1.2 | 测 TP4054 的 PROG(Pin5) 对 GND 电阻 | 断电测 R1 阻值 | 24kΩ → 充电电流约 41.7mA | 待执行 |
| 2.1.3 | 测 TP4054 的 BAT(Pin3) 对 GND 电压 | 插入 USB + 接电池 | 电池电压 3.7~4.2V | 待执行 |
| 2.1.4 | 测 TP4054 的 CHRG(Pin1) 电平 | 未接USB/充电中/充满 | 未接:高阻;充电中:低电平;充满:高阻 | 待执行 |
| 2.1.5 | 追踪 CHRG → DET → MCU 引脚号的 PCB 走线 | 对照原理图 | 确认连接至 MCU GPIO 引脚号 | 待执行 |
| 2.1.6 | 确认 DET 网络终端的 MCU 引脚电平兼容性 | 万用表测 MCU 侧 | TP4054 CHRG 需 MCU 内部上拉 | 待执行 |
| 2.1.7 | 测量 BAT_ADC(P1.3) 分压网络 | 万用表 | R15=10kΩ, R16=22kΩ, 分压比 22/32 | 待执行 |
### 2.2 充电检测代码实现
| # | 操作步骤 | 涉及文件 | 说明 | 状态 |
|---|---------|---------|------|------|
| 2.2.1 | 确认 MCU 实际连接 CHRG/DET 的引脚号 | 原理图追踪 | CHRG → DET → MCU | ✔ 已完成 |
| 2.2.2 | 在 config.h 添加 sbit CHRG_DET 定义 | config.h | 定义充电检测引脚 | ✔ 已添加 |
| 2.2.3 | 在 GPIO_Init() 中将该引脚配置为高阻输入+内部上拉 | system.c | 适配 TP4054 开漏输出 | ✔ 已添加 |
| 2.2.4 | 实现 bit Is_Charging(void) | adc.c / adc.h | 返回 0=未充电, 1=充电中 | ✔ 已实现 |
| 2.2.5 | 在 UI 层接入充电状态显示 | ui.c / app_clock.c | 充电时显示 `+` 后缀 | ✔ 已完成 |
| 2.2.6 | 在 Self_Test() 中输出充电状态 | system.c | 上电自检时UART输出充电状态 | ✔ 已添加 |
### 2.3 充电功能验证
| # | 测试用例 | 操作 | 预期结果 | 状态 |
|---|---------|------|---------|------|
| 2.3.1 | 未插入 USB 充电 | 观察待机界面 | 仅显示百分比,无 `+` 后缀 | 待执行 |
| 2.3.2 | 插入 USB 充电(电池未满) | USB 供电 | 状态栏显示 `85%+`CHRG 低电平 | 待执行 |
| 2.3.3 | 电池充满后 | 等待充电完成 | `+` 后缀消失CHRG 变高阻 | 待执行 |
| 2.3.4 | 充电时热插拔 USB | 反复插拔 | 状态切换无延迟 > 2s | 待执行 |
| 2.3.5 | 低电量充电(电池 < 3.3V | 电池耗尽后充电 | 充电电流约 41.7mAUI 显示充电中 | 待执行 |
| 2.3.6 | 充电时进入休眠 | 插入 USB 等待 60s 自动休眠 | 休眠后应能被唤醒 | 待执行 |
| 2.3.7 | 充电时触发报警/SOS | 充电中触发报警 | 报警功能正常 | 待执行 |
| 2.3.8 | Is_Charging() 电平反转验证 | 强制拉高/拉低 DET 引脚 | UART 调试输出正确状态 | 待执行 |
### 2.4 ADC 电池电压校准
| # | 操作步骤 | 验证手段 | 预期结果 | 状态 |
|---|---------|---------|---------|------|
| 2.4.1 | 用万用表实测电池电压 | 记录 Vref | 如 3.85V | 待执行 |
| 2.4.2 | UART 读取 ADC_GetBatteryVoltage_mV() 返回值 | 串口助手 | 偏差应在 ±50mV 内 | 待执行 |
| 2.4.3 | 如果偏差过大,调整校准系数 | adc.c:122 | val * Vref_mV / adc_raw | 待执行 |
| 2.4.4 | 验证电量百分比线性度 | 可调电源模拟 3.3V~4.2V | 0%~100% 线性输出 | 待执行 |
---
## 三、测试环境与工具清单
- Keil C251 编译器 + STC-ISP 烧录工具
- USB 转 TTL 串口模块 (115200 baud)
- 数字万用表
- 双通道数字示波器(至少 10MHz 带宽)
- 可调直流稳压电源(模拟电池 3.0~4.2V
- 逻辑分析仪(可选)
- 已组装好的手环 PCB x 1~2pcs
- 433MHz 发射模块/遥控器
- 锂电池 3.7V
---
## 四、代码修改记录
| 文件 | 修改内容 | 状态 |
|------|---------|------|
| App/config.h | 新增 CHRG_DET sbit 定义 | ✔ |
| App/system.c | GPIO_Init() 中增设 RGB_DIN=0配置 CHRG_DET 引脚Self_Test() 输出充电状态 | ✔ |
| App/main.c | GPIO_Init() 后立即发 WS2812B RESET 脉冲 | ✔ |
| Drivers/adc.h | 新增 Is_Charging() 声明 | ✔ |
| Drivers/adc.c | 实现 Is_Charging() | ✔ |
| App/rgb.c | 确认 SPI 配置正确 | ✔ 已验证 |
| App/ui.c | UI_ShowClockPage 中接入 Is_Charging() 显示 `+` 后缀 | ✔ |

162
work-order/wo-04 Normal file
View File

@@ -0,0 +1,162 @@
# 📋 工单 wo-04: 多国语言支持(法/英)与 RF 接收中断化改造
**工单编号**: WO-04
**创建时间**: 2026-07-31
**模块组件**: 菜单/UI (app_menu.c, ui.c) / 数据持久化 (database.c) / 射频驱动 (rf.c) / 定时器 (Timer0)
**优先级**: 中
**状态**: 进行中(阶段 1 已实机验证通过;阶段 2 代码已完成,待编译烧录验证)
---
## 零、阶段 2 开发过程中顺带发现并修复的既有 bug
在设计 RF 中断化的看门狗超时判断(需要用 `ms_tick` 判断是否卡在半成品帧状态太久)时,发现 **`ms_tick` 全局变量从未被递增过**,永远等于 0
* `App/main.c` 声明 `volatile u16 ms_tick = 0;`,全项目除此之外没有任何地方对它做 `++`。
* 用 `git log -S "ms_tick++"` 定位到 commit `c0d1fe7`(App 框架重构):重构前 `Timer1_Isr` 里确实有 `ms_tick++;` 及配套的 1000 归零逻辑,重构时把时钟相关代码抽到 `Clock_IncMS()`(用它自己的私有计数器 `ms_cnt`),误删了 `ms_tick++` 这一行,此后再未补回。
* 影响范围广:`app_menu.c` 呼吸灯与 IPC 绑定帧重发、`app_pair.c` 对码等待动画与 30 秒超时、`app_alarm.c`/`app_sos.c` 灯光闪烁与马达节拍、`app_clock.c` 时间设置光标闪烁、`event.c` 报警红框闪烁,全部依赖 `ms_tick` 计时,此前实际上都是冻结/不生效的。
* **修复**:在 `App/event.c` 的 `Timer1_Isr` 中补回 `ms_tick++;`(0~999 循环,与原逻辑一致)。已完成,不在此工单原定范围内,但顺带修复。
---
## 一、需求概述
本工单包含两项互相独立的改造,按顺序分阶段实施,每阶段单独提交、单独验证:
1. **阶段 1 — 多国语言支持**:菜单与界面文案支持法语(FR)/英语(EN)切换,断电重启后保留上次选择。
2. **阶段 2 — RF 接收中断化**:把现在阻塞轮询的 `EV1527_Decode()` 改造成外部中断驱动 + Timer0 时间戳测量,消除主循环阻塞。
---
## 二、阶段 1: 多国语言支持(法/英)
### 1.1 背景与结论
- 目标语言为法语 + 英语,均为纯 ASCII 字符,现有 `FONT_8x16` 字库已完全覆盖,**不需要新增任何字模资源**。
- 参考了 `100_smart_boat` 项目 `xtell_remote_control/language` 的 X-Macro 字符串表模式,去掉了其中中文字库相关的部分(本工单不涉及中文)。
### 1.2 设计要点(已与用户确认)
| 决策项 | 结论 |
|---|---|
| 语言选择是否持久化 | 需要。断电重启后恢复上次选择 |
| 持久化存储位置 | **不单开新 Flash 扇区**,追加存入 `sensor_list` 所在的同一扇区(该扇区 512 字节仅用了 352 字节,剩余空间足够),复用现有 `Load_Database`/`Save_Database` 读写流程 |
| 菜单位置与顺序 | 语言切换项放在**主菜单第 1 项**,原有 3 项("HORLOGE 时间/APPAIRAGE 对码/LIAISON 绑定")依次后移一位 |
| 交互方式 | **不做二级选择子菜单**。菜单高亮在语言项时,直接按■(确认键)在 FR/EN 间原地切换并立即保存,同时用新语言重绘当前菜单 |
### 1.3 实现内容
1. 新增 `App/language.h` + `App/language.c`:
- `typedef enum { LANG_FR = 0, LANG_EN, LANG_COUNT } Language;`
- `void Lang_Init(Language lang);` / `void Lang_Set(Language lang);` / `Language Lang_Current(void);`
- `const char* Lang_Get(StringID id);`
2. 新增 `App/strings.h`:用 X-Macro (`WRISTBAND_STRING_LIST`)登记 `ui.c` 中全部界面文案、`database.c` 的 `sensor_names_fr[5]`、`app_menu.c` 的 `menu_items[5]`,每条包含 FR/EN 两列译文。
3. `App/ui.c`:所有 `LCD_ShowStringCentered(y, "字面量", ...)` / `LCD_ShowString(...)` 改为经 `Lang_Get(STR_XXX)` 取值。
4. `App/app_menu.c`:
- 主菜单项数从 3 改为 4,`menu_select` 上限从 2 改为 3。
- 序号重排:0-语言切换,1-HORLOGE,2-APPAIRAGE,3-LIAISON。
- `menu_level == 0` 分支中,`menu_select == 0` 时按 CONFIRM_CLICK 直接切换语言 + 保存 + 重绘,不进入新的 `menu_level`。
5. `App/ui.c` 的 `UI_ShowMainMenu()`:新增语言项对应的图标与文案(法语环境显示 "LANGUE",英语环境显示 "LANGUAGE"),原有 3 个图标绘制的 `selected_index` 判断值整体 +1。
6. `App/database.c`:
- `Load_Database()`/`Save_Database()` 扩展,在 `sensor_list` 数组之后追加 1 字节语言标志的读写(同一扇区、同一次擦写周期)。
- 开机时 `Load_Database()` 读到的语言字节传给 `Lang_Init()`。
### 1.4 验收标准
- [ ] 开机默认语言为法语(或 Flash 中已保存的上次选择)。
- [ ] 主菜单第 1 项显示"语言"选项,上下键可以正常移动到该项(不影响其余 3 项的原有功能)。
- [ ] 在语言项上按确认键,菜单文案立即从法语切换为英语(或反之),无需退出重进菜单。
- [ ] 切换语言后,退出菜单进入其余各页面(时钟页、对码流程、报警页、SOS页)文案均正确跟随当前语言显示。
- [ ] 切换语言后断电重启,开机后菜单语言与断电前一致。
- [ ] 现有传感器数据库(已绑定的传感器列表)在语言切换与断电重启后不受影响、不丢失。
---
## 三、阶段 2: RF 接收中断化改造
### 2.1 背景
- 现状:`EV1527_Decode()`([Drivers/rf.c](Drivers/rf.c))为阻塞轮询实现,同步头捕获阶段最坏情况阻塞达 30ms,由后台 `RfMonitorApp_onRun()` 在主循环中无条件调用,可能干扰按键长按计时的准确性(按键计数按"调用次数"而非"真实时间"累加)。
- 确认结论:STC32G12K128(至少本项目 `stc32g.h` 覆盖的寄存器范围)**没有硬件捕获(CAP/PCA)寄存器**,只能用"外部中断 + 自由运行定时器时间戳"的软件方案实现等效功能。
### 2.2 设计要点
| 决策项 | 结论 |
|---|---|
| 边沿检测 | 复用现有 P3.6(`RF_RX_DATA`)端口中断(`interrupt 16`),从"只在休眠时开启"改为"常态双边沿触发常驻开启" |
| 时间戳来源 | 复用现有 Timer0(已用于 `RF_Delay_us`/`GetPulseDuration`),精度 0.5us/tick(24MHz 晶振 12T 模式下),来自晶振本身,精度足够,不需要改 1T 模式 |
| 计时范围 | Timer0 为 16 位,0.5us/tick 约 32.768ms 溢出一次,而同步头低电平上限要求测到 60ms —— 需要加 Timer0 溢出中断,把 16 位硬件计数扩展为软件维护的更宽时间戳,避免溢出截断 |
| 解码逻辑归属 | 沿用现有的同步头判定 + 24 位比值解调算法,只是从"主循环阻塞轮询"搬到"中断内增量式状态机推进";中断中不直接操作事件队列,只写标志位/结果变量,由主循环取用后生成 `SystemEvent` |
### 2.3 顺带修复
- `GetPulseDuration()`([Drivers/rf.c:160-172](Drivers/rf.c#L160-L172))中 `timeout_ticks` 用 `u16` 存储,60ms 超时换算成 tick(120000)超出 `u16` 上限被截断为约 27.2ms,与注释/调用方预期不符。本次改造一并修正(改用能容纳完整量程的类型)。
### 2.4 实现内容
1. Timer0 改为开机后常驻自由运行(不再是 `RF_Delay_us`/`GetPulseDuration` 里那种每次启停复位的一次性用法),新增 Timer0 溢出中断以扩展时间戳位宽。
2. P3.6 端口中断改为常态开启双边沿触发;`Port3_Isr` 内根据系统是否处于休眠切换态,分别处理"仅唤醒"和"正常解码"两种职责。
3. 中断内维护一个小状态机(等待同步头 / 累计第几位 / 暂存 addr 和 data),每次边沿中断推进一步,solved 24 位后把结果写入类似 `p0_wakeup_flag` 模式的标志变量。
4. `RfMonitorApp_onRun()` 及 `PairApp_onRun()` 改为检查该标志变量,而不是直接调用阻塞的 `EV1527_Decode()`。
5. 增加"半成品帧超时看门狗":同步头之后若在预期时间内未凑够 24 位,自动放弃并复位状态机,防止被干扰卡死。
### 2.5 验收标准
- [ ] 主循环轮询 RF 不再有阻塞现象(可用示波器/计时对比主循环单次迭代耗时)。
- [ ] 对码(`PairApp`)、后台报警监听(`RfMonitorApp`)功能行为与改造前一致,能正确捕获/解码射频信号。
- [ ] 按键长按计时(3 秒判定)在有射频信号持续输入时不再受干扰、计时准确。
- [ ] 休眠状态下 P3.6 收到射频信号仍能正常唤醒,唤醒功能不受本次改造影响。
- [ ] 干扰噪声下(无有效信号)状态机不会卡死在"半成品帧"状态。
---
## 四、代码修改记录
| 阶段 | 文件 | 修改内容 | 状态 |
|---|---|---|---|
| 1 | App/language.h / App/language.c | 新建,语言状态管理与取字符串接口(`Lang_Init/Lang_Set/Lang_Current/Lang_Get`) | ✔ 已完成 |
| 1 | App/strings.h | 新建,`WRISTBAND_STRING_LIST` X-Macro 字符串表(法/英) | ✔ 已完成 |
| 1 | App/config.h | 移除旧的 `sensor_names_fr` extern 声明 | ✔ 已完成 |
| 1 | App/database.h / App/database.c | 新增 `sensor_type_name_ids[5]` 字符串 ID 表;`Load_Database`/`Save_Database` 追加读写语言字节(与 `sensor_list` 共用同一扇区,偏移地址 `sizeof(sensor_list)`);`Add_Sensor_With_Zone` 改用 `Lang_Get()` 生成名称 | ✔ 已完成 |
| 1 | App/app_menu.c | 主菜单改 4 项并重排序号(0-语言/1-HORLOGE/2-APPAIRAGE/3-LIAISON);语言项按确认键原地切换 FR/EN 并调用 `Save_Database()` 持久化;`menu_items` 改名并改造为 `device_menu_ids` 字符串 ID 表 | ✔ 已完成 |
| 1 | App/ui.c | 全部界面文案字面量替换为 `Lang_Get(STR_XXX)`;`UI_ShowMainMenu` 新增第 0 项语言图标(直接显示当前语言 "FR"/"EN" 缩写) | ✔ 已完成 |
| 1 | wristband.uvproj | 新增 `App/language.c` 参与 Keil 工程编译 | ✔ 已完成 |
| 2 | Drivers/rf.c | 新增 Timer0 常驻自由运行 + 溢出中断 (`Timer0_Isr`) 扩展为 32 位时间戳;新增 `RF_HandleEdgeInterrupt()` 边沿增量解码状态机;`RF_Delay_us` 改为对运行中 Timer0 的快照式等待 (不再启停复位)`EV1527_Decode()` 改为非阻塞检查中断解码结果 (签名不变);移除死代码 `mock_rf_*` 与旧版 `GetPulseDuration` | ✔ 已完成 |
| 2 | Drivers/rf.h | 移除 `GetPulseDuration` 声明,新增 `RF_HandleEdgeInterrupt` 声明,更新 `EV1527_Decode` 注释 | ✔ 已完成 |
| 2 | App/main.c | `Port3_Isr` 去掉旧版"触发一次即自禁用 P3INTE"的逻辑,改为每次都调用 `RF_HandleEdgeInterrupt()`,身兼休眠唤醒与常态解码两职 | ✔ 已完成 |
| 2 | App/system.c | `Enter_Low_Power_Sleep`P3.6 双边沿触发与中断允许改为常态 (在 `RF_Init` 一次性配置),此处只保留重新武装 `P3WKUE`;新增休眠前禁用 `ET0`。`Wakeup_Restore``P3INTE` 唤醒后保留 P3.6 位 (不再清零),新增唤醒后恢复 `ET0` | ✔ 已完成 |
| 2 | App/app_rf_monitor.c / App/app_pair.c | 无需改动 —— `EV1527_Decode()` 函数签名与调用方式保持不变 | 不涉及 |
---
## 五之一、后续结构调整:新增 Drivers/timer.c 统一存放定时器驱动代码
用户指出 `Timer0_Isr`/`Timer1_Isr` 属于底层驱动代码,不应该分散在 `Drivers/rf.c` 和 `App/event.c` 里,要求集中放到 `Drivers/timer.c`。据此做了以下调整(纯代码搬家,行为不变)
* 新建 `Drivers/timer.h` / `Drivers/timer.c`,收纳:
- `Timer0_Init()` / `Timer0_Isr()` / `Timer0_GetTimestamp()`(原来内联在 `RF_Init()` 里 + `rf.c` 的 `Timer0_Isr`/`RF_GetTimestamp`)
- `Timer1_Init()` / `Timer1_Isr()` / `key_scan_flag` 的定义(原来在 `App/event.c`)
* `Drivers/rf.c``RF_Init()` 改为调用 `Timer0_Init()``RF_Delay_us`/`RF_HandleEdgeInterrupt` 里的 `RF_GetTimestamp()` 改调用 `Timer0_GetTimestamp()`。
* `App/event.c`:删除 `Timer1_Init`/`Timer1_Isr`/`key_scan_flag` 定义与不再需要的 `clock.h` 引入;`Event_KeyScan_Poll` 等其余逻辑不变。
* `App/event.h`:移除 `Timer1_Init` 声明(改由 `Drivers/timer.h` 提供)`key_scan_flag` 的 extern 声明保留(定义位置变了,对外接口不变)。
* `App/main.c`:新增 `#include "../Drivers/timer.h"` 以取得 `Timer1_Init` 声明。
* `wristband.uvproj`:新增 `Drivers/timer.c` 参与编译。
## 五、阶段 2 实现备注
* Timer0 改为开机后 (`RF_Init()`) 启动一次、永不停止的自由运行计数器,配合 `Timer0_Isr` 溢出中断把 16 位硬件计数扩展为 32 位软件时间戳 (`RF_GetTimestamp()`),解决了原 `GetPulseDuration()` 里 `u16 timeout_ticks` 存不下 60ms 对应的 120000 tick、被截断成约 27.2ms 的既有 bug。
* `EV1527_TxFrame()` 用到的 `RF_Delay_us()` 原本会启停复位 Timer0与"常驻自由运行"直接冲突 (会导致发射一次后 RX 时间戳永久停摆),已改为对运行中的 Timer0 做起止快照差值等待,纯只读,不影响其自由运行状态。
* `EV1527_Transmit()` 整个发射过程是 `EA=0` 屏蔽全局中断执行的 (原有设计,未改动),期间 Timer0 硬件仍在计数但溢出中断进不来,`rf_timer_ovf_count` 会短暂漏计;发射结束后的第一条 RX 边沿会因为时间差异常大而被判定超出合法区间、直接丢弃,随后自动恢复正常——这是状态机自愈的正常代价,不需要额外处理。开机阶段 `Self_Test()`/`Load_Database()` 耗时较长同理,也只会导致开机后第一条边沿被丢弃一次。
* 看门狗超时阈值取 200ms (`RF_DECODE_TIMEOUT_MS`),明显大于一帧最坏情况的理论总耗时 (~95ms同步头最长 61.5ms + 24 位数据最长约 33.6ms),避免正常长帧被误杀。
* P3.6 双边沿中断现在身兼两职:休眠期间做唤醒源 (`p3_wakeup_flag`),正常运行期间驱动 `RF_HandleEdgeInterrupt()` 解码;`Enter_Low_Power_Sleep`/`Wakeup_Restore` 中对应调整为"不再清零 P3.6 的中断允许位",避免唤醒后 RX 解码功能失效。
* 尚未实机编译烧录验证,第三节验收标准仍需在真实硬件上逐项确认 (尤其是干扰环境下的解码成功率、休眠唤醒功能是否受影响)。
### 2.6 实机验证发现的问题与修复P3.6 悬空引发中断风暴,导致开机卡死
用户实测反馈:改动后开机背光闪一下就灭,按住开机键不放背光反复闪烁。排查确认根因:
* LR690L 芯片在 `SHUT=1`(休眠/发射时也会关断接收,见 `RF_SetMode(0)`/`RF_SetMode(2)`)期间,数据脚 (P3.6) 为高阻输出。原来的 P3.6 配置是纯高阻输入、**没有内部上拉**。
* 开机时射频芯片默认关闭 (`RF_Init()` 里 `SHUT=1`),要到 `AppManager_StartApp(APP_ID_CLOCK)` 里才会调用 `RF_SetMode(1)` 真正打开接收——这段时间 P3.6 处于悬空状态,容易受旁边射频电路干扰而电平乱跳。
* 旧版设计里 P3.6 中断只在临睡前临时开启且触发一次自动关闭,悬空乱跳最多误触发一次,代价很小;本次改造把它变成开机后永久常开、不再自动关闭,悬空噪声在 `EA=1` 之后会引发中断风暴CPU 完全无法执行主循环,导致开机画面卡在初始化那一下就再也画不出时钟界面。
* **修复**:给 P3.6 加内部上拉 (`P3PU |= 0x40;`),在悬空 (芯片输出高阻) 时把电平钳在稳定的高电平,不再乱跳;芯片主动驱动数据时上拉阻值足够弱,不影响正常接收。
- `Drivers/rf.c` 的 `RF_Init()` 中新增。
- `App/system.c` 的 `Wakeup_Restore()` 中新增恢复 (因为 `Enter_Low_Power_Sleep()` 休眠前为了省电特意把这个上拉关掉了,唤醒后要重新打开)。
## 六、阶段 1 实现备注
* 语言字节与传感器数据库共用同一 Flash 扇区(偏移地址为 `sizeof(sensor_list)`,当前为 352,扇区剩余空间足够),复用现有 `Load_Database`/`Save_Database`,未新增独立扇区。
* 未做二级选择子菜单:主菜单第 1 项选中"语言"时直接按■原地切换 FR/EN,立即保存并用新语言重绘当前菜单。
* 首次开机(Flash 从未写入过语言字节,读到擦除态 `0xFF`)时 `Lang_Init` 会自动回退为 `LANG_FR`,不会出现非法枚举值。
* 本阶段仅替换界面文案与新增语言状态管理,未改动任何按键状态机、事件队列或射频协议相关代码逻辑。
* 尚未实机编译烧录验证,第四节验收标准仍需在真实硬件上逐项确认。