14 CAN编程(二)

14.4 Linux socketcan基础应用编程

14.4.1 socketcan概述

​ socketcan是在Linux下CAN协议(Controller Area Network)实现的一种实现方法。 CAN是一种在世界范围内广泛用于自动控制、嵌入式设备和汽车领域的网络技术。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可以当作发送源的地址来使用。

14.4.2 socketcan基本知识点

​ 在“14.3 STM32 CAN应用编程”中我们已经完整的构建了CAN应用编程框架,但是在linux应用编程中,操作CAN底层驱动与STM32思路上相似,但是操作方法或者说调用的接口还是差异很大的,因为STM32是直接调用的SDK包或直接操作寄存器,但是linux系统是需要通过调用系统命令或linuxCAN驱动来实现物理层的操作。

因此这里我们重点介绍linux上的一些系统调用命令,和一些socketcan相关的概念。

14.4.2.1 CAN设备操作

​ CAN设备有开启、关闭、设置参数3个功能。因为linux下CAN设备是模拟网络操作的方式,这里CAN设备的开启、关闭和设置,均通过ip命令来操作。

​ 在100ask_IMX6ULL开发板上打开串口,使用“ifconfig -a”查看所有的网络节点,发现第1个节点就是“can0”。

(1)Linux CAN设备开启:

#define ip_cmd_open      "ifconfig can0 up"     /* 打开CAN0 */

说明:can0:can设备名;

up: 打开设备命令

(2)Linux CAN设备关闭:

#define ip_cmd_close      "ifconfig can0 down"    /* 关闭CAN0 */

说明:can0:can设备名;

down: 关闭设备命令

(2)Linux CAN参数设置(波特率,采样率):

#define ip_cmd_set_can_params "ip link set can0 type can bitrate 500000 triple-sampling on"

/* 将CAN0波特率设置为500000 bps */

说明:can0:can设备名;

down: 关闭设备命令

Type can: 设备类型为can

Bitrate 500000: 波特率设置为500kbps

Triple-sampleing on: 采样打开

14.4.2.2 什么是Socket套接口

​ 在linux里网络操作使用socket进行接口创建,竟然CAN设备也是虚拟成网络接口,也是使用的socket套接口。

​ 如下图所示,电话A呼叫电话B,电话A会输入电话B的号码,电话B会接收到电话A的来电。

电话A和电话B是两个端点。而linux套接口与这个电话通信类似,套接口就是一个通信的端点,端点之间是通信链路;电话通信是通过电话号码进行拨号通信,而套接口是使用地址进行识别对方的。

14.4.2.3 Socket接口函数

我们要创建并使用socket套接口进行通信编程,就需要了解一下socket相关的接口函数。

需要查询linux系统里的函数,可以通过man命令查看。

举例:

man socket /* 查看socket函数描述 */

(1)socket()函数

在linux系统下,通过“man socket”命令,查询socket()函数描述如下:

Socket函数原型如下:

#include <sys/types.h>     

#include <sys/socket.h>

int socket(int domain, int type, int protocol);  /* 套接口函数原型 */

函数三个参数如下:

domain:即协议域,又称为协议族(family)。 常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type: 指定socket类型。常用的socket类型有, SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
protocol:就是指定协议。 常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

注意:

​ 1.并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

​ 当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

​ 2. Socketcan使用的domain协议域是AF_CAN(或PF_CAN),type类型是SOCK_RAW, 指定协议protocol是CAN_RAW.

(2)bind()函数

​ 在linux系统下,通过“man bind”命令,查询bind()函数描述如下:

​ bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

​ Bind函数原型如下所示:

#include <sys/types.h>          
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

函数的三个参数分别为:

sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,

如ipv4对应的是:

struct sockaddr_in {
    sa_family_t    sin_family;   /* address family: AF_INET */
    in_port_t      sin_port;    /* port in network byte order */
    struct in_addr sin_addr;     /* internet address */
};
/* Internet address. */struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

ipv6对应的是:

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr  sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};
struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};

Unix域对应的是:

#define UNIX_PATH_MAX  108
struct sockaddr_un { 
  sa_family_t sun_family;        /* AF_UNIX */ 
  char    sun_path[UNIX_PATH_MAX]; /* pathname */ 
};

CAN域对应的是:

在文件“Linux-4.9.88\include\uapi\linux\can.h”中有定义,这个是本章需要重点了解的。

/**
 * struct sockaddr_can - CAN sockets的地址结构
 * @can_family:  地址协议族 AF_CAN.
 * @can_ifindex:  CAN网络接口索引
 * @can_addr:    协议地址信息
 */
struct sockaddr_can {
	__kernel_sa_family_t can_family;
	int         can_ifindex;
	union {
		/* 传输协议类地址信息 (e.g. ISOTP) */
		struct { canid_t rx_id, tx_id; } tp;

		/* 预留给将来使用的CAN协议地址信息*/
	} can_addr;
};

addrlen:对应的是地址的长度。

通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

(3)ioctl()函数

在linux系统下,通过“man ioctl”命令,查询ioctl()函数描述如下:

Ioctl()函数调用层次如下图所示:

Ioctl()函数原型如下:

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);

用ioctl获得本地网络接口地址时要用到两个结构体ifconf和ifreq。

struct ifreq定义
ifreq用来保存某个接口的信息。

在文件“Linux-4.9.88\include\uapi\linux\if.h”中有定义struct ifreq,这个只需要了解是在ioctl()函数调用时用来获取CAN设备索引(ifr_ifindex)使用,其他的参数可以不用关注。

/*
 * Interface request structure used for socket
 * ioctl's.  All interface ioctl's must have parameter
 * definitions which begin with ifr_name.  The
 * remainder may be interface specific.
 */

/* for compatibility with glibc net/if.h */
#if __UAPI_DEF_IF_IFREQ
struct ifreq {
#define IFHWADDRLEN	6
	union
	{
		char	ifrn_name[IFNAMSIZ];		/* if name, e.g. "en0" */
	} ifr_ifrn;
	
	union {
		struct	sockaddr ifru_addr;
		struct	sockaddr ifru_dstaddr;
		struct	sockaddr ifru_broadaddr;
		struct	sockaddr ifru_netmask;
		struct  sockaddr ifru_hwaddr;
		short	ifru_flags;
		int	ifru_ivalue;
		int	ifru_mtu;
		struct  ifmap ifru_map;
		char	ifru_slave[IFNAMSIZ];	/* Just fits the size */
		char	ifru_newname[IFNAMSIZ];
		void __user *	ifru_data;
		struct	if_settings ifru_settings;
	} ifr_ifru;
};
#endif /* __UAPI_DEF_IF_IFREQ */

#define ifr_name	ifr_ifrn.ifrn_name	/* interface name 	*/
#define ifr_hwaddr	ifr_ifru.ifru_hwaddr	/* MAC address 		*/
#define	ifr_addr	ifr_ifru.ifru_addr	/* address		*/
#define	ifr_dstaddr	ifr_ifru.ifru_dstaddr	/* other end of p-p lnk	*/
#define	ifr_broadaddr	ifr_ifru.ifru_broadaddr	/* broadcast address	*/
#define	ifr_netmask	ifr_ifru.ifru_netmask	/* interface net mask	*/
#define	ifr_flags	ifr_ifru.ifru_flags	/* flags		*/
#define	ifr_metric	ifr_ifru.ifru_ivalue	/* metric		*/
#define	ifr_mtu		ifr_ifru.ifru_mtu	/* mtu			*/
#define ifr_map		ifr_ifru.ifru_map	/* device map		*/
#define ifr_slave	ifr_ifru.ifru_slave	/* slave device		*/
#define	ifr_data	ifr_ifru.ifru_data	/* for use by interface	*/
#define ifr_ifindex	ifr_ifru.ifru_ivalue	/* interface index	*/
#define ifr_bandwidth	ifr_ifru.ifru_ivalue    /* link bandwidth	*/
#define ifr_qlen	ifr_ifru.ifru_ivalue	/* Queue length 	*/
#define ifr_newname	ifr_ifru.ifru_newname	/* New name		*/
#define ifr_settings	ifr_ifru.ifru_settings	/* Device/proto settings*/

struct ifconf定义
ifconf通常是用来保存所有接口信息的,本章节未使用到,在此不作详细介绍。

(4)setsockopt()函数

​ 在linux系统下,通过“man setsockopt”命令,查询setsockopt()函数描述如下:


setsockopt()和getsockopt函数原型如下:

#include <sys/types.h>    
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

Setsockopt()用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。

其函数参数如下:可以看出其参数

sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP,IPPROTO_IPV6,SOL_CAN_RAW等。
optname:需设置的选项。
optval:指针,指向存放选项待设置的新值的缓冲区
optlen:optval缓冲区长度。

函数调用示例如下:

示例1:设置CAN过滤器为不接收所有报文。
//禁用过滤规则,本进程不接收报文,只负责发送

//设置过滤规则 setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
示例2:设置CAN过滤器为接收某个指定报文
//定义接收规则,只接收表示符等于 0x201 的报文

//在linux头文件有定义,也可以自己定义
#define CAN_SFF_MASK 0x000007ffU

//定义过滤器(1个)
struct can_filter rfilter[1];
rfilter[0].can_id = 0x201;
rfilter[0].can_mask = CAN_SFF_MASK;
//设置过滤规则
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
示例2:设置CAN过滤器为接收某个指定报文
//定义接收规则,只接收表示符等于 0x201 的报文

//在linux头文件有定义,也可以自己定义
#define CAN_SFF_MASK 0x000007ffU

//定义过滤器(3个)
struct can_filter rfilter[3];
rfilter[0].can_id = 0x201;
rfilter[0].can_mask = CAN_SFF_MASK;

rfilter[1].can_id = 0x401;
rfilter[1].can_mask = CAN_SFF_MASK;

rfilter[2].can_id = 0x601;
rfilter[2].can_mask = CAN_SFF_MASK;

//设置过滤规则
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));

(5)write()函数

在linux系统下,通过“man 2 write”命令,查询write()函数描述如下:

Write函数原型如下:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

(6)read()函数

在linux系统下,通过“man 2 read”命令,查询read()函数描述如下:

Read函数原型如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

(7)close()函数

在linux系统下,通过“man 2 close”命令,查询close()函数描述如下:

close()函数原型如下:

#include <unistd.h>
int close(int fd);

14.4.3 socket_can简单发送实例

简单发送实例代码目录:“02_socketcan_send”

案例描述:

  1. 实现周期1秒发送报文ID:0x101的报文;

了解内容:IMX6 CAN接口电路

从下面CAN外围电路看,和STM32是完全相同的,只是处理内部的CAN控制器因为不同芯片制造厂家的不同,会有一些较小的差异。这里电路只是对比了解一下,做linux应用可以不需要关注底层驱动处理。

那我们现在按照14.3章节构建STM32下CAN应用编程的框架,一步一步编写linux下socketCAN的应用编程。

准备工作:

我们按照14.3章节准备好can应用的代码文件:

文件名 文件内容描述
App_can.c CAN应用功能实现
App_can.h CAN应用功能头文件
Can_controller.c CAN驱动操作抽象层具体实现
Can_controller.h CAN驱动操作抽象层头文件
Can_msg.h CAN报文基本结构体,从STM32 CAN驱动拷贝过来的,主要在使用CAN报文时使用我们最熟悉的结构体。 此文件相对STM32为新增文件,因为我们的框架是基于单片机应用,然后类比迁移学习到linux上。
Makefile Makefile编译脚本

14.4.3.1 编写抽象框架的实现函数

首先我们使用14.3章节已经构建好的抽象结构体,如下:

见第14章节代码“02_socketcan_send_addline”中“can_controller.h”。

34 /* CAN通信抽象结构体定义*/
35 typedef struct _CAN_COMM_STRUCT
36 {
37     /* CAN硬件名称 */
38     char *name;
39     /* CAN端口号,裸机里为端口号;linux应用里作为socket套接口 */
40     int  can_port;                                
41     /* CAN控制器配置函数,返回端口号赋值给can_port */
42     int  (*can_set_controller)( void );                  
43     /* CAN接口中断创建,在linux中对应创建接收线程 */
44     void (*can_set_interrput)( int can_port , pCanInterrupt callback );             
45     /* CAN读取报文接口 */
46     void (*can_read)( int can_port , CanRxMsg* recv_msg);   
47     /* CAN发送报文接口*/
48     void (*can_write)( int can_port , CanTxMsg send_msg);   
49 }CAN_COMM_STRUCT, *pCAN_COMM_STRUCT;
50 

我们就按照这个结构体的顺序依次编写can_controller.c中的CAN驱动操作具体实现函数。

(1)定义CAN设备

根据14.4.2.1章节描述,linux应用层操作CAN设备,需要知道设备名。.

在100ask_IMX6ULL开发板上打开串口,使用“ifconfig -a”命令查看,知道当前CAN设备名称为”can0”。

直接在linux命令行直接使用ip命令可以打开,设置,和关闭CAN设备,因此我们定义了三个宏ip_cmd_open, ip_cmd_close,ip_cmd_set_can_params, 这三个宏可以通过系统调用system()进行执行。

见第14章节代码“02_socketcan_send_addline”中“can_controller.c”文件中宏定义。

29 /**************宏定义**************************************************/
30 
31 /* 将CAN0波特率设置为500000 bps */
32 #define ip_cmd_set_can_params  "ip link set can0 type can bitrate 500000 triple-sampling on"
33 
34 /* 打开CAN0 */
35 #define ip_cmd_open            "ifconfig can0 up"     
36 
37 /* 关闭CAN0 */    
38 #define ip_cmd_close           "ifconfig can0 down"   

(2)配置CAN控制器

配置CAN控制器有3个部分:打开can0设备,CAN波特率配置,CAN过滤器配置。

见第14章节代码“01_stm32f407_can_addline”中“can_controller.c”文件int CAN_Set_Controller( void )函数。

A.配置波特率,打开can0设备

使用(1)中的三个命令ip_cmd_open, ip_cmd_close,ip_cmd_set_can_params,通过system系统调用:具体代码如下

77     /* 通过system调用ip命令设置CAN波特率 */
78     system(ip_cmd_close);               
79     system(ip_cmd_set_can_params);
80     system(ip_cmd_open);

B.创建套接口

因为linux应用操作设备均使用读read写write操作,linux一切皆文件,而socketcan又是一个特殊的文件,因此我们需要调用socket()函数创建一个socketcan接口,获取sock_fd描述符。

具体代码如下:

82   /*************************************************************/
83   /* 创建套接口 sock_fd */
84   sock_fd = socket(AF_CAN, SOCK_RAW, CAN_RAW);
85 	if(sock_fd < 0)
86 	{
87 		perror("socket create error!\n");
88 		return -1;
89 	}

C.绑定can0设备与套接口

具体代码如下:

92   //将套接字与 can0 绑定
93   strcpy(ifr.ifr_name, "can0");
94 	ioctl(sock_fd, SIOCGIFINDEX,&ifr); // 设置设备为can0
95 
96 	ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
97 	printf("ifr_name:%s \n",ifr.ifr_name);
98 	printf("can_ifindex:%d \n",ifr.ifr_ifindex);
99 
100 addr.can_family = AF_CAN;
101 addr.can_ifindex = ifr.ifr_ifindex;
102 		
103 if( bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 )
104 {
105 	perror("bind error!\n");
106 	return -1;
107 }

C.配置过滤器

具体代码如下:

109 	/*************************************************************/
110 	//禁用过滤规则,本进程不接收报文,只负责发送
111  setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); 

D.配置非阻塞操作

Linux系统调用read和write函数有阻塞和非阻塞,我们在周期调用时,此采用非阻塞方式对CAN报文进行读写操作。

具体代码实现如下:

114     //设置read()和write()函数设置为非堵塞方式
115     int flags;
116     flags = fcntl(sock_fd, F_GETFL);
117     flags |= O_NONBLOCK;
118     fcntl(sock_fd, F_SETFL, flags);  

E.返回sock_fd套接口

具体代码实现如下:

int CAN_Set_Controller( void )函数直接结束后,返回值赋值给CAN_COMM_STRUCT的can_port成员。

后续应用层所访问的sock_fd描述符即为can_port.

(3)创建CAN接收线程

在STM32中,接收使用的接收FIFO中断进行处理,在linux应用中,我们则采用线程轮询去读取报文。

因此我们需要创建一个CAN接收线程,具体代码实现如下:

127 /**********************************************************************
128 * 函数名称: void CAN_Set_Interrupt(int can_port,  pCanInterrupt callback)
129 * 功能描述: 创建CAN接收线程,并传入应用的的回调函数,回调函数主要处理应用层的功能
130 * 输入参数: can_port,端口号
131 *          callback: 中断具体处理应用功能的回调函数
132 * 输出参数: 无
133 * 返 回 值: 无
134 * 修改日期             版本号        修改人           修改内容
135 * -----------------------------------------------
136 * 2020/05/13         V1.0             bert            创建
137 ***********************************************************************/
138 void CAN_Set_Interrupt(int can_port,  pCanInterrupt callback)
139 {
140     int err;
141     
142     if ( NULL != callback ) 
143     {
144         g_pCanInterrupt = callback;
145     }
146     
147     err = pthread_create(&ntid, NULL,CAN1_RX0_IRQHandler, NULL );
148     if( err !=0 )
149     {
150         printf("create thread fail! \n");
151         return ;
152     }
153     printf("create thread success!\n");
154     
155 
156     return ;
157 }

创建后的线程函数如下所示:

CAN1_RX0_IRQHandler是一个CAN接收线程函数,与CAN接收中断功能相似,只这里采用轮询方式读取CAN报文。

253 /**********************************************************************
254 * 函数名称: void CAN1_RX0_IRQHandler(void)
255 * 功能描述: CAN接收线程函数
256 * 输入参数: 无  
257 * 输出参数: 无
258 * 返 回 值: 无
259 * 修改日期             版本号        修改人           修改内容
260 * -----------------------------------------------
261 * 2020/05/13         V1.0             bert            创建
262 ***********************************************************************/
263 void *CAN1_RX0_IRQHandler(void *arg)
264 {
265     /* 接收报文定义 */
266     while( 1 )
267     {
268     /* 如果回调函数存在,则执行回调函数 */
269         if( g_pCanInterrupt != NULL)
270         {
271             g_pCanInterrupt();
272         }
273         usleep(10000);
274     }
275 }

(4)CAN报文读取函数

161 /**********************************************************************
162 * 函数名称: void CAN_Read(int can_port, CanRxMsg* recv_msg)
163 * 功能描述: CAN读取接收寄存器,取出接收到的报文
164 * 输入参数: can_port,端口号     
165 * 输出参数: recv_msg:接收报文
166 * 返 回 值: 无
167 * 修改日期             版本号        修改人           修改内容
168 * -----------------------------------------------
169 * 2020/05/13         V1.0             bert            创建
170 ***********************************************************************/
171 void CAN_Read(int can_port, CanRxMsg* recv_msg)
172 { 
173     unsigned char i;
174     static unsigned int rxcounter =0;
175     
176     int nbytes;
177     struct can_frame rxframe;
178     
179     
180     nbytes = read(can_port, &rxframe, sizeof(struct can_frame));
181 	if(nbytes>0)
182 	{
183 	    printf("nbytes = %d \n",nbytes );
184 	    
185 	    recv_msg->StdId = rxframe.can_id;
186 	    recv_msg->DLC = rxframe.can_dlc;
187 	    memcpy( recv_msg->Data, &rxframe.data[0], rxframe.can_dlc);
188 	    
189 		rxcounter++;
190 		printf("rxcounter=%d, ID=%03X, DLC=%d, data=%02X %02X %02X %02X %02X %02X %02X %02X \n",  \
191 			rxcounter,
192 			rxframe.can_id, rxframe.can_dlc,  \
193 			rxframe.data[0],\
194 			rxframe.data[1],\
195 			rxframe.data[2],\
196 			rxframe.data[3],\
197 			rxframe.data[4],\
198 			rxframe.data[5],\
199 			rxframe.data[6],\
200 			rxframe.data[7] );
201 	}
202 
203     return ;
204 }
205  

(5)CAN报文发送函数

206 /**********************************************************************
207 * 函数名称: void CAN_Write(int can_port, CanTxMsg send_msg)
208 * 功能描述: CAN报文发送接口,调用发送寄存器发送报文
209 * 输入参数: can_port,端口号     
210 * 输出参数: send_msg:发送报文
211 * 返 回 值: 无
212 * 修改日期             版本号        修改人           修改内容
213 * -----------------------------------------------
214 * 2020/05/13         V1.0             bert            创建
215 ***********************************************************************/
216 void CAN_Write(int can_port, CanTxMsg send_msg)
217 {
218     unsigned char i;
219     static unsigned int txcounter=0;
220     int nbytes;
221     
222     struct can_frame txframe;
223     
224     txframe.can_id = send_msg.StdId;
225     txframe.can_dlc = send_msg.DLC;
226     memcpy(&txframe.data[0], &send_msg.Data[0], txframe.can_dlc);
227 
228     nbytes = write(can_port, &txframe, sizeof(struct can_frame)); //发送 frame[0]
229 	
230 	if(nbytes == sizeof(txframe))
231 	{
232 	    txcounter++;
233 	    printf("txcounter=%d, ID=%03X, DLC=%d, data=%02X %02X %02X %02X %02X %02X %02X %02X \n",  \
234 			txcounter,
235 			txframe.can_id, txframe.can_dlc,  \
236 			txframe.data[0],\
237 			txframe.data[1],\
238 			txframe.data[2],\
239 			txframe.data[3],\
240 			txframe.data[4],\
241 			txframe.data[5],\
242 			txframe.data[6],\
243 			txframe.data[7] );
244     }
245     else
246 	{
247 		//printf("Send Error frame[0], nbytes=%d\n!",nbytes);
248 	}
249 
250     return ;
251 }
252 

(6)CAN抽象结构体框架初始化

与14.3章节STM32定义实例类似。

定义一个can1通信结构实例CAN_COMM_STRUCT can1_controller;

使用(1)~(5)步骤实现的函数,初始化can1_controller,构成与应用层关联的一个连接点。

298 /**********************************************************************
299 * 名称:     can1_controller
300 * 功能描述: CAN1结构体初始化
301 * 修改日期             版本号        修改人           修改内容
302 * -----------------------------------------------
303 * 2020/05/13         V1.0             bert            创建
304 ***********************************************************************/
305 CAN_COMM_STRUCT can1_controller = {
306     .name                   = "can0",
307     .can_port               = CAN_PORT_CAN1,
308     .can_set_controller     = CAN_Set_Controller,
309     .can_set_interrput      = CAN_Set_Interrupt,
310     .can_read               = CAN_Read,
311     .can_write              = CAN_Write,
312 };

请问一下这个源码在哪能找到,我下载的资料中没有