1. MAX30100传感器驱动
MAX30100传感器是以IIC为接口的心率和血氧浓度传感器,这个传感器可以存储16个样本,每个样本包含1个RED和1个IR数据,每个RED和IR又由2个字节组成,一般为了使用方便,不使用内部的64bytes的FIFO,而只使用4bytes的FIFO,即每次RED和IR数据采集完成后,就直接读取其值,去读完后将读写指针归零,这样就相当于只用了4字节的FIFO。根据官方给出的程序,可以看出也是用的这种方式。
完整工程(STM32F013):
链接:https://pan.baidu.com/s/1nMlNo9XDEonXbudeDR7Q1A
提取码:8zd1
在编写IIC驱动时,应该注意IIC的时钟速度不得大于400KHz,同时为了提高代码的移植性,这里使用IIC软件模拟的方法:
MAX30100.c源文件:
/*============================================================================
* 文件编码: Encoding in UTF-8 without signature *
* 文件描述: 该文件是MAX30100的源文件,主要实现用户API *
* *
* 基本功能: 实现用户API *
* 作者 : ElecM *
* 版权 : 遵守BSD开源协议 *
* 时间 : 2020.10.5 *
* 责任声明: 使用该代码所造成的一切后果均由使用者承担,作者不负任何法律责任。*
============================================================================*/
#include "MAX30100.h"
static void DelayUs(unsigned char us)
{
unsigned int i;
while(us--)
{
for(i=0;i<100;i++)
;
}
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_GPIO_Init(void)
* 描述:MAX30100引脚初始化
* 参数:无
* 返回:无
*---------------------------------------------------------------------------*/
static void MAX30100_GPIO_Init(void)
{
RCC->APB2ENR |= (1<<2); //开启GPIOA时钟
GPIOA->CRL &= 0XFFFFF000; //设置GPIOA2为上拉输入,设置GPIOA0,1为推挽输出
GPIOA->CRL |= 0X00000833;
GPIOA->ODR |= ((1<<2)|(1<<1)|(1<<0));//挂起IIC总线
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_IIC_Start(void)
* 描述:IIC主机发出开始条件
* 参数:无
* 返回:无
*---------------------------------------------------------------------------*/
static void MAX30100_IIC_Start(void)
{
MAX30100_SDA_SET_OUT
MAX30100_SCL_SET_H //IIC开始条件
MAX30100_SDA_SET_H
DelayUs(5);
MAX30100_SDA_SET_L
DelayUs(5);
MAX30100_SCL_SET_L
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_IIC_SendByte(void)
* 描述:IIC主机往从机发送数据
* 参数:无
* 返回:0:发送成功; 1:发送失败
*---------------------------------------------------------------------------*/
static void MAX30100_IIC_SendByte(MAX30100_U8 byte)
{
MAX30100_U8 i;
MAX30100_SDA_SET_OUT
MAX30100_SCL_SET_L
for(i=0;i<8;i++) //传输单个Byte
{
if(byte &(0x80))
MAX30100_SDA_SET_H
else
MAX30100_SDA_SET_L
byte <<= 1;
DelayUs(5);
MAX30100_SCL_SET_H
DelayUs(5);
MAX30100_SCL_SET_L
DelayUs(5);
}
MAX30100_SDA_SET_H
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_IIC_ReceiveByte(void)
* 描述:IIC主机从从机接收数据
* 参数:无
* 返回:byte:接收到的数据
*---------------------------------------------------------------------------*/
static MAX30100_U8 MAX30100_IIC_ReceiveByte(void)
{
MAX30100_U8 i,byte=0;
MAX30100_SDA_SET_IN
MAX30100_SDA_SET_H
for(i=0;i<8;i++) //传输单个Byte
{
byte <<= 1;
MAX30100_SCL_SET_L
DelayUs(5);
MAX30100_SCL_SET_H
DelayUs(5);
if(MAX30100_SDA_GET)
byte |= 0x01;
DelayUs(5);
}
MAX30100_SCL_SET_L
MAX30100_SDA_SET_H
DelayUs(5);
return byte;
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_IIC_Stop(void)
* 描述:IIC主机发出停止条件
* 参数:无
* 返回:无
*---------------------------------------------------------------------------*/
static void MAX30100_IIC_Stop(void)
{
MAX30100_SDA_SET_OUT
MAX30100_SCL_SET_L
MAX30100_SDA_SET_L
DelayUs(5);
MAX30100_SCL_SET_H
DelayUs(5);
MAX30100_SDA_SET_H
DelayUs(5);
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_IIC_WaitACK(void)
* 描述:IIC主机等待从机应答
* 参数:无
* 返回:1:从机无应答; 0:从机应答
*---------------------------------------------------------------------------*/
static MAX30100_U8 MAX30100_IIC_WaitACK(void)
{
MAX30100_U16 time_out=0;
MAX30100_SCL_SET_L
MAX30100_SDA_SET_IN
DelayUs(5);
MAX30100_SCL_SET_H
DelayUs(5);
while(MAX30100_SDA_GET)
{
time_out ++ ;
if(time_out>2000)
{
MAX30100_IIC_Stop();
return 1;
}
}
MAX30100_SCL_SET_L
DelayUs(5);
return 0;
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_IIC_SendACK(void)
* 描述:IIC主机发出应答
* 参数:无
* 返回:无
*---------------------------------------------------------------------------*/
static void MAX30100_IIC_SendACK(void)
{
MAX30100_SDA_SET_OUT
MAX30100_SCL_SET_L
DelayUs(5);
MAX30100_SDA_SET_L
DelayUs(5);
MAX30100_SCL_SET_H
DelayUs(5);
MAX30100_SDA_SET_L
DelayUs(5);
MAX30100_SCL_SET_L
DelayUs(5);
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_IIC_SendNACK(void)
* 描述:IIC主机发出非应答
* 参数:无
* 返回:无
*---------------------------------------------------------------------------*/
static void MAX30100_IIC_SendNACK(void)
{
MAX30100_SDA_SET_OUT
MAX30100_SCL_SET_L
DelayUs(5);
MAX30100_SDA_SET_H
DelayUs(5);
MAX30100_SCL_SET_H
DelayUs(5);
MAX30100_SCL_SET_L
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_WriteDatas(MAX30100_U8 Reg, MAX30100_U8 byte)
* 描述:把数据写入到相应的寄存器中
* 参数:无
* 返回:0:写入成功; 1:写入失败
*---------------------------------------------------------------------------*/
static MAX30100_U8 MAX30100_WriteByte(MAX30100_U8 Reg, MAX30100_U8 Byte)
{
MAX30100_IIC_Start();
MAX30100_IIC_SendByte(0XAE);
MAX30100_IIC_WaitACK();
MAX30100_IIC_SendByte(Reg);
MAX30100_IIC_WaitACK();
MAX30100_IIC_SendByte(Byte);
MAX30100_IIC_WaitACK();
MAX30100_IIC_Stop();
return 0;
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_ReadOneByte(MAX30100_U8 Reg, MAX30100_U8 *Byte)
* 描述:把数据从寄存器中读出一个字节
* 参数:Reg:要读的寄存器; *Byte:读出数据
* 返回:0:读出成功; 1:读出失败
*---------------------------------------------------------------------------*/
static MAX30100_U8 MAX30100_ReadOneByte(MAX30100_U8 Reg, MAX30100_U8 *Byte)
{
MAX30100_IIC_Start();
MAX30100_IIC_SendByte(0XAE);
if(MAX30100_IIC_WaitACK())
return 1;
MAX30100_IIC_SendByte(Reg);
if(MAX30100_IIC_WaitACK())
return 1;
MAX30100_IIC_Start();
MAX30100_IIC_SendByte(0XAF);
if(MAX30100_IIC_WaitACK())
return 1;
(*Byte) = MAX30100_IIC_ReceiveByte();
MAX30100_IIC_SendNACK();
MAX30100_IIC_Stop();
return 0;
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_ReadBytes(MAX30100_U8 Reg, MAX30100_U8 *Bytes,MAX30100_U8 Len)
* 描述:把数据从寄存器中读出
* 参数:Reg:要读的寄存器; *Bytes:读出数据流; Len:读取的数据长度
* 返回:0:读出成功; 1:读出失败
*---------------------------------------------------------------------------*/
static MAX30100_U8 MAX30100_ReadBytes(MAX30100_U8 Reg, MAX30100_U8 *Bytes,MAX30100_U8 Len)
{
MAX30100_U8 i,data;
MAX30100_IIC_Start();
MAX30100_IIC_SendByte(0XAE);
if(MAX30100_IIC_WaitACK())
return 1;
MAX30100_IIC_SendByte(Reg);
if(MAX30100_IIC_WaitACK())
return 1;
MAX30100_IIC_Start();
MAX30100_IIC_SendByte(0XAF);
if(MAX30100_IIC_WaitACK())
return 1;
for(i=0;i<Len-1;i++)
{
*Bytes = MAX30100_IIC_ReceiveByte();
MAX30100_IIC_SendACK();
Bytes++;
}
*Bytes = MAX30100_IIC_ReceiveByte();
MAX30100_IIC_SendNACK();
MAX30100_IIC_Stop();
return 0;
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_Init(void)
* 描述:MAX30100初始化
* 参数:无
* 返回:无
*---------------------------------------------------------------------------*/
void MAX30100_Init(void)
{
MAX30100_GPIO_Init();
MAX30100_WriteByte(MAX30100_CONFIG_MODE,0X40); //复位
MAX30100_WriteByte(MAX30100_INT_ENB,0X50); //打开MAX30100的温度和SPO2数据采集完成中断
MAX30100_WriteByte(MAX30100_FIFO_WR_PTR,0X00); //清零FIFO写寄存器
MAX30100_WriteByte(MAX30100_FIFO_OVF_COUNTER,0X00);//清零FIFO溢出计数器
MAX30100_WriteByte(MAX30100_FIFO_RD_PTR,0X00); //清零FIFO读寄存器
MAX30100_WriteByte(MAX30100_CONFIG_MODE,0X0b); //开启SPO2和温度功能和SPO2功能
MAX30100_WriteByte(MAX30100_CONFIG_SPO2,0X43); //开启高精度ADC,设置脉冲宽度为800uS
MAX30100_WriteByte(MAX30100_CONFIG_LED,0X88); //设置RED和IR的LED电流均为27.1mA
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_ReadIrAndRedData(MAX30100_U16 *Ir,MAX30100_U16 *Red)
* 描述:读取MAX30100中的RED和IR数据
* 参数:*Ir:读出的红外数据指针; *Red:读出的红光数据指针
* 返回:1:读出失败; 0:读取成功
*---------------------------------------------------------------------------*/
MAX30100_U8 MAX30100_ReadIrAndRedData(MAX30100_U16 *Ir,MAX30100_U16 *Red)
{
MAX30100_U8 LED[4];
if(!MAX30100_ReadBytes(0X05,LED,4))
{
*Red = ((LED[0]<<8)|(LED[1]));
*Ir = ((LED[2]<<8)|(LED[3]));
}
else
return 1;
MAX30100_WriteByte(MAX30100_FIFO_WR_PTR,0X00); //读写指针清零
MAX30100_WriteByte(MAX30100_FIFO_OVF_COUNTER,0X00);
MAX30100_WriteByte(MAX30100_FIFO_RD_PTR,0X00);
return 0;
}
/*---------------------------------------------------------------------------
* 函数:MAX30100_CheckIrAndRedDataRdy(void)
* 描述:检查RED和IR数据是否就位
* 参数:无
* 返回:1:未就位; 0:已就位
*---------------------------------------------------------------------------*/
MAX30100_U8 MAX30100_CheckIrAndRedDataRdy(void)
{
MAX30100_U8 status;
if(!MAX30100_INT_GET)
{
MAX30100_ReadOneByte(0x00,&status);
if(status &0x10)
return 0;
else
return 1;
}
else
return 1;
}
根据MAX30100的数据手册,可以编写如下头文件:
MAX30100.h头文件:
/*============================================================================
* 文件编码: Encoding in UTF-8 without signature *
* 文件描述: 该文件是MAX30100的头文件,主要提供用户API *
* *
* 基本功能: 提供MAX30100读取FIFO的API *
* 作者 : ElecM *
* 版权 : 遵守BSD开源协议 *
* 时间 : 2020.10.5 *
* 责任声明: 使用该代码所造成的一切后果均由使用者承担,作者不负任何法律责任。*
============================================================================*/
#ifndef __MAX30100_H__
#define __MAX30100_H__
#include "stm32f10x.h"
/*---------------------------------------------------------------------------
* MAX30100模块引脚定义
* 参考MAX30100模块手册
---------------------------------------------------------------------------*/
#define MAX30100_INT_GET (GPIOA->IDR&(1<<2))
#define MAX30100_SDA_SET_IN GPIOA->CRL&=0XFFFFFFF0;GPIOA->CRL|=0X00000008;GPIOA->ODR|=(1<<0);//设置为上拉输入
#define MAX30100_SDA_SET_OUT GPIOA->CRL&=0XFFFFFFF0;GPIOA->CRL|=0X00000003;//设置为推挽输出
#define MAX30100_SDA_SET_L GPIOA->ODR&=(~(1<<0));
#define MAX30100_SDA_SET_H GPIOA->ODR|=((1<<0));
#define MAX30100_SDA_GET (GPIOA->IDR&(1<<0))
#define MAX30100_SCL_SET_L GPIOA->ODR&=(~(1<<1));
#define MAX30100_SCL_SET_H GPIOA->ODR|=((1<<1));
/*---------------------------------------------------------------------------
* MAX30100使用变量类型定义
* 参考编译器中使用的类型
---------------------------------------------------------------------------*/
typedef vu8 MAX30100_U8;
typedef vu16 MAX30100_U16;
typedef vu32 MAX30100_U32;
typedef vs8 MAX30100_S8;
typedef vs16 MAX30100_S16;
typedef vs32 MAX30100_S32;
/*---------------------------------------------------------------------------
* MAX30100寄存器声明
* 参考MAX30100数据手册
---------------------------------------------------------------------------*/
//状态寄存器
#define MAX30100_INT_STATUS 0X00
#define MAX30100_INT_ENB 0X01
//FIFO寄存器
#define MAX30100_FIFO_WR_PTR 0X02
#define MAX30100_FIFO_OVF_COUNTER 0X03
#define MAX30100_FIFO_RD_PTR 0X04
#define MAX30100_FIFO_DATA 0X05
//配置寄存器
#define MAX30100_CONFIG_MODE 0X06
#define MAX30100_CONFIG_SPO2 0X07
#define MAX30100_CONFIG_LED 0X09
//温度相关寄存器
#define MAX30100_TEMP_INT 0X16
#define MAX30100_TEMP_FRAC 0X17
#define MAX30100_TEMP_EN 0X21
//芯片ID寄存器
#define MAX30100_REV_ID 0XFE
#define MAX30100_PART_ID 0XFF
/*###########################################################################
# MAX30100用户API #
#----------------------------------------------------------------------------
# 使用方法(查询):首先初始化MAX30100模块,再检测模块是否准备就绪, #
# 随后就可以读取MAX30100内部FIFO数据。 #
#----------------------------------------------------------------------------
# 使用方法(中断):在中断服务函数中设置标志位,根据标志位响应读取数据 #
# #
# by ElecM 2020.10.05 #
###########################################################################*/
void MAX30100_Init(void);
MAX30100_U8 MAX30100_CheckIrAndRedDataRdy(void);
MAX30100_U8 MAX30100_ReadIrAndRedData(MAX30100_U16 *Ir,MAX30100_U16 *Red);
#endif
在主函数中不停地检测数据就绪状态,然后把采集的数据送入缓冲区,最先显示在串口中。
2.MAX30100的PPG数据分析
上面得到的是RED和IR数据是PPG数据,这些数据不能直接使用,由于MAX30100内部集成了滤波器,所以滤波这部分就不需要考虑了,我们重点关注PPG数据的脉冲解析。将串口打印的PPG数据输入到MATLAB中,如下图所示:
图片组的最后一张由于位置变化造成的,在使用MAX30100时,手指最好不要直接按压在RED上,可以用一片玻璃或者硬透明塑料片垫在传感器上方,然后将手压在玻璃片上,这样测得的效果相对较好。
从图中可以发现,波形的峰值是有一些规律的,我们现在需要做的工作就是识别出波形中出现脉冲的个数,然后根据这个数值来计算心率。官方虽然给出了计算心率的代码,但是经过测试发现效果并不理想,在下一篇中将介绍一些效果相对较好的算法来实现心率的计算。
有关心率解析算法可以参考我的另一篇博客:https://blog.csdn.net/C_ElecM/article/details/119302061