# GD32F427心率监控设备 **Repository Path**: hehung/GD32F427_pulse_monitor ## Basic Information - **Project Name**: GD32F427心率监控设备 - **Description**: 使用GD32F427V-START夹TFTLCD实现心率监控设备,TFTLCD可以显示心率波形以及心率值 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 2 - **Created**: 2022-12-15 - **Last Updated**: 2024-04-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 前言 本文章实现了一个心率监控设备,可以通过脉搏传感器采集脉搏信息的ADC值并通过解算采集的信号,将信号转换成实际的脉搏值。该文章实现了如下功能: 1. ADC采集心率数值并解算出心率值; 2. SPI驱动TFTLCD显示心率值以及心率波形;(没有使用LVGL,本来想使用的,但是LVGL的lv_line功能画出来的曲线有断点,所以就自己写了波形显示函数) 3. 基于RT-Thread实现 # 硬件连接 ## 脉搏传感器 脉搏传感器使用的是Pulse Sensor,关于其描述,网上可以直接搜索到不在赘述,直接说下电路连接。 ![1.png](./images/1.png) | pulse sensor | MCU | | ---------------| ----------| | + | 3.3V | | - | GND | | S | PA1 | 原理简述: 心率传感器采集原理是,通过发出绿色光,测量反馈的光的强度,转换成ADC值并放大输出到S引脚,单片机将S引脚连接到ADC上即可,采集的ADC值就是心率变化值,通过计算出两个脉搏之间的距离就可以算出每分钟的心跳次数。 ## LCD连接 这块不在赘述,直接参考我之前文章即可:[【GD32F427开发板试用】5. SPI驱动TFTLCD屏幕](https://aijishu.com/a/1060000000374184) 本次驱动没有使用lvgl功能,因为我需要绘制曲线,使用lvgl的lv-line功能绘制出来的曲线存在断点,不够连续,没找到问题原因,所有我就自己通过画点来绘制了心率曲线图,效果还可以。 # 软件实现 本部分只说明实现的核心部分,整个工程在gitee上,有需要可以参考,见文章末尾。 ## ADC数据采集 采集ADC数据,并且转换为10bit精度,本来想直接使用10bit精度的,但是设置了发现没生效,不知道为什么。 ```C /* 阅读采集自心率传感器的ADC值 */ static void Ps_ReadSampleValueFromAdc(void) { rt_uint32_t value; /* 读取采样值 */ value = Adc_ChSample(ADC_CHANNEL_1); /* 转换采集到的ADC值的精度为10bits */ Signal = value>>2; } ``` ## 心率处理部分 此部分包含了采集的ADC信号中的心率信号进行识别的逻辑,代码中有比较详细的说明,本文不在赘述。 ```C //心率采集相关变量 int BPM; //脉搏率==就是心率 int Signal; //传入的原始数据。 int IBI = 600; //节拍间隔,两次节拍之间的时间(ms)。计算:60/IBI(s)=心率(BPM) bool Pulse = false; //脉冲高低标志。当脉波高时为真,低时为假。 bool QS = false; //当发现一个节拍时,就变成了真实。 int rate[10]; //数组保存最后10个IBI值。 uint32_t sampleCounter = 0; //用于确定脉冲定时。 uint32_t lastBeatTime = 0; //用于查找IBI int P = 512; //用于在脉冲波中寻找峰值 int T = 512; //用于在脉冲波中寻找波谷 int thresh = 512; //用来寻找瞬间的心跳 int amp = 100; //用于保持脉冲波形的振幅 int Num; uint8_t firstBeat = true; //第一个脉冲节拍 uint8_t secondBeat = false; //第二个脉冲节拍,这两个变量用于确定两个节拍 /* 采集心率信号的处理函数 */ static void Ps_HeartRateDeal(void) { unsigned int runningTotal; uint8_t i; sampleCounter += 2; Num = sampleCounter - lastBeatTime; //监控最后一次节拍后的时间,以避免噪声 //找到脉冲波的波峰和波谷 if((Signal < thresh) && (Num > (IBI/5)*3)) //为了避免需要等待3/5个IBI的时间 { if(Signal < T) { //T是阈值 T = Signal; //跟踪脉搏波的最低点,改变阈值 } } if((Signal > thresh) && (Signal > P)) //采样值大于阈值并且采样值大于峰值 { P = Signal; //P是峰值,改变峰值 } //现在开始寻找心跳节拍 if (Num > 250) //避免高频噪声 { if ((Signal > thresh) && (Pulse == false) && (Num > (IBI/5)*3)) { Pulse = true; //当有脉冲的时候就设置脉冲信号。 IBI = sampleCounter - lastBeatTime; //测量节拍的ms级的时间 lastBeatTime = sampleCounter; //记录下一个脉冲的时间。 if(secondBeat) //如果这是第二个节拍,如果secondBeat == TRUE,表示是第二个节拍 { secondBeat = false; //清除secondBeat节拍标志 for(i=0; i<=9; i++) //在启动时,种子的运行总数得到一个实现的BPM。 { rate[i] = IBI; } } if(firstBeat) //如果这是第一次发现节拍,如果firstBeat == TRUE。 { firstBeat = false; //清除firstBeat标志 secondBeat = true; //设置secongBeat标志 return; //IBI值是不可靠的,所以放弃它。 } //保留最后10个IBI值的运行总数。 runningTotal = 0; //清除runningTotal变量 for(i=0; i<=8; i++) //转换数据到rate数组中 { rate[i] = rate[i+1]; //去掉旧的的IBI值。 runningTotal += rate[i]; //添加9个以前的老的IBI值。 } rate[9] = IBI; //将最新的IBI添加到速率数组中。 runningTotal += rate[9]; //添加最新的IBI到runningTotal。 runningTotal /= 10; //平均最后10个IBI值。 BPM = 60000/runningTotal; //一分钟有多少拍。即心率BPM if(BPM>200) BPM=200; //限制BPM最高显示值 if(BPM<30) BPM=30; //限制BPM最低显示值 QS = true; } } if (Signal < thresh && Pulse == true) //当值下降时,节拍就结束了。 { Pulse = false; //重设脉冲标记,这样方便下一次的计数 amp = P - T; //得到脉冲波的振幅。 thresh = amp/2 + T; //设置thresh为振幅的50%。 P = thresh; //重新设置下一个时间 T = thresh; } if (Num > 2500) //如果2.5秒过去了还没有节拍 { thresh = 512; //设置默认阈值 P = 512; //设置默认P值 T = 512; //设置默认T值 lastBeatTime = sampleCounter; //把最后的节拍跟上来。 firstBeat = true; //设置firstBeat为true方便下一次处理 secondBeat = false; } } ``` ## 波形处理 此部分包含了TFTLCD需要显示的波形数据,将采集的波形数据放置到一个数组中,用于显示。 ```C //波形处理函数,放在定时器中执行,20ms执行一次 static void Ps_WaveformDeal(uint16_t adc_value) { int16_t temp; uint16_t i = 0; temp = adc_value - 224; temp = 500 - temp; if (temp < 0) temp = 0; else if (temp > 500) temp = 500; temp = (uint8_t)(temp/2); #if (COMMON_USE_LVGL == COMMON_OFF) /* 超过LCD显示范围之后从新开始 */ for(i = 0; i < (PS_WAVE_POINT_NUM-1); i++) { ps_waveformlist[i] = ps_waveformlist[i+1]; } ps_waveformlist[PS_WAVE_POINT_NUM-1] = temp; #else /* 超过LCD显示范围之后从新开始 */ for(i = 0; i < (PS_WAVE_POINT_NUM-1); i++) { ps_waveformlist[i].x = i; ps_waveformlist[i].y = ps_waveformlist[i+1].y; } ps_waveformlist[PS_WAVE_POINT_NUM-1].x = PS_WAVE_POINT_NUM-1; ps_waveformlist[PS_WAVE_POINT_NUM-1].y = temp; #endif QS = 0; } ``` ## 定时器中断 软件使能了一个2ms的定时器,用于没2ms采集一次数据并进行分析。 ```C /* Timer interrupt service function */ void TIMER1_IRQHandler(void) { if(SET == timer_interrupt_flag_get(TIMER1, TIMER_INT_UP)) { /* Sample adc every 2ms */ Ps_ReadSampleValueFromAdc(); /* Calculate heart rate */ Ps_HeartRateDeal(); /* clear TIMER interrupt flag */ timer_interrupt_flag_clear(TIMER1, TIMER_INT_UP); } } ``` ## LCD显示函数 此部分包含了心率显示,心率采集波形显示等。 ```C void Gui_Init(void) { /* Initialize lcd */ lcd_init(); lcd_clear(WHITE); lcd_draw_font_gbk16(5, 0, BLUE, WHITE, "GD32F427V-START | aijishu.com | hehung"); } static void Gui_MainFunction(void) { char bpm_str[20]; uint16_t x; uint16_t y; sprintf(bpm_str, "BPM:%d ", Ps_GetBpm()); lcd_draw_font_gbk16(5, 16, RED, WHITE, bpm_str); pulse_data_point = Ps_GetWaveformList(); /* 显示波形 */ LCD_CS_CLR; for (x = 0; x < PS_WAVE_POINT_NUM; x++) { for (y = 0; y < 200; y++) { if (pulse_data_point[x] == y) { lcd_draw_point(x, y+40, BLUE); } else if ((x != (PS_WAVE_POINT_NUM-1)) && (y > Gui_GetNumMin(pulse_data_point[x], pulse_data_point[x+1])) && (y < Gui_GetNumMax(pulse_data_point[x], pulse_data_point[x+1]))) { // rt_kprintf("test:%d\n",y); lcd_draw_point(x, y+40, BLUE); } else { lcd_draw_point(x, y+40, WHITE); } } } LCD_CS_SET; } ``` # 显示效果 左上角显示当前采集的心率值,下部显示心率波形。 ![2.png](./images/2.png) ![3.png](./images/3.png) 演示视频见bilibili: https://www.bilibili.com/video/BV1M8411p7dj/?aid=221308023&cid=924776343&page=1 # 程序代码 程序放置到了gitee,其中还包括了硬件IIC实现了OLED的驱动代码,只不过在这个工程中并没有使用。 [https://gitee.com/hehung/GD32F427_pulse_monitor](https://gitee.com/hehung/GD32F427_pulse_monitor)