VS2015+QT 多线程

场景:

1、上位机实现曲线和实时数据显示

2、通讯模块负责上位机与设备之间的数据 控制 状态等数据处理转发

需求:

1、上位机实现硬件测试

2、上位机创建试验,实时发送设备控制、温度设置、设备加热、三通阀控制、压缩机控制、循环泵控制等控制

3、上位机实现温度表PID整定

4、上位机实现下位机的电机控制

5、通讯模块可分布式部署

网络通讯:

1、上位机和通讯模块 采用socket 网络通讯;

2、通讯模块与设备PLC采用sna7协议通讯;

技术瓶颈

电机控制需要密集发送命令,因此通讯模块单独的线程无法满足需求;

解决方案:

通讯模块采用多线程方式解决

1、心跳线程

PLC需要通讯模块定时发送心跳,防止异常断连的情况,设备继续加热或制冷等

2、数据读取线程

为了防止数据获取的实时性的问题,需要单独的线程运行;

3、弹模测试电机控制线程

实验过程中,会频繁出现密集的发送电机移动命令,为了不影响其实时性,需要单独一个线程运行;

4、设备启停状态设置线程

主要设备的根据试验阶段的控制命令,需要实时的通知设备,进行及时状态变化,非常关键的一个控制,也不能因其他原因,卡顿等;

各自独立运行,互不影响,保证运行的时效性

线程创建过程:

1、class CSnap7Base : public QThread

该线程,主要负责与PLC的连接,管理读写PLC的主要线程,网络连接正常的情况,会启动线程函数run()

run函数中,会启动其他线程,常驻后台,根据状态机决定是否控制和空闲状态;

当出现网络异常需要自动重连,该线程会主动销毁的时候,其他线程也会同步销毁;自动成功之后,所有的线程在重新创建;

线程通过共享一个读写锁控制对PLC设备的底层操作;

2、class MotorCmdThread : public QThread

弹模控制线程

3、class HeartbeatThread : public QThread

心跳线程

4、class CtrlCmdThread : public QThread

控制命令线程

void CSnap7Base::run()

{

//启动独立电机线程,常驻后台

startMotorCmdThread(); //弹模线程

startHeartbeatThread();//心跳线程

startCtrlCmdThread(); //控制线程

while (!m_endThreadFlag)

{

。。。。。。。。

}

// ★ 退出前,停止线程

stopMotorCmdThread();

stopHeartbeatThread();

stopMotorCmdThread();

}

CtrlCmdThread.h

#ifndef CTRLCMDTHREAD_H
#define CTRLCMDTHREAD_H

#include <QThread>

class CSnap7Base;

class CtrlCmdThread : public QThread
{
public:
explicit CtrlCmdThread(CSnap7Base* owner, QObject* parent = nullptr);

protected:
void run() override;

private:
CSnap7Base* m_owner = nullptr;
};

#endif // CTRLCMDTHREAD_H

#include "CtrlCmdThread.h"

#include "Snap7Base.h"

CtrlCmdThread::CtrlCmdThread(CSnap7Base* owner, QObject* parent)

: QThread(parent)

, m_owner(owner)

{

}

void CtrlCmdThread::run()

{

if (!m_owner)

return;

try

{

m_owner->ctrlCmdThreadFunc();

}

catch (...)

{

}

}

业务处理函数:ctrlCmdThreadFunc(); 在主线程中处理,通过指针调用实现;heartbeatThreadFunc();

motorCmdThreadFunc();

业务说明:

例如上位机下发电机控制命令: 正向移动 反向移动 停止 弹模结束

1、上位机下发的命令,保存内存中,不直接操作设备,否则会出现堵塞(上位机下发的命令周期短,PLC命令执行完成的周期长)

例如:其中 ST_MOTOR_CTRL_CMD 有上位机下发的动作参数:方向 移动量和速度

void CSnap7Base::OnMessageReceivedMotorCmd(const ST_MOTOR_CTRL_CMD & data)

{

//m_iMotorMoveType: 0:停止 1:正向运动 2:反向运动

QMutexLocker locker(&m_motorCmdMutex);

//将 int 强转为枚举类型,方便阅读

MotorMoveType moveType = static_cast<MotorMoveType>(data.m_iMotorMoveType);

switch (moveType)

{

case MOTOR_ESTOP: // 3: 急停

m_bEstopPending = true; // 急停抢占

m_bHasLatestMove = false; // 丢弃未执行的普通移动

break;

case MOTOR_MOLD_DONE: // 4: 完成弹模

// 清空待处理命令,清空急停,回到空闲状态

m_bHasLatestMove = false;

m_bEstopPending = false;

// 如果此时电机序列走到一半,强制打断回到 IDLE(或者你想等它走完就不断言)

m_motorState = M_IDLE;

break;

case MOTOR_FORWARD: // 1: 正向运动

case MOTOR_REVERSE: // 2: 反向运动

case MOTOR_STOP: // 0: 停止 (如果停止也要发给PLC的话)

// 存入最新指令,等待线程执行

m_latestMove = data;

m_bHasLatestMove = true;

break;

default:

break;

}

}

线程函数:

//电机线程主循环 (上面示例不是这个线程函数,没关系都类似)

void CSnap7Base::motorCmdThreadFunc()

{

while (true)

{

// 1) 线程退出检测

{

QMutexLocker locker(&m_motorCmdMutex);

if (m_motorThreadStop) return;

}

qint64 now = QDateTime::currentMSecsSinceEpoch();

// 2) 急停抢占

{

QMutexLocker locker(&m_motorCmdMutex);

if (m_bEstopPending && !isEstopState(m_motorState))

{

m_bEstopPending = false;

m_bHasLatestMove = false;

m_motorState = M_ESTOP_DISABLE;

m_motorNextMs = now;

}

}

// 3) 空闲状态(空转)

if (m_motorState == M_IDLE)

{

QMutexLocker locker(&m_motorCmdMutex);

if (m_bHasLatestMove)

{

// 收到移动指令,启动完整序列

m_runningCmd = m_latestMove;

m_bHasLatestMove = false;

m_motorState = M_MOVE_ALARM1;

m_motorNextMs = now;

}

else

{

// 没有指令,释放锁,睡 10ms 然后继续空转等待

QThread::msleep(10);

continue;

}

}

// 4) 未到下一步时间,按时间片睡眠

if (now < m_motorNextMs)

{

qint64 waitMs = m_motorNextMs - now;

if (waitMs > 10) waitMs = 10;

QThread::msleep((unsigned long)waitMs);

continue;

}

// 5) 执行一步(持短锁,不持锁 sleep)

bool bConnected = false;

{

QWriteLocker locker(&m_rwMutex);

bConnected = m_bConnected;

if (bConnected)

{

execOneMotorStep();

}

else

{

m_motorState = M_IDLE; // 掉线直接放弃

}

}

if (!bConnected) continue;

// 6) 安排下一次时间

if (m_motorState == M_DONE)

{

//完整的移动序列走完,变成空闲,等待下一次指令下发

m_motorState = M_IDLE;

}

else

{

m_motorNextMs = QDateTime::currentMSecsSinceEpoch() + 100;

}

}

}

//单步执行

void CSnap7Base::execOneMotorStep()

{

// 调用前提:已持有 m_rwMutex,且 m_bConnected == true

switch (m_motorState)

{

// ============ 急停序列 ============

case M_ESTOP_DISABLE:

m_SnapWriter->writeStepAxisDisable(1);

m_motorState = M_ESTOP_POS0;

break;

case M_ESTOP_POS0:

m_SnapWriter->writeStepRelativePos(0);

m_motorState = M_ESTOP_SPEED0;

break;

case M_ESTOP_SPEED0:

m_SnapWriter->writeStepRelativeSpeed(0);

m_motorState = M_ESTOP_ALARM1;

break;

case M_ESTOP_ALARM1:

m_SnapWriter->writeStepAlarmReset(1);

m_motorState = M_ESTOP_ALARM0;

break;

case M_ESTOP_ALARM0:

m_SnapWriter->writeStepAlarmReset(0);

m_motorState = M_DONE;

break;

// ============ 普通移动序列 ============

case M_MOVE_ALARM1:

m_SnapWriter->writeStepAlarmReset(1);

m_motorState = M_MOVE_ALARM0;

break;

case M_MOVE_ALARM0:

m_SnapWriter->writeStepAlarmReset(0);

m_motorState = M_MOVE_DISABLE0;

break;

case M_MOVE_DISABLE0:

m_SnapWriter->writeStepAxisDisable(0);

m_motorState = M_MOVE_ENABLE1;

break;

case M_MOVE_ENABLE1:

m_SnapWriter->writeStepAxisEnable(1);

m_motorState = M_MOVE_POS;

break;

case M_MOVE_POS:

m_SnapWriter->writeStepRelativePos(m_runningCmd.m_dMotorMoveValue);

m_motorState = M_MOVE_SPEED;

break;

case M_MOVE_SPEED:

m_SnapWriter->writeStepRelativeSpeed(30);

m_motorState = M_MOVE_CMD0;

break;

case M_MOVE_CMD0:

m_SnapWriter->writeStepRelativeCmd(0);

m_motorState = M_MOVE_CMD1;

break;

case M_MOVE_CMD1:

m_SnapWriter->writeStepRelativeCmd(1);

m_motorState = M_DONE;

break;

default:

m_motorState = M_IDLE;

break;

}

}

//启动

void CSnap7Base::startMotorCmdThread()

{

{

QMutexLocker locker(&m_motorCmdMutex);

m_motorThreadStop = false;

}

if (!m_motorThread)

{

m_motorThread = new MotorCmdThread(this);

m_motorThread->start();

}

}

//停止

void CSnap7Base::stopMotorCmdThread()

{

{

QMutexLocker locker(&m_motorCmdMutex);

m_motorThreadStop = false;

}

if (!m_motorThread)

{

m_motorThread = new MotorCmdThread(this);

m_motorThread->start();

}

}

总结扩展的步骤:

1、创建一个单独的线程;

2、该线程的所有函数在主线程类内部实现;

主要:通知处理函数、线程主函数、线程启动 线程停止和其他的中间过程函数。

3、扩展的线程类,在构造的时候,已经把主线程类的指针传过去,这样,所有的线程都可以使用一个指针,操作PLC设备。

相关推荐
码匠许师傅1 小时前
【C++三方组件】mbedTLS:面向嵌入式的轻量 TLS
开发语言·c++
迪菲赫尔曼2 小时前
【基础篇 12】把环境与源码接起来:检查 GPU,再安装服务器上的项目
运维·服务器·开发语言·人工智能·python·yolo·目标检测
2601_962885722 小时前
2026 Python 股票数据接口选型指南(横评 + 决策树)
开发语言·python·区块链
Evand J3 小时前
【PMSM例程4】PMSM电阻辨识与模型补偿EKF,MATLAB例程。附下载链接,包运行成功
开发语言·matlab·电机·ekf·卡尔曼·pmsm
王仔小奶3 小时前
Rust 正在接管金融圈?深度拆解一份高薪 FinTech 岗位 JD
开发语言·金融·rust
小王C语言3 小时前
Qt常用控件:多元素控件,纵向列表,表格控件,树状控件
开发语言·qt
fanruitian3 小时前
python 可视化 panda seaborn matplotlib.pyplot
开发语言·python·matplotlib
jnrjian3 小时前
delete file shell 让 AI 写脚本
服务器·开发语言
huailiang20103 小时前
QSG 与 QRhi:把一个 PBR 渲染器搬进 QML 渲染栈
qt·pbr·qrhi