基于单片机的脉搏与呼吸监测报警设备设计要点解析
发布时间:2026/10/27 17:47:39 锦皓数字建站

简介一套面向单片机课程设计与毕业设计的完整项目资料围绕基于单片机的脉搏与呼吸监测报警设备完整覆盖硬件原理、程序设计、Proteus仿真与说明文档整理。压缩包共28个文件体积约1.13MB主要包含C语言源码与启动文件、可直接烧录的十六进制固件、Keil工程配置、Proteus仿真工程、原理图PDF、8幅流程框图、4张仿真效果图、元件清单和文字说明可满足从电路搭建、信号处理到代码调试各环节的需要。压缩包已有47人学习下载。源码实现了脉搏与呼吸数据采集、滤波算法、异常检测与报警输出并通过LCD显示屏实时显示状态仿真图与流程图能帮助理解传感器信号转数字处理的链路器件清单和原理图则方便对照实物组装与后期维护。整个项目从传感器选型到软件算法均有详细交代适合动手复现、二次开发也可作为毕业设计或课程设计的参考方案。1. 脉搏与呼吸监测报警设备设计难点全在信号调理和报警判据基于单片机的脉搏与呼吸监测报警设备设计与实现这标题看着像是两个传感器加一个蜂鸣器真正动手才知道难点在两处一是把脉搏、呼吸两路微弱信号调到单片机 ADC 能读的范围二是报警判据不能乱写否则不是乱响就是险情漏报。简单来说这类项目的核心并不是“把波形显示到屏幕”而是“在异常出现时可靠地发出报警”适合电子信息专业毕业设计、嵌入式入门到完整便携健康监测样机的过渡。做这类设备熟悉单片机顶多算基础信号链设计和阈值策略才是拉开差距的地方后面每一章节都在围绕这两件事展开。2. 两路生理信号的采集链路传感器选型、前置放大与滤波参数这一章解决的是信号能不能进单片机的问题。做过这类项目的人都知道最容易翻车的不是代码而是前级模拟链路光电传感器输出的是微安级电流压电薄膜输出的是电荷单片机 ADC 只能读 0~5V 电压中间不做信号调理后面的滤波和报警算法全是空中楼阁。所以我一般建议先把传感器选型定下来再逐级调放大电路最后才接单片机。2.1 脉搏采集方案光电式PPG为什么是首选脉搏采集常见方案有两种。光电式 PPG光电容积脉搏波利用 LED 照射皮肤光敏管接收透射或反射光血液搏动使组织对光的吸收量变化光敏管输出随之变化的电流压电式脉搏传感器则贴在桡动脉表面把动脉搏动变成电荷信号。对这类报警设备来说我首选 PPG。原因很直接佩戴方便夹手指或耳垂就行成本低一个红外对管加运放就能搭信号链路成熟I-V 转换后就是常见的小电压信号。压电式脉搏传感器虽然能直接反映动脉搏动但佩戴位置必须对准动脉而且前级要配电荷放大器身体一动信号就飘调试成本明显更高。我个人的常见做法是用反射式红外对管红外发射管和光敏三极管并排贴在手指腹一侧避开指甲。光敏管发射极接一个 100kΩ 负载电阻到地从发射极取电压信号。这个 100kΩ 是灵敏度关键太小信号幅度不足太大会进入饱和手指放上去波形削顶。实际调试时用示波器观察这个节点的波形调整到安静状态下峰峰值在 0.2~0.5V 比较合适。信号从光敏管出来后还不能直接进 ADC。要经过一个高通滤波把直流偏置和基线漂移去掉再经过低通滤波滤掉高频噪声最后做同相放大。我常给的参数如下电路位置阻容值截止频率作用I-V 负载100kΩ—光电流转电压决定灵敏度高通10µF 100kΩ约 0.16Hz去掉直流偏置和基线漂移同相放大LM358增益 5~10 倍—把信号抬到 ADC 量程低通33kΩ 1µF约 4.8Hz滤掉高频噪声保留脉搏主波这套参数下脉搏信号频带落在 0.5~5Hz 内ADC 输入幅度能做到几百 mV 到 2V。注意运放要用单电源供电同相输入端加 VCC/2 的偏置不然信号负半周会被削掉。调试顺序是先量运放静态输出是否在 VCC/2 附近再放手指看放大输出最后才接 ADC。2.2 呼吸采集的三种方案对比与推荐呼吸监测没有“夹手指”那么方便常见方案有三种热敏电阻气流法、应变式呼吸绑带、压电薄膜式呼吸绑带。热敏电阻放在鼻孔前呼吸气流改变电阻温度电路最简单一个分压加放大就够但佩戴体验差微弱呼吸时输出变化很小而且对环境温度敏感放在床边稍微有点风就会漂。应变式呼吸绑带通过胸腹周长变化改变电阻需要搭桥式电路稳定但器件偏多。压电薄膜绑带利用呼吸时胸腹扩张拉伸薄膜产生电荷输出信号和呼吸深度线性相关波形比热敏电阻干净适合做报警判据。我的选择是压电薄膜式。原因在于报警设备最怕基线漂移和误报压电式的信号信噪比相对高波形特征明显做暂停检测更容易。前级电路也不复杂常见做法是电荷放大级加带通滤波运放反相端并一个反馈电容 C_f1nF 和反馈电阻 R_f10MΩ把电荷转成电压时间常数配合得到约 16Hz 的低通后面再串一个 10µF 隔直电容到放大级去掉直流漏电。方案输出信号佩戴体验抗干扰推荐度热敏电阻电压变化幅度小差鼻孔处不适中怕环境温度低应变式绑带电阻变化需电桥中可接受中中压电薄膜绑带电荷信号信噪比好较好绑在胸腹较高高压电薄膜有个坑连接电缆必须固定好衣物反复摩擦会产生摩擦电荷叠到呼吸波形上就是毛刺和漂移。所以我会用屏蔽线并且在软件里对呼吸通道单独加一阶高通滤波截止频率设在 0.05Hz 上下专门对付基线缓慢上移。2.3 把毫伏级信号抬到ADC能读的范围前级调试三步骤前级调好了项目就成功一半。这里给出一套可复现的调试流程我每次搭这类板子都按这个顺序走。第一步上电后先用万用表量运放输出脚的静态电压。单电源供电时理论值应该是 VCC/2 附近偏差太大说明偏置电路有问题或者运放工作点不对。第二步接上传感器但先不接单片机用示波器从 I-V 转换输出开始看波形确认有周期性信号如果什么都没有优先检查传感器接反、LED 是否点亮、负载电阻是否虚焊。第三步逐级往后量调整增益电阻让放大输出峰峰值落在 1~3V确保不削顶也不太小。这一章还有个容易忽略的地方ADC 参考电压。很多带片内 ADC 的 51 内核单片机参考电压直接就是 VCCVCC 不稳ADC 读数就会跟着漂。所以我习惯把模拟电源和数字电源用 10Ω 电阻或磁珠分开供电传感器和运放走模拟电源单片机走数字电源VREF 引脚附近加一个 100µF 电解电容和一个 104 电容。这个做法成本低但能直接减少后面报警误触发的一大半问题。3. 单片机上的信号处理与报警状态机ADC采样、峰值检测与去抖判定传感器信号调理明白之后接下来就是单片机的活了。这里不只是把 ADC 值读进来再比大小还要解决采样节奏、峰值定位、心率换算和报警状态迁移。很多同学拿着开发板点灯很熟一写这种多任务逻辑就乱本质是没有把“采样”和“判定”拆成两个频率不同的事。3.1 系统架构与采样策略两个通道共用一套ADC主控我建议直接用带片内 10 位 ADC 的 STC 系列比如 STC12C5A60S2 或 STC15 系列省掉外挂 ADC0809 那一堆并口走线。用经典 AT89C52 也行但外扩片外 ADC 会让代码量翻倍Proteus 仿真里好使实物接线却很痛苦。采样率不用高。脉搏波主频约 1Hz呼吸波约 0.3Hz按奈奎斯特原则 10Hz 就够但为了峰值定位和心率间隔计算我一般把采样率定在 50Hz也就是 10ms 定时中断一次ADC 交替采两个通道。这样每一路实际采样率也是 50Hz完全够用。定时器初始化代码按 12MHz 晶振、定时器 0 方式 1 写void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器0方式116位不自动重装 TH0 (65536 - 10000) / 256; // 12MHz晶振10ms定时 TL0 (65536 - 10000) % 256; ET0 1; TR0 1; EA 1; }注意这里的初值是按传统 51 的 12T 模式算的也就是机器周期 1µs10ms 需要 10000 个计数。如果买了 STC 的 1T 系列又不小心在烧录工具里选了 1T这个初值要重新算否则定时时间会快 12 倍。定时中断里轮流采集unsigned int adc_val[2]; unsigned char ch_sel 0; void Timer0_ISR(void) __interrupt 1 { adc_val[ch_sel] ADC_Read(ch_sel); ch_sel ^ 1; // 两个通道轮流采各50Hz }逻辑说明ch_sel 每次中断后翻转一次两个通道交替进入 ADC采样间隔严格等距不会出现主循环被按键阻塞导致采样断档的情况。主循环只做滤波、峰值检测、状态机和显示不做任何延时等待。3.2 脉搏的峰值检测与瞬时心率滑动窗口里的判定技巧ADC 原始值直接拿来找峰值会非常痛苦量化噪声和高频毛刺会产生一堆假峰。我先做移动平均滤波窗口选 8 个点50Hz 下对应 160ms 的平滑时间不会把 1Hz 的主波峰削平又能滤掉大部分高频抖动。用循环队列实现#define WIN 8 unsigned int buf[WIN]; unsigned char idx 0; unsigned int Filter_ADC(unsigned int raw) { static unsigned int sum 0; sum raw - buf[idx]; buf[idx] raw; idx (idx 1) % WIN; return sum / WIN; }逻辑说明sum 保存窗口内总和每次新数据进来替换掉最旧的一个实现 O(1) 的滑动平均。要注意 sum 是 unsigned int8 个 10 位 ADC 值最大才 8184不会溢出如果窗口加到 32 就该换 unsigned long。峰值检测不能用“当前值大于前一个值”这种简单判断因为 ADC 噪声会让斜率判断乱跳。我习惯用“候选峰值下降幅度”法记录一个候选峰值当信号从峰值回落超过 25 个 ADC 量化值时才认为刚才那个峰成立。unsigned int last_peak_tick 0; unsigned int hr_bpm 0; unsigned long tick 0; // 10ms递增一次 void Update_Pulse(unsigned int x) { static unsigned int peak_cand 0, peak_tick 0; if (x peak_cand) { peak_cand x; peak_tick tick; } else if (peak_cand - x 25) { unsigned int interval tick - last_peak_tick; if (interval 300 interval 1500) { hr_bpm 60000 / (interval * 10); last_peak_tick tick; } peak_cand 0; } }参数说明25 这个阈值是消噪门槛太小容易把毛刺当峰太大又会漏掉真实脉搏我用 10 位 ADC、信号幅度在 1~3V 时25 量化值大约对应 120mV 的回退幅度实际效果比较稳。interval 单位是 tick每个 tick 是 10ms所以真实间隔是 interval*10ms心率就是 60000 除以这个毫秒数。interval 限制在 300~1500 个 tick也就是 3 到 15 秒对应心率 40~200 次/分超出这个范围的间隔直接丢弃不做心率更新。3.3 呼吸暂停判定与报警状态机去抖和迟滞别省呼吸暂停不依赖幅值而是依赖“有没有新峰值”。我的做法是单独维护一个 last_breath_tick只要呼吸波检测到新峰就刷新状态机里判断 tick - last_breath_tick 是否超过设定秒数一旦超过就进入呼吸暂停报警。报警逻辑不要用 if 堆要用状态机。报警最忌讳的是在阈值边界来回切换所以我给自己定了一条规矩进入报警需要连续超阈值 N 秒退出报警需要心率回到阈值内并保持一段时间同时用迟滞区间防止临界抖动。typedef enum { MONITOR, PULSE_ALERT, BREATH_ALERT, MANUAL_SILENCE } AlarmState; AlarmState alarm_state MONITOR; unsigned int pul_high_cnt 0; unsigned int breath_pause_cnt 0; void Alarm_Task(void) { switch (alarm_state) { case MONITOR: if (hr_bpm HR_HIGH_LIMIT) { if (pul_high_cnt 250) { // 250个tick 2.5秒确认 alarm_state PULSE_ALERT; Buzzer_On(BEEP_FAST); } } else { pul_high_cnt 0; } if (tick - last_breath_tick BREATH_PAUSE_LIMIT_TICK) { if (breath_pause_cnt 100) { // 再延迟1秒 alarm_state BREATH_ALERT; Buzzer_On(BEEP_LONG); } } else { breath_pause_cnt 0; } break; case PULSE_ALERT: if (hr_bpm HR_HIGH_LIMIT - 5 hr_bpm HR_LOW_LIMIT 5) { pul_high_cnt 0; alarm_state MONITOR; } break; case BREATH_ALERT: if (tick - last_breath_tick BREATH_PAUSE_LIMIT_TICK) { breath_pause_cnt 0; alarm_state MONITOR; } break; case MANUAL_SILENCE: // 静音30秒后回到MONITOR条件满足会再次报警 break; } }逻辑说明进入脉搏报警之前心率必须连续 2.5 秒超上限这能滤掉一次性的运动伪迹退出报警用了 HR_HIGH_LIMIT-5 和 HR_LOW_LIMIT5 的迟滞窗口防止设备在阈值边缘反复响。呼吸暂停是在“超时未出现新峰”基础上再连续确认 1 秒才报警避免呼吸波形毛刺导致计时短暂刷新。MANUAL_SILENCE 状态由按键触发30 秒后强制回到 MONITOR避免用户按一次静音就不再响。提示状态机里的 250、100、5 这些数字不是玄学是依据“报警确认时间”和“误报容忍度”反推出来的。先把设备接到电脑连续记录几小时数据离线回放看误报率再把这些参数调平比在板子上靠肉眼猜靠谱得多。4. 硬件调试避坑Proteus仿真没问题板子却翻车的五类原因这一章写硬件调试里最常见的坑每一条都是我或者同行实际踩过的。仿真软件解决不了电源噪声和真实传感器的问题所以实物调试才是这个项目真正花时间的地方。4.1 电源纹波让脉搏波形带毛刺阈值没法设现象示波器看脉搏波形主波上叠了一层几十毫伏的高频毛刺报警阈值无论设多少都会偶尔误报一次。原因开关电源或 USB 供电的纹波直接串进了模拟前级传感器排线太长又没有屏蔽工频 50Hz 干扰耦合进来运放的模拟地和数字地混在一起。解决我给模拟部分单独用线性稳压供电传感器和运放不走开发板的 5V 输出模拟地和数字地单点汇合位置选在单片机的 GND 引脚附近I-V 转换输出处加一阶 RC 低通33kΩ 加 1µF截止约 4.8Hz最后在软件里再叠加 8 点移动平均。做完这三步毛刺基本能压到可接受范围。4.2 单片机下载失败不是所有STC都能随便冷启动现象51 单片机下载软件一直提示“正在检测单片机”波特率换了几档都不行按钮按烂了也进不了下载状态。原因最常见是没做冷启动。STC 单片机要在软件点“下载”之后给板子断电再重新上电程序才会进入 ISP 引导区另外 P3.0/P3.1 如果被 LED 或按键占用会把下载时序拉低导致握手失败。解决先把 P3.0/P3.1 外接的上拉或负载断开然后点下载按钮再给板子断电上电如果还是失败把波特率降到 2400 试一次同时检查 USB 转串口芯片是否虚焊。这一步不是玄学是时序握手问题按这个顺序排查基本能解决。4.3 呼吸通道波形一直漂压电薄膜和电荷放大器是难兄难弟现象呼吸波刚上电时正常过一两分钟基线缓慢上移最后运放输出饱和ADC 读回满幅呼吸暂停报警直接失效。原因压电薄膜本身有直流漏电电荷放大器的输入阻抗不够时漏电会被放大反馈电阻和反馈电容配出的时间常数太长基线漂移无法滤掉电缆反复弯折产生的摩擦电荷也会叠加到信号上。解决电荷放大级反馈电阻用 10MΩ反馈电容用 1nF令下限截止在约 0.016Hz软件里再给呼吸通道加一阶 IIR 高通截止频率设在 0.05Hz。两道高通一起把基线压住。同时把呼吸电缆用胶带固定避免它跟着身体运动来回弯曲。4.4 Proteus仿真通过真板子却不工作最小系统三件套背锅现象Proteus 里 LED 正常闪烁ADC 数值也正常程序烧到真板子上不是没反应就是乱跑。原因仿真软件默认晶振、复位、上拉都是理想状态真板子少了负载电容、复位电路或者 P0 口上拉电阻最小系统就不成立。解决对照最小系统图逐项核对晶振两个引脚各接一个 20~30pF 负载电容复位脚接 10µF 电容到 VCC、10kΩ 电阻到 GNDP0 口加 4.7kΩ 或 10kΩ 上拉排阻芯片 VCC 和 GND 之间加 104 去耦电容。这些器件在仿真里可以省略实物上一个都不能少。4.5 信号脱落和运动伪迹导致误报警真机测试才暴露现象传感器被碰掉或者手指移开设备报“心率过低”并持续报警翻个身、挥一下手呼吸通道就报“呼吸暂停”。原因算法把所有 ADC 值都当成有效信号没有区分“信号脱落”和“真实幅值偏小”固定阈值对运动伪迹没有分辨能力幅度大的尖峰被误判成脉搏峰。解决在最前面加信号有效性判断。ADC 值接近满幅或接近 0直接认定传感器脱落点亮脱落指示灯但不触发心率报警瞬时心率超过 200 或低于 40认为是伪峰丢弃不更新。检测到信号脱落时报警状态机回到 MONITOR等信号恢复后重新计数。这样设备至少能做到“没信号时不乱叫”。5. 报警阈值怎么调才不误报基线标定、滤波系数与边界情况前面把硬件和状态机都跑通了最后决定这套设备好不好用的是阈值怎么设。阈值太死换个使用者就疯狂误报阈值太松真实异常又会被漏掉。我的做法是分层处理先开机标定信号基线再按人群预设阈值档位最后补充边界策略。5.1 开机基线标定先学30秒“正常”信号设备上电后不直接进入监测而是先进入 30 秒标定模式指示灯闪三下提示用户保持安静。期间统计两个通道的最小值、最大值和过峰次数得到信号幅度基线。代码骨架可以这样写#define CALIB_TICKS 3000 void Calibration_Task(void) { static unsigned int ticks 0; static unsigned int v_min 0xFFFF, v_max 0; if (ticks CALIB_TICKS) { if (adc_val[0] v_min) v_min adc_val[0]; if (adc_val[0] v_max) v_max adc_val[0]; ticks; } else { unsigned int amp v_max - v_min; if (amp MIN_VALID_AMP) { status SIGNAL_LEAD_OFF; } ticks 0; v_min 0xFFFF; v_max 0; } }参数说明30 秒是折中值。太短用户咳嗽一下或者调整姿势就会把极值带偏太长等待感受很差。标定的目的不是把当前心率设成“正常基线”而是确认传感器贴好了、信号幅度足够大。如果 30 秒内信号幅度低于设定门槛直接提示重新佩戴不进入后续监测流程。5.2 成人、儿童、老人三档预设阈值表不是拍脑袋报警阈值必须面对真实人群。成人静止心率 60~100 次/分但老人可能只有 50 上下儿童安静时 110~130 也常见。所以我在设置菜单里提供三个档位长按按键进入短按切换再长按确认档位脉搏下限脉搏上限呼吸下限呼吸暂停延迟适用人群成人501201210秒18~60岁老年451201015秒60岁以上儿童60140148秒3~12岁说明一下这套数值是按常见生理参考范围和“宁误报不漏报”原则折中的结果不是医疗器械级标准。呼吸暂停延迟必须比正常呼吸周期长成人呼吸周期约 3~5 秒10 秒没有新峰已经很能说明问题设太短屏住呼吸几秒钟就会误报老人睡眠中翻身憋气也容易触发。5.3 滤波系数、迟滞窗口和边界策略滤波系数不是越大越好。移动平均窗口从 8 加到 16波形确实更平滑但峰值时刻会被拉后瞬时心率更新变慢呼吸暂停检测的响应也会变迟钝。我的经验是50Hz 采样下脉搏窗口 8 点、呼吸窗口 4 点够用即可。迟滞窗口前面已经提过这里再强调边界策略。报警设备最怕的其实不是不报而是被“狼来了”消耗信任。所以我把边界判定优先级排在阈值判定之前if (adc_val[0] 5 || adc_val[0] 1018) { status SIGNAL_LEAD_OFF; return; } if (hr_bpm 200 || hr_bpm 30) { status SIGNAL_UNRELIABLE; return; }逻辑说明第一段判断传感器脱落或饱和10 位 ADC 满量程 1023接近边界说明光路被完全遮挡或运放输出饱和这种数据不能用于报警。第二段判断心率数值是否生理可能超过 200 次/分或低于 30 次/分在绝大多数成年人身上都不成立出现这种值说明峰值检测被运动伪迹干扰应该丢弃而不是触发报警。边界情况处理完报警才不会被当成笑话。设备可以在少数时候保持沉默但不能在传感器脱落时把“脱落的安静”误判成“心跳停搏”。6. 升级成一拖多的病房监测方案用串口把多台设备连到一台电脑单台设备只能就地声光报警护士站根本听不到。常见做法是给每台设备加 RS485 通信用地址区分节点把所有数据汇总到一台电脑上做集中显示。这样单片机端只保留了“本地报警”和“数据上报”两个功能上位机负责记录和二次报警。6.1 多设备串口拓扑与ASCII协议RS485 总线只需要两根线几十米距离没问题适合病房这类场景。每台单片机分配一个地址从 1 到 32通过拨码开关设置。每秒上报一行 ASCII 文本格式很简单NODE1,HR72,BR16,STATUSOK NODE2,HR0,BR0,STATUSLEAD_OFF用 ASCII 而不是二进制协议的原因很实际串口助手直接就能看调试期不用写上位机解析工具9600 波特率下每秒传几十字节余量非常充足。6.2 上位机验证与联调方法上位机验证我一般用 Python 的 pyserial 写一个最小监听脚本import serial ser serial.Serial(COM3, 9600, timeout1) while True: line ser.readline().decode(ascii, errorsignore).strip() if not line: continue if ALERT in line or LEAD_OFF in line: print(line)这段脚本不做什么复杂逻辑只把报警状态打出来用来做 24 小时连续运行的误报率记录。正式验证我会先用信号发生器代替真人正弦波模拟脉搏 1Hz方波模拟呼吸 0.25Hz连续跑 24 小时要求误报为 0然后接真人试戴 2 小时允许一次由大幅度运动引起的误报但不允许连续误报。最后说一个我自己的教训。最早搭这套设备时我把采样、滤波、报警全塞在一个 while 循环里结果按键扫描一卡报警延迟跟着乱改成定时器采样加主循环状态机之后误跳频才真正压下来。从那以后我给自己立了规矩采样必须走中断判定必须走状态机所有阈值必须做成可调参数并记录调整日期。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。