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

2023-10-15 05:30

本文主要是介绍惊帆JF141心率血氧模块简单使用(STM32标准库代码),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我的那个模组长这个样子

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

让我们看一下产品规格书

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

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

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

void XU_Yang_Init(void)
{//开启GPIOA和USART1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//配置PA9GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 ;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化USART1USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate = 38400;//配置波特率(一般选择9600/115200等常用值)USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//选择是否启用硬件流控制()此处选择的是不开启硬件流USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//选择工作模式(此处选择的是发送和接收模式)USART_InitStructure.USART_Parity = USART_Parity_No;//选择奇偶校验模式(此处选择的是无校验)USART_InitStructure.USART_StopBits = USART_StopBits_1;//选择停止位数(此处选择的是一位停止位)USART_InitStructure.USART_WordLength = USART_WordLength_8b;//选择数据位数(此处选择的是8位,因为没有校验位(不包含停止位和起始位))USART_Init(USART1,&USART_InitStructure);USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//使能中断(选择的是接收数据寄存器不为空中断)//配置优先级分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//初始化NVICNVIC_InitTypeDef NVIC_Initstructure;NVIC_Initstructure.NVIC_IRQChannel = USART1_IRQn;//选择USART1通道NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;//开启选择的IRQn通道NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority = 0;//设置抢占优先级NVIC_Initstructure.NVIC_IRQChannelSubPriority = 0;//响应优先级NVIC_Init(&NVIC_Initstructure);//开启USARTUSART_Cmd(USART1,ENABLE);
}

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

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

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

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

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

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

void XU_Yang_SendByte(uint8_t Byte)
{USART_SendData(USART1,Byte);while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);/*发送数据寄存器空标志位;若为RESET则发送数据寄存器非空,此时不能继续向该寄存器继续写数据,如果发送数据寄存器非空继续写数据,就会覆盖之前的数据导致发送的数据丢失一部分;*/
}
uint8_t XU_Yang_GetRxFlag(void)
{if(XU_Yang_RxFlag==1){return 1;}return 0;
}

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

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

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

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

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

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

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

//在中断实现接收一个HEX数据包
void USART1_IRQHandler(void)
{static uint8_t RxState;//用来记录状态数据static uint8_t pRxPacket;//用来记录接收了几个数据if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)//再次确认标志位是否正确置位{uint8_t RxData = USART_ReceiveData(USART1);//读取接收数据寄存器中的数据,存储到uint8_t RxData中if(RxState == 0)//状态零(接收包头){if(RxData == 0xFF){RxState = 1;pRxPacket=2;}}else if(RxState == 1)//状态1(接收数据){	if(pRxPacket>=66 && pRxPacket<68){//将接收到的心率和血氧数据放在数组后两位XU_Yang_RxPacket[pRxPacket-64]=RxData;pRxPacket++;}else if(pRxPacket<2){//将接收到的监测数据有效性的数据放在数组前两位XU_Yang_RxPacket[pRxPacket]=RxData;pRxPacket++;}else if(pRxPacket>=2 && pRxPacket<66){pRxPacket++;}else if(pRxPacket>=68){pRxPacket=0;RxState=0;XU_Yang_RxFlag = 1;//将接收信号位置1,代表数据接收完成,可以读取}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);//清除接收数据寄存器非空标志位}
}

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

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

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

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

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

/*** @brief       过滤无效数据,并返回数据是否采集成功(无效数据过多,代表采集失败,最多等待200次数据包),成功采集一次就跳出采集程序                * @param       无                  
* @retval       1 数据采集成功;0数据采集失败         */uint8_t XU_Yang_Receive(void)
{uint8_t  num=0;//接收到几个数据包
//	XU_Yang_SendByte(0x8A);//开始采集while(1){if(XU_Yang_GetRxFlag()==1){
////过滤无效数据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) ){//数据无效,将无效数据清除for(uint8_t i=0;i<4;i++){XU_Yang_RxPacket[i]=0;}num++;//失败次数过多,采集失败if(num >200){delay_ms(10);//两次采集时间间隔过短导致了一直采集失败//原因未知//num=0;
//				XU_Yang_SendByte(0x88);//结束采集return 0;}}else//采集成功{num=0;
//			XU_Yang_SendByte(0x88);//结束采集return 1;}}}}

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

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "XU_Yang.h"
#include "Key.h"
uint8_t n;
//uint32_t u=0;
uint16_t s=650;
int main()
{/*现象:将手指放上去,刚开始时显示失败,等待大约15s左右,显示采集成功,采集成功后,数据更新也会加快,*/OLED_Init();XU_Yang_Init();OLED_ShowString(1,1,"RxPacket:");OLED_ShowString(2,1,"000ci   00%");XU_Yang_SendByte(0x8A);//开始采集while(1){while(s--){n = XU_Yang_Receive();}if(n==1){OLED_ShowString(3,1,"      ");OLED_ShowNum(2,1,XU_Yang_RxPacket[2],3);XU_Yang_RxPacket[2] = 0;OLED_ShowNum(2,9,XU_Yang_RxPacket[3],3);XU_Yang_RxPacket[3] = 0;OLED_ShowString(3,1,"Right!");}else if(n==0){OLED_ShowNum(2,1,0,3);OLED_ShowNum(2,9,0,3);OLED_ShowString(3,1,"      ");OLED_ShowString(3,1,"Lose!");}}
}​

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

这篇关于惊帆JF141心率血氧模块简单使用(STM32标准库代码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/215629

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::