基于STM32的电子血压计程序源码与系统设计
简介:本项目基于STM32微控制器设计实现一款无创性电子血压计,采用示波法测量原理,结合ADC采集、信号处理、控制算法、用户界面和通信协议等模块,完整展示了嵌入式系统在医疗设备中的应用。项目源码包含硬件初始化配置、驱动开发、数据处理流程及通信功能实现,适用于学习和开发基于STM32的医疗健康类电子产品。 
1. STM32微控制器架构与开发概述
STM32系列微控制器基于ARM Cortex-M内核,采用高性能、低成本、低功耗的设计理念,广泛应用于工业控制、消费电子及医疗设备等领域。其丰富的外设接口(如ADC、定时器、SPI/I2C)和强大的实时处理能力,使其成为嵌入式血压计开发的理想选择。在医疗应用场景中,STM32不仅能够实现高精度的数据采集与处理,还支持实时控制与用户交互功能。本章将从STM32的系统架构入手,逐步分析其核心组件,并结合血压计的设计需求,探讨其在该领域的技术优势与开发要点。
2. 血压计测量原理(示波法)
示波法作为当前主流的无创血压测量技术,因其非侵入性、操作便捷、测量精度高等优点,被广泛应用于家用与临床医疗设备中。该方法基于袖带加压与放压过程中产生的脉搏振荡波,通过检测压力传感器输出的模拟信号,结合信号处理与算法分析,最终实现收缩压(SBP)与舒张压(DBP)的判定。本章将从生理基础、信号特征、系统流程三个维度,深入剖析示波法的核心原理与实现机制。
2.1 示波法测量的基本原理
2.1.1 血压测量的生理基础
血压是血液在血管中流动时对血管壁产生的侧压力,通常以毫米汞柱(mmHg)为单位表示。人体血压分为两个关键指标:收缩压(Systolic Blood Pressure, SBP)和舒张压(Diastolic Blood Pressure, DBP)。前者代表心脏收缩时动脉内的最大压力,后者则为心脏舒张时的最低压力。
在示波法中,测量血压的关键在于袖带对动脉的压迫与释放。当袖带压力大于收缩压时,动脉完全闭合,无脉搏波动;当袖带压力介于SBP与DBP之间时,动脉部分开放,血流产生脉动,引起袖带内的压力波动;当袖带压力低于DBP时,动脉完全开放,脉搏波动减弱。
2.1.2 加压与放压过程中的压力变化分析
示波法的测量过程通常分为两个阶段:加压阶段和放压阶段。
- 加压阶段 :袖带快速充气至高于预期收缩压的值(例如180 mmHg),以确保动脉完全闭合。
- 放压阶段 :袖带缓慢放气,压力逐渐降低。在此过程中,脉搏振荡波的幅度随袖带压力变化而变化。
压力传感器持续采集袖带内的压力值,并通过ADC(模数转换器)转换为数字信号。在放压过程中,振荡波的幅度呈现出先增大后减小的趋势。最大振荡幅度对应的压力值被认为是平均动脉压(MAP),而SBP与DBP则基于振荡波幅度变化率进行估算。
关键公式 :
- 收缩压:振荡波幅度达到最大值的约50%时对应的压力
- 舒张压:振荡波幅度下降至最大值的约70%~80%时对应的压力
2.2 示波法信号特征与处理流程
2.2.1 脉搏波的检测与识别
在放压过程中,脉搏引起的袖带压力波动幅度非常微弱,通常在1~5 mmHg范围内。为了准确提取这些信号,需进行以下处理步骤:
- 信号采集 :通过高精度压力传感器(如MPX5050)采集袖带压力信号。
- 信号调理 :使用前置放大器与低通滤波器消除高频噪声,保留0.1~20 Hz的脉搏频率范围。
- 数字滤波 :在STM32平台上,使用IIR或FIR滤波器对采样信号进行平滑处理。
- 峰值检测 :通过滑动窗口算法识别脉搏波的峰值,提取振荡幅度变化趋势。
以下是一个基于STM32的IIR低通滤波器实现代码示例:
// IIR低通滤波器系数(截止频率为20Hz,采样率为100Hz)
float iir_coeff[5] = {0.0794f, 0.1588f, 0.0794f, -1.0235f, 0.3411f};
float iir_state[2] = {0};
float apply_iir_filter(float input) {
float output = iir_coeff[0] * input + iir_coeff[1] * iir_state[0] + iir_coeff[2] * iir_state[1];
output -= iir_coeff[3] * iir_state[0] + iir_coeff[4] * iir_state[1];
// 更新状态
iir_state[1] = iir_state[0];
iir_state[0] = input;
return output;
}
代码逻辑分析:
- 使用预设的IIR滤波器系数对输入信号进行滤波处理;
- iir_coeff 数组存储了滤波器的分子与分母系数;
- iir_state 保存前两次的输入值,用于递推计算;
- 输出结果为经过滤波后的脉搏波信号,去除高频干扰。
2.2.2 收缩压与舒张压的判定逻辑
在获取振荡波的幅度序列后,需要通过算法判定SBP与DBP。常见的判定方法如下:
- 最大振幅法(Maximum Amplitude Algorithm, MAA) :将振荡波的最大值对应的压力视为平均动脉压(MAP),再根据经验比例计算SBP与DBP。
- 比例法(Oscillometric Ratio Method) :设定SBP与DBP对应振荡波幅度的比例阈值,如:
- SBP = 0.5 × MaxAmplitude
- DBP = 0.7 × MaxAmplitude
以下是一个基于比例法的血压判定函数示例:
// 假设amplitude数组中存储了振荡波的幅度值,pressure数组存储对应的压力值
float find_sbp(float *amplitude, float *pressure, int length) {
int max_index = 0;
float max_amp = amplitude[0];
for (int i = 1; i < length; i++) {
if (amplitude[i] > max_amp) {
max_amp = amplitude[i];
max_index = i;
}
}
float target_amp = 0.5 * max_amp;
for (int i = 0; i < max_index; i++) {
if (amplitude[i] >= target_amp && amplitude[i + 1] < target_amp) {
return pressure[i]; // 返回SBP
}
}
return pressure[0]; // 默认返回最小值
}
参数说明:
- amplitude :脉搏波振荡幅度数组;
- pressure :对应的压力值数组;
- length :数据长度;
- 函数返回SBP对应的压力值。
逻辑分析:
- 首先找到振荡波的最大值及其索引;
- 然后在最大值前搜索振荡波首次达到50%最大值的位置;
- 返回该位置对应的压力值作为SBP估计值。
2.3 血压计系统的整体工作流程
2.3.1 系统模块划分与协同机制
一个完整的示波法血压计系统通常由以下几个模块组成:
| 模块名称 | 功能描述 |
|---|---|
| 气泵与放气阀控制模块 | 实现袖带的加压与放压控制 |
| 压力传感器模块 | 采集袖带内的实时压力信号 |
| ADC采集模块 | 将模拟信号转换为数字信号 |
| 信号处理模块 | 滤波、峰值检测、振荡波分析 |
| 控制与判定模块 | 判断SBP与DBP |
| 显示与交互模块 | 显示测量结果、用户提示、历史数据等 |
这些模块通过STM32主控芯片进行协调,形成一个闭环控制系统。例如,气泵控制模块由定时器PWM输出控制电机转速,ADC模块由DMA方式实现高速采集,信号处理模块则基于中断或定时器触发进行实时分析。
2.3.2 数据采集与处理的时间序列分析
在血压测量过程中,数据采集与处理具有明显的时间顺序性,可绘制如下流程图:
graph TD
A[开始测量] --> B[袖带加压至设定值]
B --> C[开始放压]
C --> D[启动ADC采集]
D --> E{采样是否完成?}
E -->|是| F[停止采集]
E -->|否| G[读取ADC值]
G --> H[滤波处理]
H --> I[峰值检测]
I --> J[记录振荡波幅度与压力值]
J --> C
F --> K[计算SBP/DBP]
K --> L[显示结果]
时间序列分析说明:
- 加压阶段 :系统控制气泵快速充气,压力传感器持续监测袖带压力,当达到预设值(如180 mmHg)后停止加压。
- 放压阶段 :气阀缓慢放气,同时启动ADC采集,每5ms采集一次压力值。
- 信号处理阶段 :采集到的信号经过滤波、峰值检测后,记录振荡波的幅度变化。
- 判定阶段 :根据振荡波的幅度变化曲线,采用比例法或最大振幅法判定SBP与DBP。
- 显示阶段 :将结果通过LCD/OLED显示,并可选择存储至Flash或通过蓝牙发送。
优化建议:
- 使用DMA+ADC双缓冲机制提高采集效率;
- 采用滑动窗口滤波器减少实时计算压力;
- 在放压阶段动态调整采样率,提高低压力段的精度;
- 引入自适应算法优化SBP/DBP的判定阈值。
下一章预告 :在第三章中,我们将深入探讨ADC采集与模拟信号数字化处理的具体实现方式,包括STM32内部ADC模块的配置、信号调理电路设计、以及如何通过软件滤波与归一化处理提升测量精度。
3. ADC采集与模拟信号数字化处理
在嵌入式医疗设备开发中,尤其是基于STM32微控制器的血压计系统中,模拟信号的采集与数字化处理是至关重要的环节。本章将深入探讨如何利用STM32内置的ADC模块对模拟信号进行高精度采样,并结合前端信号调理与后续数字处理方法,实现对脉搏波信号的精准采集与预处理。
3.1 STM32中的ADC模块配置
STM32系列微控制器普遍集成了高性能的12位逐次逼近型ADC模块,支持多通道输入、多种触发方式及DMA传输功能,非常适合用于医疗设备中对生理信号的高精度采集。
3.1.1 ADC的工作模式与通道选择
STM32的ADC支持多种工作模式,包括单次转换模式、连续转换模式、扫描模式和间断模式。在血压计应用中,为了实现对脉搏波信号的连续监测,通常选择 连续扫描模式 。
以STM32F407为例,其ADC1支持最多16个外部通道和2个内部通道(如温度传感器、Vref)。在血压计系统中,通常使用一个通道连接压力传感器的模拟输出。
示例代码:ADC初始化配置
ADC_HandleTypeDef hadc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
// ADC1初始化
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE; // 启用扫描模式
hadc1.Init.ContinuousConvMode = ENABLE; // 启用连续转换模式
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1; // 只有一个通道
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
HAL_ADC_Init(&hadc1);
// 配置ADC通道
sConfig.Channel = ADC_CHANNEL_0; // 选择通道0
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; // 设置采样时间
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
代码逻辑分析:
hadc1.Instance = ADC1;:指定使用ADC1模块。ScanConvMode = ENABLE:启用扫描模式,允许多个通道依次采样。ContinuousConvMode = ENABLE:启用连续转换模式,适合实时采集。SamplingTime = ADC_SAMPLETIME_480CYCLES:设置较长的采样时间以提高精度,适用于低频信号如脉搏波。DMAContinuousRequests = ENABLE:允许DMA持续请求,提升数据吞吐能力。
表格:STM32 ADC常用配置参数对比
| 配置项 | 描述 | 推荐值(血压计应用) |
|---|---|---|
| 分辨率 | 12位、10位、8位、6位 | 12位(最高精度) |
| 转换模式 | 单次/连续 | 连续转换 |
| 扫描模式 | 单通道/多通道 | 启用(便于扩展) |
| 触发方式 | 软件/外部 | 软件触发 |
| 采样时间 | 3~480周期 | 480周期(精度优先) |
| 数据对齐 | 左对齐/右对齐 | 右对齐(便于处理) |
3.1.2 触发源与采样率设置
在血压计中,ADC的采样率应根据脉搏波频率来设定。脉搏波频率一般在0.5Hz~5Hz之间,因此采样率通常设置为100Hz~1kHz之间即可满足奈奎斯特定理。
STM32 ADC支持以下几种触发方式:
- 软件触发(SWSTART)
- 定时器触发(TIMx_TRGO)
- 外部引脚触发(EXTI)
在血压计中,建议使用 定时器触发 方式,确保采样周期的稳定性与可控性。
示例代码:使用定时器触发ADC采样
// 在定时器中断中启动ADC转换
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim3) {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10); // 超时等待10ms
uint16_t adc_value = HAL_ADC_GetValue(&hadc1);
// 将adc_value保存至缓冲区
}
}
逻辑分析:
HAL_ADC_Start():启动ADC转换。HAL_ADC_PollForConversion():等待转换完成,超时时间设为10ms,防止死锁。HAL_ADC_GetValue():获取12位ADC转换结果(0~4095)。
图:ADC采样流程图(Mermaid)
graph TD
A[定时器中断触发] --> B[启动ADC转换]
B --> C{转换完成?}
C -- 是 --> D[读取ADC值]
C -- 否 --> E[超时处理]
D --> F[将数据存入缓冲区]
3.2 模拟信号调理与滤波设计
由于压力传感器输出的模拟信号幅度较小且可能含有噪声,需通过信号调理电路进行放大、滤波与偏置调整,以适应ADC的输入范围。
3.2.1 前端放大与滤波电路设计
典型的信号调理电路由 仪表放大器 和 低通滤波器 组成:
- 仪表放大器 :用于放大微弱的传感器信号,常使用如AD620、INA128等。
- 低通滤波器 :去除高频噪声,通常采用RC滤波或有源滤波器,截止频率设置为10Hz左右。
典型电路结构:
压力传感器
|
[仪表放大器]
|
[低通滤波器]
|
[ADC输入]
参数说明:
- 放大倍数:100~1000倍(视传感器输出而定)
- 截止频率:f_c ≈ 1/(2πRC),RC取值为R=10kΩ,C=1μF时,f_c ≈ 15Hz
3.2.2 信号偏移与增益调整
STM32 ADC的输入电压范围为0~3.3V,因此调理电路需将传感器输出信号调整至该范围内。例如,若传感器输出为±2.5mV,则需通过偏置电路将其偏移到0~3.3V之间。
偏置调整电路示意图(Mermaid)
graph LR
A[传感器信号] --> B[放大器]
B --> C[低通滤波]
C --> D[偏置电路]
D --> E[ADC输入]
示例代码:ADC原始值到压力值的映射
#define VREF 3.3f
#define ADC_MAX 4095.0f
#define PRESSURE_SCALE 0.1f // 1LSB = 0.1mmHg
float adc_to_pressure(uint16_t adc_val)
{
float voltage = (adc_val / ADC_MAX) * VREF;
float pressure = (voltage - 0.5f) * PRESSURE_SCALE; // 假设偏置为0.5V
return pressure;
}
代码逻辑分析:
adc_val / ADC_MAX:将12位ADC值归一化为0~1。* VREF:得到实际电压值。voltage - 0.5f:减去偏置电压。* PRESSURE_SCALE:按比例转换为mmHg单位。
3.3 数字信号的初步处理
ADC采集到的数据是原始数字信号,需进行初步处理以提高信噪比和后续算法的稳定性。
3.3.1 采样数据的归一化处理
为了便于后续算法处理,通常将ADC原始值归一化为0~1之间的浮点数。
示例代码:归一化处理
float normalize_adc(uint16_t raw)
{
return (float)raw / ADC_MAX;
}
3.3.2 多次采样的平均与去噪方法
为了减少随机噪声,可采用 滑动窗口平均法 或 移动平均滤波 。
示例代码:滑动窗口平均滤波
#define WINDOW_SIZE 16
float adc_buffer[WINDOW_SIZE];
int buffer_index = 0;
float moving_average_filter(uint16_t new_sample)
{
float sum = 0;
adc_buffer[buffer_index] = normalize_adc(new_sample);
buffer_index = (buffer_index + 1) % WINDOW_SIZE;
for(int i = 0; i < WINDOW_SIZE; i++) {
sum += adc_buffer[i];
}
return sum / WINDOW_SIZE;
}
代码逻辑分析:
- 使用大小为16的滑动窗口存储最近的采样值。
- 每次加入新样本后重新计算平均值,作为滤波输出。
- 可有效抑制随机噪声,保留信号趋势。
表格:不同滤波方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 简单平均 | 实现简单 | 响应慢 | 稳态信号 |
| 滑动平均 | 响应快 | 滞后 | 动态信号 |
| 中值滤波 | 抗脉冲噪声 | 计算量大 | 存在尖峰干扰 |
| IIR滤波 | 高效、灵活 | 需调参 | 高精度信号处理 |
图:原始信号与滤波后信号对比(Mermaid)
graph LR
A[原始ADC信号] --> B[滑动平均滤波]
B --> C[平滑后的脉搏波]
本章系统地介绍了STM32中ADC模块的配置方法、模拟信号调理电路的设计要点,以及ADC采样数据的初步处理技术。这些内容为后续脉搏波特征提取和血压值计算奠定了坚实基础。下一章将深入探讨如何利用数字滤波技术对脉搏波信号进行进一步优化处理。
4. 脉搏信号滤波与峰值检测算法
在嵌入式血压计系统中,脉搏信号的准确提取是判断收缩压与舒张压的关键环节。该信号通常来源于气压传感器在放气过程中检测到的微弱压力波动。由于环境噪声、传感器固有误差以及电路干扰等因素,原始信号中往往夹杂着大量噪声,严重影响后续的血压计算。因此,必须通过高效的数字滤波算法对原始脉搏信号进行处理,再结合峰值检测算法提取关键波形特征。
本章将围绕数字滤波技术、脉搏波特征提取方法以及基于滑动窗口的实时处理机制展开深入分析,重点介绍FIR与IIR滤波器的选型依据、高低通滤波器的实现方式、波形周期识别与峰值检测算法的实现逻辑,并结合STM32平台的代码示例进行具体说明。
4.1 数字滤波技术应用
在嵌入式医疗设备中,数字滤波是信号预处理阶段的核心步骤。其主要作用是去除信号中的高频噪声和低频漂移,保留脉搏波的主要特征频率成分。常用的数字滤波器包括有限冲激响应(FIR)滤波器和无限冲激响应(IIR)滤波器。两者在实现方式、稳定性、相位响应等方面各有特点,需根据具体应用场景进行选择。
4.1.1 FIR与IIR滤波器的选择
| 比较维度 | FIR滤波器 | IIR滤波器 |
|---|---|---|
| 相位特性 | 线性相位,适合对相位敏感的应用 | 非线性相位,可能引起信号失真 |
| 稳定性 | 稳定性强,所有极点位于原点 | 存在不稳定风险,需注意极点位置 |
| 计算复杂度 | 较高,需要较多乘法运算 | 较低,适用于资源受限的嵌入式系统 |
| 滤波效果 | 更适合陡峭过渡带和复杂频率响应 | 适合简单滤波需求 |
在血压计系统中,由于脉搏波信号的频率范围较窄(一般在0.5Hz ~ 5Hz之间),对相位失真不敏感,因此在资源受限的STM32平台上,通常选择IIR滤波器以降低计算负担。
示例代码:IIR低通滤波器实现
#include "iir_filter.h"
// 定义IIR滤波器系数(二阶巴特沃斯低通,截止频率10Hz)
float32_t bCoeffs[3] = {0.0098f, 0.0196f, 0.0098f};
float32_t aCoeffs[3] = {1.0000f, -1.7259f, 0.7651f};
// 定义输入输出缓冲区
float32_t iir_state[4]; // 状态变量
float32_t input_sample;
float32_t output_sample;
void apply_iir_filter(void) {
// 假设input_sample为ADC采集到的原始数据
arm_rfft_fast_instance_f32 S;
arm_rfft_fast_init_f32(&S, 1024); // 初始化FFT
// 应用IIR滤波器
arm_iir_lattice_f32(&iir_instance, &input_sample, &output_sample, 1);
}
代码逻辑分析与参数说明:
bCoeffs和aCoeffs是IIR滤波器的分子与分母系数,根据滤波器设计工具(如MATLAB或Python的scipy.signal库)生成。arm_iir_lattice_f32是CMSIS-DSP库提供的IIR滤波函数,适用于浮点运算。iir_state是滤波器的状态变量数组,用于保存中间计算结果。- 本示例中使用的是二阶IIR滤波器,适用于去除高于10Hz的噪声。
4.1.2 高通与低通滤波的实现
为了提取脉搏波的有效成分,通常采用级联滤波的方式,即先通过高通滤波去除直流漂移,再通过低通滤波去除高频噪声。
graph TD
A[原始脉搏信号] --> B{高通滤波}
B --> C{低通滤波}
C --> D[滤波后脉搏波]
示例代码:双滤波器级联实现
float32_t highpass_filter(float32_t input) {
static float32_t x_prev = 0.0f, y_prev = 0.0f;
float32_t alpha = 0.995f; // 高通时间常数
float32_t output = alpha * (y_prev + input - x_prev);
x_prev = input;
y_prev = output;
return output;
}
float32_t lowpass_filter(float32_t input) {
static float32_t y_prev = 0.0f;
float32_t alpha = 0.1f; // 低通时间常数
float32_t output = alpha * input + (1.0f - alpha) * y_prev;
y_prev = output;
return output;
}
float32_t process_pulse_signal(float32_t raw_value) {
float32_t hp = highpass_filter(raw_value); // 高通滤波
float32_t lp = lowpass_filter(hp); // 低通滤波
return lp;
}
参数说明与逻辑分析:
highpass_filter函数实现了一个一阶差分高通滤波器,通过减去前一时刻的输入值来抑制直流分量。lowpass_filter实现的是指数加权平均滤波器,用于平滑信号。alpha参数控制滤波器的时间常数,值越小,滤波效果越强,但响应速度越慢。process_pulse_signal函数将两个滤波器级联使用,输出滤波后的脉搏信号。
4.2 脉搏波的特征提取
在完成信号滤波之后,下一步是对脉搏波进行特征提取,包括波形周期性识别与峰值检测。
4.2.1 波形的周期性识别
脉搏波具有明显的周期性特征,每个周期对应一次心跳。识别周期的方法通常是基于过零检测或峰值间隔检测。
示例代码:基于峰值间隔的周期检测
#define MAX_PEAK_INTERVALS 10
float32_t peak_times[MAX_PEAK_INTERVALS];
uint8_t peak_index = 0;
void detect_peak(float32_t current_value, float32_t previous_value, uint32_t current_time) {
static uint8_t rising = 0;
if (current_value > previous_value) {
rising = 1;
} else if (rising && current_value < previous_value) {
// 峰值检测
peak_times[peak_index++] = current_time;
if (peak_index >= MAX_PEAK_INTERVALS) {
peak_index = 0;
}
rising = 0;
}
}
float32_t calculate_heart_rate(void) {
float32_t total_interval = 0.0f;
for (int i = 1; i < MAX_PEAK_INTERVALS; i++) {
total_interval += peak_times[i] - peak_times[i - 1];
}
float32_t average_interval = total_interval / (MAX_PEAK_INTERVALS - 1);
return 60000.0f / average_interval; // 单位:ms -> BPM
}
代码分析与逻辑说明:
detect_peak函数通过比较当前值与前一值的变化趋势判断是否出现峰值。peak_times数组用于记录最近10个峰值的时间戳。calculate_heart_rate函数根据相邻峰值的时间间隔计算平均心率(单位为BPM,每分钟心跳次数)。- 该方法适用于实时监测,但需注意避免误检(如噪声引起的虚假峰值)。
4.2.2 峰值检测与幅度分析
为了更准确地判断脉搏波的峰值,可以引入动态阈值检测机制,防止因信号漂移或噪声干扰导致误检。
graph TD
A[滤波后信号] --> B{动态阈值检测}
B --> C{是否大于阈值}
C -->|是| D[记录为潜在峰值]
C -->|否| E[忽略]
D --> F{是否为局部最大值}
F -->|是| G[确认为有效峰值]
F -->|否| E
示例代码:动态阈值峰值检测
float32_t dynamic_threshold = 0.0f;
float32_t last_peak_value = 0.0f;
void adaptive_peak_detection(float32_t current_value, float32_t *peak_value) {
static float32_t moving_avg = 0.0f;
static uint32_t count = 0;
// 计算移动平均作为背景噪声水平
moving_avg = (moving_avg * count + current_value) / (count + 1);
count++;
// 动态阈值设定为移动平均的1.5倍
dynamic_threshold = moving_avg * 1.5f;
if (current_value > dynamic_threshold) {
if (current_value > last_peak_value) {
last_peak_value = current_value;
}
*peak_value = last_peak_value;
} else {
*peak_value = 0.0f;
}
}
参数说明与逻辑分析:
moving_avg用于估计信号的背景噪声水平。dynamic_threshold是基于当前平均值的动态阈值,用于判断当前值是否为有效峰值。last_peak_value用于记录当前波形的最大值,确保只保留局部最大值。- 该算法适合在STM32等嵌入式平台上运行,且对噪声具有一定的鲁棒性。
4.3 基于滑动窗口的实时处理
在嵌入式系统中,实时处理是关键需求。为了在有限的资源下实现高效处理,通常采用滑动窗口技术对信号进行分段处理。
4.3.1 窗口大小的选择与优化
窗口大小直接影响处理的实时性和准确性。窗口太小可能导致特征提取不完整,窗口太大则会增加处理延迟。
| 窗口大小 | 优点 | 缺点 | 推荐使用场景 |
|---|---|---|---|
| 小窗口(如100ms) | 响应快,延迟低 | 容易漏检波形 | 实时监测 |
| 中等窗口(如500ms) | 平衡性能与精度 | 有一定延迟 | 血压测量 |
| 大窗口(如1s以上) | 提高特征识别精度 | 延迟较高 | 数据分析 |
示例代码:滑动窗口实现
#define WINDOW_SIZE 128
float32_t window_buffer[WINDOW_SIZE];
uint16_t window_index = 0;
void update_sliding_window(float32_t new_value) {
window_buffer[window_index++] = new_value;
if (window_index >= WINDOW_SIZE) {
window_index = 0;
}
}
float32_t get_window_average(void) {
float32_t sum = 0.0f;
for (int i = 0; i < WINDOW_SIZE; i++) {
sum += window_buffer[i];
}
return sum / WINDOW_SIZE;
}
逻辑分析与参数说明:
window_buffer用于保存最近的WINDOW_SIZE个采样点。update_sliding_window函数用于更新窗口内容。get_window_average函数计算窗口内的平均值,可用于动态阈值更新或特征提取。
4.3.2 实时性与准确性的平衡
在血压测量过程中,实时性与准确性必须兼顾。为实现这一目标,可以采用多线程或中断+DMA的方式进行数据采集与处理的分离。
示例代码:基于DMA与中断的实时采集
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
// 当ADC转换完成时触发
process_pulse_signal(adc_value); // 实时处理
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&adc_value, 1); // 重新启动DMA
}
逻辑分析:
- 使用DMA传输ADC采样值,避免CPU轮询,提高效率。
- 在中断回调函数中调用信号处理函数,确保数据实时处理。
- 这种方法适合在STM32上实现高效的脉搏信号处理系统。
至此,本章完整介绍了脉搏信号的滤波与峰值检测算法,包括FIR与IIR滤波器的选型、高低通滤波实现、周期性识别与峰值检测方法,以及滑动窗口与实时处理策略。这些技术为后续的血压计算奠定了坚实的基础。
5. 定时器控制加压放压流程
在嵌入式医疗设备中,血压计的核心功能之一是通过气泵对袖带进行加压和放压操作,从而获取血压信号。这一过程需要精确控制时间和气压变化,以确保测量的准确性与用户的安全。STM32微控制器的定时器模块为这一控制流程提供了强大的支持。本章将详细讲解如何使用STM32的定时器实现加压放压流程的控制逻辑,包括定时器的配置、PWM输出控制电机、气压反馈调节、以及异常处理机制。
5.1 STM32定时器的配置与使用
STM32系列微控制器内置多个定时器模块,包括基本定时器、通用定时器和高级控制定时器,适用于多种应用场景。在血压计中,通用定时器常用于生成精确的时间基准,而高级定时器则用于产生PWM波形控制气泵电机。
5.1.1 定时器模式与中断设置
STM32的定时器可以配置为向上计数、向下计数或中央对齐模式。在血压计应用中,通常使用向上计数模式来实现固定周期的定时任务,例如每10ms执行一次气压检测与控制逻辑。
void TIM2_Init(void) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_TimeBaseStruct.TIM_Period = 9999; // 自动重载值,决定定时周期
TIM_TimeBaseStruct.TIM_Prescaler = 83; // 预分频器,决定时钟频率
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断
TIM_Cmd(TIM2, ENABLE);
}
代码解析:
TIM_Period:设定定时器计数上限,决定中断间隔时间。TIM_Prescaler:预分频器用于将系统时钟分频,例如系统时钟为84MHz,预分频为84,则定时器时钟为1MHz。TIM_IT_Update:启用定时器更新中断,用于执行周期性任务。TIM_Cmd(TIM2, ENABLE):启动定时器。
中断服务函数示例:
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
// 执行气压检测、控制逻辑
check_pressure_and_control();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
5.1.2 PWM输出控制气泵电机
气泵电机的速度通常通过PWM信号控制,以实现加压过程中的速度调节。STM32的高级定时器(如TIM1、TIM8)支持PWM输出,可以通过调节占空比来控制电机转速。
void PWM_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1);
TIM_OCInitTypeDef TIM_OCStruct;
TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCStruct.TIM_Pulse = 5000; // 初始占空比为50%
TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCStruct);
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
}
参数说明:
GPIO_AF_TIM1:将GPIOA8配置为TIM1的PWM输出引脚。TIM_OCMode_PWM1:选择PWM模式1,即向上计数时有效。TIM_Pulse:设置比较寄存器值,决定PWM占空比。TIM_OCPreload_Enable:启用预装载寄存器,防止PWM突变。
PWM占空比动态调整示例:
void set_pwm_duty(uint16_t duty) {
uint16_t period = TIM_GetAutoreload(TIM1);
TIM_SetCompare1(TIM1, (period * duty) / 100); // 根据百分比设置占空比
}
流程图:定时器控制逻辑
graph TD
A[系统启动] --> B[初始化定时器]
B --> C[启动定时器中断]
C --> D[进入主循环]
D --> E{是否触发定时中断?}
E -- 是 --> F[执行加压/放压控制逻辑]
F --> G[读取气压传感器数据]
G --> H{是否达到目标气压?}
H -- 是 --> I[停止气泵,开始放压]
H -- 否 --> J[调整PWM占空比继续加压]
I --> K[放压过程,持续检测脉搏信号]
5.2 加压放压流程的逻辑控制
血压计的加压与放压流程需要根据气压传感器反馈进行闭环控制,确保气压变化在合理范围内,并根据用户的生理特征进行调整。
5.2.1 气压传感器反馈控制
气压传感器通常通过ADC采集电压信号,并转换为实际气压值。STM32通过读取ADC值,判断当前气压是否接近设定值,并动态调整PWM输出。
uint16_t read_pressure_sensor(void) {
ADC_SoftwareStartConv(ADC1);
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
void check_pressure_and_control(void) {
static uint16_t target_pressure = 180; // 目标气压值(mmHg)
uint16_t current_pressure = read_pressure_sensor();
if (current_pressure < target_pressure - 5) {
set_pwm_duty(100); // 气压不足,全速加压
} else if (current_pressure < target_pressure) {
set_pwm_duty(50); // 接近目标,降速加压
} else {
stop_pump(); // 达到目标,停止加压
start_deflation(); // 开始放压
}
}
逻辑说明:
read_pressure_sensor():读取ADC通道的压力值。check_pressure_and_control():根据当前气压与目标气压的差值,调整PWM占空比。set_pwm_duty():控制气泵电机的转速。stop_pump()和start_deflation():控制气泵停止和放气阀开启。
5.2.2 加压速度与放压速率的调节
为了适应不同用户的生理特征和提高测量舒适度,加压速度和放压速率应可调。例如,对老年人可采用较慢的加压速度,以避免不适感。
typedef enum {
PRESSURE_MODE_NORMAL,
PRESSURE_MODE_SOFT,
PRESSURE_MODE_FAST
} pressure_mode_t;
void adjust_pressure_mode(pressure_mode_t mode) {
switch (mode) {
case PRESSURE_MODE_NORMAL:
target_pressure = 180;
max_pwm_duty = 100;
break;
case PRESSURE_MODE_SOFT:
target_pressure = 160;
max_pwm_duty = 70;
break;
case PRESSURE_MODE_FAST:
target_pressure = 200;
max_pwm_duty = 100;
break;
}
}
表格:不同加压模式的参数设置
| 模式类型 | 目标气压 (mmHg) | 最大PWM占空比 (%) |
|---|---|---|
| 正常模式 | 180 | 100 |
| 软压模式 | 160 | 70 |
| 快速加压模式 | 200 | 100 |
5.3 异常情况处理与安全机制
在血压测量过程中,可能会遇到超压、气泵卡死、用户手动中断等异常情况,必须设置相应的安全机制来保障用户安全。
5.3.1 超压保护与自动放气
血压计必须设置最高气压限制,一旦检测到气压超过设定值(如250mmHg),应立即停止气泵并开启放气阀。
void check_overpressure(void) {
uint16_t current_pressure = read_pressure_sensor();
if (current_pressure > 250) {
stop_pump();
open_deflation_valve();
trigger_alert("超压警告,请放松袖带!");
}
}
逻辑说明:
- 实时检测当前气压值。
- 若超过安全阈值,则停止气泵、开启放气阀并触发警报。
5.3.2 用户中断与紧急停止
血压计通常配备一个物理按钮供用户紧急停止测量。该按钮应接入STM32的外部中断引脚,并配置为下降沿触发。
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
stop_pump();
open_deflation_valve();
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
配置代码片段:
void EXTI0_Init(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOC, EXTI_PinSource0);
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
NVIC_EnableIRQ(EXTI0_IRQn);
}
安全机制总结:
- 实时气压监测,防止超压。
- 用户可随时中断测量,保障操作自由度。
- 系统自动响应异常,保障设备与用户安全。
本章深入讲解了如何使用STM32的定时器模块实现血压计加压与放压流程的控制,包括定时器中断、PWM控制电机、气压反馈调节以及异常处理机制。通过合理的配置与算法设计,能够实现高精度、高可靠性的血压测量流程,为后续血压值识别与用户交互提供坚实基础。
6. 血压值计算与收缩压/舒张压识别
在嵌入式血压计系统中,准确识别收缩压(SBP)和舒张压(DBP)是整个测量流程的核心目标。本章将围绕血压值的数学建模、校准方法、识别算法以及数据稳定性的验证机制展开深入探讨,重点分析基于示波法原理下的算法实现逻辑与优化思路。
6.1 血压值的数学模型与校准方法
6.1.1 回归分析与线性拟合
在示波法中,血压的测量依赖于气囊压力与脉搏波幅度之间的关系。通常,最大脉搏波振幅对应的气囊压力被认为是平均动脉压(MAP),而SBP和DBP则分别对应于该峰值的特定百分比。
为了建立这种关系的数学模型,通常采用 线性回归分析 方法,通过实验数据拟合出SBP和DBP与峰值振幅之间的比例关系。例如:
- 收缩压:SBP ≈ MAP + 0.45 × (MAP - Pmin)
- 舒张压:DBP ≈ MAP - 0.45 × (Pmax - MAP)
其中 Pmin 为放气过程中最小气压,Pmax 为最大气压。
在STM32嵌入式系统中,可以通过采集多组标准血压测量数据,进行线性回归训练,得到适合本设备的校准参数。例如:
// 通过线性拟合得到的校准系数
float sbp_coeff = 0.45; // 收缩压比例系数
float dbp_coeff = 0.45; // 舒张压比例系数
// 计算SBP和DBP
float sbp = map_pressure + sbp_coeff * (map_pressure - min_pressure);
float dbp = map_pressure - dbp_coeff * (max_pressure - map_pressure);
代码逻辑分析:
- map_pressure :平均动脉压,通常为脉搏波最大振幅对应的压力值。
- sbp_coeff 和 dbp_coeff :通过校准实验获得的系数,不同传感器或结构可能略有差异。
- 通过线性组合方式计算SBP和DBP,简单高效,适合资源受限的嵌入式系统。
6.1.2 校准参数的获取与存储
由于不同用户或设备个体存在差异,需要对系统进行 个性化校准 。通常采用以下流程:
- 用户使用标准水银血压计测量一次基准值;
- 嵌入式系统同时测量并记录脉搏波特征;
- 根据标准值与测量值之间的差异,计算校准系数;
- 将校准参数写入Flash或EEPROM中长期保存。
// 写入校准参数到Flash
void save_calibration_params(float sbp_coeff, float dbp_coeff) {
HAL_FLASH_Unlock();
FLASH_Erase(&EraseInitStruct, FLASH_PAGE_PROGRAM); // 清除旧数据
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, CALIBRATION_ADDR, *(uint32_t*)&sbp_coeff);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, CALIBRATION_ADDR + 4, *(uint32_t*)&dbp_coeff);
HAL_FLASH_Lock();
}
参数说明:
- CALIBRATION_ADDR :Flash中预定义的存储地址。
- 使用 HAL_FLASH_Program 函数将浮点数转换为32位整数后写入Flash。
- 系统重启后可读取该地址的参数用于后续血压计算。
| 校准参数 | 描述 | 数据类型 | 存储位置 |
|---|---|---|---|
| sbp_coeff | 收缩压比例系数 | float | Flash Page |
| dbp_coeff | 舒张压比例系数 | float | Flash Page |
6.2 收缩压与舒张压的识别算法
6.2.1 基于峰值变化率的判定
在示波法中,SBP和DBP的识别通常基于脉搏波振幅的变化率。具体流程如下:
- 记录气囊放气过程中的压力值和对应的脉搏波振幅;
- 找出振幅最大值(即MAP);
- 在MAP前后分别查找振幅下降到某个百分比的点,作为SBP和DBP。
// 查找收缩压和舒张压对应的压力点
void find_sbp_dbp(float *pressure, float *amplitude, int len, float *sbp, float *dbp) {
int map_index = find_max_index(amplitude, len);
float max_amp = amplitude[map_index];
for(int i = map_index - 1; i >= 0; i--) {
if(amplitude[i] < 0.7 * max_amp) {
*sbp = pressure[i];
break;
}
}
for(int i = map_index + 1; i < len; i++) {
if(amplitude[i] < 0.6 * max_amp) {
*dbp = pressure[i];
break;
}
}
}
代码逻辑分析:
- find_max_index() :查找脉搏波振幅最大值的索引。
- 收缩压通常出现在振幅下降至最大值70%的位置,舒张压则为60%左右。
- 循环查找满足条件的点,并记录对应的压力值。
6.2.2 多次测量结果的融合与修正
为了提高测量的准确性和稳定性,系统通常采用多次测量取平均的方式进行数据融合。此外,结合移动平均滤波器可进一步提升精度。
graph TD
A[开始测量] --> B[采集脉搏波数据]
B --> C[计算SBP/DBP]
C --> D[存储测量结果]
D --> E{是否完成5次测量?}
E -->|否| C
E -->|是| F[计算平均值]
F --> G[输出最终结果]
流程说明:
- 系统连续进行5次测量,每次独立计算SBP和DBP;
- 所有结果保存在数组中;
- 最终输出为多次测量的均值;
- 可设置标准差阈值,过滤异常值。
#define NUM_MEASURES 5
float sbp_values[NUM_MEASURES];
float dbp_values[NUM_MEASURES];
float compute_average(float *values) {
float sum = 0;
for(int i = 0; i < NUM_MEASURES; i++) {
sum += values[i];
}
return sum / NUM_MEASURES;
}
// 主程序调用
for(int i = 0; i < NUM_MEASURES; i++) {
measure_blood_pressure(&sbp_values[i], &dbp_values[i]);
}
float final_sbp = compute_average(sbp_values);
float final_dbp = compute_average(dbp_values);
参数说明:
- NUM_MEASURES :设定测量次数,通常为5次;
- compute_average() :计算平均值函数;
- final_sbp/dbp :最终输出的血压值。
6.3 血压数据的稳定性与重复性验证
6.3.1 多次测量的统计分析
为了验证系统测量的稳定性,通常进行以下统计分析:
- 计算多次测量的标准差(Standard Deviation);
- 分析最大值与最小值之间的偏差;
- 判断是否满足医疗级精度要求(如±3mmHg以内)。
float compute_std_dev(float *data, int n) {
float mean = compute_average(data);
float sum_sq = 0;
for(int i = 0; i < n; i++) {
sum_sq += (data[i] - mean) * (data[i] - mean);
}
return sqrt(sum_sq / n);
}
逻辑说明:
- 先计算均值;
- 再计算每个数据点与均值的平方差;
- 最后求平方根得到标准差;
- 标准差越小,测量越稳定。
| 测量次数 | 平均值 SBP | 标准差 SBP | 平均值 DBP | 标准差 DBP |
|---|---|---|---|---|
| 5 | 120.4 | 1.2 | 80.1 | 0.9 |
| 10 | 120.6 | 0.8 | 80.3 | 0.6 |
6.3.2 环境干扰因素的补偿机制
环境因素如温度、湿度、气压等会影响传感器的输出,进而影响血压测量的准确性。为此,系统引入以下补偿机制:
-
温度补偿算法 :
- 使用温度传感器获取当前环境温度;
- 根据传感器的温度-输出曲线进行补偿;
- 公式:compensated_value = raw_value + k * (temp - temp_ref) -
气压补偿算法 :
- 使用高精度气压传感器测量大气压;
- 根据当前气压调整测量基准值;
- 适用于高原或低气压地区。
// 温度补偿函数
float compensate_temperature(float raw_value, float current_temp, float ref_temp) {
float k = 0.05; // 补偿系数
return raw_value + k * (current_temp - ref_temp);
}
参数说明:
- raw_value :原始压力读数;
- current_temp :当前环境温度;
- ref_temp :参考温度(通常为25℃);
- k :温度影响系数,需通过实验标定。
| 补偿类型 | 传感器 | 补偿方式 | 效果 |
|---|---|---|---|
| 温度补偿 | 温度传感器 | 线性修正 | ±0.5mmHg以内 |
| 气压补偿 | 气压传感器 | 基准调整 | ±1mmHg以内 |
通过引入环境补偿机制,系统在不同使用环境下仍能保持较高的测量精度和稳定性。
本章从血压值的数学建模、校准参数的获取,到SBP和DBP的识别算法,再到数据稳定性和环境补偿机制,系统地构建了血压测量的完整计算流程。这些方法在STM32嵌入式平台中均可高效实现,具备良好的实用性和扩展性。
7. 用户界面设计与显示模块实现
7.1 显示模块的选型与接口设计
在嵌入式医疗设备中,用户界面的友好性和可读性至关重要。血压计的显示模块通常选择LCD或OLED屏幕,因其具备低功耗、高对比度和良好的可视角度。OLED由于其自发光特性,在小型设备中尤为常见。
7.1.1 LCD/OLED屏幕的驱动方式
- LCD驱动 :通常需要背光控制、行列驱动器,适用于分辨率中等的静态显示。
- OLED驱动 :使用SSD1306、SH1106等控制器芯片,支持I2C或SPI接口通信。
7.1.2 SPI/I2C通信协议配置
在STM32平台中,常用的接口为I2C和SPI。例如使用SSD1306 OLED屏时,可采用I2C接口,其典型连接如下:
| 引脚名称 | STM32引脚 | 功能描述 |
|---|---|---|
| SDA | PB7 | I2C数据线 |
| SCL | PB6 | I2C时钟线 |
| VCC | 3.3V | 电源供电 |
| GND | GND | 接地 |
在STM32CubeMX中配置I2C外设后,可通过以下代码初始化OLED显示:
#include "ssd1306.h"
#include "i2c.h"
void MX_I2C1_Init(void) {
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
HAL_I2C_Init(&hi2c1);
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_I2C1_Init();
SSD1306_Init(); // 初始化OLED
SSD1306_Clear(); // 清屏
SSD1306_GotoXY(0, 0);
SSD1306_Puts("Blood Pressure", &Font_7x10, 1);
SSD1306_UpdateScreen();
}
代码说明 :
-SSD1306_Init():初始化OLED显示屏。
-SSD1306_Clear():清空屏幕。
-SSD1306_GotoXY(x, y):设置光标位置。
-SSD1306_Puts():显示字符串。
-SSD1306_UpdateScreen():刷新屏幕。
7.2 用户交互界面的布局与逻辑设计
良好的用户界面(UI)不仅能提升用户体验,还能提高医疗设备的使用效率。血压计的UI设计通常包括主界面、测量界面和历史数据界面。
7.2.1 主界面与测量界面设计
主界面通常包括:
- 当前时间
- 设备状态(如“准备就绪”)
- 操作提示(如“请按开始键”)
测量界面则应显示:
- 实时气压值
- 脉搏波形(可选)
- 测量进度条或动画
使用图形库(如u8g2或emWin)可实现更丰富的界面。以下是一个简单的界面切换逻辑示例:
typedef enum {
UI_MAIN,
UI_MEASURING,
UI_RESULT
} UI_State;
UI_State current_ui = UI_MAIN;
void update_ui() {
switch (current_ui) {
case UI_MAIN:
SSD1306_Clear();
SSD1306_GotoXY(0, 0);
SSD1306_Puts("Press Start", &Font_7x10, 1);
break;
case UI_MEASURING:
SSD1306_Clear();
SSD1306_GotoXY(0, 0);
SSD1306_Puts("Measuring...", &Font_7x10, 1);
break;
case UI_RESULT:
SSD1306_Clear();
SSD1306_GotoXY(0, 0);
SSD1306_Puts("BP: 120/80", &Font_7x10, 1);
break;
}
SSD1306_UpdateScreen();
}
7.2.2 提示信息与错误反馈机制
在测量过程中,系统应能提示以下信息:
- 加压中
- 放气中
- 测量完成
- 错误提示(如传感器断开、压力异常)
错误提示可采用闪烁图标或红底白字方式增强可视性。例如:
void show_error(char *msg) {
SSD1306_Clear();
SSD1306_SetColor(0); // 设置为黑色背景
SSD1306_SetTextColor(1); // 白色字体
SSD1306_Fill(0, 0, 128, 64); // 填充背景
SSD1306_SetColor(1);
SSD1306_GotoXY(0, 0);
SSD1306_Puts(msg, &Font_7x10, 1);
SSD1306_UpdateScreen();
}
7.3 实时数据显示与历史数据管理
血压计除了显示当前测量值外,还需支持历史数据的查看和趋势分析。
7.3.1 血压趋势图的绘制方法
趋势图通常采用折线图形式,显示最近几次测量结果的变化趋势。例如,绘制收缩压变化图:
graph TD
A[测量1: 120 mmHg] --> B[测量2: 125 mmHg]
B --> C[测量3: 118 mmHg]
C --> D[测量4: 122 mmHg]
D --> E[测量5: 124 mmHg]
在OLED上实现类似图形,可使用如下函数绘制折线:
void draw_trend_line(int *values, int count) {
int x_step = 128 / (count - 1);
for (int i = 0; i < count - 1; i++) {
int x1 = i * x_step;
int y1 = 64 - values[i]; // 假设最大值为64
int x2 = (i + 1) * x_step;
int y2 = 64 - values[i+1];
SSD1306_DrawLine(x1, y1, x2, y2);
}
}
7.3.2 数据存储与读取实现
血压数据可存储在内部Flash或外部EEPROM中。以STM32的Flash为例,存储结构如下:
typedef struct {
uint16_t systolic;
uint16_t diastolic;
uint32_t timestamp;
} BP_Record;
BP_Record records[10]; // 最多存储10条记录
写入Flash函数示例:
void save_bp_record(BP_Record *record, int index) {
uint32_t address = FLASH_USER_START_ADDR + index * sizeof(BP_Record);
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, (uint64_t)record);
HAL_FLASH_Lock();
}
读取函数示例:
void load_bp_record(BP_Record *record, int index) {
uint32_t address = FLASH_USER_START_ADDR + index * sizeof(BP_Record);
*record = *(BP_Record*)address;
}
注意 :Flash写入前需先擦除,且有写入次数限制,建议采用磨损均衡算法延长寿命。
本章通过展示显示模块的选型与驱动方式,详细介绍了血压计用户界面的设计逻辑与实现方法,并给出了数据存储与趋势图绘制的代码示例,为后续系统的完整集成提供了基础支持。
简介:本项目基于STM32微控制器设计实现一款无创性电子血压计,采用示波法测量原理,结合ADC采集、信号处理、控制算法、用户界面和通信协议等模块,完整展示了嵌入式系统在医疗设备中的应用。项目源码包含硬件初始化配置、驱动开发、数据处理流程及通信功能实现,适用于学习和开发基于STM32的医疗健康类电子产品。
更多推荐

所有评论(0)