Android CAN 应用开发
在工作时发现我们很多客户对于Android can应用开发没有一点头绪,下面我通过一个简单项目来实现一个can通信demo。
开发准备
确保开发板硬件上有can节点存在,并且确保通信正常 验证方法如下:
一 、使用cantool验证
1.1初始化并设置波特率
ifconfig can0 down
canconfig can0 bitrate 500000 ctrlmode triple-sampling on //默认使用这个初始化
#ip link set can0 type can bitrate 250000 sample-point 0.8 dbitrate 250000 dsample-point 0.75 fd on //有些厂商用的这个初始化
ifconfig can0 up
1.2 发送数据and接收数据
cansend can0 -e 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
candump can0
确保上面步骤可以正常进行通信
二、实现Android 的jni方法
首先新建一个工程 根据自己工程默认即可 语言选择java
2.1 添加C++模块
工程创建完毕选择app右键选择 Add C++ to Module
创建路径和文件名称自主修改即可
创建完毕会出现一个cpp文件夹 里面会有一个cmakelists和刚刚创建的文件
2.2 添加java方法
接下来我们创建两个java类 一个名字为 CanFrame
一个名字为 CanControl
CanFrame.java内容为:
public class CanFrame {
public int can_id;
public char can_dlc;
public byte[] data;
}
CanControl.java内容为:public class CanControl {
static {
System.loadLibrary("myapplication"); //注意这里的库名字为你刚刚创建的C++名称
}
public int fd;
public native static int OpenCan(String canx);
public native static int CanWrite(int fd,int canId, byte[] data);
public native static CanFrame CanRead(CanFrame mcanFrame, int time);
public native static int CloseCan(int fd);
}
2.3 接下来我们来实现CanControl里面的方法
点击CanControl.java里面的方法 比如 OpenCan;
鼠标移动过去会有一个Create JNl function for CanRead的提示 点击即可 其他的也是一样
都创建完了 我们来到cpp文件 可以看到编译器给我们添加了几个方法
接着我们自己来实现一下 我这里已经实现了 不想写的直接复制粘贴即可
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_CanControl_OpenCan(JNIEnv *env, jclass clazz, jstring canx) {
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;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_CanControl_CanWrite(JNIEnv *env, jclass clazz, jint fd,
jint can_id, jbyteArray data) {
// 获取 JNI 数据
jbyte *pdata = env->GetByteArrayElements(data, NULL);
jsize data_len = env->GetArrayLength(data);
// 确保数据长度不超过 CAN 最大数据长度
if (data_len > 8) {
data_len = 8; // CAN 数据长度最大为 8 字节
}
// 创建 CAN 帧
struct can_frame frame;
frame.can_id = can_id;
frame.can_dlc = data_len;
// 将数据复制到 CAN 帧中
for (jsize i = 0; i < data_len; i++) {
frame.data[i] = pdata[i] & 0xFF;
}
// 将剩余的数据部分置为 0(如果数据长度小于 8)
for (jsize i = data_len; i < 8; i++) {
frame.data[i] = 0;
}
// 释放 JNI 数据
env->ReleaseByteArrayElements(data, pdata, 0);
// 写入 CAN 总线
int ret = write(fd, &frame, sizeof(struct can_frame));
return ret;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_myapplication_CanControl_CanRead(JNIEnv *env, jclass clazz,
jobject mcanFrame, jint time) {
struct can_frame frame;
read(time, &frame, sizeof(struct can_frame));
jclass canFrameClass = env->GetObjectClass(mcanFrame);
jfieldID canIdField = env->GetFieldID(canFrameClass, "can_id", "I");
jfieldID canDlcField = env->GetFieldID(canFrameClass, "can_dlc", "C");
jfieldID dataField = env->GetFieldID(canFrameClass, "data", "[B");
env->SetIntField(mcanFrame, canIdField, static_cast<jint>(frame.can_id));
env->SetCharField(mcanFrame, canDlcField, frame.can_dlc);
jbyteArray dataArray = env->NewByteArray(frame.can_dlc);
env->SetByteArrayRegion(dataArray, 0, frame.can_dlc, (jbyte *)frame.data);
env->SetObjectField(mcanFrame, dataField, dataArray);
return mcanFrame;
}
extern "C"
JNIEXPORT int JNICALL
Java_com_example_myapplication_CanControl_CloseCan(JNIEnv *env, jclass clazz, jint fd) {
return close(fd);
}
三、测试demo
修改MainActivity.java文件
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
CanControl can0;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.textview0);
tv.setMovementMethod(ScrollingMovementMethod.getInstance());
can0 = new CanControl(this);
can0.InitCan(500000);
can0.fd = can0.OpenCan("can0");
//send
new Thread() {
byte[] data = new byte[] {(byte)0xA0, (byte)0xA1, (byte)0xA2, (byte)0xA3, (byte)0xA4, (byte)0xA5, (byte)0xA6, (byte)0xA7};
@Override
public void run() {
while (true) {
try {
sleep(1000); // 每秒发送一次数据
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新数据
data[0] = (byte) ((data[0] + 1) % 0xFF);
// 调用 CanWrite 方法发送数据
int result = CanControl.CanWrite(can0.fd,0x123, data);
if (result < 0) {
// 发送失败,处理错误
Log.e("CAN", "发送数据失败");
} else {
// 发送成功
Log.i("CAN", "数据发送成功");
}
}
}
}.start();
// Initialize CanFrame
final CanFrame[] canFrame = {new CanFrame()};
//receive
new Thread() {
@Override
public void run() {
while (true) {
// Call the CanRead method
canFrame[0] = CanControl.CanRead(canFrame[0], can0.fd);
// Extract data from CanFrame
int can0id = canFrame[0].can_id;
int can0eff = (can0id & 0x80000000) != 0 ? 1 : 0; // Extended frame
int can0rtr = (can0id & 0x40000000) != 0 ? 1 : 0; // Remote transmission request
int can0len = canFrame[0].can_dlc;
byte[] can0data = canFrame[0].data;
runOnUiThread(() -> {
// Prepare the string to display
String str = "can0 RX ";
str += (can0eff == 0) ? "S " : "E ";
str += (can0rtr == 0) ? "- " : "R ";
String strid = Integer.toHexString(can0id & 0x1FFFFFFF); // Mask to get ID
if (can0eff == 0) {
for (int i = 0; i < 3 - strid.length(); i++) {
strid = '0' + strid;
}
} else {
for (int i = 0; i < 8 - strid.length(); i++) {
strid = '0' + strid;
}
}
str = str + strid + " [" + can0len + "] ";
for (int i = 0; i < can0len; i++) {
String hex = Integer.toHexString(can0data[i] & 0xFF);
hex = (hex.length() == 1) ? ('0' + hex) : hex;
str = str + ' ' + hex;
}
str = str.toUpperCase();
str += '\n';
// Update the TextView
if (tv.getLineCount() > 1000) {
tv.setText("");
}
tv.append(str);
int offset = tv.getLineCount() * tv.getLineHeight();
if (offset > tv.getHeight()) {
tv.scrollTo(0, offset - tv.getHeight());
}
});
}
}
}.start();
}
}
修改activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textview0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>