Rockchip系列之深度分析CAN接口系列(1)_一歲抬頭的博客-CSDN博客
Rockchip系列之CAN 新增framework系统jni接口访问(2)-CSDN博客
Rockchip系列之CAN 新增framework封装service+manager访问(3)-CSDN博客
Rockchip系列之CAN APP测试应用实现(4)_一歲抬頭的博客-CSDN博客
Rockchip CAN 部分波特率收发不正常解决思路_一歲抬頭的博客-CSDN博客
Android JNI与CAN通信遇到的问题总结_android can通信-CSDN博客
Android 内核关闭CAN 串口设备回显功能_一歲抬頭的博客-CSDN博客
在上一篇博客中,我介绍了如何在Android平台上使用JNI来实现CAN通信的功能。在JNI层编写了一些本地方法,用于与CAN设备进行交互,并在Java层定义了相应的本地方法,用于调用JNI层的功能。在这篇博客中,将介绍如何在framework层编写一个服务类CanService和SystemCan,用于提供CAN通信的接口给其他应用程序或模块。
CanService类的定义
在frameworks/base/services/core/java/com/android/server/CanService.java文件中定义了CanService类,这个类继承了ICanService.Stub类,实现了ICanService接口。ICanService是一个AIDL接口,用于定义跨进程通信的方法。可以在frameworks/base/core/java/android/xxx/ICanService.aidl文件中看到它的定义:
package android.xxx; interface ICanService { int dev_openCan(String canx); int dev_closeCan(int fd); int dev_sendCan(int fd, long canid, long eff, long rtr, int len, int[] data); long[] dev_receiveCan(int fd); }
这个接口定义了四个方法,分别是:
- dev_openCan:打开指定的CAN设备,并返回一个文件描述符
- dev_closeCan:关闭指定的文件描述符
- dev_sendCan:向指定的文件描述符发送一帧CAN数据
- dev_receiveCan:从指定的文件描述符接收一帧CAN数据,并返回一个长整型数组
这些方法和JNI层的本地方法是一一对应的,只是在名称前加了native_前缀。可以通过实现这个接口来调用JNI层的本地方法。
CanService类的实现
在CanService类中实现了ICanService接口的四个方法,以及一个init方法和五个本地方法。看一下其中一个方法的实现:
@Override public int dev_openCan(String canx) { Slog.i(TAG, "dev_openCan: " + canx); return native_dev_openCan(canx); }
这个方法接受一个字符串参数canx,表示要打开的CAN设备的名称,例如"can0"或"can1"。打印一条日志信息,表示进入了这个方法,然后调用本地方法native_dev_openCan,并将canx作为参数传递。最后返回本地方法的返回值,表示打开设备后得到的文件描述符,如果打开失败,则返回负数。
其他三个方法的实现逻辑类似
init方法是用于初始化JNI层的,它没有参数和返回值,只是简单地调用本地方法init:
public void init() { Slog.i(TAG, "init"); native_init(); }
五个本地方法是用于声明JNI层对应的本地方法,它们使用native关键字标识,并且没有具体的实现,在C/C++层完成:
private native void init(); private native int native_dev_openCan(String canx); private native int native_dev_closeCan(int fd); private native int native_dev_sendCan(int fd, long canid, long eff, long rtr, int len, int[] data); private native long[] native_dev_receiveCan(int fd);
这些本地方法的签名必须和JNI层定义的签名一致,否则会导致注册失败或者运行时错误 直接崩溃 懂我意思吧。。
CanService类的使用
在CanService类的构造函数中打印了一条日志信息,表示启动了CanService,并调用了init方法:
public CanService() { Slog.i(TAG, "Starting Can Service"); init(); }
这个构造函数会在系统启动时被调用,可以在frameworks/base/services/core/java/com/android/server/SystemServer.java文件中看到它的调用:
private void startOtherServices() { // ... try { Slog.i(TAG, "Can Service"); mSystemServiceManager.startService(CanService.class); } catch (Throwable e) { reportWtf("starting Can Service", e); } // ... }
这个方法是用于启动系统服务的,其中有一行代码是用于启动CanService类,即mSystemServiceManager.startService(CanService.class)。这样,就可以在系统中创建一个CanService对象,并初始化JNI层。
SystemCan类的定义
在frameworks/base/core/java/android/btf/SystemCan.java文件中定义了SystemCan类,当需要使用CanService类提供的CAN通信功能时,可以通过Binder机制来获取一个ICanService接口的引用,然后调用其方法。例如,可以使用以下代码来获取一个ICanService接口的引用:
ICanService canService = ICanService.Stub.asInterface( ServiceManager.getService("can"));
SystemCan类的实现
在SystemCan类中实现了四个方法,分别对应了CanService类提供的四个接口。还实现了一个构造函数,用于初始化sService对象和设置CAN设备的参数。看一下其中一个方法的实现:
public int dev_openCan(String canx) throws RemoteException { return sService.dev_openCan(canx); }
这个方法接受一个字符串参数canx,表示要打开的CAN设备的名称。它直接调用sService对象的dev_openCan方法,并返回其结果。这样,就可以通过SystemCan对象来简化对CanService对象的调用,而不需要每次都获取一个ICanService接口的引用。
在构造函数中实现了对sService对象的初始化和对CAN设备的配置。看一下它的实现:
public SystemCan(String canx, int baudrate) { if (!canx.equals("can0") && !canx.equals("can1")) { throw new IllegalArgumentException("canx must be either can0 or can1"); } if (baudrate < 0) { throw new IllegalArgumentException("baudrate cannot be less than 0"); } try { sService = ICanService.Stub.asInterface(ServiceManager.getService("can")); //ShellUtils.execCmd("ip link set " + canx + " type can tq 133 prop-seg 6 phase-seg1 6 phase-seg2 2 sjw 1", true); ShellUtils.CommandResult commandResult = ShellUtils.execCmd("ip link set " + canx + " down && ip link set " + canx + " type can bitrate " + baudrate + " && ip link set " + canx + " up", true); Log.d(TAG, "SystemCan:" + commandResult.result + "," + commandResult.successMsg + "," + commandResult.errorMsg); } catch (Exception e) { Log.e(TAG, "Error setting up CAN system: " + e.getMessage()); } }
这个构造函数接受两个参数:canx表示要打开的CAN设备的名称,baudrate表示要设置的CAN设备的波特率。它首先检查两个参数是否合法,如果不合法,就抛出异常。然后,它尝试获取CanService类的实例,并赋值给sService变量。接着,它执行一个shell命令,用于设置CAN设备的波特率,并将其关闭再打开。最后,它打印shell命令的执行结果。如果在这个过程中发生异常,它就打印错误信息。
SystemCan类的使用
在SystemCan类中封装了CanService类提供的接口,并提供了一个简单的构造函数,用于初始化和配置CAN设备。这样,就可以更方便地使用SystemCan类来实现CAN通信的功能。看一下一个使用示例:
SystemCan systemCan = new SystemCan("can0", 500000); //创建一个SystemCan对象,指定要打开和设置的CAN设备为"can0",波特率为500000 int fd = systemCan.dev_openCan("can0"); //调用SystemCan对象的dev_openCan方法,打开"can0"设备,并返回一个文件描述符 if (fd >= 0) { //如果文件描述符不是负数,表示打开成功 int[] data = {0x11, 0x22, 0x33, 0x44}; //定义一个整型数组,表示要发送的数据内容 int ret = systemCan.dev_sendCan(fd, 0x123, 0, 0, 4, data); //调用SystemCan对象的dev_sendCan方法,向文件描述符发送一帧CAN数据,帧ID为0x123,标准帧,数据帧,长度为4 if (ret > 0) { //如果返回值大于0,表示发送成功 long[] frame = systemCan.dev_receiveCan(fd); //调用SystemCan对象的dev_receiveCan方法,从文件描述符接收一帧CAN数据,并返回一个长整型数组 if (frame != null) { //如果返回值不是空,表示接收成功 Log.d("TEST-CAN","Received CAN frame: " + Arrays.toString(frame)); //打印接收到的CAN数据 } } systemCan.dev_closeCan(fd); //调用SystemCan对象的dev_closeCan方法,关闭文件描述符 }
这这样,就可以更简洁地使用SystemCan类来实现CAN通信的功能。
总结
在这篇博客中,介绍了如何在Java层编写一个服务类CanService和SystemCan类,用于提供CAN通信的接口给其他应用程序或模块。分别介绍了CanService/SystemCan类的定义,实现和使用。利用了AIDL接口和Binder机制来实现跨进程通信,并调用JNI层提供的本地方法来实现与CAN设备的交互。