CAN 通信原理学习
文章目录
CAN通信
一:基本概述
1.1 can总线是什么
CAN 是 Controller Area Network 的缩写,是 ISO 国际标准化的串行通信协议。通俗来讲,CAN总线就是一种传输数据的线,用于在不同的ECU之间传输数据。
CAN
(Controller Area Network)是ISO国际标准化的串行通信协议。广泛应用于汽车、船舶等。具有已经被大家认可的高性能和可靠性。
CAN控制器通过组成总线的2根线 (CAN-H和CAN-L)的电位差来确定总线的电平 ,在任一时刻,总线上有2种电平:显性电平和隐性电平。
“显性”具有“优先”的意味,只要有一个单元输出显性电平,总线上即为显性电平,并且,“隐性”具有“包容”的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平。(显性电平比隐性电平更强)。
总线上执行逻辑上的线“与”时,显性电平的逻辑值为“0”,隐性电平为“1”。
下图显示了一个典型的CAN拓扑连接图。
连接在总线上的所有单元都能够发送信息,如果有超过一个单元在同一时刻发送信息,有最高优先级的单元获得发送的资格,所有其它单元执行接收操作。
can的拓扑结构:
1.2 can总线协议的特点
CAN总线协议具有下面的特点:
1) 多主控制
当总线空闲时,连接到总线上的所有单元都可以启动发送信息,这就是所谓的多主控制的概念。
先占有总线的设备获得在总线上进行发送信息的资格。这就是所谓的CSMA/CR(Carrier Sense MultipleAccess/Collosion Avoidance)方法
如果多个设备同时开始发送信息,那么发送最高优先级ID消息的设备获得发送资格。
2) 信息的发送
在CAN协议中,所有发送的信息要满足预先定义的格式。当总线没有被占用的时候,连接在总线上的任何设备都能起动新信息的传输,如果两个或更多个设备在同时刻启动信息的传输,通过ID来决定优先级。ID并不是指明信息发送的目的地,而是指示信息的优先级。如果2个或者更多的设备在同一时刻启动信息的传输,在总线上按照信息所包含的ID的每一位来竞争,赢得竞争的设备(也就是具有最高优先级的信息)能够继续发送,而失败者则立刻停止发送并进入接收操作。因为总线上同一时刻只可能有一个发送者,而其它均处于接收状态,所以,并不需要在底层协议中定义地址的概念。
3) 系统的灵活性
连接到总线上的单元并没有类似地址这样的标识,所以,添加或去除一个设备,无需改变软件和硬件,或其它设备的应用层软件。
4) 通信速度
可以设置任何通讯速度,以适应网络规模。
对一个网络,所有单元必须有相同的通讯速度,如果不同,就会产生错误,并妨碍网络通讯,然而,不同网络间可以有不同的通讯速度。
5) 远程数据请求
可以通过发送“遥控帧”,请求其他单元发送数据。
6) 错误检测、错误通知、错误恢复功能
所有单元均可以检测出错误(错误检测功能)。
检测到错误的单元立刻同时通知其它所有的单元(错误通知功能)。如果一个单元发送信息时检测到一个错误,它会强制终止信息传输,并通知其它所有设备发生了错误,然后它会重传直到信息正常传输出去(错误恢复功能)。
7) 错误隔离
在CAN总线上有两种类型的错误:暂时性的错误(总线上的数据由于受到噪声的影响而暂时出错);持续性的错误(由于设备内部出错(如驱动器坏了、连接有问题等)而导致的)。CAN能够区别这两种类型,一方面降低常出错单元的通讯优先级以阻止对其它正常设备的影响,另一方面,如果是一种持续性的错误,将这个设备从总线上隔离开。
8) 连接
CAN总线允许多个设备同时连接到总线上且在逻辑上没有数目上的限制。然而由于延迟和负载能力的限制,实际可连接得设备还是有限制的,可以通过降低通讯速度来增加连接的设备个数。相反,如果连接的设备少,通讯的速度可以增加。
1.3 can的网络通信结构
1.3.1
实际上,CAN总线网络底层只采用了OSI基本参照模型中的数据链路层、传输层。而在CAN网络高层仅采用了OSI基本参照模型的应用层
1.3.2 can协议网络层次
在CAN协议中,ISO标准只对数据链路层和物理层做了规定。对于数据链路层和物理层的一部分,ISO11898和ISO11519-2的规定是相同,但是在物理层的PMD子层和MDI子层是不同的。
在CAN总线,每一层网络中定义的事项如下:
二:socket can在通信网络中的应用
socket can
应用实例
server端:
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
int can_recv() {
int sock_fd;
unsigned long nbytes, len;
struct sockaddr_can addr;
struct ifreq ifr;
/*为了能够接收CAN报文,我们需要定义一个CAN数据格式的结构体变量*/
struct can_frame frame;
struct can_frame *ptr_frame;
/* 建立套接字,设置为原始套接字,原始CAN协议 */
sock_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
/* 对CAN接口进行初始化,设置CAN接口名,即当我们用ifconfig命令时显示的名字 */
strcpy(ifr.ifr_name, "can0");
ioctl(sock_fd, SIOCGIFINDEX, &ifr);
/*设置CAN协议 */
addr.can_family = AF_CAN;
addr.can_ifindex = 0;
/*将刚生成的套接字与网络地址进行绑定*/
bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
/*开始接收数据*/
nbytes = recvfrom(sock_fd, &frame, sizeof(struct can_frame), 0,
(struct sockaddr *)&addr, (socklen_t *)&len);
/*get interface name of the received CAN frame*/
ifr.ifr_ifindex = addr.can_ifindex;
ioctl(sock_fd, SIOCGIFNAME, &ifr);
printf("Received a CAN frame from interface %sn", ifr.ifr_name);
/*将接收到的CAN数据打印出来,其中ID为标识符,DLC为CAN的字节数,DATA为1帧报文的字节数*/
printf("CAN frame:nID = %xnDLC = %xnDATA = %sn", frame.can_id,
frame.can_dlc, frame.data);
ptr_frame = &frame;
return 0;
}
client
端
#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
int can_send() {
int sock_fd;
unsigned long nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
/*建立套接字,设置为原始套接字,原始CAN协议 */
sock_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
/* 对CAN接口进行初始化,设置CAN接口名,即当我们用ifconfig命令时显示的名字 */
strcpy((char *)(ifr.ifr_name), "can0");
ioctl(sock_fd, SIOCGIFINDEX, &ifr);
printf("can0 can_ifindex = %xn", ifr.ifr_ifindex);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
/*将刚生成的套接字与CAN套接字地址进行绑定*/
bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
/*设置CAN帧的ID号,可区分为标准帧和扩展帧的ID号*/
frame.can_id = 0x1122;
strcpy((char *)frame.data, "hello");
frame.can_dlc = strlen((char *)frame.data);
printf("Send a CAN frame from interface %sn", ifr.ifr_name);
/*开始发送数据*/
nbytes = sendto(sock_fd, &frame, sizeof(struct can_frame), 0,
(struct sockaddr *)&addr, sizeof(addr));
return 0;
}
上面两个程序看完后,大家可能会有疑问,为什么这两个程序没有listen()和accept()函数呢?
其实这两个程序是独立的运行的,并不像字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM),需要先运行服务器进行侦听。SOCK_STREAM和SOCK_DGRAM的两个server和client程序是通过网络相互收发数据。
而CAN的socket的server和client程序收发数据的对象是CAN总线
。server从CAN总线上接收数据,client将数据发到CAN总线上,当CAN总线上有数据时,server才能接收数据,当CAN总线空闲时,client才能将数据发送出去。
三 一个程序
近写了个自认为不错的基于linux socket can
程序,主要功能:
1.程序具备全部CAN功能,包括CAN标准帧/扩展帧接收与发送、CAN总线错误判断、环回等功能
2.适用基于LINUX SOCKET机制实现的CAN接口,可用于嵌入式LINUX的CAN测试
3.程序采用标准LINUX命令行参数选项形式,接受用户参数
int main(int argc, char **argv)
{
S_CanFrame sendframe, recvframe;
byte *psendframe = (byte *)&sendframe;
byte *precvframe = (byte *)&recvframe;
u_canframe_data_t *psend_data = (u_canframe_data_t *)sendframe.data;
const int can_frame_len = sizeof(S_CanFrame);
pid_t pid = -1;
int status;
int ret = 0;
char buf[128] = {0};
bool carry_bit = false;// 进位标志
int segment_id;//id for shared memo
if (parse_options(argc, argv))
{
usage(); return 0;
}
if (!find_can(port))
{
sprintf(buf, "nt错误:CAN%d设备不存在nn", port + 1);
panic(buf);
return -1;
}
close_can(port);// 必须先关闭CAN,才能成功设置CAN波特率
set_bitrate(port, bitrate);// 操作CAN之前,先要设置波特率
open_can(port, bitrate);
send_socket_fd = socket_connect(port);
recv_socket_fd = socket_connect(port);
//printf("send_socket_fd = %d, recv_socket_fd = %dn", send_socket_fd, recv_socket_fd);
if (send_socket_fd < 0 || send_socket_fd < 0)
{
disconnect(&send_socket_fd);
disconnect(&recv_socket_fd);
panic("nt打开socket can错误nn");
return -1;
}
set_can_filter();
set_can_loopback(send_socket_fd, lp);
printf_head();
memset(&sendframe, 0x00, sizeof(sendframe));
memset(&recvframe, 0x00, sizeof(recvframe));
if (extended_frame) // 指定发送帧类型:扩展帧或标准帧
{
sendframe.can_id = (send_frame_id & CAN_EFF_MASK) | CAN_EFF_FLAG;
}
else
{
sendframe.can_id = (send_frame_id & CAN_SFF_MASK);
}
sendframe.can_dlc = dlc;
memcpy(sendframe.data, send_frame_data, dlc);
segment_id = shmget(IPC_PRIVATE, sizeof(int), S_IRUSR | S_IWUSR);// allocate memo
pframeno = (int *)shmat(segment_id, NULL, 0);// attach the memo
if (pframeno == NULL)
{
panic("nt创建共享内存失败nn");
return -1;
}
*pframeno = 1;
run = true;
pid = fork();
if(pid == -1)
{
panic("nt创建进程失败nn");
return -1;
}
else if(pid == 0) // 子进程,用于发送CAN帧
{
while (run && (send_frame_times > 0))
{
ret = send_frame(send_socket_fd, (char *)&sendframe, sizeof(sendframe));
printf_frame(sendframe.can_id & CAN_EFF_MASK, sendframe.data, sendframe.can_dlc,
((sendframe.can_id & CAN_EFF_FLAG) ? true : false),
ret > 0 ? true : false,
true);
delay_ms(send_frame_freq_ms);
if (send_frame_id_inc_en)
{
sendframe.can_id++;
if (extended_frame)
{
sendframe.can_id = (sendframe.can_id & CAN_EFF_MASK) | CAN_EFF_FLAG;
}
else
{
sendframe.can_id = (sendframe.can_id & CAN_SFF_MASK);
}
}
if (send_frame_data_inc_en && dlc > 0)
{
if (dlc > 4 && psend_data->s.dl == ((__u32)0xFFFFFFFF))
{
carry_bit = true;// 发生进位
}
psend_data->s.dl++;
if (dlc <= 4)
{
if (psend_data->s.dl >= (1 << (dlc * 8)))
{
psend_data->s.dl = 0;
}
}
else if (dlc <= 8)
{
if (carry_bit)
{
psend_data->s.dh++;
if (psend_data->s.dh >= (1 << ((dlc - 4) * 8)))
{
psend_data->s.dh = 0;
}
carry_bit = false;
}
}
}
send_frame_times--;
}
exit(0);
}
else // 父进程,接收CAN帧
{
install_sig();
while (run)
{
memset(precvframe, 0x00, can_frame_len);
ret = recv_frame(recv_socket_fd, precvframe, can_frame_len, 5 * 1000);
if (ret > 0)
{
printf_frame(recvframe.can_id & CAN_EFF_MASK, recvframe.data, recvframe.can_dlc,
((recvframe.can_id & CAN_EFF_FLAG) ? true : false),
true,
false);
}
}
while(((pid = wait(&status)) == -1) && (errno == EINTR))
{
delay_ms(10);
}
}
disconnect(&send_socket_fd);
disconnect(&recv_socket_fd);
shmdt(pframeno);// detach memo
shmctl(segment_id, IPC_RMID, NULL);// remove
return 0;
}