Hartslag Meten Met De KY-039 Arduino Sensor
Onderdelen en benodigdheden
| Onderdeel | Aantal |
|---|---|
| Arduino UNO | 1 |
| KY-039 hartslagsensor | 1 |
| Jumper wires (standaard) | 1 |
Over dit project
In de set van 37 sensoren voor Arduino zit een hartslagsensor. De naam belooft te veel. Mensen denken dat het een digitaal getal via I2C of dergelijke levert, een getal dat de hartslag aangeeft. Wat de sensor geeft is puur een "analoge" waarde van 0 tot 1023, die aangeeft hoeveel infrarood licht de lichtsensor ontvangt, of eigenlijk hoeveel er de lichtsensor beschaduwt. Hoe hogere waarde, hoe minder infrarood licht.
Kortom: plaats je vinger tussen de IR-led en de lichttransistor van de sensor. Je hartslag doet de bloedvaten in je vinger uitzetten, wat het IR-licht filtert. Dit creëert een pulserend signaal.
In dit project beschrijf ik hoe dit signaal omgezet wordt in een hartslag zoals 66 BPM (slagen per minuut).
De eenvoudige stappen
Als je de waarden van de KY-039 sensor gewoon uitleest en plot, krijg je zoiets als dit:
De waarden zijn gehele getallen. Niet heel nauwkeurig. In plaats daarvan bereken je een gemiddelde van een aantal metingen en plot dat. Je krijgt dit:
Hier zie je al de hartslag. Bepaal het tijdsverschil tussen elke significante piek. Hieruit kun je de hartslag in BPM berekenen.
(De kleine zigzag in de afbeelding hierboven komt door kunstlicht, 50 Hz, nog een uitdaging waar rekening mee gehouden moet worden.)
Stappen uitgelegd
Hier is simpele code om gewoon wat je van de KY-039 sensor leest uit te voeren:
// Pulse Monitor Test Script
int sensorPin = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
while(1) {
Serial.print(analogRead(sensorPin));
Serial.print('\n');
}
}
Wat je kunt krijgen is zoiets als dit:
Omdat dit een afbeelding is van de seriële monitor die de seriële output van je Arduino leest op 9600 baud, wordt het hele proces getimed door de Serial.print() functie, wat een snelheid dicteert voor het lezen en plotten van waarden. Toch is de curve erg scherpbollig, want hij varieert tussen 360 en 383 en heeft alleen gehele waarden.
Vergladden (Smoothing)
Om een gladder resultaat te krijgen, neem je een gemiddelde van de laatste 20 metingen. Hier is hoe ik dat doe. Ik definieer een constant voor het aantal metingen:
#define samp_siz 20
Dan heb ik een array die dat aantal metingen bijhoudt:
int reads[samp_siz];
Bij elke nieuwe meting trek ik de oudste meting van de som af en tel de nieuwste erbij op. In de array vervang ik de oudste meting door de nieuwste.
reader = analogRead(sensorPin); // lees sensor
sum -= reads[ptr]; // trek oudste meting van som af
sum += reader; // tel nieuwste meting op bij som
reads[ptr] = reader; // bewaar nieuwste meting in array
last = float(sum) / samp_siz; // bereken gemiddelde
ptr++; // update array-index
ptr %= samp_siz; // herstart bij 0 wanneer nodig
Met een array-grootte van 20 en een baudrate van 9600 in de seriële monitor, krijg ik een plot als deze:
Hier zie je de echte hartslagen als steile stijgende curves. Maar je ziet ook een kleine zigzag. De kleinere zigzag komt van mijn keukenlamp, drie LED-lampen die de kamer verlichten. Het netspanning in mijn huis is 240V, 50Hz AC. Dus 50 keer per seconde is er een piek in lichtintensiteit, blijkbaar ook in het IR-bereik. Ik wil die 50Hz ruis wegvlakken. Dat zou werken als ik waardes van de sensor uitlees gedurende een periode van 20ms en het gemiddelde neem. Laten we kijken...
n = 0;
start = millis();
reader = 0.;
do {
reader += analogRead(sensorPin); // lees en tel waarden op
n++;
now = millis();
} while (now < start + 20); // ...tot 20ms verstreken zijn
reader /= n; // en neem het gemiddelde van de waarden
Met dit stukje code neem ik de sensor-metingen in 20ms blokken, wat de 50Hz flitserende van kunstlicht wegvlakt. Als je in een land met 60Hz woont, gebruik dan 16,67ms blokken. Of 16667µs.
Omdat ik de curve al in 20ms secties gladstreep, heb ik de array die ik eerder gebruikte eigenlijk niet meer nodig, maar omdat hij er is en makkelijk te resizen is, laat ik hem staan. En een array-grootte van 5 lijkt de laatste irritante ruis weg te vangen. Dit is wat ik nu heb:
Het laatste wat ik moet doen is een herkenbaar deel in het herhalende patroon vinden. Omdat de stijgende helling regelmatiger is, kies ik daarvoor. Let op hoe de y-waarden in alle grafieken heel anders zijn. Ik kan niet zomaar op absolute waarden vertrouwen. Ik kan alleen vertrouwen op de stijging en daling van de curve. Wiskundigen zouden praten over de afgeleide. Ik ben tevreden als ik n opeenvolgende stijgende waarden vind, waar n een handige instelbare waarde is. Ik begin met 5. Hiervoor heb ik de constant rise_threshold gedefinieerd in de code. Als ik 5 opeenvolgende stijgende waarden vind, weet ik dat ik aan de bodem van de curve ben, op weg omhoog. Ik noteer de tijd. Ik wacht op een dalende curve, dan wacht ik op de volgende 5 stijgende waarden en noteer de tijd. Dan print ik de corresponderende BPM.
Ik deed een test en telde hoeveel opeenvolgende stijgende waarden er in de curve zijn en vond uit dat er tussen 10 en 15 zaten. Dus als ik tot 5 tel, weet ik zeker dat ik de start van de hartslag gevonden heb.
Omdat ik alleen na elke hartslag print, is er niet veel printen. Er is meer tijd voor het lezen van de sensor. Ik zou meer hoogfrequente ruis kunnen vangen, die ik niet zie omdat de plotter niet aanstaat. Laten we zien hoe het werkt.
De finale code
#define samp_siz 4
#define rise_threshold 5
// Pulse Monitor Test Script
int sensorPin = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
float reads[samp_siz], sum;
long int now, ptr;
float last, reader, start;
float first, second, third, before, print_value;
bool rising;
int rise_count;
int n;
long int last_beat;
for (int i = 0; i < samp_siz; i++) reads[i] = 0;
sum = 0;
ptr = 0;
while(1) {
// Bereken gemiddelde van sensor gedurende 20ms periode
// (dit elimineert 50Hz ruis van elektrisch licht)
n = 0;
start = millis();
reader = 0.;
do {
reader += analogRead(sensorPin);
n++;
now = millis();
} while (now < start + 20);
reader /= n; // we hebben een gemiddelde
// Voeg nieuwste meting toe aan array
// en trek oudste meting van array af
// om som van laatste metingen te behouden
sum -= reads[ptr];
sum += reader;
reads[ptr] = reader;
last = sum / samp_siz; // last bevat nu gemiddelde van array
// Controleer op stijgende curve (= hartslag)
if (last > before) {
rise_count++;
if (!rising && rise_count > rise_threshold) {
// OK, we hebben een stijgende curve gedetecteerd, wat een hartslag impliceert.
// Noteer tijd sinds vorige slag, houd vorige twee tijden bij (first, second, third)
// voor een gewogen gemiddelde.
// De rising-flag voorkomt dat we dezelfde stijging vaker detecteren.
rising = true;
first = millis() - last_beat;
last_beat = millis();
// Bereken gewogen gemiddelde van hartslagfrequentie
// volgens de laatste drie slagen
print_value = 60000. / (0.4 * first + 0.3 * second + 0.3 * third);
Serial.print(print_value);
Serial.print('\n');
third = second;
second = first;
}
} else {
// OK, de curve daalt
rising = false;
rise_count = 0;
}
before = last;
ptr++;
ptr %= samp_siz;
}
}
Het werkt prima. Hier is een video.
Let op dat de kleine RX-led op de Arduino knippert in sync met mijn hart. Omdat bij een hartslag de frequentie berekend en naar de seriële poort geprint wordt, knippert de led. Als de vinger een beetje beweegt, zijn er foutieve metingen.
Verdere ontwikkeling
Op dit moment wordt de geprintte frequentie berekend op basis van de laatste drie slagen. Maar het zou passender kunnen zijn om het te berekenen over een periode van 15 seconden. Ik zou 15 opeenvolgende frequentie-waarden kunnen opslaan, het gemiddelde berekenen, dan de vijf waarden die het verst van het gemiddelde afwijken weglaten en een nieuw gemiddelde berekenen. Dit zou een betrouwbare en stabiele meting van de hartslag opleveren.
Ik heb de sensor alleen getest op mijn vrouw en op mezelf. Elke stap in het verbeteren van het signaal heb ik gebaseerd op de vorige metingen. Iemand anders zou een ander soort hartslag kunnen hebben wat een anders gevormde curve oplevert, wat een andere aanpak vereist voor het vinden van de slag. Misschien is de dalende curve makkelijker te herkennen. Of de piek. En wat gebeurt er als de pols 180-200 BPM is? Het vinden van de stijgende curve zou lastiger kunnen zijn.
Code
Finale versie (C/C++)
Het programma leest de hartslag en print de frequentie in het seriële venster.
#define samp_siz 4
#define rise_threshold 4
// Pulse Monitor Test Script
int sensorPin = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
float reads[samp_siz], sum;
long int now, ptr;
float last, reader, start;
float first, second, third, before, print_value;
bool rising;
int rise_count;
int n;
long int last_beat;
for (int i = 0; i < samp_siz; i++) reads[i] = 0;
sum = 0;
ptr = 0;
while(1) {
// Bereken gemiddelde van sensor gedurende 20ms periode
// (dit elimineert 50Hz ruis van elektrisch licht)
n = 0;
start = millis();
reader = 0.;
do {
reader += analogRead(sensorPin);
n++;
now = millis();
} while (now < start + 20);
reader /= n; // we hebben een gemiddelde
// Voeg nieuwste meting toe aan array
// en trek oudste meting van array af
// om som van laatste metingen te behouden
sum -= reads[ptr];
sum += reader;
reads[ptr] = reader;
last = sum / samp_siz; // last bevat nu gemiddelde van array
// Controleer op stijgende curve (= hartslag)
if (last > before) {
rise_count++;
if (!rising && rise_count > rise_threshold) {
// OK, we hebben een stijgende curve gedetecteerd, wat een hartslag impliceert.
// Noteer tijd sinds vorige slag, houd vorige twee tijden bij (first, second, third)
// voor een gewogen gemiddelde.
// De rising-flag voorkomt dat we dezelfde stijging vaker detecteren.
rising = true;
first = millis() - last_beat;
last_beat = millis();
// Bereken gewogen gemiddelde van hartslagfrequentie
// volgens de laatste drie slagen
print_value = 60000. / (0.4 * first + 0.3 * second + 0.3 * third);
Serial.print(print_value);
Serial.print('\n');
third = second;
second = first;
}
} else {
// OK, de curve daalt
rising = false;
rise_count = 0;
}
before = last;
ptr++;
ptr %= samp_siz;
}
}
Schema's

Productieproces
- Real-time Temperatuurdata Streamen van een ppDAQC Pi-Plate naar InitialState met een Headless Raspberry Pi
- Een Multi-sensor Temperatuur- & Lichtmonitoringssysteem Bouwen met Raspberry Pi & DS18B20
- Van $4 Zonnestandaard tot Slimme Draadloze Sensor-knooppunt: MSP430 + nRF24L01+ voor Tuinmonitoring
- Raspberry Pi-gebaseerde badkamerbezet-sensor met spraak- en SMS-waarschuwingen via Twilio
- Hartslagpulsen uitlezen met Windows 10 IoT Core op Raspberry Pi – Stap-voor-stap gids
- Raspberry Pi Lichtsensor Tutorial – Een LDR aansluiten en lichtniveaus uitlezen met Python
- Digitale Lichtsensor – LED Aansturen met Omgevingslicht via Windows 10 IoT Core
- Remote Hartslagmonitoringssysteem met AWS IoT-waarschuwingen
- Kleursensoren: Hoe Ze Werken & Belangrijkste Toepassingen
- OPT3007 Ultra-dun Omgevingslichtsensor – Precieze Lichtmeting voor Slimme Apparaten
- Omgevingslichtsensoren: Functionaliteit, Ontwerp en Belangrijke Toepassingen