FreeRTOS導入スケッチ
Mutexでマルチタスクを実行。動作は時系列によるループ関数と同等だが、タスクの管理・増減・優先度の変更などを容易にするメリットがあります。
/*
Optical SP02 Detection (SPK Algorithm) using the MAX30102 Breakout
Mesuring Temperatures of Object and Ambient by MLX90614
on ESP32
By: Takanobu Fuse @ FICUSONLINE(F9E) https://ficus.myvnc.com
Date: June 2nd, 2021
Hardware Connections (Breakoutboard to Arduino):
-5V = 5V (3.3V is allowed)
-GND = GND
-SDA = A4 (or SDA)
-SCL = A5 (or SCL)
-INT = Not connected
The MAX30102 Breakout can handle 5V or 3.3V I2C logic.
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_MLX90614.h>
#include "MAX30105.h"
#include "heartRate.h"
#include "spo2_algorithm.h"
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
MAX30105 particleSensor;
Adafruit_MLX90614 mlx = Adafruit_MLX90614();
#define MAX_BRIGHTNESS 255
// MAX30102 Check Presence variables
bool checkFlag = 0; // Flag for finding error
long unblockedValue; //Average IR at power up
// MAX30102 Heart Rate variables
uint32_t chkCount;
const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0; //Time at which the last beat occurred
int32_t beatsPerMinute;
int32_t beatAvg;
long redValue;
long delta;
// MAX30102 Spo2 variables
uint32_t irBuffer[100]; //infrared LED sensor data
uint32_t redBuffer[100]; //red LED sensor data
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
// MAX30102 Config
byte ledBrightness = 0x1F; //Options: 0=Off to 255=50mA
byte sampleAverage = 1; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulseWidth = 411; //Options: 69, 118, 215, 411
int adcRange = 4096; //Options: 2048, 4096, 8192, 16384
// Led indicators
byte blueLED = 12; //Must be on PWM pin
byte greenLED = 13; //Blinks with each data read
// Final Outputs
typedef struct {
char *title1;
char *title2;
char *value1;
char *value2;
char *base_unit1;
char *base_unit2;
} finalData;
int str_len;
char char_array1[10];
char char_array2[10];
SemaphoreHandle_t xMutex;
void setup() {
// initialize serial communication at 115200 bits per second:
Serial.begin(9600);
// create mutex and assign it a already create handler
xMutex = xSemaphoreCreateMutex();
pinMode(blueLED, OUTPUT);
pinMode(greenLED, OUTPUT);
// Initialize sensor
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
{
Serial.println(F("MAX30105 was not found. Please check wiring/power."));
while (1);
}
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
delay(1000);
display.clearDisplay();
display.setTextColor(WHITE);
// MAX30102
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
// MLX90614
mlx.begin();
// set up 4 tasks to run independently.
xTaskCreate(
preCheck
, "preCheck" // A name just for humans
, 2000 // This stack size can be checked & adjusted by reading the Stack Highwater
, NULL
, 3 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, NULL);
xTaskCreate(
hrMax30102
, "hrMax30102" // A name just for humans
, 3000 // This stack size can be checked & adjusted by reading the Stack Highwater
, NULL
, 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, NULL);
xTaskCreate(
spo2Max30102
, "spo2Max30102" // A name just for humans
, 4000 // This stack size can be checked & adjusted by reading the Stack Highwater
, NULL
, 1 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, NULL);
xTaskCreate(
tempMlx90614
, "tempMlx90614" // A name just for humans
, 3000 // This stack size can be checked & adjusted by reading the Stack Highwater
, NULL
, 0 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, NULL);
}
void loop() {
}
//**************************TASKS***********************************
//******************************************************************
// MAX30102:Checking Presence
void preCheck(void *pvParameters)
{
(void) pvParameters;
xSemaphoreTake(xMutex, portMAX_DELAY);
for (;;)
{
particleSensor.setPulseAmplitudeRed(0x00); //Red Led off
//Take an average of IR readings at power up
unblockedValue = 0;
for (byte x = 0 ; x < 32 ; x++)
{
unblockedValue += particleSensor.getIR(); //Read the IR value
}
unblockedValue /= 32;
Serial.print("IR ");
Serial.print(particleSensor.getIR());
long currentDelta = particleSensor.getIR() - unblockedValue;
Serial.print(", delta ");
Serial.println(currentDelta);
if (currentDelta > (long)500)
{
Serial.println(" Something is there!");
particleSensor.setPulseAmplitudeRed(ledBrightness);
xSemaphoreGive(xMutex); // release mutex
vTaskDelay(pdMS_TO_TICKS(100));
vTaskDelete(NULL);
}
displayNotice();
}
}
// MAX30102:Mesuring Heart Beat
void hrMax30102(void *pvParameters)
{
(void) pvParameters;
xSemaphoreTake(xMutex, portMAX_DELAY);
checkFlag=false;
digitalWrite(blueLED, 1);
displayChecking();
particleSensor.setup(); //Configure sensor with default settings
particleSensor.setPulseAmplitudeRed(0x0A); //Turn Red LED to low to indicate sensor is running
particleSensor.setPulseAmplitudeIR(0); //Turn off IR LED
for(int i=0; i<1500; i++) {
digitalWrite(blueLED, !digitalRead(blueLED)); //Blink onboard LED with every data read
redValue = particleSensor.getRed();
if (checkForBeat(redValue) == true)
{
//We sensed a beat!
delta = millis() - lastBeat;
lastBeat = millis();
beatsPerMinute = 60 / (delta / 1000.0);
if (beatsPerMinute < 255 && beatsPerMinute > 20)
{
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.print("Red=");
Serial.print(redValue);
Serial.print(", BPM=");
Serial.print(beatsPerMinute);
Serial.print(", Avg BPM=");
Serial.print(beatAvg);
if (redValue < 10000) {
chkCount++;
Serial.print(" No finger?");
if (chkCount > 30) {
break;
}
}
Serial.println();
if(i > 500) {
if((beatAvg >30) && (beatsPerMinute > 0.95*beatAvg && beatsPerMinute < 1.05*beatAvg)) {
checkFlag=true;
break;
}
}
}
digitalWrite(blueLED, 0);
if(checkFlag==true) {
finalData *p = (finalData *)malloc(sizeof(finalData));
p->title1 = "Average BPM";
p->title2 = "BPM";
p->value1 = strToChar1(String(beatAvg));
p->value2 = strToChar2(String(beatsPerMinute));
p->base_unit1 = "bpm";
p->base_unit2 = "bpm";
displayFinal(p);
}else {
displayError();
}
vTaskDelay(pdMS_TO_TICKS(100));
vTaskDelete(NULL);
}
// MAX30102:Mesuring SpO2 Pulse Oximeter
void spo2Max30102(void *pvParameters)
{
(void) pvParameters;
xSemaphoreTake(xMutex, portMAX_DELAY);
checkFlag=false;
digitalWrite(greenLED, 1);
displayChecking();
// max30102
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
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
for (int x=0; x< 25; x++)
{
//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];
}
//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
digitalWrite(greenLED, !digitalRead(greenLED)); //Blink onboard LED with every data read
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);
}
//After gathering 25 new samples recalculate HR and SP02
maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
if((heartRate > 0.8*beatAvg && heartRate < 1.2*beatAvg) && (validHeartRate==1 && validSPO2==1)) {
checkFlag=true;
break;
}
}
digitalWrite(greenLED, 0);
if(checkFlag==true) {
finalData *p = (finalData *)malloc(sizeof(finalData));
p->title1 = "SpO2";
p->title2 = "Heart Rate(BPM)";
p->value1 = strToChar1(String(spo2));
p->value2 = strToChar2(String(heartRate));
p->base_unit1 = "%";
p->base_unit2 = "bpm";
displayFinal(p);
} else {
displayError();
}
vTaskDelay(pdMS_TO_TICKS(100));
vTaskDelete(NULL);
}
// MLX90614:Measuring Object Temp and Ambient Temp
void tempMlx90614(void *pvParameters)
{
(void) pvParameters;
xSemaphoreTake(xMutex, portMAX_DELAY);
finalData *p = (finalData *)malloc(sizeof(finalData));
p->title1 = "TEMPERATURE:";
p->title2 = "AMBIENT:";
p->value1 = strToChar1(String(mlx.readObjectTempC()));
p->value2 = strToChar2(String(mlx.readAmbientTempC()));
p->base_unit1 = "`C";
p->base_unit2 = "`C";
displayFinal(p);
ESP.restart();
}
// Output data will be transformed to char from string
char *strToChar1(String str) {
str_len=str.length()+1;
str.toCharArray(char_array1, str_len);
return char_array1;
}
char *strToChar2(String str) {
str_len=str.length()+1;
str.toCharArray(char_array2, str_len);
return char_array2;
}
//************************OLED Display Functions*********************************
//*****************************************************************************
void displayNotice() {
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0,0);
display.print("Start >>>");
display.setTextSize(1);
display.setCursor(0,30);
display.print("Put Your Finger on");
display.display();
delay(2000);
display.clearDisplay();
display.display();
delay(500);
}
void displayFinal(finalData *p) {
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0,0);
display.print(p->value1);
display.print(" ");
display.print(p->base_unit1);
display.setTextSize(1);
display.setCursor(0,18);
display.print(p->title1);
display.setTextSize(2);
display.setCursor(0,34);
display.print(p->value2);
display.print(" ");
display.print(p->base_unit2);
display.setTextSize(1);
display.setCursor(0,52);
display.print(p->title2);
delay(10);
yield();
display.display();
delay(3000);
xSemaphoreGive(xMutex); // release mutex
}
void displayError() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(20,30);
display.print(F("Read Error..."));
display.setCursor(20,40);
display.print(F("Try Again!"));
delay(10);
yield();
display.display();
delay(3000);
xSemaphoreGive(xMutex); // release mutex
tempMlx90614(NULL);
}
void displayChecking() {
delay(500);
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0,0);
display.print("Checking..");
display.setTextSize(1);
display.setCursor(0,30);
display.print("Keep Your Finger");
display.setCursor(0,40);
display.print("putting on");
display.display();
}
https://stackoverflow.com/questions/10162152/how-to-work-with-string-fields-in-a-c-struct