Skip to content

Commit 1188840

Browse files
committed
Fix example portability failures in CI
Compile Examples had been failing since the 2.1.0 modernisation, on arduino:avr:uno, arduino:avr:nano and arduino:mbed_nano:nanorp2040connect. Both causes were real portability bugs in the examples. EEPROM: the Nano RP2040 Connect defines ARDUINO_ARCH_RP2040 and ARDUINO_ARCH_MBED, but only the earlephilhower rp2040 core ships an EEPROM library. 07.SaveLoadCalibrationEEPROM's architecture guard let the board through and the build died on a missing EEPROM.h; it now excludes mbed. SRAM on 2 KB AVRs: examples 04, 06, 07, 08 and 10 placed every Serial string literal in RAM, pushing global usage to 102-124% of an Uno's 2048 bytes. Literals now live in flash via F(), which is enough for 10 (124% -> 84%). The four calibration examples also carry the up-to-824-byte calibration vector and cannot fit whatever is done with strings, so they build an informative stub on AVRs with under 4 KB of SRAM -- the pattern 11.FirmwareFlash already used. That also covers the Leonardo, which previously squeaked past the compiler at 94% RAM and would very likely have failed at runtime. Mega and every non-AVR target still build the full sketches: verified on avr:uno, avr:nano, avr:leonardo, avr:mega, mbed_nano:nanorp2040connect and renesas_uno:minima, 11 of 11 each.
1 parent 252734e commit 1188840

6 files changed

Lines changed: 170 additions & 83 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
All notable changes to the ProtoCentral Pulse Express library.
44

5+
## [Unreleased]
6+
7+
### Fixed
8+
- **CI `Compile Examples` was failing on five boards.** Both causes were real
9+
portability bugs in the examples, not CI configuration:
10+
- `07.SaveLoadCalibrationEEPROM` failed on `arduino:mbed_nano:nanorp2040connect`
11+
with `EEPROM.h: No such file or directory`. The Nano RP2040 Connect defines
12+
`ARDUINO_ARCH_RP2040` *and* `ARDUINO_ARCH_MBED`, but only the earlephilhower
13+
rp2040 core ships an EEPROM library; the architecture guard now excludes mbed.
14+
- `04`, `06`, `07`, `08` and `10` overflowed SRAM on 2 KB AVRs (up to 124 %).
15+
String literals now live in flash via `F()`, which was enough for `10`
16+
(124 % -> 84 %). The four calibration examples need the up-to-824-byte
17+
calibration vector and genuinely cannot fit, so they build an informative
18+
stub on AVRs with under 4 KB of SRAM — the pattern `11.FirmwareFlash`
19+
already used. Mega and every non-AVR target still build the full sketch.
20+
521
## [2.2.0] - 2026-08-27
622

723
### Fixed

examples/04.BPTCalibration/04.BPTCalibration.ino

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@
2626
#include <Wire.h>
2727
#include "protocentral_pulse_express.h"
2828

29+
// The BPT calibration vector is up to 824 bytes, which does not fit alongside
30+
// the Serial buffers and locals in 2 KB of SRAM. Build a stub on those parts
31+
// rather than failing with "data section exceeds available space".
32+
#if defined(__AVR__) && (RAMEND < 0x1000)
33+
34+
void setup()
35+
{
36+
Serial.begin(57600);
37+
while (!Serial && millis() < 3000) {}
38+
Serial.println(F("04.BPTCalibration needs more RAM than this board has:"));
39+
Serial.println(F("the BPT calibration vector alone is up to 824 bytes."));
40+
Serial.println(F("Use a Mega, UNO R4, ESP32, RP2040, STM32 or similar."));
41+
}
42+
void loop() {}
43+
44+
#else
45+
2946
#define RESET_PIN 4
3047
#define MFIO_PIN 2
3148

@@ -44,7 +61,7 @@ static void halt(const char *step, PulseExpressStatus s)
4461
{
4562
while (true)
4663
{
47-
Serial.print(step); Serial.print(" failed: 0x");
64+
Serial.print(step); Serial.print(F(" failed: 0x"));
4865
Serial.println(uint8_t(s), HEX);
4966
delay(5000);
5067
}
@@ -69,7 +86,7 @@ static PulseExpressStatus startCalibration()
6986

7087
static void dumpHex(const uint8_t *buf, size_t len)
7188
{
72-
Serial.println("---- BEGIN CALIBRATION VECTOR ----");
89+
Serial.println(F("---- BEGIN CALIBRATION VECTOR ----"));
7390
for (size_t i = 0; i < len; ++i)
7491
{
7592
if (buf[i] < 0x10) Serial.print('0');
@@ -78,7 +95,7 @@ static void dumpHex(const uint8_t *buf, size_t len)
7895
else Serial.print(' ');
7996
}
8097
Serial.println();
81-
Serial.println("---- END CALIBRATION VECTOR ----");
98+
Serial.println(F("---- END CALIBRATION VECTOR ----"));
8299
}
83100

84101
void setup()
@@ -90,20 +107,20 @@ void setup()
90107
PulseExpressStatus s = hub.begin();
91108
if (s != PulseExpressStatus::Ok) halt("hub.begin()", s);
92109
if (!hub.firmwareSupported())
93-
Serial.println("WARNING: firmware outside validated 40.x line.");
110+
Serial.println(F("WARNING: firmware outside validated 40.x line."));
94111

95112
s = startCalibration();
96113
if (s != PulseExpressStatus::Ok) halt("startCalibration", s);
97114

98-
Serial.println("Place your finger on the sensor — hold still until 100%.");
115+
Serial.println(F("Place your finger on the sensor — hold still until 100%."));
99116
PulseExpressSample sample;
100117
unsigned long startMs = millis();
101118
while (true)
102119
{
103120
if (hub.readSample(sample) == PulseExpressStatus::Ok)
104121
{
105-
Serial.print("progress: "); Serial.print(sample.progress);
106-
Serial.print("% status: "); Serial.println(uint8_t(sample.bpStatus));
122+
Serial.print(F("progress: ")); Serial.print(sample.progress);
123+
Serial.print(F("% status: ")); Serial.println(uint8_t(sample.bpStatus));
107124
if (sample.bpStatus == PulseExpressBpStatus::Success && sample.progress >= 100)
108125
break;
109126
}
@@ -115,15 +132,17 @@ void setup()
115132
s = hub.readCalibrationVector(calibVector, sizeof(calibVector), &len);
116133
if (s != PulseExpressStatus::Ok) halt("readCalibrationVector", s);
117134

118-
Serial.print("Calibration complete. Vector length: ");
135+
Serial.print(F("Calibration complete. Vector length: "));
119136
Serial.println(len);
120137
dumpHex(calibVector, len);
121138

122139
hub.stop();
123-
Serial.println("Done. Save the vector above for use with example 05.");
140+
Serial.println(F("Done. Save the vector above for use with example 05."));
124141
}
125142

126143
void loop()
127144
{
128145
delay(1000);
129146
}
147+
148+
#endif // RAM guard

examples/06.BPTCalibrateAndEstimate/06.BPTCalibrateAndEstimate.ino

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@
3636
#include <Wire.h>
3737
#include "protocentral_pulse_express.h"
3838

39+
// The BPT calibration vector is up to 824 bytes, which does not fit alongside
40+
// the Serial buffers and locals in 2 KB of SRAM. Build a stub on those parts
41+
// rather than failing with "data section exceeds available space".
42+
#if defined(__AVR__) && (RAMEND < 0x1000)
43+
44+
void setup()
45+
{
46+
Serial.begin(57600);
47+
while (!Serial && millis() < 3000) {}
48+
Serial.println(F("06.BPTCalibrateAndEstimate needs more RAM than this board has:"));
49+
Serial.println(F("the BPT calibration vector alone is up to 824 bytes."));
50+
Serial.println(F("Use a Mega, UNO R4, ESP32, RP2040, STM32 or similar."));
51+
}
52+
void loop() {}
53+
54+
#else
55+
3956
#define RESET_PIN 4
4057
#define MFIO_PIN 2
4158

@@ -63,7 +80,7 @@ static void haltWithError(const char *step, PulseExpressStatus s)
6380
while (true)
6481
{
6582
Serial.print(step);
66-
Serial.print(" failed: 0x");
83+
Serial.print(F(" failed: 0x"));
6784
Serial.println(uint8_t(s), HEX);
6885
delay(5000);
6986
}
@@ -93,7 +110,7 @@ static PulseExpressStatus runCalibration()
93110
}
94111
if (s != PulseExpressStatus::Ok) return s;
95112

96-
Serial.println("Place your finger on the sensor — hold still until 100%.");
113+
Serial.println(F("Place your finger on the sensor — hold still until 100%."));
97114

98115
PulseExpressSample sample;
99116
unsigned long startMs = millis();
@@ -102,9 +119,9 @@ static PulseExpressStatus runCalibration()
102119
PulseExpressStatus r = hub.readSample(sample);
103120
if (r == PulseExpressStatus::Ok)
104121
{
105-
Serial.print("progress: ");
122+
Serial.print(F("progress: "));
106123
Serial.print(sample.progress);
107-
Serial.print("% status: ");
124+
Serial.print(F("% status: "));
108125
Serial.println(uint8_t(sample.bpStatus));
109126

110127
if (sample.bpStatus == PulseExpressBpStatus::Success && sample.progress >= 100) break;
@@ -130,21 +147,21 @@ void setup()
130147
PulseExpressStatus s = hub.begin();
131148
if (s != PulseExpressStatus::Ok) haltWithError("hub.begin()", s);
132149

133-
Serial.print("Hub firmware ");
150+
Serial.print(F("Hub firmware "));
134151
Serial.print(hub.version().major); Serial.print('.');
135152
Serial.print(hub.version().minor); Serial.print('.');
136153
Serial.println(hub.version().patch);
137-
Serial.print("Calibration vector size: ");
154+
Serial.print(F("Calibration vector size: "));
138155
Serial.println(hub.caps().calibVectorBytes);
139156

140157
s = runCalibration();
141158
if (s != PulseExpressStatus::Ok) haltWithError("calibration", s);
142-
Serial.println("Calibration complete; reading vector.");
159+
Serial.println(F("Calibration complete; reading vector."));
143160

144161
size_t calibLen = 0;
145162
s = hub.readCalibrationVector(calibVector, sizeof(calibVector), &calibLen);
146163
if (s != PulseExpressStatus::Ok) haltWithError("readCalibrationVector", s);
147-
Serial.print("Stored "); Serial.print(calibLen); Serial.println(" calibration bytes.");
164+
Serial.print(F("Stored ")); Serial.print(calibLen); Serial.println(F(" calibration bytes."));
148165

149166
s = hub.stop(); // tear down calibration mode before re-enabling for estimation
150167
if (s != PulseExpressStatus::Ok) haltWithError("hub.stop()", s);
@@ -162,7 +179,7 @@ void setup()
162179
s = hub.startEstimation(coeffs);
163180
if (s != PulseExpressStatus::Ok) haltWithError("startEstimation", s);
164181

165-
Serial.println("Estimation running.");
182+
Serial.println(F("Estimation running."));
166183
delay(1000);
167184
}
168185

@@ -179,13 +196,15 @@ void loop()
179196

180197
for (size_t i = 0; i < n; ++i)
181198
{
182-
Serial.print("sys="); Serial.print(samples[i].systolic);
183-
Serial.print(" dia="); Serial.print(samples[i].diastolic);
184-
Serial.print(" hr="); Serial.print(samples[i].heartRate(), 1);
185-
Serial.print(" spo2="); Serial.print(samples[i].spo2(), 1);
186-
Serial.print(" status=");
199+
Serial.print(F("sys=")); Serial.print(samples[i].systolic);
200+
Serial.print(F(" dia=")); Serial.print(samples[i].diastolic);
201+
Serial.print(F(" hr=")); Serial.print(samples[i].heartRate(), 1);
202+
Serial.print(F(" spo2=")); Serial.print(samples[i].spo2(), 1);
203+
Serial.print(F(" status="));
187204
Serial.println(uint8_t(samples[i].bpStatus));
188205
}
189206
} while (n == 8);
190207
delay(100);
191208
}
209+
210+
#endif // RAM guard

examples/07.SaveLoadCalibrationEEPROM/07.SaveLoadCalibrationEEPROM.ino

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,15 @@
2323
// lack it). Guard on architecture so the full CI board matrix still compiles;
2424
// unsupported boards build a tiny stub instead. A plain #include (not
2525
// __has_include) is required so arduino-cli adds the EEPROM library path.
26-
#if defined(__AVR__) || defined(ARDUINO_ARCH_MEGAAVR) || defined(ARDUINO_ARCH_RENESAS) || \
27-
defined(ESP32) || defined(ESP8266) || defined(ARDUINO_ARCH_RP2040) || \
28-
defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_APOLLO3)
26+
// NOTE: the Nano RP2040 Connect defines ARDUINO_ARCH_RP2040 *and*
27+
// ARDUINO_ARCH_MBED, but the Arduino mbed core ships no EEPROM library — only
28+
// the earlephilhower rp2040 core does. Exclude mbed explicitly or this guard
29+
// lets that board through and the build dies on a missing EEPROM.h.
30+
#if (defined(__AVR__) || defined(ARDUINO_ARCH_MEGAAVR) || defined(ARDUINO_ARCH_RENESAS) || \
31+
defined(ESP32) || defined(ESP8266) || defined(ARDUINO_ARCH_RP2040) || \
32+
defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_APOLLO3)) && \
33+
!defined(ARDUINO_ARCH_MBED) && \
34+
!(defined(__AVR__) && (RAMEND < 0x1000))
2935

3036
#include <EEPROM.h>
3137

@@ -49,7 +55,7 @@ static void halt(const char *msg, PulseExpressStatus s)
4955
{
5056
while (true)
5157
{
52-
Serial.print(msg); Serial.print(" (0x"); Serial.print(uint8_t(s), HEX);
58+
Serial.print(msg); Serial.print(F(" (0x")); Serial.print(uint8_t(s), HEX);
5359
Serial.println(')'); delay(5000);
5460
}
5561
}
@@ -107,14 +113,14 @@ static PulseExpressStatus calibrate(size_t &lenOut)
107113
}
108114
if (s != PulseExpressStatus::Ok) return s;
109115

110-
Serial.println("Calibrating — finger on sensor, hold still until 100%.");
116+
Serial.println(F("Calibrating — finger on sensor, hold still until 100%."));
111117
PulseExpressSample sample;
112118
unsigned long startMs = millis();
113119
while (true)
114120
{
115121
if (hub.readSample(sample) == PulseExpressStatus::Ok)
116122
{
117-
Serial.print("progress: "); Serial.print(sample.progress); Serial.println('%');
123+
Serial.print(F("progress: ")); Serial.print(sample.progress); Serial.println('%');
118124
if (sample.bpStatus == PulseExpressBpStatus::Success && sample.progress >= 100) break;
119125
}
120126
if (millis() - startMs > 120000UL) return PulseExpressStatus::Timeout;
@@ -132,23 +138,23 @@ void setup()
132138
PulseExpressStatus s = hub.begin();
133139
if (s != PulseExpressStatus::Ok) halt("hub.begin()", s);
134140
if (!hub.firmwareSupported())
135-
Serial.println("WARNING: firmware outside validated 40.x line.");
141+
Serial.println(F("WARNING: firmware outside validated 40.x line."));
136142

137143
const size_t vecLen = hub.caps().calibVectorBytes;
138144
eeBegin(sizeof(EeHeader) + vecLen);
139145

140146
if (loadFromEeprom(vecLen))
141147
{
142-
Serial.println("Loaded calibration vector from EEPROM.");
148+
Serial.println(F("Loaded calibration vector from EEPROM."));
143149
}
144150
else
145151
{
146-
Serial.println("No stored vector — running calibration once.");
152+
Serial.println(F("No stored vector — running calibration once."));
147153
size_t len = 0;
148154
s = calibrate(len);
149155
if (s != PulseExpressStatus::Ok) halt("calibration", s);
150156
saveToEeprom(len);
151-
Serial.println("Calibration saved to EEPROM.");
157+
Serial.println(F("Calibration saved to EEPROM."));
152158
hub.stop();
153159
}
154160

@@ -160,7 +166,7 @@ void setup()
160166

161167
s = hub.startEstimation();
162168
if (s != PulseExpressStatus::Ok) halt("startEstimation", s);
163-
Serial.println("Estimation running.");
169+
Serial.println(F("Estimation running."));
164170
delay(1000);
165171
}
166172

@@ -175,10 +181,10 @@ void loop()
175181
if (hub.readSamples(samples, 8, &n) != PulseExpressStatus::Ok) return;
176182
for (size_t i = 0; i < n; ++i)
177183
{
178-
Serial.print("sys="); Serial.print(samples[i].systolic);
179-
Serial.print(" dia="); Serial.print(samples[i].diastolic);
180-
Serial.print(" hr="); Serial.print(samples[i].heartRate(), 1);
181-
Serial.print(" spo2="); Serial.println(samples[i].spo2(), 1);
184+
Serial.print(F("sys=")); Serial.print(samples[i].systolic);
185+
Serial.print(F(" dia=")); Serial.print(samples[i].diastolic);
186+
Serial.print(F(" hr=")); Serial.print(samples[i].heartRate(), 1);
187+
Serial.print(F(" spo2=")); Serial.println(samples[i].spo2(), 1);
182188
}
183189
} while (n == 8);
184190
delay(100);
@@ -190,8 +196,9 @@ void setup()
190196
{
191197
Serial.begin(57600);
192198
while (!Serial && millis() < 3000) {}
193-
Serial.println("EEPROM not available on this board — see example 05 to load");
194-
Serial.println("a calibration vector from a code array instead.");
199+
Serial.println(F("This board has no EEPROM library, or too little RAM for the"));
200+
Serial.println(F("824-byte calibration vector. See example 05 to load a vector"));
201+
Serial.println(F("from a code array instead."));
195202
}
196203
void loop() {}
197204

0 commit comments

Comments
 (0)