IT评测·应用市场-qidao123.com

标题: (六)CAN总线通讯 [打印本页]

作者: 钜形不锈钢水箱    时间: 2025-1-11 16:53
标题: (六)CAN总线通讯

CAN总线回环测试

第一种基于板载CAN测试

第一步确认板载是否支持

确保你的硬件支持 CAN,并且已经精确安装了驱动程序。可以通过以下下令查抄是否检测到了 CAN 设备:
  1. ip link show type can
复制代码
可以看到板载自带了can0和can1.

can0 和 can1:是接口的名称,分别表示系统中的第一个和第二个 CAN 接口。
<NOARP,ECHO>:
NOARP:表示该接口不使用 ARP(地址解析协议)。CAN 总线不需要 ARP 由于它不是基于 IP 的网络。
ECHO:可能启用了回环模式,发送的消息会被自己的 CAN 控制器重新吸收。这里不确定是否启用了,可以用指令ip -details link show 检察到:

大概单看can0,can1的状态,以can0为例子:
  1. ip -details link show can0
复制代码

mtu 16:最大传输单元(Maximum Transmission Unit),对于 CAN 接口来说MTU 通常是 16 字节,这是由于 CAN 消息的最大有效载荷是 8 字节,加上一些额外的头部信息。
qdisc noop:队列调度算法(Queueing Discipline),这里使用的是 noop,即空操作队列调度器。这意味着没有任何包调度计谋被应用,所有数据包将直接传递而不会排队。
state DOWN:当前接口状态为关闭(DOWN),意味着接口未激活,无法进行通讯。要激活接口,可以使用 ip link set can0 up

或 ip link set can1 up。

mode DEFAULT:表示接口的工作模式,默认情况下是标准模式。
group default:指明这个接口属于哪个组,default 是默认组。
qlen 10:队列长度(Queue Length),表示可以排队等候处理的数据包数量,在这里是 10。
链路范例
link/can:表明这是一个 CAN 范例的接口。


第二步关闭 CAN 接口将 CAN 接口置于非运动状态

假如没有激活的情况,直接跳过此步
  1. ip link set can0 down
复制代码


第三步 配置 CAN 接口

第一步 设置 CAN 接口比特率

  1. ip link set can0 type can bitrate 500000
复制代码
ip link set:这是一个用来配置网络接口的下令。
can0:指定要配置的网络接口名称,这里是 can0。
type can:指明该接口是 CAN 范例的。
bitrate 500000:设置 CAN 总线的数据传输速率(比特率)为 500 kbps。这个值应该根据你的硬件支持和需求进行调解。

设置完可以借助指令检察:
  1. ip -details link show can0
复制代码


第二步 设置 CAN 启用回环模式

指令:
  1. ip link set can0 type can loopback on
复制代码
loopback on:启用回环模式。在这种模式下,所有从 CAN 控制器发出的消息都会被重新送回到同一个控制器,而不会真正发送到物理总线上。这对于测试非常有用,由于它允许你在不毗连任何其他设备的情况下验证软件是否正常工作。

设置完同样可以借助指令检察:
  1. ip -details link show can0
复制代码


第三步 启用 CAN 接口

指令:
  1. ip link set can0 up
复制代码

启动成功后同样可以借助指令检察:
  1. ip -details link show can0
复制代码


第四步 测试CAN总线回环

捕获 CAN 消息

使用 nohup 和 candump 捕获消息并记录到文件
  1. nohup candump can0 > log.txt &
复制代码
nohup:使程序在用户退出终端后继续运行。这对于长时间运行的任务很有用。
candump:这是 Linux 下的一个工具,用于捕获并显示 CAN 消息。它会实时监听指定的 CAN 接口,并输出吸收到的消息。
can0:指定要监听的 CAN 接口。
log.txt:将 candump 的输出重定向到一个名为 log.txt 的文件中,而不是打印到屏幕上。
&:将下令放入背景执行,这样可以在同一终端窗口中继续输入其他下令。


发送 CAN 消息

标准数据帧发8位数据:
  1. cansend can0 0B000123#00.00.00.00.00.00.00.01
复制代码
#反面每一位数据不加.也行,留意别发错了
cansend:这是 Linux 下的一个工具,用于向指定的 CAN 接口发送 CAN 消息。
can0:指定要使用的 CAN 接口。

0B000123#00.00.00.00.00.00.00.01:这是要发送的 CAN 消息格式。
具体来说:0B000123是 CAN ID,其中 0B 表示标准帧格式(11位ID),000123是具体的 ID 值。
#分隔符,反面跟着的是数据字段。00.00.00.00.00.00.00.01 是数据字段的内容,表示 8 字节的数据。每个字节用两位十六进制数表示。


实在都用默认,就是标准数据帧:
  1. cansend can0 123#00.00.00.00.00.00.00.02
复制代码

数据也可以发0-8任意字节:
  1. cansend can0 234#88
复制代码

第二种基于Linux库的原生开发

在 Linux 下进行 CAN 总线应用开发时,通常需要通过系统调用和特定的套接字 API 来与 CAN 接口交互。Linux 内核提供了一个叫做 SocketCAN 的子系统,它使得 CAN 通讯可以像普通的网络编程一样使用标准的 BSD 套接字接口来实现。下面是进行 CAN 应用开发的基本步调:
1. 确认硬件支持

  1. ip link show type can
复制代码
加粗样式
可以看到板载自带了can0和can1.


2. 配置 CAN 接口

先关闭can接口
  1. ip link set can0 down
复制代码
配置波特率:
  1. ip link set can0 type can bitrate 500000
复制代码
假如使用回环如下操作,不使用大概使用正常模式就跳过此步,我这里接纳回环测试因此执行此步调:
  1. ip link set can0 type can loopback on
复制代码
启动can接口
  1. ip link set can0 up
复制代码
至此所有配置完成

3. 使用 SocketCAN API 编写应用程序

创建 CAN 套接字

在 C/C++ 中,可以通过 socket() 函数创建一个 CAN 套接字。例如:
  1. #include <sys/socket.h>
  2. #include <linux/can.h>
  3. #include <linux/can/raw.h>
  4. int s;
  5. if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
  6.     perror("Socket creation failed");
  7.     return -1;
  8. }
复制代码

绑定 CAN 接口

接下来,将套接字绑定到具体的 CAN 接口(如 can0)。这可以通过 bind() 函数完成:
  1. struct ifreq ifr;
  2. struct sockaddr_can addr;
  3. addr.can_family = AF_CAN;
  4. strcpy(ifr.ifr_name, "can0");
  5. if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
  6.     perror("Interface index get failed");
  7.     close(s);
  8.     return -1;
  9. }
  10. addr.can_ifindex = ifr.ifr_ifindex;
  11. if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
  12.     perror("Bind to CAN interface failed");
  13.     close(s);
  14.     return -1;
  15. }
复制代码

发送和吸收 CAN 消息

  1. struct can_frame frame;
  2. // 构造要发送的 CAN 帧
  3. frame.can_id = 0x123; // CAN ID
  4. frame.can_dlc = 8;    // 数据长度码(DLC),表示数据域的字节数
  5. memset(frame.data, 0x01, frame.can_dlc); // 设置数据域内容
  6. // 发送 CAN 帧
  7. if (write(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
  8.     perror("Write CAN frame failed");
  9.     close(s);
  10.     return -1;
  11. }
  12. // 接收 CAN 帧
  13. if (read(s, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
  14.     perror("Read CAN frame failed");
  15.     close(s);
  16.     return -1;
  17. }
  18. printf("Received CAN ID=%x, DLC=%d\n", frame.can_id, frame.can_dlc);
  19. for (int i = 0; i < frame.can_dlc; ++i)
  20.     printf("Data[%d]: %02x\n", i, frame.data[i]);
复制代码

示例代码

下面是一段完整的示例代码,展示了如何创建 CAN 套接字、绑定接口、发送和吸收消息:
这里我开启了线程,父线程用来写,子线程用来读;代码被屏蔽掉的那部门是,禁止回环模式的,为了防止配置的时候关闭回环失败,因此软件上再关闭一次。
  1. #include "can_config.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <string.h>
  6. #include <signal.h>
  7. #include <libgen.h>
  8. #include <getopt.h>
  9. #include <limits.h>
  10. #include <sys/types.h>
  11. #include <sys/socket.h>
  12. #include <sys/uio.h>
  13. #include <sys/ioctl.h>
  14. #include <net/if.h>
  15. #include <linux/can.h>
  16. #include <linux/can/raw.h>
  17. int main()
  18. {
  19.         int s; // 就是fd
  20.         int n_read = 0; // 读取到的数据个数
  21.         //创建 CAN 套接字
  22.         if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
  23.                 perror("Socket creation failed");
  24.                 return -1;
  25.         }
  26.         printf("socket ok ========================\n");
  27.         /*
  28.         // 禁用回环模式(如果需要)
  29.     int loopback = 0; // 0 表示关闭回环模式
  30.     if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)) < 0) {
  31.         perror("Failed to disable loopback mode");
  32.         close(s);
  33.         return -1;
  34.     }
  35.     // 禁用监听自己的消息(可选)
  36.     int recv_own_msgs = 0; // 0 表示关闭接收自己的消息
  37.     if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &recv_own_msgs, sizeof(recv_own_msgs)) < 0) {
  38.         perror("Failed to disable receiving own messages");
  39.         close(s);
  40.         return -1;
  41.     }
  42.         */
  43.         // 绑定 CAN 接口
  44.         struct ifreq ifr;
  45.         struct sockaddr_can addr;
  46.         addr.can_family = AF_CAN;
  47.         strcpy(ifr.ifr_name, "can0");
  48.         if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) {
  49.                 perror("Interface index get failed");
  50.                 close(s);
  51.                 return -1;
  52.         }
  53.         addr.can_ifindex = ifr.ifr_ifindex;
  54.         if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
  55.                 perror("Bind to CAN interface failed");
  56.                 close(s);
  57.                 return -1;
  58.         }
  59.         printf("bind ok ========================\n");
  60.         // 构造要发送的 CAN 帧
  61.         struct can_frame wirte_frame;
  62.         struct can_frame recive_frame;
  63.         wirte_frame.can_id = 0x123; // CAN ID
  64.         wirte_frame.can_dlc = 8;    // 数据长度码(DLC),表示数据域的字节数
  65.         memset(wirte_frame.data, 0x01, wirte_frame.can_dlc); // 设置数据域内容
  66.        
  67.         int data = 0x01;
  68.         //开启线程 一个接收一个发送;
  69.         __pid_t pid;
  70.         pid = fork();//返回的pid号 父进程是正数id号码,子进程是0
  71.         // 父进程 write  子进程接收
  72.         if(pid>0){
  73.                 printf("in farther ok ========================\n");
  74.                 while(1){
  75.                         // 发送 CAN 帧  间隔3秒写一次
  76.                         if (write(s, &wirte_frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
  77.                                 perror("Write CAN frame failed");
  78.                                 close(s);
  79.                                 return -1;
  80.                         }
  81.                         sleep(3);
  82.                         memset(wirte_frame.data,++data, wirte_frame.can_dlc); // 设置数据域内容
  83.                 }       
  84.         }else if(pid==0){
  85.                 printf("in son ok ========================\n");
  86.                 while(1){
  87.                         // 接收 CAN 帧
  88.                         if ((n_read = read(s, &recive_frame, sizeof(struct can_frame)) )!= sizeof(struct can_frame)) {
  89.                                 perror("Read CAN frame failed");
  90.                                 close(s);
  91.                                 return -1;
  92.                         }
  93.                         if(n_read>0){
  94.                                 printf("Received CAN ID=%x, DLC=%d\n", recive_frame.can_id, recive_frame.can_dlc);
  95.                                 for (int i = 0; i < recive_frame.can_dlc; ++i)
  96.                                         printf("Data[%d]: %02x\n", i, recive_frame.data[i]);
  97.                         }
  98.                 }
  99.         }else{
  100.                 perror("fork error\n");
  101.         }
  102.         close(s);
  103.         return 0 ;
  104. }
复制代码
4. 应用程序测试

先用交错编译工具编译:
  1. arm-linux-gnueabi-gcc mycan.c -o mycan
复制代码


远程发送到板子:
  1. scp ./mycan root@192.168.1.101:/root/zhua
复制代码

执行:



适当改一改代码:

再测试:

这里有个小题目就是write不停写,FIFO文件队列会满,然后就是溢出报错,因此要学习错误处理等操作。
所以末了关于帧格式,位同步,仲裁,错误处理等可以深入相识。保举在学习stm32的时候学习寄存器配置开发,可以深入的相识到。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4