模拟心电图采样数据
定时发送数据: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次/分钟(略低于正常范围,可通过调整定时器间隔优化)。
 
效果测试图:

