Android JNI与CAN通信遇到的问题总结

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Android JNI与CAN通信遇到的问题总结

最近这段时间在搞can, 在使用JNI接口与CAN设备通信是一种常见的做法。这种通信可能会遇到一些问题,需要深入分析和解决。本文将探讨3个与此相关的问题,并提供相应的解决方案。


Rockchip系列之深度分析CAN接口系列(1)

Rockchip系列之CAN 新增framework系统jni接口访问(2)

Rockchip系列之CAN 新增framework封装service+manager访问(3)

Rockchip系列之CAN APP测试应用实现(4)

Rockchip CAN 部分波特率收发不正常解决思路

Android JNI与CAN通信遇到的问题总结

Android 内核关闭CAN 串口设备回显功能


1. CAN设备与Android设备间的波特率不匹配问题
问题描述:
&can0 {
        assigned-clocks = <&cru CLK_CAN0>;
        assigned-clock-rates = <200000000>;
        pinctrl-names = "default";
        pinctrl-0 = <&can0m1_pins>;
        status = "disabled";
};
&can1 {
        assigned-clocks = <&cru CLK_CAN1>;
        assigned-clock-rates = <200000000>;
        pinctrl-names = "default";
        pinctrl-0 = <&can1m1_pins>;
        status = "disabled";
};

当两个Android设备的CAN通信波特率设置为500k时,它们之间的通信没有问题。但在某些情况下,CAN设备上位机只能接收Android设备的数据,而不能发送数据到Android设备。

原因分析:

这个问题可能是由于Android设备的CAN频率与CAN设备的频率不匹配导致的。例如,当assigned-clock-rates设置为<150000000>时,部分波特率可能会出现这种情况。

验证方法:

为了验证这是否是波特率导致的问题,可以将一个Android设备A的频率设置为150,另一个Android设备B的频率设置为200。这样,A和B之间的通信将只能接收,不能发送。当使用CAN设备时,如果出现部分波特率不能发送的情况,应该考虑设备自身的原因。


2. JNI接口层的frame.can_id数据问题
问题描述:
static jbyteArray dev_receiveCan(JNIEnv *env, jobject thiz, jint fd) {
    //ALOGI("dev_receiveCan");
    jbyteArray ret;
    ret = env->NewByteArray(15); // 修改数组大小为15
    struct can_frame frame;
    read(fd, &frame, sizeof(struct can_frame));
    /*
    上层can_id 发送0x12345678 , 这里打印的是678 , candump 打印的也是678。
    上层can_id 发2047 ,  打印的确是7ff  
    上层can_id 发2048 ,  打印的确是0
    上层can_id 发2049 ,  打印的确是1
    感觉2048是一个轮
    */
    //ALOGI("dev_receiveCan %x \n",frame.can_id);
    
    jbyte data[15]; 
    /*
    标准格式:这种格式的ID长度为11位。
    扩展格式:这种格式的ID长度为29位。
      0x1FFFFFFF 是一个具有29个低位为1的32位掩码。当你使用 &(按位与)操作符将 frame.can_id 与这个掩码相与时,会得到 frame.can_id 的低29位,而高3位会被清零。
      这样的操作通常用于确保我们只获取ID的扩展格式部分,并忽略其他可能存在的标志或信息。
    */
    // 将32位的can_id分解为4个字节  
    data[0] = (frame.can_id >> 24) & 0xFF; //使用& 0xFF确保canData[i]转换为十六进制字符串时是正数
    data[1] = (frame.can_id >> 16) & 0xFF;
    data[2] = (frame.can_id >> 8) & 0xFF;
    data[3] = frame.can_id & 0xFF;
    data[4] = (frame.can_id >> 31) & 0x1;
    data[5] = (frame.can_id >> 30) & 0x1;
    data[6] = frame.can_dlc;
 
    for(uint8_t i = 0; i < frame.can_dlc; i++) {
        data[i + 7] = frame.data[i]; // 从data[7]开始填充数据
    }
    env->SetByteArrayRegion(ret, 0, 15, data); // 修改数组大小为15
    return ret;
}

在JNI层,当上层发送can_id0x12345678时,JNI层打印的是678。此外,当can_id值超过2047(0x7FF)时,它似乎回绕到0。

原因分析:

这种行为似乎表明frame.can_id被限制在了11位内,这是标准CAN帧的ID长度。当can_id达到0x7FF(即2047)时,它会回绕到0。

解决方案:
  • 确保struct can_frame中的can_id是一个32位整数。
  • 检查CAN驱动和硬件的文档,确定它们是否支持扩展帧,并查看如何配置。
  • 确保发送CAN消息的设备或软件被配置为发送扩展帧。

3. CAN接收数据重复或者接收到未知数据问题
问题描述:

我们验证过并发测试,比如循环1000次每次发100帧,每次延迟10毫秒。这个时候就会出现重复帧和未知数据。

new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i <1000 ; i++) {
                        for (int j = 0; j < 100; j++) {
                            needBlinkForSend.set(true);
                            int result = viewModel.canUtils.dev_sendCan(viewModel.canUtils.fd, j, 0, 0, dataArray.length, byteArrayToIntArray(dataArray)); // 使用viewModel中的canUtils来操作CAN
                            if (result != 0) {
                                //showToast("发送数据失败");
                            } else {
                                //showToast("发送数据成功");
                            }
                        }
                        Thread.sleep(10);
                    }
                } catch (Exception e) {
                    //showToast("发送数据出现异常");
                    Log.e(TAG, "Error sending CAN data: " + e.getMessage());
                }
            }
        }).start();
原因分析:

这种情况最终查出来的原因是由于我之前在can driver底层加了逻辑,CAN数据似乎是每次都是要有响应的,如果未及时响应就会导致重复发或者未知数据 所以千万别在底层加任何延迟和逻辑, 哪怕是1毫秒在并发面前都是不堪一击 会测出很多问题, 就算加了线程也是一样的。

解决方案:

最终我把can driver进行了还原把之前的逻辑挪到了上层处理,底层要保持干净尽量不要去改动 不如会引起非常多的问题。

4. JNI层CAN ID解析不正确问题
问题描述:

在Android应用中,通过JNI接口从CAN设备接收数据时,发现扩展帧的can_id解析出来是98121001,而期望的是18121001。例如,当发送扩展帧0x18121001到Android设备时,JNI层和Android应用层都打印出了98121001

jbyte data[15]; 
bool is_extended = frame.can_id & CAN_EFF_FLAG;
if (is_extended) {
    data[0] = (frame.can_id >> 24) & 0xFF;
    data[1] = (frame.can_id >> 16) & 0xFF;
    data[2] = (frame.can_id >> 8) & 0xFF;
    data[3] = frame.can_id & 0xFF;
    ...
}
原因分析:

这个问题是由于can_id的某些标志位被设置导致的。在CAN协议中,can_id不仅仅包含实际的ID,还可能包含其他的标志位,例如CAN_EFF_FLAGCAN_RTR_FLAG等。当我们直接解析can_id而不考虑这些标志位时,可能会得到不正确的结果。

CAN_EFF_FLAG是一个标志位,用于表示CAN帧是否为扩展帧。当这个标志位被设置时,can_id的高位将包含这个标志,导致我们解析出的ID不正确。

解决方法:

为了得到正确的can_id,我们需要在解析之前清除可能设置的标志位。这可以通过使用& 0x1FFFFFFF来实现,这个操作会清除can_id的高3位,确保我们得到的是纯粹的ID。

在JNI代码中,我们可以这样修改:

static jbyteArray dev_receiveCan(JNIEnv *env, jobject thiz, jint fd) {
    jbyteArray ret;
    ret = env->NewByteArray(15);
    struct can_frame frame;
    read(fd, &frame, sizeof(struct can_frame));
    jbyte data[15]; 
    bool is_extended = frame.can_id & CAN_EFF_FLAG; // CAN_EFF_FLAG表示扩展帧标志
    if (is_extended) {
        // 清除标志位 为了得到正确的can_id,需要在解析之前清除可能设置的标志位。通过使用& 0x1FFFFFFF来实现,这个操作会清除can_id的高3位,确保我们得到的是纯粹的ID。
        uint32_t clean_can_id = frame.can_id & 0x1FFFFFFF;
        data[0] = (clean_can_id >> 24) & 0xFF;
        data[1] = (clean_can_id >> 16) & 0xFF;
        data[2] = (clean_can_id >> 8) & 0xFF;
        data[3] = clean_can_id & 0xFF;
        data[4] = (frame.can_id >> 31) & 0x1; // RTR位
        data[5] = (frame.can_id >> 30) & 0x1; // IDE位
        data[6] = frame.can_dlc; // 数据长度
        for(uint8_t i = 0; i < frame.can_dlc; i++) {
            data[i + 7] = frame.data[i];
        }
    } else {
        /*data[0] = (frame.can_id >> 8) & 0xFF; 
        data[1] = frame.can_id & 0xFF;
        data[2] = (frame.can_id >> 31) & 0x1; // RTR位
        data[3] = (frame.can_id >> 30) & 0x1; // IDE位
        data[4] = frame.can_dlc; // 数据长度
        for(uint8_t i = 0; i < frame.can_dlc; i++) {
            data[i + 5] = frame.data[i];
        }*/
        
        data[0] = (frame.can_id >> 24) & 0xFF; //使用& 0xFF确保canData[i]转换为十六进制字符串时是正数
        data[1] = (frame.can_id >> 16) & 0xFF;
        data[2] = (frame.can_id >> 8) & 0xFF;
        data[3] = frame.can_id & 0xFF;
    
        data[4] = (frame.can_id >> 31) & 0x1;
        data[5] = (frame.can_id >> 30) & 0x1;
        data[6] = frame.can_dlc;
     
        for(uint8_t i = 0; i < frame.can_dlc; i++) {
            data[i + 7] = frame.data[i]; // 从data[7]开始填充数据
        }
    }
    ALOGI("v1.2 dev_receiveCan %x , %s\n", (frame.can_id & 0x1FFFFFFF) , is_extended ? "true" : "false"); // 这里也进行了相应的修改,以确保日志输出正确的can_id
    env->SetByteArrayRegion(ret, 0, 15, data);
    return ret;
}
public void startCanReceiverThread() { // 新增的方法,用来启动接收CAN数据的线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                new Thread() {
                    byte[] ret = new byte[15]; // 修改数组大小为15
                    @Override
                    public void run() {
                        while (true) {
                            if (isCanOpen) {
                                try {
                                    ret = canUtils.dev_receiveCan(canUtils.fd);
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                                count++;
                                needBlinkForReceive.set(true);
                                // 解析canId为一个32位的整数
                                // long canId = ((ret[0] & 0xFF) << 24) | ((ret[1] & 0xFF) << 16) | ((ret[2] & 0xFF) << 8) | (ret[3] & 0xFF);
                                long canId = ((ret[0] & 0xFFL) << 24) | ((ret[1] & 0xFFL) << 16) | ((ret[2] & 0xFFL) << 8) | (ret[3] & 0xFFL);
                                // long canId = (((long)ret[0] & 0xFF) << 24) | (((long)ret[1] & 0xFF) << 16) | (((long)ret[2] & 0xFF) << 8) | ((long)ret[3] & 0xFF);
                                byte canEff = ret[4];
                                byte canRtr = ret[5];
                                byte canLen = ret[6];
                                byte[] canData = Arrays.copyOfRange(ret, 7, 7 + canLen); // 从ret[7]开始复制
                                String str = "can RX ";
                                str += (canEff == 0) ? "S " : "E ";
                                str += (canRtr == 0) ? "-  " : "R  ";
                                String canIdStr = Long.toHexString(canId);
                                if (canEff == 0) {
                                    for (int i = 0; i < 3 - canIdStr.length(); i++) {
                                        canIdStr = '0' + canIdStr;
                                    }
                                } else {
                                    for (int i = 0; i < 8 - canIdStr.length(); i++) {
                                        canIdStr = '0' + canIdStr;
                                    }
                                }
                                str = str + canIdStr + "   [" + Long.toString(canLen) + "]  ";
                                for (int i = 0; i < canLen; i++) {
                                    String hex = Long.toHexString(canData[i] & 0xFF); // 使用 & 0xFF 来确保是正数
                                    hex = (hex.length() == 1) ? ('0' + hex) : hex;
                                    str = str + ' ' + hex;
                                }
                                str = str.toUpperCase();
                                str += '\n';
                                String finalStr = str;
                                Log.d(TAG, "Received CAN data: count:" + count +", finalStr:"+finalStr);
                                Message message = handler.obtainMessage(RECEIVE_CAN_DATA);
                                Bundle bundle = new Bundle();
                                bundle.putString("data", finalStr);
                                message.setData(bundle);
                            }
                        }
                    }
                }.start();
            }
        });
        thread.start();
    }

当我们从JNI函数中接收数据时,can_id将是正确的,应用层代码无需进行任何额外的处理。

验证方法:

为了验证这是否是can_id标志位导致的问题,可以在JNI代码中使用& 0x1FFFFFFF来清除可能设置的标志位,并重新解析can_id。这样,应该能得到与candump相同的输出。如果问题仍然存在,建议检查其他部分的代码,确保在JNI代码和上层之间没有其他的数据转换或处理逻辑。


希望本文提供的解决方案能帮助到刚好遇到坑的你 如果有问题欢迎留言私信。

相关文章
|
28天前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
111 4
|
7月前
|
Android开发
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
1074 1
|
4月前
|
Java Android开发 C++
Android Studio JNI 使用模板:c/cpp源文件的集成编译,快速上手
本文提供了一个Android Studio中JNI使用的模板,包括创建C/C++源文件、编辑CMakeLists.txt、编写JNI接口代码、配置build.gradle以及编译生成.so库的详细步骤,以帮助开发者快速上手Android平台的JNI开发和编译过程。
330 1
|
3月前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
392 4
|
5月前
|
Java Android开发 Spring
Android Spingboot 实现SSE通信案例
【7月更文挑战第14天】以下是使用Android和Spring Boot实现SSE(Server-Sent Events)通信的案例摘要: 在`MainActivity`中: - 初始化界面元素并设置按钮点击事件。 - `startSseRequest`方法创建`WebClient`对象,设置请求头,发送请求,并处理响应和错误。 请确保将`your-server-url`替换为实际的服务器地址。
137 14
|
4月前
|
Android开发
Android项目架构设计问题之C与B通信如何解决
Android项目架构设计问题之C与B通信如何解决
22 0
|
4月前
|
移动开发 前端开发 weex
Android项目架构设计问题之模块化后调用式通信如何解决
Android项目架构设计问题之模块化后调用式通信如何解决
22 0
|
5月前
|
Dart Android开发 Windows
Flutter和Native 通信 android端
Flutter和Native 通信 android端
|
7月前
|
Java Android开发
Android JNI 调用
Android JNI 调用
43 1
|
7月前
|
Java 物联网 Linux
Android硬件通信之 串口通信
Android硬件通信之 串口通信
120 0