通过简单的程序来学习CAN总线的通讯,其中包括Linux系统下使用SocketCan库来进行通信,还有就是使用Qt来实现CAN通信,假设将设备通过CAN总线发送的速度来显示到QT界面上。
CAN(Controller Area Network)的数据报文格式通常由两种类型的消息构成:标准帧(Standard Frame)和扩展帧(Extended Frame)。这两种帧格式的结构略有不同,下面分别介绍它们的主要特点:
标准帧(Standard Frame):
标准帧用于传输常规的数据和消息。其主要特点包括:
11位标识符:标准帧使用11位的标识符(Identifier)来区分不同类型的消息。这个标识符通常用于表示消息的优先级和类型。
0-8个字节的数据:标准帧可以携带0到8个字节的数据。数据字节包含了消息的实际信息。控制位:标准帧包括一些控制位,用于标识消息的类型(数据帧或远程帧)以及是否需要进行数据重发等。
标准帧的基本结构如下:
Start of Frame | Identifier | RTR | IDE | Data | CRC | Ack | End of Frame |
---|
Start of Frame:帧起始标志,表示帧的开始。
Identifier:11位标识符,用于标识消息的类型和优先级。
RTR(Remote Transmission Request):1位,指示消息是数据帧还是远程帧。
IDE(Identifier Extension):1位,指示是否使用扩展标识符。
Data:0-8个字节的数据,携带消息内容。
CRC(Cyclic Redundancy Check):15位的CRC校验码,用于检测数据的错误。
Ack:用于表示消息是否被接收成功。
End of Frame:帧结束标志,表示帧的结束。
扩展帧(Extended Frame):
扩展帧用于传输具有更大标识符空间的消息,通常用于更复杂的应用。其主要特点包括:
29位标识符:扩展帧使用29位的标识符来区分不同类型的消息,提供了更大的标识符空间。
0-8个字节的数据:与标准帧类似,扩展帧可以携带0到8个字节的数据。
控制位:扩展帧也包括一些控制位,用于标识消息的类型和其他信息。
扩展帧的基本结构与标准帧类似,但标识符位数更多,CRC位数也可能不同,具体取决于CAN协议版本和实现。
CAN通信的报文格式与硬件和应用有关,通常需要根据实际需求和硬件配置进行适当的配置和设置。这些报文格式用于在CAN总线上传输数据和消息,以实现各种应用,如车辆控制、工业自动化和嵌入式系统通信。
C++编写两个程序,一个用于发送CAN消息,另一个用于接收CAN消息。这两个程序都使用SocketCAN库进行CAN总线通信。
发送程序
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
const char *can_interface = "can0"; // 根据您的系统和硬件配置选择适当的接口名称
int socket_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (socket_fd == -1) {
perror("Socket creation error");
return 1;
}
struct ifreq ifr{};
std::strncpy(ifr.ifr_name, can_interface, IFNAMSIZ - 1);
if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1) {
perror("ioctl error");
close(socket_fd);
return 1;
}
struct sockaddr_can addr{};
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(socket_fd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1) {
perror("Binding error");
close(socket_fd);
return 1;
}
struct can_frame frame{};
frame.can_id = 0x123; // 设置消息的标识符
frame.can_dlc = 4; // 设置消息数据长度
frame.data[0] = 0x01;
frame.data[1] = 0x02;
frame.data[2] = 0x03;
frame.data[3] = 0x04;
if (write(socket_fd, &frame, sizeof(struct can_frame)) == -1) {
perror("Write error");
close(socket_fd);
return 1;
}
close(socket_fd);
return 0;
}
接收程序
#include
#include
#include
#include
#include
#include
int main() {
const char *can_interface = "can0"; // 根据您的系统和硬件配置选择适当的接口名称
int socket_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (socket_fd == -1) {
perror("Socket creation error");
return 1;
}
struct ifreq ifr{};
std::strncpy(ifr.ifr_name, can_interface, IFNAMSIZ - 1);
if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1) {
perror("ioctl error");
close(socket_fd);
return 1;
}
struct sockaddr_can addr{};
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(socket_fd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1) {
perror("Binding error");
close(socket_fd);
return 1;
}
struct can_frame frame{};
while (true) {
if (read(socket_fd, &frame, sizeof(struct can_frame)) == -1) {
perror("Read error");
close(socket_fd);
return 1;
}
std::cout << "Received Message: ID=" << std::hex << frame.can_id << " Data=";
for (int i = 0; i < frame.can_dlc; ++i) {
std::cout << std::hex << static_cast<int>(frame.data[i]) << " ";
}
std::cout << std::endl;
}
close(socket_fd);
return 0;
}
1、在主函数中,定义了要使用的CAN总线接口的名称:
const char *can_interface = "can0"; // 根据您的系统和硬件配置选择适当的接口名称
2、创建一个套接字来与CAN总线通信:
int socket_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (socket_fd == -1) {
perror("Socket creation error");
return 1;
}
3、使用ioctl函数获取CAN接口的索引:
struct ifreq ifr{};
std::strncpy(ifr.ifr_name, can_interface, IFNAMSIZ - 1);
if (ioctl(socket_fd, SIOCGIFINDEX, &ifr) == -1) {
perror("ioctl error");
close(socket_fd);
return 1;
}
4、配置套接字地址以绑定到特定的CAN接口:
struct sockaddr_can addr{};
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(socket_fd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1) {
perror("Binding error");
close(socket_fd);
return 1;
}
5、创建一个CAN帧并设置其标识符和数据:
struct can_frame frame{};
frame.can_id = 0x123; // 设置消息的标识符
frame.can_dlc = 4; // 设置消息数据长度
frame.data[0] = 0x01;
frame.data[1] = 0x02;
frame.data[2] = 0x03;
frame.data[3] = 0x04;
6、使用write函数将CAN帧发送到CAN总线上:
if (write(socket_fd, &frame, sizeof(struct can_frame)) == -1) {
perror("Write error");
close(socket_fd);
return 1;
}
7、在一个无限循环中,使用read函数接收来自CAN总线的CAN帧,并打印它们的标识符和数据:
while (true) {
if (read(socket_fd, &frame, sizeof(struct can_frame)) == -1) {
perror("Read error");
close(socket_fd);
return 1;
}
std::cout << "Received Message: ID=" << std::hex << frame.can_id << " Data=";
for (int i = 0; i < frame.can_dlc; ++i) {
std::cout << std::hex << static_cast<int>(frame.data[i]) << " ";
}
std::cout << std::endl;
}
在Windows上使用Qt进行CAN总线通信,可以使用第三方CAN库来支持CAN通信。在Windows环境下,SocketCAN不直接可用,但有一些其他CAN库和工具可以帮助您实现CAN通信。这里使用Kvaser的CANlib库以及Qt来创建一个简单的CAN通信应用程序。假设设备通过CAN总线发送了车速信息,qt端来接收车速信息并显示。
#include
#include // Kvaser CANlib头文件
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 初始化CANlib
canStatus stat = canInitializeLibrary();
if (stat != canOK) {
qDebug() << "CANlib initialization failed";
return -1;
}
// 打开CAN通道
int channel = 0; // 通道号,根据您的硬件配置设置
canHandle hnd = canOpenChannel(channel, canOPEN_ACCEPT_VIRTUAL);
if (hnd < 0) {
qDebug() << "Failed to open CAN channel";
canClose(hnd);
return -1;
}
// 设置CAN通信参数,如波特率
int bitrate = canBITRATE_500K; // 500 kbps,根据您的硬件和需求设置
stat = canSetBusParams(hnd, bitrate, 0, 0, 0, 0, 0);
if (stat != canOK) {
qDebug() << "Failed to set CAN bus parameters";
canClose(hnd);
return -1;
}
// 启动CAN通信
stat = canBusOn(hnd);
if (stat != canOK) {
qDebug() << "Failed to start CAN bus";
canClose(hnd);
return -1;
}
// 接收CAN消息
while (true) {
canMessage msg;
stat = canRead(hnd, &msg);
if (stat == canOK) {
if (msg.id == 0x123) { // 假设车速信息的CAN消息标识符为0x123
int speed = (msg.data[0] << 8) | msg.data[1];
qDebug() << "Vehicle Speed: " << speed << " km/h";
}
}
}
// 停止CAN通信并关闭通道
canBusOff(hnd);
canClose(hnd);
// 反初始化CANlib
canUnloadLibrary();
return a.exec();
}