APP下载

一种新型轻型机械臂示教软件架构设计

2018-03-10王祺王堃张璇琛

软件导刊 2018年2期

王祺+王堃+张璇琛

摘 要:传统轻型六轴机械臂控制软件构架一般包括控制器、示教器、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::vectormessages;

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::vectormessages;

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.