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秒发送报文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 };