
基于ai8051温湿度计+心率血氧仪
简介
基于ai8051设计了温湿度计+心率血氧仪。主控采用AI8051U-34K64-QFP48,0.91寸IIC接口屏显示环境的温湿度,0.96寸SPI接口屏显示max30102采集的心率和血样数据。
简介:基于ai8051设计了温湿度计+心率血氧仪。主控采用AI8051U-34K64-QFP48,0.91寸IIC接口屏显示环境的温湿度,0.96寸SPI接口屏显示max30102采集的心率和血样数据。开源协议
:TAPR Open Hardware License
描述
B站演示视频
https://www.bilibili.com/video/BV1xSzpYjEQ8/?vd_source=24f1befd6441a33d7b240715cb07c7b5
前言
参加Ai8051U开源创意电子设计大赛,本来是打算来白嫖个优惠券而言。未曾想一下子入坑了。我只不过来象征性学习一下Ai8051啊。没有想到做这个血氧仪一下子陷进去了,这个心率和血氧算法太难搞了,不管是官方的,还是各个论坛的。能找到的都试了。心率算法,我依然觉得不满意。血氧数据还凑合吧。
算法路上依然艰难啊。
希望各位大佬多指点,提供一些好的算法来验证一下。
个人能力太有限了呀。
设计描述
硬件设计
主控选择
采用了STC最新研究的产品AI8051U-34K64-QFP48,性能比较强劲,个人觉得接近M3了。
具体如下:
本次使用主控的管脚并不多,原理图如下:
tpye-c供电输入+下载口
通过type-c接口供电,和程序下载。
micro USB供电+下载口
预留micro USB,也可以下载
LDO转3.3V电路
通过LDO转换,为了各个模块供电统一。
复位电路
预留了复位电路,方便程序下载时,复位后进入下载状态。
冷启动下载按键
冷启动下载按键。非常必要。
SPI显示屏幕接口
预留了多种SPI接口屏,本次使用的0.96寸的128X96,其它相同接口的屏幕可以使用。
这也是为了后续进一步学习使用,故而预留了更多的接口。
IIC显示屏幕接口
适配多数IIC接口的屏幕,本次使用的0.91寸单色屏。128X32.
温湿度传感器sht40
为了板子不浪费,预留了这个接口,不使用的时候,也可以用来当温湿度计使用。
心率血氧传感器max30102
IIC接口的传感器模块,淘宝购买。
本次的核心模块哦。
LED显示
方便调试,使用指示作用
按键
这里预留学习使用
其它电路
预留验证和学习使用,本次用不到。多打样的板子,可以用来验证多余的电路。
这是穷人的办法哦。
PCB设计
整体如下:
从3D上可以看出,这个设计是可以兼容多种接口的屏幕哦。
这也是为了后续学习和验证各个显示模块哦。
程序设计
废话不多说,直接上干货
这里只贴主程序代码
其它驱动详见附件工程代码
#include "Ai8051U.H"
#include
#include
#include "oled.h"
#include "pic.h"
#include "i2c.h"
#include "algorithm.h"
#include "max30102.h"
#include "i2c_oled.h"
#include "sht40.h"
#define DELAY_TIME 2000
sbit LED = P5^6; //
#define MAX_BRIGHTNESS 255
#define BUFFER_LENTH 500
u32 aun_ir_buffer[BUFFER_LENTH]; // IR LED sensor data
u32 aun_red_buffer[BUFFER_LENTH]; // Red LED sensor data
s32 n_sp02; // SPO2 value
s8 ch_spo2_valid; // indicator to show if the SP02 calculation is valid
s32 n_heart_rate; // heart rate value
s8 ch_hr_valid; // indicator to show if the heart rate calculation is valid
//=====================================================================
// 函数: void delay_ms(unsigned int ms)
// 描述: 毫秒级延时函数。
// 参数: ms,要延时的ms数,自动适应主时钟.
//=====================================================================
//void delay_ms(unsigned int ms)
//{ unsigned int i;
// do{ i = MAIN_Fosc / 6000;
// while(--i);
// } while(--ms);
//}
void sys_init()
{
WTST = 0; //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
EAXFR = 1; //扩展寄存器(XFR)访问使能
CKCON = 0; //提高访问XRAM速度
NOP1();
P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口
P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口
P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口
P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口
P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口
P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口
P6M1 = 0x00; P6M0 = 0x00; //设置为准双向口
P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口
}
/**** 主函数入口 ************************/
void main(void)
{
//u8 i;
float dat= -3.145;
u32 un_min, un_max, un_prev_data;
int i;
s32 n_brightness;
float f_temp;
u8 temp[6];
char ss[32];
sys_init(); //系统初始化
EA = 1; //开总中断
OLED_Init(); //OLED初始化
OLED_DisplayTurn(0);
OLED_LightSet(250);
I2C_OLED_Init();
I2C_OLED_DisplayTurn(1);
//I2C_OLED_ShowString(0,0, "Temperature:",8,1);//6*8
I2C_OLED_ShowString(0,0, "ET:",8,1);//6*8
//I2C_OLED_ShowString(64,0, "Humidity:",8,1);//6*8
I2C_OLED_ShowString(73,0, "RH:",8,1);//6*8
I2C_OLED_Refresh();
OLED_BuffClear(); //清除全部缓存
OLED_BuffShowString(0,0,"HeartRate:",0); //显示数据
OLED_BuffShowString(0,4,"BloodOxyg:",0); //显示数据
OLED_BuffShow();
MAX_IIC_Init();
max30102_init(); // max30102
n_brightness = 0;
un_min = 0x3FFFF;
un_max = 0;
// 读取前500个样本,并确定信号范围
for(i = 0; i < BUFFER_LENTH; i++)
{
// 等待中断引脚
while(MAX30102_INT == 1);
max30102_FIFO_ReadBytes(REG_FIFO_DATA, temp); // 读取传感器数据,赋值到temp中
aun_red_buffer[i] = (long)((long)((long)temp[0] & 0x03) << 16) | (long)temp[1] << 8 | (long)temp[2]; // 将值合并得到实际数字
aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03) << 16) | (long)temp[4] << 8 | (long)temp[5]; // 将值合并得到实际数字
if(un_min > aun_red_buffer[i])
un_min = aun_red_buffer[i]; // 更新信号最小值
if(un_max < aun_red_buffer[i])
un_max = aun_red_buffer[i]; // 更新信号最大值
}
un_prev_data = aun_red_buffer[i];
// 计算前500个样本后的心率和SpO2(样本的前5秒)
maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, BUFFER_LENTH, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid);
while (1)
{
// 读取和计算max30102数据,总体用缓存的500组数据分析,实际每读取100组新数据分析一次
un_min = 0x3FFFF;
un_max = 0;
// 将前100组样本转储到内存中(实际没有),并将后400组样本移到顶部,将100-500缓存数据移位到0-400
for(i = 100; i < 500; i++)
{
aun_red_buffer[i - 100] = aun_red_buffer[i]; // 将100-500缓存数据移位到0-400
aun_ir_buffer[i - 100] = aun_ir_buffer[i]; // 将100-500缓存数据移位到0-400
// 更新信号的最小值和最大值
if(un_min > aun_red_buffer[i]) // 寻找移位后0-400中的最小值
un_min = aun_red_buffer[i];
if(un_max < aun_red_buffer[i]) // 寻找移位后0-400中的最大值
un_max = aun_red_buffer[i];
}
// 在计算心率前取100组样本,取的数据放在400-500缓存数组中
for(i = 400; i < 500; i++)
{
un_prev_data = aun_red_buffer[i - 1]; // 临时记录上一次读取数据
// 等待中断引脚
while(MAX30102_INT == 1);
max30102_FIFO_ReadBytes(REG_FIFO_DATA, temp); // 读取传感器数据,赋值到temp中
aun_red_buffer[i] = (long)((long)((long)temp[0] & 0x03) << 16) | (long)temp[1] << 8 | (long)temp[2]; // 将值合并得到实际数字,数组400-500为新读取数据
aun_ir_buffer[i] = (long)((long)((long)temp[3] & 0x03) << 16) | (long)temp[4] << 8 | (long)temp[5]; // 将值合并得到实际数字,数组400-500为新读取数据
if(aun_red_buffer[i] > un_prev_data)
{ // 用新获取的一个数值与上一个数值对比
f_temp = aun_red_buffer[i] - un_prev_data;
f_temp /= (un_max - un_min);
f_temp *= MAX_BRIGHTNESS; // 公式(心率曲线)=(新数值-旧数值)/(最大值-最小值)*255
n_brightness -= (int)f_temp;
if(n_brightness < 0)
n_brightness = 0;
}
else
{
f_temp = un_prev_data - aun_red_buffer[i];
f_temp /= (un_max - un_min);
f_temp *= MAX_BRIGHTNESS; // 公式(心率曲线)=(旧数值-新数值)/(最大值-最小值)*255
n_brightness += (int)f_temp;
if(n_brightness > MAX_BRIGHTNESS)
n_brightness = MAX_BRIGHTNESS;
}
}
maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, BUFFER_LENTH, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); // 传入500个心率和血氧数据计算传感器检测结论,反馈心率和血氧测试结果
if((1 == ch_hr_valid) && (1 == ch_spo2_valid) && (n_heart_rate < 120) && (n_sp02 < 101))
{
//printf("HeartRate=%i, BloodOxyg=%i\r\n", n_heart_rate, n_sp02);
OLED_BuffClear(); //清除全部缓存
OLED_BuffShowString(0,0,"HeartRate:",0); //显示数据
OLED_BuffShowNum(0,2,n_heart_rate,0);
OLED_BuffShowString(0,4,"BloodOxyg:",0); //显示数据
OLED_BuffShowNum(0,6,n_sp02,0);
OLED_BuffShow(); //将缓存写入显示屏显示
//delay_ms(DELAY_TIME);
}
i=SHT40_Cal();
if(!i)
{
sprintf(ss,"%3.1f",SHT40_GetTEM());
I2C_OLED_ShowString(0,8,ss,24,1);//6*8
I2C_OLED_ShowString(48,20,"C",8,1);//6*8
sprintf(ss,"%3.1f%",SHT40_GetHUM());
//I2C_OLED_ShowString(0,0,ss,8,1);//6*8
I2C_OLED_ShowString(73,8,ss,24,1);//6*8
I2C_OLED_ShowString(121,20,"%",8,1);//6*8
}else{
sprintf(ss,"%d",i);
I2C_OLED_ShowString(0,8,ss,24,1);//6*8
}
I2C_OLED_Refresh();
LED=!LED;
// delay_ms(500);
}
}
温湿度sht40输出验证
这个移植过程也花了不少时间,之前模块在其它主控下,ESP,梁山派、stm32等都很正常,但是在这个里面就是无法输出正确的湿度数据。
后来才发现IIC时序有差异,真的是坑死了我。
花了一周多的时间,这个教训太深刻了呀。
心率血氧输出验证
通过上位机接收,可以看到,数据中心率变化比较大。
目前还没有找到更好算法来验证。
血氧数据比较稳定。
算法优化路漫漫长啊。
整体调试验证
终于把程序集成在一起验证了,虽然坎坷,数据也不是最令人满意的。
但是这却是让我记忆深刻的,也是让我对这个小作品付出很大心血的。
距离好的产品,还有很大差距,一点一滴的去积累吧。
2024年11月30日21:46:23完成
设计图

BOM


评论