Skip to content

Commit 1ce6b99

Browse files
committed
PPGv2
1 parent f2814dd commit 1ce6b99

20 files changed

Lines changed: 865 additions & 420 deletions

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,3 @@
44
[submodule "src/libs/littlefs"]
55
path = src/libs/littlefs
66
url = https://github.com/littlefs-project/littlefs.git
7-
[submodule "src/libs/arduinoFFT"]
8-
path = src/libs/arduinoFFT
9-
url = https://github.com/kosme/arduinoFFT.git

src/CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -675,9 +675,9 @@ set(INCLUDE_FILES
675675
heartratetask/HeartRateTask.h
676676
components/heartrate/Ppg.h
677677
components/heartrate/HeartRateController.h
678-
libs/arduinoFFT/src/arduinoFFT.h
679-
libs/arduinoFFT/src/defs.h
680-
libs/arduinoFFT/src/types.h
678+
components/heartrate/FFT.h
679+
components/heartrate/IIR.h
680+
components/heartrate/RLS.h
681681
components/motor/MotorController.h
682682
buttonhandler/ButtonHandler.h
683683
touchhandler/TouchHandler.h

src/components/heartrate/FFT.h

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#include <complex>
2+
#include <array>
3+
#include <bit>
4+
#include <numbers>
5+
6+
namespace Pinetime {
7+
namespace Utility {
8+
// Fast Fourier transform
9+
// Implements in-place N to N point complex-to-complex FFT
10+
// Implements in-place 2N to N point real-to-complex FFT
11+
// Performing these transforms requires some "twiddling" constants to be known
12+
// These constants depend only on the size of the transform
13+
// Since they are expensive to compute, they can only be computed at compile time (consteval)
14+
class FFT {
15+
public:
16+
static consteval std::size_t IntegerLog2(std::size_t n) {
17+
return std::bit_width(n) - 1;
18+
}
19+
20+
template <std::size_t N>
21+
static consteval std::array<std::complex<float>, IntegerLog2(N)> GenComplexTwiddle() {
22+
using namespace std::complex_literals;
23+
24+
std::array<std::complex<float>, IntegerLog2(N)> result;
25+
for (std::size_t i = 0; i < IntegerLog2(N); i++) {
26+
result[i] = exp_consteval(-2.i * std::numbers::pi / static_cast<double>(1 << (i + 1)));
27+
}
28+
return result;
29+
}
30+
31+
template <std::size_t N>
32+
static consteval std::array<std::complex<float>, (N / 4) - 1> GenRealTwiddle() {
33+
using namespace std::complex_literals;
34+
35+
std::array<std::complex<float>, (N / 4) - 1> result;
36+
for (std::size_t i = 0; i < (N / 4) - 1; i++) {
37+
result[i] = exp_consteval(-2.i * std::numbers::pi * static_cast<double>(i + 1) / static_cast<double>(N));
38+
}
39+
return result;
40+
}
41+
42+
template <std::size_t N>
43+
static void ComplexFFT(std::array<std::complex<float>, N>& array, const std::array<std::complex<float>, IntegerLog2(N)>& twiddle) {
44+
// In-place Cooley-Tukey
45+
InplaceBitReverse(array);
46+
for (std::size_t s = 1; s < IntegerLog2(N) + 1; s++) {
47+
std::size_t m = 1 << s;
48+
for (std::size_t k = 0; k < N; k += m) {
49+
std::complex<float> omega = 1.f;
50+
for (std::size_t j = 0; j < m / 2; j++) {
51+
std::complex<float> t = omega * array[k + j + (m / 2)];
52+
std::complex<float> u = array[k + j];
53+
array[k + j] = u + t;
54+
array[k + j + (m / 2)] = u - t;
55+
omega *= twiddle[s - 1];
56+
}
57+
}
58+
}
59+
}
60+
61+
template <std::size_t N>
62+
static void RealFFT(std::array<std::complex<float>, N>& array,
63+
const std::array<std::complex<float>, (N / 2) - 1>& realTwiddle,
64+
const std::array<std::complex<float>, IntegerLog2(N)>& complexTwiddle) {
65+
using namespace std::complex_literals;
66+
67+
// See https://www.robinscheibler.org/2013/02/13/real-fft.html for how this works
68+
FFT::ComplexFFT(array, complexTwiddle);
69+
// Compute DC bin directly (xe/xo simplify)
70+
// Nyquist bin ignored as unneeded
71+
array[0] = array[0].real() + array[0].imag();
72+
// Since computations depend on the inverse of the index (mirrored)
73+
// compute two at once, outside to inside
74+
for (std::size_t index = 1; index < N / 2; index++) {
75+
std::size_t indexInv = N - index;
76+
std::complex<float> xeLo = (array[index] + std::conj(array[indexInv])) / 2.f;
77+
std::complex<float> xoLo = -1.if * ((array[index] - std::conj(array[indexInv])) / 2.f);
78+
std::complex<float> xeHi = (array[indexInv] + std::conj(array[index])) / 2.f;
79+
std::complex<float> xoHi = -1.if * ((array[indexInv] - std::conj(array[index])) / 2.f);
80+
array[index] = xeLo + (xoLo * realTwiddle[index - 1]);
81+
array[indexInv] = xeHi + (xoHi * -std::conj(realTwiddle[index - 1]));
82+
}
83+
// Middle element not computed by above loop
84+
// Since index == indexInv
85+
// the middle simplifies to the conjugate as the twiddle is always -i
86+
std::size_t middle = N / 2;
87+
array[middle] = std::conj(array[middle]);
88+
}
89+
90+
private:
91+
// consteval wrappers of builtins
92+
template <typename _Tp>
93+
static consteval std::complex<_Tp> exp_consteval(const std::complex<_Tp>& __z) {
94+
return polar_consteval<_Tp>(__builtin_exp(__z.real()), __z.imag());
95+
}
96+
97+
template <typename _Tp>
98+
static consteval std::complex<_Tp> polar_consteval(const _Tp& __rho, const _Tp& __theta) {
99+
return std::complex<_Tp>(__rho * __builtin_cos(__theta), __rho * __builtin_sin(__theta));
100+
}
101+
102+
template <class T, std::size_t N>
103+
static void InplaceBitReverse(std::array<T, N>& array) {
104+
// Gold-Rader algorithm
105+
// Faster algorithms exist, but this is sufficient
106+
std::size_t swapTarget = 0;
107+
for (std::size_t index = 0; index < N - 1; index++) {
108+
// Only swap in one direction
109+
// Otherwise entire array gets swapped twice
110+
if (index < swapTarget) {
111+
T temp = array[index];
112+
array[index] = array[swapTarget];
113+
array[swapTarget] = temp;
114+
}
115+
std::size_t kFactor = N / 2;
116+
while (kFactor <= swapTarget) {
117+
swapTarget -= kFactor;
118+
kFactor /= 2;
119+
}
120+
swapTarget += kFactor;
121+
}
122+
}
123+
};
124+
}
125+
}

src/components/heartrate/HeartRateController.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
#include "components/heartrate/HeartRateController.h"
2-
#include <heartratetask/HeartRateTask.h>
3-
#include <systemtask/SystemTask.h>
2+
3+
#include <cstdint>
4+
#include "heartratetask/HeartRateTask.h"
45

56
using namespace Pinetime::Controllers;
67

7-
void HeartRateController::Update(HeartRateController::States newState, uint8_t heartRate) {
8+
void HeartRateController::UpdateState(HeartRateController::States newState) {
89
this->state = newState;
10+
}
11+
12+
void HeartRateController::UpdateHeartRate(uint8_t heartRate) {
913
if (this->heartRate != heartRate) {
1014
this->heartRate = heartRate;
1115
service->OnNewHeartRateValue(heartRate);
@@ -14,14 +18,14 @@ void HeartRateController::Update(HeartRateController::States newState, uint8_t h
1418

1519
void HeartRateController::Enable() {
1620
if (task != nullptr) {
17-
state = States::NotEnoughData;
21+
state = States::Stopped;
1822
task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::Enable);
1923
}
2024
}
2125

2226
void HeartRateController::Disable() {
2327
if (task != nullptr) {
24-
state = States::Stopped;
28+
state = States::Disabled;
2529
task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::Disable);
2630
}
2731
}

src/components/heartrate/HeartRateController.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ namespace Pinetime {
1515
namespace Controllers {
1616
class HeartRateController {
1717
public:
18-
enum class States : uint8_t { Stopped, NotEnoughData, NoTouch, Running };
18+
enum class States : uint8_t { Disabled, Stopped, NotEnoughData, Searching, Ready, NoTouch };
1919

2020
HeartRateController() = default;
2121
void Enable();
2222
void Disable();
23-
void Update(States newState, uint8_t heartRate);
23+
void UpdateState(States newState);
24+
void UpdateHeartRate(uint8_t heartRate);
2425

2526
void SetHeartRateTask(Applications::HeartRateTask* task);
2627

@@ -36,7 +37,7 @@ namespace Pinetime {
3637

3738
private:
3839
Applications::HeartRateTask* task = nullptr;
39-
States state = States::Stopped;
40+
States state = States::Disabled;
4041
uint8_t heartRate = 0;
4142
Pinetime::Controllers::HeartRateService* service = nullptr;
4243
};

src/components/heartrate/IIR.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <array>
2+
3+
namespace Pinetime {
4+
namespace Utility {
5+
struct SOSCoeffs {
6+
float b0;
7+
float b1;
8+
float b2;
9+
float a1;
10+
float a2;
11+
};
12+
13+
struct FilterState {
14+
float s1;
15+
float s2;
16+
};
17+
18+
// Infinite impulse response digital filter
19+
// Implemented as cascaded second order sections (SOS)
20+
template <std::size_t NSections>
21+
class IIRFilter {
22+
public:
23+
IIRFilter(const std::array<SOSCoeffs, NSections>& coeffs, const std::array<FilterState, NSections>& zi) : coeffs {coeffs}, zi {zi} {};
24+
25+
float FilterStep(float input) {
26+
for (std::size_t i = 0; i < NSections; i++) {
27+
// Transposed form II
28+
float output = (coeffs[i].b0 * input) + filterStates[i].s1;
29+
filterStates[i].s1 = ((-coeffs[i].a1) * output) + (coeffs[i].b1 * input) + filterStates[i].s2;
30+
filterStates[i].s2 = ((-coeffs[i].a2) * output) + (coeffs[i].b2 * input);
31+
input = output;
32+
}
33+
return input;
34+
}
35+
36+
void Prime(float value) {
37+
for (std::size_t i = 0; i < NSections; i++) {
38+
filterStates[i].s1 = zi[i].s1 * value;
39+
filterStates[i].s2 = zi[i].s2 * value;
40+
}
41+
}
42+
43+
private:
44+
std::array<FilterState, NSections> filterStates;
45+
const std::array<SOSCoeffs, NSections>& coeffs;
46+
const std::array<FilterState, NSections>& zi;
47+
};
48+
}
49+
}

0 commit comments

Comments
 (0)