代码都是用ai和网上教程做的,用esp32c3测试能用,目前可以通过局域网控制开关,通过WebSocket读取数据,还可以在本地局域网自建echarts网页,用WebSocket获取实时心率血氧图表。
现在就是检测的心率和血氧不准,我还看不懂是哪里的问题...
arduino代码:
#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
#include <WiFi.h> // 引入WiFi 库
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
// WiFi 参数
const char* ssid = "wifi名";
const char* password = "WiFi密码";
//WiFiServer server(80); // 在端口80上创建一个web服务器
MAX30105 particleSensor;
#define MAX_BRIGHTNESS 255
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Arduino Uno不具备足够的SRAM来存储32位格式的50个红外LED数据和红色LED数据。
//为了解决这个问题,采样数据的16位MSB将被截断。样品变成16位数据。
uint16_t irBuffer[50]; //红外LED传感器数据
uint16_t redBuffer[50]; //红色LED传感器数据
#else
uint32_t irBuffer[50]; //红外LED传感器数据
uint32_t redBuffer[50]; //红色LED传感器数据
#endif
int32_t spo2; //SPO2值
int8_t validSPO2; //指示SPO2计算是否有效
int32_t heartRate; //心率值
int8_t validHeartRate; //指示心率计算是否有效
bool sensorEnabled = false; //标志位,表示传感器是否启用
void setup()
{
Serial.begin(115200); // 以115200比特/秒初始化串行通信:
// 初始化WIFI连接
Serial.println();
Serial.println();
Serial.print("连接到 ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi 连接成功");
Serial.println(WiFi.localIP());
// 初始化Web服务器
server.begin();
Serial.println("Web服务器已启动");
// 初始化传感器
Wire.begin(4, 5); // SDA = GPIO 4, SCL = GPIO 5
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //使用默认的I2C端口,400kHz的速度
{
Serial.println(F("未找到MAX30105。 请检查布线/电源。"));
while (1);
}
particleSensor.setup(55, 4, 2, 200, 411, 4096); //使用这些设置配置传感器
// 初始化结束后关闭传感器模块
particleSensor.shutDown();
sensorEnabled = false;
// 启动 Web 服务器
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
String content = "<html><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta charset=\"UTF-8\">";
content += "<head><title>MAX30102 心率检测</title></head>";
content += "<body>";
content += "<h1>MAX30102 心率检测</h1>";
content += "<div id='data-box'></div>"; //增加数据展示框
content += "<p>当前状态: " + String(sensorEnabled ? "已打开" : "已关闭") + "</p>";
if (sensorEnabled) {
content += "<a href=\"/setSensorState?state=off\"><button class=\"button\">关闭模块</button></a>";
} else {
content += "<a href=\"/setSensorState?state=on\"><button class=\"button\">开启模块</button></a>";
}
//增加WebSocket连接
content += "<script>var socket = new WebSocket('ws://' + window.location.hostname + ':80/ws');";
content += "socket.onmessage = function(event) { document.getElementById('data-box').innerHTML = event.data; };";
content += "</script>";
content += "</body>";
content += "</html>";
request->send(200, "text/html", content);
});
server.on("/setSensorState", HTTP_GET, [](AsyncWebServerRequest *request){
if (request->hasParam("state")) {
String state = request->getParam("state")->value();
if (state == "on" && !sensorEnabled) {
// 打开传感器模块
particleSensor.wakeUp();
delay(500);
sensorEnabled = true;
Serial.print(F("打开"));
} else if (state == "off" && sensorEnabled) {
// 关闭传感器模块
shutDownSensorModule();
Serial.print(F("关闭"));
}
}
request->redirect("/");
});
//启动WebSocket服务
ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len){
if(type == WS_EVT_CONNECT){
Serial.println("WebSocket client connected");
} else if(type == WS_EVT_DISCONNECT){
Serial.println("WebSocket client disconnected");
}
});
server.addHandler(&ws);
server.begin();
}
long lastActiveTime = 0;
void loop()
{
// 检查传感器是否需要关闭
if (sensorEnabled && (millis() - lastActiveTime > 60000)) {
shutDownSensorModule();
}
//读取前50个样本,并确定信号范围
for (byte i = 0 ; i < 50 ; i++)
{
while (particleSensor.available() == false) //是否有新数据?
particleSensor.check(); //检查传感器是否有新数据
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //处理完当前样本后,转到下一个样本
Serial.print(F("红光="));
Serial.print(redBuffer[i], DEC);
Serial.print(F(", 红外="));
Serial.println(irBuffer[i], DEC);
}
//在前50个样本之后计算心率和SpO2(前4秒的样本)
maxim_heart_rate_and_oxygen_saturation(irBuffer, 50, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
//无线循环地从MAX30102中采样。每1秒计算一次心率和SpO2
while (1)
{
//将内存中的前25个样本丢弃,并将最后25个样本移动到顶部
for (byte i = 25; i < 50; i++)
{
redBuffer[i - 25] = redBuffer[i];
irBuffer[i - 25] = irBuffer[i];
}
//在计算心率之前,采取25个样本集。
for (byte i = 25; i < 50; i++)
{
while (particleSensor.available() == false) //是否有新数据?
particleSensor.check(); //检查传感器是否有新数据
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //处理完当前样本后,转到下一个样本
Serial.print(F("红光="));
Serial.print(redBuffer[i], DEC);
Serial.print(F(", 红外="));
Serial.print(irBuffer[i], DEC);
Serial.print(F(", 心率="));
Serial.print(heartRate, DEC);
Serial.print(F(", HRvalid="));
Serial.print(validHeartRate, DEC);
Serial.print(F(", 血氧饱和度="));
Serial.print(spo2, DEC);
Serial.print(F(", SPO2Valid="));
Serial.println(validSPO2, DEC);
}
//收集25个新样本后,重新计算HR和SP02
maxim_heart_rate_and_oxygen_saturation(irBuffer, 50, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
sendDataToWebSocket(); //将数据推送到客户端
}
}
void sendDataToWebSocket() {
if(validSPO2 && validHeartRate) {
String data = "{\"心率\":" + String(heartRate, DEC) + ",\"血氧饱和度\":" + String(spo2, DEC) + "}";
ws.textAll(data); //调用WebSocket推送数据到客户端
} else {
Serial.println(F("无效"));
}
}
void printToSerial() {
if(validSPO2 && validHeartRate) {
Serial.print(F("心率: ")); Serial.println(heartRate, DEC);
Serial.print(F("血氧饱和度: ")); Serial.println(spo2, DEC);
} else {
Serial.println(F("无效"));
}
}
// 如果需要关闭传感器模块,则可以使用以下函数
void shutDownSensorModule() {
if (sensorEnabled) {
particleSensor.shutDown();
sensorEnabled = false;
}
}
局域网echarts心率血氧实时监测页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>心率血氧实时监测</title>
<script src="../sjwsd/style/echarts.min.js"></script>
<script src="../sjwsd/style/echarts.common.min.js"></script>
</head>
<body>
<div id="chart" style="width: 800px; height: 600px;"></div>
<script>
// 初始化 ECharts 实例
var myChart = echarts.init(document.getElementById('chart'));
// 定义初始数据
var heartRateData = [];
var spo2Data = [];
var timestampData = [];
// 初始化图表参数
myChart.setOption({
title: { text: '心率血氧实时监测' },
tooltip: { trigger: 'axis' },
legend: { data: ['心率', '血氧饱和度'] },
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: [
{
type: 'value',
name: '心率'
},
{
type: 'value',
name: '血氧饱和度'
}
],
series: [
{
name: '心率',
type: 'line',
yAxisIndex: 0,
showSymbol: false,
hoverAnimation: false,
data: heartRateData
},
{
name: '血氧饱和度',
type: 'line',
yAxisIndex: 1,
showSymbol: false,
hoverAnimation: false,
data: spo2Data
}
]
});
// 建立 WebSocket 连接
var socket = new WebSocket('ws://192.168.41.10/ws');
// 接收并处理从后端发送来的新数据,并更新图表
socket.onmessage = function (event) {
var data = JSON.parse(event.data);
var timestamp = new Date().toLocaleTimeString('en-US', { hour12: false });
timestampData.push(timestamp);
heartRateData.push(data.心率);
spo2Data.push(data.血氧饱和度);
if (timestampData.length >= 30) {
timestampData.shift();
heartRateData.shift();
spo2Data.shift();
}
myChart.setOption({
xAxis: {
data: timestampData
},
series: [
{
data: heartRateData
},
{
data: spo2Data
}
]
});
};
</script>
</body>
</html>
手机
视频链接:VID_20230401_001318.mp4(47.16 MB)