CAN(Controller Area Network,控制器局域网总线)是一种用于实时应用的串行通讯协议总线,它可以使用双绞线来传输信号,是世界上应用最广泛的现场总线之一。由德国博世公司在20世纪80年代专门为汽车行业开发。一组CAN上面可以支持多个节点。
在Linux下,CAN是网络设备,通过socket通信的方式来实现收发。
开发板上有一路CAN,已经带了CAN收发器,位置如下
对应的网卡设备为 CAN0。将引出的线束直接与CAN分析仪的CANH 和CANL连接即可进行收发测试。接线如下
系统已经带了canutil工具, 使用canutil可以进行相关操作。
设置波特率为250k,不接收自己发出去的帧
ip link set can0 type can bitrate 250000 loopback off
启动CAN0
ifconfig can0 up
发送标准帧 id 0x123 数据0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
cansend can0 123#1122334455667788
发送扩展帧 id 0x12345678 数据0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
cansend can0 12345678#1122334455667788
发送之后 CAN分析仪显示如下
接收数据
candump can0
使用CAN分析仪发送标准帧id 0x123 数据0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
android下面的apk即使有system权限,也无法设置CAN波特率,只有root用户才有权限设置CAN波特率,因此需要按上一章的方法,在命令行下配置CAN波特率并启动CAN0。如果需要固化到系统,则需要在rc文件添加
exec - root root -- /system/bin/ip link set can0 type can bitrate 250000 loopback off
exec - root root -- /system/bin/ifconfig can0 up
android 编程时可以使用C语言操作CAN,然后封装一个jni接口,由java来调用。C语言实现如下
int user_can_send(char *can_name, struct can_frame frame)
{
int sk_fd = -1;
struct sockaddr_can addr;
struct ifreq ifr;
int nbytes;
/* create a socket */
sk_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(sk_fd < 0) {
LOGE("socket error\n");
}
strcpy(ifr.ifr_name, can_name);
/* determine the interface index */
ioctl(sk_fd, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
/* bind the socket to a CAN interface */
if(bind(sk_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
LOGE("bind error\n");
}
nbytes = write(sk_fd, &frame, sizeof(struct can_frame));
close(sk_fd);
return nbytes;
}
int user_can_recv(char *can_name, struct can_frame *frame)
{
int sk_fd = -1;
struct sockaddr_can addr;
struct ifreq ifr;
int nbytes;
/* create a socket */
sk_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if(sk_fd < 0) {
LOGE("socket error\n");
}
strcpy(ifr.ifr_name, can_name);
/* determine the interface index */
ioctl(sk_fd, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
/* bind the socket to a CAN interface */
if(bind(sk_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
LOGE("bind error\n");
}
/* receive frame */
nbytes = read(sk_fd, frame, sizeof(struct can_frame));
close(sk_fd);
return nbytes;
}
这里封装了 user_can_send 用于CAN数据发送,user_can_recv 用于CAN数据接收
JNI接口代码如下,这里返回是一个字符串,保存了执行的结果
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_testdemo_MainActivity_CANTest(JNIEnv *env, jobject /* this */)
{
struct can_frame frame_tx = {0};
struct can_frame frame_rx = {0};
int nbytes = 0;
memset(test_result,0,sizeof(test_result));
//标准帧
frame_tx.can_id = 0x123;
frame_tx.can_dlc = 8;
frame_tx.data[0] = 0x11;
frame_tx.data[1] = 0x22;
frame_tx.data[2] = 0x33;
frame_tx.data[3] = 0x44;
frame_tx.data[4] = 0x55;
frame_tx.data[5] = 0x66;
frame_tx.data[6] = 0x77;
frame_tx.data[7] = 0x88;
nbytes = user_can_send("can0",frame_tx);
//扩展帧
frame_tx.can_id = 0x12345678 | CAN_EFF_FLAG;
frame_tx.can_dlc = 8;
frame_tx.data[0] = 0x11;
frame_tx.data[1] = 0x22;
frame_tx.data[2] = 0x33;
frame_tx.data[3] = 0x44;
frame_tx.data[4] = 0x55;
frame_tx.data[5] = 0x66;
frame_tx.data[6] = 0x77;
frame_tx.data[7] = 0x88;
nbytes = user_can_send("can0",frame_tx);
sleep(1);
nbytes = user_can_recv("can0",&frame_rx);
/* printf the received frame */
if (nbytes > 0) {
snprintf(reinterpret_cast<char *const>(test_result), sizeof(test_result) - 1, "%s can recv: %#x [%d] ",test_result, (frame_rx.can_id & (~CAN_EFF_FLAG)), frame_rx.can_dlc);
for (unsigned char i = 0; i < frame_rx.can_dlc; i++)
snprintf(reinterpret_cast<char *const>(test_result), sizeof(test_result) - 1, "%s %#x ",test_result, frame_rx.data[i]);
snprintf(reinterpret_cast<char *const>(test_result), sizeof(test_result) - 1,"%s \n",test_result);
}
return env->NewStringUTF(reinterpret_cast<const char *>(test_result));
}
在第一章节创建的app基础上,增加一个按钮和一个文本框,按下按钮则执行此测试程序
在MainActivity的onCreate增加
button7.setText("can start");
button7.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 设置要显示的文字
textDisplay.setText("start can test... \n");
new Thread(new Runnable() {
@Override
public void run() {
final String can_test_result = CANTest();
runOnUiThread(new Runnable() {
@Override
public void run() {
textDisplay.append("cantest: " + can_test_result);
}
});
}
}).start();
}
});
同时声明上面定义的JNI接口
public native String CANTest();
测试时按下 can start 的按钮可以看到 CAN 分析仪收到了两帧数据,CAN分析仪发送数据后文本框可以看到收到的一帧数据