模拟心电图采样数据
定时发送数据:timeIdHeart = startTimer(150)
startTimer()
函数:
- 所属类:
QObject
(所有 Qt 类的基类,因此所有 Qt 对象都可以使用该方法)。 - 函数原型:
int QObject::startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer)
。interval
:定时器触发的时间间隔(单位:毫秒)。timerType
:定时器精度类型(默认为Qt::CoarseTimer
,表示定时器触发时间允许有 5% 的误差,以优化系统资源)。
- 返回值:返回一个唯一的整数 ID(即
timeIdHeart
),用于标识这个定时器,后续可通过该 ID 停止或区分不同的定时器。
定时器处理函数:
void SerialDialog::timerEvent(QTimerEvent *event)
{if (event->timerId() == timeIdHeart) {// 每 200 毫秒执行一次的代码(心率数据发送逻辑)}else if (event->timerId() == timeIdPressure) {// 其他定时器的处理逻辑}
}
完整的代码如下:
void SerialDialog::timerEvent(QTimerEvent *event)
{if(event->timerId()==timeIdPressure){QByteArray message;unsigned char hp = (unsigned char)(120+rand()%10);unsigned char lp = (unsigned char)(80+rand()%5);unsigned char pulse = (unsigned char)(65+rand()%5);message.append(hp);message.append(lp);message.append(pulse);m_serialport->write(message);}else if(event->timerId()== timeIdHeart){QByteArray msg;QString strnums;int nums[12];for(int i=0;i<12;i++){nums[i]=arrs[index]+(unsigned)rand()%7;strnums.append(QString::number(nums[i])+",");}index++;if(index==LENGTH)index=0;// 修复:添加toUtf8()转换msg.append(strnums.toUtf8());m_serialport->write(msg);}else if(event->timerId()== timeIdBreath){QByteArray message;float num1 = 140+rand()%10*1.2;float num2 = 20 +rand()%5*1.2;QString nums = QString::number(num1,'f',1)+","+ QString::number(num2,'f',1);// 修复:添加toUtf8()转换message.append(nums.toUtf8());m_serialport->write(message);}
}
这段代码是 SerialDialog
类中处理定时器事件的核心函数,负责通过串口周期性发送模拟的生理数据(血压、心率、呼吸)。以下是详细解释:
函数整体功能
timerEvent(QTimerEvent *event)
是 Qt 框架中用于处理定时器事件的虚函数。当通过 startTimer()
启动一个定时器后,Qt 会每隔指定的毫秒数触发一次该函数,并传入对应的定时器事件(包含定时器 ID)。
函数通过判断 event->timerId()
来区分不同的定时器,执行不同的数据发送逻辑:
timeIdPressure
:发送血压数据(每 1000ms 一次)timeIdHeart
:发送心率数据(每 200ms 一次)timeIdBreath
:发送呼吸数据(每 1000ms 一次)
血压数据发送逻辑
if(event->timerId() == timeIdPressure)
{QByteArray message;unsigned char hp = (unsigned char)(120 + rand()%10); // 收缩压 (120-129)unsigned char lp = (unsigned char)(80 + rand()%5); // 舒张压 (80-84)unsigned char pulse = (unsigned char)(65 + rand()%5); // 脉搏 (65-69)message.append(hp);message.append(lp);message.append(pulse);m_serialport->write(message);
}
- 数据格式:3 个无符号字节(8 位整数),依次为收缩压、舒张压、脉搏。
- 范围:模拟正常血压范围,随机波动以模拟真实测量。
- 传输方式:二进制格式(非文本),适合高效数据传输。
心率数据发送逻辑
else if(event->timerId() == timeIdHeart)
{QByteArray msg;QString strnums;int nums[12];for(int i=0; i<12; i++){nums[i] = arrs[index] + (unsigned)rand()%7; // 从预设数组中取数据并添加随机波动strnums.append(QString::number(nums[i]) + ","); // 转换为字符串,用逗号分隔}index++;if(index == LENGTH)index = 0; // 数组循环使用msg.append(strnums.toUtf8()); // 转换为UTF-8字节数组m_serialport->write(msg);
}
- 数据格式:CSV(逗号分隔值)字符串,如
16,12,9,8,6,34,20,22,20,16,12,9,
。 - 数据来源:
arrs
数组存储预设的心率波形数据点,通过循环和随机波动模拟真实心率变化。 - 采样频率:每 200ms 发送 12 个数据点,相当于每秒 60 个点(60Hz 采样率),符合心率监测需求。
呼吸数据发送逻辑
else if(event->timerId() == timeIdBreath)
{QByteArray message;float num1 = 140 + rand()%10 * 1.2; // 呼吸幅度 (140-151.9)float num2 = 20 + rand()%5 * 1.2; // 呼吸频率 (20-25.9)QString nums = QString::number(num1, 'f', 1) + "," + QString::number(num2, 'f', 1);message.append(nums.toUtf8()); // 转换为UTF-8字节数组m_serialport->write(message);
}
- 数据格式:两个浮点数组成的 CSV 字符串,如
145.2,22.8
。 - 含义:
num1
:呼吸幅度(模拟胸腔运动幅度)num2
:呼吸频率(次 / 分钟)
- 精度:保留 1 位小数,适合医疗监测场景。
关键技术细节
定时器管理:
- 通过
startTimer(interval)
启动定时器,返回唯一 ID。 - 通过
killTimer(id)
停止定时器(在串口关闭时调用)。
- 通过
数据传输优化:
- 血压数据用二进制格式(节省带宽,适合嵌入式设备)。
- 心率和呼吸数据用文本格式(可读性好,便于调试和解析)。
随机数生成:
- 使用
rand()
生成随机波动,但需注意:- 未调用
srand()
初始化随机种子,导致每次运行的随机序列相同。 - 建议在程序启动时调用
srand(time(NULL))
初始化(需包含<cstdlib>
和<ctime>
)。
- 未调用
- 使用
线程安全:
timerEvent()
在主线程执行,与 UI 操作同一线程,避免多线程冲突。- 串口写操作是同步的,但数据量小,不会阻塞 UI。
总之:
函数通过定时器机制实现了三种生理数据的周期性模拟发送:
- 血压:每 1 秒发送一次,二进制格式(3 字节)。
- 心率:每 200 毫秒发送一次,CSV 格式(12 个数据点)。
- 呼吸:每 1 秒发送一次,CSV 格式(两个浮点数)。
心电图模拟数据详解:
预设波形数组 arrs
int arrs[]={16,12,9,8,6,34,20,22,20,16,12,9,8,9,10,8,6,-1,2,-3,-7,-8 ,-7,-6,7,2,-1,1,9,9,10,8,6,-1,2,-3,-7,-8,-7,-6,-6,7,2,-1,1,9,-7,-8,-7,-6,7,2,-1,1,-1,2,-3,-7,-8 ,8,6,-7,2,-1,1,9,-101,-200,-120,-40,40,120,280,459,670,835,900,835,670,450,340,240,27,18,14,10,6};
- 数据结构:包含 87 个整数,代表一个完整 ECG 周期的采样点。
- 特征分析:
- QRS 波群:数组末尾部分(如
900,835,670
)是最高幅值区域,对应心室去极化(心脏收缩)。 - 基线与小波动:数组前半部分(如
16,12,9
)模拟基线和 P 波、T 波等小幅波动。
- QRS 波群:数组末尾部分(如
- 单位:数据单位为 微伏(μV),符合 ECG 信号的实际幅度范围。
3. 数据生成与发送逻辑
QByteArray msg;
QString strnums;
int nums[12];
for(int i=0; i<12; i++)
{nums[i] = arrs[index] + (unsigned)rand()%7; // 从预设数组取数据并添加随机扰动strnums.append(QString::number(nums[i]) + ","); // 转换为CSV格式
}
index++;
if(index == LENGTH)index = 0; // 数组循环使用msg.append(strnums.toUtf8()); // 转换为字节数组
m_serialport->write(msg); // 通过串口发送
关键步骤解析:
数据采样:
- 每次从
arrs
中取出 12 个连续点,组成一组数据。 index
作为数组索引,每次增加 1,遍历整个arrs
数组。- 当
index
达到数组长度(87)时,重置为 0,实现循环采样。
- 每次从
随机扰动:
(unsigned)rand()%7
生成 0-6 的随机数,添加到原始数据上。- 目的:模拟测量噪声和生理波动,使数据更接近真实信号。
数据格式:
- 将 12 个整数转换为 CSV 字符串(如
16,12,9,8,6,34,20,22,20,16,12,9,
)。 - 通过
toUtf8()
转换为字节数组后通过串口发送。
- 将 12 个整数转换为 CSV 字符串(如
发送频率:
- 定时器每 200 毫秒 触发一次(
startTimer(200)
),即每秒发送 5 组数据。 - 每组 12 个点,因此采样率为 60Hz(符合 ECG 监测的基本要求)。
- 定时器每 200 毫秒 触发一次(
4. 模拟效果分析
波形连续性:
- 每次发送 12 个点,相邻两组数据在
arrs
中是连续的,保证波形平滑。 - 例如:第一组取
arrs[0]~arrs[11]
,第二组取arrs[1]~arrs[12]
,依此类推。
- 每次发送 12 个点,相邻两组数据在
心率计算:
arrs
包含一个完整周期的波形,共 87 个点。- 每 200ms 发送 12 个点,遍历完整个
arrs
需要87/12 × 200ms ≈ 1.45秒
。 - 因此模拟心率约为
60/1.45 ≈ 41次/分钟
(略低于正常范围,可通过调整定时器间隔优化)。