Android操作CAN总线(基于linux-canutils库,发送、接收、设置过滤器)

06-01 714阅读

文章目录

    • 一、前言
    • 二、前言2
    • 三、前置知识
      • 3.1 epoll
      • 3.2 jni
      • 四、具体实现
        • 4.1 aar设计
        • 4.2 部分代码解读
        • 五、应用层调用示例
            • 1. 打开CAN口和接收功能
            • 2. 发送CAN帧
            • 3. 关闭CAN口
            • 六、参考文档
            • 七、附录

              一、前言

              CAN总线(Controller Area Network Bus)控制器局域网总线 CAN总线是构建的一种局域网网络。

              最近工作涉及到在Android端发送和接收Can帧的内容,写一篇博客记录下。

              主要是移植了linux-canutils库中的candump.c和cansend.c代码到aar中。

              后续会给出aar和应用层代码的具体调用。

              aar源码见七、附录

              二、前言2

              最开始的can_aar是基于SOCKETCAN实现的,这里给出SOCKET_CAN的官方文档(https://www.kernel.org/doc/html/v4.17/networking/can.html#socketcan-raw-sockets),SOCKETCAN是使用一个socket绑定所有CAN口,但是如果是高速发送:比如每帧间隔1ms 连续发送几百帧以上会有丢帧的问题,表征为概率性无法收到can帧

              于是参照github上linux-canutils的代码对can_aar进行修改,设备有多个can口,

              效果:使用两个CAN口和电脑端连接,电脑端间隔1ms发送一帧,电脑端发送的同时,设备端也发送CAN帧,共发送3000000+帧未发生丢帧的情况。

              这里先给出linux-canutils candump.c和cansend.c的链接:

              https://github.com/linux-can/can-utils/blob/master/candump.c

              https://github.com/linux-can/can-utils/blob/master/cansend.c

              candump对应通过can口读数据,cansend对应通过can口写数据,也分别对应cantest.c中的doRealCanReadBytes和canWriteBytesDebug两个函数。

              三、前置知识

              3.1 epoll

              定位:Linux内核提供的一种高效I/O事件通知机制,专为处理大量并发连接设计(如Web服务器、实时通信系统)。

              核心功能:监控多个文件描述符(如Socket)的I/O状态变化(可读/可写/异常),避免轮询消耗CPU资源。

              设计目标:解决传统select/poll的性能瓶颈(线性扫描、内存拷贝、连接数限制)。

              Android操作CAN总线(基于linux-canutils库,发送、接收、设置过滤器)

              3.2 jni

              作为java代码调用c代码的媒介,详见《Android开发艺术探索》

              四、具体实现

              4.1 aar设计

              Android操作CAN总线(基于linux-canutils库,发送、接收、设置过滤器)

              can_test.c: 各类函数具体实现的代码

              com_example_x6_mc_cantest_CanUtils.h: jni函数

              CanFilter: Can帧过滤器实体类

              CanFrame: Can帧实体类

              CanUtils: 封装给上层调用的工具类

              DataListener:接口类,用于向上层回调数据

              4.2 部分代码解读

              CanUtils.java:

              // 打开can口并使能
              public void canOpen(String can, String baudRate) {
                  execRootCmdSilent("ifconfig " + can + " down");
                  execRootCmdSilent("ip link set " + can + " up type can bitrate " + baudRate);
              }
              // 提供给上层调用,也是jni函数,通过can口发送数据,具体实现见can_test.c
              public native void canWriteBytesDebug(CanFrame canFrame, String canPort);
              // 提供给上层调用,通过can口接收数据,具体实现见can_test.c
              public void canReadBytesDebug(DataListener listener, ArrayList can){
                  createEpoll();
                  for(int i = 0; i  
              

              can_test.c:

              //关注CanUtils.java中的canReadBytesDebug
              // 1. canReadBytesDebug 先createEpoll()创建系统调用,再绑定socket到can接口,最后开启监听
              JNIEXPORT void JNICALL
              Java_com_example_x6_mc_1cantest_CanUtils_createEpoll(JNIEnv *env, jobject thiz) {
              	LOGE("createEpoll begin");
              	fd_epoll = epoll_create(1);
              	if (fd_epoll GetStringUTFChars(env, can, NULL);
              	LOGE("port = %s",port);
              	struct if_info* obj = &sock_info[port[3] - '0'];
              	obj->s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
              	if (obj->s s, &event_setup)) {
              		LOGE("failed to add socket to epoll");
              		return -1;
              	}
              	obj->cmdlinename = port; /* save pointer to cmdline name of this socket */
              	nbytes = strlen(port); /* no ',' found => no filter definitions */
              	if (nbytes > max_devname_len)
              		max_devname_len = nbytes; /* for nice printing */
              	addr.can_family = AF_CAN;
              	memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
              	strncpy(ifr.ifr_name, port, nbytes);
              	if (strcmp(ANYDEV, ifr.ifr_name) != 0) {
              		if (ioctl(obj->s, SIOCGIFINDEX, &ifr) s, SOL_CAN_RAW, CAN_RAW, &canfd_on, sizeof(canfd_on));
              	if (bind(obj->s, (struct sockaddr *)&addr, sizeof(addr)) DeleteLocalRef(env,port);
              }
              // 该函数实现:当epoll监听到有数据时,就回调数据给上层
              JNIEXPORT void JNICALL
              Java_com_example_x6_mc_1cantest_CanUtils_doRealCanReadBytes(JNIEnv *env, jobject thiz, jobject listener) {
              	running = 1;
              	jclass callClass = (*env)->GetObjectClass(env,listener);
              	jmethodID callMethod = (*env)->GetMethodID(env,callClass,"onData",
              											   "(Ljava/lang/String;Ljava/lang/String;I)V");
              	/* these settings are static and can be held out of the hot path */
              	iov.iov_base = &frame;
              	msg.msg_name = &addr;
              	msg.msg_iov = &iov;
              	msg.msg_iovlen = 1;
              	msg.msg_control = &ctrlmsg;
              	while (running) {
              		num_events = epoll_wait(fd_epoll, events_pending, currmax, timeout_ms);
              		if (num_events == -1) {
              			running = 0;
              			continue;
              		}
                      LOGE("num_events = %d",num_events);
              		for (int i = 0; i s, &msg, 0);
              			if (nbytes s);
              //			LOGE("CAN口 = %s",devname[idx]);
              			frame_count ++;
              			LOGE("recv frame count = %d\n",frame_count);
              			fprint_long_canframe(stdout, &frame, NULL, view, maxdlen); //输出收到的can帧数据:canid can帧长度 can帧数据
              			jstring data = (*env)->NewStringUTF(env,buf);
              			jstring canPort = (*env)->NewStringUTF(env,devname[idx]);
              			(*env)->CallVoidMethod(env,listener,callMethod,data,canPort,frame_count);
                          (*env)->DeleteLocalRef(env,data);
                          (*env)->DeleteLocalRef(env,canPort);
              		}
              	}
                  LOGE("canReadBytesDebug end");
              	(*env)->DeleteLocalRef(env,callClass);
              }
              

              五、应用层调用示例

              前言:

              TestCan是进行CAN测试的APK示例,基于can_aar (linux-can-utils)实现。

              目前只支持3位canid(表示标准帧)和8位canid(表示扩展帧),可以设置Can帧过滤器

              须在build.gradle中添加一行代码以使用can_aar

              implementation files('libs/can-debug.aar')
              

              can_aar包含以下功能:

              1. 打开CAN口和接收功能
              private CanUtils canUtil; //can工具类实例
              public void openCan() {
                  canUtil = CanUtils.getInstance();
                  canUtil.canOpen("can0","1000000");
                  canUtil.canOpen("can1","1000000");
                  canUtil.canOpen("can2","1000000");
                  ArrayList canList = new ArrayList();
                  canList.add("can0");
              	canList.add("can1");
              	canList.add("can2");
                  Executors.newCachedThreadPool().execute(new Runnable() {
                      @Override
                      public void run() {
                          canUtil.canReadBytesDebug(new DataListener() {
                              @Override
                              public void onData(String data, String canPort, int frameCount) {
                                  Log.e(TAG, "canPort == " + canPort + " data == " + data + " frameCount == " + frameCount);
                              }
                          }, canList);
                      }
                  });
                  //监听3个can口,就将can0,can1,can2都加入canList中
              }
              
              2. 发送CAN帧
              //CanFrame实体类
              public class CanFrame {
                  public String canId; //can帧id
                  public String data; //can帧数据
                  public CanFrame() {
                  }
              }
              
              // 发送代码示例
              if (canUtil != null) {
                  CanFrame canFrame = makeCanFrame();
                  canUtil.canWriteBytesDebug(canFrame,"can0"); // 通过can0口发送
                  canUtil.canWriteBytesDebug(canFrame,"can1"); // 通过can1口发送
                  canUtil.canWriteBytesDebug(canFrame,"can2"); // 通过can2口发送
              }
              private CanFrame makeCanFrame() {
                  CanFrame canFrame = new CanFrame();
                  canFrame.canId = "123";
                  canFrame.data = "1122334455667788"
                  return canFrame;
              }
              
              3. 关闭CAN口
              public void closeCan() {
                  if (canUtil != null) {
                      canUtil.canClose();
                  }
                  canUtil = null;
              }
              

              六、参考文档

              https://www.xiaolincoding.com/os/8_network_system/selete_poll_epoll.html#epoll

              https://github.com/linux-can/can-utils

              七、附录

              源码见:https://github.com/qilin02811/can_aar

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码