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(Controller Area Network)通信的功能。
JNI层的结构和组成
为了实现CAN通信的功能,需要在JNI层编写一些本地方法,这些方法会被Java层的CanService类调用。需要完成以下几个步骤:
- 在Java层定义本地方法,使用native关键字标识 (下一篇讲)
- 在C/C++层实现本地方法,使用JNI提供的数据结构和函数来操作Java对象和本地资源
- 在C/C++层注册本地方法,使用jniRegisterNativeMethods函数将本地方法和Java方法关联起来
下面来看一下具体的代码实现。
Java层的代码
在frameworks/base/services/core/jni/com_android_server_CanService.cpp文件中定义了CanService类,这个类是一个服务类,它提供了一些公开的方法供其他应用程序调用。其中有五个本地方法,分别是:
- init:初始化JNI层,目前没有具体的实现(没需求 留着)
- native_dev_openCan:打开指定的CAN设备,并返回一个文件描述符
- native_dev_closeCan:关闭指定的文件描述符
- native_dev_sendCan:向指定的文件描述符发送一帧CAN数据
- native_dev_receiveCan:从指定的文件描述符接收一帧CAN数据,并返回一个长整型数组
这些本地方法都是私有的,只能在CanService类内部调用。它们的具体实现在C/C++层完成。看一下其中一个本地方法的定义:
private native int native_dev_openCan(String canx);
这个方法接受一个字符串参数canx,表示要打开的CAN设备的名称,例如"can0"或"can1"。它返回一个整型值,表示打开设备后得到的文件描述符,如果打开失败,则返回负数。
C/C++层的代码
在C/C++层实现了Java层定义的本地方法,以及注册本地方法的函数。需要包含以下几个头文件:
#include <jni.h> // JNI头文件,包含了JNI相关的数据结构和函数 #include <nativehelper/JNIHelp.h> // JNI辅助头文件,包含了jniRegisterNativeMethods函数 #include <android_runtime/AndroidRuntime.h> // Android运行时头文件,包含了Android相关的数据结构和函数 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.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> // CAN相关头文件,包含了CAN相关的数据结构和常量 //#define LOG_TAG "com_android_server_CanService" //这玩意不要定义 有冲突 #include <utils/Log.h> // 日志头文件,包含了ALOGI等日志函数 //使用了android命名空间,以便于使用Android相关的类和函数: namespace android { // ... }
看一下其中一个本地方法的实现:
static jint dev_openCan(JNIEnv *env, jobject thiz, jstring canx) { ALOGI("dev_openCan"); int fd; struct ifreq ifr; struct sockaddr_can addr; /* open socket */ if ((fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) { return -1; } const char *str = env->GetStringUTFChars(canx, 0); strcpy(ifr.ifr_name, str); ioctl(fd, SIOCGIFINDEX, &ifr); memset(&addr, 0, sizeof(addr)); addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { return -2; } return fd; }
这个函数实现了打开CAN设备的功能,它的逻辑如下:
- 首先加一条打印日志信息,表示进入了本地方法(调试打印
- 然后创建一个原始套接字(socket),用于与CAN设备通信,如果创建失败,则返回-1
- 接着获取Java层传入的字符串参数canx,并将其转换为C风格的字符串str,使用env->GetStringUTFChars函数
- 然后将str复制到ifr.ifr_name中,并使用ioctl函数获取CAN设备的索引号ifr.ifr_ifindex
- 接着初始化一个sockaddr_can结构体addr,并设置其can_family为AF_CAN,can_ifindex为ifr.ifr_ifindex
- 最后将套接字fd绑定到addr上,如果绑定失败,则返回-2
- 如果一切顺利,则返回fd作为文件描述符
注意,在使用完env->GetStringUTFChars函数返回的字符串后,应该调用env->ReleaseStringUTFChars函数来释放其内存,以避免内存泄漏。在这里,没有这样做,是因为知道str只是一个临时变量,不会被其他地方引用。
其他四个本地方法的实现逻辑类似。
注册本地方法
为了让Java层能够调用本地方法,需要在C/C++层注册本地方法。使用jniRegisterNativeMethods函数来完成这个任务。这个函数需要三个参数:
- env:指向JNIEnv结构体的指针
- className:要注册本地方法的Java类的完整名称
- gMethods:一个JNINativeMethod结构体数组,表示要注册的本地方法及其签名和函数指针
定义了一个JNINativeMethod结构体数组gMethods,包含了五个元素,分别对应于五个本地方法:
static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "init", "()V", (void*) init }, { "native_dev_openCan", "(Ljava/lang/String;)I", (void*) dev_openCan }, { "native_dev_closeCan", "(I)I", (void*) dev_closeCan }, { "native_dev_sendCan", "(IJJJI[I)I", (void*) dev_sendCan }, { "native_dev_receiveCan", "(I)[J", (void*) dev_receiveCan }, };
然后,定义了一个函数register_android_server_CanService,用于调用jniRegisterNativeMethods函数:
int register_android_server_CanService(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/android/server/CanService", gMethods, NELEM(gMethods)); }
最后在framework native中注册这个JNI , 基操 就不赘述。
我验证过后的完整代码:
#include <jni.h> #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.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> //#define LOG_TAG "com_android_server_CanService" #include <utils/Log.h> namespace android { static void init(JNIEnv *env, jobject thiz) { ALOGI("init"); } static jint dev_openCan(JNIEnv *env, jobject thiz, jstring canx) { ALOGI("dev_openCan"); int fd; struct ifreq ifr; struct sockaddr_can addr; /* open socket */ if ((fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) { return -1; } const char *str = env->GetStringUTFChars(canx, 0); strcpy(ifr.ifr_name, str); ioctl(fd, SIOCGIFINDEX, &ifr); memset(&addr, 0, sizeof(addr)); addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { return -2; } return fd; } static jint dev_closeCan(JNIEnv *env, jobject thiz, jint fd) { ALOGI("dev_closeCan"); return close(fd); } static jint dev_sendCan(JNIEnv *env, jobject thiz, jint fd, jlong canid, jlong eff, jlong rtr, jint len, jintArray data) { ALOGI("dev_sendCan"); struct can_frame frame; frame.can_id = (eff << 31) | (rtr << 30) | canid; frame.can_dlc = len; jint *pdata = env->GetIntArrayElements(data, 0); for(uint8_t i = 0; i < len; i++) { frame.data[i] = pdata[i] & 0xFF; } int ret = write(fd, &frame, sizeof(struct can_frame)); env->ReleaseIntArrayElements(data, pdata, 0); return ret; } static jlongArray dev_receiveCan(JNIEnv *env, jobject thiz, jint fd) { ALOGI("dev_receiveCan"); jlongArray ret; ret = env->NewLongArray(12); struct can_frame frame; read(fd, &frame, sizeof(struct can_frame)); int64_t data[12]; data[0] = frame.can_id & 0x1FFFFFFF; data[1] = (frame.can_id >> 31) & 0x1; data[2] = (frame.can_id >> 30) & 0x1; data[3] = frame.can_dlc; for(uint8_t i = 0; i < frame.can_dlc; i++) { data[i + 4] = frame.data[i]; } env->SetLongArrayRegion(ret, 0, 12, data); return ret; } static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "init", "()V", (void*) init }, { "native_dev_openCan", "(Ljava/lang/String;)I", (void*) dev_openCan }, { "native_dev_closeCan", "(I)I", (void*) dev_closeCan }, { "native_dev_sendCan", "(IJJJI[I)I", (void*) dev_sendCan }, { "native_dev_receiveCan", "(I)[J", (void*) dev_receiveCan }, }; int register_android_server_CanService(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/android/server/CanService", gMethods, NELEM(gMethods)); } };
通过这篇文章,我介绍了如何在Rockchip系列芯片中(其他平台也一样 我只是在rockchip验证而已),使用JNI来实现CAN 通讯。如果你有任何问题或者想法,欢迎在评论区留言 三连支持 。