ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于F103C8T6与AD8232的完整心电采集系统:硬件、固件到上位机实战

基于F103C8T6与AD8232的完整心电采集系统:硬件、固件到上位机实战 从一颗F103C8T6开始的完整心电采集方案PCB、固件到C#上位机的全链路实现先说结论想用STM32F103C8T6做出一套能看见真实心电波形、能算心率、能连续存储回放的全套系统完全可行。TI杯那套开源资料我已经重新梳理过一遍这篇文章会把我个人在复现和改造过程中踩过的坑、验证过的电路、改过三版才稳定的代码逻辑全部讲透。这周刚好有个学生问我为什么照着网上开源资料焊出来的心电板子示波器上一片噪声连基线都是飘的我让他把PCB拍照发过来扫了一眼就发现问题了——模拟前端的地和数字地混在一起AD8232的输出引脚直接拉了一根十几厘米的飞线到STM32的ADC引脚。这种情况神仙来了也救不了。所以这篇文章不止是讲怎么做更想把为什么这么做讲清楚。无论你是要准备电赛、做毕设还是单纯想给自己做个便携心电仪建议按这个顺序把链路吃透模拟前端选型 → PCB设计 → STM32采样与滤波 → C#上位机解析与显示。四个环节环环相扣任何一个掉链子出来的波形都没法看。1. 整体方案架构为什么是F103C8T6又为什么需要独立模拟前端1.1 选型时的核心权衡STM32F103C8T6这颗芯片在2025年的今天依然活跃在各类项目里不是没有道理的。它内置3个12位ADC最快采样率能到1MHzRAM有20KBFlash有64KB售价在国产替代方案里已经压到几块钱。对心电采集这个场景来说ADC分辨率12位、采样率500Hz就够用F103C8T6的性能完全在舒适区里。有人可能会问ECG信号那么微弱直接用STM32内部ADC采样行不行答案是可以但前提是前端必须有一颗专用的模拟前端芯片或者运放电路来完成信号调理。因为心电信号的特点是幅度只有0.5mV~4mV典型的R波峰值在1mV左右频率范围集中在0.05Hz~100HzQRS波群的主要能量集中在10Hz左右叠加了50Hz工频干扰、肌电干扰30Hz~300Hz、电极极化电压最高可达±300mV。如果不做前端放大和滤波直接把电极信号送进ADC那12位的分辨率根本不够用——整个信号都被淹没在直流偏置和噪声里。1.2 我最终采用的双芯片方案信号链路是电极 → 模拟前端仪表放大器右腿驱动 → 主控ADC → 串口 → 上位机这里有必要专门说下模拟前端的选型。市面上常见的有两颗芯片ADI的AD8232和TI的ADS1292R。我的建议是对比维度AD8232ADS1292R输出形式模拟电压输出24位数字SPI输出硬件复杂度低外围元件较少中需要SPI时序和寄存器配置功耗典型170μA典型335μA导联数单导联双导联/多导联适用场景入门、快速出波形需要高精度、多通道的进阶项目成本约10~15元约25~40元如果目标是TI杯这类竞赛想快速跑通并且让评审看到清晰的波形AD8232是最稳的选择。它内部集成了仪表放大器、二级放大、高通滤波和低通滤波只需要极少的外围器件就能输出一个可直接进ADC的模拟信号。唯一的注意点是它内部的高通滤波器截止频率固定用默认配置时大约0.5Hz信号需要几百毫秒到几秒才能稳定这是正常现象不是故障。我手头这块板子的具体方案是AD8232输出引脚经一个RC低通滤波器100Ω 1μF截止频率约1.6kHz后接入STM32的PA4引脚ADC由TIM2的TRGO事件触发采样率配置为500Hz。这个采样率对心电来说不但够用而且为后续软件滤波留了足够的频域空间。2. 硬件设计的关键细节从原理图到PCB布局的实战经验2.1 原理图设计中的几个容易忽视的点原理图部分很多开源资料里都直接给了AD8232的参考电路照着画一般不会有大问题。但有几个细节如果不注意后面调试时会非常痛苦。第一右腿驱动RLD引脚必须接。心电采集最怕的是共模干扰——人体相对于大地相当于一个天线50Hz的工频信号会以共模形式进入两个输入电极。AD8232的右腿驱动引脚可以输出一个反相的共模信号到人体右腿形成一个前馈抵消回路能把共模干扰抑制掉40dB以上。不接这个引脚也能出波形但波形会带着明显的50Hz毛刺后期算法再厉害也难完全干净。第二电源去耦要对AD8232单独对待。F103C8T6的供电和模拟前端最好采用两个LDO分别供电模拟部分用AMS1117-3.3再加一级LC滤波10μH电感10μF电容数字部分直接LDO输出就行。如果共用一路电源STM32的IO翻转噪声会顺着电源线耦合进模拟前端表现出来就是波形上有周期性的小毛刺。第三电极输入端的保护电阻和滤波电容一定不能省。两个输入引脚各串一个100kΩ的电阻对地并联一个100pF的电容。这两个RC组合既能构成EMI滤波器又能在电极意外脱落或人体静电放电时保护芯片输入端。提示AD8232的输出引脚之前串联的电阻值建议选100Ω不要选太大。选大了会抬高源阻抗影响后级ADC的采样保持电容充电时间可能导致采样值偏差。2.2 PCB布局的黄金法则把模拟区域隔离出来PCB设计这块是整篇内容里水分最少、含金量最高的部分。我见过太多人把原理图画得漂漂亮亮一打样回来就是一片噪底。核心原则只有一句话所有模拟元件AD8232及其外围必须集中在一片区域数字走线不允许穿越这片区域模拟地和数字地在电源输入端单点连接。具体布局时我按照以下顺序操作在PCB上明确划分模拟区左侧和数字区右侧中间用一条实体地线隔离带分开AD8232放在模拟区的正中心两个电极输入插座我用的标准3.5mm音频插座做导联接口放在板边走线尽量短右腿驱动信号走线包裹在顶层和底层的GND敷铜之间这是最容易被忽略的细节——如果RLD走线旁边平行着其他数字信号线共模抑制效果会大打折扣STM32最小系统放在数字区晶振尽量靠近芯片且晶振底下不要走模拟信号线整板底层大面积敷铜接地顶层模拟区周围用一圈接地过孔围起来形成一个法拉第笼效果。这块板子我实际打样了三次。第一版问题出在AD8232输出走线与SPI通信线预留的调试口平行走了3cm波形上直接叠加了周期性尖峰。第二版把走线彻底分开、单独加屏蔽地线后底噪从±20mV降到了±3mV左右。所以说布局布线不是玄学全是实打实的电磁兼容问题。2.3 电源与电池供电的考量用USB供电时电脑开关电源的噪声会通过USB地线传导进来表现为波形上有宽频带噪底。测试时可以用充电宝或锂电池供电波形会瞬间干净一个量级。考虑到便携场景这套系统建议用一节3.7V锂电池加低 dropout LDO供电。实测下来整机工作电流约40mA包含STM32全速运行、LED指示、AD8232、串口芯片一块500mAh的电池可以连续工作10小时以上。3. STM32固件开发CubeMX配置、中值滤波与心率计算3.1 用CubeMX生成工程时需要注意的配置项现在用STM32CubeMX配置工程已经是主流做法F103C8T6的库支持非常完善。关键配置项如下每一项都有我标注的原因RCCHSE晶振8MHz这是经典配置F103最高72MHz主频8MHz经过PLL×9倍频即可。ADC1开启连续转换模式分辨率12位采样时间拉到最大239.5周期。心电是低速信号不需要追求采样速度反而要保证采样电容充分充电。触发源选TIM2 Trigger Out事件。用定时器触发ADC的好处是采样间隔可以由硬件精确控制不占用CPU。我把TIM2的时钟配置为72MHz/(72×2000)500Hz即每2ms一次触发。DMAADC1的DMA设置为Circular模式循环模式数据宽度Half Word方向Peripheral to Memory。这样ADC连续采样的数据会通过DMA自动搬运到内存数组里CPU完全不用干预。USART1波特率1152008N1。串口发送采用DMA方式避免在发送过程中被打断。PA4配置为ADC1_IN4模拟输入注意PA4在F103上是ADC1的第4通道。这里有个很多教程不会提的坑DMA缓冲区的数组大小必须设置为2的幂比如256或512。原因是DMA循环模式下当传输计数用完之后需要重新装载如果缓冲区不是2的幂会出现数据错位导致后续滤波算法拿到的是一个错乱的时间序列。我一开始用的233波形出来以后负责心率检测的阈值完全乱掉换成256之后立刻正常。3.2 软件滤波中值滤波、去基线漂移与工频陷波ADC原始数据进来之后直接拿去画波形是不行的。实际采到的信号里包含几个成分心电信号本身的AC分量、呼吸引起的基线漂移频率通常0.1Hz~0.5Hz、50Hz工频干扰、电极运动伪迹。我的处理链路是三级滤波第一步滑动窗口中值滤波抑制运动伪迹和尖峰噪声实现方法维护一个长度为5的滑动窗口每次新的采样点进来自动丢弃最老的点取中间值作为当前输出。中值滤波对脉冲噪声比如电极接触不良产生的尖峰有奇效而且不会像均值滤波那样把QRS波群的尖锐边缘吃掉。在代码里我开了一个长度为5的数组每来一个新数据就做一次插入排序取中间值开销极小。第二步高通滤波去基线漂移这里用一阶IIR高通滤波器截至频率设为0.5Hz。差分方程如下我直接写在代码注释里方便大家抄// y[n] α * (y[n-1] x[n] - x[n-1]) // α 1 / (1 2π * fc / fs) // fc 0.5Hz, fs 500Hz, 则 α ≈ 0.9937 float alpha 0.9937f; float ecg_hp alpha * (ecg_hp_prev ecg_raw - ecg_raw_prev); ecg_raw_prev ecg_raw; ecg_hp_prev ecg_hp;这个滤波器的作用是把0.5Hz以下的缓慢漂移全部砍掉。呼吸动作、身体轻微移动造成的基线漂移主要集中在这一频段。第三步50Hz IIR陷波器去工频干扰前端的右腿驱动已经滤掉了大部分共模工频干扰但导线上的差模干扰依然存在所以软件里再加一道保险。采用二阶IIR陷波器中心频率50HzQ值设为1.5。Q值不能太高太高会伤到QRS波群中50Hz附近的频率成分导致波形变形。// 采样率500Hz下50Hz陷波器系数由matlab fdatool生成后手工填入 float b[3] {0.9647f, -0.9999f, 0.9647f}; float a[3] {1.0000f, -0.9999f, 0.9294f};三级滤波全部跑完后波形已经具备以下特征基线水平稳定在0附近、R波尖锐可辨、T波圆滑可见。我在实验里对比过滤波前后的R波幅值滤波会造成约5%的幅度衰减这是IIR滤波器的正常群延迟代价不影响心率算法。3.3 R波检测与心率计算动态阈值法实现心率检测最怕的是阈值固定——姿势变化、电极接触松动都可能导致信号幅值大幅波动。我采用的方法是动态滑动阈值维护两个滑动窗口一个是信号峰值历史的最大值用于计算阈值一个是RR间隔的历史用于计算瞬时心率和平均心率。R波检测逻辑如下对滤波后的信号计算一阶差分R波下降沿是强烈负斜率设定阈值 0.6 × 最近8个R波峰值的滑动平均值信号从阈值以下上升穿过阈值时标记为一个可能的R波候选为避免T波被误检为R波加入300ms的空白期绝对不应期在这个时段内不进行新的检测。RR间隔 相邻两个R波的时间差。瞬时心率 60000 / RR间隔单位ms平均心率 最近8个RR间隔的平均值转化为每分钟心率。这套逻辑在正常窦性心律下非常稳定误差基本能控制在正负2次/分钟以内。要说局限就是它对心律失常比如室性早搏的宽大畸形QRS波不敏感但作为学习项目和小型可穿戴设备足够用了。4. C#上位机开发从串口数据解析到波形绘制4.1 通信协议设计帧格式必须一开始就定好固件和上位机之间的通信协议第一步就要把帧格式写死不要想着后面再改。我用的协议如下简洁可靠字节偏移内容说明0帧头 0xAA 0x55两个固定字节用于同步2数据长度固定为2即后面两个字节为有效数据3ECG数据高字节12位ADC值右移4位后的高8位4ECG数据低字节12位ADC值低8位5心率值0~255单位bpm6校验和从帧头开始到心率值所有字节的异或值帧长固定为7字节每2ms发送一帧一秒钟500帧每帧的数据净荷只有4字节ECG高、低心率校验串口负载仅为 7×500×10bit 35kbps115200波特率下占空比小于三分之一。余量很足如果后续要加体温、血氧数据也完全加得下。4.2 串口接收与解析VS2019、VS2015兼容性处理C#上位机这块我可以明确告诉你VS2019开发的工程用VS2015打开是可以做到的但有条件。关键在于你创建项目时选择.NET Framework版本。如果你在VS2019里选的是.NET Framework 4.6.2或更低版本VS2015默认最高支持4.6.2那么用VS2015打开时基本没有障碍。但如果你选了.NET Core或.NET 5/6/7/8那VS2015完全打不开因为它不支持新式SDK风格项目。考虑到很多评委老师和学生电脑还停留在VS2015我的项目是直接在VS2015里新建的WinForms工程目标框架设为.NET Framework 4.6.2这样在VS2019里也能打开走的是向下兼容的路子。串口解析的逻辑建议用后台线程队列的方式不要直接在串口DataReceived事件里做UI操作。这会引发跨线程访问控件的异常虽然可以Invoke解决但频繁的UI刷新会卡死界面。我的实现方案是private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { byte[] buffer new byte[1024]; int n serialPort.Read(buffer, 0, buffer.Length); lock (syncLock) { for (int i 0; i n; i) { receiveQueue.Enqueue(buffer[i]); } ParseQueue(); // 在这里逐字节解析帧 } }解析状态机是常规的逐字节状态跃迁等待AA → 等待55 → 读取数据长度 → 读取数据载荷 → 读取校验 → 校验通过后把ECG数据推入波形缓存、心率和RR值用于UI显示。如果校验失败状态机回到初始状态继续找帧头这样即使偶尔串口丢数据也能很快恢复同步。4.3 实时波形绘制双缓冲与增量绘制波形绘制是上位机的门面环节做得好看不好看直接影响答辩观感。这里有两个性能优化手段是必须掌握的。双缓冲绘制把波形画在一个Bitmap上绘制完成后一次性显示到PictureBox上。如果不做双缓冲波形刷新时会出现明显的闪烁。WinForms里可以设置控件属性DoubleBuffered true但更推荐的做法是自己创建Bitmap再Graphics.DrawImage。增量绘制每次定时器触发我用的是30Hz刷新率只画新到达的数据点不重画整条曲线。具体思路是维护一个List每次定时器把新数据追加进去只从上一帧画到当前帧。大部分教程里的做法是每帧把整条线画一遍性能低且会出现滞后感。private void timerRefresh_Tick(object sender, EventArgs e) { using (var g Graphics.FromImage(bmp)) { g.Clear(Color.FromArgb(30, 30, 30)); // 画网格线 DrawGrid(g); // 把波形数据缩放到画布高度取最近3000个点 float scaleY paintHeight / 1000f; // 假设信号范围±500mV Pen penWave new Pen(Color.LimeGreen, 1.5f); for (int i lastPointIndex; i pointList.Count - 1; i) { g.DrawLine(penWave, (i - startIndex) * scaleX, paintHeight / 2 - pointList[i] * scaleY, (i 1 - startIndex) * scaleX, paintHeight / 2 - pointList[i 1] * scaleY); } lastPointIndex pointList.Count - 2; pictureBox1.Image bmp; } }另外画心率数字时用大号蓝色字体可以在黑底绿波的界面上非常醒目。很多开源项目不太注重这个细节但实际答辩时一个随时跳动的心率数值带来的观感比一张漂亮的波形图更能说明系统的可靠性。4.4 数据存储与回放功能需求里如果有回放要求TI杯这类竞赛经常有上位机就需要把原始数据落盘。我用最简单的CSV格式每行记录一个时间戳和一个ECG原始值。回放时开一个Timer按固定时间间隔读取CSV里对应行数的数据喂给显示区就能复现当时的实时绘制效果。这个功能看着不起眼但在调试算法的时候特别有用。我在调中值滤波参数时就是靠回放数据反复试验不用让被测者一直坐在那里重复采集。5. 系统联调噪声排查、地环路处理与数据验证方法5.1 一套标准化的调试验证流程每次改完硬件或固件我都会按这个顺序检查能极大缩短定位时间短路测试用1kΩ电阻短接两个电极输入此时正确的波形应该是近似一条平线。如果波形有波动问题一定在前端或电源不是软件滤波的事。自检信号测试AD8232有一个导联脱落检测输出LO和LO-可以验证电极接触是否正常。单手触碰测试单手指轻轻触碰左/右电极输入观察波形应该有一个大幅度的50Hz抖动。这个测试用来初步判断共模抑制是否生效——正常情况下抖动幅度很小如果幅度过大说明右腿驱动没接好或者供电不干净。正常人体测试双手分别按住左/右电极保持湿润保持静止此刻波形应该能稳定显示P波、QRS波群和T波。呼吸时会看到基线平滑漂移这属于正常现象。长时稳定性测试记录10分钟数据观察心率数值漂移范围。正常情况下静息心率的波动应该在±3bpm以内。5.2 我最难忘的一次排查神秘周期干扰做第二版PCB测试时波形上总是出现频率大约2Hz的周期性干扰幅度有0.5mV不属于心电信号。我检查了供电干净、检查了电极换全新、检查了AD8232配置没问题一度以为是芯片坏了。最后发现问题出在F103的PB2引脚BOOT1上。我在调试时给这个引脚接了LED指示灯而它的翻转频率恰好约2Hz。LED电流走的地回路刚好穿过模拟区造成地电位微小波动。把LED的限流电阻和走线改到板边单独走线后干扰立刻消失。这个坑让我彻底明白了任何数字状态变化IO翻转、PWM、LED闪烁、SPI通信都会通过地电流干扰模拟前端唯一的解决办法是物理隔离和分区布线。这也解释了为什么成熟的心电采集方案会使用独立的ADC和模拟前端芯片把模拟和数字彻底分开。5.3 常见问题速查表现象可能原因排查方向波形完全平线电极接触不良更换电极贴片检查导联线波形平线且有心率值ADC通道配置错误检查CubeMX引脚与代码是否一致波形有50Hz正弦叠加共模抑制失效检查右腿驱动接线检查电源滤波波形低频大幅漂移基线漂移没有滤除检查高通滤波器参数检查电极极化电压波形有随机尖峰数字串扰或静电检查布局分区增加输入RC滤波心率数值跳动大R波检测阈值不合适调整阈值系数和空白期时长串口上位机收不到数据波特率不匹配或帧协议不一致检查串口配置确认帧头字节6. TI杯参赛与资料复现的工程化经验最后这部分是给准备拿这个项目去参赛的同学写的。TI杯评审的关注点跟平时实验室用完全是两回事评审更看重系统级稳定性和指标的实测结果而不是用了多高级的芯片。我当年答辩的时候评委问的第一个问题就是你的噪声指标是多少第二个问题是如果有人心率失常你的系统能报警吗。所以建议在准备材料时把这三件事做扎实量化指标把基线噪声实测±3mV以下、心率误差静息状态下±2bpm、系统功耗约40mA实测记录下来做成表格放进报告。光写稳定准确没人信有数据才有说服力。故障注入测试演示拔掉一个电极时系统能检测到导联脱落并报警用AD8232的LO-引脚实现手拍打桌面模拟运动伪迹观察系统能恢复正常而不会死机。这两个现场演示在答辩时非常加分。界面友好度上位机的界面不要只画波形建议把心率数值、导联连接状态正常/脱落、数据存储状态、时间轴都显示出来。细节决定评审印象分。至于TI杯开源的资料包我在复现时发现它给的原理图和C源码是能直接用的但PCB布局文件年代较早模拟地平面处理得不够干净。如果你时间充裕强烈建议按本文第2章的布局原则重新布线打样一块波形质量会有质的提升。如果时间紧张直接用原版PCB数据打样再搭配本文的软件滤波链路也能得到可接受的结果。还有一个很实际的经验给板子留一个SWD调试接口4线SWDIO、SWCLK、GND、3.3V不要只靠串口下载程序。调试滤波参数和R波检测逻辑时用J-Link或DAP-Link在线调试能省一半时间。我用的是顺手做的DAP-Link下载器F103C8T6自带USB刷个DAPLink固件就能当下载器用而且C8T6的USB引脚PA11/PA12刚好空闲不占额外资源。另外F103C8T6作为老牌芯片国产替代方案已经很成熟。如果采购困难或者想控制成本完全可以选用同封装的国产兼容型号引脚和寄存器地址完全一致Keil下直接选对应型号编译即可。不过要留意国产芯片的ADC温漂参数普遍不如ST原厂长时间连续采集时可能会发现基线有缓慢的偏移这时上位机端再加一档数字高通滤波或者定期校准就能解决。这套链路全部打通之后下一步可以玩的方向很多把FreeRTOS移植进来做多任务调度波形采集、心率计算、数据显示各占一个任务、用蓝牙模块替换串口线实现无线传输、用SPI接口的Flash存储离线数据、甚至给系统加一个触摸屏做成手持版本。基础架构只要铺好了往上叠加功能都是水到渠成的事。
返回列表