您的位置:首页 > 博客中心 > 网络系统 >

CAN总线基础和在linux下使用实战

时间:2022-04-03 15:12

CAN总线基础和在linux下使用实战

CAN 是Controller Area Network 的缩写
有CANH和CANL两线,即差分信号通信。当然设备芯片还会有电源和地等线。
在总线空闲时,所有的单元都可开始发送消息(多主控制)。
最先访问总线的单元可获得发送权(CSMA/CA 方式)。
多个单元同时开始发送时,发送高优先级 ID 消息的单元可获得发送权。
没有目标地址和源地址的概念,只有标识符,根据标识符决定优先级,根据表示符,设备自己判断是否接收给上层,让上层处理。即消息是广播的形式。
两个以上的单元同时开始发送消息时,对各消息ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。这个是与CAN总线中,多个电平同时出现时,显式电平为最终值这个机理有关。所以,设备一边发送,一遍检查总线的实际值,即可知道是否有别的设备在同时发送了。这种不算出错误,而是算仲裁是否取胜。 后面CRC检验错误等,才是错误。
标准格式有11 个位的标识符(Identifier: 以下称ID),扩展格式有29 个位的ID。
通信是通过以下5 种类型的帧进行的。 数据帧、 遥控帧、错误帧、 过载帧、 帧间隔

技术图片

技术图片

作为驱动开发人员,应该了解,总线协议哪些部分是硬件实现的,哪些部分是软件实现的。
数据帧的数据内容和遥控帧的数据内容,应该是软件填入,并由硬件进行协助处理。错误帧、过载帧、帧间隔,这种东西,应该是硬件直接处理,只是可能会转换为一个中断控制器的一个status来通知上层软件逻辑,出现某些问题。

对于linux软件来说,CAN host controller驱动会把硬件收到的CAN数据组织为struct can_frame 。
linux内核代码来看
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 data[CAN_MAX_DLEN] attribute((aligned(8)));
};
can_id就是表示符字段,并含CAN_ID + EFF/RTR/ERR flags
例如imx6的flexcan的驱动,在can_id上也指明了一些错误信息给上层使用。通过can_id的标记位指明。具体看flexcan的代码。
/

Controller Area Network Identifier structure
bit 0-28 : CAN identifier (11/29 bit)
bit 29 : error message frame flag (0 = data frame, 1 = error message)
bit 30 : remote transmission request flag (1 = rtr frame)
bit 31 : frame format flag (0 = standard 11 bit, 1 = extended 29 bit)
*/
即linux重新定义了canid,这个与can总线上的格式不同,但意义类似。而且提供了filter机制,让应用层的socket只是关心某些canframe的包。注意,其他操作系统,实现方式可能不同。
使用CAN分析仪,安装驱动,并使用CANTest工具,
选择好设备和can通道后,并设置频率后,即可接收和发送。

CANH接CANH,CANL接CANL

此CANTest工具运行后,并设置速率为500KHz

另外一侧,即设备侧,linux中运行
ip link set can0 down
ip link set can0 up type can bitrate 500000
即设置为500KHz。
然后运行
cansend can0 1F334455#11223355
linux上发送上面的数据,#之前是can id,#号之后是数据。CANTest上,就会收到并显示。

linux设备侧,运行candump -l any,0:0,#FFFFFFFF,然后CANTest上发送数据。
然后candump会告诉你保存到哪个文件,你即可
cat这个文件看到内容。

linux侧的can使用介绍,具体可以看Documentation/networking/can.txt

linux CAN应用开发涉及的api:参考candump和cansend的代码
struct sockaddr_can addr;
struct iovec iov;
struct msghdr msg;
struct cmsghdr cmsg;
struct can_filter
rfilter;
can_err_mask_t err_mask;
struct canfd_frame frame;
struct ifreq ifr;
socket(PF_CAN, SOCK_RAW, CAN_RAW);
ioctl(s[i], SIOCGIFINDEX, &ifr)
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_ERR_FILTER,&err_mask, sizeof(err_mask)); //根据需要
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS,&join_filter, sizeof(join_filter)) //根据需要
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FILTER,rfilter, numfilter sizeof(struct can_filter)); //根据需要, 设置filter,过滤某些CAN_ID的数据
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on)); //根据需要
bind(s[i], (struct sockaddr
)&addr, sizeof(addr))
select(s[currmax-1]+1, &rdfs, NULL, NULL, timeout_current)
recvmsg(s[i], &msg, 0); //或者recv()
close(s[i]);
ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
ioctl(s, SIOCGIFMTU, &ifr)
bind(s, (struct sockaddr *)&addr, sizeof(addr)
write(s, &frame, required_mtu)

由于linux暴露给上层的CAN网络设备只支持
socket(PF_CAN, SOCK_RAW, CAN_RAW);
而android上层使用的是java,所以需要native层的service把此种socket转为另外的tcp或者udp或者进程间通信机制的socket才能发给java上层。

具体请参考我的免费的linux各种驱动开发课程如下:

另外我的相关培训视频请看:
欢迎观看我发布的各个课程:

本类排行

今日推荐

热门手游