1. 项目概述从零搭建一个桌面级心电信号采集系统心电信号也就是我们常说的ECG是心脏肌肉在收缩和舒张过程中产生的微弱电信号。对于电子爱好者、生物医学工程的学生或者任何对生理信号采集感兴趣的人来说能够亲手搭建一个系统从自己身上采集并看到实时的心跳波形是一件极具成就感的事情。这个项目就是围绕Arduino Uno设计并实现一个完整的心电信号采集与显示系统。它不仅仅是一个简单的“传感器读数”项目而是融合了模拟电路设计信号放大与滤波、生物信号特性理解以及嵌入式系统编程的综合实践。整个系统的核心目标很明确从人体体表拾取仅有毫伏级别的微弱心电信号经过一系列电路处理将其放大、净化最终通过Arduino转换为数字信号在电脑上实时绘制出清晰的心电图波形并计算出实时心率。你最终会得到一套可以放在桌面上运行的设备通过几个电极贴片连接到身体通常是手腕和脚踝就能在电脑的串口绘图仪上看到自己的心跳。需要强调的是这个系统是一个教学和实验性质的设备它的精度、安全性和可靠性都无法与专业的医疗诊断设备相提并论绝不能用于任何形式的医疗诊断或健康评估其价值在于学习和理解生物信号采集的基本原理与工程实现。2. 系统核心设计思路与方案选型为什么选择这样的架构这背后是一连串的工程权衡。心电信号极其微弱典型幅度只有0.5mV到2mV而环境中充斥着各种噪声尤其是50Hz国内或60Hz国外的工频干扰其强度可能远大于心电信号本身。因此直接用一个Arduino的模拟输入口分辨率为5V/1024 ≈ 4.9mV去读取信号早就淹没在噪声里了。所以我们必须设计一个前端的模拟信号调理电路完成“放大”和“滤波”两大任务。2.1 为什么是三级电路我们采用了经典的三级模拟电路串联方案仪表放大器 - 陷波滤波器 - 低通滤波器。这是一种逐级解决问题的思路。第一级仪表放大器。它的首要任务是“放大”。我们需要将毫伏信号放大到伏特级别以便Arduino能够有效分辨。为什么选择仪表放大器而不是普通运放因为从人体采集的信号是差分信号两个测量点之间的电位差仪表放大器具有极高的共模抑制比能有效抑制同时出现在两个输入端的干扰比如工频干扰只放大我们需要的差分信号。这是获取干净信号的第一步。第二级陷波滤波器。它的任务是“精准剔除”。经过放大信号变大了但噪声也被放大了。其中50/60Hz的工频干扰是最顽固、最强大的单一频率噪声源。陷波滤波器就像一个频率“陷阱”专门针对这一个特定频率进行深度衰减理想情况下在该频率点增益为0从而将其从信号中剔除。第三级低通滤波器。它的任务是“模糊处理”。心电信号的有效频率成分一般在0.05Hz到100Hz左右超过150Hz的成分大多是肌电干扰肌肉活动、电极接触噪声等无用高频噪声。一个截止频率在100-150Hz的低通滤波器可以平滑地衰减这些高频噪声使波形看起来更干净。2.2 核心器件选型考量运算放大器 uA741这是一个古老而经典的通用型运放。选择它主要是因为其易得、廉价且文档丰富非常适合教学和原型验证。但它并非最优选择其输入偏置电流较大、带宽有限。在实际追求更高性能的项目中可以考虑如AD620专用仪表放大器芯片、OPA系列低噪声、高精度运放等。本项目使用uA741完全足以验证原理并得到可见波形。Arduino Uno选择它是因为其庞大的社区支持和简单的开发环境。它的10位ADC模拟-数字转换器对于这个项目来说精度足够放大后的信号峰值控制在0-5V范围内。其内置的串口通信和强大的Serial Plotter串口绘图仪工具使得实时波形显示变得异常简单无需编写复杂的上位机软件。电源使用两节9V电池为运放电路供电构成±9V的双电源系统。这是关键大多数运放需要正负双电源才能输出正负摆幅的信号心电信号有正有负。电池供电也彻底隔离了市电避免了通过电源线引入的额外工频干扰这是生物信号采集中的常见技巧。注意电路中的电阻、电容值并非绝对不可变。原文作者也提到了因手头元件进行的替换。在电子制作中理解电路原理比死记硬背元件值更重要。只要通过公式计算保持电路的时间常数、截止频率等关键参数在目标范围内使用相近值的元件是完全可以的。3. 硬件电路详解与搭建要点这一部分我们将深入每一级电路的原理、计算和搭建时的“坑”。3.1 仪表放大器把微伏信号“喊大声”仪表放大器的核心在于其差分放大结构。我们使用三个uA741运放搭建经典的三运放仪表放大器电路。增益计算仪表放大器的差分增益公式为G (1 2*R1/Rg) * (R3/R2)。在典型电路中通常令R2 R3公式简化为G 1 2*R1/Rg。原文设定目标增益为2000。假设我们选择R1 100kΩ那么根据公式2000 1 2*100k / Rg可以反推出增益电阻Rg ≈ 100Ω。这是一个非常小的电阻在实际中可能需要用精密电位器仔细调节。实操要点布局与走线这是高增益放大电路布局至关重要。输入线来自电极的导线要尽可能短最好使用屏蔽线并将屏蔽层单点接地通常在Arduino的GND点。所有连接要牢固避免使用过长、松散的跳线。电源去耦必须在每个运放的电源引脚V和V-附近紧贴着芯片放置一个0.1μF的陶瓷电容到地用于滤除电源线上的高频噪声。这是保证运放稳定工作、避免自激振荡的必须步骤很多初学者会忽略这一点导致电路异常。偏置通路uA741需要提供输入偏置电流的返回路径。由于我们通过电极连接人体而人体对直流可以视为开路因此需要在两个输入端通过大电阻如1MΩ - 10MΩ连接到地为运放的输入晶体管提供微小的偏置电流通路否则输出可能会饱和到电源电压。3.2 双T型陷波滤波器精准狙击50/60Hz干扰我们采用经典的双T型有源陷波滤波器。它的传递函数在中心频率处有一个很深的凹陷。中心频率计算对于对称双T网络陷波中心频率f0 1 / (2πRC)。目标是滤除50Hz国内或60Hz国外干扰。以60Hz为例如果我们选择C 0.1μF (0.1 × 10^-6 F)那么可以计算出R 1 / (2π * 60 * 0.1e-6) ≈ 26.5 kΩ。在实际电路中为了精确调谐常会用一个固定电阻串联一个精密电位器来微调这个R值。品质因数Q值Q值决定了陷波的“尖锐”程度。Q值越高陷波越深越窄。通过调整双T网络与运放反馈网络中的电阻比例可以调节Q值。对于工频干扰一个中等Q值的陷波器即可太窄可能因为市电频率微小波动而失效太宽则会过度损伤心电信号中靠近该频率的有用成分。实操心得搭建好后最好用函数发生器和示波器进行验证。输入一个正弦波从低频到高频扫频观察输出幅度。你应该能看到在50/60Hz附近输出信号幅度急剧下降。如果没有示波器可以临时用Arduino的ADC读取滤波前后的信号在50/60Hz输入下观察读数是否显著降低。3.3 二阶低通滤波器给信号“磨皮”采用压控电压源型的二阶低通滤波器。它结构简单性能适中。截止频率计算截止频率fc 1 / (2π * sqrt(R1*R2*C1*C2))。通常为了设计方便令R1 R2 RC1 C2 C则公式简化为fc 1 / (2πRC)。假设我们设定截止频率为100Hz选择C 0.1μF则R ≈ 15.9 kΩ。巴特沃斯响应通过调整电容C1和C2的比例例如令C2 2*C1可以获得巴特沃斯响应即在通带内具有最平坦的幅度特性。这对于希望保持波形形状不失真的心电信号来说是比较好的选择。搭建检查低通滤波器的验证同样可以通过扫频进行。输入一个高频信号如500Hz输出应衰减到很小输入一个低频信号如10Hz输出应几乎无衰减地通过。3.4 电极连接与身体导联这是信号进入系统的“门户”其质量直接决定了最终结果的成败。导联配置本项目采用标准的肢体导联II配置。具体接法是红色电极正输入- 左腿或左脚踝。黄色电极负输入- 右臂或右手腕。黑色电极参考地- 右腿或右脚踝。 这个配置产生的心电波形通常P波、QRS波群和T波都非常明显。电极使用技巧皮肤处理用酒精棉片清洁电极粘贴部位的皮肤去除油脂和死皮能显著降低接触阻抗和运动伪影。导电膏如果使用可重复使用的电极片涂抹少量导电膏可以极大改善信号质量。一次性电极片通常自带导电凝胶。保持静止在测量时尽量保持身体和四肢静止。轻微的肌肉运动都会产生肌电信号EMG其幅度可能与ECG相当干扰严重。导线固定将电极导线用胶带或夹子固定在身体或衣服上避免导线悬空摆动这种摆动会产生摩擦生电的噪声。4. 软件编程与Arduino数据处理逻辑硬件电路输出的已经是比较干净的模拟心电信号了接下来Arduino的工作是采样、简单的数字处理以及数据发送。4.1 模拟信号采样Arduino Uno的ADC是10位精度参考电压默认为5V。这意味着它将0-5V的输入电压映射为0-1023的整数值。int sensorValue analogRead(A0); // 从A0引脚读取原始值 float voltage sensorValue * (5.0 / 1023.0); // 转换为电压值单位伏特在连接电路时需要确保放大滤波后的信号峰值在0-5V之间最好居中如偏置在2.5V附近以充分利用ADC的动态范围同时避免信号削顶超过5V或削底低于0V。4.2 心率检测算法解析原文提供的代码使用了一种简单的阈值检测法来识别QRS波心电图中最高大的波峰进而计算心率。这是嵌入式系统中一种经典且高效的轻量级算法。int UpperThreshold 50; // 峰值检测阈值 int LowerThreshold 90; // 回落检测阈值注意此值高于峰值阈值原文可能有误 int reading 0; bool IgnoreReading false; unsigned long FirstPulseTime 0; unsigned long SecondPulseTime 0; unsigned long PulseInterval 0; void loop() { reading analogRead(A0); // 检测心跳上升沿QRS波峰值 if(reading UpperThreshold IgnoreReading false){ if(FirstPulseDetected false){ FirstPulseTime millis(); FirstPulseDetected true; } else { SecondPulseTime millis(); PulseInterval SecondPulseTime - FirstPulseTime; // 计算两次心跳间隔 FirstPulseTime SecondPulseTime; // 更新时间点 } IgnoreReading true; // 标记为已检测防止在同一个波峰内重复计数 } // 检测心跳下降沿信号回落 if(reading LowerThreshold){ IgnoreReading false; // 重置标记准备检测下一个波峰 } // 计算心率次/分钟 if(PulseInterval 0){ // 避免除以零 BPM 60000 / PulseInterval; // 60000毫秒 1分钟 } Serial.print(BPM: ); Serial.println(BPM); }算法核心逻辑设置一个UpperThreshold。当采样值超过它时认为可能检测到了一个QRS波峰。一旦检测到波峰立即设置一个“忽略区”IgnoreReading true防止在同一个宽大的QRS波内因信号波动而多次触发。设置一个较低的LowerThreshold。只有当信号回落到这个阈值以下时才解除“忽略区”IgnoreReading false系统才准备检测下一个QRS波。记录连续两个QRS波峰的时间点其时间差即为心跳间隔PulseInterval据此计算心率BPM。重要纠偏与优化建议原文代码中LowerThreshold 90且判断条件为reading LowerThreshold reading 2这看起来有些矛盾。通常LowerThreshold应低于UpperThreshold用于判断信号是否已从峰值充分回落。一个更合理的设置是UpperThreshold 520(对应约2.5V)LowerThreshold 480(对应约2.35V)具体值需要根据你实际放大后的信号幅度来调整。优化方向简单的阈值法易受基线漂移和噪声干扰。更鲁棒的算法可以加入滑动窗口均值滤波来动态估算基线或者使用导数法检测R波更陡峭的上升沿来提高准确性。4.3 串口数据输出与波形显示这是最直观的部分。Arduino通过串口同时发送两样东西原始电压值用于绘制波形。Serial.println(voltage);计算出的心率值用于数字显示。Serial.print(BPM: ); Serial.println(BPM);在Arduino IDE中打开工具 - 串口绘图仪。正确配置波特率与代码中Serial.begin()一致如74880或更常见的9600、115200你就能看到一个实时滚动的波形图并在下方看到不断更新的心率数值。串口绘图仪会自动将接收到的数字绘制成曲线将“BPM: 72”这样的文本显示在角落。5. 系统集成、调试与故障排查实录当所有模块搭建完毕集成调试才是真正的挑战。以下是我在多次实践中总结的步骤和常见问题。5.1 上电前终极检查电源极性反复检查±9V电池是否正确连接到运放的V和V-引脚切勿反接用万用表确认电压。接地一致性确保整个系统只有一个“地”参考点。将所有电路的地、Arduino的GND、电池的中间抽头如果是双电池供电的虚拟地可靠地连接在一起。元件值对照电路图用万用表的电阻档和电容档抽查关键位置的电阻、电容值特别是决定增益和频率的元件。5.2 分级调试法强烈推荐不要一次性连接所有电路。采用“从前到后”或“从后到前”的分级调试。方法A从后到前先不接电极用函数发生器给低通滤波器的输入注入一个1Hz-100Hz的正弦波幅度约1V。用示波器观察其输出确认低通滤波器工作正常高频衰减。将信号源移到陷波滤波器的输入输入一个50/60Hz正弦波。观察输出是否被极大衰减。再输入一个30Hz或100Hz信号观察是否正常通过。最后测试仪表放大器。在其输入端接入一个很小的差分信号可用两个电阻分压产生几毫伏的电压差测量其输出增益是否符合设计。方法B使用Arduino验证如果没有示波器可以在每一级电路的输出端临时连接到Arduino的A0口通过串口监视器观察数值变化。输入不同的频率和幅度看读数变化是否符合滤波器特性例如输入60Hz时读数应最小。5.3 连接人体测试与信号优化当模拟测试通过后断开函数发生器连接电极到身体。初始状态先不要运行心率计算代码只运行发送原始电压值的代码。打开串口绘图仪观察原始波形。常见问题与对策问题1信号幅度太小或太大。排查检查仪表放大器增益电阻Rg。信号太小则减小Rg太大则增大Rg。确保信号峰值在0-5V范围内且最好有正负摆动。问题2波形上有规律的50/60Hz正弦波干扰。排查陷波滤波器未调准或失效。检查双T网络的R、C值尝试微调电阻。确保电路接地良好电极导线使用屏蔽线。问题3波形基线缓慢上下漂移基线漂移。原因这是心电采集的典型问题由呼吸、电极接触电位变化引起。对策在软件中加入数字高通滤波即基线漂移校正。最简单的方法是在Arduino代码中计算一个滑动平均作为“动态基线”然后从每个采样点中减去这个基线值。问题4波形毛刺多不光滑。排查高频噪声。确认低通滤波器截止频率是否合适。检查电源去耦电容是否焊好。确保测量时身体和导线保持静止。问题5心率计算完全不准确或乱跳。排查调整UpperThreshold和LowerThreshold。它们必须根据你实际的信号幅度来设置。打开串口绘图仪观察稳定的QRS波峰值和谷值对应的ADC读数将阈值设在其间。例如峰值读数为800谷值读数为200则UpperThreshold可设为700LowerThreshold可设为300。5.4 提升系统稳定性的进阶技巧右腿驱动电路这是一个高级但非常有效的技巧。它不是简单地将右腿接地而是通过一个运放将身体共模信号反相放大后再驱动右腿电极。这能主动抵消从身体感应的共模干扰主要是工频干扰可以显著提升信号质量。软件数字滤波在Arduino上实现简单的数字滤波器如移动平均滤波去毛刺或一阶高通滤波去基线漂移可以弥补硬件电路的不足且灵活可调。使用专用仪表放大器芯片如AD620或INA128。它们将三运放仪表放大器集成在一颗芯片内匹配精度高共模抑制比远超分立元件搭建的电路能极大简化设计并提升性能。当你按照以上步骤最终在屏幕上看到随着自己呼吸和心跳而规律跳动的清晰波形时所有的调试和折腾都是值得的。这个项目就像一座桥梁连接了抽象的电子学原理与鲜活的生命现象。它教会你的远不止如何连接几个运放和写几行代码更是一种系统性的工程思维从需求分析、方案设计、模块实现、集成调试到问题解决。你可以在此基础上继续探索比如加入SD卡存储、OLED本地显示、蓝牙传输到手机甚至尝试更复杂的心律失常算法检测。