一、前言
芯片使用mcp2515,使用spi转can的方式,配置好linux中的设备树和驱动文件后,在can一切正常的情况下才能继续下面的工作,前期我这边也就是调试出驱动,总共调试了两路can,第一路用spi1,第二路用spi2,调试第二路有些不顺利是因为spi2默认引脚电平是1.8v,后来重画电路板加了一个电平转换芯片解决问题,从此就可以驱动两路can啦。此贴是我调试2路spi方式
二、环境
开发环境:window10,ubuntu16.04
目标arm环境:A40i,linux3.10,qt5.9
三、正文
话不多说,正文开始,捞干的说就是在已经调试好can0、can1的设备节点后,在qt上写相应的程序去驱动,官方手册自带的测试方法仅仅能够用于测试,无法用于程序开发,这里介绍在qt5.9上做出can读取和发送数据,使用标准帧或扩展帧两种方式,目前使用最高频率是500k,已经买了24M晶振,准备提升频率到1M。
更新:
买的24M晶振到了,换上之后,修改一下设备树的时钟为24000000,测试暂时没发现什么问题,但是有些丢帧,建议使用速率不是必须达到500k以上时,还是用8M晶振,必须1M速率时容易丢帧
clocks {
mcp251x_clock:mcp251x_clock{
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
};
};
(福利)本文不做任何保留,全部代码如下,工程文件我就不上传了,没多少东西,都是干的,可在此基础上继续开发。
驱动节点信息
pro文件
QT += core gui serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = socketcan
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
DEPENDPATH += .
INCLUDEPATH += .
# Input
HEADERS += \
mainwindow.h \
thread.h
FORMS += \
mainwindow.ui
SOURCES += \
main.cpp \
mainwindow.cpp \
thread.cpp
CONFIG += mobility
MOBILITY =
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
startcan(0);//开启CAN0
QTimer *time1=new QTimer();
time1->start(100);
connect(time1,&QTimer::timeout,[=](){
on_send_clicked();
});
}
MainWindow::~MainWindow()
{
delete ui;
stopcan();//程序退出,关闭当前开启的CAN
}
//开启can0/1
void MainWindow::startcan(int v)
{
if(v == 0){
system("ifconfig can0 down");
system("ip link set can0 up type can bitrate 500000 triple-sampling on");
system("ifconfig can0 up");
}
else{
system("ifconfig can1 down");
system("ip link set can1 up type can bitrate 500000 triple-sampling on");
system("ifconfig can1 up");
}
//创建套接字
//PF_CAN 为域位 同网络编程中的AF_INET 即ipv4协议
//SOCK_RAW使用的协议类型 SOCK_RAW表示原始套接字 报文头由自己创建
//CAN_RAW为使用的具体协议 为can总线协议
socket = ::socket(PF_CAN,SOCK_RAW,CAN_RAW);//创建套接字
struct ifreq ifr;//接口请求结构体
strcpy((char *)(ifr.ifr_name),v == 0 ? "can0" : "can1");//判断开启的是can0/1
//fcntl(socket, F_SETFL, ReadMode); //标志FNDELAY可以保证read函数在端口上读不到字符的时候返回0
fcntl(socket, F_SETFL, 0); //回到正常(阻塞)模式
ioctl(socket,SIOCGIFINDEX,&ifr);//指定 CAN0/1 设备
addr.can_family = AF_CAN;//协议类型
addr.can_ifindex = ifr.ifr_ifindex;//can总线外设的具体索引 类似 ip地址
bind(socket,(struct sockaddr*)&addr,sizeof(addr));//将套接字和canbus外设进行绑定,即套接字与 can0/1 绑定
//禁用过滤规则,进程不接收报文,只负责发送,如需接受注释掉此函数即可
//setsockopt(stSocket_LO, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
t = NULL;
t = new Thread(socket);//开启单独线程接受监听
connect(t,SIGNAL(message(sockaddr,socklen_t)),this,SLOT(msg(sockaddr,socklen_t)));
t->start();
}
void MainWindow::stopcan()
{
if(t){//如果线程已经开启,关闭线程
t->stop();
t->deleteLater();
}
::close(socket);
system("ifconfig can0 down");//关闭CAN0
system("ifconfig can1 down");//关闭CAN1
}
void MainWindow::on_send_clicked()
{
struct can_frame frame;
memset(&frame,0,sizeof(struct can_frame));
frame.can_id = (0x123456 & CAN_EFF_MASK) | CAN_EFF_FLAG;//扩展帧
//frame.can_id = (0x123456 & CAN_SFF_MASK);//标准帧
frame.can_dlc= 8;
frame.data[0]= 0x11;
frame.data[1]= 0x22;
frame.data[2]= 0x33;
frame.data[3]= 0x44;
frame.data[4]= 0xaa;
frame.data[5]= 0xbb;
frame.data[6]= 0xcc;
frame.data[7]= 0xdd;
//发送can数据:方式一
//sendto(socket,&frame,sizeof(struct can_frame),0,(struct sockaddr*)&addr,sizeof(addr));
//发送can数据:方式二
write(socket, &frame, sizeof(frame)); //发送 frame
return;
}
void MainWindow::msg(sockaddr addr,socklen_t num)
{
//qDebug()<<addr<<num<<buf;
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QProcess>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QThread>
#include <QTimer>
#include <QDateTime>
#include <QDebug>
#include "thread.h"
extern "C" {
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
}
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
private slots:
void on_send_clicked();
void msg(sockaddr addr, socklen_t num);
void stopcan();
void startcan(int v);
private:
Ui::MainWindow *ui;
int socket;
struct sockaddr_can addr;//can总线的地址 同socket编程里面的 socketaddr结构体 用来设置can外设的信息
Thread *t;
};
#endif // MAINWINDOW_H
thread.cpp
#include "thread.h"
#include "mainwindow.h"
Thread::Thread(int s,QObject *parent) :
QThread(parent)
{
socket = s;
running = true;
}
void Thread::run()
{
//qDebug()<<"start can receive Thread!";
int nbytes;
int len;
struct can_frame frame;
struct sockaddr_can addr;
char buf[8];
while(running){
//接收can数据:方式一
//nbytes=recvfrom(socket,&frame,sizeof(struct can_frame),0,(struct sockaddr *)&addr,(socklen_t*)&len);
// if(nbytes>0){
// memset(buf,0,8);
// strncpy(buf,(char*)frame.data,8);
// //emit message(&addr,&len);
// printf("id=%x,len=%d\n",(struct sockaddr *)&addr,(socklen_t*)&len);
// }
//接收can数据:方式二
nbytes = read(socket, &frame, sizeof(frame)); //接收报文
if(nbytes > 0){
printf("id=%x,len=%d,data=%x%x%x%x%x%x%x%x\n",frame.can_id&0x7fffffff,frame.can_dlc,frame.data[0],frame.data[1],frame.data[2],frame.data[3],frame.data[4],frame.data[5],frame.data[6],frame.data[7]);
}
}
}
void Thread::stop()
{
running = false;
}
20230331这里更改方式一注释未注释全,导致偶尔发两次接收一次问题
thread.h
#ifndef THREAD_H
#define THREAD_H
#include <QThread>
extern "C" {
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
}
#ifndef PF_CAN
#define PF_CAN 29
#endif
#ifndef AF_CAN
#define AF_CAN PF_CAN
#endif
class Thread : public QThread
{
Q_OBJECT
public:
explicit Thread(int s,QObject *parent = 0);
signals:
void message(sockaddr addr,socklen_t num);
public slots:
void run();
void stop();
private:
int socket;
bool running;
};
#endif // THREAD_H
全部代码已经贴完,欢迎查看使用,祝君成功!
测试效果如下:
稳定性测试:
500K(8M晶振上限):
未完待续
1M(24M晶振上限):
未完待续
四、结语
😔