
简介面向单片机健康监测和生物医学信号处理学习者的一份完整毕业设计PDF围绕STM32平台实现心冲击BCG信号的采集、处理与显示系统解决人体微弱生理信号难以拾取与解读的工程问题。资料从硬件到算法逐层展开基于eTouch压电薄膜设计了前置电荷放大、滤波、主放大、陷波、电平提升与稳压五部分采集电路可适应坐和躺两种状态下的BCG信号检测算法侧采用0.48Hz低通数字滤波实现心跳与呼吸波分离并通过快速傅里叶变换在频域中提取心率、呼吸率最终交由STM32单片机完成实时处理与LCD显示。整包共1个文件为23.39MB的PDF文档图文与代码逻辑完整章节结构清晰便于对照学习。已有663人学习下载对电子、嵌入式及生物医学工程方向学生和设计开发人员而言是兼顾理论推导与工程实现的实用参考资料。1. 基于单片机的心冲击信号采集难点在信噪比不在算力心冲击信号BCG是心脏射血时身体受到的机械反作用力不用贴电极在床垫、椅背、贴身织物里都能采到。它带宽只有 0.520Hz200Hz 采样就足够单片机算力完全不是瓶颈真正难的是信噪比——信号弱到毫伏甚至微伏级而呼吸漂移、体动干扰、工频耦合都比它大一个数量级。这解释了为什么照着论文抄一套电路拿到手的往往是一条“看得出起伏、数不出心跳”的曲线。采集节奏由谁保证、缓冲怎么搬运、滤波系数从哪来、峰值阈值怎么自适应任何一环掉链子整个链路就白搭。下面按传感器选型、采样参数、采集固件、滤波与 J 波检测、端到端验证五个步骤展开每步给具体参数和可直接改着用的代码。适合做医疗电子与无感监护的单片机工程师也适合正在赶单片机毕业设计的学生最终目标是把基于单片机的心冲击信号采集与处理链路完整搭起来。2. 心冲击信号的传感器选型与核心采样参数采集方案的分叉点在第一级传感器直接决定后面走模拟调理电路还是数字接口。常见做法是先定传感器再倒推放大器和采样参数而不是先把单片机最小系统焊好再想信号源。2.1 PVDF、MEMS加速度计与称重传感器怎么选PVDF 压电膜在研究和产品里出现最多贴在床垫或椅背下面。它输出电荷必须经过电荷放大器和 GΩ 级输入阻抗才能保住低频0.5Hz 以下的响应全靠电荷放大器的 RC 时间常数硬撑。这类传感器低频响应容易漂但 J 波形态锐利适合做波形分析。MEMS 加速度计是原型验证最快的方案。SPI/I2C 数字输出直接进单片机带直流响应安装角度带来的重力分量可以在数字域扣掉。缺点是必须贴在胸骨附近或与床板刚性耦合否则波形偏钝。桥式称重传感器用于整床式监护测的是重心随心跳的微小移动天然包含呼吸波和体动信息但需要仪表放大器和桥路激励硬件成本最高适合做产品而非首次验证。方案输出形态低频/直流响应前端工作量快速上手的难度PVDF 压电膜电荷需电荷放大差高中等MEMS 加速度计数字 I2C/SPI好低低桥式称重传感器差分电压好高高选型的同时把信号极性定下来。PVDF 装好后 J 波可能是负的软件里要预留极性和符号校准避免在硬件上反复改线浪费两天时间。2.2 前置放大器的增益和频带0.5Hz 高通与 40Hz 低通目标是把 J 波最大摆幅放大到 ADC 满量程的 60%80%。举例传感器输出 5mV12 位 ADC 参考电压 3.3V总增益取 0.8 乘以 3.3 再除以 0.005约 500 倍。工程上分两级第一级 50 倍做电荷转电压第二级 10 倍做电压放大每一级都要留出约 3dB 裕量否则翻身时瞬间就饱和。高通截止放 0.30.5Hz。0.3Hz 会保留较多呼吸波0.5Hz 对 J 波更安全。低通截止放 40HzBCG 有诊断意义的分量都在 20Hz 以下40Hz 低通在 50Hz 工频之前就开始衰减等于先把工频压掉三分之一以上同时给 200Hz 采样留出抗混叠余量。用一阶 RC 实现取 100nF 电容配 39kΩ 电阻焊完用示波器测一下通带别只信计算值。2.3 采样率定在 200Hz、ADC 取 12 位的计算依据心率上限按 180bpm 算基频只有 3Hz要看清 J 波形态至少得保留到 20Hz 的谐波。200Hz 采样给出 5ms 时间分辨率折算成心率误差在 ±3bpm 以内对绝大多数应用足够。不要迷信高采样率把采样率提到 1kHz 只会多采高频噪声和白白增加功耗。12 位分辨率对 BCG 够用。信号放大到 80% 满量程时一个 LSB 只占满量程的 0.024%而人体耦合和传感器本底噪声通常远大于这个值量化噪声早就被覆盖了。只有当曲线呈明显台阶状、且确认信号幅度长期不足满量程三分之一时才需要考虑外置 16 位或 24 位 ADC多数情况下放大信号比换 ADC 优先级更高。参数推荐值理由采样率200Hz覆盖 20Hz 带宽J 峰时间精度 5msADC 分辨率12 位放大到位后量化噪声低于本底低通截止40Hz压制 50Hz 工频留抗混叠余量高通截止0.30.5Hz去掉呼吸基线保留 J 波慢分量单帧点数512 点2.56s够算心率又适合 DMA 双缓冲3. 单片机采集代码定时器触发 ADC 加 DMA 双缓冲3.1 采样触发为什么必须由定时器而非软件延时承担如果在主循环里调用延时再读 ADC中断响应和任务切换会让采样间隔抖动几十到上百微秒。BCG 本身是慢信号抖动对幅值影响有限但对 J 峰定位是致命的——采样间隔抖动会折算成时间噪声最后算出来的心率序列忽快忽慢。定时器触发 ADC 的硬件路径把抖动压到亚微秒级。DMA 再把转换结果直接搬进内存每 5ms 才产生一次中断CPU 大部分时间留给滤波和峰值检测。200Hz 的转换速率不算重但一旦后面叠加了滤波和检测算法轮询式读取很快会变成瓶颈。3.2 STM32F103 最小采集代码TIM3_TRGO 触发 ADC1下面是寄存器级最小骨架按 STM32F103、72MHz 主频、PA1 模拟输入展开。代码把采样率定为 200Hz用 TIM3 更新事件映射到 TRGOADC1 由 TRGO 上升沿触发DMA1 通道 1 以循环模式搬运 512 点。#define BUF_LEN 512 // 512 点200Hz 下正好 2.56 秒 static uint16_t adc_buf[2][BUF_LEN / 2]; // 双缓冲各 256 点 static volatile uint8_t ready_half; // 1前半段就绪, 2后半段就绪 void bcg_capture_init(void) { // 1) TIM3 产生 200Hz 更新事件并映射到 TRGO RCC-APB1ENR | RCC_APB1ENR_TIM3EN; TIM3-PSC 7199; // 72MHz / 7200 10kHz TIM3-ARR 49; // 10kHz / 50 200Hz TIM3-CR2 | TIM_CR2_MMS_1; // 更新事件送给 TRGO // 2) ADC1 单通道 12 位由 TIM3_TRGO 上升沿触发 RCC-APB2ENR | RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN; GPIOA-CRL ~GPIO_CRL_CNF1_Msk; // PA1 设为模拟输入 ADC1-CR2 | (4U 17); // EXTSEL100: 选择 TIM3_TRGO ADC1-CR2 | ADC_CR2_EXTTRIG | ADC_CR2_DMA; ADC1-CR2 | ADC_CR2_ADON; // 给 ADC 上电 // 3) DMA1 通道1 循环模式在两块缓冲之间自动交替 RCC-AHBENR | RCC_AHBENR_DMA1EN; DMA1_Channel1-CPAR (uint32_t)ADC1-DR; DMA1_Channel1-CMAR (uint32_t)adc_buf; DMA1_Channel1-CNDTR BUF_LEN; DMA1_Channel1-CCR DMA_CCR_CIRC | DMA_CCR_MINC | DMA_CCR_MSIZE_1 | DMA_CCR_PSIZE_1; DMA1_Channel1-CCR | DMA_CCR_EN; } void DMA1_Channel1_IRQHandler(void) { if (DMA1-ISR DMA_ISR_HTIF1) { // 搬满了前半段 256 点 DMA1-IFCR DMA_IFCR_HTIF1; ready_half 1; } if (DMA1-ISR DMA_ISR_TCIF1) { // 搬满了后半段 256 点 DMA1-IFCR DMA_IFCR_TCIF1; ready_half 2; } }关键参数说明PSC 和 ARR 一起把 72MHz 分到 200Hz改任何一个都会改变采样率双缓冲各 256 点DMA 循环模式会自动回绕半传输和全传输中断自然交替12 位转换一次约 1µs远小于 5ms 的触发周期采样时间不是瓶颈。主循环里只做一件事发现 ready_half 非 0就取对应半段数据做滤波和峰值检测处理完清零。200Hz 下一段 256 点代表 1.28 秒滤波加峰值检测只要在 1.28 秒内跑完就不会丢数据。F103 的 20KB RAM 里双缓冲只占 1KB很宽裕。3.3 没有 DMA 的 51 单片机中断里读 ADC主循环里滤波如果平台是 STC 或经典 51没有 DMA 化采集通道常见做法是定时器中断里逐点读 ADC。注意三个要点一是 200Hz 定时必须用定时器模式不要在中断里做软件计数累加二是中断服务函数里只读 ADC、写缓冲滤波和 J 波检测全部放到主循环三是每次转换可以连续读三次取中间值因为这类 ADC 受电源和布局影响更大单次读数更容易出现尖刺。工程项51STC 系列路径STM32F1 路径采样触发定时器中断内读 ADCTIMx_TRGO 硬件触发数据搬运软件写数组DMA 循环模式时间抖动十微秒级可接受亚微秒级双缓冲手动切换索引HT/TC 中断自动切换浮点滤波慎用优先整数算法可直接软浮点不管是哪条路验证采样节奏都要放在第一步把 ADC 输入短接到一个已知频率的方波检查恢复出的波形周期是否精确等于 200Hz。这一步不过后面的滤波全部白做。4. 单片机端的心冲击信号处理滤波、J 波检测与心率估计4.1 三步预处理去基线漂移、去工频干扰、0.520Hz 带通基线漂移来自呼吸和体位变化幅度是心跳成分的几倍到几十倍。先做一阶基线估计base 0.987f * base 0.013f * x再用y x - base得到去漂移后的信号。这个一阶高通在 200Hz 下的等效截止约 0.4Hz计算量小在 51 上也可以改写成整数除法。第二步是 0.520Hz 带通把残余呼吸和 50Hz 工频一起挡掉。系数在电脑上用 scipy 算好再粘贴进代码不要在单片机上去现算滤波器系数from scipy.signal import butter, sos2tf # 200Hz 采样率0.5~20Hz 带通2 阶 Butterworth b, a sos2tf(butter(2, [0.5, 20.0], bandpass, fs200, outputsos)) print(b , , .join(f{v:.9f} for v in b)) print(a , , .join(f{v:.9f} for v in a))把打印出的 6 个数填进下面的二阶 IIR 结构typedef struct { float b0, b1, b2; // 分子系数 float a1, a2; // 分母系数a0 归一化为 1 float x1, x2, y1, y2; } biquad_t; float biquad_run(biquad_t *f, float x) { float y f-b0 * x f-b1 * f-x1 f-b2 * f-x2 - f-a1 * f-y1 - f-a2 * f-y2; // 直接 I 型 f-x2 f-x1; f-x1 x; f-y2 f-y1; f-y1 y; return y; }说明二阶带通输出正好一组 6 个系数a0 在 scipy 里已归一化成 1。a2 的值通常非常接近 1意味着极点靠近单位圆定点实现时容易抖动所以在 Cortex-M3 这类没有硬浮点的芯片上直接开软浮点就好。200Hz 采样率下叠加几级 biquad每秒也不过几千次乘加CPU 占用可以忽略。4.2 自适应阈值加不应期的 J 波峰值检测BCG 的 J 波是射血开始时刻的机械标志通常是整段波形里摆幅最大、上升最快的成分。阈值固定成一条水平线必定翻车呼吸幅度变化和被子滑动都会让整段幅值平移。在线做法是让阈值跟随最近峰值缓慢更新同时在检测到候选峰后插入 300ms 不应期避免 T 波残留或 J 波双峰被重复计数。int bcg_find_j_peaks(const float *x, int n, int *out_idx, int *out_n) { const int search_len 30; // 局部搜索窗 150ms 200Hz const int refractory 60; // 不应期 300ms 200Hz float thresh 0.5f; // 用首段数据最大值的 30% 初始化 int i 0; *out_n 0; while (i search_len n) { if (x[i] thresh) { // 超过阈值进入候选搜索 int j i; for (int m i; m i search_len; m) if (x[m] x[j]) j m; // 窗内取局部最大 out_idx[(*out_n)] j; thresh 0.6f * x[j] 0.4f * thresh; // 阈值随峰值更新 i j refractory; // 跳过不应期 } else { i; } } return *out_n; }参数说明search_len 取 30 个点就是 150ms用于在窗内找真正的 J 峰refractory 取 60 个点对应 300ms允许最大心率到 200 次/分而不漏检。阈值更新系数 0.6/0.4 的意思是新阈值以当前峰为主、旧阈值为辅保证呼吸波幅度缓慢变化时阈值跟得上。调用前先除以本段最大绝对值做一次归一化阈值就和体位无关了。若 J 波为负向先乘 -1 或取绝对值再送进检测函数。4.3 心率统计与信号质量门限J 峰序列出来后差分得到 J-J 间隔。单次间隔受呼吸和体动影响抖动很大输出心率一般取最近若干间隔的中位数而不是平均值中位数对单点误检不敏感。质量门限则用变异系数判断这一段波形是否可信。输出量计算方式门限瞬时心率60000 / 单次 J-J 间隔(ms)仅展示不参与统计输出心率最近 7 个间隔的中位数间隔 3332000ms 才进入序列质量指数CV σ/mean × 100%CV 10% 判定为可用段体动标志ADC 打到 0 或 4095 附近或幅值长期大于 0.9 倍满量程命中则整段丢弃并重置阈值质量门限比滤波更值得花时间调。BCG 极易被体位变化破坏与其把错误心率送进界面不如直接上报“信号质量差请调整姿态”这对无感监护场景反而更可靠。5. 端到端验证的三个手段串口抓包、心电对齐、脉冲响应核对5.1 串口抓包确认原始数据和滤波数据都对先给采集板加一路简单的串口导出。每帧由 2 字节帧头、512 点 16 位原始 ADC 值、1 字节校验组成void bcg_uart_send_frame(const uint16_t *block, int len) { uint8_t crc 0; uint8_t hdr[2] {0xAA, 0x55}; uart_send(hdr, 2); uart_send((uint8_t *)block, len * 2); // 16 位小端原始值 for (int i 0; i len; i) crc ^ block[i] 0xFF; uart_send(crc, 1); }512 点加上帧头只有约 1KB38400 波特率就能在 0.3 秒内传完远比 2.56 秒的一帧数据来得快不用担心阻塞采集。用串口助手存成二进制文件后在电脑上用 numpy 解析画图一眼就能看出滤波前后波形的差别在哪里。5.2 心电对齐用 R 波给 J 峰做参照同时间采集单导联心电把 R 波时间戳和 J 波时间戳做逐拍配对。正常受试者两者时间差大约在 100250ms 范围且逐拍方差很小。若方差明显偏大说明 J 峰定位被呼吸或体动噪声干扰回头调带通或阈值更新系数。这个验证不需要写复杂算法导出两列时间戳到 Excel 就能算差值分布。5.3 用单位脉冲响应核对滤波器系数最容易踩的坑是系数贴错符号位。单独构造一个测试块第一个点设为 2048其余全为 0分别送进电脑端 scipy 和单片机里的 biquad导出两组输出逐点相减。浮点实现偏差应小于 1e-4定点实现偏差不应超过 1 个 LSB。绝大多数对不上的情况都出在 a1、a2 进代码时丢了负号对照打印出的系数逐位检查就能定位。这一招把“程序能跑”和“算法真对”彻底分开整套系统只要过了这关剩下的就只是换传感器位置和微调阈值参数了。本文还有配套的精品资源点击获取