您的位置:首页 > 运维架构 > Linux

TX2平台下can总线收发功能的实现(二)——Linux下can总线编程模型和源码解读

2018-03-12 23:00 2176 查看
上回初步了解了Linux下canbus的通讯办法,这次要更加深入一些。。。
进入github找到开源应用程序:can-utils  地址:https://github.com/linux-can/can-utils



有400多个星,不算少。下载下来,按照套路应该是:解压->./config->install->make install

但can-utils比较狠,可以自己自动配置。运行脚本:autogen.sh



之后运行./configure进行真正的配置
然后vim Makefile 看看这个程序的结构



结构非常简单的一个程序,原程序实现了比较多的功能,但我只关注发送和接收,其中canbus的配置之前上一篇文章中是采用ip link set的命令,因此应该是系统函数实现了,暂时不去管了。打开源代码:



大多功能都是包含的标准库,发送和接收基本编程模型和Linux下的socket编程模型在许多细节上不同,但是都有基本的建立套接字和bind还是有的,要事半功倍就需要了解编程模型,因此百度Linux can总线编程模型,得到:







基本编程模板的流程为:
(1)创建套接字socket
(2)指定can设备号
(3)绑定bind
(4)如果是发送,就禁用过滤;如果是接受就设置过滤条件
(5)对套接字的fd进行read,write操作实现收发功能
再查看can-utils的发送源代码,英文注释为原作者注释,中文为我后续加上去的#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <linux/can.h>
#include <linux/can/raw.h>

#include "lib.h"

int main(int argc, char **argv)//cansend <can_interface> <can_frame>
{
int s; /* can raw socket */
int required_mtu; //传输内容的最大传输内容
int mtu;                    //发送数据长度
int enable_canfd = 1; //开启关闭flag位
struct sockaddr_can addr; //can总线的地址 同socket编程里面的 socketaddr结构体 用来设置can外设的信息
struct canfd_frame frame; //要发送的buffer
struct ifreq ifr; //接口请求结构体

/* check command line options */ //检测命令行参数是不是3个
if (argc != 3) {
fprintf(stderr, "Usage: %s <device> <can_frame>.\n", argv[0]);
return 1;
}

/* parse CAN frame */
required_mtu = parse_canframe(argv[2], &frame);//检验要发送的can报文是否符合标准,同时组成canbus报文
if (!required_mtu){                            //报错信息
fprintf(stderr, "\nWrong CAN-frame format! Try:\n\n");
fprintf(stderr, " <can_id>#{R|data} for CAN 2.0 frames\n");
fprintf(stderr, " <can_id>##<flags>{data} for CAN FD frames\n\n");
fprintf(stderr, "<can_id> can have 3 (SFF) or 8 (EFF) hex chars\n");
fprintf(stderr, "{data} has 0..8 (0..64 CAN FD) ASCII hex-values (optionally");
fprintf(stderr, " separated by '.')\n");
fprintf(stderr, "<flags> a single ASCII Hex value (0 .. F) which defines");
fprintf(stderr, " canfd_frame.flags\n\n");
fprintf(stderr, "e.g. 5A1#11.2233.44556677.88 / 123#DEADBEEF / 5AA# / ");
fprintf(stderr, "123##1 / 213##311\n 1F334455#1122334455667788 / 123#R ");
fprintf(stderr, "for remote transmission request.\n\n");
return 1;
}

/* 创建socket套接字
PF_CAN 为域位 同网络编程中的AF_INET 即ipv4协议
SOCK_RAW使用的协议类型 SOCK_RAW表示原始套接字 报文头由自己创建
CAN_RAW为使用的具体协议 为can总线协议
*/
/* open socket */
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
perror("socket");
return 1;
}

/*接口请求结构用于套接字ioctl的所有接口
ioctl的一定参数的定义开始ifr_name。
其余部分可能是特定于接口的。*/
strncpy(ifr.ifr_name, argv[1], IFNAMSIZ - 1); //给借口请求结构体赋予名字
ifr.ifr_name[IFNAMSIZ - 1] = '\0'; //字符串结束位
ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name); //指定网络接口名称字符串作为参数;若该接口存在,则返回相应的索引,否则返回0 就是检验该接口是否存在!
if (!ifr.ifr_ifindex) {
perror("if_nametoindex");
return 1;
}

addr.can_family = AF_CAN; //协议类型
addr.can_ifindex = ifr.ifr_ifindex; //can总线外设的具体索引 类似 ip地址

if (required_mtu > CAN_MTU) { //Maximum Transmission Unit最大传输单元超过了

/* check if the frame fits into the CAN netdevice */
if (ioctl(s, SIOCGIFMTU, &ifr) < 0) { //获取can借口最大传输单元
perror("SIOCGIFMTU");
return 1;
}
mtu = ifr.ifr_mtu;

if (mtu != CANFD_MTU) { //和canbus最大传输单元不符合 就报错
printf("CAN interface is not CAN FD capable - sorry.\n");
return 1;
}

/* interface is ok - try to switch the socket into CAN FD mode */
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, //用于任意类型、任意状态套接口的设置选项值
&enable_canfd, sizeof(enable_canfd))){
printf("error when enabling CAN FD support\n");
return 1;
}

/* ensure discrete CAN FD length values 0..8, 12, 16, 20, 24, 32, 64 *///检验数据是否为8字节的整数倍
frame.len = can_dlc2len(can_len2dlc(frame.len));                    
}

/* disable default receive filter on this RAW socket */
/* This is obsolete as we do not read from the socket at all, but for */
/* this reason we can remove the receive list in the Kernel to save a */
/* little (really a very little!) CPU usage. */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用过滤

if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { //绑定套接字 就是 套接字和canbus外设进行绑定
perror("bind");
return 1;
}

/* send frame */
if (write(s, &frame, required_mtu) != required_mtu) { //发送数据!
perror("write");
return 1;
}

close(s); //关闭套接字

return 0;
}源码中组成报文格式的过程由parse_canframe这个函数来完成,can总线的报文一帧最大字节数为4字节,在linux的结构体中有对can总线报文定义的结构体:
struct can_frame {
canid_t can_id;//CAN 标识符
__u8 can_dlc;//数据场的长度
__u8 data[8];//数据

};
如果从头编程,那就要自己手动写函数,手动构成报文。

但是在can-utils中,它有函数已经对组成报文这一功能进行了封装,并且自己定义了命令行下的发送识别符号 * CAN 2.0 frames
 * - string layout <can_id>#{R{len}|data}
 * - {data} has 0 to 8 hex-values that can (optionally) be separated by '.'
 * - {len} can take values from 0 to 8 and can be omitted if zero
 * - return value on successful parsing: CAN_MTU
* 123# -> standard CAN-Id = 0x123, len = 0
* 12345678# -> extended CAN-Id = 0x12345678, len = 0
* 123#R -> standard CAN-Id = 0x123, len = 0, RTR-frame
* 123#R0 -> standard CAN-Id = 0x123, len = 0, RTR-frame
* 123#R7 -> standard CAN-Id = 0x123, len = 7, RTR-frame
* 7A1#r -> standard CAN-Id = 0x7A1, len = 0, RTR-frame
*
* 123#00 -> standard CAN-Id = 0x123, len = 1, data[0] = 0x00
* 123#1122334455667788 -> standard CAN-Id = 0x123, len = 8
* 123#11.22.33.44.55.66.77.88 -> standard CAN-Id = 0x123, len = 8
* 123#11.2233.44556677.88 -> standard CAN-Id = 0x123, len = 8
* 32345678#112233 -> error frame with CAN_ERR_FLAG (0x2000000) set
*
* 123##0112233 -> CAN FD frame standard CAN-Id = 0x123, flags = 0, len = 3
* 123##1112233 -> CAN FD frame, flags = CANFD_BRS, len = 3
* 123##2112233 -> CAN FD frame, flags = CANFD_ESI, len = 3
* 123##3 -> CAN FD frame, flags = (CANFD_ESI | CANFD_BRS), len = 0
123#就表示发送的是标准数据帧,123#R7,表示数据帧在#后加R和数字表示数据的长度,数据为0到8个十六进制值。

发送解析结束,之后就是接收,由于can-utils中对于接收的功能做了许多工作,有许多选项,代码有1000多行,为了快速完成这一任务,采用网上搜索到的编程模型进行解析:/* 2. 报文过滤接收程序 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main()
{
int s, nbytes;                                //socket套接字和发送字节数目
struct sockaddr_can addr;                     //设置用结构体
struct ifreq ifr;                             //指定can设备用结构体   
struct can_frame frame;                       //接收数据的buffer
struct can_filter rfilter[1];                 //过滤用的结构体
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);      //创建套接字
strcpy(ifr.ifr_name, "can0" );                
ioctl(s, SIOCGIFINDEX, &ifr);                 //指定 can0 设备
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //将套接字与 can0 绑定

rfilter[0].can_id = 0x11;                        //定义接收规则,只接收表示符等于 0x11 的报文
rfilter[0].can_mask = CAN_SFF_MASK;

setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter)); //设置过滤规则
while(1)                                       //while读取canbus消息
{
nbytes = read(s, &frame, sizeof(frame));       //接收报文//显示报文
if(nbytes > 0)
{
printf(“ID=0x%X DLC=%d data[0]=0x%X\n”, frame.can_id,
frame.can_dlc, frame.data[0]);
}
}
close(s);
return 0;}
OK!读取和发送canbus数据的代码解析就已经完成了,由于在问题中提到can总线的收发需要注意什么,那我认为应该就是发送线程的线程同步,和高并发通讯下的接收线程的处理。。。大概。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: