标题 | 一种新型轻型机械臂示教软件架构设计 |
范文 | 王祺+王堃+张璇琛 摘 要:传统轻型六轴机械臂控制软件构架一般包括控制器、示教器、canopen通讯等部分。传统控制器是一个程序,机械臂动作參数设定时,一个动作信号需要一组控制器參数,大量的数据收发常常引发主线程与其它线程争夺资源而出现死锁,导致主线程不能继续往下执行,出现卡死。对此,使用Qt软件及C++语言,开发了一款新型六轴机械臂控制软件。采用TCP/IP通讯实现程序间通讯,多线程提高单个程序效率,以QTcpSocket类进行网络编程。通过控制轻型六轴机械臂运动实验,证明此控制软件有效、稳定,能解决界面卡死问题,具有良好的可扩展性与可移植性,界面友好,运行流畅。 关键词:TCP/IP通讯;图形界面卡死;QTcpSocket DOIDOI:10.11907/rjdk.172360 中图分类号:TP319 文献标识码:A 文章编号:1672-7800(2018)002-0124-04 0 引言 图1是六轴轻型机械臂控制系统。控制软件安装在控制器里,示教器是控制器外的触屏。控制器和示教器连在一起是低配的平板电脑,运算和储存要求不高。PCAN又叫做PCAN-USB,是一个CAN转USB接口,通过它可以将CAN网络上的报文通过USB接口传输到PC上,通过相关软件查看CAN报文。PCAN的另一端连接控制器CAN卡,CAN卡与六轴机械臂相连。使用Qt编写程序,语言为C++。 1 界面卡死原因 “界面卡死”是计算机系统由于过量的进程资源消耗,使图形界面进程受到影响的现象。控制程序较为复杂的指令有发送和接收报文、进行运动轨迹规划等。用户通过示教器的图形界面发出指令,在进行稍微复杂的处理时就会有延迟,使得界面(GUI)卡死。对此进行改进,将控制器的程序拆分为两个,如图2所示。一个程序是用户界面程序(GUI),称为RH-LBR,负责收集用户指令,另一个程序Communication_APP专门负责收集下位机发来的报文,以及通过GUI指令向下位机发送指令。这样耗时的处理都由Communication_APP来处理,用户交互界面RH-LBR不会被卡死。两个程序之间的通讯模式为TCP/IP。 2 建立TCP通讯 下面分别介绍RH_LBR和Communication_APP这两个程序里负责通讯的类。CIRT_LBR_GUI类定义RH_LBR程序的GUI,有信号与槽函数和ControllerSocket类互通消息。ControllerSocket类定义TCP里的用户端类。Communication_APP程序里有TcpTransaction类,主要定义TCP里的服务器端,见图3。 在RH_LBR程序的ControllerSocket类中,重要函数如下:①void ControllerSocket::connectToController()建立TCP连接;②void ControllerSocket::readMessage()接收Communication_APP这个程序发来的信息,会有对应的sendMessage函数在Communication_APP程序里;③void ControllerSocket::writeBytes(const QString & Message)传输信息,使Communication_APP可以接收到信息。 在Communication_APP程序的TcpTransaction类中,重要函数有:①void TcpTransaction::sessionOpened()。TCP通信的网络配置槽函数;②void TcpTransaction::readMessage()。获取用户程序发送的全部报文,并解析后通过信号发送给子线程:HS_Interface;③void TcpTransaction::sendMessage(const QString & Message)。通过本函数将需要发送ControllerSocket类的信息发送出去。 2.1 RH_LBR用户界面程序兩个主要类 RH_LBR程序里有两个主要类:CIRT_LBR_GUI和ControllerSocket类。 在CIRT_LBR_GUI类中用信号与槽函数调用ControllerSocket类中的startTCPConnection()函数,建立TCP连接。 void CIRT_LBR_GUI::initTCPConnection() { 开始新建socket的线程和socket的对象 TCPConnectionThread=new QThread; controllerSocket=new ControllerSocket; controllerSocket->moveToThread(TCPConnectionThread); 下一行代码表示用GUI界面的信号函数触发ControllerSocket类的TCP连接函数: connect(this,SIGNAL(startTCPConnection()),controllerSocket,SLOT(startTCPConnection())); 下一行代码表示ControllerSocket类的TCP连接结果反馈给GUI界面: connect(controllerSocket,SIGNAL(socketConnectionResult(bool)),this,SLOT(getSocketConnectionResult(bool))); TCPConnectionThread->start();开始事件循环} 下面是ControllerSocket类中定义的一些参数和槽函数。 QString ControllerSocket::hostName="127.0.0.1";TCP主机名,不是实际的,可自行设定 int ControllerSocket::portNo=30001;TCP端口名 QTcpSocket*socket; QDataStream dataInputStream; ControllerSocket::ControllerSocket(QObject*parent):QObject(parent) {socket=new QTcpSocket(this);新建socket connect(socket,SIGNAL(connected()),this,SLOT(onConnected())); connect(socket,SIGNAL(disconnected()),this,SLOT(onDisconnected())); connect(socket,SIGNAL(readyRead()),this,SLOT(readMessage()));读取socket发来的信息 } void ControllerSocket::startTCPConnection() {connectToController();} void ControllerSocket::connectToController() {socket->connectToHost(hostName,portNo); if(!socket->waitForConnected()) {qDebug()<<"can not connect to controller"; return;} dataInputStream.setDevice(socket); dataInputStream.setVersion(QDataStream::Qt_4_0);} 下面的readMessage()函数表示接收Communication_APP这个程序发来的信息,会有对应的sendMessage函数在Communication_APP程序里。 void ControllerSocket::readMessage() {std::vector bool committransaction=true; while (committransaction && socket->bytesAvailable()>0){ dataInputStream.startTransaction(); QString message; dataInputStream>>message; committransaction=dataInputStream.commitTransaction(); if(committransaction) {messages.push_back(message); parseMessage(message);这个函数表示消息格式识别,具体代码省略,这个函数会发送Q_EMIT信号函数给CIRT_LBR_GUI类}}} void ControllerSocket::writeBytes(const QString & Message) 这个writeBytes函数传输信息,使得Communication_APP程序可以接收到信息: {QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out< {socket->write(block); socket->flush();}} 2.2 Communication_APP TCP通訊服务器端程序 Communication_APP程序最重要是TcpTransaction类,下面介绍如何建立TCP通讯和信息传递。 QTcpServer*tcpServer(tcp通信的服务器);QTcpSocket*tcpsocket(tcp通信的socket); QDataStream in;用于和驱动器通信的子线程; HardSoft_Interface*HS_Interface;这是和硬件连接的类,负责向下位机发送报文,不详细介绍。 QThread HS_Thread;管理HS_interface qthread类 TcpTransaction::TcpTransaction(QWidget*parent):QDialog(parent),statusLabel(new QLabel),tcpServer(Q_NULLPTR),HS_Interface(new HardSoft_Interface()),HS_Thread(this) {sessionOpened();TCP通信的网络配置槽函数,具体代码如下: HS_Interface->moveToThread(&HS_Thread);将HS_Interface移动到子线程 将信号与槽进行连接 QPushButton*quitButton=new QPushButton(tr("Quit")); quitButton->setAutoDefault(false); connect(quitButton,&QAbstractButton::clicked,this,&QWidget::close); 注意Initial函數表示每当一个新的客户端连接上服务器后,不管前面的客户端是否退出,应该delete之前的tcpsocket,而不只是修改服务器的tcpsocket指针指向: connect(tcpServer,&QTcpServer::newConnection,this,&TcpTransaction::Initial); connect(quitButton,&QAbstractButton::clicked,HS_Interface,&HardSoft_Interface::Quit); onnect(HS_Interface,&HardSoft_Interface::Exit,this,&TcpTransaction::ExitHsInterface); connect(&HS_Thread,&QThread::finished,this,&QWidget::close);HS_interface一旦退出,服务器也必须退出,页面布局代码忽略} void TcpTransaction::sessionOpened() {tcpsocket=Q_NULLPTR; tcpServer=new QTcpServer(this); QString testipaddress("127.0.0.1");非实际值,只是示例 int port=30001; if(!tcpServer->listen(QHostAddress(testipaddress),port)){listen函数 QMessageBox::critical(this,tr("Communication Server"), tr("Unable to start the server:%1.") .arg(tcpServer->errorString())); close(); return;}} Initial函数步骤:①如果有客户连接到服务器,则delete以前的服务器tcpsocket,然后获取新的客户tcp指针;②连接上客户端后,将readyread信号和readmessage槽函数进行连接(见下面部分代码);③将用户指令通过信号与槽和HS_interface进行连接;④开启HS_INTERFACE线程。 void TcpTransaction::Initial() {如果客户端退出,新客户端连接到服务器,若原来的tcpsocket不被销毁,可能会导致内存泄漏,所以删除之前的tcpsocket if(tcpsocket) delete tcpsocket; tcpsocket=tcpServer->nextPendingConnection(); connect(tcpsocket,&QIODevice::readyRead,this,&TcpTransaction::readMessage); in.setDevice(tcpsocket);将DataStream和当前的tcpsocket绑定 in.setVersion(QDataStream::Qt_4_0);设置DataStream的版本 将HS_interface发来的消息通过本线程发送给用户APP,sendMessage详细代码: connect(HS_Interface,SIGNAL(SendMessage(QString)),this,SLOT(sendMessage(QString))); 将所有用户发来的指令解析后发送给子线程:HS_Interface,由HS_Interface经过Pcan发送给can总线,从而和驱动器通信。 connect(this,SIGNAL(InitRobot()),HS_Interface,SLOT(start()));初始化机器人 connect(this,SIGNAL(SetJointVel(const int&,const double&)),HS_Interface,SLOT(SetJointVel(const int&,const double&)));等等,不一一列举。 HS_Thread.start();}开启子线程 下面的readMessage函数获取用户程序发送的全部报文,解析后通过信号发送给子线程:HS_Interface void TcpTransaction::readMessage() { std::vector bool committransaction=true; while(committransaction && tcpsocket->bytesAvailable()>0){ in.startTransaction(); QString message; in>>message; committransaction=in.commitTransaction(); if(committransaction){ messages.push_back(message); int TcpExceptionCode; MsgData messageData=parseMessage(message,TcpExceptionCode); 檢查TCP通信获得的字符串是否存在异常: switch(TcpExceptionCode){…… switch(messageData.type){…… }}}} 通过sendMessage函数将需要发送的信息发送出去: void TcpTransaction::sendMessage(const QString&Message) { QByteArray block; QDataStream out(&block,QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out< {tcpsocket->write(block); tcpsocket->flush();}} 3 软件架构改进 通过以上步骤,将耗时的程序以及与下位机通讯的程序都转移为GUI界面卡死问题。pcan与can卡之间通讯不稳定,有很多超时现象,软件架构改进方向是:控制器和can卡采用TCP直接通讯,不再借用pcan转换,使控制系统更加稳定,见图4。通过控制轻型六轴机械臂运动,证明此软件有效,解决了界面卡死问题。 参考文献: [1] 谢希仁.计算机网络教程[M].北京:人民邮电出版社,2002. [2] DOUGLAS E, COMER.Internetworking With TCP/IP[Z].2001. [3] 凌俊峰.TCP/IP协议浅释[J].韶关学院学报,2001(9):138-142. [4] 张延双,张建标,王全民.TCP/IP协议分析及应用[M].北京:机械工业出版社,2007. [5] BRUCE ECKEL.Think in C++[M].刘宗田,译.北京:机械工业出版社,2000. [6] JASMINBLANCHETTE, MARKSUMMERFIELD. C++GUIQt4编程[M].第2版.闫锋欣,译.北京:电子工业出版社,2008. [7] 霍亚飞.QT Creator快速入门[M].北京:北京航空航天大学出版社,2012. [8] 黄维通.面向对象程序设计与QT程序设计入门[M].北京:北京航空航天大学出版社,2010. [9] JIM BEVERIDGE, ROBERT WIENER,侯捷.Win32多线程序设计[M].武汉:华中科技大学出版社,2002. [10] 清山博客.使用SOCKET实现TCP/IP协议的通讯[EB/OL].http://blog.csdn.net/a497785609/article/details/12871301. [11] STANLEY B. LIPPMAN. C++Primer[M].北京:人民邮电出版社,2006. [12] 李宋琛.Linux面向对象窗口高级编程[M].北京:科学出版社,2001. [13] 罗亚非.基于TCP的Socket多线程通信[J].电脑知识与技术,2009(2):36-39. |
随便看 |
|
科学优质学术资源、百科知识分享平台,免费提供知识科普、生活经验分享、中外学术论文、各类范文、学术文献、教学资料、学术期刊、会议、报纸、杂志、工具书等各类资源检索、在线阅读和软件app下载服务。