• 惊帆JF141心率血氧模块简单使用(STM32标准库代码)


    我的那个模组长这个样子

    从它的外观上也可以看出,是用下面的这个模块采集心率和血氧数据,并通过上方的串口接口将采集到的数据发送出去的。

    让我们看一下产品规格书

    和我们的设想是一致的,所以通过此处,大概也就知道了,应该如何配置单片机的串口来实现通信了

    这里我用的是PA9和PA10串口1

    并且开启接收中断,用于及时的接收数据

    1. void XU_Yang_Init(void)
    2. {
    3. //开启GPIOA和USART1的时钟
    4. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    5. RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    6. //配置PA9
    7. GPIO_InitTypeDef GPIO_InitStructure;
    8. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    9. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
    10. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    11. GPIO_Init(GPIOA,&GPIO_InitStructure);
    12. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    13. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;
    14. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    15. GPIO_Init(GPIOA,&GPIO_InitStructure);
    16. //初始化USART1
    17. USART_InitTypeDef USART_InitStructure;
    18. USART_InitStructure.USART_BaudRate = 38400;//配置波特率(一般选择9600/115200等常用值)
    19. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//选择是否启用硬件流控制()此处选择的是不开启硬件流
    20. USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//选择工作模式(此处选择的是发送和接收模式)
    21. USART_InitStructure.USART_Parity = USART_Parity_No;//选择奇偶校验模式(此处选择的是无校验)
    22. USART_InitStructure.USART_StopBits = USART_StopBits_1;//选择停止位数(此处选择的是一位停止位)
    23. USART_InitStructure.USART_WordLength = USART_WordLength_8b;//选择数据位数(此处选择的是8位,因为没有校验位(不包含停止位和起始位))
    24. USART_Init(USART1,&USART_InitStructure);
    25. USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//使能中断(选择的是接收数据寄存器不为空中断)
    26. //配置优先级分组
    27. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    28. //初始化NVIC
    29. NVIC_InitTypeDef NVIC_Initstructure;
    30. NVIC_Initstructure.NVIC_IRQChannel = USART1_IRQn;//选择USART1通道
    31. NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;//开启选择的IRQn通道
    32. NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority = 0;//设置抢占优先级
    33. NVIC_Initstructure.NVIC_IRQChannelSubPriority = 0;//响应优先级
    34. NVIC_Init(&NVIC_Initstructure);
    35. //开启USART
    36. USART_Cmd(USART1,ENABLE);
    37. }

    再往下看就是这个模组怎么用串口使用了,当然就是类似于WIFI模块的AT指令那样,给模组发一个指令来让模组工作或者停止工作。让我们看看产品说明书上怎么写的

    看到这里我们也就清楚了,模组的指令分为三种分别是采集、体检、休眠。

    在这里我们想要的是简单使用所以只关注最基础的采集指令就可以了。

    当然了,笔者在看规格书的时候也顺便看了一点,体检指令就是集中采集数据并上传到云端进行分析,但是云端这个解析并没有在官网上面找到,所以我也就没有过多关注了,休眠功能的话,我建议将采集指令学习明白之后再尝试着加入休眠指令,要不然不就本末倒置了嘛!

    我们知道了开关指令,当然就可以通过STM32的串口来发送指令达到效果啦!

    需要指出的是在实际使用中虽然模组能近乎百分之百的响应指令,但是发送指令之后建议延时600ms左右(可以直接写到XU_Yang_SendByte(uint8_t Byte)函数中,笔者之前忘记了,大家自己添加一下吧),等待模组进入工作状态再进行读取

    1. void XU_Yang_SendByte(uint8_t Byte)
    2. {
    3. USART_SendData(USART1,Byte);
    4. while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    5. /*发送数据寄存器空标志位;若为RESET则发送数据寄存器非空,
    6. 此时不能继续向该寄存器继续写数据,如果发送数据寄存器非空继续写数据,就会覆盖之前的数据
    7. 导致发送的数据丢失一部分;
    8. */
    9. }
    10. uint8_t XU_Yang_GetRxFlag(void)
    11. {
    12. if(XU_Yang_RxFlag==1)
    13. {
    14. return 1;
    15. }
    16. return 0;
    17. }

    好了言归正传既然我们知道了采集的开关指令,那么我们怎么知道接收的哪些是我们想要的心率和血样数据呢?

    最简单的办法当然就是继续看产品规格书啦!

    从中我们就可以了解到采集开启之后,模组会通过串口发送以0xFF为包头的实时数据包,里面不仅包括了心率和血氧数据,也包括了心跳的原始数据和微循环等数据,我们的目的是读取心率和血氧,所以重点读取这两个数据就可以了。

    所以我们既然知道了接受的数据长什么样子,需要的数据在哪个位置,那么我们就可以来编写接收中断函数了。

    这段代码的思路是检测到包头开始接受,接收到最大数量重新开始检测包头,如此循环接收数据并将数据放在数组里面方便调用。

    串口代码是参考江科大的,所以也用了状态机的思路。

    从产品规格书上我们可以看到,总共76个字节,但我们需要的只有仅仅两个字节,所以我就想着定义一个只有两个字节的数组直接用于接收需要的数据,但是使用的时候发现,干扰数据有点多,所以又选取了前两个心跳数据来用于数据有效性检测。当然这可能不是必须的,大家也不必在这里纠结过多。

    1. //在中断实现接收一个HEX数据包
    2. void USART1_IRQHandler(void)
    3. {
    4. static uint8_t RxState;//用来记录状态数据
    5. static uint8_t pRxPacket;//用来记录接收了几个数据
    6. if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//再次确认标志位是否正确置位
    7. {
    8. uint8_t RxData = USART_ReceiveData(USART1);
    9. //读取接收数据寄存器中的数据,存储到uint8_t RxData中
    10. if(RxState == 0)//状态零(接收包头)
    11. {
    12. if(RxData == 0xFF)
    13. {
    14. RxState = 1;
    15. pRxPacket=2;
    16. }
    17. }else if(RxState == 1)//状态1(接收数据)
    18. {
    19. if(pRxPacket>=66 && pRxPacket<68)
    20. {
    21. //将接收到的心率和血氧数据放在数组后两位
    22. XU_Yang_RxPacket[pRxPacket-64]=RxData;
    23. pRxPacket++;
    24. }else if(pRxPacket<2)
    25. {
    26. //将接收到的监测数据有效性的数据放在数组前两位
    27. XU_Yang_RxPacket[pRxPacket]=RxData;
    28. pRxPacket++;
    29. }else if(pRxPacket>=2 && pRxPacket<66)
    30. {
    31. pRxPacket++;
    32. }else if(pRxPacket>=68)
    33. {
    34. pRxPacket=0;
    35. RxState=0;
    36. XU_Yang_RxFlag = 1;//将接收信号位置1,代表数据接收完成,可以读取
    37. }
    38. }
    39. USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除接收数据寄存器非空标志位
    40. }
    41. }

    到这里,我们从中断中就接收到了需要的数据,但是这些数据并不能直接使用,因为当你不放置手指时此时数据肯定是无效的,尽管你的手指放在上面,由于各种因素刚开始产生的数据当然也是我们不想要看到的,大家可以尝试一个下,用串口模块连接血氧模块,进行测试,会发现没有放手指时,心跳数据一般都是0XC4,当放置手指之后,按照规格书的说明读取对应位的数据也会发现这些数据有些违背常识。我们一般来说写一个驱动函数的目的就是方便主函数的调用,最好是那种,主函数一调用,就能知道准确的数据。所以以此想法为思路,我们便可以写一下我们的读取函数了。

    首先就是读取数据的间隔,我们通过上一个图可以看出,实时数据包每隔1.64s发送一次,所以理论上我们对就收数据的调用间隔也应该是1.64s,但是我习惯让单片机找点事情做,所以就没有通过延时调整时间间隔,而是让单片机一直筛选数据,(当然了可能也有弊端,比如接收的数据只接收了前两位,然后刚好单片机新一轮的筛选数据开始进行,就会导致,前两位是这一次的数据,后两位是上一次的数据,就会导致这一次的数据被当成错误的给筛除掉,当然了我觉得我这样做的也是有好处的,既然正确的都能给筛除掉,那我读到数据的时候自然就是模组稳定之后的数据啦!哈哈哈哈)

    说一下我筛出数据的思路:就是通过判断数组的前两位和后两位的约束条件判断是否采集成功。

    前两位为心跳数据,当为0xC4时可以判定为采集失败,当后两位心率和血氧数据不在正常的范围同理也可以判断为采集失败,

    在下面的代码中也可以看到我里面将发送开始与结束采集的命令注释掉了,因为本来想着读完数据之后就结束采集,但是,发现频繁的开关导致模组反应不及时,所以索性,就在主函数控制开启和关闭采集了。

    1. /**
    2. * @brief 过滤无效数据,并返回数据是否采集成功(无效数据过多,代表采集失败,最多等待200次数据包),成功采集一次就跳出采集程序
    3. * @param 无
    4. * @retval 1 数据采集成功;0数据采集失败
    5. */
    6. uint8_t XU_Yang_Receive(void)
    7. {
    8. uint8_t num=0;//接收到几个数据包
    9. // XU_Yang_SendByte(0x8A);//开始采集
    10. while(1)
    11. {
    12. if(XU_Yang_GetRxFlag()==1)
    13. {
    14. //
    15. //过滤无效数据
    16. if(XU_Yang_RxPacket[0]==0xC4 | XU_Yang_RxPacket[1]==0xC4 | (XU_Yang_RxPacket[2]<60 | XU_Yang_RxPacket[2]>130) | (XU_Yang_RxPacket[3]<80 | XU_Yang_RxPacket[3]>99) )
    17. {//数据无效,将无效数据清除
    18. for(uint8_t i=0;i<4;i++)
    19. {
    20. XU_Yang_RxPacket[i]=0;
    21. }
    22. num++;
    23. //失败次数过多,采集失败
    24. if(num >200)
    25. {
    26. delay_ms(10);//两次采集时间间隔过短导致了一直采集失败//原因未知//
    27. num=0;
    28. // XU_Yang_SendByte(0x88);//结束采集
    29. return 0;
    30. }
    31. }else//采集成功
    32. {
    33. num=0;
    34. // XU_Yang_SendByte(0x88);//结束采集
    35. return 1;
    36. }
    37. }
    38. }
    39. }

    模块文件写好之后,主函数就简单多了

    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. #include "XU_Yang.h"
    5. #include "Key.h"
    6. uint8_t n;
    7. //uint32_t u=0;
    8. uint16_t s=650;
    9. int main()
    10. {
    11. /*
    12. 现象:将手指放上去,刚开始时显示失败,等待大约15s左右,显示采集成功,采集成功后,数据更新也会加快,
    13. */
    14. OLED_Init();
    15. XU_Yang_Init();
    16. OLED_ShowString(1,1,"RxPacket:");
    17. OLED_ShowString(2,1,"000ci 00%");
    18. XU_Yang_SendByte(0x8A);//开始采集
    19. while(1)
    20. {
    21. while(s--)
    22. {
    23. n = XU_Yang_Receive();
    24. }
    25. if(n==1)
    26. {
    27. OLED_ShowString(3,1," ");
    28. OLED_ShowNum(2,1,XU_Yang_RxPacket[2],3);
    29. XU_Yang_RxPacket[2] = 0;
    30. OLED_ShowNum(2,9,XU_Yang_RxPacket[3],3);
    31. XU_Yang_RxPacket[3] = 0;
    32. OLED_ShowString(3,1,"Right!");
    33. }else if(n==0)
    34. {
    35. OLED_ShowNum(2,1,0,3);
    36. OLED_ShowNum(2,9,0,3);
    37. OLED_ShowString(3,1," ");
    38. OLED_ShowString(3,1,"Lose!");
    39. }
    40. }
    41. }

    下面时实际效果,用到的工程代码,和产品规格书我都会上传上去,有需要请自取哈

  • 相关阅读:
    【Docker部署私服仓库Harbor详细教程步骤&镜像拉取&推送到Harbor仓库实战演练应用】
    FPGA运算
    【Apache Doris】周FAQ集锦:第 8 期
    深入理解机器学习——类别不平衡学习(Imbalanced Learning):样本采样技术-[基础知识]
    XSS漏洞DOM型总结、XSS利用、XSS绕过
    L64.linux命令每日一练 -- 第十章 Linux网络管理命令 -- ifconfig和ifup
    Vue学习笔记 —— 使用Vue的ref实现动态添加活动类名
    阻塞队列-生产者消费者模型
    什么是恶意代码?
    分布式事务解决方案之2PC
  • 原文地址:https://blog.csdn.net/Cola_psoda/article/details/132894076