脈搏血氧儀是一種廣泛使用的醫(yī)療測量儀器,它是一種非侵入性和無痛的測試,可以測量我們血液中的氧飽和度水平,可以很容易地檢測到氧氣的微小變化。在當前的 Covid-19 情況下,在不與患者接觸的情況下遠程同時跟蹤多名患者的氧氣水平變得很重要。
因此,在這個項目中,我們使用MAX30100 脈搏血氧儀和 ESP32 構(gòu)建了一個脈搏血氧儀,它將跟蹤血氧水平并通過連接到 Wi-Fi 網(wǎng)絡(luò)通過互聯(lián)網(wǎng)發(fā)送數(shù)據(jù)。這樣,我們可以通過與患者保持社交距離來遠程監(jiān)控多個患者。獲得的數(shù)據(jù)將顯示為圖表,便于跟蹤和分析患者的狀況。
除 Covid-19 應用外,該項目還可廣泛用于慢性阻塞性肺病 (COPD)、哮喘、肺炎、肺癌、貧血、心臟病發(fā)作或心力衰竭,或先天性心臟缺陷。
請注意,該項目中使用的傳感器未經(jīng)過醫(yī)學評估,并且該項目未針對防故障應用進行測試。始終使用醫(yī)療級脈搏血氧儀來確定患者的脈搏和氧氣水平,并與醫(yī)生討論。這里討論的項目僅用于教育目的。
MAX30100 傳感器
MAX30100傳感器是集成脈搏血氧儀和心率監(jiān)測模塊。它與 I2C 數(shù)據(jù)線通信并向主機微控制器單元提供SpO2 和脈搏信息。它使用光電探測器、光學元件,其中紅色、綠色 IR LED 調(diào)制 LED 脈沖。LED 電流可配置為 0 至 50mA。下圖顯示了 MAX30100 傳感器。
上述傳感器模塊在 1.8V 至 5.5V 范圍內(nèi)工作。I2C 引腳的上拉電阻包含在模塊中。
所需組件
WiFi 連接
ESP32
MAX30100 傳感器
Adafruit IO 用戶 ID 和自定義創(chuàng)建的儀表板(將進一步說明)
5V足夠的電源單元,額定電流至少1A
USB 數(shù)據(jù)線 Micro USB 轉(zhuǎn) USBA
帶有 Arduino IDE 和 ESP32 編程環(huán)境的 PC。
連接 MAX30100 血氧計與 ESP32
下面給出了帶有 ESP32 的 MAX30100的完整電路圖。
這是一個非常簡單的示意圖。ESP32 devkit C 的引腳 21 和 22 通過 SDA 和 SCL 引腳與脈搏血氧計傳感器 MAX30100 連接。血氧儀也由 ESP32 開發(fā)板上的 5V 引腳供電。我使用面包板和連接線進行連接,我的測試設(shè)置如下所示 -
帶有 ESP32 的 Adafruit IO 用于心率監(jiān)測
我們之前已經(jīng)為不同的物聯(lián)網(wǎng)應用構(gòu)建了許多 Adafruit IO 項目。Adafruit IO 是一個出色的平臺,可以在其中創(chuàng)建自定義儀表板。要為基于 IoT 的脈搏血氧計傳感器創(chuàng)建自定義儀表板,請使用以下步驟 -
第 1 步:首先在 adafruit IO 中注冊,然后提供 Fist 姓名、姓氏、電子郵件地址、用戶名和密碼。
第 2 步:登錄過程完成后,將打開空白儀表板窗口。在此部分中,我們將需要創(chuàng)建一個儀表板以各種方式顯示數(shù)據(jù)。因此,是時候創(chuàng)建新儀表板并提供儀表板的名稱和描述了。
第 3 步:填寫完上述表格后,就可以為傳感器創(chuàng)建圖形和控制部分了。
選擇開關(guān)塊。打開或關(guān)閉脈搏血氧計傳感器需要它。
第 4 步:寫下塊名稱。正如我們在上圖中看到的,切換功能將提供兩種狀態(tài),開和關(guān)。在同一過程中,選擇圖形塊。
此圖表部分需要選擇兩次,因為將顯示兩個圖表,Heart bit 和 SpO2。兩個部分都已創(chuàng)建。如我們所見,我們選擇了所有輸入和輸出功能。
第 5 步:下一步也是最后一步是擁有 adafruit 密鑰。如我們所見,我們得到了 adafruit 密鑰,這需要添加到代碼中。
Adafruit IO 現(xiàn)在已配置。是時候為這個項目準備硬件和創(chuàng)建固件了。
代碼說明
這段代碼使用了很多庫,而且都很重要。這些庫是 MAX30100 脈搏血氧計傳感器庫、用于 I2C 的Wire.h 、用于ESP32中的 WiFi 相關(guān)支持的 WiFi.h、Adafruit MQTT和MQTT 客戶端庫。完整的程序可以在本頁底部找到。
上面提到的那些庫包含在代碼的開頭。
?
#include#include #include #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" #include "MAX30100_PulseOximeter.h" //使用 arduino 內(nèi)置 MAX30100 lib ( https://github.com/oxullo/Arduino-MAX30100 )
?
接下來的兩個定義是 WLAN SSID 和 WLAN 密碼。這必須準確,ESP32 將使用它來連接 WiFi 網(wǎng)絡(luò)。
?
#define WLAN_SSID "xxxxxxxxx" #define WLAN_PASS "2581xxxxxx2"
?
接下來,我們定義了 Adafruit io 定義。
?
#define AIO_UPDATE_RATE_SEC 5 #define AIO_SERVER "io.adafruit.com" #define AIO_SERVERPORT 1883 #define AIO_USERNAME "xxxxxxxxxxxxx" #define AIO_KEY "abcdefgh"
?
更新速率將每 5 秒更新一次數(shù)據(jù),服務器將是io.adafruit.com,服務器端口為 1883。用戶名和密碼將是從 adafruit IO 儀表板生成的用戶名和密碼。它對所有人都不同,需要按照 adafruit 設(shè)置部分中的說明生成。
之后定義 I2C 端口,如原理圖所示。
?
#define I2C_SDA 21 #define I2C_SCL 22
?
接下來,使用三個變量來存儲最后的報告以及 bpm 和 spo2 值。
?
uint32_t tsLastReport = 0; 浮動 bpm_dt=0; 浮動 spo2_dt = 0;
?
MQTT 使用發(fā)布-訂閱模型(發(fā)布和訂閱)。在此工作模型中,向 Adafruit 服務器提交數(shù)據(jù)的設(shè)備保持在 Adafruit IO 服務器訂閱相同數(shù)據(jù)點的發(fā)布模式。在這種效果中,每當設(shè)備發(fā)布任何新數(shù)據(jù)時,訂閱該數(shù)據(jù)的服務器都會接收數(shù)據(jù)并提供必要的操作。
當服務器發(fā)布數(shù)據(jù)并且設(shè)備訂閱它時,也會發(fā)生同樣的事情。在我們的應用程序中,設(shè)備將 SPO2 和 BPM 的數(shù)據(jù)發(fā)送到服務器,因此它發(fā)布相同的數(shù)據(jù)并從服務器接收 ON-OFF 狀態(tài),從而訂閱這個。這個東西是在下面描述的代碼片段中配置的——
?
WiFiClient客戶端; Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); Adafruit_MQTT_Subscribe sw_sub = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/switch"); // 注意 AIO 的 MQTT 路徑遵循以下格式:/feeds/ Adafruit_MQTT_Publish bpm_pub = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/bpm"); Adafruit_MQTT_Publish spo2_pub = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/SpO2");
?
在設(shè)置功能中,我們正在啟動 I2C,使用預定義的 SSID 和密碼連接 WiFi,并啟動開關(guān)狀態(tài)的 MQTT 訂閱過程(在 Adafruit IO 儀表板中創(chuàng)建的開關(guān)按鈕)。
?
無效設(shè)置() { 序列號.開始(115200); Wire.begin(I2C_SDA,I2C_SCL); WiFi.開始(WLAN_SSID,WLAN_PASS); 而(WiFi.status()!= WL_CONNECTED){ 延遲(500); Serial.print("."); } 序列號.println(); Serial.println("WiFi 連接"); Serial.println("IP地址:"); Serial.println(WiFi.localIP()); mqtt.subscribe(&sw_sub); Serial.print("正在初始化脈搏血氧儀.."); // 初始化 PulseOximeter 實例 // 故障一般是由于I2C接線不當,缺少電源 //或錯誤的目標芯片 如果(!pox.begin()){ Serial.println("失敗"); 為了(;;); } 別的 { Serial.println("成功"); } // IR LED 的默認電流為 50mA,可以更改 // 通過取消注釋以下行。檢查 MAX30100_Registers.h 中的所有 // 可用選項。 pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA); // 為節(jié)拍檢測注冊回調(diào) pox.setOnBeatDetectedCallback(onBeatDetected); stopReadPOX(); }
?
在所有這些之后,max30100 以 LED 電流設(shè)置啟動。MAX30100 頭文件中還提供不同的電流設(shè)置,用于不同的配置。心跳檢測回調(diào)函數(shù)也被啟動。完成所有這些設(shè)置后,血氧計傳感器將停止。
在循環(huán)函數(shù)中,每 5000 毫秒啟動 MQTT 連接并檢查訂閱模型。在這種情況下,如果打開開關(guān),它就會開始讀取血氧計傳感器并發(fā)布Heartbeat 和 SPO2 值的數(shù)據(jù)。如果開關(guān)關(guān)閉,它將暫停與脈搏血氧計傳感器相關(guān)的所有任務。
?
無效循環(huán)(){ MQTT_connect(); Adafruit_MQTT_Subscribe *訂閱; 而 ((訂閱 = mqtt.readSubscription(5000))) { 如果(訂閱 == &sw_sub) { Serial.print(F("得到:")); Serial.println((char *)sw_sub.lastread); if (!strcmp((char*) sw_sub.lastread, "ON")) { Serial.print(("正在啟動 POX...")); startReadPOX(); BaseType_t xReturned; 如果(poxReadTaskHld == NULL){ xReturned = xTaskCreate( poxReadTask, /* 實現(xiàn)任務的函數(shù)。*/ "pox_read", /* 任務的文本名稱。*/ 1024*3, /* 以字為單位的堆棧大小,而不是字節(jié)。*/ NULL, /* 傳遞給任務的參數(shù)。*/ 2,/* 創(chuàng)建任務的優(yōu)先級。*/ &poxReadTaskHld ); /* 用于傳遞創(chuàng)建任務的句柄。*/ } 延遲(100); 如果(mqttPubTaskHld == NULL){ xReturned = xTaskCreate( mqttPubTask, /* 實現(xiàn)任務的函數(shù)。*/ "mqttPub", /* 任務的文本名稱。*/ 1024*3, /* 以字為單位的堆棧大小,而不是字節(jié)。*/ NULL, /* 傳遞給任務的參數(shù)。*/ 2,/* 創(chuàng)建任務的優(yōu)先級。*/ &mqttPubTaskHld ); /* 用于傳遞創(chuàng)建任務的句柄。*/ } } 別的 { Serial.print(("正在停止 POX...")); // 刪除 POX 讀取任務 如果(poxReadTaskHld != NULL) vTaskDelete(poxReadTaskHld); poxReadTaskHld = NULL; } // 刪除 MQTT 發(fā)布任務 如果(mqttPubTaskHld != NULL){ vTaskDelete(mqttPubTaskHld); mqttPubTaskHld = NULL; } stopReadPOX(); } } } }
?
基于物聯(lián)網(wǎng)的脈搏血氧儀演示
電路在面包板上正確連接,下面給出的程序被上傳到 ESP32。確保在您的代碼中相應地更改 Wi-Fi 和 Adafruit 憑據(jù)以使其適合您。
與 WiFi 和 Adafruit IO 服務器連接后,它開始按預期工作。
正如我們所見,SPO2 水平顯示為 96%,心跳顯示為每分鐘 78 到 81 位。它還提供了捕獲數(shù)據(jù)的時間。
如上圖所示,開關(guān)關(guān)閉,數(shù)據(jù)為0。該項目的完整工作視頻也可以在本頁底部找到。
?
#include
#include
#include
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include "MAX30100_PulseOximeter.h" //使用 arduino 內(nèi)置 MAX30100 lib (https://github.com/oxullo/Arduino-MAX30100)
#define WLAN_SSID "xxxxxxxxx"
#define WLAN_PASS "2581xxxxxx2"
#define AIO_UPDATE_RATE_SEC 5
#define AIO_SERVER "io.adafruit.com"
#define AIO_SERVERPORT 1883? ? ? ? ? ? ? ? ?
#define AIO_USERNAME "xxxxxx"
#define AIO_KEY "abcdefgh"
#define I2C_SDA 21
#define I2C_SCL 22
TaskHandle_t poxReadTaskHld = NULL;
TaskHandle_t mqttPubTaskHld = NULL;
// PulseOximeter 是傳感器的高級接口
脈搏血氧儀痘;
uint32_t tsLastReport = 0;
浮動 bpm_dt=0;
浮動 spo2_dt = 0;
WiFiClient客戶端;
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);? ? ? ??
Adafruit_MQTT_Subscribe sw_sub = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/switch");
// 注意 AIO 的 MQTT 路徑遵循以下格式:
Adafruit_MQTT_Publish bpm_pub = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/bpm");
Adafruit_MQTT_Publish spo2_pub = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/SpO2");
// 檢測到脈沖時觸發(fā)回調(diào)(在下面注冊)
無效 onBeatDetected()
{
? ? ? ? ? ? ? ? Serial.println("Beat!")
}
/******************************************* MAX30100 讀取暫停函數(shù) * *************************************************/
無效 stopReadPOX(){
? pox.shutdown();
}
/******************************************* 啟動 MAX30100 讀取函數(shù) * *************************************************/
無效 startReadPOX(){
? pox.resume();
}
/******************************************* MAX30100 讀取任務 *** ***********************************************/
無效 poxReadTask(無效 * 參數(shù))
{
? 而(1){
? ? ? ? ? ? ? ? // 確保盡快調(diào)用更新
? ? ? ? ? ? ? ? pox.update();
? ? ? ? ? ? ? ? vTaskDelay(1/portTICK_PERIOD_MS);
? }
? poxReadTaskHld = NULL;
? vTaskDelete(NULL); //殺死自己
}
/******************************************* MQTT 發(fā)布任務 *** ***********************************************/
無效 mqttPubTask( 無效 * 參數(shù) )
{
? uint8_t sec_count=0;
? 而(1){
? ? ? ? ? ? ? ? Serial.print("心率:");
? ? ? ? ? ? ? ? 浮動 bpm_dt = pox.getHeartRate();
? ? ? ? ? ? ? ? 串行打印(bpm_dt);
? ? ? ? ? ? ? ? Serial.print("bpm / SpO2:");
? ? ? ? ? ? ? ? 浮動 spo2_dt = pox.getSpO2();
? ? ? ? ? ? ? ? 序列號.print(spo2_dt);
? ? ? ? ? ? ? ? 序列號.println("%");
? ? ? ? ? ? ? ? 如果(sec_count >= AIO_UPDATE_RATE_SEC){
? ? ? ? ? ? ? ? 如果(!bpm_pub.publish(bpm_dt)){
? ? ? ? ? ? ? ? Serial.println(F("無法發(fā)布 bmp.."));
? ? ? ? ? ? ? ? } 別的 {
? ? ? ? ? ? ? ? Serial.println(F("bmp 發(fā)布成功!"));
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? 如果(!spo2_pub.publish(spo2_dt)){
? ? ? ? ? ? ? ? Serial.println(F("未能發(fā)布 SpO2.."));
? ? ? ? ? ? ? ? } 別的 {
? ? ? ? ? ? ? ? Serial.println(F("SpO2 發(fā)布成功!"));
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? sec_count=0;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? vTaskDelay(1000 / portTICK_PERIOD_MS);
? ? ? ? ? ? ? ? 秒計數(shù)++;
? }
? mqttPubTaskHld = NULL;
? vTaskDelete(NULL); //殺死自己
}
/************************************************ MQTT 連接函數(shù) ** ****************************************************** ***/
// 根據(jù)需要連接和重新連接到 MQTT 服務器的函數(shù)。
無效 MQTT_connect() {
? int8_t ret;
? 如果 (mqtt.connected()) {
? ? ? ? ? ? ? ? 返回;
? }
? Serial.print("正在連接 MQTT...");
? uint8_t 重試次數(shù) = 3;
? 而 ((ret = mqtt.connect()) != 0) {
? ? ? ? ? ? ? ? Serial.println(mqtt.connectErrorString(ret));
? ? ? ? ? ? ? ? Serial.println("5 秒后重試 MQTT 連接...");
? ? ? ? ? ? ? ? mqtt.disconnect();
? ? ? ? ? ? ? ? 延遲(5000);
? ? ? ? ? ? ? ? 重試——;
? ? ? ? ? ? ? ? 如果(重試 == 0){
? ? ? ? ? ? ? ? 而(1);
? ? ? ? ? ? ? ? }
? }
? Serial.println("MQTT 已連接!");
}
/************************************************* *************************************************/
無效設(shè)置()
{
? ? ? ? ? ? ? ? 序列號.開始(115200);
? ? ? ? ? ? ? ? Wire.begin(I2C_SDA,I2C_SCL);
? ? ? ? ? ? ? ? WiFi.開始(WLAN_SSID,WLAN_PASS);
? ? ? ? ? ? ? ? 而(WiFi.status()!= WL_CONNECTED){
? ? ? ? ? ? ? ? 延遲(500);
? ? ? ? ? ? ? ? Serial.print(".");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? 序列號.println();
? ? ? ? ? ? ? ? Serial.println("WiFi 連接");
? ? ? ? ? ? ? ? Serial.println("IP地址:"); Serial.println(WiFi.localIP());
? ? ? ? ? ? ? ? mqtt.subscribe(&sw_sub);
? ? ? ? ? ? ? ? Serial.print("正在初始化脈搏血氧儀..");
? ? ? ? ? ? ? ? // 初始化 PulseOximeter 實例
? ? ? ? ? ? ? ? // 故障一般是由于I2C接線不當,缺少電源
? ? ? ? ? ? ? ? //或錯誤的目標芯片
? ? ? ? ? ? ? ? 如果(!pox.begin()){
? ? ? ? ? ? ? ? Serial.println("失敗");
? ? ? ? ? ? ? ? 為了(;;);
? ? ? ? ? ? ? ? } 別的 {
? ? ? ? ? ? ? ? Serial.println("成功");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? // IR LED 的默認電流為 50mA,可以更改
? ? ? ? ? ? ? ? // 通過取消注釋以下行。檢查 MAX30100_Registers.h 中的所有
? ? ? ? ? ? ? ? // 可用選項。
? ? ? ? ? ? ? ? pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);
? ? ? ? ? ? ? ? // 為節(jié)拍檢測注冊回調(diào)
? ? ? ? ? ? ? ? pox.setOnBeatDetectedCallback(onBeatDetected);
? ? ? ? ? ? ? ? stopReadPOX();
}
無效循環(huán)(){
? MQTT_connect();
? Adafruit_MQTT_Subscribe *訂閱;
? 而 ((訂閱 = mqtt.readSubscription(5000)))
? {
? ? ? ? ? ? ? ? 如果(訂閱 == &sw_sub)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? Serial.print(F("得到:"));
? ? ? ? ? ? ? ? Serial.println((char *)sw_sub.lastread);
? ? ? ? ? ? ? ? if (!strcmp((char*) sw_sub.lastread, "ON"))
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? Serial.print(("正在啟動 POX..."));
? ? ? ? ? ? ? ? startReadPOX();
? ? ? ? ? ? ? ? BaseType_t xReturned;
? ? ? ? ? ? ? ? 如果(poxReadTaskHld == NULL){
? ? ? ? ? ? ? ? xReturned = xTaskCreate(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? poxReadTask, /* 實現(xiàn)任務的函數(shù)。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "pox_read", /* 任務的文本名稱。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1024*3, /* 以字為單位的堆棧大小,而不是字節(jié)。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? NULL, /* 傳遞給任務的參數(shù)。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2,/* 創(chuàng)建任務的優(yōu)先級。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &poxReadTaskHld ); /* 用于傳遞創(chuàng)建任務的句柄。*/
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? 延遲(100);
? ? ? ? ? ? ? ? 如果(mqttPubTaskHld == NULL){
? ? ? ? ? ? ? ? xReturned = xTaskCreate(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? mqttPubTask, /* 實現(xiàn)任務的函數(shù)。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "mqttPub", /* 任務的文本名稱。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1024*3, /* 以字為單位的堆棧大小,而不是字節(jié)。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? NULL, /* 傳遞給任務的參數(shù)。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2,/* 創(chuàng)建任務的優(yōu)先級。*/
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &mqttPubTaskHld ); /* 用于傳遞創(chuàng)建任務的句柄。*/
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? 別的
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? Serial.print(("正在停止 POX..."));
? ? ? ? ? ? ? ? // 刪除 POX 讀取任務
? ? ? ? ? ? ? ? 如果(poxReadTaskHld != NULL){
? ? ? ? ? ? ? ? vTaskDelete(poxReadTaskHld);
? ? ? ? ? ? ? ? poxReadTaskHld = NULL;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? // 刪除 MQTT 發(fā)布任務
? ? ? ? ? ? ? ? 如果(mqttPubTaskHld != NULL){
? ? ? ? ? ? ? ? vTaskDelete(mqttPubTaskHld);
? ? ? ? ? ? ? ? mqttPubTaskHld = NULL;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? stopReadPOX();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? }
}
評論
查看更多