Linux+QT+SocketCAN:使用信号槽机制实现数据收发
最近在考虑采用面对对象的方式重新搭建机器人的主控程序框架,虽然之前的框架也是有这种思想在里面,但是总感觉还是有程序化编程的影子,很多地方的处理都不太理想,而且虽然之前也是一直在采用QT Creator作为IDE开发程序,但是实际上并没有怎么用QT的库,只是单纯的作为一个IDE来用,未免也是空守宝山而不入。
因此这次决定充分利用QT的库和信号槽机制来对程序框架进行重新构建。
0.开发环境
- HOST平台: Ubuntu16.04
- ARM平台:at91sam9x35
- QT版本: 5.13.1
1.分析
针对Linux上的CAN通信,Socket CAN提供了一种十分简单方便的解决方案,QT中并没有像QTcpSocket一样的库可以使用,因此仍然使用linux内核中提供的库。
考虑到电机等设备会循环向主控返回电机的状态信息,CAN的接收工作十分耗时,因此选择新建一个继承QObject的类,将其Moveto一个QThread中单独执行循环接收工作。
考虑到CAN和Motor对象均为Robot类的对象成员,而各个Motor对象需要调用CAN的发送接口向其他对象发送数据,为避免各对象成员之间的数据交互造成各种麻烦,因此在各对象成员中建立signal与slot,在Robot中进行connect。
eg:
Robot具有两个对象成员CAN和Motor
class Robot:public QObject
{
Q_OBJECT
public:
Robot();
~Robot();
...
CAN *mCAN;
Motor *Wheel_1;
...
}
CAN中构建数据发送槽
class CAN:public QObject
{
Q_OBJECT
...
public slots:
void Transmit(can_frame pFrame);
...
}
Motor中构建数据发送信号
class Motor:public QObject
{
Q_OBJECT
...
signals:
void sendCanMeg(can_frame pFrame);
...
}
Robot中将其连接
Robot::Robot()
{
...
mCAN = new CAN("can1");
Wheel_1= new Motor(WHEEL_1,SPEED_MODE)
connect(Wheel_1,&Motor::sendCanMeg,mCAN ,&CAN::Transmit);
...
}
2.源码
2.1 can.h
#ifndef CAN_H
#define CAN_H
#include <QObject>
#include <QThread>
#include <QtCore>
#include <fcntl.h> /*文件控制定义*/
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <Utilities.h>
#include <QDebug>
#include <unistd.h>
// CAN接收线程
class CAN_RcvThread :public QObject
{
Q_OBJECT
public:
CAN_RcvThread(QObject *parent = 0) ;
public slots:
void RcvMegThread(int s);
void stopRcvThread();
private:
bool isStop=false;
};
class CAN:public QObject
{
Q_OBJECT
public:
CAN(const char * CANx);
~CAN();
int Receive(can_frame *pFrame);
void startRcv();
void stopRcv();
private:
CAN_RcvThread *mRcvThread;
QThread *mQThread;
int s;
const char * mCAN;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_filter *rfilter;
u8 CAN_FILTER_NUM = 1; // 注意:定义了过滤器数组之后必须全部赋值 否则会收到所有的报文
signals:
void startRcvThread(int);
void stopRcvThread();
public slots:
void Transmit(can_frame pFrame);
};
#endif // CAN_H
2.2 can.cpp
#include "can.h"
/**
* @brief CAN对象构造函数
* @param CAN设备名
* can0 or can1
* @note 默认比特率为1M
* 默认滤波器数量为1
* 默认报文读取模式为快速响应模式
* 默认报文过滤规则为不接受所有报文
*/
CAN::CAN(const char * CANx)
{
mCAN = CANx;
Bitrate = R1M;
if(!strcmp(mCAN, "can0"))
{
system("ifconfig can0 down"); //close can0
system("ip link set can0 type can bitrate 1000000"); //set can0 bitrate 1M
system("ifconfig can0 up"); //open can0
}
else if(!strcmp(mCAN,"can1"))
{
system("ifconfig can1 down"); //close can0
system("ip link set can1 type can bitrate 1000000"); //set can0 bitrate 1M
system("ifconfig can1 up"); //open can0
}
else
{
qDebug() << "Input CANx Error , Please Input can0 or can1 for the 9x35";
}
CAN_FILTER_NUM = 1; // 初始化CAN过滤器数量
rfilter = (struct can_filter*)malloc((sizeof(rfilter) * CAN_FILTER_NUM));
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//创建套接字
strcpy(ifr.ifr_name,mCAN);
//fcntl(s, F_SETFL, ReadMode); //标志FNDELAY可以保证read函数在端口上读不到字符的时候返回0
fcntl(s, F_SETFL, 0); //回到正常(阻塞)模式
ioctl(s, SIOCGIFINDEX, &ifr); //指定 CANx 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));// 将套接字与 CANx 绑定
//设置过滤器0 接收任何数据
rfilter[0].can_id = 0;
rfilter[0].can_mask = 0;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
mQThread = new QThread(this);
mRcvThread = new CAN_RcvThread;
mRcvThread->moveToThread(mQThread);
connect(mQThread, &QThread::finished, mRcvThread, &QObject::deleteLater);
connect(mQThread, &QThread::finished, mQThread, &QThread::deleteLater);
connect(this, SIGNAL(startRcvThread(int)), mRcvThread, SLOT(RcvMegThread(int)));
connect(this, SIGNAL(stopRcvThread()), mRcvThread, SLOT(stopRcvThread()));
mQThread->start();
}
CAN::~CAN() {
if(mQThread)
mQThread->quit();
mQThread->wait();
}
/**
* @brief 接收CAN报文
* @param 用于接收的报文
* @return 接收到的数据的长度
*/
int CAN::Receive(can_frame *Rx_Message)
{
return read(s, Rx_Message, sizeof(can_frame));
}
/**
* @brief 发送CAN报文
* @param pFrame 用于发送的报文
*/
void CAN::Transmit(can_frame pFrame)
{
int nbytes = write(s, &pFrame, sizeof(can_frame)); //发送数据
if(nbytes != sizeof(can_frame)){ //如果 nbytes 不等于帧长度,就说明发送失败
qDebug() << mCAN << " Send Error! ";
}
}
void CAN::startRcv()
{
emit startRcvThread(s);
}
void CAN::stopRcv()
{
emit stopRcvThread();
}
CAN_RcvThread::CAN_RcvThread(QObject *parent )
{
}
void CAN_RcvThread::RcvMegThread(int s)
{
qDebug()<<"CAN_RcvThread is begin"<<QThread::currentThread();
can_frame *rx_msg = new can_frame();
int byte;
while(!isStop)
{
qDebug()<<"CAN_RcvThread is run";
byte=read(s,rx_msg,sizeof (can_frame));
qDebug()<<byte;
if(byte>0)
{
qDebug()<<hex<<rx_msg->can_id;
qDebug()<<hex<<rx_msg->can_dlc;
qDebug()<<hex<<rx_msg->data[0];
qDebug()<<hex<<rx_msg->data[1];
qDebug()<<hex<<rx_msg->data[2];
}
else
{
perror("read:");
}
}
}
void CAN_RcvThread::stopRcvThread()
{
isStop = true;
}
2.3 motor.h
#ifndef MOTOR_H
#define MOTOR_H
#include "driver.h"
class Motor:public QObject
{
Q_OBJECT
public:
Motor(u8 id,u8 mode);
void initMotor();
private:
u8 motor_mode;
u8 motor_id;
signals:
void sendCANMsg(can_frame Tx_Msg);
};
#endif // MOTOR_H
2.3 motor.cpp
#include "motor.h"
Motor::Motor(u8 id ,u8 mode)
{
motor_id = id;
motor_mode = mode;
}
void Motor::initMotor()
{
can_frame frame;
memset(&frame, 0, sizeof(can_frame));
frame.can_id = 0;
frame.can_dlc = 8;
frame.data[0] = 1;
for(int i = 1; i < 8; i++){
frame.data[i] = 0;
}
emit sendCANMsg(frame);
}
2.4 robot.h
#ifndef ROBOT_H
#define ROBOT_H
#include "can.h"
#include "motor.h"
class Robot:public QObject
{
Q_OBJECT
public:
Robot();
public:
CAN *mCAN;
Motor *Wheel_1;
};
#endif // ROBOT_H
2.5 robot.cpp
#include "robot.h"
Robot::Robot()
{
mCAN = new CAN("can1");
mCAN->startRcv(); //开启接收线程
Wheel_1 = new Motor(3,SPEED_MODE);
//将各电机的发送信号与CAN发送槽连接
connect(Wheel_1,SIGNAL(sendCANMsg(can_frame)),mCAN,SLOT(Transmit(can_frame)));
//初始化电机,由于初始化的过程中需要发送报文,因此需要在电机与CAN建立连接之后才能调用
Wheel_1->initMotor();
}
评论(0)
您还未登录,请登录后发表或查看评论