Android操作CAN总线(基于linux-canutils库,发送、接收、设置过滤器)
文章目录
- 一、前言
- 二、前言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的性能瓶颈(线性扫描、内存拷贝、连接数限制)。
3.2 jni
作为java代码调用c代码的媒介,详见《Android开发艺术探索》
四、具体实现
4.1 aar设计
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