一种新型轻型机械臂示教软件架构设计
2018-03-10王祺王堃张璇琛
王祺+王堃+张璇琛
摘 要:传统轻型六轴机械臂控制软件构架一般包括控制器、示教器、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();开始事件循环}endprint
下面是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< qDebug()<<"to server:"< if(socket->state()==QAbstractSocket::ConnectedState) {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< if(tcpsocket->state()==QAbstractSocket::ConnectedState) {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.