Linux下的CAN通讯
CAN总线
CAN总线简介
CAN(Controller Area Network)总线是一种多主从式 <font color = red>
异步半双工串行 </font>
通信总线,它最早由Bosch公司开发,用于汽车电子系统。CAN总线具有以下特点:
- 多主从式:CAN总线允许多个节点同时进行通信,每个节点都可以发送和接收数据。
- 工作频率:CAN总线的工作频率通常为40kHz到1MHz,具体取决于应用场景。
- 通信距离:CAN总线可以在不同的通信距离下工作,最长可达1公里。
- 通信速率:CAN总线支持多种通信速率,从10kbps到1Mbps不等。高速CAN的通信速率为125kbps到1Mbps。低速CAN的通信速率为低于125kbps。
CAN的硬件连接
一般没有can控制器的设备,可以靠使用SPI转CAN模块,再外接CAN收发器,实现can的数据收发
CAN总线两端的120欧姆为终端电阻,是为了消除总线上的信号反射
电器属性
在高速 CAN中:
- 当 CANH 和 CANL 电压相同 (CANH= CANL=2.5V) 时为逻辑“1”(隐形状态)
- CANH 和 CANL 电压相差 2V (CANH=3.5V,CANL= 1.5V) 时为逻辑“0”(显性状态)。
CAN协议
缩写 | 含义 | 解释 |
---|---|---|
SOF | (strat offrame): | 帧起始。表示一帧数据的起始 |
ID | (identify): | 标识符 |
RTR | (remote transmission request): | 远程请求标志位 |
IDE | (identify extension): | 扩展标志位 |
SRR | (substitute remote request): | 替代远程请求标志位 |
R0/R1 | (reserve): | 保留位 |
DLC | (date length code) : | 表示数据字段有多少个字节 |
CRC | (cyclic redundancy check): | CRC校验 |
ACK | (acknowledge): | 应答 |
EOF | (end offrame): | 帧结束。表示一帧数据结束 |
1.数据帧
can通信是通过数据帧进行发送数据的,所以数据帧是:数据帧携带数据从发送设备到接收设备
1.2.数据帧格式
数据帧有 标准数据帧 和 扩展数据帧
1.3.1标准数据帧格式
标准数据帧格式分析:
can的空闲电平是1(高电平,隐性),所以起始帧是0(低电平,显性)
- 帧起始和帧结束
帧起始和帧结束表示一帧数据的起始和结束,帧起始由 1 个显性位组成,结束由 7 个连续
的隐形位组成。 - 仲裁字段
仲裁字段包括 11位ID 位,1位 RTR 位,共 12 位。D 位用可以用来区分数据的功能(不
司功能的报数据帧,ID 是不同的),也可以用来区分优先级。根据仲裁规则,ID 小的数据帧
优先发送。禁止高 7 位都为隐性(禁止设定: ID=1111111xXXX)。
RTR 是远程请求标志位,用于区分数据帧还是遥控帧。数据帧 RTR 位必须为显性 0,遥控
必须位隐性 1。(相同ID的数据和遥控,数据的优先级大干遥控帧)。 - 控制字段
控制字段由 1位IDE 位,1位 RO 保留位,4 位 DLC共6 位组成。
- IDE 位表示数据帧是标准数据帧还是扩展数组帧,标准格式固定为显示0,扩展格式固定为
隐性 1。 - RO 保留位,固定为显示 0。
- DLC 位表示数据字段的数据字节数(数据段的长度)。
- 数据字段
可以承载 0 到 64 位数据。 - CRC 校验字段
CRC 校验字段由 15 位校验位和 1位 CRC 界定字符组成,前 15 位用于 CRC 校验。CRC 界定符
必须为隐性 1 - ACK 字段
ACK 字段由确定间隙位和确认界定符组成,确认界定符必须是隐性 1。
1.3.2扩展数据帧格式
相比于标准帧格式,扩展帧多了一个扩展ID,扩展ID由29位组成,标准帧的ID由11位组成
SRR替换了原有的RTR,IDE为扩展帧的标识符(低电平0),外加了18为扩展ID,其他的并无异样
2.遥控帧
遥控帧用于接收设备主动请求数据。发送方通过广播的形式发送数据,数据帧中通过ID 区分不同功能的数据帧,如果发送方没有广播这个数据,接收方可以广播发送一个遥控帧,有接收方想要的数据的发送方就会广播这个数据出来。接收方就可以接收到这个数据了。
2.1 遥控帧格式
数据帧去掉数据段就是遥控帧,所以遥控帧也有两种帧格式
3.错误帧
在发送或者接收时,总线上的设备发生错误的时候,可以通过发送错误帧的方式告知其他节点发生了错误。错误帧由俩个部分组成,分别是错误标志和错误界定符。错误标志:
- 主动错误标志:6 个连续显性位
- 被动错误标志:6 个连续隐性位
错误界定符:8 个连续隐性位
4.过载帧
当某个接收节点没有做好接收下一帧数据的准备时,接收方将发送过载帧通知发送节点节点最多可产生俩条连续的过载顺来延迟一下次发送。过载标志的构成与主动错误标志的构成相同,过载界定符的构成与错误界定符的构成相同。
- 过载标志:6个位的显行
- 过载界定符:8个位的隐行
5.帧间隔
帧间隔是用于分隔数据帧和遥控帧的帧。数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、遥控帧、错误帧、过载帧) 分开。
过载帧和错误帧前不能插入帧间隔。
6.位填充
CAN 总线上的数据是按照位来传输的,为了保证数据传输的可靠性,CAN 总线对数据进行了位填充。位填充是指在数据传输过程中,如果连续 5 个位都是显性位,那么就会插入一个隐形位。如果连续 5 个位都是隐形位,那么就会插入一个显性位。
仲裁机制
CAN 总线是一种多主从式总线,多个节点可以同时发送数据。为了防止多个节点同时发送数据导致冲突,CAN 总线采用了仲裁机制。仲裁机制是通过比较各个节点的 ID 来决定哪个节点可以发送数据。ID 小的节点优先发送数据。如果多个节点同时发送数据,那么 ID 小的节点会优先发送数据,其他节点会停止发送数据并进入监听状态,等待 ID 小的节点发送完数据后再发送数据。
在总线空闲态,最先开始发送消息的单元获得发送权。多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。
主要通过以下方法:
- 非破坏性仲裁:显性优先,即显性优先级大于隐性优先级(线与机制: 只有所有节点都发送隐性 1 时,总线才为 1,只要有一个节点发送显性 0,则总线就为显性 0)
- 载波侦听: 总线上各个节点在发送数据前要侦听总线的状态,只有在总线是空闲状态时才允许发送。
- 回读:节点在发送数据时要不停的检测要发送的数据。通过非破坏性仲裁判断是否与其他节点的数据发生冲突。
CAN的应用工具
iproute2 工具移植
下载路径:
https://mirrors.edge.kernel.org/pub/linux/utils/net/iproute2/
./configure
make CC=arm-linux-gcc
make install
CAN是作为网络设备存在的,所以查看CAN设备:
ifconfig -a
启动:
# 设置波特率
ip link set can0 type can bitrate 500000
# 启动can0设备
ifconfig can0 up
CAN的应用编程
CAN被规划为网络设备,所以在通信中使用的是socket编程。
int sokcet(int domain, int type, int protocol);
参数 | 解释 |
---|---|
domain: | 协议域,通常为PF_CAN |
type: | 指定协议类型,SOCK_RAW表示原始套接字,SOCK_DGRAM表示数据报套接字 |
protocol: | 指定协议,通常为CAN_RAW或CAN_BCM |
domain的常用协议族:
- PF_UNIX, PF_LOCAL: 本地通信
- PF_INET: IPv4网络通信
- PF_INET6: IPv6网络通信
- PF_NETLINK: 内核套接字,
- PF_CAN: CAN网络通信
type的常用协议类型:
- SOCK_STREAM:面向连接、可靠的、双向的、基于字节流的通数据按顺序传输,不会丢失或重复。通常用于TCP 协议
- SOCK_DGRAM:支持无连接、不可靠的、固定最大长度的消息据报传输。消息可能会丢失、重复或乱序。通常用于 UDP 协议
- SOCK SEQPACKET:提供面向连接、可靠的、双向的、基于固定最度数据报的通信,接收者必须每次系统调用读个数据包
- SOCK RAW:对原始网络协议的访问,允许应用程序直接发接收 IP 层的数据包,绕过传输层协议
- SOCK RDM:提供一种可靠的数据报层,但不保证顺序
- SOCK PACKET:提供对底层网络协议的直接访问,通常用于与设备驱动程序的通信
- SOCK NONBLOCK:新打开的文件描述符的非阻塞标志
- SOCK CLOEXEC:新文件描述符的在执行时关闭 (FD CLOEXEC)如果程序执行了一个新的程序,这个文件描将会被关闭,这有助于防止文件泄露到不受信程序中
SOURCE CODE
send CAN data:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
int main(int argc, char *argv[]){
int s = socket(AF_CAN, SOCK_RAW, CAN_RAW); // Create CAN socket
if(s < 0){
printf("Error creating socket\n");
return 1;
}
struct ifreq ifr; // Interface request
strcpy(ifr.ifr_name, "can0"); // CAN interface name
if(ioctl(s, SIOCGIFINDEX, &ifr) < 0){ // Get CAN interface index
printf("Error getting interface index\n");
}
struct sockaddr_can addr; // CAN socket address
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex; // CAN interface index (0 for first interface)
if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){
printf("Error binding socket\n");
return 1;
}
struct can_frame frame[3]; // CAN frame 标准帧
frame[0].can_id = 0x123; // CAN ID
frame[0].can_dlc = 8; // Data length code
for(int i = 0; i < 8; i++){
frame[0].data[i] = i; // Data
}
if(send(s, &frame[0], sizeof(frame[0]), 0) < 0){
printf("Error sending frame\n");
return 1;
}
frame[1].can_id = 0x456 | CAN_EFF_FLAG; // CAN ID with EFF flag 扩展帧
frame[1].can_dlc = 2; // Data length code
frame[1].data[0] = 0x12;
frame[1].data[1] = 0x34;
if(send(s, &frame[1], sizeof(frame[1]), 0) < 0){
printf("Error sending frame\n");
return 1;
}
frame[2].can_id = 0x789 | CAN_RTR_FLAG; // CAN ID with RTR flag 远程帧
frame[2].can_dlc = 0; // Data length code
if(send(s, &frame[2], sizeof(frame[2]), 0) < 0){
printf("Error sending frame\n");
return 1;
}
close(s);
return 0;
}
recv CAN data:
- 在接收时可以配置过滤规则,只接收符合过滤条件的帧。
/* 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 */*
在配置时只需记得后缀为FLAG是要过滤的帧,后缀为MASK是要接收的帧。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
int main(int argc, char *argv[]){
int s = socket(AF_CAN, SOCK_RAW, CAN_RAW); // Create CAN socket
if(s < 0){
printf("Error creating socket\n");
return 1;
}
struct ifreq ifr; // Interface request
strcpy(ifr.ifr_name, "can0"); // CAN interface name
if(ioctl(s, SIOCGIFINDEX, &ifr) < 0){ // Get CAN interface index
printf("Error getting interface index\n");
}
struct sockaddr_can addr; // CAN socket address
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex; // CAN interface index (0 for first interface)
if(bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){
printf("Error binding socket\n");
return 1;
}
struct can_frame frame; // CAN frame
char buff[1024];
struct can_filter filter[2]; // CAN filter 设置两条过滤规则
// 设置过滤规则,只接收ID为0x123的标准帧
filter[0].can_id = 0x123; // Filter ID
filter[0].can_mask = CAN_SFF_MASK; // Filter mask
// 设置过滤规则,只接收ID为0x456的扩展帧和标准帧,但不要远程帧
filter[1].can_id = 0x123; // Filter ID
filter[1].can_mask = CAN_SFF_MASK | CAN_EFF_MASK | CAN_RTR_FLAG; // Filter mask
setsockopt(s,SOL_CAN_RAW, CAN_RAW_FILTER, &filter, sizeof(filter)); // 设置过滤规则
while (1)
{
int ret = read(s, &frame, sizeof(frame)); // Read CAN frame
if(ret < 0){
printf("Error reading frame\n");
return 1;
}else{
if(frame.can_id & CAN_EFF_FLAG){
printf("Extended ID: %x\n", frame.can_id & CAN_EFF_MASK);
}else{
printf("Standard ID: %x\n", frame.can_id & CAN_SFF_MASK);
}
printf("Data: ");
for(int i = 0; i < frame.can_dlc; i++){
printf("%02x ", frame.data[i]);
}
if(frame.can_id && CAN_RTR_FLAG){
printf("Remote Transmission Request\n");
}
}
}
close(s);
return 0;
}