Wio Terminal でパルスオキシメータを制作
前回はWio Terminalの基本機能の一部を堪能させていただきました。
今回は少し応用でWio Terminal を用いてパルスオキシメータを作ってみました。
トレンディーでしょ?ウフフ
目次
心拍数センサ MAX30105
センサにはMAX30105を使用しました。
赤、緑、赤外線LEDと高感度光子検出器を搭載したモジュールで、
粉塵や血中酸素飽和度(SpO2)などを測ることができます。
Wio Terminalとの接続
MAX30105をWio Terminalの4ピンのGroveコネクタに接続します。
Wio Terminalの回路図を紐解くと2個のGroveコネクタの左側がハードウェアI2Cとつながっておりました。
パルスオキシメータ動作
酸素飽和度(SpO2)、脈拍数と心電図を表示しています。
心拍に合わせてブザーで音も出しています。
ブザー音の演出も功を奏し、いい感じの仕上がりとなりました♪
Arduino コード
MAX30105のライブラリは以下にございます。
https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library
以下のサンプルコードを参考にコーディングしました。
Wio TerminalのLCD表示は以下を参考にいたしました。
https://wiki.seeedstudio.com/Wio-Terminal-LCD-Linecharts/
ブザー音出力は以下を参考にしました。
https://wiki.seeedstudio.com/Wio-Terminal-Buzzer/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
#include <Wire.h> #include "MAX30105.h" #include"seeed_line_chart.h" //include the library #include "spo2_algorithm.h" MAX30105 particleSensor; TFT_eSPI tft; long baseValue = 0; long HB = 0, oldHB = 0; int diffHB = 0; int state = 0; int th = -500; #define max_size 50 //maximum size of data doubles data; //Initilising a doubles type to store data TFT_eSprite spr = TFT_eSprite(&tft); // Sprite long lastBeat = 0; //Time at which the last beat occurred const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good. byte rates[RATE_SIZE]; //Array of heart rates byte rateSpot = 0; float beatsPerMinute; int beatAvg; long delta; #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) //Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format //To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data. uint16_t irBuffer[100]; //infrared LED sensor data uint16_t redBuffer[100]; //red LED sensor data #else uint32_t irBuffer[100]; //infrared LED sensor data uint32_t redBuffer[100]; //red LED sensor data #endif int32_t bufferLength; //data length int32_t spo2; //SPO2 value int8_t validSPO2; //indicator to show if the SPO2 calculation is valid int32_t heartRate; //heart rate value int8_t validHeartRate; //indicator to show if the heart rate calculation is valid void setup() { pinMode(WIO_BUZZER, OUTPUT); tft.begin(); tft.setRotation(3); spr.createSprite(TFT_HEIGHT,TFT_WIDTH); Serial.begin(115200); Serial.println("Initializing..."); // Initialize sensor if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed { Serial.println("MAX30105 was not found. Please check wiring/power. "); while (1); } particleSensor.setup(); //Configure sensor with default settings particleSensor.setPulseAmplitudeRed(20); //Turn Red LED to low to indicate sensor is running particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED } void loop() { bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps //read the first 100 samples, and determine the signal range for (byte i = 0 ; i < bufferLength ; i++) { while (particleSensor.available() == false) //do we have new data? particleSensor.check(); //Check the sensor for new data redBuffer[i] = particleSensor.getRed(); irBuffer[i] = particleSensor.getIR(); particleSensor.nextSample(); //We're finished with this sample so move to next sample Serial.print(F("red=")); Serial.print(redBuffer[i], DEC); Serial.print(F(", ir=")); Serial.println(irBuffer[i], DEC); } //calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples) maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate); //Continuously taking samples from MAX30102. Heart rate and SpO2 are calculated every 1 second while (1) { //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top for (byte i = 25; i < 100; i++) { redBuffer[i - 25] = redBuffer[i]; irBuffer[i - 25] = irBuffer[i]; display(); } //take 25 sets of samples before calculating the heart rate. for (byte i = 75; i < 100; i++) { while (particleSensor.available() == false) //do we have new data? particleSensor.check(); //Check the sensor for new data redBuffer[i] = particleSensor.getRed(); irBuffer[i] = particleSensor.getIR(); particleSensor.nextSample(); //We're finished with this sample so move to next sample //send samples and calculation result to terminal program through UART Serial.print(F("red=")); Serial.print(redBuffer[i], DEC); Serial.print(F(", ir=")); Serial.print(irBuffer[i], DEC); Serial.print(F(", HR=")); Serial.print(heartRate, DEC); Serial.print(F(", HRvalid=")); Serial.print(validHeartRate, DEC); Serial.print(F(", SPO2=")); Serial.print(spo2, DEC); Serial.print(F(", SPO2Valid=")); Serial.println(validSPO2, DEC); display(); } //After gathering 25 new samples recalculate HR and SP02 maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate); } } void display() { spr.fillSprite(TFT_WHITE); if (data.size() == max_size) { data.pop();//this is used to remove the first read variable } HB = particleSensor.getIR(); diffHB = HB - oldHB; data.push(diffHB); //read variables and store in data if(state == 0 && diffHB < th){ delta = millis() - lastBeat; lastBeat = millis(); beatsPerMinute = 60 / (delta / 1000.0); rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array rateSpot %= RATE_SIZE; //Wrap variable //Take average of readings beatAvg = 0; for (byte x = 0 ; x < RATE_SIZE ; x++) beatAvg += rates[x]; beatAvg /= RATE_SIZE; Serial.println(beatAvg); state = 1; analogWrite(WIO_BUZZER, 128); Serial.println("Beat!!!!!!!"); }else if(state == 1 && diffHB > th){ state = 0; analogWrite(WIO_BUZZER, 0); } //Settings for SpO2 String stSpO2 = " SpO2:"; if(validSPO2){ stSpO2 += spo2; }else{ stSpO2 += "-"; } char charSpO2[20]; stSpO2.toCharArray(charSpO2, 20); auto header = text(0, 0) .value(charSpO2) .align(left) .valign(vcenter) .width(tft.width()) .thickness(3); header.height(header.font_height() * 2); header.draw(); //Header height is the twice the height of the font //Settings for HeartRate String stHB = " HeartRate:"; stHB += beatAvg; char charHB[20]; stHB.toCharArray(charHB, 20); auto header2 = text(0, header.height()) .value(charHB) .align(left) .valign(vcenter) .width(tft.width()) .thickness(3); header2.height(header.font_height() * 2); header2.draw(); //Header height is the twice the height of the font //Settings for the line graph auto content = line_chart(0, header.height() + header2.height()); //(x,y) where the line graph begins content .height(tft.height() - (header.height() + header2.height()) * 1.0) //actual height of the line chart .width(tft.width() - content.x() * 2) //actual width of the line chart .based_on(0.0) //Starting point of y-axis, must be a float .show_circle(false) //drawing a cirle at each point, default is on. .value(data) //passing through the data to line graph .color(TFT_PURPLE) //Setting the color for the line .draw(); spr.pushSprite(0, 0); oldHB = HB; } |