前言
本文章实现了一个心率监控设备,可以通过脉搏传感器采集脉搏信息的ADC值并通过解算采集的信号,将信号转换成实际的脉搏值。该文章实现了如下功能:
1、ADC采集心率数值并解算出心率值;
2、SPI驱动TFTLCD显示心率值以及心率波形;(没有使用LVGL,本来想使用的,但是LVGL的lv_line功能画出来的曲线有断点,所以就自己写了波形显示函数)
3、基于RT-Thread实现
硬件连接
脉搏传感器
脉搏传感器使用的是Pulse Sensor,关于其描述,网上可以直接搜索到不在赘述,直接说下电路连接。

原理简述:
心率传感器采集原理是,通过发出绿色光,测量反馈的光的强度,转换成ADC值并放大输出到S引脚,单片机将S引脚连接到ADC上即可,采集的ADC值就是心率变化值,通过计算出两个脉搏之间的距离就可以算出每分钟的心跳次数。
LCD连接
这块不在赘述
本次驱动没有使用lvgl功能,因为我需要绘制曲线,使用lvgl的lv-line功能绘制出来的曲线存在断点,不够连续,没找到问题原因,所有我就自己通过画点来绘制了心率曲线图,效果还可以。
软件实现
本部分只说明实现的核心部分,整个工程在gitee上,有需要可以参考,见文章末尾。
ADC数据采集
采集ADC数据,并且转换为10bit精度,本来想直接使用10bit精度的,但是设置了发现没生效,不知道为什么。
static void Ps_ReadSampleValueFromAdc(void)
{
rt_uint32_t value;
value = Adc_ChSample(ADC_CHANNEL_1);
Signal = value>>2;
}
心率处理部分
此部分包含了采集的ADC信号中的心率信号进行识别的逻辑,代码中有比较详细的说明,本文不在赘述。
int BPM;
int Signal;
int IBI = 600;
bool Pulse = false;
bool QS = false;
int rate[10];
uint32_t sampleCounter = 0;
uint32_t lastBeatTime = 0;
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))
{
if(Signal < T)
{
T = Signal;
}
}
if((Signal > thresh) && (Signal > P))
{
P = Signal;
}
if (Num > 250)
{
if ((Signal > thresh) && (Pulse == false) && (Num > (IBI/5)*3))
{
Pulse = true;
IBI = sampleCounter - lastBeatTime;
lastBeatTime = sampleCounter;
if(secondBeat)
{
secondBeat = false;
for(i=0; i<=9; i++)
{
rate[i] = IBI;
}
}
if(firstBeat)
{
firstBeat = false;
secondBeat = true;
return;
}
runningTotal = 0;
for(i=0; i<=8; i++)
{
rate[i] = rate[i+1];
runningTotal += rate[i];
}
rate[9] = IBI;
runningTotal += rate[9];
runningTotal /= 10;
BPM = 60000/runningTotal;
if(BPM>200)
BPM=200;
if(BPM<30)
BPM=30;
QS = true;
}
}
if (Signal < thresh && Pulse == true)
{
Pulse = false;
amp = P - T;
thresh = amp/2 + T;
P = thresh;
T = thresh;
}
if (Num > 2500)
{
thresh = 512;
P = 512;
T = 512;
lastBeatTime = sampleCounter;
firstBeat = true;
secondBeat = false;
}
}
波形处理
此部分包含了TFTLCD需要显示的波形数据,将采集的波形数据放置到一个数组中,用于显示。
//波形处理函数,放在定时器中执行,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)
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
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采集一次数据并进行分析。
void TIMER1_IRQHandler(void)
{
if(SET == timer_interrupt_flag_get(TIMER1, TIMER_INT_UP))
{
Ps_ReadSampleValueFromAdc();
Ps_HeartRateDeal();
timer_interrupt_flag_clear(TIMER1, TIMER_INT_UP);
}
}
LCD显示函数
此部分包含了心率显示,心率采集波形显示等。
void Gui_Init(void)
{
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])))
{
lcd_draw_point(x, y+40, BLUE);
}
else
{
lcd_draw_point(x, y+40, WHITE);
}
}
}
LCD_CS_SET;
}
显示效果
左上角显示当前采集的心率值,下部显示心率波形。


原作者:hehung