当前位置:   article > 正文

STM32单片机+MAX30102心率血氧传感器+OLED屏幕+心率血氧数据发送到串口调试助手+源代码_stm32 心率血氧 oled 云平台

stm32 心率血氧 oled 云平台

目录

一、MAX30102心率血氧传感器介绍

二、MAX30102心率血氧传感器接线说明

三、测试说明、MAX30102心率血氧传感器驱动代码

四、串口调试助手把采集的心率血氧数据发送出来

五、串口调试助手软件下载

六、源代码

七、MAX30102心率血氧传感器原理图资料


一、MAX30102心率血氧传感器介绍

        MAX30102是一款集成了心率和血氧测量功能的传感器模块。它采用了专利的心率血氧测量技术,能够通过反射光谱测量来实时监测心率和血氧饱和度。

        MAX30102传感器模块内置了红外光和红光LED,通过这两种光源照射皮肤,然后利用光电二极管接收反射光信号。这个过程中,红外光主要用于血氧测量,而红光则用于心率检测。

        MAX30102传感器模块具有高灵敏度、低功耗和噪声抑制等特点,适用于可穿戴设备、医疗设备、运动健康监测等领域。它可以通过I2C接口与微控制器进行通信,并且提供了丰富的寄存器接口和算法库,方便开发者进行定制化的应用开发。

二、MAX30102心率血氧传感器接线说明

VIN(+):电源正极接口,外接电源 5V电源正极,接单片机的5v引脚

GND(-):电源负极接口,外接电源负极或地线GND,接单片机的GND

   SCL       :时钟线,接单片机的PB7引脚

   SDA      :数据线,接单片机的PB8引脚

   INT       :接单片机的PB9引脚

三、测试说明、MAX30102心率血氧传感器驱动代码

编译环境:keil5

测试单片机: STM32F103C8T6

功能:功能1:采集心率、血氧数据在OLED屏幕上显示出来

           功能2:把采集心率、血氧数据发送到串口调试助手上

MAX30102.c文件编写心率血氧传感器驱动代码如下:

  1. #include "MAX30102.h"
  2. #include "MAX30102_IIC.h"
  3. #include "delay.h"
  4. u8 max30102_Bus_Write(u8 Register_Address, u8 Word_Data)
  5. {
  6. /* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */
  7. /* 第1步:发起I2C总线启动信号 */
  8. IIC_Start();
  9. /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
  10. IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */
  11. /* 第3步:发送ACK */
  12. if (IIC_Wait_Ack() != 0)
  13. {
  14. goto cmd_fail; /* EEPROM器件无应答 */
  15. }
  16. /* 第4步:发送字节地址 */
  17. IIC_Send_Byte(Register_Address);
  18. if (IIC_Wait_Ack() != 0)
  19. {
  20. goto cmd_fail; /* EEPROM器件无应答 */
  21. }
  22. /* 第5步:开始写入数据 */
  23. IIC_Send_Byte(Word_Data);
  24. /* 第6步:发送ACK */
  25. if (IIC_Wait_Ack() != 0)
  26. {
  27. goto cmd_fail; /* EEPROM器件无应答 */
  28. }
  29. /* 发送I2C总线停止信号 */
  30. IIC_Stop();
  31. return 1; /* 执行成功 */
  32. cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
  33. /* 发送I2C总线停止信号 */
  34. IIC_Stop();
  35. return 0;
  36. }
  37. u8 max30102_Bus_Read(u8 Register_Address)
  38. {
  39. u8 data;
  40. /* 第1步:发起I2C总线启动信号 */
  41. IIC_Start();
  42. /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
  43. IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */
  44. /* 第3步:发送ACK */
  45. if (IIC_Wait_Ack() != 0)
  46. {
  47. goto cmd_fail; /* EEPROM器件无应答 */
  48. }
  49. /* 第4步:发送字节地址, */
  50. IIC_Send_Byte((uint8_t)Register_Address);
  51. if (IIC_Wait_Ack() != 0)
  52. {
  53. goto cmd_fail; /* EEPROM器件无应答 */
  54. }
  55. /* 第6步:重新启动I2C总线。下面开始读取数据 */
  56. IIC_Start();
  57. /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
  58. IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */
  59. /* 第8步:发送ACK */
  60. if (IIC_Wait_Ack() != 0)
  61. {
  62. goto cmd_fail; /* EEPROM器件无应答 */
  63. }
  64. /* 第9步:读取数据 */
  65. {
  66. data = IIC_Read_Byte(0); /* 读1个字节 */
  67. IIC_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
  68. }
  69. /* 发送I2C总线停止信号 */
  70. IIC_Stop();
  71. return data; /* 执行成功 返回data值 */
  72. cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
  73. /* 发送I2C总线停止信号 */
  74. IIC_Stop();
  75. return 0;
  76. }
  77. void max30102_FIFO_ReadWords(u8 Register_Address,u16 Word_Data[][2],u8 count)
  78. {
  79. u8 i=0;
  80. u8 no = count;
  81. u8 data1, data2;
  82. /* 第1步:发起I2C总线启动信号 */
  83. IIC_Start();
  84. /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
  85. IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */
  86. /* 第3步:发送ACK */
  87. if (IIC_Wait_Ack() != 0)
  88. {
  89. goto cmd_fail; /* EEPROM器件无应答 */
  90. }
  91. /* 第4步:发送字节地址, */
  92. IIC_Send_Byte((uint8_t)Register_Address);
  93. if (IIC_Wait_Ack() != 0)
  94. {
  95. goto cmd_fail; /* EEPROM器件无应答 */
  96. }
  97. /* 第6步:重新启动I2C总线。下面开始读取数据 */
  98. IIC_Start();
  99. /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
  100. IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */
  101. /* 第8步:发送ACK */
  102. if (IIC_Wait_Ack() != 0)
  103. {
  104. goto cmd_fail; /* EEPROM器件无应答 */
  105. }
  106. /* 第9步:读取数据 */
  107. while (no)
  108. {
  109. data1 = IIC_Read_Byte(0);
  110. IIC_Ack();
  111. data2 = IIC_Read_Byte(0);
  112. IIC_Ack();
  113. Word_Data[i][0] = (((u16)data1 << 8) | data2); //
  114. data1 = IIC_Read_Byte(0);
  115. IIC_Ack();
  116. data2 = IIC_Read_Byte(0);
  117. if(1==no)
  118. IIC_NAck(); /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
  119. else
  120. IIC_Ack();
  121. Word_Data[i][1] = (((u16)data1 << 8) | data2);
  122. no--;
  123. i++;
  124. }
  125. /* 发送I2C总线停止信号 */
  126. IIC_Stop();
  127. cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
  128. /* 发送I2C总线停止信号 */
  129. IIC_Stop();
  130. }
  131. void max30102_FIFO_ReadBytes(u8 Register_Address,u8* Data)
  132. {
  133. max30102_Bus_Read(REG_INTR_STATUS_1);
  134. max30102_Bus_Read(REG_INTR_STATUS_2);
  135. /* 第1步:发起I2C总线启动信号 */
  136. IIC_Start();
  137. /* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
  138. IIC_Send_Byte(max30102_WR_address | I2C_WR); /* 此处是写指令 */
  139. /* 第3步:发送ACK */
  140. if (IIC_Wait_Ack() != 0)
  141. {
  142. goto cmd_fail; /* EEPROM器件无应答 */
  143. }
  144. /* 第4步:发送字节地址, */
  145. IIC_Send_Byte((uint8_t)Register_Address);
  146. if (IIC_Wait_Ack() != 0)
  147. {
  148. goto cmd_fail; /* EEPROM器件无应答 */
  149. }
  150. /* 第6步:重新启动I2C总线。下面开始读取数据 */
  151. IIC_Start();
  152. /* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
  153. IIC_Send_Byte(max30102_WR_address | I2C_RD); /* 此处是读指令 */
  154. /* 第8步:发送ACK */
  155. if (IIC_Wait_Ack() != 0)
  156. {
  157. goto cmd_fail; /* EEPROM器件无应答 */
  158. }
  159. /* 第9步:读取数据 */
  160. Data[0] = IIC_Read_Byte(1);
  161. Data[1] = IIC_Read_Byte(1);
  162. Data[2] = IIC_Read_Byte(1);
  163. Data[3] = IIC_Read_Byte(1);
  164. Data[4] = IIC_Read_Byte(1);
  165. Data[5] = IIC_Read_Byte(0);
  166. /* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
  167. /* 发送I2C总线停止信号 */
  168. IIC_Stop();
  169. cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
  170. /* 发送I2C总线停止信号 */
  171. IIC_Stop();
  172. // u8 i;
  173. // u8 fifo_wr_ptr;
  174. // u8 firo_rd_ptr;
  175. // u8 number_tp_read;
  176. // //Get the FIFO_WR_PTR
  177. // fifo_wr_ptr = max30102_Bus_Read(REG_FIFO_WR_PTR);
  178. // //Get the FIFO_RD_PTR
  179. // firo_rd_ptr = max30102_Bus_Read(REG_FIFO_RD_PTR);
  180. //
  181. // number_tp_read = fifo_wr_ptr - firo_rd_ptr;
  182. //
  183. // //for(i=0;i<number_tp_read;i++){
  184. // if(number_tp_read>0){
  185. // IIC_ReadBytes(max30102_WR_address,REG_FIFO_DATA,Data,6);
  186. // }
  187. //max30102_Bus_Write(REG_FIFO_RD_PTR,fifo_wr_ptr);
  188. }
  189. void max30102_init(void)
  190. {
  191. GPIO_InitTypeDef GPIO_InitStructure;
  192. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  193. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
  194. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  195. GPIO_Init(GPIOB, &GPIO_InitStructure);
  196. IIC_Init();
  197. max30102_reset();
  198. // max30102_Bus_Write(REG_MODE_CONFIG, 0x0b); //mode configuration : temp_en[3] MODE[2:0]=010 HR only enabled 011 SP02 enabled
  199. // max30102_Bus_Write(REG_INTR_STATUS_2, 0xF0); //open all of interrupt
  200. // max30102_Bus_Write(REG_INTR_STATUS_1, 0x00); //all interrupt clear
  201. // max30102_Bus_Write(REG_INTR_ENABLE_2, 0x02); //DIE_TEMP_RDY_EN
  202. // max30102_Bus_Write(REG_TEMP_CONFIG, 0x01); //SET TEMP_EN
  203. // max30102_Bus_Write(REG_SPO2_CONFIG, 0x47); //SPO2_SR[4:2]=001 100 per second LED_PW[1:0]=11 16BITS
  204. // max30102_Bus_Write(REG_LED1_PA, 0x47);
  205. // max30102_Bus_Write(REG_LED2_PA, 0x47);
  206. max30102_Bus_Write(REG_INTR_ENABLE_1,0xc0); // INTR setting
  207. max30102_Bus_Write(REG_INTR_ENABLE_2,0x00);
  208. max30102_Bus_Write(REG_FIFO_WR_PTR,0x00); //FIFO_WR_PTR[4:0]
  209. max30102_Bus_Write(REG_OVF_COUNTER,0x00); //OVF_COUNTER[4:0]
  210. max30102_Bus_Write(REG_FIFO_RD_PTR,0x00); //FIFO_RD_PTR[4:0]
  211. max30102_Bus_Write(REG_FIFO_CONFIG,0x0f); //sample avg = 1, fifo rollover=false, fifo almost full = 17
  212. max30102_Bus_Write(REG_MODE_CONFIG,0x03); //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
  213. max30102_Bus_Write(REG_SPO2_CONFIG,0x27); // SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS)
  214. max30102_Bus_Write(REG_LED1_PA,0x24); //Choose value for ~ 7mA for LED1
  215. max30102_Bus_Write(REG_LED2_PA,0x24); // Choose value for ~ 7mA for LED2
  216. max30102_Bus_Write(REG_PILOT_PA,0x7f); // Choose value for ~ 25mA for Pilot LED
  217. // // Interrupt Enable 1 Register. Set PPG_RDY_EN (data available in FIFO)
  218. // max30102_Bus_Write(0x2, 1<<6);
  219. // // FIFO configuration register
  220. // // SMP_AVE: 16 samples averaged per FIFO sample
  221. // // FIFO_ROLLOVER_EN=1
  222. // //max30102_Bus_Write(0x8, 1<<4);
  223. // max30102_Bus_Write(0x8, (0<<5) | 1<<4);
  224. // // Mode Configuration Register
  225. // // SPO2 mode
  226. // max30102_Bus_Write(0x9, 3);
  227. // // SPO2 Configuration Register
  228. // max30102_Bus_Write(0xa,
  229. // (3<<5) // SPO2_ADC_RGE 2 = full scale 8192 nA (LSB size 31.25pA); 3 = 16384nA
  230. // | (1<<2) // sample rate: 0 = 50sps; 1 = 100sps; 2 = 200sps
  231. // | (3<<0) // LED_PW 3 = 411μs, ADC resolution 18 bits
  232. // );
  233. // // LED1 (red) power (0 = 0mA; 255 = 50mA)
  234. // max30102_Bus_Write(0xc, 0xb0);
  235. // // LED (IR) power
  236. // max30102_Bus_Write(0xd, 0xa0);
  237. }
  238. void max30102_reset(void)
  239. {
  240. max30102_Bus_Write(REG_MODE_CONFIG,0x40);
  241. max30102_Bus_Write(REG_MODE_CONFIG,0x40);
  242. }
  243. void maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)
  244. {
  245. // char ach_i2c_data[2];
  246. // ach_i2c_data[0]=uch_addr;
  247. // ach_i2c_data[1]=uch_data;
  248. //
  249. // IIC_WriteBytes(I2C_WRITE_ADDR, ach_i2c_data, 2);
  250. IIC_Write_One_Byte(I2C_WRITE_ADDR,uch_addr,uch_data);
  251. }
  252. void maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)
  253. {
  254. // char ch_i2c_data;
  255. // ch_i2c_data=uch_addr;
  256. // IIC_WriteBytes(I2C_WRITE_ADDR, &ch_i2c_data, 1);
  257. //
  258. // i2c.read(I2C_READ_ADDR, &ch_i2c_data, 1);
  259. //
  260. // *puch_data=(uint8_t) ch_i2c_data;
  261. IIC_Read_One_Byte(I2C_WRITE_ADDR,uch_addr,puch_data);
  262. }
  263. void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)
  264. {
  265. uint32_t un_temp;
  266. unsigned char uch_temp;
  267. char ach_i2c_data[6];
  268. *pun_red_led=0;
  269. *pun_ir_led=0;
  270. //read and clear status register
  271. maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);
  272. maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);
  273. IIC_ReadBytes(I2C_WRITE_ADDR,REG_FIFO_DATA,(u8 *)ach_i2c_data,6);
  274. un_temp=(unsigned char) ach_i2c_data[0];
  275. un_temp<<=16;
  276. *pun_red_led+=un_temp;
  277. un_temp=(unsigned char) ach_i2c_data[1];
  278. un_temp<<=8;
  279. *pun_red_led+=un_temp;
  280. un_temp=(unsigned char) ach_i2c_data[2];
  281. *pun_red_led+=un_temp;
  282. un_temp=(unsigned char) ach_i2c_data[3];
  283. un_temp<<=16;
  284. *pun_ir_led+=un_temp;
  285. un_temp=(unsigned char) ach_i2c_data[4];
  286. un_temp<<=8;
  287. *pun_ir_led+=un_temp;
  288. un_temp=(unsigned char) ach_i2c_data[5];
  289. *pun_ir_led+=un_temp;
  290. *pun_red_led&=0x03FFFF; //Mask MSB [23:18]
  291. *pun_ir_led&=0x03FFFF; //Mask MSB [23:18]
  292. }

 MAX30102_IIC.c文件编写心率血氧传感器IIC代码如下:

  1. #include "MAX30102_IIC.h"
  2. #include "delay.h"
  3. //初始化IIC
  4. void IIC_Init(void)
  5. {
  6. GPIO_InitTypeDef GPIO_InitStructure;
  7. //RCC->APB2ENR|=1<<4;//先使能外设IO PORTC时钟
  8. RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
  9. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8;
  10. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
  11. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  12. GPIO_Init(GPIOB, &GPIO_InitStructure);
  13. IIC_SCL=1;
  14. IIC_SDA=1;
  15. }
  16. //产生IIC起始信号
  17. void IIC_Start(void)
  18. {
  19. SDA_OUT(); //sda线输出
  20. IIC_SDA=1;
  21. IIC_SCL=1;
  22. Delay_us(4);
  23. IIC_SDA=0;//START:when CLK is high,DATA change form high to low
  24. Delay_us(4);
  25. IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
  26. }
  27. //产生IIC停止信号
  28. void IIC_Stop(void)
  29. {
  30. SDA_OUT();//sda线输出
  31. IIC_SCL=0;
  32. IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
  33. Delay_us(4);
  34. IIC_SCL=1;
  35. IIC_SDA=1;//发送I2C总线结束信号
  36. Delay_us(4);
  37. }
  38. //等待应答信号到来
  39. //返回值:1,接收应答失败
  40. // 0,接收应答成功
  41. u8 IIC_Wait_Ack(void)
  42. {
  43. u8 ucErrTime=0;
  44. SDA_IN(); //SDA设置为输入
  45. IIC_SDA=1;Delay_us(1);
  46. IIC_SCL=1;Delay_us(1);
  47. while(READ_SDA)
  48. {
  49. ucErrTime++;
  50. if(ucErrTime>250)
  51. {
  52. IIC_Stop();
  53. return 1;
  54. }
  55. }
  56. IIC_SCL=0;//时钟输出0
  57. return 0;
  58. }
  59. //产生ACK应答
  60. void IIC_Ack(void)
  61. {
  62. IIC_SCL=0;
  63. SDA_OUT();
  64. IIC_SDA=0;
  65. Delay_us(2);
  66. IIC_SCL=1;
  67. Delay_us(2);
  68. IIC_SCL=0;
  69. }
  70. //不产生ACK应答
  71. void IIC_NAck(void)
  72. {
  73. IIC_SCL=0;
  74. SDA_OUT();
  75. IIC_SDA=1;
  76. Delay_us(2);
  77. IIC_SCL=1;
  78. Delay_us(2);
  79. IIC_SCL=0;
  80. }
  81. //IIC发送一个字节
  82. //返回从机有无应答
  83. //1,有应答
  84. //0,无应答
  85. void IIC_Send_Byte(u8 txd)
  86. {
  87. u8 t;
  88. SDA_OUT();
  89. IIC_SCL=0;//拉低时钟开始数据传输
  90. for(t=0;t<8;t++)
  91. {
  92. IIC_SDA=(txd&0x80)>>7;
  93. txd<<=1;
  94. Delay_us(2); //对TEA5767这三个延时都是必须的
  95. IIC_SCL=1;
  96. Delay_us(2);
  97. IIC_SCL=0;
  98. Delay_us(2);
  99. }
  100. }
  101. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
  102. u8 IIC_Read_Byte(unsigned char ack)
  103. {
  104. unsigned char i,receive=0;
  105. SDA_IN();//SDA设置为输入
  106. for(i=0;i<8;i++ )
  107. {
  108. IIC_SCL=0;
  109. Delay_us(2);
  110. IIC_SCL=1;
  111. receive<<=1;
  112. if(READ_SDA)receive++;
  113. Delay_us(1);
  114. }
  115. if (!ack)
  116. IIC_NAck();//发送nACK
  117. else
  118. IIC_Ack(); //发送ACK
  119. return receive;
  120. }
  121. void IIC_WriteBytes(u8 WriteAddr,u8* data,u8 dataLength)
  122. {
  123. u8 i;
  124. IIC_Start();
  125. IIC_Send_Byte(WriteAddr); //发送写命令
  126. IIC_Wait_Ack();
  127. for(i=0;i<dataLength;i++)
  128. {
  129. IIC_Send_Byte(data[i]);
  130. IIC_Wait_Ack();
  131. }
  132. IIC_Stop();//产生一个停止条件
  133. Delay_ms(10);
  134. }
  135. void IIC_ReadBytes(u8 deviceAddr, u8 writeAddr,u8* data,u8 dataLength)
  136. {
  137. u8 i;
  138. IIC_Start();
  139. IIC_Send_Byte(deviceAddr); //发送写命令
  140. IIC_Wait_Ack();
  141. IIC_Send_Byte(writeAddr);
  142. IIC_Wait_Ack();
  143. IIC_Send_Byte(deviceAddr|0X01);//进入接收模式
  144. IIC_Wait_Ack();
  145. for(i=0;i<dataLength-1;i++)
  146. {
  147. data[i] = IIC_Read_Byte(1);
  148. }
  149. data[dataLength-1] = IIC_Read_Byte(0);
  150. IIC_Stop();//产生一个停止条件
  151. Delay_ms(10);
  152. }
  153. void IIC_Read_One_Byte(u8 daddr,u8 addr,u8* data)
  154. {
  155. IIC_Start();
  156. IIC_Send_Byte(daddr); //发送写命令
  157. IIC_Wait_Ack();
  158. IIC_Send_Byte(addr);//发送地址
  159. IIC_Wait_Ack();
  160. IIC_Start();
  161. IIC_Send_Byte(daddr|0X01);//进入接收模式
  162. IIC_Wait_Ack();
  163. *data = IIC_Read_Byte(0);
  164. IIC_Stop();//产生一个停止条件
  165. }
  166. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data)
  167. {
  168. IIC_Start();
  169. IIC_Send_Byte(daddr); //发送写命令
  170. IIC_Wait_Ack();
  171. IIC_Send_Byte(addr);//发送地址
  172. IIC_Wait_Ack();
  173. IIC_Send_Byte(data); //发送字节
  174. IIC_Wait_Ack();
  175. IIC_Stop();//产生一个停止条件
  176. Delay_ms(10);
  177. }

  Algorithm.c文件编写心率血氧传感器算法代码如下:

  1. #include "algorithm.h"
  2. const uint16_t auw_hamm[31]={ 41, 276, 512, 276, 41 }; //Hamm= long16(512* hamming(5)');
  3. //uch_spo2_table is computed as -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;
  4. const uint8_t uch_spo2_table[184]={ 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99,
  5. 99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
  6. 100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97,
  7. 97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91,
  8. 90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81,
  9. 80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67,
  10. 66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50,
  11. 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29,
  12. 28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5,
  13. 3, 2, 1 } ;
  14. static int32_t an_dx[ BUFFER_SIZE-MA4_SIZE]; // delta
  15. static int32_t an_x[ BUFFER_SIZE]; //ir
  16. static int32_t an_y[ BUFFER_SIZE]; //red
  17. void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid,
  18. int32_t *pn_heart_rate, int8_t *pch_hr_valid)
  19. /**
  20. * \brief Calculate the heart rate and SpO2 level
  21. * \par Details
  22. * By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the ratio for the SPO2 is computed.
  23. * Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.
  24. * Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each ratio.
  25. *
  26. * \param[in] *pun_ir_buffer - IR sensor data buffer
  27. * \param[in] n_ir_buffer_length - IR sensor data buffer length
  28. * \param[in] *pun_red_buffer - Red sensor data buffer
  29. * \param[out] *pn_spo2 - Calculated SpO2 value
  30. * \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid
  31. * \param[out] *pn_heart_rate - Calculated heart rate value
  32. * \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid
  33. *
  34. * \retval None
  35. */
  36. {
  37. uint32_t un_ir_mean ,un_only_once ;
  38. int32_t k ,n_i_ratio_count;
  39. int32_t i, s, m, n_exact_ir_valley_locs_count ,n_middle_idx;
  40. int32_t n_th1, n_npks,n_c_min;
  41. int32_t an_ir_valley_locs[15] ;
  42. int32_t an_exact_ir_valley_locs[15] ;
  43. int32_t an_dx_peak_locs[15] ;
  44. int32_t n_peak_interval_sum;
  45. int32_t n_y_ac, n_x_ac;
  46. int32_t n_spo2_calc;
  47. int32_t n_y_dc_max, n_x_dc_max;
  48. int32_t n_y_dc_max_idx, n_x_dc_max_idx;
  49. int32_t an_ratio[5],n_ratio_average;
  50. int32_t n_nume, n_denom ;
  51. // remove DC of ir signal
  52. un_ir_mean =0;
  53. for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;
  54. un_ir_mean =un_ir_mean/n_ir_buffer_length ;
  55. for (k=0 ; k<n_ir_buffer_length ; k++ ) an_x[k] = pun_ir_buffer[k] - un_ir_mean ;
  56. // 4 pt Moving Average
  57. for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
  58. n_denom= ( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3]);
  59. an_x[k]= n_denom/(int32_t)4;
  60. }
  61. // get difference of smoothed IR signal
  62. for( k=0; k<BUFFER_SIZE-MA4_SIZE-1; k++)
  63. an_dx[k]= (an_x[k+1]- an_x[k]);
  64. // 2-pt Moving Average to an_dx
  65. for(k=0; k< BUFFER_SIZE-MA4_SIZE-2; k++){
  66. an_dx[k] = ( an_dx[k]+an_dx[k+1])/2 ;
  67. }
  68. // hamming window
  69. // flip wave form so that we can detect valley with peak detector
  70. for ( i=0 ; i<BUFFER_SIZE-HAMMING_SIZE-MA4_SIZE-2 ;i++){
  71. s= 0;
  72. for( k=i; k<i+ HAMMING_SIZE ;k++){
  73. s -= an_dx[k] *auw_hamm[k-i] ;
  74. }
  75. an_dx[i]= s/ (int32_t)1146; // divide by sum of auw_hamm
  76. }
  77. n_th1=0; // threshold calculation
  78. for ( k=0 ; k<BUFFER_SIZE-HAMMING_SIZE ;k++){
  79. n_th1 += ((an_dx[k]>0)? an_dx[k] : ((int32_t)0-an_dx[k])) ;
  80. }
  81. n_th1= n_th1/ ( BUFFER_SIZE-HAMMING_SIZE);
  82. // peak location is acutally index for sharpest location of raw signal since we flipped the signal
  83. maxim_find_peaks( an_dx_peak_locs, &n_npks, an_dx, BUFFER_SIZE-HAMMING_SIZE, n_th1, 8, 5 );//peak_height, peak_distance, max_num_peaks
  84. n_peak_interval_sum =0;
  85. if (n_npks>=2){
  86. for (k=1; k<n_npks; k++)
  87. n_peak_interval_sum += (an_dx_peak_locs[k]-an_dx_peak_locs[k -1]);
  88. n_peak_interval_sum=n_peak_interval_sum/(n_npks-1);
  89. *pn_heart_rate=(int32_t)(6000/n_peak_interval_sum);// beats per minutes
  90. *pch_hr_valid = 1;
  91. }
  92. else {
  93. *pn_heart_rate = -999;
  94. *pch_hr_valid = 0;
  95. }
  96. for ( k=0 ; k<n_npks ;k++)
  97. an_ir_valley_locs[k]=an_dx_peak_locs[k]+HAMMING_SIZE/2;
  98. // raw value : RED(=y) and IR(=X)
  99. // we need to assess DC and AC value of ir and red PPG.
  100. for (k=0 ; k<n_ir_buffer_length ; k++ ) {
  101. an_x[k] = pun_ir_buffer[k] ;
  102. an_y[k] = pun_red_buffer[k] ;
  103. }
  104. // find precise min near an_ir_valley_locs
  105. n_exact_ir_valley_locs_count =0;
  106. for(k=0 ; k<n_npks ;k++){
  107. un_only_once =1;
  108. m=an_ir_valley_locs[k];
  109. n_c_min= 16777216;//2^24;
  110. if (m+5 < BUFFER_SIZE-HAMMING_SIZE && m-5 >0){
  111. for(i= m-5;i<m+5; i++)
  112. if (an_x[i]<n_c_min){
  113. if (un_only_once >0){
  114. un_only_once =0;
  115. }
  116. n_c_min= an_x[i] ;
  117. an_exact_ir_valley_locs[k]=i;
  118. }
  119. if (un_only_once ==0)
  120. n_exact_ir_valley_locs_count ++ ;
  121. }
  122. }
  123. if (n_exact_ir_valley_locs_count <2 ){
  124. *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range
  125. *pch_spo2_valid = 0;
  126. return;
  127. }
  128. // 4 pt MA
  129. for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){
  130. an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int32_t)4;
  131. an_y[k]=( an_y[k]+an_y[k+1]+ an_y[k+2]+ an_y[k+3])/(int32_t)4;
  132. }
  133. //using an_exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration ratio
  134. //finding AC/DC maximum of raw ir * red between two valley locations
  135. n_ratio_average =0;
  136. n_i_ratio_count =0;
  137. for(k=0; k< 5; k++) an_ratio[k]=0;
  138. for (k=0; k< n_exact_ir_valley_locs_count; k++){
  139. if (an_exact_ir_valley_locs[k] > BUFFER_SIZE ){
  140. *pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range
  141. *pch_spo2_valid = 0;
  142. return;
  143. }
  144. }
  145. // find max between two valley locations
  146. // and use ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2
  147. for (k=0; k< n_exact_ir_valley_locs_count-1; k++){
  148. n_y_dc_max= -16777216 ;
  149. n_x_dc_max= - 16777216;
  150. if (an_exact_ir_valley_locs[k+1]-an_exact_ir_valley_locs[k] >10){
  151. for (i=an_exact_ir_valley_locs[k]; i< an_exact_ir_valley_locs[k+1]; i++){
  152. if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i];n_x_dc_max_idx =i; }
  153. if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i];n_y_dc_max_idx=i;}
  154. }
  155. n_y_ac= (an_y[an_exact_ir_valley_locs[k+1]] - an_y[an_exact_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_exact_ir_valley_locs[k]); //red
  156. n_y_ac= an_y[an_exact_ir_valley_locs[k]] + n_y_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]) ;
  157. n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from raw
  158. n_x_ac= (an_x[an_exact_ir_valley_locs[k+1]] - an_x[an_exact_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_exact_ir_valley_locs[k]); // ir
  159. n_x_ac= an_x[an_exact_ir_valley_locs[k]] + n_x_ac/ (an_exact_ir_valley_locs[k+1] - an_exact_ir_valley_locs[k]);
  160. n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac; // subracting linear DC compoenents from raw
  161. n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value
  162. n_denom= ( n_x_ac *n_y_dc_max)>>7;
  163. if (n_denom>0 && n_i_ratio_count <5 && n_nume != 0)
  164. {
  165. an_ratio[n_i_ratio_count]= (n_nume*20)/n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ; ///*************************n_nume原来是*100************************//
  166. n_i_ratio_count++;
  167. }
  168. }
  169. }
  170. maxim_sort_ascend(an_ratio, n_i_ratio_count);
  171. n_middle_idx= n_i_ratio_count/2;
  172. if (n_middle_idx >1)
  173. n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median
  174. else
  175. n_ratio_average = an_ratio[n_middle_idx ];
  176. if( n_ratio_average>2 && n_ratio_average <184){
  177. n_spo2_calc= uch_spo2_table[n_ratio_average] ;
  178. *pn_spo2 = n_spo2_calc ;
  179. *pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table
  180. }
  181. else{
  182. *pn_spo2 = -999 ; // do not use SPO2 since signal ratio is out of range
  183. *pch_spo2_valid = 0;
  184. }
  185. }
  186. void maxim_find_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num)
  187. /**
  188. * \brief Find peaks
  189. * \par Details
  190. * Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE
  191. *
  192. * \retval None
  193. */
  194. {
  195. maxim_peaks_above_min_height( pn_locs, pn_npks, pn_x, n_size, n_min_height );
  196. maxim_remove_close_peaks( pn_locs, pn_npks, pn_x, n_min_distance );
  197. *pn_npks = min( *pn_npks, n_max_num );
  198. }
  199. void maxim_peaks_above_min_height(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height)
  200. /**
  201. * \brief Find peaks above n_min_height
  202. * \par Details
  203. * Find all peaks above MIN_HEIGHT
  204. *
  205. * \retval None
  206. */
  207. {
  208. int32_t i = 1, n_width;
  209. *pn_npks = 0;
  210. while (i < n_size-1){
  211. if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i-1]){ // find left edge of potential peaks
  212. n_width = 1;
  213. while (i+n_width < n_size && pn_x[i] == pn_x[i+n_width]) // find flat peaks
  214. n_width++;
  215. if (pn_x[i] > pn_x[i+n_width] && (*pn_npks) < 15 ){ // find right edge of peaks
  216. pn_locs[(*pn_npks)++] = i;
  217. // for flat peaks, peak location is left edge
  218. i += n_width+1;
  219. }
  220. else
  221. i += n_width;
  222. }
  223. else
  224. i++;
  225. }
  226. }
  227. void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)
  228. /**
  229. * \brief Remove peaks
  230. * \par Details
  231. * Remove peaks separated by less than MIN_DISTANCE
  232. *
  233. * \retval None
  234. */
  235. {
  236. int32_t i, j, n_old_npks, n_dist;
  237. /* Order peaks from large to small */
  238. maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks );
  239. for ( i = -1; i < *pn_npks; i++ ){
  240. n_old_npks = *pn_npks;
  241. *pn_npks = i+1;
  242. for ( j = i+1; j < n_old_npks; j++ ){
  243. n_dist = pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1
  244. if ( n_dist > n_min_distance || n_dist < -n_min_distance )
  245. pn_locs[(*pn_npks)++] = pn_locs[j];
  246. }
  247. }
  248. // Resort indices longo ascending order
  249. maxim_sort_ascend( pn_locs, *pn_npks );
  250. }
  251. void maxim_sort_ascend(int32_t *pn_x,int32_t n_size)
  252. /**
  253. * \brief Sort array
  254. * \par Details
  255. * Sort array in ascending order (insertion sort algorithm)
  256. *
  257. * \retval None
  258. */
  259. {
  260. int32_t i, j, n_temp;
  261. for (i = 1; i < n_size; i++) {
  262. n_temp = pn_x[i];
  263. for (j = i; j > 0 && n_temp < pn_x[j-1]; j--)
  264. pn_x[j] = pn_x[j-1];
  265. pn_x[j] = n_temp;
  266. }
  267. }
  268. void maxim_sort_indices_descend(int32_t *pn_x, int32_t *pn_indx, int32_t n_size)
  269. /**
  270. * \brief Sort indices
  271. * \par Details
  272. * Sort indices according to descending order (insertion sort algorithm)
  273. *
  274. * \retval None
  275. */
  276. {
  277. int32_t i, j, n_temp;
  278. for (i = 1; i < n_size; i++) {
  279. n_temp = pn_indx[i];
  280. for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--)
  281. pn_indx[j] = pn_indx[j-1];
  282. pn_indx[j] = n_temp;
  283. }
  284. }

四、串口调试助手把采集的心率血氧数据发送出来

  1. printf("心率= %d BPM 血氧= %d ",dis_hr,dis_spo2);
  2. Serial_SendString("%\r\n");

五、串口调试助手软件下载

串口调试助手-keil5调试工具_dht11温度传感器串口资源-CSDN文库

六、源代码

《STM32单片机+MAX30102心率血氧传感器+OLED屏幕+心率血氧数据发送到串口调试助手》源代码资源-CSDN文库

七、MAX30102心率血氧传感器原理图资料

MAX30102心率血氧传感器原理图、模块参考资料、50hz采样率的数据绘制的心率波形、芯片数据手册资源-CSDN文库

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/856015
推荐阅读
相关标签
  

闽ICP备14008679号