模组图片:
让我们看一下产品规格书
通过此处,大概也就知道了,应该如何配置单片机的串口来实现通信了。这里我用的是PA9和PA10串口1,并且开启接收中断,用于及时的接收数据。
模组的指令分为三种分别是采集、体检、休眠。
在这里我们想要的是简单使用,所以只关注最基础的采集指令就可以了。
需要指出的是在实际使用中虽然模组能近乎百分之百的响应指令,但是发送指令之后建议延时600ms左右(可以直接写到XU_Yang_SendByte(uint8_t Byte)函数中,之前忘记了,大家自己添加一下吧),等待模组进入工作状态再进行读取。
既然我们知道了采集的开关指令,那么我们怎么知道接收的哪些是我们想要的心率和血样数据呢?
从中我们就可以了解到采集开启之后,模组会通过串口发送以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!");
}
}
}