ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

单片机心率检测实战:从传感器选型到信号处理算法

2026/9/18 23:52:57 拓冰建站 浏览量
单片机心率检测实战:从传感器选型到信号处理算法 简介一份基于单片机的心率检测系统完整设计文档面向电子、嵌入式相关专业的学生与开发者适合用于课程设计、毕业设计或单片机应用项目参考。文档以STC89C52单片机为核心详细介绍了红外光电传感器采集、信号放大整形与滤波电路、LCD1602显示、上下限报警及定时提醒等功能的实现方案涵盖主控模块选型、中断系统、液晶接口设计等关键环节。压缩包内为单个doc文件大小2.7MB按摘要、前言、选题意义、方案论证、硬件设计、软件设计、系统调试等章节组织目录结构完整可直接参考整体设计流程与电路实现方案。文档还包含系统调试方法与注意事项对实际搭建和测试有指导作用。资源已有251人学习下载可作为心率检测类单片机设计的参考模板帮助读者快速理解系统架构、软硬件协同方式及模块化设计思路。1. 单片机心率检测先想清楚测量目标门诊分诊台或者家庭健康监测场景中想看实时心率最直接的想法是拿一个现成的心率模块读寄存器里的数值。但示波器接上传感器输出后会发现真正的问题不在“读”而在“从噪声里把心跳周期挑出来”。传感器输出通常是一个压在直流电平上的几毫伏到几十毫伏交流小信号手指轻微一动运动伪迹就能把心搏波动完全淹没。基于单片机的心率检测系统本质上不是传感器读取教程而是信号质量工程从光电信号里稳定提取脉搏周期再换算成每分钟心跳数。这个题目覆盖传感器选型、模拟前端设计、定时采样、数字滤波和峰值检出资源要求并不高传统51内核能做自带ADC的STC单片机或者手头常备的STM32也能做区别只在资源分配和功耗策略上。课程设计、毕设原型和入门级可穿戴设备都适用。2. 传感器选型与信号链设计用一路ADC抓住脉搏波动2.1 为什么优先选光电容积脉搏波心率检测有两条主流路径心电ECG和光电容积脉搏波PPG。ECG测的是心脏电活动信号幅度在毫伏级需要仪表放大器、右腿驱动、屏蔽线和比较讲究的电极接触对模拟前端要求高PPG测的是血液容积变化引起的光吸收波动只需要一个发光管、一个光敏管加上合适的放大偏置电路就能出信号。对课程设计和原型验证来说PPG在硬件复杂度和实现周期上有明显优势。PPG的信号特征也决定了它适合单片机处理脉搏基波频率在0.5Hz到3Hz之间对应30到180拍每分钟谐波主要集中在10Hz以内。这个频段远低于音频甚至低于很多工频干扰的频段用一两级RC低通就能压住高频噪声ADC采样率取100到200Hz完全够用。相比之下ECG的QRS波群含有较高频率分量要准确检出R波采样率至少要250Hz对MCU的定时和中断占用更大。2.2 传感器方案的横向对比方案信号类型硬件成本信号质量适用场景血氧探头模块透射式PPG较高好光路遮挡好输出稳定指夹式产品、长时间监测反射式PPG模块绿光/红光PPG中等受手指压力影响大需要算法补偿手环、按压式测量分立式光敏管加红外LEDPPG低原始信号噪声大需认真处理课程设计、原理验证课程设计里最常被推荐的是第一类成品血氧探头自带屏蔽和固定光路。探头内部已经包含发光管和光敏接收管外部只有四根线电源、地、信号输出有的型号内部带简单的I/V转换电路。这类模块的电信号已经是经过初步放大的脉搏波形单片机侧只负责采样和计算。反射式模块优点是体积小但输出对按压力量极敏感做出来“数值跳来跳去”的情况非常多见新手不建议直接用。2.3 模拟前端偏置、放大和低通滤波传感器原始输出是直流叠加交流。以透射式PPG为例直流分量可能到1V以上交流分量只有10到50mV。如果直接把信号送进单片机ADC会发现采样值在一个较大的基值附近微小波动10位ADC在5V参考电压下LSB约为4.9mV交流分量也就两三个LSB量化噪声大峰值检出的稳定性很差。所以常见做法是先隔直再偏置到ADC量程中点附近然后放大。我一般用这样的结构输入信号经过0.1Hz到0.5Hz的高通滤波去掉直流和缓变漂移进入运放同相放大放大倍数取20到50倍再把输出电压偏置到参考电压的二分之一。以5V系统为例静态工作点设在2.5V放大后的脉搏波动幅度可以到0.5V到1VADC能分到100到200个码值做阈值检测就有余量了。单片机内置ADC的参考电压如果不够干净会在采样结果里叠加周期性纹波放大后的信号反而更难处理。这时候可以在参考电压引脚并联10uF和100nF电容靠近引脚放置。运放选择LM358或MCP6002这类单电源轨到轨器件就行LM358在接近地电位时输出会失真所以偏置点设在1/2VCC恰好避开了这个问题。2.4 采样率怎么定从心率谐波倒推采样率的依据不是传感器带宽而是你要保留多少信号频带。心率计算要识别的特征点是主波峰对应的能量集中在基波和二次、三次谐波180bpm时基波是3Hz三次谐波9Hz。为了保留上升沿细节低通滤波器的截止频率放到15到20Hz比较合适。根据奈奎斯特定理采样率至少是两倍信号最高频率但工程上至少要留3到5倍余量避免滤波器滚降不足引起混叠。取100Hz是比较保险的下限200Hz是舒适区间。51单片机用12MHz晶振时1ms的定时器中断比较舒服对应采样率1000Hz这对PPG来说偏高了。更好的做法是让定时器中断保持1ms精度用一个计数器分频每5次或10次中断采集一次ADC得到200Hz或100Hz的有效采样率。这样定时精度由硬件保证不受主循环里其他任务影响采集的相邻采样点间隔均匀对后续差分计算非常重要。3. 固件实现定时采样、滤波与心率计算3.1 定时器定采样率别用延时很多课程设计代码里会出现这样的写法主循环里调用延时函数延时结束读一次ADC然后继续循环。这在仿真里看着能用但一旦加入按键扫描、LCD刷新或串口打印采样间隔就被拉长波形上升沿的数据点变少差分阈值法会漏检峰值。真正的做法是用定时器产生固定时基在中断里设置采样标志主循环检测到标志时读取ADC并处理。下面是一段51内核的定时器初始化代码。// 定时器0配置16位定时12MHz晶振1ms中断 void Timer0_Init(void) { TMOD 0xF0; // 保留定时器1的配置 TMOD | 0x01; // 定时器0模式116位 TH0 0xFC; // 初值65536-1000 64536 TL0 0x18; // 低8位 ET0 1; // 允许定时器0中断 TR0 1; // 启动定时器 } unsigned int tick_1ms 0; unsigned char adc_sample_flag 0; void Timer0_ISR(void) interrupt 1 { TH0 0xFC; // 重装初值 TL0 0x18; tick_1ms; if (tick_1ms % 5 0) { // 5ms进一次等效200Hz采样率 adc_sample_flag 1; } }逻辑说明定时器初值0xFC18对应64536定时器每计数到65536溢出一次正好耗时1ms。中断里重装初值保证连续运行误差很小。tick_1ms每毫秒加一对5取模为0时置采样标志意味着每5ms采样一次得到200Hz采样率。主循环里检测到标志位后立即读ADC、清标志、进入滤波和峰值检测流程。参数说明如果晶振是11.0592MHz初值需要重新计算常见做法是用65536 - (11059200 / 12 / 1000)算完再拆分高低字节。采样率不必追求整数但要稳定一致因为最终心率计算依赖两个相邻峰值之间的采样点数量。若改用100Hz采样只需把模值从5改成10。3.2 滑动平均和中值滤波的取舍采样序列进入算法前要先做预处理。滑动平均滤波器对周期性噪声的抑制效果好实现简单但会让波形边缘变钝窗口越长峰值位置延迟越大。中值滤波对尖峰脉冲噪声比如静电放电、电源开关毛刺有很好的滤除效果但排序开销大实时性差些。对心率检测我采用两级方案先做一次5点滑动平均去掉高频抖动再做一次3点中值滤波处理偶发毛刺。#define SAMPLE_BUF_LEN 5 unsigned int adc_raw[SAMPLE_BUF_LEN]; unsigned char buf_index 0; unsigned int filtered_value 0; // 返回滑动平均后的滤波值 unsigned int MovingAverage(unsigned int new_sample) { unsigned char i; unsigned long sum 0; adc_raw[buf_index] new_sample; buf_index (buf_index 1) % SAMPLE_BUF_LEN; for (i 0; i SAMPLE_BUF_LEN; i) { sum adc_raw[i]; } return (unsigned int)(sum / SAMPLE_BUF_LEN); }逻辑说明环形缓冲区保存最近5个采样点每次新采样覆盖最旧的值求和后取平均。缓冲区占用的RAM很少51单片机完全承受得住。为什么窗口取5而不是更大以200Hz采样率计算5点窗口对应25ms的时间跨度对脉搏波形来说只是把上升沿修整得更平滑不会抹掉主峰。参数说明窗口加大会增加相位延迟峰值点晚出现几个采样周期在心率计算公式里会造成读数偏低。如果信号噪声大可以先用10点平均但必须检查滤波后波形的峰值位置有没有明显右移。中值滤波窗口取3就够取5会明显改变波形的上升沿斜率。3.3 差分阈值法检出峰值并换算心率预处理完的信号是平滑的周期波动接下来要从中提取主波峰。方法很多有傅里叶变换、自相关、小波分析但51内核的算力有限浮点运算慢实用首选是差分阈值法。原理很简单把滤波序列做一阶差分脉搏波上升沿对应差分值显著增大经过一个正的阈值就认为可能进入上升段再配合峰值锁定逻辑避免一个主波被检出多个峰。unsigned int previous_filtered 0; signed int diff_value 0; signed int diff_threshold 30; unsigned char peak_lock 0; unsigned int last_peak_tick 0; unsigned int current_heart_rate 0; // 每收到一个新的滤波样点调用一次 void HeartRateDetect(unsigned int new_value) { diff_value (signed int)new_value - (signed int)previous_filtered; previous_filtered new_value; if (diff_value diff_threshold peak_lock 0) { // 检测到主峰的上升沿 peak_lock 1; if (last_peak_tick ! 0) { unsigned int interval tick_1ms - last_peak_tick; // 心率范围限制300bpm到30bpm即200ms到2000ms if (interval 200 interval 2000) { current_heart_rate 60000/interval; // ms换算为bpm } } last_peak_tick tick_1ms; } if (diff_value -diff_threshold) { // 下降沿到来解除锁定等待下一次上升 peak_lock 0; } }逻辑说明每5ms执行一次检测。diff_value是当前滤波值和上一次滤波值的差值脉搏波上升段会有连续的正差分超过阈值时认为是主峰起点记录当前时间戳。peak_lock的作用是避免上升段内的所有点都触发检测——锁存后只有差分值跌落到负阈值以下才认为一个脉搏周期结束。区间限制条件把两拍间隔限制在200ms到2000ms之间有效心率范围30到300bpm超出这个区间的间隔可能是干扰或运动伪迹不参与心率更新。参数说明diff_threshold是固定阈值需要根据放大后信号的幅度实测调整。更稳的写法是把阈值设为一段窗口内平均差分值的倍数例如取过去50个差分的绝对值平均后再乘1.2这样信号强弱变化时阈值能跟着变。锁存解除条件的负阈值可以比正阈值绝对值小一些保留一定的对称容差常见取值为正阈值的0.6到0.8倍。3.4 异常区间过滤与心率输出差分阈值法在信号平稳时表现良好但遇到基线漂移或突然的幅度变化仍可能把一次大的抖动当成一个周期。异常区间过滤的核心手段是生理合理性约束心率在一拍到下一拍之间不会突变超过某个范围。实际操作中每次算出的新心率如果与上次有效心率相差30%以上就不直接采用而是连续出现两个相近的新值才更新这能滤掉绝大多数误检。心率输出值的处理同样关键。每次主峰检测都会算出一个瞬时心率直接显示会看到数字在不停跳动可读性差。常见做法是取最近3到5次瞬时心率的平均作为显示值但要注意这个平均窗口不要跨过较长的暂停间隔否则在一次漏检后显示值会出现平缓拖尾。4. Proteus仿真与实物调试边看波形边调参数4.1 在Proteus中搭建最小可运行环境先讲仿真场景下可复现的步骤。Proteus仿真51单片机时传统AT89C52不带片内ADC需要外接一个ADC0809占用P1口读数据P3口控制通道选择和启动转换。如果你用的是STC12C5A60S2这类带内置ADC的STC单片机Proteus中的仿真模型不完全支持所有内部功能所以仿真建议统一用AT89C52加ADC0809方案代码和实物之间只需修改硬件抽象层。在原理图中放置AT89C52、ADC0809、LM358运放、信号发生器和虚拟串口。信号发生器输出频率设为1Hz、直流偏置2V、幅度0.2V的正弦波用来模拟放大后的脉搏波。运放输出接ADC0809的IN0通道。程序里编写一个Read_ADC()函数每次调用时启动通道0转换等待EOC引脚变低读取P1口数据。4.2 用虚拟信号源模拟脉搏波真实脉搏波不是正弦波它的特征是快速上升相对缓慢下降有重搏波切迹。Proteus内置的信号发生器只有标准波形生成的信号和真实数据有差距。更好的做法是把一组保存的真实波形数据放进单片机ROM里通过定时器中断按固定节奏输出到DAC或PWM引脚再把输出接到ADC输入形成自闭环。常见的做法是数组里存300个点代表3秒的脉搏波采样率100Hz每隔10ms从数组取一个点经过PWM输出再经RC低通平滑成模拟电压。这套结构的好处是波形完全可控你能随意调整幅值和波形形态用来验证算法在信号强弱变化时的表现。我在Proteus里调试心率检测算法时习惯在信号源输出和ADC输入之间串接一个可调电位器模拟信号幅值衰减确认算法在信号降到正常幅值一半时仍然可以稳定检测。4.3 逐步调试先看原始波形再谈算法仿真时最容易犯的错误是一上来就调阈值。正确顺序是先关掉所有数字滤波和峰值检测逻辑只保留定时采样功能采样结果通过串口发到虚拟终端用图形化显示查看原始波形幅度范围。Proteus的虚拟终端以数字方式显示不方便观察波形形态更好的办法是用串口把数据发到电脑端的上位机通过虚拟示波器绘制曲线。具体操作步骤把ADC采样值打包成三字节帧结构帧头加通道号波特率设为9600或19200虚拟串口把数据透传到PC端上位机上位机按帧解析后绘制波形。看到原始波形后先检查两个指标一是静态电平是否在量程中段二是波动幅度是否占到总量程的10%以上。如果这两个指标不满足回硬件端调偏置电压或放大倍数不要在软件里硬拉幅度。调试参数时可以记录一组对比数据不同滑动平均窗口长度下同一段波形检测到的峰值间隔是多少。举一个我在调试中遇到过的例子原始数组300个点代表3秒脉搏波用200Hz采样率采集信号发生器输出1Hz正弦波不滤波时差分阈值设为50峰值间隔平均199ms滤波窗口改到10点后峰值间隔变成了205ms瞬时心率计算值从301bpm掉到292bpm这个偏差完全由滤波相位延迟造成。理解这一点后在代码里固定滤波窗口不要频繁改动否则每次调整都得重新校准阈值。4.4 实物调试常见噪声来源实物调试比仿真多出几个现实问题。电源纹波是最常见的干扰源特别是用USB供电时开关电源的纹波会直接耦合到传感器和运放电源脚表现为波形上有规律的毛刺或正弦叠加。处理方法是在传感器和运放的电源引脚各加一个100uF电解电容和一个104瓷片电容。第二个常见问题是环境光干扰透射式探头对光路敏感手指没有完全遮挡探头时50Hz或100Hz的日光灯频闪会被光敏管接收在波形上形成周期性的幅值调制。第三个容易被忽略的问题是触摸屏或LCD刷新时的地弹。如果LCD数据总线在刷新瞬间有大电流变化地线上会形成瞬时电位差ADC采样值跳变。解决办法是在硬件上把模拟地和数字地单点连接在布局上让传感器信号走线远离数据总线。这些坑在Proteus里看不到实物阶段才会暴露提前在设计阶段做好处理能省去大量排查时间。5. 进阶技巧低功耗采样与心率跳变防抖5.1 断续采样1秒窗口只采200ms心率是慢变量但为了捕捉峰值不能把采样率降得太低。低功耗的思路不是降低采样率而是缩短采样时长。可以在100ms到200ms的窗口内以200Hz采样采集到20到40个点随后进入掉电模式等待下一个窗口开始。这种断续采样需要记录每次窗口内的有效心搏时间戳两次相邻窗口的时间差要用来修正间隔计算否则心率值会偏慢。对于带片内ADC的STC或STM32这个方案很容易实现用一个定时器产生适当周期的唤醒信号唤醒后进入采样循环完成窗口采集和特征检测后调用低功耗指令等下一个唤醒周期到来。这样平均功耗能降到一个数量级适合电池供电的场景。5.2 显示层防抖连续两拍确认再更新心率数值直接显示时会出现一个常见的“假跳变”问题某次干扰导致误检出一个异常短的间隔瞬时心率显示到180bpm下一秒又恢复正常。防抖的处理原则是新心率要连续得到两次相近的测量值才替换旧值。unsigned int display_hr 72; unsigned int previous_hr 0; unsigned int confirm_count 0; void UpdateDisplayHR(unsigned int new_hr) { // 与前一次比较偏差小于15%才认为可确认 if (previous_hr ! 0) { unsigned int diff (new_hr previous_hr) ? (new_hr - previous_hr) : (previous_hr - new_hr); if (diff * 10 previous_hr * 15) { confirm_count; } else { confirm_count 0; } } previous_hr new_hr; if (confirm_count 2) { display_hr new_hr; } }逻辑说明函数每检测到一个新的瞬时心率就调用一次。新心率与前一个瞬时心率做差如果偏差幅度小于15%说明两次检测结果物理上可信确认计数加一一旦偏差过大确认计数清零显示值保持不动。连续通过两次确认后才更新显示值。这个方法能把偶发误检隔离在显示层之外而不会丢失真实心率变化的灵敏度。5.3 联调检查点与参数复核做完上述所有步骤后用这样的流程做最后一轮联调先让被测对象静坐一分钟观察显示值波动范围是否在正负3bpm以内然后做小幅深呼吸观察心率变化能否在数秒内跟上并稳定下来最后用力甩动被测手确认显示值不会出现瞬时跳变而在一两秒内平滑过渡。如果第三个测试失败优先检查差分阈值是否为固定值尝试改成相对窗口均值的动态阈值并确认异常区间过滤逻辑没有因为静默期的存在而冻结更新。心率检测系统的排错顺序永远是信号在前、算法在后。先确认模拟前端输出的基线稳定、幅值足够再投入时间调滤波和阈值参数。把这一步做扎实剩下的代码实现只是时间问题。本文还有配套的精品资源点击获取