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
1 change: 1 addition & 0 deletions .github/workflows/keil-path-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ jobs:
with:
python-version: '3.11'
- run: python scripts/check_keil_paths.py
- run: python scripts/check_hex_snapshot.py
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ For detailed hardware list and connection instructions, please refer to [hardwar

**Build outputs:** Keil writes generated objects to `firmware/OBJ/` and listings to `firmware/USER/Listings/`. These build artifacts are intentionally ignored. A previously committed `Template.hex` snapshot is preserved under [`firmware/prebuilt/`](firmware/prebuilt/); its hardware compatibility has not been independently verified. For a reproducible firmware image, rebuild from source in Keil.

For validation and device-build limitations, see [Firmware verification](docs/FIRMWARE-VERIFICATION.md). The retained `firmware/prebuilt/Template.hex` is a historical snapshot, **not a hardware-tested release**.

### Mobile APP Usage

1. **Install APP**
Expand Down
2 changes: 2 additions & 0 deletions README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@

**构建产物管理:** Keil 生成的对象文件位于 `firmware/OBJ/`,Listing 文件位于 `firmware/USER/Listings/`,均不再纳入版本控制。原仓库中的 `Template.hex` 快照已保存在 [`firmware/prebuilt/`](firmware/prebuilt/),**尚未对其硬件兼容性进行独立验证**;建议使用 Keil 从源码重新编译固件。

固件路径、HEX 快照完整性及实物测试说明详见 [固件验证文档](docs/FIRMWARE-VERIFICATION.md)。`firmware/prebuilt/Template.hex` 仅为历史快照,**不代表已验证可用于所有开发板**。

### 手机 APP 使用

1. **安装 APP**
Expand Down
38 changes: 38 additions & 0 deletions docs/FIRMWARE-VERIFICATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# PulseBand — Firmware verification

This is an **educational STM32F103 prototype**, not a validated medical or consumer wearable product. A clean repository and a valid Intel HEX file are only initial checks.

## Source-of-truth

- Keil µVision project: `firmware/USER/Template.uvprojx`
- Core firmware / peripheral drivers: `firmware/CORE/`, `firmware/FWLIB/`, `firmware/HARDWAR/`, `firmware/SYSTEM/`
- Historical compiled firmware snapshot: `firmware/prebuilt/Template.hex`
- Build output (ignored by Git): `firmware/OBJ/` and `firmware/USER/Listings/`

## Automated checks

The repository's pull request workflow runs:

```shell
python scripts/check_keil_paths.py
python scripts/check_hex_snapshot.py
```

The first command checks source/include relative paths referenced by the Keil project. The second verifies Intel HEX record lengths, per-record checksums, end-of-file framing and prints the snapshot's SHA-256 checksum.

**Passing these checks is not an MCU compilation or functional board test.** The prebuilt HEX snapshot should not be assumed suitable for every STM32F103 board.

## Build on a development workstation

1. Install a suitably licensed Keil MDK / µVision toolchain and the compatible STM32F1 device support pack.
2. Open `firmware/USER/Template.uvprojx`.
3. Review chip selection, compiler settings, peripheral pin mappings, clock frequency and flash configuration for the actual hardware revision.
4. Select **Project → Build Target** and review errors/warnings. Rebuild the HEX from current sources if possible.
5. Compare the generated binary and its SHA-256 checksum to the reference only if toolchain, configuration and firmware revision are known to match.
6. Before flashing through ST-Link, confirm supply voltage and pin connections; preserve a backup of the existing firmware where permitted.

## Hardware acceptance checklist

Verify sensor communication, display, wireless protocol, alarm logic and power consumption on actual hardware. Document board revision, test equipment, firmware hash and observed results. Claims about SpO2, heart-rate precision, step-count accuracy and battery life require independent reference measurements.

**No clinical or diagnostic validity is claimed.**
50 changes: 50 additions & 0 deletions scripts/check_hex_snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Validate Intel HEX framing and checksums of the preserved firmware image.

This checks file integrity only, not compatibility with actual hardware.
"""
import hashlib
from pathlib import Path


def validate_hex(path):
records = 0
data_bytes = 0
eof = False
for lineno, raw in enumerate(path.read_text(encoding="ascii").splitlines(), 1):
line = raw.strip()
if not line:
continue
if eof:
raise ValueError("Line {}: trailing records after EOF".format(lineno))
if not line.startswith(":"):
raise ValueError("Line {}: not an Intel HEX record".format(lineno))
try:
payload = bytes.fromhex(line[1:])
except ValueError as exc:
raise ValueError("Line {}: invalid hexadecimal bytes".format(lineno)) from exc
if len(payload) < 5 or len(payload) != payload[0] + 5:
raise ValueError("Line {}: invalid record length".format(lineno))
if sum(payload) % 256:
raise ValueError("Line {}: checksum mismatch".format(lineno))
typ = payload[3]
if typ not in (0, 1, 2, 3, 4, 5):
raise ValueError("Line {}: unsupported record type".format(lineno))
if typ == 0:
data_bytes += payload[0]
if typ == 1:
if payload[0] != 0 or payload[1] != 0 or payload[2] != 0:
raise ValueError("Line {}: malformed EOF".format(lineno))
eof = True
if typ == 4 and payload[0] != 2:
raise ValueError("Line {}: malformed extended linear address".format(lineno))
records += 1
if not eof or not data_bytes:
raise ValueError("No data records or terminal EOF record found")
return records, data_bytes


if __name__ == "__main__":
target = Path(__file__).resolve().parents[1] / "firmware/prebuilt/Template.hex"
records, count = validate_hex(target)
print("Intel HEX: {} valid records, {} data bytes".format(records, count))
print("SHA-256:", hashlib.sha256(target.read_bytes()).hexdigest())
Loading