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