Skip to content

Commit fb23ff1

Browse files
committed
Increased all command delays
1 parent f4f180b commit fb23ff1

2 files changed

Lines changed: 197 additions & 144 deletions

File tree

src/max32664.cpp

Lines changed: 181 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,37 @@
3535
namespace
3636
{
3737

38-
// Per-command CMD_DELAY values from UG6921 Tables 2/5/6. Where a step is not
39-
// explicitly tagged, the doc's default of 2 ms applies.
40-
constexpr uint16_t kDefaultCmdDelayMs = 2;
41-
constexpr uint16_t kSetDateTimeDelayMs = 5;
42-
constexpr uint16_t kSetCalIndexDelayMs = 5;
43-
constexpr uint16_t kSetSpo2CoeffsDelayMs = 5;
38+
// Per-command CMD_DELAY values from UG6921 Tables 2/5/6, biased upward to
39+
// match what the original 1.0.x driver shipped (uniform 45 ms + extra ad-hoc
40+
// delays). UG6921 §1.1 quotes a 2 ms default; in practice the hub returns
41+
// 0xFE (busy) on commands that proxy to the MAX30101 over its secondary I2C
42+
// bus when the host polls that aggressively, and the doc itself recommends
43+
// "Increase the CMD_DELAY" as the remedy. We err on the safe side here and
44+
// rely on writeImpl()/readImpl() retry-on-0xFE for residual races.
45+
constexpr uint16_t kDefaultCmdDelayMs = 10;
46+
constexpr uint16_t kSetDateTimeDelayMs = 10;
47+
constexpr uint16_t kSetCalIndexDelayMs = 10;
48+
constexpr uint16_t kSetSpo2CoeffsDelayMs = 10;
4449
constexpr uint16_t kCalibVectorChunkDelayMs = 30;
45-
constexpr uint16_t kEnableAgcDelayMs = 20;
46-
constexpr uint16_t kEnableAfeDelayMs = 40;
47-
constexpr uint16_t kEnableBptDelayMs = 500;
48-
constexpr uint16_t kDisableAfeDelayMs = 40;
49-
constexpr uint16_t kDisableBptDelayMs = 500;
50-
constexpr uint16_t kDisableAgcDelayMs = 20;
51-
constexpr uint16_t kPostEnableSettleMs = 100;
52-
constexpr uint16_t kResetSettleMs = 1000;
53-
constexpr uint16_t kEnterAppModeDelayMs = 10;
54-
constexpr uint16_t kRawModeSettleMs = 180;
50+
constexpr uint16_t kEnableAgcDelayMs = 30;
51+
constexpr uint16_t kEnableAfeDelayMs = 60;
52+
constexpr uint16_t kEnableBptDelayMs = 600;
53+
constexpr uint16_t kDisableAfeDelayMs = 60;
54+
constexpr uint16_t kDisableBptDelayMs = 600;
55+
constexpr uint16_t kDisableAgcDelayMs = 30;
56+
constexpr uint16_t kPostEnableSettleMs = 200;
57+
constexpr uint16_t kResetSettleMs = 1500;
58+
constexpr uint16_t kEnterAppModeDelayMs = 20;
59+
constexpr uint16_t kRawModeSettleMs = 250;
60+
// LED-current writes (0x40/0x03/RegAddr/Val) proxy to the MAX30101 over the
61+
// hub's secondary I2C bus — give them substantial headroom over the default.
62+
constexpr uint16_t kLedCurrentDelayMs = 25;
63+
64+
// Retry policy for 0xFE responses. UG6921 §1.1 says "Device is busy. Try
65+
// again. Increase the CMD_DELAY." We retry up to kBusyRetryMax times and
66+
// double the post-write delay each attempt (capped at kBusyMaxDelayMs).
67+
constexpr uint8_t kBusyRetryMax = 4;
68+
constexpr uint16_t kBusyMaxDelayMs = 400;
5569

5670
// I2C chunk size for the long calibration-vector upload. Kept at 30 to match
5771
// the AVR Wire BUFFER_LENGTH of 32 with two bytes of headroom; on platforms
@@ -359,12 +373,14 @@ Max32664Status Max32664::startRaw()
359373
delay(kRawModeSettleMs);
360374

361375
// LED1 (red) and LED2 (IR) currents to half-scale. Must come AFTER the
362-
// algorithm enable or the values are overwritten during init.
376+
// algorithm enable or the values are overwritten during init. The 0x40/03
377+
// family proxies to the MAX30101 over the secondary I2C bus, so it needs
378+
// more headroom than the trivial-command default.
363379
s = writeCmd3(0x40, 0x03, kMax30101Led1RegAddr, kMax30101LedHalfScale, 0,
364-
kDefaultCmdDelayMs);
380+
kLedCurrentDelayMs);
365381
if (s != Max32664Status::Ok) return s;
366382
return writeCmd3(0x40, 0x03, kMax30101Led2RegAddr, kMax30101LedHalfScale, 0,
367-
kDefaultCmdDelayMs);
383+
kLedCurrentDelayMs);
368384
}
369385

370386
Max32664Status Max32664::readRaw(Max32664RawSample *out, size_t cap, size_t *count, bool wantRed)
@@ -628,162 +644,191 @@ void Max32664::parseSample(const uint8_t *buf, Max32664Sample &s) const
628644
// Low-level I2C primitives
629645
/////////////////////////////////////////////////////////////////////////////////////////
630646

647+
// All command transactions go through writeImpl()/readImpl(), which:
648+
// - send the pre-built command frame on the I2C bus,
649+
// - delay CMD_DELAY,
650+
// - read the hub status byte (and any payload, for the read variant),
651+
// - retry up to kBusyRetryMax times on 0xFE (busy), doubling the delay
652+
// each attempt up to kBusyMaxDelayMs.
653+
// Per UG6921 §1.1: "0xFE: Device is busy. Try again. Increase the CMD_DELAY."
654+
655+
Max32664Status Max32664::writeImpl(const uint8_t *frame, size_t frameLen, uint16_t cmdDelayMs)
656+
{
657+
uint16_t actualDelay = cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs;
658+
659+
for (uint8_t attempt = 0; attempt <= kBusyRetryMax; ++attempt)
660+
{
661+
_bus.beginTransmission(MAX32664_I2C_ADDR);
662+
for (size_t i = 0; i < frameLen; ++i) _bus.write(frame[i]);
663+
if (_bus.endTransmission() != 0)
664+
{
665+
tracef("write fam=0x%02X idx=0x%02X i2c-nack",
666+
frameLen > 0 ? frame[0] : 0, frameLen > 1 ? frame[1] : 0);
667+
return Max32664Status::HostCommError;
668+
}
669+
delay(actualDelay);
670+
if (_bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(1)) != 1)
671+
return Max32664Status::HostCommError;
672+
uint8_t st = uint8_t(_bus.read());
673+
674+
if (st == 0x00) return Max32664Status::Ok;
675+
if (st != 0xFE)
676+
{
677+
tracef("write fam=0x%02X idx=0x%02X status=0x%02X",
678+
frameLen > 0 ? frame[0] : 0, frameLen > 1 ? frame[1] : 0, st);
679+
return Max32664Status(st);
680+
}
681+
if (attempt == kBusyRetryMax)
682+
{
683+
tracef("write fam=0x%02X idx=0x%02X 0xFE busy after %u retries",
684+
frameLen > 0 ? frame[0] : 0, frameLen > 1 ? frame[1] : 0, attempt);
685+
return Max32664Status::DeviceBusy;
686+
}
687+
actualDelay = (actualDelay < kBusyMaxDelayMs / 2)
688+
? uint16_t(actualDelay * 2u)
689+
: kBusyMaxDelayMs;
690+
}
691+
return Max32664Status::DeviceBusy;
692+
}
693+
694+
Max32664Status Max32664::readImpl(const uint8_t *frame, size_t frameLen,
695+
uint8_t *out, size_t outLen, uint16_t cmdDelayMs)
696+
{
697+
uint16_t actualDelay = cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs;
698+
699+
for (uint8_t attempt = 0; attempt <= kBusyRetryMax; ++attempt)
700+
{
701+
_bus.beginTransmission(MAX32664_I2C_ADDR);
702+
for (size_t i = 0; i < frameLen; ++i) _bus.write(frame[i]);
703+
if (_bus.endTransmission() != 0)
704+
{
705+
tracef("read fam=0x%02X idx=0x%02X i2c-nack",
706+
frameLen > 0 ? frame[0] : 0, frameLen > 1 ? frame[1] : 0);
707+
return Max32664Status::HostCommError;
708+
}
709+
delay(actualDelay);
710+
711+
// Drain (1 + outLen) bytes across as many requestFrom() calls as the
712+
// platform's Wire buffer requires. The leading byte is the status; on
713+
// 0xFE we discard the rest of the chunk and retry the whole command.
714+
size_t remaining = outLen;
715+
uint8_t *cursor = out;
716+
bool firstChunk = true;
717+
uint8_t status = 0;
718+
bool transient = false;
719+
720+
while (remaining > 0 || firstChunk)
721+
{
722+
uint16_t want = uint16_t((firstChunk ? 1u : 0u) + remaining);
723+
if (want > kReadChunkBytes) want = kReadChunkBytes;
724+
uint8_t got = _bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(want));
725+
if (got != want) return Max32664Status::HostCommError;
726+
727+
if (firstChunk)
728+
{
729+
status = uint8_t(_bus.read());
730+
--got;
731+
firstChunk = false;
732+
if (status != 0x00)
733+
{
734+
while (got--) _bus.read();
735+
transient = (status == 0xFE);
736+
break;
737+
}
738+
}
739+
while (got-- && remaining > 0)
740+
{
741+
*cursor++ = uint8_t(_bus.read());
742+
--remaining;
743+
}
744+
if (remaining == 0) break;
745+
}
746+
747+
if (status == 0x00) return Max32664Status::Ok;
748+
if (!transient)
749+
{
750+
tracef("read fam=0x%02X idx=0x%02X status=0x%02X",
751+
frameLen > 0 ? frame[0] : 0, frameLen > 1 ? frame[1] : 0, status);
752+
return Max32664Status(status);
753+
}
754+
if (attempt == kBusyRetryMax)
755+
{
756+
tracef("read fam=0x%02X idx=0x%02X 0xFE busy after %u retries",
757+
frameLen > 0 ? frame[0] : 0, frameLen > 1 ? frame[1] : 0, attempt);
758+
return Max32664Status::DeviceBusy;
759+
}
760+
actualDelay = (actualDelay < kBusyMaxDelayMs / 2)
761+
? uint16_t(actualDelay * 2u)
762+
: kBusyMaxDelayMs;
763+
}
764+
return Max32664Status::DeviceBusy;
765+
}
766+
631767
Max32664Status Max32664::writeCmd(uint8_t fam, uint8_t idx, uint16_t cmdDelayMs)
632768
{
633-
_bus.beginTransmission(MAX32664_I2C_ADDR);
634-
_bus.write(fam);
635-
_bus.write(idx);
636-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
637-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
638-
if (_bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(1)) != 1)
639-
return Max32664Status::HostCommError;
640-
uint8_t st = uint8_t(_bus.read());
641-
return st == 0x00 ? Max32664Status::Ok : Max32664Status(st);
769+
uint8_t frame[2] = {fam, idx};
770+
return writeImpl(frame, 2, cmdDelayMs);
642771
}
643772

644773
Max32664Status Max32664::writeCmd(uint8_t fam, uint8_t idx, uint8_t v0, uint16_t cmdDelayMs)
645774
{
646-
_bus.beginTransmission(MAX32664_I2C_ADDR);
647-
_bus.write(fam);
648-
_bus.write(idx);
649-
_bus.write(v0);
650-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
651-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
652-
if (_bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(1)) != 1)
653-
return Max32664Status::HostCommError;
654-
uint8_t st = uint8_t(_bus.read());
655-
return st == 0x00 ? Max32664Status::Ok : Max32664Status(st);
775+
uint8_t frame[3] = {fam, idx, v0};
776+
return writeImpl(frame, 3, cmdDelayMs);
656777
}
657778

658779
Max32664Status Max32664::writeCmd(uint8_t fam, uint8_t idx, uint8_t v0, uint8_t v1,
659780
uint16_t cmdDelayMs)
660781
{
661-
_bus.beginTransmission(MAX32664_I2C_ADDR);
662-
_bus.write(fam);
663-
_bus.write(idx);
664-
_bus.write(v0);
665-
_bus.write(v1);
666-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
667-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
668-
if (_bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(1)) != 1)
669-
return Max32664Status::HostCommError;
670-
uint8_t st = uint8_t(_bus.read());
671-
return st == 0x00 ? Max32664Status::Ok : Max32664Status(st);
782+
uint8_t frame[4] = {fam, idx, v0, v1};
783+
return writeImpl(frame, 4, cmdDelayMs);
672784
}
673785

674786
Max32664Status Max32664::writeCmd3(uint8_t fam, uint8_t idx,
675787
uint8_t v0, uint8_t v1, uint8_t v2,
676788
uint16_t cmdDelayMs)
677789
{
678-
_bus.beginTransmission(MAX32664_I2C_ADDR);
679-
_bus.write(fam);
680-
_bus.write(idx);
681-
_bus.write(v0);
682-
_bus.write(v1);
683-
_bus.write(v2);
684-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
685-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
686-
if (_bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(1)) != 1)
687-
return Max32664Status::HostCommError;
688-
uint8_t st = uint8_t(_bus.read());
689-
return st == 0x00 ? Max32664Status::Ok : Max32664Status(st);
790+
uint8_t frame[5] = {fam, idx, v0, v1, v2};
791+
return writeImpl(frame, 5, cmdDelayMs);
690792
}
691793

692794
Max32664Status Max32664::writeCmd(uint8_t fam, uint8_t idx,
693795
const uint8_t *payload, size_t len,
694796
uint16_t cmdDelayMs)
695797
{
696-
_bus.beginTransmission(MAX32664_I2C_ADDR);
697-
_bus.write(fam);
698-
_bus.write(idx);
699-
for (size_t i = 0; i < len; ++i) _bus.write(payload[i]);
700-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
701-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
702-
if (_bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(1)) != 1)
703-
return Max32664Status::HostCommError;
704-
uint8_t st = uint8_t(_bus.read());
705-
return st == 0x00 ? Max32664Status::Ok : Max32664Status(st);
798+
// Inline assemble: max payload across callers is 12 bytes (SpO2 coeffs).
799+
uint8_t frame[16];
800+
if (2 + len > sizeof(frame)) return Max32664Status::BufferTooSmall;
801+
frame[0] = fam;
802+
frame[1] = idx;
803+
for (size_t i = 0; i < len; ++i) frame[2 + i] = payload[i];
804+
return writeImpl(frame, 2 + len, cmdDelayMs);
706805
}
707806

708807
Max32664Status Max32664::writeCmd(uint8_t fam, uint8_t idx, uint8_t sub,
709808
const uint8_t *payload, size_t len,
710809
uint16_t cmdDelayMs)
711810
{
712-
_bus.beginTransmission(MAX32664_I2C_ADDR);
713-
_bus.write(fam);
714-
_bus.write(idx);
715-
_bus.write(sub);
716-
for (size_t i = 0; i < len; ++i) _bus.write(payload[i]);
717-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
718-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
719-
if (_bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(1)) != 1)
720-
return Max32664Status::HostCommError;
721-
uint8_t st = uint8_t(_bus.read());
722-
return st == 0x00 ? Max32664Status::Ok : Max32664Status(st);
723-
}
724-
725-
// Internal helper for the read primitives below: pulls (1 + len) bytes off
726-
// the bus across as many requestFrom() calls as the platform's Wire buffer
727-
// requires, validates the leading status byte, and writes the payload bytes
728-
// to `out`.
729-
namespace
730-
{
731-
Max32664Status drainResponse(TwoWire &bus, uint8_t *out, size_t len)
732-
{
733-
size_t remaining = len;
734-
uint8_t *cursor = out;
735-
bool firstChunk = true;
736-
737-
while (remaining > 0 || firstChunk)
738-
{
739-
uint16_t want = uint16_t((firstChunk ? 1u : 0u) + remaining);
740-
if (want > kReadChunkBytes) want = kReadChunkBytes;
741-
uint8_t got = bus.requestFrom(uint8_t(MAX32664_I2C_ADDR), uint8_t(want));
742-
if (got != want) return Max32664Status::HostCommError;
743-
744-
if (firstChunk)
745-
{
746-
uint8_t st = uint8_t(bus.read());
747-
--got;
748-
firstChunk = false;
749-
if (st != 0x00)
750-
{
751-
while (got--) bus.read();
752-
return Max32664Status(st);
753-
}
754-
}
755-
while (got-- && remaining > 0)
756-
{
757-
*cursor++ = uint8_t(bus.read());
758-
--remaining;
759-
}
760-
if (remaining == 0) break;
761-
}
762-
return Max32664Status::Ok;
811+
uint8_t frame[16];
812+
if (3 + len > sizeof(frame)) return Max32664Status::BufferTooSmall;
813+
frame[0] = fam;
814+
frame[1] = idx;
815+
frame[2] = sub;
816+
for (size_t i = 0; i < len; ++i) frame[3 + i] = payload[i];
817+
return writeImpl(frame, 3 + len, cmdDelayMs);
763818
}
764-
} // namespace
765819

766820
Max32664Status Max32664::readBytes(uint8_t fam, uint8_t idx,
767821
uint8_t *out, size_t len, uint16_t cmdDelayMs)
768822
{
769-
_bus.beginTransmission(MAX32664_I2C_ADDR);
770-
_bus.write(fam);
771-
_bus.write(idx);
772-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
773-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
774-
return drainResponse(_bus, out, len);
823+
uint8_t frame[2] = {fam, idx};
824+
return readImpl(frame, 2, out, len, cmdDelayMs);
775825
}
776826

777827
Max32664Status Max32664::readBytes(uint8_t fam, uint8_t idx, uint8_t sub,
778828
uint8_t *out, size_t len, uint16_t cmdDelayMs)
779829
{
780-
_bus.beginTransmission(MAX32664_I2C_ADDR);
781-
_bus.write(fam);
782-
_bus.write(idx);
783-
_bus.write(sub);
784-
if (_bus.endTransmission() != 0) return Max32664Status::HostCommError;
785-
delay(cmdDelayMs ? cmdDelayMs : kDefaultCmdDelayMs);
786-
return drainResponse(_bus, out, len);
830+
uint8_t frame[3] = {fam, idx, sub};
831+
return readImpl(frame, 3, out, len, cmdDelayMs);
787832
}
788833

789834
/////////////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)