Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .trellis/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

# Commit message used when auto-committing journal/index changes
# after running add_session.py
session_commit_message: "chore: record journal"
session_commit_message: "chore: 记录会话日志"

# Maximum lines per journal file before rotating to a new one
max_journal_lines: 2000
Expand Down
2 changes: 1 addition & 1 deletion .trellis/scripts/common/task_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def _auto_commit_archive(task_name: str, repo_root: Path) -> None:
print("[OK] No task changes to commit.", file=sys.stderr)
return

commit_msg = f"chore(task): archive {task_name}"
commit_msg = f"chore(task): 归档 {task_name}"
rc, _, err = run_git(["commit", "-m", commit_msg], cwd=repo_root)
if rc == 0:
print(f"[OK] Auto-committed: {commit_msg}", file=sys.stderr)
Expand Down
75 changes: 74 additions & 1 deletion .trellis/spec/backend/quality-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ void scheduler_run(void) { ... }
- `refactor(scheduler): 支持动态添加任务`
- `docs: 同步 README 外设配置说明`

**自动提交也必须中文**:Trellis 脚本产生的提交说明同样遵循本格式。
- `session_commit_message` 必须配置为中文,例如 `chore: 记录会话日志`
- 归档任务提交必须使用中文动词,例如 `chore(task): 归档 04-20-max30102`

**错误示例**:
- `chore: record journal`
- `chore(task): archive 04-20-max30102`

### 6. 代码风格

- **缩进**:4 空格,禁用 Tab
Expand Down Expand Up @@ -164,7 +172,62 @@ void scheduler_run(void) { ... }
- 修改 HAL 调用 → 是否影响中断优先级
- 修改全局变量 → 消费者模块是否同步更新

### 4. 未来扩展(不强制)
### 4. 生理传感器算法验证(MAX30102 类)

**适用范围**:光电心率、血氧、姿态融合等需要从连续窗口解算生理值的模块。

**接口合同**:
- `*_task()` 可在启动期、无手指、FIFO 暂无样本时直接返回,但不得输出伪有效值。
- 结果全局变量无效时为 `0`,对应 `*_valid` 必须同步为 `0`。
- HR 单位固定为 bpm,SpO2 单位固定为 `%`,SpO2 有效上限允许为 `100`。
- 窗口算法必须区分 `raw DC`、去均值 `AC`、有效标志、保持/过期策略。

**验证矩阵**:

| 场景 | 必须行为 |
|------|----------|
| 启动缓冲未填满 | 用实际已填样本数做手指判断,不得拿未初始化窗口参与均值 |
| 无手指或 IR DC 过低 | `heart_rate=0`、`spo2=0`、`hr_valid=0`、`spo2_valid=0` |
| 手指刚放上 | 允许短时间无有效值,不能长期卡在无数据 |
| 静止贴合 10 秒 | HR 应进入合理范围并避免相邻窗口持续大幅跳变 |
| SpO2 输出 100 | 允许,但必须来自有效 DC/AC ratio 与查表,不得只靠钳位制造成功 |
| 连续解算失败 | 有限窗口内可保持旧值,超过保持次数必须清零 valid |

**根因复盘**:
- HR 大幅跳变通常来自峰值窗口不稳、峰间距常量与采样率不一致、缺少 beat 间隔均值。
- 长时间无数据通常来自门限过严或启动期把未填满缓冲区当完整窗口计算。
- SpO2 成功率异常通常来自 AC/DC 混用、DC 饱和/过低未过滤、失败窗口立即清零导致抖动。
- `100%` 本身不是错误;错误是没有通过质量门限却被 clamp 成 `100`。

**正确模式**:

```c
uint16_t mean_count = samples_filled < WINDOW ? samples_filled : WINDOW;
uint32_t ir_mean = recent_ir_mean(mean_count);
if (ir_mean < FINGER_THRESHOLD) {
clear_result();
return;
}

expire_stale_heart_rate();
compute_spo2(&sp, &sp_ok);
```

**错误模式**:

```c
uint32_t ir_mean = full_window_mean(WINDOW); /* 启动期包含无效样本 */
if (!sp_ok) clear_result(); /* 单次失败立刻清零导致闪断 */
spo2 = clamp(sp, 0, 100); /* clamp 不能代表检测成功 */
```

**测试点**:
- 串口记录至少覆盖:无手指、刚贴合、稳定贴合 10 秒、移开手指。
- 稳定贴合时检查 `(hr_ok, spo2_ok)` 不应长期为 `(0, 0)`。
- 移开手指后检查 valid 标志和数值同步清零。
- cppcheck 必须无 `unused variable`,例如临时算法数组未使用时必须删除。

### 5. 未来扩展(不强制)
- 可考虑 Ceedling / Unity 做纯算法逻辑(NMEA 解析、环形缓冲区)的 PC 侧单元测试
- 仅限与硬件解耦的纯 C 函数

Expand Down Expand Up @@ -200,14 +263,24 @@ PR 作者与审阅者按以下清单逐项确认:
- [ ] 初始化失败不阻塞主流程(可降级)
- [ ] 未调用 `Error_Handler()` 于应用层
- [ ] 全局变量单一写者
- [ ] 传感器解算失败时同步清零结果与 valid 标志

### 日志与调试
- [ ] 高频任务(≤10ms)中无 `printf`(或已加级别控制)
- [ ] 无遗留的调试 `printf("test...")`
- [ ] 日志无敏感信息(凭据、精确坐标等)

### 传感器算法
- [ ] 生理算法区分 raw DC、去均值 AC、插值 baseline,禁止用 AC 峰替代 DC 项
- [ ] 峰/谷索引必须做边界检查,去重逻辑不得丢弃窗口起始处的合法峰
- [ ] HR/SpO2 有效范围、采样率和峰间距常量必须一致
- [ ] 启动期用实际样本数判断接触状态,禁止未填满窗口参与均值
- [ ] 连续失败、旧值保持、过期清零策略明确,`value` 与 `valid` 同步
- [ ] SpO2 `100%` 允许,但必须来自有效 ratio/查表路径,不得仅由钳位产生

### 提交与文档
- [ ] 提交消息遵循 Conventional Commits 中文格式
- [ ] Trellis 自动提交消息也已配置为中文
- [ ] `README.md` 在外设/模块变动时同步更新
- [ ] `CLAUDE.md` 在架构/约定变动时同步更新
- [ ] `dev_log.md` 本地记录变更(不入库)
4 changes: 4 additions & 0 deletions .trellis/tasks/archive/2026-04/04-20-max30102/check.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{"file": ".claude/commands/trellis/finish-work.md", "reason": "Finish work checklist"}
{"file": ".claude/commands/trellis/check.md", "reason": "Code quality check spec"}
{"file": "CLAUDE.md", "reason": "验证代码风格/注释/命名是否符合项目约定"}
{"file": "APP/mpu6050.c", "reason": "对照参考模板检查驱动结构一致性"}
1 change: 1 addition & 0 deletions .trellis/tasks/archive/2026-04/04-20-max30102/debug.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"file": ".claude/commands/trellis/check.md", "reason": "Code quality check spec"}
7 changes: 7 additions & 0 deletions .trellis/tasks/archive/2026-04/04-20-max30102/implement.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"file": ".trellis/workflow.md", "reason": "Project workflow and conventions"}
{"file": ".trellis/spec/backend/index.md", "reason": "Backend development guide"}
{"file": "CLAUDE.md", "reason": "项目代码风格与架构约定(外设表、命名、头文件规则)"}
{"file": "APP/mpu6050.c", "reason": "I2C 模块驱动模板(HAL_I2C_Mem_Read 用法、task 函数风格)"}
{"file": "APP/mpu6050.h", "reason": "头文件守卫/extern 全局变量的写法模板"}
{"file": "APP/bsp_system.h", "reason": "应用模块登记位置"}
{"file": "APP/scheduler.c", "reason": "调度器任务注册方式"}
93 changes: 93 additions & 0 deletions .trellis/tasks/archive/2026-04/04-20-max30102/prd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# MAX30102 心率血氧传感器模块

## Goal

在 SmartHelm 现有框架中新增 MAX30102 心率/血氧传感器驱动模块,通过硬件 I2C1 总线采集 Red/IR 光电数据并解算 HR(心率 bpm)与 SpO2(血氧饱和度 %),以现有传感器模块(MPU6050/DHT11/MQ2)一致的风格集成至调度器。

## What I already know

- I2C1 已在 CubeMX 中配置为 Fast Mode 400 kHz(PB6/PB7),当前由 MPU6050 (0x68) 独占使用。
- MAX30102 7-bit I2C 地址固定为 **0x57**(写 0xAE / 读 0xAF),与 MPU6050 **不冲突**,可共享 I2C1 总线。
- 项目模块模板已成熟:`APP/<module>.{c,h}` + `bsp_system.h` 登记 + `scheduler.c` 注册周期任务 + `main.c` 初始化。
- 现有驱动全部使用 `HAL_I2C_Mem_Read/Write(&hi2c1, addr<<1, reg, ...)` 阻塞 API,非 DMA。
- 输出风格一致为 `printf` 走 USART1(115200)。
- STM32F103C8T6 资源:64 KB Flash / 20 KB SRAM(心率算法需要环形缓冲区,典型 100~500 个样本的 int32,需评估内存占用)。
- MAX30102 芯片 Part ID 寄存器(0xFF)= 0x15,可用于上电自检。
- MAX30102 VLED 需 3.3V、VDD 需 1.8V,通常模块板自带 LDO(硬件责任,软件不关心)。

## Assumptions (temporary)

- 采用 **轮询 FIFO + 调度器周期任务** 的风格(与 MPU6050 一致),不启用 MAX30102 的 INT 引脚,避免改动 CubeMX 引脚分配。
- 采样率 100 Hz(SMP_AVE=1,SPO2_SR=100),FIFO 几乎满时的轮询周期应 ≤ 50 ms 以防溢出(32 深 FIFO × 1 sample = 320 ms 满,但建议 50 ms 读一次留余量)。
- HR/SpO2 算法采用 Maxim 官方参考实现(MAXREFDES117 精简版,约 4~6 KB Flash)。
- 算法更新周期约 1 秒(每累积 100 个样本解算一次)。

## Decision (ADR-lite)

**Context**: 需决定 MAX30102 的 I2C 总线归属与 FIFO 读取触发机制。

**Decision**:
1. **独立 I2C2 总线**(PB10/PB11,Fast Mode 400 kHz,Duty cycle 2),不与 MPU6050 共用 I2C1。CubeMX 已配置并生成代码。
2. **纯轮询方案(A)**:调度器每 50 ms 读一次 FIFO,不接 MAX30102 的 INT 脚。

**Consequences**:
- 利:总线隔离减少相互阻塞风险;无需改动 `.ioc` 再引入额外 GPIO/EXTI;与 MPU6050/DHT11/MQ2 现有轮询风格完全一致。
- 弊:多占用 I2C2 外设与 PB10/PB11 两个引脚;100 Hz 采样下每 50 ms 产生约 30 字节 I2C 读(总线 400 kHz 下可忽略)。
- 未来如需低功耗或高采样率,可再接 INT 脚升级为 B 方案,驱动接口保持兼容。

## Requirements (evolving)

- 新增 `APP/max30102.{c,h}`,通过 I2C1 总线与 MAX30102 通讯。
- 提供 `max30102_init()`:上电自检(读 Part ID=0x15 验证连接)、寄存器配置(SPO2 模式、100Hz 采样、LED 电流、FIFO)。
- 提供 `max30102_task()`:周期读取 FIFO Red/IR 数据 → 更新心率/血氧。
- 提供全局变量(extern):`heart_rate`(bpm, int32)、`spo2`(%, int32)、`hr_valid` / `spo2_valid`(uint8_t)。
- 集成到 `scheduler_task[]` 与 `main.c` 初始化流程。
- 登记至 `bsp_system.h`。
- 更新 Keil `helmet.uvprojx` 添加源文件。
- 更新 `CLAUDE.md` 与 `README.md` 的外设/模块章节。

## Acceptance Criteria (evolving)

- [ ] 冷启动后 2 秒内能通过 I2C 读到 Part ID = 0x15(自检通过)。
- [ ] 手指放置 10 秒内,串口能持续稳定输出合理的 HR(40~180)和 SpO2(85~100)。
- [ ] MPU6050 姿态解算、DHT11、MQ2 功能不受 I2C 总线共享影响(所有串口输出保持正常)。
- [ ] CI 质量门禁通过(cppcheck、头文件守卫、UTF-8 无 BOM、LF)。
- [ ] 手指离开时 `hr_valid=0`,不输出错误的心率。

## Definition of Done

- [ ] 代码通过 Keil MDK 编译无 warning。
- [ ] 通过 CI 质量门禁(`.github/workflows/quality.yml`)。
- [ ] 实机串口验证 HR/SpO2 在手指贴合时数值可信。
- [ ] `bsp_system.h`、`scheduler.c`、`main.c`、`helmet.uvprojx` 同步更新。
- [ ] `CLAUDE.md`(外设表格、架构章节)与 `README.md`(功能模块)同步更新。
- [ ] Trellis session 通过 `add_session.py` 记录。

## Out of Scope (explicit)

- 不新增 I2C2 或软件 I2C。
- 不引入外部第三方库(例如 Arduino SparkFun 库),算法代码内嵌。
- 不做跌倒+心率的融合逻辑。
- 不做蓝牙/无线上报。
- 不做 OLED 显示。

## Technical Notes

- 可参考 Maxim 官方 **MAXREFDES117#** 参考设计中的 `algorithm.c`(自相关峰值检测 + 比值解算 SpO2),License 为 Maxim 自家宽松许可。
- FIFO 每样本 6 字节(Red 3B + IR 3B,18-bit)。读取需使用 `HAL_I2C_Mem_Read` burst。
- 寄存器地址常用:`FIFO_WR_PTR=0x04`、`FIFO_RD_PTR=0x06`、`FIFO_DATA=0x07`、`MODE=0x09`、`SPO2=0x0A`、`LED1_PA=0x0C`(Red)、`LED2_PA=0x0D`(IR)、`PART_ID=0xFF`。
- 典型配置:MODE=0x03(SpO2 模式),SPO2=0x27(100Hz,18-bit,4.7ms 脉宽),LED 电流 0x24(约 7 mA)。

## Research Notes

### 相似方案对比

- **MAXREFDES117 官方算法**:Maxim 参考实现,自相关峰值检测心率 + 经验公式 SpO2,约 400~500 行 C,成熟可靠,几乎所有 STM32/Arduino 开源工程的基石。
- **DFRobot / SparkFun Arduino 库**:包装官方算法 + C++ OO 封装,结构相对重,不适合直接移植到裸 C 嵌入式。
- **自研滑窗峰检测**:代码量最小(~100 行)但临床精度差、抗运动干扰弱。

### 项目约束

- STM32F103 资源紧张(64KB Flash / 20KB SRAM),需要尽量减小算法内存占用。
- 已有 MPU6050 DMP 固件占用不小 Flash,需注意剩余空间。
- 必须纯 C(Keil MDK-ARM,armcc/armclang)。
53 changes: 53 additions & 0 deletions .trellis/tasks/archive/2026-04/04-20-max30102/task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"id": "max30102",
"name": "max30102",
"title": "brainstorm: 新增 MAX30102 心率血氧传感器模块",
"description": "",
"status": "completed",
"dev_type": "backend",
"scope": null,
"package": null,
"priority": "P2",
"creator": "Maple",
"assignee": "Maple",
"createdAt": "2026-04-20",
"completedAt": "2026-04-28",
"branch": null,
"base_branch": "dev",
"worktree_path": null,
"current_phase": 4,
"next_action": [
{
"phase": 1,
"action": "brainstorm"
},
{
"phase": 2,
"action": "research"
},
{
"phase": 3,
"action": "implement"
},
{
"phase": 4,
"action": "check"
},
{
"phase": 5,
"action": "update-spec"
},
{
"phase": 6,
"action": "record-session"
}
],
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}
9 changes: 5 additions & 4 deletions .trellis/workspace/Maple/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

<!-- @@@auto:current-status -->
- **Active File**: `journal-1.md`
- **Total Sessions**: 6
- **Last Active**: 2026-04-19
- **Total Sessions**: 7
- **Last Active**: 2026-04-28
<!-- @@@/auto:current-status -->

---
Expand All @@ -19,7 +19,7 @@
<!-- @@@auto:active-documents -->
| File | Lines | Status |
|------|-------|--------|
| `journal-1.md` | ~280 | Active |
| `journal-1.md` | ~314 | Active |
<!-- @@@/auto:active-documents -->

---
Expand All @@ -29,6 +29,7 @@
<!-- @@@auto:session-history -->
| # | Date | Title | Commits | Branch |
|---|------|-------|---------|--------|
| 7 | 2026-04-28 | 完成 MAX30102 心率血氧模块 | `3b0f8f8`, `51a993f` | `dev` |
| 1 | 2026-03-05 | 项目初始化,搭建项目框架 | `86a9534` | `main` |
| 2 | 2026-03-06 | 新增 MQ2 烟雾传感器模块 | `c77edde` | `main` |
| 3 | 2026-03-06 | 新增 DHT11 温湿度传感器模块 | `7ba2525`, `c7825ba`, `c38070c` | `main` |
Expand All @@ -43,4 +44,4 @@

- Sessions are appended to journal files
- New journal file created when current exceeds 2000 lines
- Use `add_session.py` to record sessions
- Use `add_session.py` to record sessions
34 changes: 34 additions & 0 deletions .trellis/workspace/Maple/journal-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,37 @@
### Next Steps

- 后续新增模块应沿用当前 CI 约束,保持编码规范与静态分析零告警


## Session 7: 完成 MAX30102 心率血氧模块

**Date**: 2026-04-28
**Task**: 完成 MAX30102 心率血氧模块
**Branch**: `dev`

### Summary

新增 MAX30102 驱动与调度集成,移植并修复心率/血氧解算稳定性问题,更新项目文档和质量规范;用户实机测试确认通过。

### Main Changes

(Add details)

### Git Commits

| Hash | Message |
|------|---------|
| `3b0f8f8` | (see git log) |
| `51a993f` | (see git log) |

### Testing

- [OK] (Add test results)

### Status

[OK] **Completed**

### Next Steps

- None - task complete
Loading
Loading