最近这段时间在搞can, 在使用JNI接口与CAN设备通信是一种常见的做法。这种通信可能会遇到一些问题,需要深入分析和解决。本文将探讨3个与此相关的问题,并提供相应的解决方案。
Rockchip系列之CAN 新增framework系统jni接口访问(2)
Rockchip系列之CAN 新增framework封装service+manager访问(3)
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_id
为0x12345678
时,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_FLAG
、CAN_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代码和上层之间没有其他的数据转换或处理逻辑。
希望本文提供的解决方案能帮助到刚好遇到坑的你 如果有问题欢迎留言私信。