Linux+QT+SocketCAN:使用信号槽机制实现数据收发

最近在考虑采用面对对象的方式重新搭建机器人的主控程序框架,虽然之前的框架也是有这种思想在里面,但是总感觉还是有程序化编程的影子,很多地方的处理都不太理想,而且虽然之前也是一直在采用QT Creator作为IDE开发程序,但是实际上并没有怎么用QT的库,只是单纯的作为一个IDE来用,未免也是空守宝山而不入。
因此这次决定充分利用QT的库和信号槽机制来对程序框架进行重新构建。

0.开发环境

  1. HOST平台: Ubuntu16.04
  2. ARM平台:at91sam9x35
  3. 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();
}