Arm Linux Can
- 一:can-utils 安装
- 二:can-utils 使用
- can网络关闭
- can波特率设置
- 查询can0设备的参数设置
- can网络开启
- 查询工作状态
- can发送数据
- can接受数据
- 三:can回环测试
- 四:C语言CAN编程
- 初始化
- 数据结构
- 数据发送
- 错误处理
- 过滤规则
- 回环设置
- 五:Linux 系统中CAN 接口应用程序示例
- 报文发送程序
- 报文过滤接收程序
一:can-utils 安装
生产如下工具:
二:can-utils 使用
ip link set can0 down
ip link set can0 type can bitrate 250000 loopback off//设置波特率,关闭回环测试
ip link set can0 up
说明:
- loopback off:关闭回环模式。测试发现关闭后,可以实现CAN设备与外界的收发通信。
- loopback on:打开回环模式。测试发现打开后,可以实现自发自收,不依赖外部CAN设备。
can网络关闭
ip link set can0 down
can波特率设置
ip link set can0 type can bitrate 500000 loopback off
查询can0设备的参数设置
ip -details link show can0
can网络开启
ip link set can0 up
查询工作状态
ip -details -statistics link show can0
can发送数据
数据发送格式:
cansend can0 XXX#11223344
eg:
cansend can0 12345678#112233
XXX:表示标识符ID,ID越小优先级越高CAN数据冲突时,优先发送。
112233344:表示发送的数据,以16进制表示。最小发送数据单位是字节,可发0~8个字节。
can接受数据
candump can0 或 candump can0 &
三:can回环测试
ip link set down can0
ip link set can0 type can loopback on
ip link set up can0
candump can0 -L &
cansend can0 123#1122334455667788
ip -details link show can0
四:C语言CAN编程
由于系统将 CAN 设备作为网络设备进行管理,因此在 CAN 总线应用开发方面, Linux 提供了SocketCAN 接口,使得 CAN 总线通信近似于和以太网的通信,应用程序开发接口更加通用,也更加灵活。
初始化
SocketCAN 中大部分的数据结构和函数在头文件 linux/can.h 中进行了定义。 CAN 总线套接字的创建采用标准的网络套接字操作来完成。网络套接字在头文件 sys/socket.h 中定义。 套接字的初始化方法如下:
int init(char *drvcan_name)
{
int ret;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_filter rfilter[1];
int fd;
char can_down_cmd[64] = "";
char can_init_cmd[64] = "";
char can_up_cmd[64] = "";
if (drvcan_name == NULL)
{
printf("drvcan_name is null");
return -1;
}
sprintf(can_down_cmd, CAN_DOWN, drvcan_name);
sprintf(can_init_cmd, CAN_INIT, drvcan_name);
sprintf(can_up_cmd, CAN_UP, drvcan_name);
system(can_down_cmd);
system(can_init_cmd);
system(can_up_cmd);
fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (fd < 0)
{
printf("socket error\n");
return -2;
}
sprintf(ifr.ifr_name, "%s", drvcan_name);
ret = ioctl(fd, SIOCGIFINDEX, &ifr);
if (ret < 0)
{
printf("ioctl error ret:%d", ret);
return -3;
}
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
/*设置过滤规则*/
// rfilter[0].can_id = 0x2;
// rfilter[0].can_mask = 0;
// Setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
// setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, NULL);
/*启动接收线程*/
thread_exit_state = EM_THREAD_STATE_RUN;
pthread_t _threadID;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&_threadID, &attr, can_recv_thread, fd);
pthread_attr_destroy(&attr);
return fd;
}
数据结构
在数据收发的内容方面,CAN 总线与标准套接字通信稍有不同,每一次通信都采用 can_frame 结构体将数据封装成帧。 结构体定义
/**
* struct can_frame - basic CAN frame structure
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
* @can_dlc: frame payload length in byte (0 .. 8) aka data length code
* N.B. the DLC field from ISO 11898-1 Chapter 8.4.2.3 has a 1:1
* mapping of the 'data length code' to the real payload length
* @__pad: padding
* @__res0: reserved / padding
* @__res1: reserved / padding
* @data: CAN frame payload (up to 8 byte)
*/
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
__u8 __pad; /* padding */
__u8 __res0; /* reserved / padding */
__u8 __res1; /* reserved / padding */
__u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));
};
can_id 为帧的标识符,如果发出的是标准帧,就使用 can_id的低11位。如果为扩展帧,就使用 0~ 28 位。can_id 的第 29、 30、 31 位是帧的标志位,用来定义帧的类型,定义如下:
/* special address description flags for the CAN_ID */
#define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB 扩展帧*/
#define CAN_RTR_FLAG 0x40000000U /* remote transmission request 远程帧*/
#define CAN_ERR_FLAG 0x20000000U /* error message frame 错误帧*/
/* valid bits in CAN ID for frame formats */
#define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */
#define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */
#define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */
数据发送
struct can_frame frame;
frame.can_id = 0x123;//如果为扩展帧,那么 frame.can_id = CAN_EFF_FLAG | 0x123;
frame.can_dlc = 1; //数据长度为 1
frame.data[0] = 0xAB; //数据内容为 0xAB
int nbytes = write(s, &frame, sizeof(frame)); //发送数据
if(nbytes != sizeof(frame)) //如果 nbytes 不等于帧长度,就说明发送失败
printf("Error\n!");
如果要发送远程帧(标识符为 0x123),可采用如下方法进行发送
struct can_frame frame;
frame.can_id = CAN_RTR_FLAG | 0x123;
write(s, &frame, sizeof(frame));
当然, 套接字数据收发时常用的 send、 sendto、 sendmsg 以及对应的 recv 函数也都可以用于 CAN总线数据的收发
错误处理
当帧接收后,可以通过判断 can_id 中的 CAN_ERR_FLAG 位来判断接收的帧是否为错误帧。 如果为错误帧,可以通过 can_id 的其他符号位来判断错误的具体原因。
错误帧的符号位在头文件 linux/can/error.h 中定义。
过滤规则
在数据接收时,系统可以根据预先设置的过滤规则,实现对报文的过滤。过滤规则使用 can_filter 结构体来实现,定义如下:
/**
* struct can_filter - CAN ID based filter in can_register().
* @can_id: relevant bits of CAN ID which are not masked out.
* @can_mask: CAN mask (see description)
*
* Description:
* A filter matches, when
*
* <received_can_id> & mask == can_id & mask
*
* The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
* filter for error message frames (CAN_ERR_FLAG bit set in mask).
*/
struct can_filter {
canid_t can_id;
canid_t can_mask;
};
接收到的数据帧的 can_id & mask == can_id & mask。
通过这条规则可以在系统中过滤掉所有不符合规则的报文,使得应用程序不需要对无关的报文进行处理。
struct can_filter rfilter[2];
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK; //#define CAN_SFF_MASK 0x000007FFU
rfilter[1].can_id = 0x200;
rfilter[1].can_mask = 0x700;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));//设置规则
在极端情况下,如果应用程序不需要接收报文,可以禁用过滤规则。这样的话,原始套接字就会忽略所有接收到的报文。在这种仅仅发送数据的应用中,可以在内核中省略接收队列,以此减少 CPU 资源的消耗。禁用方法如下:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用过滤规则
通过错误掩码可以实现对错误帧的过滤, 例如:
can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, err_mask, sizeof(err_mask));
回环设置
在默认情况下, 本地回环功能是开启的,可以使用下面的方法关闭回环/开启功能:
int loopback = 0; // 0 表示关闭, 1 表示开启( 默认)
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
在本地回环功能开启的情况下,所有的发送帧都会被回环到与 CAN 总线接口对应的套接字上。 默认情况下,发送 CAN 报文的套接字不想接收自己发送的报文,因此发送套接字上的回环功能是关闭的。可以在需要的时候改变这一默认行为:
int ro = 1; // 0 表示关闭( 默认), 1 表示开启
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof(ro));
五:Linux 系统中CAN 接口应用程序示例
报文发送程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame[2] = {{0}};
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); // 创建套接字
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr); // 指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); // 将套接字与 can0 绑定
// 禁用过滤规则,本进程不接收报文,只负责发送
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
// 生成两个报文
frame[0].can_id = 0x11;
frame[0].can_dlc = 1;
frame[0].data[0] = 'Y';
frame[0].can_id = 0x22;
frame[0].can_dlc = 1;
frame[0].data[0] = 'N';
// 循环发送两个报文
while (1)
{
nbytes = write(s, &frame[0], sizeof(frame[0])); // 发送 frame[0]
if (nbytes != sizeof(frame[0]))
{
printf("Send Error frame[0]\n!");
break; // 发送错误,退出
}
sleep(1);
nbytes = write(s, &frame[1], sizeof(frame[1])); // 发送 frame[1]
if (nbytes != sizeof(frame[0]))
{
printf("Send Error frame[1]\n!");
break;
}
sleep(1);
}
close(s);
return 0;
}
报文过滤接收程序
/* 2. 报文过滤接收程序 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
int s, nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
struct can_filter rfilter[1];
s = socket(PF_CAN, SOCK_RAW, CAN_RAW); // 创建套接字
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr); // 指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); // 将套接字与 can0 绑定
// 定义接收规则,只接收表示符等于 0x11 的报文
rfilter[0].can_id = 0x11;
rfilter[0].can_mask = CAN_SFF_MASK;
// 设置过滤规则
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
while (1)
{
nbytes = read(s, &frame, sizeof(frame)); // 接收报文
// 显示报文
if (nbytes > 0)
{
printf(“ID = 0x % X DLC = % d data[0] = 0x % X\n”, frame.can_id,
frame.can_dlc, frame.data[0]);
}
}
close(s);
return 0;
}