栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Android CAN 简记

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android CAN 简记

文章目录
    • 概要
    • adb 连接
    • adb 常用操作
    • CAN配置脚本
    • SocketCAN
    • JNI
    • JNI与SocketCAN
      • Makefile
      • jnican.java
      • 生成的 jnican..h
      • jnican.c
      • 运行
      • github jni can
    • NDK
    • Android Studio
    • Android_SocketCAN 工程
    • Apktool
    • Github Android_SocketCAN

概要

5年前的配置了:

  • MSM8953(骁龙625, 14nm, 8*Cortex-A53, 64bit, ARMv8-A, 2016年), 2+16GB
  • Android 7.1.x, Linux内核是3.18.x
  • 屏幕分辨率是常见的 1280*800
  • SOC没有CAN, 通过SPI外扩的两路CAN, MCP2515, 应用层上看还是SocketCAN. 这个Linux驱动其实挺不好整的, 特别是较老的内核和驱动, 连发丢帧的问题很明显
adb 连接

操作:

  • 设置->关于手机->连点5下版本号->返回到开发者选项->开启USB调试. (可能不需要这个步骤)
  • 设置->更多->USB Host开关两次, 最后的状态是关

插拔USB, 就可以在 Win 的设备管理里面看到

然后就可以在 wsl2 或者 powershell 中操作了

# 配置环境变量就直接adb, 不配置就切换到adb目录, ./adb.exe
adb version
adb devices
adb logcat
adb shell

# 如果出现 more than one device/emulator 错误
# 如果只有一个模拟器和一个手机连着电脑:
# 可以用adb -e 来指明是操作模拟器 Emulator
# 可以用adb -d 来指明是操作设备 Device
adb -d shell

参考:

  • 消除ADB错误“more than one device and emulator”的方法_杭州山不高的博客-CSDN博客
adb 常用操作
# 似乎默认没有文本编辑, 在shell中编辑文件显得困难
# 可以先拷出来, 编辑好后, 再推回去
adb root
adb remount
adb pull  
编辑...
adb push  

# 很多Linux命令是可以直接用的, 命令大都在 `/system/bin` 里面
# 因为很多权限没有, 索性直接root后再进shell
adb root
adb shell

# 查看内存
free -m

# 查看分区
df -H

# 打印内核信息
dmesg

# 查看网络信息
ifconfig

ping www.baidu.com
route -n

# 很多应用可以交叉编译或使用aarch64的, 放到 /system/bin 里面
CAN配置脚本

注意用于Android平板时开头是 #!/system/bin/sh, 不是Ubuntu中的#!/bin/bash, 脚本开机启动

#!/system/bin/sh

sleep 10

ip link set down can0
ip link set can0 type can bitrate 500000 sample-point 0.875 restart-ms 100
ip link set up can0 mtu 16
ifconfig can0 txqueuelen 1000

ip link set down can1
ip link set can1 type can bitrate 500000 sample-point 0.875 restart-ms 100
ip link set up can1 mtu 16
ifconfig can1 txqueuelen 1000

这些命令固然可以在C中用system方式运行, 但不建议, 毕竟这个算是系统的, 类似于eth, 各个进程都能用, 也为了方便调试, 最好不让一个进程独占或者开关.

检验

adb shell
ip -d -s link show can0
ip -d -s link show can1

can-util 工具仍然可以正常使用, 交叉编译后可以用adb推过去. 因为Android系统System分区启动之后只读, 在 adb shell 中也操作不了, 得 remount 重新挂载, 让分区可以读写

adb root
adb remount
adb push ./can-utils
#include 


#ifndef _Included_jnican
#define _Included_jnican
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint JNICALL Java_jnican_socketcan_1open
  (JNIEnv *, jobject, jint);


JNIEXPORT jint JNICALL Java_jnican_socketcan_1write
  (JNIEnv *, jobject, jint, jlong, jint, jbyteArray, jint, jint);


JNIEXPORT jbyteArray JNICALL Java_jnican_socketcan_1read
  (JNIEnv *, jobject, jint);


JNIEXPORT jint JNICALL Java_jnican_socketcan_1close
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif

jnican.c

socketcan的代码这里就不填充了, 只做演示

#include "jnican.h"
#include 
#include 

JNIEXPORT jint JNICALL Java_jnican_socketcan_1open
  (JNIEnv *env, jobject obj, jint channel)
{
    printf("socketcan open code put here...n");
    return 0;
}

JNIEXPORT jint JNICALL Java_jnican_socketcan_1write
  (JNIEnv *env, jobject obj, jint handle, jlong canid, jint dlc, jbyteArray buf, jint ext, jint rtr)
{
    printf("socketcan write code put here...n");
    uint8_t *data = (uint8_t *)((*env)->GetByteArrayElements(env, buf, 0));
    printf("socketcan write: %X, %d, %c, %02Xn", (uint32_t)canid, dlc, (ext==1)?'E':'S', data[0]);
    (*env)->ReleaseByteArrayElements(env, buf, (jbyte *)data, 0);
    return 0;
}
  
JNIEXPORT jbyteArray JNICALL Java_jnican_socketcan_1read
  (JNIEnv *env, jobject obj, jint handle)
{
    // read, recvfrom, epoll 等方式
    printf("socketcan read code put here...n");
    jbyteArray ret;
    ret = (*env)->NewIntArray(env, 13);
    uint8_t data[13] = {0x78, 0x56, 0x34, 0x92, 8, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
    (*env)->SetIntArrayRegion(env, ret, 0, 13, (jint *)data);
    return ret;
}

JNIEXPORT jint JNICALL Java_jnican_socketcan_1close
  (JNIEnv *env, jobject obj, jint handle)
{
    printf("socketcan close code put here...n");
    return 0;
}

编译成.so共享库

make so
# 生成 libjnican.so
# 默认 .so 使用去掉前缀lib
# java 中 System.loadLibrary("jnican");

注意代码中 C 和 C++ 的实现是不一样的

//c
uint8_t *data = (uint8_t *)((*env)->GetByteArrayElements(env, buf, 0));

//cpp
uint8_t *data = (uint8_t *)(env->GetByteArrayElements(buf, 0));

参考: Android Studio下JNI开发的基本步骤 - 掘金 (juejin.cn), 引用:

在C语言中,JNIEnv是指向JNINativeInterface结构的指针,使用它必须要解引用。而第一个参数还是env,学过C和C++语言都知道,C语言是面向过程语言,NewStringUTF只是一个函数指针,调用该方法还不清楚调用者,所以要传递env,而C++就不用,因为C++是面向对象语言,这个就不解释咯

运行
$ make run
java -Djava.library.path=. jnican
socketcan open code put here
socketcan write code put here...
socketcan write: 123, 8, S, AF
socketcan read code put here...
read: 12345678 8 E
socketcan close code put here
github jni can

自己写问题不大, 去github扒参考也不错:

  • linux-can/can-utils: Linux-CAN / SocketCAN user space applications (github.com), cansend, candump等的实现都在这里, 没有什么比参考标准实现更快的了
  • MaskingM/SocketCan, 这个是Github一个开源的实现, 可以参考. 里面的CANFrame.java中CAN数据接收的注释: 数据会反射到此处,在此处添加程序数据处理功能
  • pschichtel/JavaCAN: A simple JNI wrapper for the socketcan API provided by the Linux kernel. As it is wrapping a Linux Kernel API, it is intended for use on Linux only. (github.com), 仅用于Linux, 不过支持的很好, can, canfd, CAN_RAW, CAN_BCM, CAN_ISOTP, 平台也挺多的 arm, x86, riscv等, 较新的平台或内核可以参考这个
  • ZengjfOS/SocketCanJNI: 尝试将libsocket-can-java移植到Android上的,里面对libsocket-can-java添加了注释,发现该库还需要加强。 (github.com)
NDK

NDK: Native Development Kit, 原生开发套件. 简言之, 在 Android 应用中使用 C 和 C++ 代码, 想把JNI用起来, 就需要NDK.

可以在 Android Studio 2.2 或更高版本 中使用 NDK 将 C 和 C++ 代码编译到原生库中,然后使用 Android Studio 的集成构建系统 Gradle 将原生库打包到 APK 中.

Android Studio 编译原生库的默认构建工具是 CMake。由于很多现有项目都使用 ndk-build 构建工具包,因此 Android Studio 也支持 ndk-build。不过,如果您要创建新的原生库,则应使用 CMake。

Android Studio 中可以直接安装 NDK 和 CMake, 下面会有介绍.

参考

  • NDK 使用入门 | Android NDK | Android Developers (google.cn)
  • android/ndk-samples: Android NDK samples with Android Studio (github.com)
  • 什么是安卓系统中的NDK?- 极客极客 (geeksforgeeks.org)
Android Studio

虽然是google的, 国内不翻也能正常用:

探索 Android Studio | Android 开发者 | Android Developers (google.cn)

装的这个版本 android-studio-2021.2.1.15-windows.exe

Android_SocketCAN 工程

以下面这个版本为例

File -> New -> New Project, 选择 Phone and Tablet -> Empty Activity

语言选择Java

点击 SDK Manager 安装 NDK 和 CMake

app右键选择 Add C++ to Module

添加一个新的 java class: android_socketcan

把接口填进去:

  • eff 指标准帧(0)还是扩展帧(0), 是 struct can_frame 的 can_id 的 31位
  • rtr 指远程帧是 struct can_frame 的 can_id 的30位
  • canid 是 struct can_frame 的 can_id 的低29位(0x0~0x1FFFFFFF)
  • 读接口直接返回一个long数组0:id, 1:eff, 2:rtr, 3:len, 4~11:data
  • 感觉 java 和 kotlin 的数据类型有些残缺, 无符号的支持不好, 这里往大了扩, 免得来回转麻烦, 这一点 swift 就有 UInt8 UInt32 UInt64, 让使用者不至于那么难受.
// android_socketcan.java
package com.example.android_socketcan;

public class android_socketcan {
    static {
        System.loadLibrary("android_socketcan");
    }
	public int fd;
    public native int socketcanOpen(String canx);   //return fd
    public native int socketcanClose(int fd);       //return 0 is success
    public native int socketcanWrite(int fd, long canid, long eff, long rtr, int len, int[] data);
    public native long[] socketcanRead(int fd);
}

鼠标悬停红色文字, 弹窗点 Creat JNI function for

依次点击这4个接口, 就可以在cpp中自动生成接口代码, jni.h 也包含好了

接着把 SocketCAN的C代码填进去, 整个文件如下

// android_socketcan.cpp
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_android_1socketcan_android_1socketcan_socketcanOpen(JNIEnv *env, jobject thiz,
                                                                     jstring canx) {
    // TODO: implement socketcanOpen()
    int fd;
    struct ifreq ifr;
    struct sockaddr_can addr;

    
    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_android_1socketcan_android_1socketcan_socketcanClose(JNIEnv *env, jobject thiz,
                                                                      jint fd) {
    // TODO: implement socketcanClose()
    return close(fd);
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_android_1socketcan_android_1socketcan_socketcanWrite(JNIEnv *env, jobject thiz,
                                                                      jint fd, jlong canid,
                                                                      jlong eff, jlong rtr,
                                                                      jint len, jintArray data) {
    // TODO: implement socketcanWrite()
    struct can_frame frame;
    frame.can_id = (eff << 31) | (rtr << 30) | canid;
    frame.can_dlc = len;
    jint *pdata = env->GetIntArrayElements(data, 0);
    for(uint8_t i = 0; i < len; i++) {
        frame.data[i] = pdata[i] & 0xFF;
    }
    int ret =  write(fd, &frame, sizeof(struct can_frame));
    env->ReleaseIntArrayElements(data, pdata, 0);
    return  ret;
}

extern "C"
JNIEXPORT jlongArray JNICALL
Java_com_example_android_1socketcan_android_1socketcan_socketcanRead(JNIEnv *env, jobject thiz,
                                                                     jint fd) {
    // TODO: implement socketcanRead()
    jlongArray ret;
    ret = env->NewLongArray(12);
    struct can_frame frame;
    read(fd, &frame, sizeof(struct can_frame));
    int64_t data[12];
    data[0] = frame.can_id & 0x1FFFFFFF;
    data[1] = (frame.can_id >> 31) & 0x1;
    data[2] = (frame.can_id >> 30) & 0x1;
    data[3] = frame.can_dlc;
    for(uint8_t i = 0; i < frame.can_dlc; i++) {
        data[i + 4] = frame.data[i];
    }
    env->SetLongArrayRegion(ret, 0, 12, data);
    return ret;
}

给默认的TextView添加一个id, 还有 maxLines属性设为10

在MainActivity里面用can0测试一下

package com.example.android_socketcan;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.widget.TextView;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    android_socketcan 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 android_socketcan();
        can0.fd = can0.socketcanOpen("can0");

        //send
        new Thread() {
            int[] data = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7};
            @Override
            public void run() {
                while (true) {
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    data[0] = (data[0] + 1) % 0xFF;
                    can0.socketcanWrite(can0.fd, 0x123, 0, 0, 8, data);
                }
            }
        }.start();

        //receive
        new Thread() {
            long[] ret = new long[12];
            @Override
            public void run() {
                while (true) {
                    ret = can0.socketcanRead(can0.fd);
                    long can0id = ret[0];
                    long can0eff = ret[1];
                    long can0rtr = ret[2];
                    long can0len = ret[3];
                    long[] can0data = Arrays.copyOfRange(ret, 4, (int) (4+can0len));
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //can0  RX E R  123   [8]  CF A1 A2 A3 A4 A5 A6 A7
                            String str = "can0  RX ";
                            str += (can0eff==0) ? "S " : "E ";
                            str += (can0rtr==0) ? "-  " : "R  ";
                            String strid = Long.toHexString(can0id);
                            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 + "   [" + Long.toString(can0len) + "]  ";
                            for(int i=0; i
                                String hex = Long.toHexString(can0data[i]);
                                hex = (hex.length()==1) ? ('0'+hex) : hex;
                                str = str + ' ' + hex;
                            }
                            str = str.toUpperCase();
                            str += 'n';

                            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();

    }
}

连接Android平板的CAN0到周立功CAN分析仪的CAN0, 运行APP:

  • 平板每秒发出0x123的标准帧, 第0字节递增
  • 平板接收CAN分析仪发出的标准帧和扩展帧, 显示在屏幕正中间

Apktool

Apktool - A tool for reverse engineering 3rd party, closed, binary Android apps. (ibotpeaches.github.io)

使用Apktool查看下APK到底有没有打包.so

# APK路径
# Android_SocketCANappbuildoutputsapkdebugapp-debug.apk

.apktool_2.6.1.jar d .app-debug.apk

发现把所有平台的 .so 打包进去了

Github Android_SocketCAN

weifengdq/Android_SocketCAN: 在Android平板上使用SocketCAN (github.com)

欢迎扫描二维码关注微信公众号, 及时获取最新文章:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1005097.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号