1 前言
BMD101传感器是神念科技开发的心电传感器。目前,一些企业已经开发出来BMD101传感器模块基本上利用蓝牙直接传输数据PC或者手机。本文介绍了数据通过串口传输给单片机,并在单片机上完成数据分析和心电波形LCD显示功能。
2 解释通信协议
BMD101 通过 UART 接口通信。这是一个标准 UART 界面,定义为 1 个起始位,8 个数据位,1 停止格式,波特率 57600。 串口输出的数据包格式如下: 包括Header(帧头)、data payload(数据有效载荷),CRC验证字节的三个部分。
帧头包括两个SYNC字节(其值均为0xAA),用来指示一帧数据的开始。plength字节用于指示数据有效载荷部分的字节数。它的值可能是0-169的一个值。
Data Payload 它是由一系列连续的 DataRow 组成。分析 Data Payload 每一个都涉及到分析 DataRow。DataRow组成如下: DataRow 可能有零或多个起始位置[EXCODE](扩展代码)字节,这些字节的值都是 0x55。EXCODE 字节数表示 Extended Code Level。Extended Code Level 是用来与[CODE]一起确定字节DataRow数据表示信息的方面。 如下表所示:Extended Code Level为0和[CODE]=0x02决定了当前DataRow表示信号质量信息。 如果[CODE]字节在 0x00 和 0x7F 之间,那么DataRow就没有[LENGTH]跟着字节[CODE]是字节[DATA]值,然后 DataRow 结束。
如果[CODE]字节的值在 0x80 和 0xFF 之间,然后跟着[LENGTH],它表示[DATA]的字节数。
通常我们从BMD101接收到的DataRow主要有三种:[CODE]=0x02,即信号质量数据,信号质量是0-200之间的数据。值越大,传感器收集的信号质量越好,可能是因为电极与人体接触不良。
[CODE]=0x03,即实时心率数据,用字节表示。BMD在101的便利性方面,开发者不需要通过算法获得心率,只需读取串口发送的心率数据。心率数据通常每秒发送一次,但当信号质量差时,心率数据仍会输出。因此,在使用心率数据之前,应验证信号质量是否过差。
[CODE]=0x80点,输出数据[DATA]表示原始的心电波形数据,每一个数据由16位补码组成,其值范围为-32768到32767之间。这16位数据的第一个字节是高8位字节,第二个字节是低8位,为了通过这两个字节还原心电波形数据值,可以通过以下代码完成: short raw =(Value[0]<<8) | Value[1]; [DATA]它可能包含许多心电图数据,因此它可能由许多字节组成,通常BMD每秒输出512个原始心电波形数据,即采样率为512Hz。
数据包分析步骤
- 从数据流中连续读取字节,直到[SYNC]字节(0xAA)时。
- 读下一个字节,确保它也是一个字节[SYNC]字节(如果没有,返回步骤 1)。
- 读[PLENGTH]字节。
- 从[PAYLOAD…]中读取下一个 PLENGTH 在存储区域保存字节(如 unsigned char payload [ 256 ]数组)。按接收顺序将每个字节累加到校准器中。
- 使校验器中的低 8 位取反 C 代码: checksum &= 0xff; checksum = ~ checksum & 0xff;
- 读取[CRC]字节并验证它是否符合您的计算验证和(如果没有,返回步骤 1)。
- 循环,直到 payload[]数组中的所有字节(即 DataRows)被解析: a)解析和计算[EXCODE]字节值(0x55),一般在当前 DataRow 的开始。 b)解析当前 DataRow 中的[CODE]字节数据。 c)若适用,分析当前 DataRow 中的[LENGTH]字节数据。d)分析和处理 当前 DataRow 中的[DATA…]基于字节(或数组)的字节 DataRow 的 [EXCODE]等级、[CODE]和[LENGTH]。 e)如果不是所有的字节都来自 payload []完成数组分析,返回 a)解析下一个DataRow。
3 STM32实现数据包分析
配置STM代码如下:
u8 Uart2_Buffer[256]; //接收缓冲区 u8 Uart2_Rx = 0; //Uart2_Buffer下标 u8 Uart2_Len; //数据长度crc) int checksum=0; //根据Payload计算出的验证值 u8 Uart2_Sta=0; ///正确标记数据帧 u8 Uart2_check; ///帧尾验证值 u8 sig_quality=200; ///信号质量 //@brief: Receive a packet.If the packet is received correctly, //function parsePayload will be called,else this packet will be discarded void USART2_IRQHandler() {
if(USART_GetIStatus(USART2,USART_IT_RXNE) != RESET)
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
Uart2_Buffer[Uart2_Rx] = USART_ReceiveData(USART2);
Uart2_Rx++;
}
if(Uart2_Rx < 3) //判断是否完成帧头接收
{
if(Uart2_Buffer[Uart2_Rx-1] != SYNC) // 异常
{
Uart2_Rx = 0; //下标清0
Uart2_Sta = 0; //标志置0
}
}
else if(Uart2_Rx == 3) //得到Payload长度
Uart2_Len = Uart2_Buffer [Uart2_Rx-1];
else if(Uart2_Rx < 4 + Uart2_Len) //接收Payload
{
checksum += Uart2_Buffer[Uart2_Rx-1]; //计算校验值
//checksum &= 0xFF;
}
else //接收校验位
{
Uart2_check = Uart2_Buffer[Uart2_Rx-1];
checksum &= 0xFF ;
checksum = ~checksum & 0xFF;
if(checksum != Uart2_check) //校验错误 ,丢弃该数据包
{
Uart2_Rx = 0; //下标清0
Uart2_Sta = 0; //标志置0
checksum = 0;
}
else
Uart2_Sta = 1;//接收完成
}
if(Uart2_Sta) //检测到标志,说明成功接收
{
parse_payload(); //调用数据解析函数
Uart2_Rx = 0; //下标清0
Uart2_Sta = 0; //标志置0
checksum = 0;
}
}
下面给出数据解析的函数:
void parse_payload(void) //对有效负载的内容作分析处理
{
u8 bytesParsed = 0;//已处理的字节数
u8 code;
u8 length; //当前DataRow包含的Value的字节数
u8 extendedCodeLevel;
short int rawValue = 0; //心电数据
while(bytesParsed < Uart2_Len)
{
extendedCodeLevel=0;
while(Uart2_Buffer[3+bytesParsed] == EXCODE)
{
extendedCodeLevel++;
bytesParsed++;
}
code = Uart2_Buffer[3+bytesParsed];
bytesParsed++;
if(code >= 0x80)
{
length = Uart2_Buffer[3+bytesParsed];
bytesParsed++;
}
else
length = 1;
//now we get ExCodeLevel,code and the length of DataValue
//in fact,Extended Code Level is always 0,so we can ignore it
switch(code)
{
case 0x80://two bytes signed data of raw wave value,big-endian
if(sig_quality > 0)
{
rawValue = Uart2_Buffer[3+bytesParsed];
rawValue <<= 8;
rawValue |= Uart2_Buffer[4+bytesParsed];
drawCurve(rawValue);
}
else
LCD_Clear(WHITE);//信号质量不佳,清屏
countData=0;
break;
case 0x02:
//one byte data of signal quality,0 stands for poor quality while 200 stands for good
sig_quality=Uart2_Buffer[3+bytesParsed];
break;
case 0x03://one byte data of heart rate value
LCD_ShowNum(290,50,Uart2_Buffer[3+bytesParsed],2,12); //在屏幕上显示心率值
break;
}
bytesParsed += length;
}
}
点击下载完整Keil工程文件 LCD显示用的是正点原子Mini板,以及相应的LCD库函数。drawCurve(rawValue);是根据心电数据在屏幕上绘制心电波形,具体实现参考我的另一篇博客https://blog.csdn.net/weixin_44509533/article/details/108912968
实现效果如下: 由于没有滤波处理,BMD101的输出信号噪声还是比较多的。另外BMD101对噪声十分敏感,传感器的串口不应该直接与开发板的串口通过杜邦线直接连接,否则波形失真严重。可以在两者之间增加一个数字隔离模块。