基本已经实现 这大概是我目前做的最小巧的东西了 心跳和憋气测试有效果 (目前还没到考虑标定的那步)
从机器内通过串口导出的内部真实算出频谱结果如下 应该算具备足够的分辨能力了
中间用采到的心跳数据 使用格兹尔算法也模拟了一遍 也有类似的效果
这个算法同样是非帧的 可以累进处理的 对内存要求也一样 但是多了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; }
虽然极简 但是效果不错
目前程序离8k还差几百字节(已经包括了接近检测 光功率自动调整 串口输出和命令下载等所有的杂项功能) 还没有用任何浮点运算 (否则就只能把字库和查表放到eeprom里面省出足够空间) 可以算可用的初版了 下面等电池到货 设计一下外壳就差不多了 可能还想再优化一下鉴相算法 尤其是低通收敛这块 看看能不能让结果更平滑点
....................................
试了一下内建余弦表 使用余弦鉴相代替开关鉴相 得到心跳频谱图如下
有所改观但是区别不大 但是算力恰恰够 我的显示刷新是在空闲时候进行(并未打开中断而是轮询) 使用余弦鉴相后 刷新率下降到了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次 再三衡量 还是用这个罢
所以 题目虽然是开关鉴相 但是 最后还是做了余弦鉴相 .... 不过 也不妨碍开关鉴相好用可用
时段 | 个数 |
---|---|
{{f.startingTime}}点 - {{f.endTime}}点 | {{f.fileCount}} |