Skip to content

Commit 49c4b00

Browse files
committed
Isochronous PPG sampling
1 parent b897c0a commit 49c4b00

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

src/heartratetask/HeartRateTask.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
#include "heartratetask/HeartRateTask.h"
22
#include <drivers/Hrs3300.h>
33
#include <components/heartrate/HeartRateController.h>
4+
#include <limits>
45

56
using namespace Pinetime::Applications;
67

78
namespace {
89
constexpr TickType_t backgroundMeasurementTimeLimit = 30 * configTICK_RATE_HZ;
10+
11+
// dividend + (divisor / 2) must be less than the max T value
12+
template <std::unsigned_integral T>
13+
constexpr T RoundedDiv(T dividend, T divisor) {
14+
return (dividend + (divisor / static_cast<T>(2))) / divisor;
15+
}
916
}
1017

1118
std::optional<TickType_t> HeartRateTask::BackgroundMeasurementInterval() const {
@@ -24,9 +31,40 @@ bool HeartRateTask::BackgroundMeasurementNeeded() const {
2431
return xTaskGetTickCount() - lastMeasurementTime >= backgroundPeriod.value();
2532
};
2633

27-
TickType_t HeartRateTask::CurrentTaskDelay() const {
34+
TickType_t HeartRateTask::CurrentTaskDelay() {
2835
auto backgroundPeriod = BackgroundMeasurementInterval();
2936
TickType_t currentTime = xTaskGetTickCount();
37+
auto CalculateSleepTicks = [&]() {
38+
TickType_t elapsed = currentTime - measurementStartTime;
39+
40+
// Target system tick is the the elapsed sensor ticks multiplied by the sensor tick duration (i.e. the elapsed time)
41+
// multiplied by the system tick rate
42+
// Since the sensor tick duration is a whole number of milliseconds, we compute in milliseconds and then divide by 1000
43+
// To avoid the number of milliseconds overflowing a u32, we take a factor of 2 out of the divisor and dividend
44+
// (1024 / 2) * 65536 * 100 = 3355443200 which is less than 2^32
45+
46+
// Guard against future tick rate changes
47+
static_assert((configTICK_RATE_HZ / 2ULL) * (std::numeric_limits<decltype(count)>::max() + 1ULL) *
48+
static_cast<uint64_t>((Pinetime::Controllers::Ppg::deltaTms)) <
49+
std::numeric_limits<uint32_t>::max(),
50+
"Overflow");
51+
TickType_t elapsedTarget = RoundedDiv(static_cast<uint32_t>(configTICK_RATE_HZ / 2) * (static_cast<uint32_t>(count) + 1U) *
52+
static_cast<uint32_t>((Pinetime::Controllers::Ppg::deltaTms)),
53+
static_cast<uint32_t>(1000 / 2));
54+
55+
// On count overflow, reset both count and start time
56+
// Count is 16bit to avoid overflow in elapsedTarget
57+
// Count overflows every 100ms * u16 max = ~2 hours, much more often than the tick count (~48 days)
58+
// So no need to check for tick count overflow
59+
if (count == std::numeric_limits<decltype(count)>::max()) {
60+
count = 0;
61+
measurementStartTime = currentTime;
62+
}
63+
if (elapsedTarget > elapsed) {
64+
return elapsedTarget - elapsed;
65+
}
66+
return static_cast<TickType_t>(0);
67+
};
3068
switch (state) {
3169
case States::Disabled:
3270
return portMAX_DELAY;
@@ -43,8 +81,11 @@ TickType_t HeartRateTask::CurrentTaskDelay() const {
4381
return 0;
4482
case States::BackgroundMeasuring:
4583
case States::ForegroundMeasuring:
46-
return Pinetime::Controllers::Ppg::deltaTms;
84+
return CalculateSleepTicks();
4785
}
86+
// Needed to keep dumb compiler happy, this is unreachable
87+
// Any new additions to States will cause the above switch statement not to compile, so this is safe
88+
return portMAX_DELAY;
4889
}
4990

5091
HeartRateTask::HeartRateTask(Drivers::Hrs3300& heartRateSensor,
@@ -57,7 +98,7 @@ void HeartRateTask::Start() {
5798
messageQueue = xQueueCreate(10, 1);
5899
controller.SetHeartRateTask(this);
59100

60-
if (pdPASS != xTaskCreate(HeartRateTask::Process, "Heartrate", 500, this, 0, &taskHandle)) {
101+
if (pdPASS != xTaskCreate(HeartRateTask::Process, "Heartrate", 500, this, 1, &taskHandle)) {
61102
APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
62103
}
63104
}
@@ -130,6 +171,7 @@ void HeartRateTask::Work() {
130171

131172
if (state == States::ForegroundMeasuring || state == States::BackgroundMeasuring) {
132173
HandleSensorData();
174+
count++;
133175
}
134176
}
135177
}
@@ -145,6 +187,7 @@ void HeartRateTask::StartMeasurement() {
145187
ppg.Reset(true);
146188
vTaskDelay(100);
147189
measurementSucceeded = false;
190+
count = 0;
148191
measurementStartTime = xTaskGetTickCount();
149192
}
150193

src/heartratetask/HeartRateTask.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ namespace Pinetime {
3737

3838
[[nodiscard]] bool BackgroundMeasurementNeeded() const;
3939
[[nodiscard]] std::optional<TickType_t> BackgroundMeasurementInterval() const;
40-
[[nodiscard]] TickType_t CurrentTaskDelay() const;
40+
TickType_t CurrentTaskDelay();
4141

4242
TaskHandle_t taskHandle;
4343
QueueHandle_t messageQueue;
4444
bool valueCurrentlyShown;
4545
bool measurementSucceeded;
4646
States state = States::Disabled;
47+
uint16_t count;
4748
Drivers::Hrs3300& heartRateSensor;
4849
Controllers::HeartRateController& controller;
4950
Controllers::Settings& settings;

0 commit comments

Comments
 (0)