# ヘルスチェックデバイス：脈拍数+酸素飽和度SpO2+体温測定(FreeRTOS+ESP32)

**URL:** <https://forum.ficusonline.com/t/topic/409>\
**Category:** ESP\
**Created:** [2021 年 2 月 15 日午前 6:03 UTC](https://forum.ficusonline.com/t/topic/409 "2021-02-15T06:03:25Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 2 月 15 日午前 6:03 UTC](https://forum.ficusonline.com/t/topic/409/1 "2021-02-15T06:03:25Z")

</div>

### FreeRTOS

> **[FreeRTOS™ - FreeRTOS™](https://www.freertos.org/)**

### ESP32 FreeRTOS Arduinoサンプルスケッチ

[https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/FreeRTOS/FreeRTOS.ino](https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/FreeRTOS/FreeRTOS.ino)

### ESP32 FreeRTOS

**Task API**

> **[FreeRTOS Overview - ESP32 - — ESP-IDF Programming Guide v5.5.3 documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html#freertos)**

### Functions

BaseType\_t `xTaskCreatePinnedToCore` (TaskFunction\_t _pvTaskCode_, _const_ char \*_const_ _pcName_, _const_ uint32\_t _usStackDepth_, void \*_const_ _pvParameters_, UBaseType\_t _uxPriority_, [TaskHandle\_t](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html#_CPPv412TaskHandle_t) \*_const_ _pvCreatedTask_, _const_ BaseType\_t _xCoreID_ )

Create a new task with a specified affinity.

This function is similar to xTaskCreate, but allows setting task affinity in SMP system.

**Return**

pdPASS if the task was successfully created and added to a ready list, otherwise an error code defined in the file projdefs.h

**Parameters**

- `pvTaskCode`: Pointer to the task entry function. Tasks must be implemented to never return (i.e. continuous loop), or should be terminated using vTaskDelete function.
- `pcName`: A descriptive name for the task. This is mainly used to facilitate debugging. Max length defined by configMAX\_TASK\_NAME\_LEN - default is 16.
- `usStackDepth`: The size of the task stack specified as the number of bytes. Note that this differs from vanilla FreeRTOS.
- `pvParameters`: Pointer that will be used as the parameter for the task being created.
- `uxPriority`: The priority at which the task should run. Systems that include MPU support can optionally create tasks in a privileged (system) mode by setting bit portPRIVILEGE\_BIT of the priority parameter. For example, to create a privileged task at priority 2 the uxPriority parameter should be set to ( 2 | portPRIVILEGE\_BIT ).
- `pvCreatedTask`: Used to pass back a handle by which the created task can be referenced.
- `xCoreID`: If the value is tskNO\_AFFINITY, the created task is not pinned to any CPU, and the scheduler can run it on any core available. Values 0 or 1 indicate the index number of the CPU which the task should be pinned to. Specifying values larger than (portNUM\_PROCESSORS - 1) will cause the function to fail.

_static_ BaseType\_t `xTaskCreate` (TaskFunction\_t _pvTaskCode_, _const_ char \*_const_ _pcName_, _const_ uint32\_t _usStackDepth_, void \*_const_ _pvParameters_, UBaseType\_t _uxPriority_, [TaskHandle\_t](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html#_CPPv412TaskHandle_t) \*_const_ _pvCreatedTask_ )

### ESP-IDF FreeRTOS SMP Changes

[https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/freertos-smp.html#esp-idf-freertos-smp-changes](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/freertos-smp.html#esp-idf-freertos-smp-changes)  
ESP32は2コアCPU構成で、この2者間でメモリを共有することにより、同一タスクを割振ることが可能です。

> **[ESP32 multicore task scheduling](https://community.hiveeyes.org/t/esp32-multicore-task-scheduling/1554)**
>
> Introduction As outlined in the programming guide, the ESP-IDF FreeRTOS derivate introduces a small but significant change to the vanilla FreeRTOS round robin scheduler: Tasks in the ESP-IDF FreeRTOS are designed to run on a particular core. ...

脈拍数・酸素飽和度・体温を測定するヘルスデバイスの作成

- マイコン：ESP32S
- パルスオキシメーター： MAX30102
- 非接触型赤外線温度センサー： GY-906 MLX90614
- OLED：2色、 128X64 I2C SSD1306

各センサーの制御をFreeRTOSにより管理、MQTTプロトコルにより出力値をホームオートメーションシステムに送信することで、遠隔でのヘルスチェック、異常時の緊急呼び出しなどを実現します。

**注）** MAX30102では、データ読取りサンプリング中に他のタスクの割り込みが入ると結果の数値が大きく影響を受けるため、RTOSでタスク管理する際は、この点に留意する必要があります。

**パルスオキシメータ原理**  
[https://www.howequipmentworks.com/pulse\_oximeter/](https://www.howequipmentworks.com/pulse_oximeter/)

![graph_animation](https://forum.ficusonline.com/uploads/default/original/1X/17c2bc4751aeb7f98c5a0355e68c5cf58972d0d5.gif)

**MAX30100 SpO2測定アルゴリズム**

> **[MORF - Coding And Engineering](https://morf.lv/implementing-pulse-oximeter-using-max30100)**
>
> Projects and tutorials in computer programming and electronics engineering

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 11 日午後 2:03 UTC](https://forum.ficusonline.com/t/topic/409/2 "2021-05-11T14:03:45Z")

</div>

**動作確認中(I2C接続)**

 ![esp32+i2c](https://forum.ficusonline.com/uploads/default/original/1X/1132da76da51312c6b2a87e4921b8b23ccf54ded.jpeg)

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 12 日午前 2:24 UTC](https://forum.ficusonline.com/t/topic/409/3 "2021-05-12T02:24:42Z")

</div>

## 各デバイスのI2Cアドレス確認

> **[ESP32 I2C Communication: Set Pins, Multiple Bus Interfaces and Peripherals |...](https://randomnerdtutorials.com/esp32-i2c-communication-arduino-ide/)**
>
> The ESP32 has two I2C physical bus interfaces that can serve as I2C master or slave. Learn how I2C communication protocol works with ESP32 board using Arduino IDE

**確認用Arduinoスケッチ**

```auto
/ *********
  Rui Santos
  Complete project details at https://randomnerdtutorials.com  
********* /

#include <Wire.h>
 
void setup() {
  Wire.begin();
  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}
 
void loop() {
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
      nDevices++;
    }
    else if (error==4) {
      Serial.print("Unknow error at address 0x");
      if (address<16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0) {
    Serial.println("No I2C devices found\n");
  }
  else {
    Serial.println("done\n");
  }
  delay(5000);          
}

```

**シリアルモニタ**

```auto
Scanning...
I2C device found at address 0x3C ---> OLED
I2C device found at address 0x57 ---> MAX30102
I2C device found at address 0x5A ---> MLX90614
done

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 14 日午前 2:09 UTC](https://forum.ficusonline.com/t/topic/409/4 "2021-05-14T02:09:03Z")

</div>

## MAX30102モジュール補足

**注）** 今回購入したMAX30102モジュールの回路図（30100を30102に置換え）は以下の通りです。MAX30100とはADC分解能、レジスタマップが異なるため、MAX30100ライブラリは適用できません。

**回路図**

 ![Schematic_MAX30102 Sensor Module](https://forum.ficusonline.com/uploads/default/original/1X/923c604c4979e7ac522cf24d29928770c5ca532d.jpeg)

**Sparkfun Arduinoライブラリ**  
以下SparkFunのライブラリを採用します。

> **[GitHub - sparkfun/SparkFun\_MAX3010x\_Sensor\_Library: An Arduino Library for the MAX3015 particle sensor...](https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library)**
>
> An Arduino Library for the MAX3015 particle sensor and MAX30102 Pulse Ox sensor

**MAX30100**  
[https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf](https://datasheets.maximintegrated.com/en/ds/MAX30100.pdf)

**MAX30102**  
[https://datasheets.maximintegrated.com/en/ds/MAX30102.pdf](https://datasheets.maximintegrated.com/en/ds/MAX30102.pdf)

 ![spo2_sample_rate](https://forum.ficusonline.com/uploads/default/original/1X/a4b6a0c1b98bf38038b76ad92639ab427aaa42d9.png)

 ![led_timing_chart](https://forum.ficusonline.com/uploads/default/original/1X/26ff3766ff2b73767aaaf3cfa35f41715c037805.png)

 ![spo2_hr_table](https://forum.ficusonline.com/uploads/default/original/1X/109af84f9503b61cde63f1960651f4768461ae22.png)

 ![fifo_config](https://forum.ficusonline.com/uploads/default/original/1X/7b5a9e2174b62ebfb67d7640582364db563dbebf.png)

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 18 日午後 2:08 UTC](https://forum.ficusonline.com/t/topic/409/5 "2021-05-18T14:08:01Z")

</div>

**Sparkfun Arduino** ライブラリでRed LEDとIR LEDの設定が逆の可能性

> <https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/issues/17>
>
> what am I missing here? what do I have to adjust to get the right numbers?
> !\[un…titled\](https://user-images.githubusercontent.com/35321883/43936658-70a244ee-9c51-11e8-9e28-f349843c4acf.jpg)

> LED1 \< — \> LED2  
> sense.red \< — \> sense.IR

**MAX30105.cpp**

```auto
.....
.....
void MAX30105::setPulseAmplitudeRed(uint8_t amplitude) {
  writeRegister8(_i2caddr, MAX30105_LED2_PULSEAMP, amplitude);
}

void MAX30105::setPulseAmplitudeIR(uint8_t amplitude) {
  writeRegister8(_i2caddr, MAX30105_LED1_PULSEAMP, amplitude);
}
.....
.....
.....
while (toGet > 0)
      {
        sense.head++; //Advance the head of the storage struct
        sense.head %= STORAGE_SIZE; //Wrap condition

        byte temp[sizeof(uint32_t)]; //Array of 4 bytes that we will convert into long
        uint32_t tempLong;

        //Burst read three bytes - RED
        temp[3] = 0;
        temp[2] = _i2cPort->read();
        temp[1] = _i2cPort->read();
        temp[0] = _i2cPort->read();

        //Convert array to long
        memcpy(&tempLong, temp, sizeof(tempLong));
		
		tempLong &= 0x3FFFF; //Zero out all but 18 bits

        sense.IR[sense.head] = tempLong; //Store this reading into the sense array

        if (activeLEDs > 1)
        {
          //Burst read three more bytes - IR
          temp[3] = 0;
          temp[2] = _i2cPort->read();
          temp[1] = _i2cPort->read();
          temp[0] = _i2cPort->read();

          //Convert array to long
          memcpy(&tempLong, temp, sizeof(tempLong));

		  tempLong &= 0x3FFFF; //Zero out all but 18 bits
          
		  sense.red[sense.head] = tempLong;
        }
.....

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 19 日午後 1:24 UTC](https://forum.ficusonline.com/t/topic/409/6 "2021-05-19T13:24:47Z")

</div>

## ESP32+FreeRTOS 参考サイト

### ESP32: Running code on a specific core

> **[ESP32: Running code on a specific core](https://techtutorialsx.wordpress.com/2017/05/09/esp32-running-code-on-a-specific-core/)**
>
> The objective of this post is to explain how to run code on a specific core of the ESP32, using the Arduino environment support.

### How to use ESP32 Dual Core with Arduino IDE

> **[ESP32 Dual Core with Arduino IDE | Random Nerd Tutorials](https://randomnerdtutorials.com/esp32-dual-core-arduino-ide/)**
>
> The ESP32 is dual core: it comes with 2 microprocessors. In this article we’ll show you how to use both ESP32 cores using Arduino IDE by creating tasks.

**ESP32** はデュアルコア構成。`loop(), setup()`はデフォルトで **Core 1** を使用  
コアを指定しない **タスク** は **Core 0** を使用

### Multitasking on ESP32 with Arduino and FreeRTOS

> **[Multitasking on ESP32 with Arduino and FreeRTOS](https://simplyexplained.com/blog/multitasking-esp32-arduino-freertos/)**
>
> By now, it's no secret that the ESP32 is my go-to chip for making IoT devices. They're small, powerful, have a ton of onboard features, and they're relatively easy to program.However, when using it together with Arduino, all your code runs on a s...

### FreeRTOS Arduino Tutorials

> **[FreeRTOS Arduino Getting Started Tutorial: How to Create Tasks](https://microcontrollerslab.com/use-freertos-arduino/)**
>
> How to use FreeRTOS with Arduino - Getting started tutorial, Introduction, How to create tasks with FreeRTOS using Arduino

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 21 日午後 3:19 UTC](https://forum.ficusonline.com/t/topic/409/7 "2021-05-21T15:19:12Z")

</div>

## FreeRTOS Arduino : How to Delete Tasks with vTaskDelete() API

> **[FreeRTOS Arduino: How to Delete Tasks with vTaskDelete() API](https://microcontrollerslab.com/freertos-arduino-how-to-delete-tasks-with-vtaskdelete-api/)**
>
> vTaskDelete() FreeRTOS API function is used to delete tasks, Learn to How to Delete Tasks using Arduino with Example and Code

**vTaskDelete() サンプルコード**

```auto
// Include Arduino FreeRTOS library
#include <Arduino_FreeRTOS.h>
#include "task.h"

TaskHandle_t TaskHandle_2; // handler for Task2

void setup() 
{
  Serial.begin(9600); // Enable serial communication library.
  pinMode(4, OUTPUT); // define LED1 pin as a digital output 
  pinMode(5, OUTPUT); // define LED2 pin as a digital output

  //Create the first task at priority 1
  // Name of task is "LED1"
  // Stack size is set to 100
  // we do not pass any value to Task1. Hence, third agument is NULL
  // Set the priority of task to one
  // Task1 handler is not used. Therefore set to Null
  xTaskCreate(Task1, "LED1", 100, NULL, 1, NULL);
  // Start FreeRTOS scheduler in Preemptive timing silicing mode
  vTaskStartScheduler();
}

void loop() 
{
// Do nothing as schduler will allocated CPU to Task1 and Task2 automatically
}
/* Task1 with priority 1 */
void Task1(void* pvParameters)
{

  while(1)
  {
    Serial.println("Task1 Running"); // print "Task1 Running" on Arduino Serial Monitor
    digitalWrite(4, HIGH); // sets the digital pin 4 on
    digitalWrite(5, LOW); // sets the digital pin 5 off
    xTaskCreate(Task2, "LED2", 100, NULL, 2, &TaskHandle_2); // create task2 with priority 2
    vTaskDelay( 100 / portTICK_PERIOD_MS ); // wait for one second
  }
}

/* Task2 with priority 2 */
void Task2(void* pvParameters)
{ 
    //digitalWrite(5, HIGH); // sets the digital pin 5 high
    //digitalWrite(4, LOW); // sets the digital pin 4 low
    Serial.println("Task2 is runnig and about to delete itself");
    vTaskDelete(TaskHandle_2); //Delete own task by passing NULL(TaskHandle_2 can also be used)
}

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 22 日午前 9:55 UTC](https://forum.ficusonline.com/t/topic/409/8 "2021-05-22T09:55:02Z")

</div>

## FreeRTOS Arduino: Changing the Priority of a Task

**プログラム実行中のタスクプライオリティの変更**

> **[Changing Tasks Priority using FreeRTOS and Arduino](https://microcontrollerslab.com/changing-task-priority-using-freertos-arduino/)**
>
> vTaskPrioritySet() is used to set task priority after the scheduler has been started. Arduino FreeRTOS Changing Tasks Priority using FreeRTOS and Arduino.

**vTaskPrioritySet() サンプルコード**

```auto
#include <Arduino_FreeRTOS.h>
#include <task.h>
void Task1( void *pvParameters );
void Task2( void *pvParameters );

TaskHandle_t TaskHandle_1; // handler for Task1
TaskHandle_t TaskHandle_2; // handler for Task2

void setup() 
{
  Serial.begin(9600); // Enable serial communication library.

   xTaskCreate(Task1, "LED1", 100, NULL, 3, &TaskHandle_1);
   xTaskCreate(Task2, "LED2", 100, NULL, 2, &TaskHandle_2);
   vTaskStartScheduler();
}

void loop() 
{
  // put your main code here, to run repeatedly:

}

//definition of Task1
void Task1(void* pvParameters)
{
     UBaseType_t uxPriority = uxTaskPriorityGet( NULL );
    while(1)
    {
    Serial.println("Task1 is running and about to raise Task2 Priority");
    vTaskPrioritySet( TaskHandle_2, ( uxPriority + 1 ) );
   
    }
}
void Task2(void* pvParameters)
{
 UBaseType_t uxPriority = uxTaskPriorityGet( NULL );
     while(1)
    {
    Serial.println("Task2 is running and about to lower Task2 Priority");
    vTaskPrioritySet( TaskHandle_2, ( uxPriority - 2 ) );
    
    }
  
}

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 24 日午後 1:35 UTC](https://forum.ficusonline.com/t/topic/409/9 "2021-05-24T13:35:39Z")

</div>

FreeRTOS非適用の通常のループ関数によるI2C接続された各デバイスの動作確認用スケッチ。  
MAX30102の酸素飽和度の測定精度（データのサンプリング）は、I2C同一ライン上に接続されたOLEDデバイスへの通信負荷により大きく影響を受けるため測定中は非表示とする（電源の問題で表示しても問題ない）。

```auto
/*
  Optical SP02 Detection (SPK Algorithm) using the MAX30102 Breakout
  By: Takanobu Fuse @ Ficusonline F9E
  Date: May 17th, 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

bool checkFlag = 0; // Flag for finding error

// Check Presence variables
long unblockedValue; //Average IR at power up

// Heart Rate variables
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

float beatsPerMinute;
int beatAvg;
long redValue;
long delta;

// 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 = 60; //Options: 0=Off to 255=50mA
byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
byte sampleRate = 1000; //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

void setup()
{
  Serial.begin(115200); // initialize serial communication at 115200 bits per second:

  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);
  displayNotice();

  // max30102
  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
  // mlx90614
  mlx.begin();
}

void loop()
{
  if (preCheck() == true) {
    displayChecking();
    readHR();
    if(checkFlag==0) {
      ESP.restart();
    }
    
    displayChecking();
    readSpo2();
    if(checkFlag==0) {
      ESP.restart();
    }
    
    readTemp();
    
    ESP.restart();
  }
}

bool preCheck() {
  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);
    return true;
  }
  
  displayNotice();
  return false;

}

void readHR() {
  checkFlag=false;
  digitalWrite(blueLED, 1);
  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 Green LED
  
  for (int i=0; i<1000; 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)
      Serial.print(" No finger?");  
  
    Serial.println();
    if((beatAvg >30) && (beatsPerMinute > 0.95*beatAvg && beatsPerMinute < 1.05*beatAvg)) {
      checkFlag=true;
      break;
    }
  }
  
  digitalWrite(blueLED, 0);
  
  if(checkFlag==true) {
    displayHR();
  }else {
    displayError();
  }

}

void readSpo2() {
  checkFlag=false;
  digitalWrite(greenLED, 1);
  
  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<20; x++)
  //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];
    }

    //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;
    }
    //if(checkFlag==true) {
    // break;
    //}
  }
  
  digitalWrite(greenLED, 0);
  
  if(checkFlag==true) {
    displaySpo2();
  } else {
    displayError();
  }
  
}

void readTemp() {
  display.clearDisplay();
  // display temperature of object
  display.setTextSize(2);
  display.setCursor(0,0);
  display.print(mlx.readObjectTempC());
  display.print(" ");
  display.setTextSize(1);
  display.cp437(true);
  display.write(167);
  display.setTextSize(2);
  display.print("C");
  display.setTextSize(1);
  display.setCursor(0,18);
  display.print("TEMPERATURE: ");
  
  // display temperature of ambient
  display.setTextSize(2);
  display.setCursor(0, 34);
  display.print(mlx.readAmbientTempC());
  display.print(" ");
  display.setTextSize(1);
  display.cp437(true);
  display.write(167);
  display.setTextSize(2);
  display.print("C");
  display.setTextSize(1);
  display.setCursor(0, 52);
  display.print("AMBIENT: ");
  
  delay(10);
  yield();
  display.display();
  delay(3000);
}

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 displayChecking() {
  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();
  delay(2000);
  display.clearDisplay();
  display.display();
}

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);
}

void displayHR() {
  display.clearDisplay();

  if(redValue > 5000) {
    display.setTextSize(2);
    display.setCursor(0,0); 
    display.print(beatAvg);
    
    display.setTextSize(1);
    display.setCursor(0,18);
    display.print(F("Average BPM"));
    
    display.setTextSize(2);
    display.setCursor(0,34); 
    display.print(beatsPerMinute);
    
    display.setTextSize(1);
    display.setCursor(0,52);
    display.print(F("BPM"));

  } else {
    display.setTextSize(1);
    display.setCursor(40,32);
    display.print(F("HR Not Valid"));
  }
  delay(10);
  yield();
  display.display();
  delay(3000);
}

void displaySpo2() {
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(0,0); 
  display.print(spo2, DEC);
  display.print(" ");
  display.print("%");
  
  display.setTextSize(1);
  display.setCursor(0,18);
  display.print(F("SpO2"));
  
  display.setTextSize(2);
  display.setCursor(0,34); 
  display.print(heartRate, DEC);
  
  display.setTextSize(1);
  display.setCursor(0,52);
  display.print(F("Heart Rate(BPM) "));
  
  delay(10);
  yield();
  display.display();
  delay(3000);
}

```

このスケッチをベースにFreeRTOSを適用したスケッチを作成中。  
完成後、心拍数・酸素飽和度・体温の各値をJSONフォーマットにより1セットにし、ホームオートメーションシステムへMQTTプロトコルによりパブリッシュ。

**YouTube動画**

[![](https://forum.ficusonline.com/uploads/default/original/2X/8/8b4160973649bbe57053a375216964809ff67614.jpeg "Spo2+ESP32 Covid-19 Check Device") ](https://www.youtube.com/watch?v=aE6bIShhQXI)

**起動画面**

 ![start](https://forum.ficusonline.com/uploads/default/original/1X/e817e9c069166918790f75b126abca0ce911997f.jpeg)

**測定モード**

 ![checking](https://forum.ficusonline.com/uploads/default/original/1X/5fc50a49447b9d1b46f2e664cf7a9b0bd55ac66c.jpeg)

**心拍数測定**

 ![heartrate](https://forum.ficusonline.com/uploads/default/original/1X/ea61d6b914f3f0f5ea13dcd77569992624ce3544.jpeg)

**酸素飽和度測定**

 ![spo2_hr](https://forum.ficusonline.com/uploads/default/original/1X/1960be25ec704a1520af2e0af080a36b2841c8f9.jpeg)

**体温測定**  
注）ケースデザイン未定のため、MLX90614に指を当てていません。

 ![temp](https://forum.ficusonline.com/uploads/default/original/1X/8ca6a1d3764374d794bf93c94585b5eade08be0f.jpeg)

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 27 日午後 2:49 UTC](https://forum.ficusonline.com/t/topic/409/10 "2021-05-27T14:49:05Z")

</div>

Arduinoのボードマネージャにより追加できるESP32のライブラリにはFreeRTOSが標準装備されています。そのため、以下の各サンプルスケッチでヘッダーファイルは省略可。

I2Cで接続された各デバイスをRTOSにより管理するため、以下の **Semaphore、Mutex** を導入します。

ローカル路線でよく見かける単線を上下線で共有する場合、中間地点に設けられた複線区間で上下線のどちらかが待つことで運行している状態が **Semaphore** であり（単線上に同時に列車が走行）、単線を上下線のどちらかしか走行できない状態が **Mutex** 。  
**Semaphore** では、複線区間の長さと車列の長さのバランスが崩れると上下線が衝突しますが、 **Mutex** では衝突は起こりません。効率は **Semaphore** が優位です。

```auto
Backtrace: 0x40081444:0x3ffb7ac0 0x400814ce:0x3ffb7ae0 0x400831d1:0x3ffb7b00 0x4000beaf:0x3ffb7b20 0x400859b6:0x3ffb7b40 0x40085c04:0x3ffb7b60 0x40083292:0x3ffb7b80 0x400832bd:0x3ffb7ba0 0x40083411:0x3ffb7bd0 0x400d732f:0x3ffb7bf0 0x400d3849:0x3ffb7eb0 0x400d37e4:0x3ffb7f0p⸮R⸮⸮fgFjRGuru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.
Core 0 register dump:
PC : 0x40081444 PS : 0x00060c33 A0 : 0x800814d1 A1 : 0x3ffb7ac0  
A2 : 0x00000054 A3 : 0x00001800 A4 : 0x00000001 A5 : 0x00000000  
A6 : 0x3ffb83a0 A7 : 0x00000008 A8 : 0x00000001 A9 : 0x3ffb7ab0  
A10 : 0x00000001 A11 : 0x00060c23 A12 : 0x00060c23 A13 : 0x00000000  
A14 : 0x00000000 A15 : 0x00000000 SAR : 0x00000000 EXCCAUSE: 0x0000001c  
EXCVADDR: 0x0000001d LBEG : 0x00000000 LEND : 0x00000000 LCOUNT : 0x00000000  

ELF file SHA256: 0000000000000000

```

## FreeRTOS Binary Semaphore

> **[FreeRTOS Binary Semaphore Tasks Interrupt Synchronization Arduino](https://microcontrollerslab.com/freertos-binary-semaphore-tasks-interrupt-synchronization-u-arduino/)**
>
> FreeRTOS Binary Semaphore tutorial to create tasks synchronization using interrupt and software tasks with Examples using Arduino

## FreeRTOS Counting Semaphores

> **[FreeRTOS Counting Semaphore Examples with Arduino](https://microcontrollerslab.com/freertos-counting-semaphore-examples-arduino/)**
>
> In this tutorial, learn to use FreeRTOS counting semaphore for resource management and events counting with Arduino examples

シリアル通信を一つの共有リソースとして、2つのタスクで割り振って使用。

### Semaphore

> 手旗信号; （コンピュータ） 使用中のリソースをロックし他のプログラムによるアクセスを妨げることで共有リソースへのアクセスを制限するために使われるステータス旗（スイッチ）

**注）** 文字数、遅いシリアルレートと小さなディレイの組合せにより、エラーが発生します。  
以下ESP32で動作確認済。スタックサイズ、レート、ディレイ変更。

```auto
SemaphoreHandle_t xCountingSemaphore;
byte LED_BUILTIN = 12;
void setup() 
{
  Serial.begin(115200); // Enable serial communication library.
  pinMode(LED_BUILTIN, OUTPUT);

 // Create task for Arduino led 
  xTaskCreate(Task1, // Task function
              "Ledon", // Task name
              1000, // Stack size 
              NULL, 
              0 ,// Priority
              NULL );
   xTaskCreate(Task2, // Task function
              "Ledoff", // Task name
              1000, // Stack size 
              NULL, 
              0, // Priority
              NULL );
   xCountingSemaphore = xSemaphoreCreateCounting(1,1);
   xSemaphoreGive(xCountingSemaphore);

}

void loop() {}

void Task1(void *pvParameters)
{
  (void) pvParameters;

  for (;;) 
    {
      xSemaphoreTake(xCountingSemaphore, portMAX_DELAY); 
      Serial.println("Inside Task1 and Serial monitor Resource Taken");
      digitalWrite(LED_BUILTIN, HIGH);
      xSemaphoreGive(xCountingSemaphore);
      vTaskDelay(5);
    }
  }

void Task2(void *pvParameters)
{
  (void) pvParameters;
  for (;;) 
    {
      xSemaphoreTake(xCountingSemaphore, portMAX_DELAY);
      Serial.println("Inside Task2 and Serial monitor Resource Taken");
      digitalWrite(LED_BUILTIN, LOW);
      xSemaphoreGive(xCountingSemaphore);
      vTaskDelay(5);

  }
}

```

## FreeRTOS Mutex

> **[Arduino: FreeRTOS Mutex tutorial - Priority Inversion & Priority Inheritance](https://microcontrollerslab.com/arduino-freertos-mutex-tutorial-priority-inversion-priority-inheritance/)**
>
> FreeRTOS mutex examples with Arduino, how to avoid priority inversion issue by using priority inheritance technique explain with timing diagrams

![FreeRTOS-mutex-example-with-shared-resource](https://forum.ficusonline.com/uploads/default/original/1X/514434d64fa7ef69ef486a035d20cc4214da794f.gif)

### Mutex

> 共通情報源への同期多重アクセス（一度に一つのプログラムへのアクセスと他の全てを遮断するロック - アンロック スイッチを使う）

**注）** 文字数、シリアルレートに関わらず動作。  
以下同一タスクのインスタンスを2つ用意し1つのリソースを共有。  
ESP32で動作確認済。スタックサイズのみ変更。

```auto
//create handle for the mutex. It will be used to reference mutex
SemaphoreHandle_t xMutex;

void setup()
{
  // Enable serial module of Arduino with 9600 baud rate
  Serial.begin(9600);
// create mutex and assign it a already create handler 
  xMutex = xSemaphoreCreateMutex();
// create two instances of task "OutputTask" which are used to display string on 
// arduino serial monitor. We passed strings as a paramter to these tasks such as ""Task 1 //#####################Task1" and "Task 2 ---------------------Task2". Priority of one //instance is higher than the other
  xTaskCreate(OutputTask,"Printer Task 1", 1000,"Task 1 #####################Task1 \r\n",1,NULL);
  xTaskCreate(OutputTask,"Printer Task 2", 1000,"Task 2 ---------------------Task2 \r\n",2,NULL);    
}

// this is a definition of tasks 
void OutputTask(void *pvParameters)
{
  char *pcStringToPrint;
  pcStringToPrint = (char *)pvParameters;
  while(1)
  {
    printer(pcStringToPrint);
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}
// this printer task send data to arduino serial monitor
//aslo it is shared resource between both instances of the tasks
void printer(const char* pcString)
{
  // take mutex
  xSemaphoreTake(xMutex, portMAX_DELAY);
  Serial.println(pcString); // send string to serial monitor
  xSemaphoreGive(xMutex); // release mutex
}
void loop(){}

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 5 月 28 日午後 12:13 UTC](https://forum.ficusonline.com/t/topic/409/11 "2021-05-28T12:13:36Z")

</div>

## FreeRTOS Gatekeeper

> **[FreeRTOS Gatekeeper Tasks Examples with Arduino](https://microcontrollerslab.com/freertos-gatekeeper-task-with-arduino/)**
>
> FreeRTOS Gatekeeper Task without the risk of priority inversion and deadlocks demo examples with Arduino and step by step guide

MutexによるDeadlock回避のためのGatekeeper

```auto
static const char *pcStringToPrint[] =
{
  "Task 1 ############################## Task1 \r\n",
  "Task 2 ------------------------------ Task2 \r\n",  
};

QueueHandle_t xPrintQueue;
void setup()
{
   Serial.begin(9600);
  xPrintQueue = xQueueCreate(5,sizeof(char *));
  xTaskCreate(SenderTask,"Printer Task1", 1000,(void *)0,1,NULL );
  xTaskCreate(SenderTask,"Printer Task2", 1000,(void *)1,2,NULL );
  xTaskCreate(GateKeeperTask, "GateKeeper", 1000,NULL,0,NULL);
  
}
void SenderTask(void *pvParameters)
{
  int indexToString ;
  indexToString = (int)pvParameters;

  while(1)
  {
   xQueueSend(xPrintQueue,&(pcStringToPrint[indexToString]),portMAX_DELAY); 
   vTaskDelay(pdMS_TO_TICKS(100));

  }
  
}

void GateKeeperTask(void *pvParameters)
{
  char *pcMessageToPrint;
  while(1)
  {
   xQueueReceive(xPrintQueue,&pcMessageToPrint,portMAX_DELAY);
   Serial.println(pcMessageToPrint); 
  }
}

void loop(){}

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 6 月 1 日午前 2:24 UTC](https://forum.ficusonline.com/t/topic/409/12 "2021-06-01T02:24:26Z")

</div>

> <https://stackoverflow.com/questions/63634917/freertos-task-should-not-return-esp32>

> **[FreeRTOS scheduling (single-core, AMP and SMP) - FreeRTOS™](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/01-Tasks-and-co-routines/05-Implementing-a-task)**
>
> FreeRTOS scheduling algorithm for single-core, asymmetric multicore (AMP), and symmetric multicore (SMP) RTOS configurations

```auto
void vATaskFunction( void *pvParameters )
    {
        for( ;; )
        {
            -- Task application code here. --
        }

        /* Tasks must not attempt to return from their implementing
        function or otherwise exit. In newer FreeRTOS port
        attempting to do so will result in an configASSERT() being
        called if it is defined. If it is necessary for a task to
        exit then have the task call vTaskDelete( NULL ) to ensure
        its exit is clean. */
        vTaskDelete( NULL );
    }

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 6 月 3 日午前 6:00 UTC](https://forum.ficusonline.com/t/topic/409/13 "2021-06-03T06:00:17Z")

</div>

## FreeRTOS導入スケッチ

Mutexでマルチタスクを実行。動作は時系列によるループ関数と同等だが、タスクの管理・増減・優先度の変更などを容易にするメリットがあります。

```auto
/*
  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>

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 6 月 9 日午後 3:28 UTC](https://forum.ficusonline.com/t/topic/409/14 "2021-06-09T15:28:53Z")

</div>

### ホストネームの変更とその呼出

> <https://github.com/espressif/arduino-esp32/issues/4074>
>
> I'm use only WiFi.setHostname("MyHostname\_ESP32"); but It's not work anymore.
> …
> My code 
> 
> \`\`\`
> \#include \<ESPmDNS.h\>
> \#include \<WiFi.h\>
>  
> const char\* ssid = "My SSID";
> const char\* password = "My Password";
>  
> void setup() {
> Serial.begin(115200);
>  
> WiFi.begin(ssid, password);
> WiFi.setHostname("myHost\_ESP");
> while (WiFi.status() != WL\_CONNECTED) {
> delay(500);
> Serial.print("Connecting...\\n\\n");
> }
>  
> if(mdns\_init()!= ESP\_OK){
> Serial.println("mDNS failed to start");
> return;
> }
>  
> int nrOfServices = MDNS.queryService("http", "tcp");
>    
> if (nrOfServices == 0) {
> Serial.println("No services were found.");
> } 
>    
> else {
>      
> Serial.print("Number of services found: ");
> Serial.println(nrOfServices);
>      
> for (int i = 0; i \< nrOfServices; i=i+1) {
>  
> Serial.println("---------------");
>        
> Serial.print("Hostname: ");
> Serial.println(MDNS.hostname(i));
>  
> Serial.print("IP address: ");
> Serial.println(MDNS.IP(i));
>  
> Serial.print("Port: ");
> Serial.println(MDNS.port(i));
>  
> Serial.println("---------------");
> }
> }
>  
> }
>  
> void loop() {}
> 
> \`\`\`
> \`\`\`

デフォルトのホストネームは `"espressif"`  
以下 `"ESP32"`へ変更する場合

```auto
#define HOSTNAME "ESP32"

void setup(void){
 ....
 .....
    WiFi.setHostname(HOSTNAME); 
.....
.....
    Serial.println(WiFi.getHostname());
.....
.....

```

> **[ESP32 Useful Wi-Fi Library Functions (Arduino IDE) | Random Nerd Tutorials](https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/)**
>
> ESP32 Wi-Fi Functions: scan networks, connect to a network, connection strength, connection status, reconnect, Wi-Fi status and modes, IP address, static IP address and more.

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 6 月 12 日午前 8:15 UTC](https://forum.ficusonline.com/t/topic/409/15 "2021-06-12T08:15:43Z")

</div>

### Arduino IDEからVS Code+PlatformIOへ移行

> **[Installing PlatformIO IDE Extension on VS Code - Getting Started with VS Code...](https://randomnerdtutorials.com/vs-code-platformio-ide-esp32-esp8266-arduino/#2)**
>
> Learn how to program the ESP32 and ESP8266 NodeMCU boards using VS Code (Microsoft Visual Studio Code) with PlatformIO IDE extension. We cover how to install the software on Windows, Mac OS X or Ubuntu operating systems. | Program the ESP32 and...

C(.ini)またはC++(.cpp)適用ルールの相違に注意

 ![vscode_platformio](https://forum.ficusonline.com/uploads/default/original/1X/0a8ab50e1fecc9e9e5ed38bbac3827ca7803a6e5.png)

Arduinoライブラリを直接ダウンロードして `platformio.ini` で指定

> **[How to include Arduino Library in PlatformIO?](https://community.platformio.org/t/how-to-include-arduino-library-in-platformio/15146/15)**
>
> This is the correct way. Make sure to build the project once and rebuild the intellisense (Ctrl+Shift+P → Rebuild IntelliSense). If you are mixing .c with .cpp code, odn’t forget to add the standard extern "C" declarations.

### PlatformIO ESP32 FreeRTOS 設定ファイル

`.platformio/packages/framework-arduinoespressif32/tools/sdk/include/freertos/freertos/FreeRTOSConfig.h`

[https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/esp32/include/freertos/include/freertos/FreeRTOSConfig.h](https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/esp32/include/freertos/include/freertos/FreeRTOSConfig.h)

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 6 月 14 日午前 8:13 UTC](https://forum.ficusonline.com/t/topic/409/16 "2021-06-14T08:13:49Z")

</div>

> <https://stackoverflow.com/questions/20944784/why-is-conversion-from-string-literal-to-char-valid-in-c-but-invalid-in-c/20944858#20944858>

Arduino IDE —\> C  
PlatformIO —\> C++

以下PlatformIOでもビルド・コンパイルできるが、警告がでるので

```auto
char* p = "abc"; // valid in C, invalid in C++

```

C++でも適合するように変更

```auto
char const *p = "abc"; // valid and safe in either C or C++.

```

上記と同義

```auto
const char *p = "abc"; // valid and safe in either C or C++.

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 6 月 23 日午後 12:15 UTC](https://forum.ficusonline.com/t/topic/409/17 "2021-06-23T12:15:38Z")

</div>

## MQTTサーバへデータをパブリッシュする機能追加

以下MQTTクライアントによるデータパブリッシュ機能を追加した最終スケッチをアップロード。

> **[GitHub - capitalfuse/capitalfuse-esp32\_max30102\_mlx90614\_oled\_hcd: Health Check Device for measuring heart rate, spo2...](https://github.com/capitalfuse/capitalfuse-esp32_max30102_mlx90614_oled_hcd)**
>
> Health Check Device for measuring heart rate, spo2 and body temperature

このデータをホームオートメーションシステムにより条件処理し、IP電話により異常を知らせます。

 ![hcd_system](https://forum.ficusonline.com/uploads/default/original/1X/7314dc9f48b0d0203a852fe860ef4a2684e445c9.png)

* * *

**APモードによる設定画面は以下の通り。**

* * *

## WiFiマネージャーメニュー

 ![hcd_ap_mode](https://forum.ficusonline.com/uploads/default/original/1X/20456f6e3c2d450ad318ac6a782fc1f7243cc260.png)

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 7 月 2 日午前 1:22 UTC](https://forum.ficusonline.com/t/topic/409/18 "2021-07-02T01:22:52Z")

</div>

### OLED表示部回転

筐体内でのOLED表示部のレイアウト上、表示を90度または180度回転させる必要がある場合に **Adafruit GFX Graphics Library** の`void setRotation(uint8_t rotation);`を利用します。

> **[Adafruit GFX Graphics Library](https://learn.adafruit.com/adafruit-gfx-graphics-library/rotating-the-display)**
>
> The Adafruit\_GFX library for Arduino provides a common syntax and set of graphics functions for all of our LCD and OLED displays.

> **[GitHub - adafruit/Adafruit-GFX-Library: Adafruit GFX graphics core Arduino library, this...](https://github.com/adafruit/Adafruit-GFX-Library)**
>
> Adafruit GFX graphics core Arduino library, this is the 'core' class that all our other graphics libraries derive from

`setup()`内で以下のように指定

```auto
.....
.....
#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);
.....
.....
setup() {
.....
.....
display.clearDisplay();
display.setTextColor(WHITE);
display.setRotation(2);
.....
.....
}

```

- `setRotation(0) ---> 0 deg`
- `setRotation(1) ---> 90 deg`
- `setRotation(1) ---> 180 deg`
- `setRotation(3) ---> 270 deg`

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 7 月 2 日午後 2:16 UTC](https://forum.ficusonline.com/t/topic/409/19 "2021-07-02T14:16:07Z")

</div>

### OLED表示エラー対応

OLEDの表示が大きく崩れる現象。クロック周波数を下げることで対応。

> **[Bot Challenge](https://esp32.com/ext/espressif/stupidbots/challenge.php?n=MTc3MzI5Njk5Ny4yMDU1NjI5MTIw&c=196b45c7ebaccda3899eb61c1cec2ebdff7054fadff58b46eb8a6f9ba30fe400&h=59d7471044133611f107dc95de4107a863a870a2e2c2b1bdc76ee8a07c753d88&from=L3ZpZXd0b3BpYy5waHA%2FdD04Njc0)**

`Projects/esp32_max30102_mlx90614_wifi/.pio/libdeps/esp32dev/Adafruit SSD1306/Adafruit_SSD1306.h`

```auto
class Adafruit_SSD1306 : public Adafruit_GFX {
public:
  // NEW CONSTRUCTORS -- recommended for new projects
  Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi = &Wire,
                   int8_t rst_pin = -1, uint32_t clkDuring = 400000UL,
                   uint32_t clkAfter = 100000UL);

```

上記クラスからクロック周波数を再定義

```auto
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET, 100000UL, 100000UL);

```

---

<div class="post-metadata">

**Author:** ![tk-fuse](https://forum.ficusonline.com/user_avatar/forum.ficusonline.com/tk-fuse/32/255_2.png) [@tk-fuse](https://forum.ficusonline.com/u/tk-fuse)\
**Post date:** [2021 年 7 月 5 日午前 9:16 UTC](https://forum.ficusonline.com/t/topic/409/21 "2021-07-05T09:16:00Z")

</div>

### ESP32 Arduino WiFiライブラリ

データパブリッシュ時のみWiFi+MQTT接続するようにWifi接続をON/OFF制御します。

> <https://github.com/espressif/arduino-esp32/issues/1077>
>
> Hi,
> I need, in order to save battery, turn off the radio and the BLE. How can I… do that on Arduino IDE?

> <https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WiFiBlueToothSwitch/WiFiBlueToothSwitch.ino>

**Station Mode起動**

```auto
WiFi.begin(STA_SSID, STA_PASS);

```

**WiFi停止**

```auto
WiFi.mode(WIFI_OFF);

```

[次のページ](https://forum.ficusonline.com/t/topic/409.md?page=2)
