PULSE SENSOR AMPED
ARDUINO CODE V1.2 WALKTHROUGH
Before we get into the line-by-line stuff, there's some things to know about the signal we are going to process, and the known techniques of doing it. No sense in reinventing the algorithm!
The Pulse Sensor that we make is essentially a photoplethysmograph, which is a well known medical device used for non-invasive heart rate monitoring. Sometimes, photoplethysmographs measure blood-oxygen levels (SpO2), sometimes they don't. The heart pulse signal that comes out of a photoplethysmograph is an analog fluctuation in voltage, and it has a predictable wave shape as shown in figure 1. The depiction of the pulse wave is called a photoplethysmogram, or PPG. Our latest hardware version, Pulse Sensor Amped, amplifies the raw signal of the previous Pulse Sensor, and normalizes the pulse wave around V/2 (midpoint in voltage). Pulse Sensor Amped responds to relative changes in light intensity. If the amount of light incident on the sensor remains constant, the signal value will remain at (or close to) 512 (midpoint of ADC range). More light and the signal goes up. Less light, the opposite. Light from the green LED that is reflected back to the sensor changes during each pulse.
figure 1
Our goal is to find successive moments of instantaneous heart beat and measure the time between, called the Inter Beat Interval (IBI). By following the predictable shape and pattern of the PPG wave, we are able to do just that.
Now, we're not heart researchers, but we play them on this blog. We're basing this page on Other People's Research that seem reasonable to us (references below). When the heart pumps blood through the body, with every beat there is a pulse wave (kind of like a shock wave) that travels along all arteries to the very extremities of capillary tissue where the Pulse Sensor is attached. Actual blood circulates in the body much slower than the pulse wave travels. Let's follow events as they progress from point 'T' on the PPG below. A rapid upward rise in signal value occurs as the pulse wave passes under the sensor, then the signal falls back down toward the normal point. Sometimes, the dicroic notch (downward spike) is more pronounced than others, but generally the signal settles down to background noise before the next pulse wave washes through. Since the wave is repeating and predictable, we could choose almost any recognizable feature as a reference point, say the peak, and measure the heart rate by doing math on the time between each peak. This, however, can run into false readings from the dicroic notch, if present, and may be susceptible to inaccuracy from baseline noise as well.There are other good reasons not to base the beat-finding algorithm on arbitrary wave phenomena. Ideally, we want to find the instantaneous moment of the heart beat. This is important for accurate BPM calculation, Heart Rate Variability (HRV) studies, and Pulse Transit Time (PTT) measurement. And it is a worthy challenge! People Smarter Than Us (note1) argue that the instantaneous moment of heart beat happens at some point during that fast upward rise in the PPG waveform.
Some heart researchers say it's when the signal gets to 25% of the amplitude, some say when it's 50% of the amplitude, and some say it's the point when the slope is steepest during the upward rise event. This version 1.1 of Pulse Sensor code is designed to measure the IBI by timing between moments when the signal crosses 50% of the wave amplitude during that fast upward rise. The BPM is derived every beat from an average of the previous 10 IBI times. Here's how we do it!
First off, it's important to have a regular sample rate with high enough resolution to get reliable measurement of the timing between each beat. To do this, we set up Timer2, an 8 bit hardware timer on the ATmega328 (UNO), so that it throws an interrupt every other millisecond. That gives us a sample rate of 500Hz, and beat-to-beat timing resolution of 2mS (note2). This will disable PWM output on pin 3 and 11. Also, it will disable the tone() command. This code works with Arduino UNO or Arduino PRO or Arduino Pro Mini 5V or any Arduino running with an ATmega328 and 16MHz clock.
void interruptSetup(){
TCCR2A = 0x02;
TCCR2B = 0x06;
OCR2A = 0x7C;
TIMSK2 = 0x02;
sei();
}
The register settings above tell Timer2 to go into CTC mode, and to count up to 124 (0x7C) over and over and over again. A prescaler of 256 is used to get the timing right so that it takes 2 milliseconds to count to 124. An interrupt flag is set every time Timer2 reaches 124, and a special function called an Interrupt Service Routine (ISR) that we wrote is run at the very next possible moment, no matter what the rest of the program is doing. sei() ensures that global interrupts are enabled. Timing is important! If you are using a different Arduino or Arduino compatible device, you will need to change this function.
If you are using a FIO or LillyPad Arduino or Arduino Pro Mini 3V or Arduino SimpleSnap or other Arduino that has ATmega168 or ATmega328 with 8MHz oscillator, change the line TCCR2B = 0x06 to TCCR2B = 0x05.
If you are using Arduino Leonardo or Adafruit's Flora or Arduino Micro or other Arduino that has ATmega32u4 running at 16MHz
void interruptSetup(){
TCCR1A = 0x00;
TCCR1B = 0x0C;
OCR1A = 0x7C;
TIMSK1 = 0x02;
sei();
}
The only other thing you will need is the correct ISR vector in the next step. ATmega32u4 devices use ISR(TIMER1_COMPA_vect)
So, when the Arduino is powered up and running with Pulse Sensor Amped plugged into analog pin 0, it constantly (every 2 mS) reads the sensor value and looks for the heart beat. Here's how that works:
ISR(TIMER2_COMPA_vect){
Signal = analogRead(pulsePin);
sampleCounter += 2;
int N = sampleCounter - lastBeatTime;
This function is called every 2 milliseconds. First thing to do is to take an analog reading of the Pulse Sensor. Next, we increment the variable sampleCounter. The sampleCounter variable is what we use to keep track of time. The variable N will help avoid noise later.
Next, we keep track of the highest and lowest values of the PPG wave, to get an accurate measure of amplitude.
if(Signal < thresh && N > (IBI/5)*3){
if (Signal < T){
T = Signal;
}
}
if(Signal > thresh && Signal > P){
P = Signal;
}
Variable P and T hold peak and trough values, respectively. The thresh variable is initialized at 512 (middle of analog range) and changes during run time to track a point at 50% of amplitude as we will see later. There is a time period of 3/5 IBI that must pass before T gets updated as a way to avoid noise and false readings from the dicroic notch.
Now, lets check and see if we have a pulse.
if (N > 250){
if ( (Signal > thresh) && (Pulse == false) && (N > ((IBI/5)*3) ){
Pulse = true;
digitalWrite(pulsePin,HIGH);
IBI = sampleCounter - lastBeatTime;
lastBeatTime = sampleCounter;
Before we even consider looking for a heart beat, a minimum amount of time has to pass. This helps avoid high frequency noise. 250 millisecond minimum N places an upper limit of 240 BPM. If you expect to have a higher BPM, adjust this accordingly and see a doctor. When the waveform rises past the thresh value, and 3/5 of the last IBI has passed, we have a pulse! Time to set the Pulse flag and turn on the pulsePin LED. (note: if you want to do something else with pin 13, comment out this line, and the later one too). Then we calculate the time since the last beat to get IBI, and update the lastBeatTime.
The next bit is used to make sure we begin with a realistic BPM value on startup.
if(secondBeat){
secondBeat = false;
for(int i=0; i
เซ็นเซอร์หมุนแน่นอน AMPEDฝึกปฏิบัติสืบรหัส V1.2 ก่อนที่เราจะเป็นสิ่งที่ละบรรทัด มีบางสิ่งที่ควรทราบเกี่ยวกับสัญญาณที่เราจะไปกระบวนการ และเทคนิคต่าง ๆ รู้จักการทำเช่นนั้น ไม่มีความรู้สึกในการฟื้นฟูอัลกอริทึมเซ็นเซอร์หมุนที่เราทำจะเป็นแบบ photoplethysmograph ซึ่งเป็นอุปกรณ์ทางการแพทย์ที่รู้จักใช้สำหรับตรวจสอบอัตราการเต้นหัวใจไม่รุกราน บางครั้ง photoplethysmographs วัดออกซิเจนเลือดระดับ (SpO2), บางครั้งพวกเขาไม่ สัญญาณชีพจรหัวใจที่มาจาก photoplethysmograph มีการผันผวนแบบแอนะล็อกในแรงดันไฟฟ้า และมีรูปคลื่นได้ดังแสดงในรูปที่ 1 แสดงให้เห็นการหมุนคลื่นที่เรียกว่า photoplethysmogram การ หรือ PPG ของเรารุ่นล่าสุดฮาร์ดแวร์ ชีพจรเซ็นเซอร์แน่นอน Amped กำลังโคจรอยู่ซึ่งสัญญาณดิบของเซ็นเซอร์หมุนก่อนหน้านี้ และ normalizes คลื่นชีพจรรอบ 2 V (จุดกึ่งกลางในแรงดันไฟฟ้า) ชีพจรเซ็นเซอร์แน่นอน Amped ตอบสนองต่อการเปลี่ยนแปลงสัมพัทธ์ความเข้มแสง ถ้าจำนวนเหตุการณ์ไฟบนเซนเซอร์ยังคงคง ค่าสัญญาณจะอยู่ที่ (หรือใกล้) 512 (จุดกึ่งกลางของช่วง ADC) เพิ่มเติมไฟและสัญญาณไปขึ้น แสงน้อย ตรงข้ามกัน แสงจาก LED สีเขียวที่กำลังกลับไปเซ็นเซอร์ระหว่างชีพจรแต่ละรูปที่ 1เป้าหมายของเราคือการ หาช่วงเวลาที่ต่อเนื่องของกำลังใจชนะ และวัดระยะเวลาระหว่าง เรียกแบบอินเตอร์ชนะช่วง (กรรมาธิการ) ตามรูปร่างได้และรูปแบบของคลื่น PPG เราจะสามารถทำสิ่งเหล่าตอนนี้ เราไม่ได้นักวิจัยหัวใจ แต่เราเล่นในบล็อกนี้ เราจะอ้างอิงหน้านี้ในงานวิจัยของคนอื่นที่ดูเหมาะสมกับเรา (อ้างอิงด้านล่าง) เมื่อปั๊มหัวใจเลือดผ่านร่างกาย ชนะทุกมีมีชีพจรคลื่น (ชนิดเช่นคลื่นกระแทก) ที่เดินทางไปตามหลอดเลือดแดงทั้งหมดจะกระสับกระส่ายมากของเนื้อเยื่อเส้นเลือดฝอย ที่เซ็นเซอร์หมุนอยู่ จริงเลือดหมุนเวียนอยู่ในร่างกายช้ากว่าเดินทางคลื่นพัลส์ ลองตามเหตุการณ์จะดำเนินการจากจุดที ' บน PPG ด้านล่าง ค่าสัญญาณขึ้นขึ้นอย่างรวดเร็วเกิดเป็นผ่านคลื่นชีพจรภายใต้เซนเซอร์ แล้วสัญญาณตกกลับลงไปยังจุดปกติ บางครั้ง บาก dicroic (ลงชั่ว) จะออกเสียงมากขึ้นกว่าคนอื่น ๆ แต่โดยทั่วไปสัญญาณจับคู่ลงไปเสียงพื้นหลังก่อนคลื่นพัลส์ถัดไปต่อผิวหน้าผ่านการ ซ้ำคลื่น และคาดเดาได้ เราสามารถเลือกทุกคุณลักษณะจำเป็นจุดอ้างอิง การพูด และวัดอัตราการเต้นหัวใจ ด้วยการทำคณิตศาสตร์เวลาระหว่างแต่ละช่วง นี้ อย่างไรก็ตาม สามารถรันเป็นเท็จอ่านจากรอย dicroic ถ้ามี และอาจจะไวต่อการ inaccuracy จากเสียงหลักเช่น มีเหตุผลอื่น ๆ ดีไม่ให้ยึดอัลกอริทึมการค้นหาเต้นตามอำเภอใจคลื่นปรากฏการณ์ ดาว เราต้องการค้นหาขณะนี้กำลังของหัวใจเต้น ซึ่งเป็นสิ่งสำคัญสำหรับคำนวณ BPM ที่ถูกต้อง การศึกษาอัตราการเต้นหัวใจสำหรับความผันผวน (HRV) และวัดชีพจรส่งต่อเวลา (PTT) และความคุ้มค่า คนฉลาดกว่าเรา (note1) โต้แย้งว่า เวลาหัวใจเต้นที่กำลังเกิดขึ้นในบางจุดระหว่างที่เพิ่มขึ้นอย่างรวดเร็วขึ้นในรูปคลื่น PPG นักวิจัยหัวใจบางคนบอกว่า เป็นเมื่อสัญญาณได้รับ 25% ของคลื่น บางคนกล่าวว่า เมื่อมันเป็น 50% ของคลื่น และบางคนบอกว่า เป็นจุดเมื่อลาด steepest ระหว่างเหตุการณ์ขึ้นขึ้น รุ่นนี้ 1.1 เซ็นเซอร์หมุนรหัสถูกออกแบบมาวัดกรรมาธิการตามเวลาระหว่างช่วงเวลาที่เมื่อสัญญาณตัด 50% ของคลื่นคลื่นในช่วงที่เพิ่มขึ้นอย่างรวดเร็วขึ้น BPM ได้รับมาทุกจังหวะจากเฉลี่ยของกรรมาธิการก่อนหน้า 10 ครั้ง นี่คือวิธีที่เราทำก่อน ปิด มันจะต้องมีอัตราอย่างปกติ มีความละเอียดสูงพอที่จะวัดระยะเวลาระหว่างแต่ละจังหวะที่เชื่อถือได้ การทำเช่นนี้ เราตั้ง Timer2 จับเวลาฮาร์ดแวร์ 8 บิตบนนี้ ATmega328 (อูโน่), เพื่อจะขว้างการขัดจังหวะทุกมิลลิวินาทีอื่น ๆ ที่ทำให้เราตัวอย่างอัตรา 500Hz และแก้ไขเวลาชนะชนะ 2mS (note2) นี้จะปิดการใช้เอาต์พุต PWM ใน pin 3 และ 11 ยัง มันจะปิดใช้งานคำสั่ง tone() รหัสนี้ทำงานกับ 5V UNO สืบ หรือสืบ PRO หรือ Mini Pro สืบหรือสืบใด ๆ ใช้ ATmega328 และนาฬิกา 16MHz {ยกเลิก interruptSetup() TCCR2A = 0X02 TCCR2B = 0X06 OCR2A = 0X7C TIMSK2 = 0X02 sei()}The register settings above tell Timer2 to go into CTC mode, and to count up to 124 (0x7C) over and over and over again. A prescaler of 256 is used to get the timing right so that it takes 2 milliseconds to count to 124. An interrupt flag is set every time Timer2 reaches 124, and a special function called an Interrupt Service Routine (ISR) that we wrote is run at the very next possible moment, no matter what the rest of the program is doing. sei() ensures that global interrupts are enabled. Timing is important! If you are using a different Arduino or Arduino compatible device, you will need to change this function.If you are using a FIO or LillyPad Arduino or Arduino Pro Mini 3V or Arduino SimpleSnap or other Arduino that has ATmega168 or ATmega328 with 8MHz oscillator, change the line TCCR2B = 0x06 to TCCR2B = 0x05.If you are using Arduino Leonardo or Adafruit's Flora or Arduino Micro or other Arduino that has ATmega32u4 running at 16MHz void interruptSetup(){ TCCR1A = 0x00; TCCR1B = 0x0C; OCR1A = 0x7C; TIMSK1 = 0x02; sei(); } The only other thing you will need is the correct ISR vector in the next step. ATmega32u4 devices use ISR(TIMER1_COMPA_vect)So, when the Arduino is powered up and running with Pulse Sensor Amped plugged into analog pin 0, it constantly (every 2 mS) reads the sensor value and looks for the heart beat. Here's how that works: ISR(TIMER2_COMPA_vect){ Signal = analogRead(pulsePin); sampleCounter += 2; int N = sampleCounter - lastBeatTime; This function is called every 2 milliseconds. First thing to do is to take an analog reading of the Pulse Sensor. Next, we increment the variable sampleCounter. The sampleCounter variable is what we use to keep track of time. The variable N will help avoid noise later.Next, we keep track of the highest and lowest values of the PPG wave, to get an accurate measure of amplitude. if(Signal < thresh && N > (IBI/5)*3){ if (Signal < T){ T = Signal; } } if(Signal > thresh && Signal > P){ P = Signal; }Variable P and T hold peak and trough values, respectively. The thresh variable is initialized at 512 (middle of analog range) and changes during run time to track a point at 50% of amplitude as we will see later. There is a time period of 3/5 IBI that must pass before T gets updated as a way to avoid noise and false readings from the dicroic notch. Now, lets check and see if we have a pulse. if (N > 250){if ( (Signal > thresh) && (Pulse == false) && (N > ((IBI/5)*3) ){ Pulse = true; digitalWrite(pulsePin,HIGH); IBI = sampleCounter - lastBeatTime; lastBeatTime = sampleCounter; Before we even consider looking for a heart beat, a minimum amount of time has to pass. This helps avoid high frequency noise. 250 millisecond minimum N places an upper limit of 240 BPM. If you expect to have a higher BPM, adjust this accordingly and see a doctor. When the waveform rises past the thresh value, and 3/5 of the last IBI has passed, we have a pulse! Time to set the Pulse flag and turn on the pulsePin LED. (note: if you want to do something else with pin 13, comment out this line, and the later one too). Then we calculate the time since the last beat to get IBI, and update the lastBeatTime.
The next bit is used to make sure we begin with a realistic BPM value on startup.
if(secondBeat){
secondBeat = false;
for(int i=0; i<=9; i++){
rate[i] = IBI;
}
}
if(firstBeat){
firstBeat = false;
secondBeat = true;
sei():
return;
}
The boolean firstBeat is initialized as true and secondBeat is initialized as false on start up, so the very first time we find a beat and get this far in the ISR, we get kicked out by the return; in the firstBeat conditional. That will end up throwing the first IBI reading away, cause it's lousy. The second time through, we can trust (more or less) the IBI, and use it to seed the rate[] array in order to start with a more accurate BPM. The BPM is derived from an average of the last 10 IBI values, hence the need to seed.
Lets calculate BPM!
word runningTotal = 0;
for(int i=0; i<=8; i++){
rate[i] = rate[i+1];
runningTotal += rate[i];
}
rate[9] = IBI;
runningTotal += rate[9];
runningTotal /= 10;
BPM = 60000/runningTotal;
QS = true;
}
}
First, we grab a large variable, runningTotal, to collect the IBIs, then the contents of rate[] are shifted over and added to runnungTotal. The oldest IBI (11 beats ago) falls out of position 0, and the fresh IBI gets put into position 9. Then it's a simple process to average the array and calculate BPM. Last thing to do is to set the QS flag (short for Quantified Self, awesome kickstarter supporters!) so the rest of the program knows we have found the beat. That's it for the things to do when we find the beat.
There's a couple of other loose ends that need tying off before we're done, like finding the not-beat.
if (Signal < thresh && Pulse == true){
digitalWrite(13,LOW);
Pulse = false;
amp = P - T;
thresh = amp/2 + T;
P = thresh;
การแปล กรุณารอสักครู่..
