m24h作者
4年4个月前修改于 4年4个月前IP:上海
905166
11楼

基本已经实现 这大概是我目前做的最小巧的东西了 心跳和憋气测试有效果 (目前还没到考虑标定的那步)

1656061886800.jpg

从机器内通过串口导出的内部真实算出频谱结果如下 应该算具备足够的分辨能力了

pd-real.png

中间用采到的心跳数据 使用格兹尔算法也模拟了一遍 也有类似的效果

Goertzel.png

这个算法同样是非帧的 可以累进处理的 对内存要求也一样 但是多了cos查表和乘法 对运算误差有些敏感 而且还没有想到怎么进行收敛 (我现在开关鉴相算法中使用一阶低通来代替积分实现收敛)

这里把算法步骤放一下 运算相当简单 利用了整数的累加溢出后等效于取模的周期性

u16 idata g_pdi;   
u16 idata g_pdq;   
u16 idata g_pdf;   
i16 xdata g_hr_i[HR_MAX-HR_MIN+1]; // I信号数组 下标是心跳频率(从最小频率开始)
i16 xdata g_hr_q[HR_MAX-HR_MIN+1]; // I信号数组 下标是心跳频率(从最小频率开始)

// (65536*HR_MIN/60/SEN_RATE-6)=322, -6 is a fixed value for smallest error in whole heart beat rate scope 
#define PD_TADD  322
// (65536/(60*SEN_RATE))=11
#define PD_FADD  11

清零 g_hr_i[] g_hr_q[];
g_pdi=0;  // 0 degree
g_pdq=16384; // 90 degree

对每次采样结果(每秒100次):	{	
	pdi=g_pdi;
        pdq=g_pdq;
	依次对频率点(30-180/分): {
		if (pdi & 0x8000)
			g_hr_i[i]-=ac;
		else 
			g_hr_i[i]+=ac;

		if (pdq & 0x8000)
			g_hr_q[i]-=ac;
		else
			g_hr_q[i]+=ac;
              .....低通收敛一下....;
		pdi+=g_pdf; 
		pdq+=g_pdf;		
	}

	g_pdi+=PD_TADD;
	g_pdq+=PD_TADD;
	g_pdf+=PD_FADD;
}

我期间看了一下美信再mbed上的算法 其复杂性令人害怕 完全时域上的各种处理 找峰值 屏蔽干扰 鉴别等等等等.... 而我求心跳峰值的算法 借鉴了二极管检波求峰的原理和现有峰值自然衰退的方法

// max/min parameters decay
red_maxd -= (red_maxd - red32 +64)>>7;

// calculate new max/min
if (red32>red_maxd) {
	g_red_max += (red32 - g_red_max + 16) >> 5;
	red_maxd=red32;
} 		

 虽然极简 但是效果不错

max_maxd.png

目前程序离8k还差几百字节(已经包括了接近检测 光功率自动调整 串口输出和命令下载等所有的杂项功能) 还没有用任何浮点运算 (否则就只能把字库和查表放到eeprom里面省出足够空间) 可以算可用的初版了 下面等电池到货 设计一下外壳就差不多了 可能还想再优化一下鉴相算法 尤其是低通收敛这块 看看能不能让结果更平滑点

....................................

试了一下内建余弦表 使用余弦鉴相代替开关鉴相 得到心跳频谱图如下

hr_cos.png

有所改观但是区别不大 但是算力恰恰够 我的显示刷新是在空闲时候进行(并未打开中断而是轮询) 使用余弦鉴相后 刷新率下降到了2秒一次 而使用开关鉴相 刷新率基本符合0.6秒一次 代码仅仅改动如下 可以略微优化但是余量不大 现在数据采集率100Hz 如果降到50Hz可能会轻松点  但是衡量一下 还是保持采集率好处更大

i16 cos(u8 x)
{
	if (x<=64)  return COS_LUT[x];
	if (x<=128) return -COS_LUT[128-x];
	if (x<=192) return -COS_LUT[x-128];
	return COS_LUT[(u8)-(i8)x];
}

#ifdef USE_COS
		g_hr_i[i]+=(i16)(((i32)ac*(i32)cos((u8)(pdi>>8)))>>16);
#else		
		if (pdi & 0x8000)
			g_hr_i[i]-=ac;
		else 
			g_hr_i[i]+=ac;
#endif

..................................

还是优化了 减少了32位乘法 表中直接使用8位数减少位移 

i16 cos(i8 x)
{
	if (x<0) x=-x;
	if (x>64) return -COS_LUT[128-(u8)x];
  return COS_LUT[x];	
}
...............
.............
#ifdef USE_COS
		g_hr_q[i]+=(ac*(cos(*(i8*)&pdq)))>>7;
#else		

刷新率到了1秒1次 再三衡量 还是用这个罢

所以 题目虽然是开关鉴相 但是 最后还是做了余弦鉴相 .... 不过 也不妨碍开关鉴相好用可用

评论
1
文号 / 905166

千古风流
名片专栏发私信
学术分 1
总主题 68 帖总回复 1007 楼拥有证书:进士 学者 机友
注册于 2020/01/22 18:44:43最后登录 2026/10/31 02:54:58
主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:上海

个人简介

个人开源项目: m24h.github.io

笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的