提醒:使用Linux CAN开发的需要具备网络编程的部分基础,Socket CAN的使用类似于TCP/IP
Linux开发板通过Socket can驱动设备的参考源码demo见文末。
CAN,全称为“Controller Area Network”,即控制器局域网,是国际上应用最广泛的现场总线之一。 最初,CAN 被设计作为汽车环境中的微控制器通讯,在车载各电子控制装置 ECU 之间交换信息,形成汽车电子控制网络。比如:发动机管理系统、变速箱控制器、仪表装备、电子主干系统中,均嵌入 CAN 控制装置。
一个由 CAN 总线构成的单一网络中,理论上可以挂接无数个节点。实际应用中,节点数目受网络硬件的电气特性所限制。例如,当使用 Philips P82C250 作为 CAN 收发器时,同一网络中允许挂接 110 个节点。CAN 可提供高达 1Mbit/s 的数据传输速率,这使实时控制变得非常容易。另外,硬件的错误检定特性也增强了 CAN 的抗电磁干扰能力。CAN 已经在汽车工业、航空工业、工业控制、安全防护等领 域中得到了广泛应用。
CAN 通讯协议主要描述设备之间的信息传递方式。CAN 层的定义与开放系统互连模型(OSI)一致。每 一层与另一设备上相同的那一层通讯。虽然CAN传输协议参考了OSI 七层模型,但是实际上CAN协议只定义了两层“物理层”和“数据链路层”,因此出现了各种不同的“应用层”协议,比如用在自动化技术的现场总线标准DeviceNet,用于工业控制的CanOpen,用于乘用车的诊断协议OBD、UDS(统一诊断服务,ISO14229),用于商用车的CAN总线协议SAEJ1939。
层次 |
描述 |
应用层 |
主要定义CAN应用层。 |
数据链路层 |
数据链路层分为逻辑链接控制子层LLC和介质访问控制子层MAC。 MAC 子层是 CAN 协议的核心。它把接收到的报文提供给 LLC 子层,并接收来自 LLC 子层的报文。 MAC 子层负责报文分帧、仲裁、应答、错误检测和标定。MAC 子层也被称作故障界定的管理实体监管 LLC 子层涉及报文滤波、过载通知、以及恢复管理。 LLC = Logical Link Control MAC = Medium Access Control |
物理层 |
物理层,为物理编码子层PCS. 该层定义信号是如何实际地传输的,因此涉及到位时间、位编码、同步。 |
CAN总线是一种分布式的控制总线,传输差分信号。
CAN总线作为一种控制器局域网,和普通以太网一样,它的网络很多CAN节点构成。
CAN网络的每个节点非常简单,均由一个MCU(微控制器)、一个CAN控制器和一个CAN收发器构成,然后使用双绞线连接到CAN网络中。
模分信号使用电平的绝对值来表示逻辑的差别,差分信号使用两个电平的差值来表示逻辑值。CAN传输使用两根数据线 ------------- CANH和CANL。
如果CANH(3.5V)和CANL(1.5V)的电压差 = 2V,此时表示逻辑0,叫做显性电平
如果CANH(2.5V)和CANL(2.5V)的电压差 = 0V,此时表示逻辑1,叫做隐性电平
//如果多个节点同时控制总线电平,最终显示的是显性电平(0)
CAN使用5种通信帧,其中数据帧是使用最多的帧类型,以数据帧为例来介绍CAN的帧结构。
数据帧的分析:
数据帧分为11位标准数据帧和29位扩展数据帧。
D --- 显性电平 R --- 隐性电平
CAN的数据帧由7部分组成:
(1)帧起始。表示数据帧开始的段。
1位显性电平
(2)仲裁段。表示该帧优先级的段。
用来实现帧的优先级和帧的过滤
1.标准帧
标准数据帧的仲裁段由11位ID和1位RTR位组成,RTR用来区分数据帧(显性电平)和遥控帧
2.扩展帧
扩展数据帧由29位ID,1位RTR,1位SRR和1位IDE组成,RTR用来区分数据帧(显性电平)和遥控帧
SRR用来代替标准帧中的RTR位,由于SRR是隐性电平,相同ID的标准帧优先级高于扩展帧
IDE用来区分标准帧(显性电平)和扩展帧,显性电平表示标准帧,隐性电平表示扩展帧
报文的优先级由总线通过ID仲裁来判断,当总线上同时出现显性电平和隐形电平时,最终显示为显性电平
当多个节点同时竞争总线占有权时,谁先出现隐形电平,将失去总线占有权,转为接收状态
(3)控制段。表示数据的字节数及保留位的段。
r0,r1为保留位,默认是显性电平
4位DLC表示数据段的长度(0~8)
(4)数据段。数据的内容,一帧可发送0~8个字节的数据。
长度0~8字节,高位先出
(5)CRC段。检查帧的传输错误的段。
CRC错误校验,由15位CRC校验码和1位CRC界定符组成
校验出了错误信息,可利用错误帧请求重发,重发次数可设定
(6)ACK段。表示确认正常接收的段。
由1位ACK槽和1位ACK界定符组成,发送方的ACK槽是隐性电平
接收方确认收到正确的数据后以显性电平应答
(7)帧结束。表示数据帧结束的段。
7位隐形电平
Socket CAN是在Linux下CAN协议(Controller Area Network)实现的一种实现方法。Linux下最早使用CAN的方法是基于字符设备来实现的,与之不同的是Socket CAN使用伯克利的socket接口和linux网络协议栈,这种方法使得can设备驱动可以通过网络接口来调用。Socket CAN的接口被设计的尽量接近TCP/IP的协议。
使用Socket CAN的主要目的就是为用户空间的应用程序提供基于Linux网络层的套接字接口。与广为人知的TCP/IP协议以及以太网不同,CAN总线没有类似以太网的MAC层地址,只能用于广播。CAN ID仅仅用来进行总线的仲裁。因此CAN ID在总线上必须是唯一的。当设计一个CAN-ECU(Electronic Control Unit 电子控制单元)网络的时候,CAN报文ID可以映射到具体的ECU。因此CAN报文ID可以当作发送源的地址来使用。
socket can通信常用函数
(1)、socket()函数
(2)、bind()函数
(3)、ioctl()函数
(4)、setsockopt()函数
(5)、write()函数
(6)、read()函数
(7)、close()函数
(8)、close()函数
USB转CAN工具上位机循环发送数据给开发板
Linux开发板运行接收程序,接收到来自CAN工具发送过来的数据
CAN驱动需要预先适配,才能使用,部分设备由于开发者自身此前在驱动开发时未进行CAN的驱动适配,或厂家并未提供CAN驱动适配,会导致无法进行CAN开发。
请提前检查Linux开发板的CAN是否正常。可使用如下指令测试CAN是否正常:
1、关闭CAN口
ip link set can0 down
2、设置 can0 传输速率为 500kbps
ip link set can0 type can bitrate 500000
3、打印can0 的信息
ip -details link show can0
4、打开 can0 接口
ip link set can0 up
如上图所示为CAN驱动存在故障,在重新烧录适配CAN驱动的Linux系统后,可恢复正常使用。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct
{
unsigned int StdId; /* CAN标准帧ID,占11bit,范围:0~0x7FF */
unsigned int ExtId; /* CAN扩展帧ID,占29bit,范围:0~0x1FFFFFFF */
unsigned char IDE; /* CAN报文ID类型,CAN_ID_STD 或 CAN_ID_EXT */
unsigned char RTR; /* CAN报文类型,CAN_RTR_DATA 或 CAN_RTR_REMOTE */
unsigned char DLC; /* CAN报文数据长度, 范围:0~8 */
unsigned char Data[8]; /* CAN报文数据内容,每个字节范围:0~0xFF*/
} CanTxMsg;
typedef struct
{
unsigned int StdId; /* CAN标准帧ID,范围:0~0x7FF */
unsigned int ExtId; /* CAN扩展帧ID,范围:0~0x1FFFFFFF */
unsigned char IDE; /* CAN报文ID类型,CAN_ID_STD 或 CAN_ID_EXT */
unsigned char RTR; /* CAN报文类型,CAN_RTR_DATA 或 CAN_RTR_REMOTE */
unsigned char DLC; /* CAN报文数据长度, 范围:0~8 */
unsigned char Data[8]; /* CAN报文数据内容,每个字节范围:0~0xFF*/
unsigned char FMI; /* 过滤模式,总共有14中,定义有宏,其值依次为0x1,0x2,0x4,0x8,0x10,0x20,0x40……,此处可以不考虑,忽略*/
} CanRxMsg;
/**
* @brief Linux socket can通信初始化
* @param fd:CAN通信文件描述符
* @retval 成功返回0,失败返回-1
*/
int can_init_for_linux(int *fd)
{
//0、设置can通信波特率
system("ip link set can0 down");
//波特率设置为500Kbps
system("ip link set can0 type can bitrate 500000");
system("ip link set can0 up");
int ret = 0;
//1、创建socket can套接字
*fd = socket(AF_CAN, SOCK_RAW, CAN_RAW);
if(*fd == -1)
{
perror("create socket can failed");
return -1;
}
//2、套接子绑定到can0端口
struct sockaddr_can addr;
struct ifreq ifr;
strcpy(ifr.ifr_name, "can0");
ioctl(*fd, SIOCGIFINDEX, &ifr);
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
ret = bind(*fd, (struct sockaddr *)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind can0 error");
return -1;
}
//4、设置CAN过滤规则
#if 0
struct can_filter recv_filter;
recv_filter.can_id = 0x201;
recv_filter.can_mask = CAN_SFF_MASK;
setsockopt(*can_fd, SOL_CAN_RAW, CAN_RAW_FILTER, &recv_filter, sizeof(recv_filter));
#endif
//5、设置read、write为非堵塞方式
int flags;
flags = fcntl(*fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(*fd, F_SETFL, flags);
return 0;
}
/**
* @brief 关闭CAN通信文件描述符
* @param fd:CAN通信文件描述符
* @retval void
*/
void can_close(int fd)
{
close(fd);
}
/**
* @brief CAN发送数据
* @param fd:CAN通信文件描述符
* @param can_id:can设备id
* @param data:需要发送的数据(一次最大8字节)
* @param data_len:需要发送数据长度(Byte)
* @retval 成功返回0,失败返回-1
*/
int can_write_data(int fd, int can_id,char data[8],int data_len)
{
int ret = 0;
CanRxMsg msg;
struct can_frame send_data;
if(fd == -1)
{
perror("can write data failed!");
return -1;
}
send_data.can_dlc = data_len;
send_data.can_id = can_id;
memcpy(&send_data.data[0], data, data_len);
ret = write(fd, &send_data, sizeof(struct can_frame));
if(ret == -1)
{
perror("can write data failed!");
return -1;
}else{
printf("ID=%03X, DLC=%d, data=%02X %02X %02X %02X %02X %02X %02X %02X \n", \
send_data.can_id, send_data.can_dlc, \
send_data.data[0],\
send_data.data[1],\
send_data.data[2],\
send_data.data[3],\
send_data.data[4],\
send_data.data[5],\
send_data.data[6],\
send_data.data[7] );
}
return 0;
}
/**
* @brief CAN发送数据
* @param fd:CAN通信文件描述符
* @param can_id:can设备id
* @param data:存储CAN接收的8字节数据
* @retval 成功返回0,失败返回-1
*/
int can_read_data(int fd, int can_id, char data[8])
{
if(fd == -1)
{
perror("can read data failed!");
return -1;
}
int ret = 0;
struct can_frame recv_data;
recv_data.can_id = can_id;
recv_data.can_dlc = 8;
ret = read(fd, &recv_data,sizeof(struct can_frame));
if(ret == -1)
{
perror("can read data failed!");
return -1;
}else{
memcpy(data, &recv_data.data[0], recv_data.can_dlc);
if(recv_data.can_id != can_id)
{
printf("recv other can id data\n");
return -1;
}
printf("ID=%03X, DLC=%d, data=%02X %02X %02X %02X %02X %02X %02X %02X \n", \
recv_data.can_id, recv_data.can_dlc, \
recv_data.data[0],\
recv_data.data[1],\
recv_data.data[2],\
recv_data.data[3],\
recv_data.data[4],\
recv_data.data[5],\
recv_data.data[6],\
recv_data.data[7] );
}
return 0;
}
/**
* @brief 主函数
* @param NONE
* @retval 成功返回0,失败返回-1
*/
int main(int argc, char **argv)
{
int can_fd;
int ret = 0;
ret = can_init_for_linux(&can_fd);
if(ret == -1)
{
perror("can init failed!\n");
return -1;
}
char data[8] = {0};
while (1)
{
sleep(2);
can_read_data(can_fd, 0x01, data);
}
printf("hello world!\n");
return 0;
}
嵌入式Linux C串口硬件驱动编程_linux c串口 csdn-CSDN博客