最近这段时间在搞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_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代码和上层之间没有其他的数据转换或处理逻辑。
希望本文提供的解决方案能帮助到刚好遇到坑的你 如果有问题欢迎留言私信。