1.芯片介绍
1.1背景
Maxim公司的MAX86150是集成了血管容积图,心电图,血氧传感器和心率监视传感器等生物传感器模块,包括了内部LED,光电探测器和带环境光抑制的低噪音电子学,容易设计在移动和可穿戴设备。工作电压1.8V,并有单独电压用于内部LED,工作温度-40℃到 +85℃,能用在智能手机,平板电脑,可穿戴设备以及健身辅助设备。本文介绍MAX86150驱动开发过程,以及代码。
1.2芯片应用电路以及使用方法
1.2.1经典电路方案
1.2.2使用方法
2.IIC通信协议
2.1协议介绍
这里我结合MAX86150寄存器以及代码对IIC进行讲解。
2.1.1 背景
IIC全称Inter-Integrated Circuit。是由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备。IIC属于半双工同步通信方式。
2.1.2 IIC特点:
(1)简单性和有效性。
由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
(2)多主控(multimastering)
其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
2.1.3 IIC结构组成
IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。转自(https://blog.csdn.net/zuo_an/article/details/89139445)
2.1.4 IIC协议
IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
(1)起始信号
当时钟线SCL为高期间,数据线SDA由高到低的跳变;
(2)停止信号
当时钟线SCL为高期间,数据线SDA由低到高的跳变;
(3)应答信号
发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
2.1.5 数据发送
IIC总线上挂在的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。
2.2 基于STM32F103的IO模拟IIC驱动
void I2C_Start(void)
{
I2C_SDA_OUT();
I2C_SDA_H;
I2C_SCL_H;
delay_us(5);
I2C_SDA_L;
delay_us(6);
I2C_SCL_L;
}
//产生停止信号
void I2C_Stop(void)
{
I2C_SDA_OUT();
I2C_SCL_L;
I2C_SDA_L;
I2C_SCL_H;
delay_us(6);
I2C_SDA_H;
delay_us(6);
}
//主机产生应答信号ACK
void I2C_Ack(void)
{
I2C_SCL_L;
I2C_SDA_OUT();
I2C_SDA_L;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L;
}
//主机不产生应答信号NACK
void I2C_NAck(void)
{
I2C_SCL_L;
I2C_SDA_OUT();
I2C_SDA_H;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L;
}
//等待从机应答信号
//返回值:1 接收应答失败
// 0 接收应答成功
u8 I2C_Wait_Ack(void)
{
u8 tempTime=0;
I2C_SDA_IN();
I2C_SDA_H;
delay_us(1);
I2C_SCL_H;
delay_us(1);
while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
{
tempTime++;
if(tempTime>250)
{
I2C_Stop();
return 1;
}
}
I2C_SCL_L;
return 0;
}
//I2C 发送一个字节
void I2C_Send_Byte(u8 txd)//没问题
{
u8 i=0;
I2C_SDA_OUT();//设置引脚为输出
I2C_SCL_L;//拉低时钟开始数据传输
for(i=0;i<8;i++)
{
if((txd&0x80)>0) //0x80 1000 0000
I2C_SDA_H;
else
I2C_SDA_L;
txd<<=1;
I2C_SCL_H;
delay_us(2); //发送数据
I2C_SCL_L;
delay_us(2);
}
}
//I2C 读取一个字节
u8 I2C_Read_Byte(u8 ack)
{
u8 i=0,receive=0;
I2C_SDA_IN();
for(i=0;i<8;i++)
{
I2C_SCL_L;
delay_us(2);
I2C_SCL_H;
receive<<=1;
if(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
receive++;
delay_us(1);
}
if(ack==0)
I2C_NAck();
else
I2C_Ack();
return receive;
}
3.MAX86150寄存器详细解读以及驱动代码
3.1寄存器
看不懂?没关系,其实就几个控制寄存器加上数据存储寄存器以及标志位寄存器。
下面说重点:
3.1寄存器读写
往寄存器写入一个字节
.从寄存器读入一个字节
从寄存器读取多个字节
根据上面的时序表我们可以写出以下三个驱动函数
//往寄存器写入数据 Success!
void MAX81650_WriteReg(u8 reg_add,u8 reg_dat)
{
I2C_Start(); //启动总线
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_WRITE); //写从器件地址
I2C_Wait_Ack(); //等待应答
I2C_Send_Byte(reg_add); //写数据地址
I2C_Wait_Ack();
I2C_Send_Byte(reg_dat); //写数据
I2C_Wait_Ack(); //等待应答
I2C_Stop();
}
u8 ReadRegData(u8 reg_add)
{
unsigned char i;
u8 Read;
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_WRITE);
I2C_Wait_Ack();
I2C_Send_Byte(reg_add);
I2C_Wait_Ack();
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_READ);
I2C_Wait_Ack();
Read =I2C_Read_Byte(0);
I2C_Stop();
return Read;
}
//从寄存器读取多个数据 Success!
void MAX86150_ReadData(u8 reg_add,unsigned char*Read,u8 num)
{
unsigned char i;
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_WRITE);
I2C_Wait_Ack();
I2C_Send_Byte(reg_add);
I2C_Wait_Ack();
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_READ);
I2C_Wait_Ack();
for(i=0;i<(num-1);i++){
*Read=I2C_Read_Byte(1);
Read++;
}
*Read=I2C_Read_Byte(0);
I2C_Stop();
}
在这里告诉大家一个很好的测试设备是否正常读写的方法,我们可以用下面的函数来读取从设备的地址,用以检测MAX是否正常工作。
//功能: 读取MAX86150默认地址
u8 MAX86150ReadID(void)
{
unsigned char Re = 0;
MAX86150_ReadData(MAX86150_WHOAMI_PART_ID ,&Re,1); //读器件地址 0x1e
return Re;
}
3.2 MAX86150驱动宏定义列表
MAX86150.h //作者精心手撸
//采集数据开关
#define RD_ONE_ELM_ECG_SW 1
#define RD_ONE_ELM_PPG_SW 0 //LED
#define RD_ONE_ELM_ECG_AND_PPG (RD_ONE_ELM_PPG_SW&RD_ONE_ELM_ECG_SW)
#define MAX86150_SLAVE_ADDRESS_READ 0xBD //主机从MAX86150读地址
#define MAX86150_SLAVE_ADDRESS_WRITE 0xBC //主机往MAX86150读地址
#define MAX86150_WHOAMI_PART_ID 0xFF //MAX86150从器件地址
/*************************************************************************
*状态寄存器地址 0x00 0x01
**************************************************************************/
#define MAX86150_REG_INT_STATUS_1 0x00
#define MAX86150_REG_INT_STATUS_2 0x01
/*************************************************************************
*中断使能寄存器0x02 0x03
**************************************************************************/
#define MAX86150_REG_INT_ENABLE_1 0x02
#define MAX86150_REG_INT_ENABLE_2 0x03
/*************************************************************************
*FIFO样本写指针寄存器0x04
**************************************************************************/
#define MAX86150_FIFO_WR_PTR 0x04
#define MAX86150_FIFO_WR_PTR_MASK (0x1F << 0)
/*************************************************************************
*计算溢出丢失样本个数寄存器
**************************************************************************/
#define MAX86150_REG_OVF_CNT 0x05
/*************************************************************************
*FIFO样本读指针寄存器
**************************************************************************/
#define MAX86150_FIFO_RD_PTR 0x06
#define MAX86150_MASK_FIFO_RD_PTR (0x1F << 0)
/*************************************************************************
*FIFO Date地址 以及寄存器设置
**************************************************************************/
#define MAX86150_REG_FIFO_DATA 0x07
/*************************************************************************
*FIFO_CFG地址 以及寄存器设置0x08 CTRL_1 0x09 CTRL_2 0x0A
**************************************************************************/
#define MAX86150_REG_FIFO_CFG 0x08
#define MAX86150_FIFO_A_FULL_1 0x1
#define MAX86150_FIFO_A_FULL_MASK (0xF << 0)//15个样本
#define MAX86150_FIFO_ROLLS_ON_FULL (0x1 << 4)//????????????????????????
#define MAX86150_A_FULL_TYPE (0x1 << 5)
#define MAX86150_A_FULL_CLR (0x1 << 6)
#define MAX86150_REG_FIFO_DATA_CTRL_1 0x09 //配置FIFO每个样本类型
#define MAX86150_REG_FIFO_DATA_CTRL_2 0x0A
#define MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED1 (1<<4)
#define MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED2 (2<<4)
#define MAX86150_FIFO_DATA_CTRLX_FD2_ECG (5<<4)
#define MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED1 1
#define MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED2 2
#define MAX86150_FIFO_DATA_CTRLX_FD1_ECG 9
/*************************************************************************
*PPG LED地址 以及寄存器设置 LED_RGE 0x14 REG_LED1_PA 0x11 LED2_PA 0x12
**************************************************************************/
#define MAX86150_REG_LED_RGE 0x14 //LED1-2 RGE address
#define MAX86150_LED1_RANGE_50 0 //配置LED电流范围
#define MAX86150_LED1_RANGE_100 1
#define MAX86150_LED2_RANGE_50 (0<<2)
#define MAX86150_LED2_RANGE_100 (1<<2)
#define MAX86150_REG_LED1_PA 0x11 //LED1_PA address
#define MAX86150_REG_LED2_PA 0x12 //LED2_PA address
#define MAX86150_LEDX_RANGE_50_0uA 0
#define MAX86150_LEDX_RANGE_50_200uA 1 //配置LED流过电流
#define MAX86150_LEDX_RANGE_50_400uA 2
#define MAX86150_LEDX_RANGE_50_600uA 3
#define MAX86150_LEDX_RANGE_50_800uA 4
#define MAX86150_LEDX_RANGE_100_0uA 0
#define MAX86150_LEDX_RANGE_100_400uA 1
#define MAX86150_LEDX_RANGE_100_800uA 2
#define MAX86150_LEDX_RANGE_100_1200uA 3
#define MAX86150_LEDX_RANGE_100_1600uA 4
#define MAX86150_LEDX_RANGE_100_8mA 0x14
#define MAX86150_LEDX_RANGE_100_10mA 25
#define MAX86150_LEDX_RANGE_100_102mA 0xff
#define MAX86150_REG_LED_PILOT_PA 0x15 //邻近模式LED脉冲 ????
/*************************************************************************
*PPG Config地址 以及寄存器设置 0x0E 0x0F
**************************************************************************/
#define MAX86150_PPG_CONFIG1 0x0E //注意范围
#define MAX86150_PPG_ADC_RGE_4096 (0 << 6) //ADC采样范围7:6 nA
#define MAX86150_PPG_ADC_RGE_8192 (1 << 6)
#define MAX86150_PPG_ADC_RGE_16384 (2 << 6)
#define MAX86150_PPG_ADC_RGE_32768 (3 << 6)
#define MAX86150_PPG_SR_10HZ (0 << 2) //配置采样率5:2
#define MAX86150_PPG_SR_100HZ (4 << 2)
#define MAX86150_PPG_SR_400HZ (6 << 2)
#define MAX86150_PPG_SR_800HZ (7 << 2)
#define MAX86150_PPG_SR_1000HZ (8 << 2)
#define MAX86150_PPG_SR_1600HZ (9 << 2)
#define MAX86150_PPG_LED_PW_50US (0 << 0)//配置LED脉冲宽度1:0
#define MAX86150_PPG_LED_PW_100US (1 << 0)
#define MAX86150_PPG_LED_PW_200US (2 << 0)
#define MAX86150_PPG_LED_PW_400US (3 << 0)
#define MAX86150_PPG_CONFIG2 0x0F //控制采样个数取平均值
#define MAX86150_SMP_AVE_1 0
#define MAX86150_SMP_AVE_8 3
#define MAX86150_SMP_AVE_16 4
#define MAX86150_SMP_AVE_32 5
/*************************************************************************
接近模式中断阈值0x10
**************************************************************************/
#define MAX86150_PROX_INT_THRESH 0x10 //接近模式中断阈值
/*************************************************************************
*ECG地址 以及寄存器设置0x3C 0x3E
**************************************************************************/
#define MAX86150_ECG_CONFIG1 0x3C //ecg_configration1 address
#define MAX86150_ECG_ADC_CLK_OSR_1600 0 //采样率以及带宽
#define MAX86150_ECG_ADC_CLK_OSR_800 1
#define MAX86150_ECG_ADC_CLK_OSR_400 2
#define MAX86150_ECG_ADC_CLK_OSR_200 3
#define MAX86150_ECG_CONFIG2 0x3E //ecg_configration2 address设置增益
#define MAX86150_ECG_PGA_GAIN_1 (0<<2) //PGA放大
#define MAX86150_ECG_PGA_GAIN_2 (1<<2)
#define MAX86150_ECG_PGA_GAIN_4 (2<<2)
#define MAX86150_ECG_PGA_GAIN_8 (3<<2)
#define MAX86150_ECG_IA_GAIN_5 0 //仪表放大器放大
#define MAX86150_ECG_IA_GAIN_9 1
#define MAX86150_ECG_IA_GAIN_20 2
#define MAX86150_ECG_IA_GAIN_50 3
/*************************************************************************
*系统控制 地址 以及寄存器设置 0x0D
**************************************************************************/
#define MAX86150_SYSTEM_CTRL 0x0D
#define MAX86150_SYSTEM_RESET (0x1 << 0)
#define MAX86150_SYSTEM_SHDN (0x1 << 1)// 省电模式
#define MAX86150_SYSTEM_FIFO_EN (0x1 << 2)//enable
3.2 MAX86150驱动函数实现
然后是MAX的初始化函数:
//初始化配置ECG
void Init_MAX86150_EcgConfig(void)
{
MAX81650_WriteReg(MAX86150_ECG_CONFIG1,MAX86150_ECG_ADC_CLK_OSR_1600);
MAX81650_WriteReg(MAX86150_ECG_CONFIG2,MAX86150_ECG_PGA_GAIN_1 | MAX86150_ECG_IA_GAIN_5);
}
//初始化配置PPG以及LED
void Init_MAX86150_PPGConfig(void)
{
//配置PPG LED电流
MAX81650_WriteReg(MAX86150_REG_LED_RGE,MAX86150_LED1_RANGE_100|MAX86150_LED2_RANGE_100);//配置范围 50mA
MAX81650_WriteReg(MAX86150_REG_LED1_PA,MAX86150_LEDX_RANGE_100_10mA);//LED1 IR
MAX81650_WriteReg(MAX86150_REG_LED2_PA,MAX86150_LEDX_RANGE_100_10mA);//LED2
//配置PPG
MAX81650_WriteReg(MAX86150_PPG_CONFIG1,MAX86150_PPG_ADC_RGE_4096//*******注意此处
|MAX86150_PPG_LED_PW_50US|MAX86150_PPG_SR_400HZ);
MAX81650_WriteReg(MAX86150_PPG_CONFIG2,MAX86150_SMP_AVE_1);
}
//初始化MAX
void Init_MAX86150(void)
{
MAX81650_WriteReg(MAX86150_SYSTEM_CTRL,MAX86150_SYSTEM_FIFO_EN);//使能FIFO
MAX81650_WriteReg(MAX86150_REG_FIFO_CFG,MAX86150_FIFO_A_FULL_MASK);// FIFO配置寄存器,设置最大存放样本数
#if RD_ONE_ELM_ECG_AND_PPG
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_1,MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED1
|MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED2);
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_2,MAX86150_FIFO_DATA_CTRLX_FD1_ECG); //元素类型 0 ECG PPG_LED1 PPG_LED2
#elif RD_ONE_ELM_PPG_SW
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_1,MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED1
|MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED2);
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_2,0);
#elif RD_ONE_ELM_ECG_SW
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_1,MAX86150_FIFO_DATA_CTRLX_FD1_ECG);
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_2,0);
#endif
}
到了这里工作基本完成了,剩下的就是读取FIFO寄存器
//读取FIFO_DATE寄存器数据
u8 MAX86150_ReadFIFODate(void)
{
u8 Read;
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,&Read,1);
return Read;
}
//读取PPG
void MAX86150_ReadPPGData(u32 *Elem)
{
u8 buf[6];
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,buf,6);
Elem[0] = (((buf[0]<<16) | (buf[1]<<8) | buf[2]))&0x07ffff;//LED2 red
Elem[1] = (((buf[3]<<16) | (buf[4]<<8) | buf[5]))&0x07ffff;//Led1 ir
}
//读取一组元素ECG
u32 MAX86150_ReadECGData(void)
{
u8 buf[3];
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,buf,3);
return (((buf[0]<<16) | (buf[1]<<8) | buf[2]) & 0x03ffff);//ECG 23:18--set 0
}
//读取三组数据
void MAX86150_ReadOneSample(u32 *Elem)
{
u8 buf[9];
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,buf,9);
Elem[0] = ((buf[0]<<16) | (buf[1]<<8) | buf[2])&0x07ffff;
Elem[1] = ((buf[3]<<16) | (buf[4]<<8) | buf[5])&0x07ffff;
Elem[2] = ((buf[6]<<16) | (buf[7]<<8) | buf[8])&0x03ffff;
}
测试效果
在树莓派上用qt写了个简单的动态曲线界面,下面是测试效果
驱动开发最主要的还是要去细读芯片的开发手册,一步一个脚印,把手册吃透了再去撸代码,这样在写驱动的时候才会少犯错误,完整的代码已上传,记得点赞。
1.芯片介绍
1.1背景
Maxim公司的MAX86150是集成了血管容积图,心电图,血氧传感器和心率监视传感器等生物传感器模块,包括了内部LED,光电探测器和带环境光抑制的低噪音电子学,容易设计在移动和可穿戴设备。工作电压1.8V,并有单独电压用于内部LED,工作温度-40℃到 +85℃,能用在智能手机,平板电脑,可穿戴设备以及健身辅助设备。本文介绍MAX86150驱动开发过程,以及代码。
1.2芯片应用电路以及使用方法
1.2.1经典电路方案
1.2.2使用方法
2.IIC通信协议
2.1协议介绍
这里我结合MAX86150寄存器以及代码对IIC进行讲解。
2.1.1 背景
IIC全称Inter-Integrated Circuit。是由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备。IIC属于半双工同步通信方式。
2.1.2 IIC特点:
(1)简单性和有效性。
由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
(2)多主控(multimastering)
其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
2.1.3 IIC结构组成
IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。转自(https://blog.csdn.net/zuo_an/article/details/89139445)
2.1.4 IIC协议
IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
(1)起始信号
当时钟线SCL为高期间,数据线SDA由高到低的跳变;
(2)停止信号
当时钟线SCL为高期间,数据线SDA由低到高的跳变;
(3)应答信号
发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
2.1.5 数据发送
IIC总线上挂在的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。
2.2 基于STM32F103的IO模拟IIC驱动
void I2C_Start(void)
{
I2C_SDA_OUT();
I2C_SDA_H;
I2C_SCL_H;
delay_us(5);
I2C_SDA_L;
delay_us(6);
I2C_SCL_L;
}
//产生停止信号
void I2C_Stop(void)
{
I2C_SDA_OUT();
I2C_SCL_L;
I2C_SDA_L;
I2C_SCL_H;
delay_us(6);
I2C_SDA_H;
delay_us(6);
}
//主机产生应答信号ACK
void I2C_Ack(void)
{
I2C_SCL_L;
I2C_SDA_OUT();
I2C_SDA_L;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L;
}
//主机不产生应答信号NACK
void I2C_NAck(void)
{
I2C_SCL_L;
I2C_SDA_OUT();
I2C_SDA_H;
delay_us(2);
I2C_SCL_H;
delay_us(5);
I2C_SCL_L;
}
//等待从机应答信号
//返回值:1 接收应答失败
// 0 接收应答成功
u8 I2C_Wait_Ack(void)
{
u8 tempTime=0;
I2C_SDA_IN();
I2C_SDA_H;
delay_us(1);
I2C_SCL_H;
delay_us(1);
while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
{
tempTime++;
if(tempTime>250)
{
I2C_Stop();
return 1;
}
}
I2C_SCL_L;
return 0;
}
//I2C 发送一个字节
void I2C_Send_Byte(u8 txd)//没问题
{
u8 i=0;
I2C_SDA_OUT();//设置引脚为输出
I2C_SCL_L;//拉低时钟开始数据传输
for(i=0;i<8;i++)
{
if((txd&0x80)>0) //0x80 1000 0000
I2C_SDA_H;
else
I2C_SDA_L;
txd<<=1;
I2C_SCL_H;
delay_us(2); //发送数据
I2C_SCL_L;
delay_us(2);
}
}
//I2C 读取一个字节
u8 I2C_Read_Byte(u8 ack)
{
u8 i=0,receive=0;
I2C_SDA_IN();
for(i=0;i<8;i++)
{
I2C_SCL_L;
delay_us(2);
I2C_SCL_H;
receive<<=1;
if(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
receive++;
delay_us(1);
}
if(ack==0)
I2C_NAck();
else
I2C_Ack();
return receive;
}
3.MAX86150寄存器详细解读以及驱动代码
3.1寄存器
看不懂?没关系,其实就几个控制寄存器加上数据存储寄存器以及标志位寄存器。
下面说重点:
3.1寄存器读写
往寄存器写入一个字节
.从寄存器读入一个字节
从寄存器读取多个字节
根据上面的时序表我们可以写出以下三个驱动函数
//往寄存器写入数据 Success!
void MAX81650_WriteReg(u8 reg_add,u8 reg_dat)
{
I2C_Start(); //启动总线
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_WRITE); //写从器件地址
I2C_Wait_Ack(); //等待应答
I2C_Send_Byte(reg_add); //写数据地址
I2C_Wait_Ack();
I2C_Send_Byte(reg_dat); //写数据
I2C_Wait_Ack(); //等待应答
I2C_Stop();
}
u8 ReadRegData(u8 reg_add)
{
unsigned char i;
u8 Read;
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_WRITE);
I2C_Wait_Ack();
I2C_Send_Byte(reg_add);
I2C_Wait_Ack();
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_READ);
I2C_Wait_Ack();
Read =I2C_Read_Byte(0);
I2C_Stop();
return Read;
}
//从寄存器读取多个数据 Success!
void MAX86150_ReadData(u8 reg_add,unsigned char*Read,u8 num)
{
unsigned char i;
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_WRITE);
I2C_Wait_Ack();
I2C_Send_Byte(reg_add);
I2C_Wait_Ack();
I2C_Start();
I2C_Send_Byte(MAX86150_SLAVE_ADDRESS_READ);
I2C_Wait_Ack();
for(i=0;i<(num-1);i++){
*Read=I2C_Read_Byte(1);
Read++;
}
*Read=I2C_Read_Byte(0);
I2C_Stop();
}
在这里告诉大家一个很好的测试设备是否正常读写的方法,我们可以用下面的函数来读取从设备的地址,用以检测MAX是否正常工作。
//功能: 读取MAX86150默认地址
u8 MAX86150ReadID(void)
{
unsigned char Re = 0;
MAX86150_ReadData(MAX86150_WHOAMI_PART_ID ,&Re,1); //读器件地址 0x1e
return Re;
}
3.2 MAX86150驱动宏定义列表
MAX86150.h //作者精心手撸
//采集数据开关
#define RD_ONE_ELM_ECG_SW 1
#define RD_ONE_ELM_PPG_SW 0 //LED
#define RD_ONE_ELM_ECG_AND_PPG (RD_ONE_ELM_PPG_SW&RD_ONE_ELM_ECG_SW)
#define MAX86150_SLAVE_ADDRESS_READ 0xBD //主机从MAX86150读地址
#define MAX86150_SLAVE_ADDRESS_WRITE 0xBC //主机往MAX86150读地址
#define MAX86150_WHOAMI_PART_ID 0xFF //MAX86150从器件地址
/*************************************************************************
*状态寄存器地址 0x00 0x01
**************************************************************************/
#define MAX86150_REG_INT_STATUS_1 0x00
#define MAX86150_REG_INT_STATUS_2 0x01
/*************************************************************************
*中断使能寄存器0x02 0x03
**************************************************************************/
#define MAX86150_REG_INT_ENABLE_1 0x02
#define MAX86150_REG_INT_ENABLE_2 0x03
/*************************************************************************
*FIFO样本写指针寄存器0x04
**************************************************************************/
#define MAX86150_FIFO_WR_PTR 0x04
#define MAX86150_FIFO_WR_PTR_MASK (0x1F << 0)
/*************************************************************************
*计算溢出丢失样本个数寄存器
**************************************************************************/
#define MAX86150_REG_OVF_CNT 0x05
/*************************************************************************
*FIFO样本读指针寄存器
**************************************************************************/
#define MAX86150_FIFO_RD_PTR 0x06
#define MAX86150_MASK_FIFO_RD_PTR (0x1F << 0)
/*************************************************************************
*FIFO Date地址 以及寄存器设置
**************************************************************************/
#define MAX86150_REG_FIFO_DATA 0x07
/*************************************************************************
*FIFO_CFG地址 以及寄存器设置0x08 CTRL_1 0x09 CTRL_2 0x0A
**************************************************************************/
#define MAX86150_REG_FIFO_CFG 0x08
#define MAX86150_FIFO_A_FULL_1 0x1
#define MAX86150_FIFO_A_FULL_MASK (0xF << 0)//15个样本
#define MAX86150_FIFO_ROLLS_ON_FULL (0x1 << 4)//????????????????????????
#define MAX86150_A_FULL_TYPE (0x1 << 5)
#define MAX86150_A_FULL_CLR (0x1 << 6)
#define MAX86150_REG_FIFO_DATA_CTRL_1 0x09 //配置FIFO每个样本类型
#define MAX86150_REG_FIFO_DATA_CTRL_2 0x0A
#define MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED1 (1<<4)
#define MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED2 (2<<4)
#define MAX86150_FIFO_DATA_CTRLX_FD2_ECG (5<<4)
#define MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED1 1
#define MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED2 2
#define MAX86150_FIFO_DATA_CTRLX_FD1_ECG 9
/*************************************************************************
*PPG LED地址 以及寄存器设置 LED_RGE 0x14 REG_LED1_PA 0x11 LED2_PA 0x12
**************************************************************************/
#define MAX86150_REG_LED_RGE 0x14 //LED1-2 RGE address
#define MAX86150_LED1_RANGE_50 0 //配置LED电流范围
#define MAX86150_LED1_RANGE_100 1
#define MAX86150_LED2_RANGE_50 (0<<2)
#define MAX86150_LED2_RANGE_100 (1<<2)
#define MAX86150_REG_LED1_PA 0x11 //LED1_PA address
#define MAX86150_REG_LED2_PA 0x12 //LED2_PA address
#define MAX86150_LEDX_RANGE_50_0uA 0
#define MAX86150_LEDX_RANGE_50_200uA 1 //配置LED流过电流
#define MAX86150_LEDX_RANGE_50_400uA 2
#define MAX86150_LEDX_RANGE_50_600uA 3
#define MAX86150_LEDX_RANGE_50_800uA 4
#define MAX86150_LEDX_RANGE_100_0uA 0
#define MAX86150_LEDX_RANGE_100_400uA 1
#define MAX86150_LEDX_RANGE_100_800uA 2
#define MAX86150_LEDX_RANGE_100_1200uA 3
#define MAX86150_LEDX_RANGE_100_1600uA 4
#define MAX86150_LEDX_RANGE_100_8mA 0x14
#define MAX86150_LEDX_RANGE_100_10mA 25
#define MAX86150_LEDX_RANGE_100_102mA 0xff
#define MAX86150_REG_LED_PILOT_PA 0x15 //邻近模式LED脉冲 ????
/*************************************************************************
*PPG Config地址 以及寄存器设置 0x0E 0x0F
**************************************************************************/
#define MAX86150_PPG_CONFIG1 0x0E //注意范围
#define MAX86150_PPG_ADC_RGE_4096 (0 << 6) //ADC采样范围7:6 nA
#define MAX86150_PPG_ADC_RGE_8192 (1 << 6)
#define MAX86150_PPG_ADC_RGE_16384 (2 << 6)
#define MAX86150_PPG_ADC_RGE_32768 (3 << 6)
#define MAX86150_PPG_SR_10HZ (0 << 2) //配置采样率5:2
#define MAX86150_PPG_SR_100HZ (4 << 2)
#define MAX86150_PPG_SR_400HZ (6 << 2)
#define MAX86150_PPG_SR_800HZ (7 << 2)
#define MAX86150_PPG_SR_1000HZ (8 << 2)
#define MAX86150_PPG_SR_1600HZ (9 << 2)
#define MAX86150_PPG_LED_PW_50US (0 << 0)//配置LED脉冲宽度1:0
#define MAX86150_PPG_LED_PW_100US (1 << 0)
#define MAX86150_PPG_LED_PW_200US (2 << 0)
#define MAX86150_PPG_LED_PW_400US (3 << 0)
#define MAX86150_PPG_CONFIG2 0x0F //控制采样个数取平均值
#define MAX86150_SMP_AVE_1 0
#define MAX86150_SMP_AVE_8 3
#define MAX86150_SMP_AVE_16 4
#define MAX86150_SMP_AVE_32 5
/*************************************************************************
接近模式中断阈值0x10
**************************************************************************/
#define MAX86150_PROX_INT_THRESH 0x10 //接近模式中断阈值
/*************************************************************************
*ECG地址 以及寄存器设置0x3C 0x3E
**************************************************************************/
#define MAX86150_ECG_CONFIG1 0x3C //ecg_configration1 address
#define MAX86150_ECG_ADC_CLK_OSR_1600 0 //采样率以及带宽
#define MAX86150_ECG_ADC_CLK_OSR_800 1
#define MAX86150_ECG_ADC_CLK_OSR_400 2
#define MAX86150_ECG_ADC_CLK_OSR_200 3
#define MAX86150_ECG_CONFIG2 0x3E //ecg_configration2 address设置增益
#define MAX86150_ECG_PGA_GAIN_1 (0<<2) //PGA放大
#define MAX86150_ECG_PGA_GAIN_2 (1<<2)
#define MAX86150_ECG_PGA_GAIN_4 (2<<2)
#define MAX86150_ECG_PGA_GAIN_8 (3<<2)
#define MAX86150_ECG_IA_GAIN_5 0 //仪表放大器放大
#define MAX86150_ECG_IA_GAIN_9 1
#define MAX86150_ECG_IA_GAIN_20 2
#define MAX86150_ECG_IA_GAIN_50 3
/*************************************************************************
*系统控制 地址 以及寄存器设置 0x0D
**************************************************************************/
#define MAX86150_SYSTEM_CTRL 0x0D
#define MAX86150_SYSTEM_RESET (0x1 << 0)
#define MAX86150_SYSTEM_SHDN (0x1 << 1)// 省电模式
#define MAX86150_SYSTEM_FIFO_EN (0x1 << 2)//enable
3.2 MAX86150驱动函数实现
然后是MAX的初始化函数:
//初始化配置ECG
void Init_MAX86150_EcgConfig(void)
{
MAX81650_WriteReg(MAX86150_ECG_CONFIG1,MAX86150_ECG_ADC_CLK_OSR_1600);
MAX81650_WriteReg(MAX86150_ECG_CONFIG2,MAX86150_ECG_PGA_GAIN_1 | MAX86150_ECG_IA_GAIN_5);
}
//初始化配置PPG以及LED
void Init_MAX86150_PPGConfig(void)
{
//配置PPG LED电流
MAX81650_WriteReg(MAX86150_REG_LED_RGE,MAX86150_LED1_RANGE_100|MAX86150_LED2_RANGE_100);//配置范围 50mA
MAX81650_WriteReg(MAX86150_REG_LED1_PA,MAX86150_LEDX_RANGE_100_10mA);//LED1 IR
MAX81650_WriteReg(MAX86150_REG_LED2_PA,MAX86150_LEDX_RANGE_100_10mA);//LED2
//配置PPG
MAX81650_WriteReg(MAX86150_PPG_CONFIG1,MAX86150_PPG_ADC_RGE_4096//*******注意此处
|MAX86150_PPG_LED_PW_50US|MAX86150_PPG_SR_400HZ);
MAX81650_WriteReg(MAX86150_PPG_CONFIG2,MAX86150_SMP_AVE_1);
}
//初始化MAX
void Init_MAX86150(void)
{
MAX81650_WriteReg(MAX86150_SYSTEM_CTRL,MAX86150_SYSTEM_FIFO_EN);//使能FIFO
MAX81650_WriteReg(MAX86150_REG_FIFO_CFG,MAX86150_FIFO_A_FULL_MASK);// FIFO配置寄存器,设置最大存放样本数
#if RD_ONE_ELM_ECG_AND_PPG
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_1,MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED1
|MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED2);
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_2,MAX86150_FIFO_DATA_CTRLX_FD1_ECG); //元素类型 0 ECG PPG_LED1 PPG_LED2
#elif RD_ONE_ELM_PPG_SW
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_1,MAX86150_FIFO_DATA_CTRLX_FD2_PPG_LED1
|MAX86150_FIFO_DATA_CTRLX_FD1_PPG_LED2);
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_2,0);
#elif RD_ONE_ELM_ECG_SW
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_1,MAX86150_FIFO_DATA_CTRLX_FD1_ECG);
MAX81650_WriteReg(MAX86150_REG_FIFO_DATA_CTRL_2,0);
#endif
}
到了这里工作基本完成了,剩下的就是读取FIFO寄存器
//读取FIFO_DATE寄存器数据
u8 MAX86150_ReadFIFODate(void)
{
u8 Read;
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,&Read,1);
return Read;
}
//读取PPG
void MAX86150_ReadPPGData(u32 *Elem)
{
u8 buf[6];
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,buf,6);
Elem[0] = (((buf[0]<<16) | (buf[1]<<8) | buf[2]))&0x07ffff;//LED2 red
Elem[1] = (((buf[3]<<16) | (buf[4]<<8) | buf[5]))&0x07ffff;//Led1 ir
}
//读取一组元素ECG
u32 MAX86150_ReadECGData(void)
{
u8 buf[3];
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,buf,3);
return (((buf[0]<<16) | (buf[1]<<8) | buf[2]) & 0x03ffff);//ECG 23:18--set 0
}
//读取三组数据
void MAX86150_ReadOneSample(u32 *Elem)
{
u8 buf[9];
MAX86150_ReadData(MAX86150_REG_FIFO_DATA,buf,9);
Elem[0] = ((buf[0]<<16) | (buf[1]<<8) | buf[2])&0x07ffff;
Elem[1] = ((buf[3]<<16) | (buf[4]<<8) | buf[5])&0x07ffff;
Elem[2] = ((buf[6]<<16) | (buf[7]<<8) | buf[8])&0x03ffff;
}
测试效果
在树莓派上用qt写了个简单的动态曲线界面,下面是测试效果
驱动开发最主要的还是要去细读芯片的开发手册,一步一个脚印,把手册吃透了再去撸代码,这样在写驱动的时候才会少犯错误,完整的代码已上传,记得点赞。
1
举报