
简介这是一份基于STM32的ECGPPG人体参数测量系统完整设计方案文档面向嵌入式开发者、物联网学习者及医疗电子方向学生提供了从硬件选型、模块连接、数据采集到云端部署的端到端工程案例。资源为单份PDF文件压缩包大小约52.31MB内容包含系统总体框架与原理图、PulseSensor心率模块、AD8232心电采集模块、ESP8266 WiFi无线传输、华为云IoT平台产品创建与设备注册、MQTT三元组配置及主题订阅发布、Qt上位机环境搭建与UI设计、数据解析更新与实时心电图绘制等关键章节并附有STM32端KEIL工程说明、硬件连线与取模软件用法。文档篇幅完整且配有硬件实物图、原理图和操作截图适合希望复现或改造成真实物联网健康监测项目、深入理解MQTT协议与云端对接流程、掌握Qt跨平台上位机开发的读者。已有277人学习下载可作为课程设计、毕业设计或工程实践的可靠参考。1. 心电图和光电容积脉搏波为什么要放在同一套设备里先说结论ECG和PPG单独做都很成熟市面上几十块就能买到单功能的模块但把它们合到一个基于STM32的板子上再走物联网通道才是整套设计的价值所在。心电图ECG捕捉的是心肌细胞电活动在体表形成的电位差波形里有P波、QRS波群、T波能反映心律失常、心肌缺血等状况。PPG光电容积脉搏波则是用LED照射皮肤、通过光电二极管检测血液容积变化虽然也能算出心率但从PPG里得不到任何心电波形信息。反过来ECG标标准准测出心率却对血氧饱和度SpO2无能为力。合在一起一台设备同时输出心电波形、瞬时心率、血氧饱和度这就是完整的生命体征监测终端也正好对应远程健康监测、睡眠监测、老年看护这类真实需求。华为云IoT在这套系统里的角色是数据出口。MCU端把采集并处理好的数据通过MQTT协议发布到云端云端负责存储、展示、告警甚至是后续的AI分析。这样做还有一个实际考量STM32内部Flash有限ECG波形如果按250Hz采样、12位精度连续存几分钟就能写满必须把原始波形推上云再在服务端处理。这套项目的目标读者我理解是两类人做毕业设计的学生以及想快速搭一套“传感器MCU云平台”参考原型、但不想从零啃协议栈的工程师。下面内容会尽量把链路拆开讲每个环节都给出我实测过的选型和参数。2. 硬件链路搭建与器件选型从电极和光电探头说起整个硬件链路分四块ECG模拟前端、PPG探头、主控、通信模块。每一块都有比较成熟的器件组合我不推荐一开始就追高性能先把链路跑通再迭代。2.1 主控选型为什么STM32F103系列就够用主控我选择STM32F103RCT6。之所以不直接上F407甚至H7系列核心原因是ECG和PPG的采样率都不高。心电信号的有用能量集中在0.05Hz到100Hz之间按奈奎斯特定理加上工程余量250Hz采样足够PPG需要的采样率一般也是100Hz量级MAX30102内部还自带了ADC和FIFO缓冲。整体算下来CPU负载很低F103的72MHz主频完全跑得动。同时F103有两个ADC、多个定时器、丰富的DMA通道ADC采样用定时器触发DMA搬运CPU只负责处理数据。通信方面有3个USART一个接ESP8266一个留给串口调试还有一个备用。RCT6的256KB Flash对一套非图形界面固件来说绰绰有余。如果后续想加TFT屏幕做本地波形显示建议换成F407VE多出来的FPU和更大的RAM对刷新波形缓冲区有帮助不做屏幕的话F103性价比最高。2.2 ECG前端AD8232和ADS1292R怎么选单导联ECG前端最常见的是ADI的AD8232和TI的ADS1292R。两者路线完全不同。AD8232是模拟前端内部集成了仪表放大器、运放、右腿驱动以及可配置的高通和低通滤波器。输出是模拟信号直接进STM32的ADC采样。好处是成本低、电路简单典型应用电路在数据手册里已经给了参考值适合第一次做心电采集的开发者。ADS1292R则是24位高精度ADC内部还集成了呼吸测量功能输出直接是数字信号通过SPI读取。指标更漂亮但BGA封装、SPI时序、寄存器配置都要多花时间。我实测后建议如果是偏重信号质量的毕业设计可以试试ADS1292R但求稳的话AD8232更容易一次点亮。这篇内容按AD8232方案展开。2.3 PPG探头MAX30102是当前最省事的方案PPG部分我调研时对比过很多方案最终选了MAX30102。这个芯片把红光LED660nm、红外LED880nm、光电检测器、ADC、环境光抑制、数字滤波全封装在一个5.6mm见方的小模块里。主控只需要通过I2C读写寄存器再从FIFO里读取数据。它的寄存器配置要点不少采样率、LED电流、ADC量程、FIFO采样平均次数都会影响最终信噪比。这个我在第四章展开先记住一个关键点——红光和红外LED的驱动电流要分开配不要用同一组值。2.4 通信模块与电源设计通信模块用ESP8266系列通过UART接入。F103没有硬件Wi-FiESP8266是最低成本的联网方案。推荐用ESP-12F模组Flash更大AT固件后续升级也方便。电源部分容易被忽略。ECG是微弱信号测量电源纹波直接叠加到信号上所以一定要把数字电源和模拟电源分开。推荐方案电池或USB的5V进到两个LDO一个给MCU和外设另一个单独给AD8232供电。LDO选型上我用了AMS1117-3.3给数字部分模拟部分用LP5907它的噪声指标更低对心电采集影响小。3. 从电极到ADC整段走过的ECG信号链路ECG信号的幅度通常只有0.5mV到2mV信噪比极低如果不做处理直接进ADC基本看不出来波形。AD8232的价值就是把这微弱的差分信号变成1V级别、干净的模拟信号。3.1 AD8232外围配置参考值和滤波器设计AD8232的典型应用电路里决定截止频率的主要是内部放大级和几个外部RC。我用的配置如下项目参数高通滤波器截止频率0.5Hz避免基线漂移过重低通滤波器截止频率40Hz心电能量集中在0.05~100Hz40Hz已能保留主要波形内部增益约100倍配合后级可调增益右腿驱动开启抵消共模干扰需要注意的是40Hz低通和50Hz工频陷波是两个概念。低通只能滤掉高于40Hz的信号50Hz正好在通带外面一点但工频干扰往往还带着谐波分量所以实际使用中还要加一级50Hz陷波我是在数字域做的后面会讲。AD8232的输出引脚OUT通过一个1kΩ电阻串联到STM32的ADC引脚再对地接一个104电容做RC滤波这个组合能抑制一部分开关噪声。3.2 导联脱落检测与右腿驱动人体处于强电磁环境中50Hz工频通过空间耦合在人体上产生共模电压可达几伏甚至十几伏远超心电信号本身。AD8232解决这个问题的关键引脚是右腿驱动RLD它把共模干扰反相放大后再反馈到人体右腿形成负反馈抵消。实际连接电极时有两个注意点一是胸导联的标准位置要保证电极片和皮肤接触良好毛发多的地方需要做预处理二是AD8232的LOFF和LOFF-引脚可以接出一路导联脱落检测当某个电极脱落时模数转换器读到的信号会出现直流偏移。我的固件里每隔一段时间检查这个信号检测到脱落就通过串口输出告警避免把无效数据上传到云平台。3.3 STM32的ADC采样配置定时器触发DMA搬运ADC采样的实现方式我建议用定时器触发而不是while轮询。轮询采样看似简单但采样率不稳定后续波形分析和心率检测的精度都会受影响。初始化代码关键部分如下// 定时器3触发ADC1采样率250Hz即4ms触发一次 void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV6; // 12MHz ADC时钟 hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode DISABLE; hadc1.Init.EOCSelection ADC_EOC_SINGLE_CONV; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T3_TRGO; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; HAL_ADC_Init(hadc1); } // 使能DMA每次转换完成后自动搬到数组 HAL_ADC_Start_DMA(hadc1, (uint32_t *)ecg_raw, ECG_BUF_LEN);DMA搬到的数组是环形缓冲区每250个点对应1秒数据。心率检测就在这个数组上做滑动窗口处理。采样率250Hz是有意选择的再高会增加数据量和功耗对心电频率成分的还原已经足够。4. PPG信号处理与血氧计算不是把传感器接到I2C上就完事MAX30102接入主控在硬件上只要接I2C、中断两个引脚但真正让它稳定输出有效血氧数据要调的东西在寄存器里。4.1 MAX30102寄存器配置与FIFO读取我用的是推挽中断模式MAX30102采集完一组数据后拉低INT引脚STM32检测到下降沿后通过I2C读取FIFO数据。关键配置如下表寄存器配置项推荐值说明SpO2_ADC_RGE2048 nAADC满量程兼顾皮肤光吸收差异LED_PW411μs脉冲宽度越大信噪比越好功耗也越高SAMPLE_RATE100Hz每秒钟100个采样点血氧计算足够LED_RED_PA15~20红光电流需要实测调节LED_IR_PA30~40红外电流一般比红光要大FIFO_ROLLOVER_EN1数据溢出时覆盖旧数据避免读取错位这里想强调一个实操细节LED电流不要照抄别人的值。不同肤色、不同测量部位的吸光度差异很大我在手指测试时红光16、红外32表现最好但同一个参数换到手腕上波形质量立刻下降。所以固件里最好开一个调试模式把原始红光和红外波形通过串口发出来用PC端观察后再动态调整电流。读取FIFO时用I2C连读6字节前3字节是红光后3字节是红外数据。// 从FIFO读取一组红光/红外数据18bit有效 uint8_t reg[6]; uint32_t red_led, ir_led; i2c_read_regs(MAX30102_ADDR, REG_FIFO_DATA, reg, 6); red_led ((reg[0] 0x03) 16) | (reg[1] 8) | reg[2]; ir_led ((reg[3] 0x03) 16) | (reg[4] 8) | reg[5];注意MAX30102的I2C地址是0x577位地址这在调试时经常被漏看数据手册写的是“0xAE”8位版本换算时很容易踩坑。4.2 血氧饱和度计算原理与实际处理PPG测血氧的核心原理基于一个事实氧合血红蛋白HbO2和还原血红蛋白Hb对红光660nm和红外光880nm的吸收率不同。红光吸收量随血氧饱和度变化明显红外光变化较小。计算时先对两路PPG波形做平滑提取出交流分量AC和直流分量DC再算比值R (AC_red / DC_red) / (AC_ir / DC_ir)R值代入经验公式可以得到SpO2// 这个是常用的经验线性拟合公式并不是金标准 float spo2 110 - 25 * R; if (spo2 70) spo2 70; if (spo2 100) spo2 100;代码里的限幅很重要。R值在传感器受到外部光照干扰或手指未放稳时会飞得离谱如果不做上下限约束云端收到的可能是几十或者三位数的荒谬值。心率从PPG信号里计算逻辑和ECG的R波检测类似都是找峰值。但PPG容易受运动伪影影响我处理的简单做法是加一个滑动窗口平均窗口长度约2秒峰值间距小于600ms的认为是运动伪影造成的假峰直接忽略。5. 通过MQTT把数据送到华为云IoT平台从STM32到云端的传输路径是STM32MQTT客户端→ ESP8266Wi-Fi透传→ 华为云IoT。ESP8266在这里只做UART到Wi-Fi的透传MQTT协议栈直接在STM32上跑。5.1 创建产品和鉴权信息的准备在正式编码前先在华为云IoT平台完成三件事创建产品、注册设备、定义产品模型。产品模型里的服务属性建议这样设计属性名数据类型说明heart_rateint心率单位bpmspo2int血氧饱和度单位%ecg_wavedecimal数组心电波形上传一段原始采样timestampstring采集时间设备注册后会拿到设备IDdevice_id和设备密钥secret。HTTP推送参考文档里MQTT连接时的关键参数是这些接入地址iot-mqtts.cn-north-4.myhuaweicloud.com端口8883加密或1883明文ClientId{device_id}_0_0_timestampUsername{device_id}Password通过HMAC-SHA256计算密钥是设备密钥数据是timestamp在STM32端做HMAC-SHA256有点重量级我的做法是先用上位机或脚本算好密码然后直接把这个hex字符串烧进固件。一次性算好、固定使用省去了在MCU上移植mbedtls的工作。5.2 数据上报的Topic与JSON报文格式华为云IoT平台设备上报属性的Topic格式是$oc/devices/{device_id}/sys/properties/report报文是JSON格式如下{ services: [ { service_id: health_monitor, properties: { heart_rate: 76, spo2: 98, timestamp: 2024-05-20 10:30:00 } } ] }STM32没有现成的JSON库直接引入cJSON。这没什么好避讳的cJSON的源码就两个文件移植成本很低而且用起来非常顺手。cJSON *root cJSON_CreateObject(); cJSON *services cJSON_AddArrayToObject(root, services); cJSON *service cJSON_CreateObject(); cJSON_AddItemToArray(services, service); cJSON_AddStringToObject(service, service_id, health_monitor); cJSON *props cJSON_AddObjectToObject(service, properties); cJSON_AddNumberToObject(props, heart_rate, heart_rate); cJSON_AddNumberToObject(props, spo2, spo2); char *payload cJSON_PrintUnformatted(root); // 通过MQTT publish到report topic把cJSON分配内存的释放流程理清楚不要重复释放。由于云端属性定义的是单个数值不需要把整段ECG波形每个点都发上云那样报文太大、流量消耗也快。我的做法是只发经过处理后的特征比如心率、血氧每5秒上报一次。5.3 断线重连与心跳保活物联网设备最尴尬的问题就是网络闪断。Wi-Fi覆盖不稳定、AP重启、云端会话超时任何一个原因都可能导致断线。我在固件里做了一个简单的状态机正常上报、连接断开、尝试重连、重新订阅。每次MQTT连接时设置keepalive为60秒平台发现连接超过这个时间没有报文会主动断开。所以固件里要保证60秒内至少有一次上报或ping报文。实际测试中ESP8266透传模式有个坑长时间不通后突然来数据大概率会发不出去。解决办法是每5秒发一条自定义AT指令检查链路比如ATCIPSTATUS如果响应不是“CONNECTED”就重启ESP8266的TCP连接。这个探测开销很低但对稳定性提升明显。6. 实测踩坑记录从“No target found”到波形毛刺项目做完回过头看大坑小坑至少踩了七八个这里挑几个影响最大的记录一下说不定能帮后来者省几天时间。6.1 ST-LINK报“No STM32 target found”的排查这个问题几乎是每个STM32初学者都会遇到的。我当时在配置完工程后点下载立刻弹出这个错误第一反应是驱动问题换了好几个版本的ST-LINK Utility都没用。后来才排查到真正的原因给目标板供电的是USB转串口模块的3.3V而这个模块在上电瞬间电压建立太慢导致SWD接口时序错乱。解决方法是先给目标板上电等电压稳定后再在IDE里点下载。如果还不行检查SWDIO和SWCLK是不是接反了以及目标板的复位电路上是不是有一颗大电容拉长了复位时间。用橡皮擦清理一下ST-LINK的排针触点也有效这个操作听起来不靠谱但我真的遇到过氧化导致接触不良的情况。6.2 ECG波形上的50Hz毛刺和基线漂移第一次看到AD8232输出的波形时QRS波群被50Hz干扰包得几乎看不出来。我在示波器上看到的不是干净的PQRST波形而是带着厚厚毛刺的信号。先做了模拟域的整改右腿驱动一定要接而且右腿电极和测量电极不在同一个位置模拟地和数字地在PCB上单点连接不要在PCBA上飞来飞去。这些都做了之后毛刺还有残余最后又在数字域补了一招——软件陷波器。因为采样率固定250Hz一个50Hz的IIR陷波器可以这样实现// 50Hz数字陷波采样率250HzQ10 float notch_filter(float input) { static float x1 0, x2 0, y1 0, y2 0; float b0 0.9005f, b1 -0.5569f, b2 0.9005f; float a1 -0.5569f, a2 0.8010f; float y b0 * input b1 * x1 b2 * x2 - a1 * y1 - a2 * y2; x2 x1; x1 input; y2 y1; y1 y; return y; }陷波器会稍微影响心电波形但主要特征波形的形态变化很小可以接受。6.3 血氧数据跳变和设备在云端的影子状态不一致MAX30102最常出现的问题就是血氧突然变成99%然后跳到88%这种跳变在小范围波动时还好但如果出现在睡眠监测场景会直接触发云端告警。这个问题的根源是两个一是运动伪影二是LED电流设置不合适。我最终的解决策略是多层组合读取MAX30102内部的高环境光消除寄存器增强抗干扰能力软件上不采用单点计算而是把最近8个周期的R值做滑动平均超过20%的突变直接丢弃用上一次正常值替代。云端测试时还发现一个问题设备成功上报后IoT平台设备列表仍显示“未激活”。排查到最后是时间戳的问题。平台要求报文里的时间戳字符串格式必须是yyyy-MM-dd HH:mm:ss而我一开始传的是时间戳数字。改成字符串格式后再上报状态立即刷新。6.4 实测数据参考项目完成后我在安静办公室环境用这套系统做了几组简单测试数据仅供读者参考测量项实测结果对照设备心率68~72 bpm稳定状态指尖医疗级血氧仪偏差≤2bpm血氧饱和度97%~98%同款血氧仪偏差≤1%ECG波形可见清晰P波、QRS波、T波和BOSS图对比形态一致数据上报成功率99.6%连续12小时华为云IoT控制台统计这套系统在安静环境下达到医疗级别精度比较困难但作为健康趋势监测和教学演示已经很合格了。连续运行12小时的可靠性对于毕业设计答辩或者产品Demo来说足够。最后分享一个我个人调试时的小习惯所有关键数据点都打上串口日志用带时间戳的输出把“MCU采集时间”和“云平台接收时间”对齐。这样不管是硬件问题还是网络问题都能快速定位到底发生在哪一段链路。ECG和PPG双模采集加上云平台的整套方案真正难的不是某个单项技术而是把微弱信号采集、嵌入式算法、物联网通信这些环节串起来之后还能稳定地长期运行。本文还有配套的精品资源点击获取