Skip to content

Commit 97a99ef

Browse files
committed
Modernize to v2.1.0: PulseExpress API, expanded examples, bootloader
- Rename driver class Max32664 -> PulseExpress and types Max32664* -> PulseExpress*; old names kept via max32664.h compatibility shim. Rename sources to protocentral_pulse_express.{h,cpp}. SPDX headers throughout. - Fix earlier-firmware regression: begin() no longer hard-fails on non-40.x hubs; version check is now a soft warning exposed via firmwareSupported(). - Fix raw PPG streaming data loss: drain FIFO with a short read delay, clamp the OpenView 16-bit scaling, and make the OpenView send non-blocking so a host stall cannot back up and overflow the sensor FIFO. - Add public readStatus() hub-diagnostics accessor. - Expand examples from 3 to 11 (01.Name convention): heart rate/SpO2, BPT calibration, BPT estimation, EEPROM persistence, multi-subject calibration, HRV, device info/diagnostics. - Add MAX32664 bootloader API (pulse_express_bootloader.{h,cpp}), the FirmwareFlash example, and a host flashing script under extras/. The .msbl firmware image is non-redistributable and is gitignored. - Update README, keywords.txt, library.properties (version 2.1.0), add CHANGELOG.md and Uno R4 upload scripts.
1 parent fb23ff1 commit 97a99ef

27 files changed

Lines changed: 2724 additions & 970 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,8 @@ dkms.conf
5353

5454
.bin
5555
CLAUDE.md
56+
57+
# Maxim/ADI firmware images are non-redistributable IP — never commit them.
58+
*.msbl
59+
*.bin
60+

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Changelog
2+
3+
All notable changes to the ProtoCentral Pulse Express library.
4+
5+
## [2.1.0] - 2026-06-17
6+
7+
### Changed
8+
- **Primary class renamed `Max32664` -> `PulseExpress`**, and the public types
9+
from `Max32664*` to `PulseExpress*`, to match modern ProtoCentral library
10+
conventions. The 2.0.x names still compile: `#include "max32664.h"` provides
11+
`using` aliases. New sketches should `#include "protocentral_pulse_express.h"`.
12+
- Source files renamed to `protocentral_pulse_express.{h,cpp}`; `max32664.h` is
13+
now a thin backward-compatibility shim.
14+
- License headers switched to the SPDX format across `src/` and `examples/`.
15+
- `library.properties`: `version=2.1.0`, added `includes=`.
16+
17+
### Fixed
18+
- **Earlier-firmware regression:** `begin()` no longer hard-fails (returned
19+
`UnsupportedFirmware`) when the hub reports a major version other than 40.
20+
Version checking is now a **soft warning**`begin()` proceeds with legacy
21+
capability defaults and exposes `firmwareSupported()` for callers to branch on.
22+
23+
### Added
24+
- `firmwareSupported()` and a public `readStatus(HubStatus&)` for hub diagnostics.
25+
- Examples expanded from 3 to 11 (renumbered, `01.Name` convention):
26+
`03.HeartRateSpO2`, `04.BPTCalibration`, `05.BPTEstimation`,
27+
`06.BPTCalibrateAndEstimate`, `07.SaveLoadCalibrationEEPROM`,
28+
`08.MultiSubjectCalibration`, `09.HeartRateVariability`,
29+
`10.DeviceInfoAndDiagnostics`, `11.FirmwareFlash`.
30+
- **Bootloader / firmware flashing (factory / recovery):**
31+
`PulseExpressBootloader` class (`src/pulse_express_bootloader.{h,cpp}`),
32+
the `11.FirmwareFlash` sketch, and a host script `extras/flash_tool/flash_msbl.py`.
33+
Implemented from Maxim/ADI UG6806 Table 9 — validate on hardware before
34+
production. The `.msbl` firmware image is non-redistributable and is never
35+
committed (`*.msbl`/`*.bin` gitignored).
36+
37+
### Known issues
38+
- `Max32664Caps::sendBpMedication` / `sendRestMode` are derived from the firmware
39+
version but not yet consumed; calibration on firmware <40.2.2 may be missing
40+
setup steps. Tracked for a follow-up once the older opcodes are confirmed.
41+
42+
## [2.0.0]
43+
44+
- Clean-break rewrite from 1.0.x: runtime multi-firmware support across the
45+
MAX32664D 40.x line, raw PPG / BPT calibration / BPT estimation modes.

README.md

Lines changed: 128 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,82 +2,155 @@ Protocentral Pulse Express with MAX30102 and MAX32664D
22
================================
33

44
[![Compile Examples](https://github.com/Protocentral/protocentral-pulse-express/workflows/Compile%20Examples/badge.svg)](https://github.com/Protocentral/protocentral-pulse-express/actions?workflow=Compile+Examples)
5-
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6+
[![Arduino Library](https://img.shields.io/badge/Arduino-Library-00979D?logo=arduino)](https://www.arduino.cc)
67

78
## Don't have one? [Buy it here](https://protocentral.com/product/pulse-express-pulse-ox-heart-rate-sensor-with-max32664/)
89

910
![](assets/pulse_exp.jpg)
1011

11-
Pulse Express is an efficient and versatile breakout board with integrated high-sensitivity optical sensors (MAX30102) and also a chip that does the calculations (biometric sensor hub MAX32664D). Integrating Maxim’s MAX32664 Version D makes Pulse Express unique, with an internal algorithm that works to measure different data as you start. With its built-in low power capability, the board is suitable for any wearable health for finger-based applications.
12+
Pulse Express is a compact breakout that pairs a high-sensitivity MAX30102 optical
13+
sensor with a MAX32664D biometric sensor hub. The MAX32664D runs Maxim/ADI's
14+
on-chip Blood-Pressure-Trending (BPT) algorithm and reports calculated heart rate,
15+
SpO2 and blood-pressure-trend data over I2C, so the host MCU does no signal
16+
processing. Suitable for finger-based wearable health R&D.
1217

13-
**Note: This device is only meant to be used for research & development purposes and is NOT to be used as a medical device. This product is not FDA, CE or FCC approved for consumer use.**
18+
> **Note:** This device is for research & development only and is **NOT** a medical
19+
> device. It is not FDA, CE or FCC approved for consumer use.
1420
15-
## Hardware Setup
21+
## Overview
1622

17-
Connection with the Arduino board is as follows:
18-
19-
|Max32664 pin label| Arduino Connection |Pin Function |
20-
|----------------- |---------------------|------------------|
21-
| SDA | A4 | Serial Data |
22-
| SCL | A5 | Serial Clock |
23-
| Vin | 5V | Power |
24-
| GND | Gnd | Gnd |
25-
| MFIO Pin | 05 | MFIO |
26-
| RESET Pin | 04 | Reset |
23+
**Key capabilities (all exposed by this library):**
24+
- Raw PPG streaming (IR + Red, 24-bit ADC counts)
25+
- Heart rate and SpO2 with per-reading confidence
26+
- Blood-pressure trending — calibration → estimation (systolic / diastolic)
27+
- SpO2 coefficient calibration (per Maxim AN6845)
28+
- Multi-subject calibration, up to 5 subjects (firmware >= 40.5.0)
29+
- Heart-rate variability (SDNN / RMSSD) from inter-beat intervals
30+
- Runtime firmware-version detection and capability adaptation (40.x line)
31+
- Factory / recovery firmware flasher for the MAX32664 bootloader
2732

33+
## Installation
2834

29-
## Visualizing Output
35+
**Arduino Library Manager (recommended):** search for *ProtoCentral Pulse Express*
36+
and click Install.
3037

31-
![](assets/pulse_express_7sec.gif)
38+
**Manual:** download this repository as a ZIP and use
39+
*Sketch -> Include Library -> Add .ZIP Library...* in the Arduino IDE.
3240

41+
## Hardware Setup
3342

34-
## For further details, refer [the documentation on Pulse Express board](https://docs.protocentral.com/getting-started-with-PulseExpress)
43+
| MAX32664 pin | Arduino UNO / R4 | ESP32 (example) | Function |
44+
|--------------|------------------|-----------------|-------------------|
45+
| SDA | A4 | GPIO21 | I2C data |
46+
| SCL | A5 | GPIO22 | I2C clock |
47+
| Vin | 5V | 5V / VIN | Power |
48+
| GND | GND | GND | Ground |
49+
| MFIO | D2 | any GPIO | Data-ready / mode |
50+
| RESET | D4 | any GPIO | Hub reset |
3551

52+
The RESET and MFIO pins are passed to the constructor, so any GPIOs work.
53+
54+
![](assets/pulse_express_7sec.gif)
55+
56+
## Quick Start
57+
58+
```cpp
59+
#include <Wire.h>
60+
#include "protocentral_pulse_express.h"
61+
62+
PulseExpress hub(/*RESET=*/4, /*MFIO=*/2);
63+
64+
void setup() {
65+
Serial.begin(57600);
66+
Wire.begin();
67+
if (hub.begin() != PulseExpressStatus::Ok) { /* handle error */ }
68+
hub.startEstimation(); // streams HR + SpO2 (BP needs calibration)
69+
}
70+
71+
void loop() {
72+
PulseExpressSample s[8];
73+
size_t n = 0;
74+
if (hub.readSamples(s, 8, &n) == PulseExpressStatus::Ok)
75+
for (size_t i = 0; i < n; ++i) {
76+
Serial.print("HR "); Serial.print(s[i].heartRate(), 1);
77+
Serial.print(" SpO2 "); Serial.println(s[i].spo2(), 1);
78+
}
79+
}
80+
```
81+
82+
## Firmware versions & capabilities
83+
84+
The MAX32664D changed its protocol across the 40.x firmware line. `begin()` reads
85+
the hub firmware version and derives a capability set (`hub.caps()`); the driver
86+
then adapts automatically. Notable breakpoints:
87+
88+
- **40.2.2+** — BP-medication / rest-mode setup steps dropped.
89+
- **40.5.0+** — calibration vector 824 -> 512 bytes, sample 23 -> 29 bytes, date
90+
format YYMMDD -> YYYYMMDD, and multi-point calibration (`calIndex` 0..4).
91+
92+
Branch on `hub.caps().multiPointCalib` if you support both. Version checking is a
93+
**soft warning**: `begin()` still proceeds on unexpected firmware and reports it via
94+
`hub.firmwareSupported()`.
95+
96+
**Firmware is pre-installed at the factory.** Boards ship flashed and ready; you do
97+
not need a firmware image for normal use. The MAX32664D application image (`.msbl`)
98+
is Maxim/ADI IP and is **not** redistributed here. Re-flashing is a factory/recovery
99+
operation — see [`examples/11.FirmwareFlash`](examples/11.FirmwareFlash) and
100+
[`extras/`](extras/).
101+
102+
## Examples
103+
104+
| # | Sketch | Shows |
105+
|----|--------|-------|
106+
| 01 | RawPPGStreamPlotter | Raw IR PPG to Arduino Serial Plotter |
107+
| 02 | RawPPGStreamOpenView | Raw IR/Red to ProtoCentral OpenView GUI |
108+
| 03 | HeartRateSpO2 | Live heart rate + SpO2 (no calibration) |
109+
| 04 | BPTCalibration | Run BPT calibration, dump the vector |
110+
| 05 | BPTEstimation | Estimate BP/HR/SpO2 from a saved vector |
111+
| 06 | BPTCalibrateAndEstimate | Full calibrate -> estimate, end to end |
112+
| 07 | SaveLoadCalibrationEEPROM | Persist the vector to EEPROM, reload on boot |
113+
| 08 | MultiSubjectCalibration | Calibrate up to 5 subjects (FW >= 40.5.0) |
114+
| 09 | HeartRateVariability | SDNN / RMSSD from inter-beat intervals |
115+
| 10 | DeviceInfoAndDiagnostics | Firmware versions, caps, live hub status |
116+
| 11 | FirmwareFlash | Factory/recovery .msbl flasher |
117+
118+
## API Reference
119+
120+
Construct with the reset and MFIO pins (and optionally a `TwoWire` bus):
121+
`PulseExpress hub(resetPin, mfioPin, Wire);`
122+
123+
- `begin()` — reset hub, enter application mode, read version, derive caps.
124+
- `version()`, `algoVersion()`, `caps()`, `firmwareSupported()` — device info.
125+
- BPT calibration: `startCalibration(...)`, `readSample(...)`, `readCalibrationVector(...)`.
126+
- BPT estimation: `loadCalibrationVector(...)`, `startEstimation(...)`, `readSamples(...)`.
127+
- Raw PPG: `startRaw()`, `readRaw(...)`.
128+
- Diagnostics / teardown: `readStatus(...)`, `stop()`.
129+
130+
All fallible calls return `PulseExpressStatus`; compare against
131+
`PulseExpressStatus::Ok`. See [`src/protocentral_pulse_express.h`](src/protocentral_pulse_express.h)
132+
for the full documented API.
133+
134+
> **Migrating from 2.0.x:** the class was `Max32664` with `Max32664*` types. Those
135+
> names still work via `#include "max32664.h"`. New code should use `PulseExpress`
136+
> and `#include "protocentral_pulse_express.h"`. See [CHANGELOG.md](CHANGELOG.md).
137+
138+
## For further details
139+
140+
Refer to [the Pulse Express documentation](https://docs.protocentral.com/getting-started-with-PulseExpress).
36141
37142
License Information
38143
===================
39144
40145
![License](license_mark.svg)
41146
42-
This product is open source! Both, our hardware and software are open source and licensed under the following licenses:
43-
44-
Hardware
45-
---------
46-
47-
**All hardware is released under the [CERN-OHL-P v2](https://ohwr.org/cern_ohl_p_v2.txt)** license.
48-
49-
Copyright CERN 2020.
50-
51-
This source describes Open Hardware and is licensed under the CERN-OHL-P v2.
52-
53-
You may redistribute and modify this documentation and make products
54-
using it under the terms of the CERN-OHL-P v2 (https:/cern.ch/cern-ohl).
55-
This documentation is distributed WITHOUT ANY EXPRESS OR IMPLIED
56-
WARRANTY, INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY
57-
AND FITNESS FOR A PARTICULAR PURPOSE. Please see the CERN-OHL-P v2
58-
for applicable conditions
59-
60-
Software
61-
--------
62-
63-
**All software is released under the MIT License(http://opensource.org/licenses/MIT).**
64-
65-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
66-
67-
Documentation
68-
-------------
69-
**All documentation is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).**
70-
![CC-BY-SA-4.0](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)
71-
72-
You are free to:
147+
This product is open source! Both our hardware and software are open source and
148+
licensed under the following licenses:
73149
74-
* Share — copy and redistribute the material in any medium or format
75-
* Adapt — remix, transform, and build upon the material for any purpose, even commercially.
76-
The licensor cannot revoke these freedoms as long as you follow the license terms.
150+
**Hardware** — [CERN-OHL-P v2](https://ohwr.org/cern_ohl_p_v2.txt). Copyright CERN 2020.
77151
78-
Under the following terms:
152+
**Software** — [MIT License](http://opensource.org/licenses/MIT).
79153
80-
* Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
81-
* ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
154+
**Documentation** — [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).
82155
83-
Please check [*LICENSE.md*](LICENSE.md) for detailed license descriptions.
156+
See [*LICENSE.md*](LICENSE.md) for the detailed license descriptions.

examples/01.RawPPGStreamPlotter/01.RawPPGStreamPlotter.ino

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,33 @@
1-
//////////////////////////////////////////////////////////////////////////////////////////
1+
// SPDX-License-Identifier: MIT
2+
// SPDX-FileCopyrightText: Copyright (c) 2025 Ashwin Whitchurch, Protocentral Electronics
23
//
3-
// Pulse Express — Raw PPG stream for the Arduino Serial Plotter
4+
// ProtoCentral Pulse Express (MAX30102 + MAX32664D)
5+
// Example 01 — Raw PPG stream for the Arduino Serial Plotter
46
//
5-
// Streams the IR PPG counter from the MAX30101 (via the MAX32664D sensor hub)
6-
// one sample per line. Place a finger on the sensor and open Tools Serial
7-
// Plotter at 57600 baud to view the waveform.
7+
// Streams the IR PPG counter from the MAX30102 (via the MAX32664D sensor hub)
8+
// one sample per line. Place a finger on the sensor and open Tools -> Serial
9+
// Plotter at 57600 baud to view the waveform.
810
//
9-
// Hardware connections (default):
10-
// | MAX32664 pin | Arduino pin | Function |
11-
// |--------------|-------------|----------------|
12-
// | SDA | A4 | I2C data |
13-
// | SCL | A5 | I2C clock |
14-
// | Vin | 5V | Power |
15-
// | GND | GND | |
16-
// | MFIO | D2 | Data-ready int |
17-
// | RESET | D4 | Hub reset |
11+
// Original 2020 example: Joice Tm, Copyright (c) 2020 ProtoCentral.
1812
//
19-
// Original 2020 example: Joice Tm, Copyright (c) 2020 ProtoCentral
20-
// Modernised: Copyright (c) 2025 ProtoCentral Electronics
21-
//
22-
// This software is licensed under the MIT License (http://opensource.org/licenses/MIT).
23-
//
24-
/////////////////////////////////////////////////////////////////////////////////////////
13+
// Hardware connections (default):
14+
// | MAX32664 pin | Arduino pin | Function |
15+
// |--------------|-------------|----------------|
16+
// | SDA | A4 | I2C data |
17+
// | SCL | A5 | I2C clock |
18+
// | Vin | 5V | Power |
19+
// | GND | GND | |
20+
// | MFIO | D2 | Data-ready int |
21+
// | RESET | D4 | Hub reset |
2522

2623
#include <Wire.h>
27-
#include "max32664.h"
24+
#include "protocentral_pulse_express.h"
2825

2926
#define RESET_PIN 4
3027
#define MFIO_PIN 2
3128
#define SAMPLE_CAP 32 // max samples to drain per loop iteration
3229

33-
Max32664 hub(RESET_PIN, MFIO_PIN);
30+
PulseExpress hub(RESET_PIN, MFIO_PIN);
3431

3532
void setup()
3633
{
@@ -39,8 +36,8 @@ void setup()
3936

4037
hub.setDebug(&Serial);
4138

42-
Max32664Status s = hub.begin();
43-
if (s != Max32664Status::Ok)
39+
PulseExpressStatus s = hub.begin();
40+
if (s != PulseExpressStatus::Ok)
4441
{
4542
Serial.print("hub.begin() failed: 0x");
4643
Serial.println(uint8_t(s), HEX);
@@ -52,7 +49,7 @@ void setup()
5249
Serial.println(hub.version().patch);
5350

5451
s = hub.startRaw();
55-
if (s != Max32664Status::Ok)
52+
if (s != PulseExpressStatus::Ok)
5653
{
5754
Serial.print("startRaw() failed: 0x");
5855
Serial.println(uint8_t(s), HEX);
@@ -63,10 +60,10 @@ void setup()
6360

6461
void loop()
6562
{
66-
Max32664RawSample buf[SAMPLE_CAP];
63+
PulseExpressRawSample buf[SAMPLE_CAP];
6764
size_t n = 0;
68-
Max32664Status s = hub.readRaw(buf, SAMPLE_CAP, &n, /*wantRed=*/false);
69-
if (s != Max32664Status::Ok) return;
65+
PulseExpressStatus s = hub.readRaw(buf, SAMPLE_CAP, &n, /*wantRed=*/false);
66+
if (s != PulseExpressStatus::Ok) return;
7067

7168
for (size_t i = 0; i < n; ++i)
7269
{

0 commit comments

Comments
 (0)