# UartTools **Repository Path**: gunix/UartTools ## Basic Information - **Project Name**: UartTools - **Description**: UartTools android jni对uart的读写操作 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2021-11-17 - **Last Updated**: 2021-11-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README @[toc] # 前言 现在一直在弄**蓝牙**的项目,已经有一年时间没有弄Android的东西了。现在有时间想把以前弄的东西整理一下,方便自己以后需要用时翻出来看看。 这个APP是以前在`MT6735`平台`Android 5.1(L1)`调试验证UART外设发送过来的数据是否正确,想着也许后面调试还用的着,就记录一下。 # 硬件平台相关配置 `APP`需要操作平台设备硬件时,需要了解以下几点信息(我这里是在`MTK`平台上,其它`Android`平台也类似): - 需要操作的**UART**是否有权限?系统默认其它APP没有读写权限,这里需要添加权限,以下有2种方式: ```c // 方式一:使用adb直接给UART设备添加权限 $ adb shell $ chmod 0666 /dev/ttyMT* // 方式二:在init.rc文件里面添加权限,每次开机它都自动添加权限。和方式一相比就不用每次手动添加权限; chmod 0666 /dev/ttyMT* chown system system /dev/ttyMT* ``` - `MTK`平台`UART`**硬件物理端口名称**和**软件字符设备名称**对应关系: | 硬件物理端口名称 | 软件字符设备名称 | | -- | -- | | UART1 | /dev/ttyMT0 | | UART2 | /dev/ttyMT1 | | UART3 | /dev/ttyMT2 | | UART4 | /dev/ttyMT3 | - 如何关闭**SELinux**权限? 在`Android 5.0`以上添加了这个权限,字符设备有`read/write`权限**APP**也不能直接访问字符设备。这里有3种处理方式: 1. 设备是ENG版本(有root权限),可以使用adb将SELinux关闭。 > `adb shell setenforce 0 ` 2. 将APP操作UART需要的权限添加到SELinux。(这里暂不介绍) > 在Kernel LOG / Main Log 中查询关键字 "avc:" 看看是否有SELinux Policy Exception。 3. 代码中关闭selinux机制。 > 文件路径:`bootable/bootloader/lk/platform/mt6735/rules.mk` > 文件中对应的内容: > // choose one of following value -> 1: disabled/ 2: permissive /3: enforcing > 把`SELINUX_STATUS := 3 `修改为`SELINUX_STATUS := 1` # APP主要实现的功能 功能很简单,主要是将UART外设发送的数据实时的在APP上面显示出来。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019021701155757.png) # APP层代码分析 APP层代码主要是2个class:`MainActivity`和`UartJniTool` - **MainActivity**是主界面窗口类,主要处理步骤如下: 1. `uartJniTool = new UartJniTool(mhandler); `实例化`UartJniTool`类,并且将mhandler传递给`UartJniTool`对象保存起来,方便后面更新UI。(Android规定只有主线程才能更新UI) 2. 定义一个`readUartBtn`,被点击后`uartJniTool.uartToolStart();`开始获取uart data,再次被点击停止获取数据并且关闭uart。 3. 定义一个`writeUartBtn`,发送固定的一段字符串,验证APP是否可以正常的发送数据。 4. 定义的mhandler主要是将接收到消息的数据,显示在TextView上; ```java public class MainActivity extends AppCompatActivity { private static final String TAG= "MainActivity"; private static final int UARTDATA= 8001; UartJniTool uartJniTool; ToggleButton readUartBtn; Button writeUartBtn; TextView uartText; private int offset; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); uartJniTool = new UartJniTool(mhandler); //uartJniTool.uartToolStart(); uartText = (TextView)findViewById(R.id.uartText); uartText.setMovementMethod(ScrollingMovementMethod.getInstance()); readUartBtn = (ToggleButton)findViewById(R.id.readUartBtn); writeUartBtn = (Button)findViewById(R.id.writeUartBtn); readUartBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (readUartBtn.isChecked()){ int result = uartJniTool.uartToolStart(); if (result < 0){ readUartBtn.setChecked(false); } }else { uartJniTool.uartToolStop(); } } }); writeUartBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { uartJniTool.writeUartData(); } }); } Handler mhandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case UARTDATA: int uartData = msg.getData().getInt("UARTDATA"); Log.i(TAG, "handleMessage---->"+uartData); uartText.append(numToHex8(uartData)+" "); offset=uartText.getLineCount()*uartText.getLineHeight(); if(offset>uartText.getHeight()){ uartText.scrollTo(0,offset-uartText.getHeight()); } break; } super.handleMessage(msg); } }; public static String numToHex8(int b) { return String.format("0x%02x", b);//2表示需要两个16进行数 } ``` - **UartJniTool**是调用JNI native方法接口类,主要处理步骤如下: 1. 加载本地`libUartToothJni.so`库,通过方法`System.loadLibrary("UartToothJni");`来加载。 2. `getUartDataHandler`方法是JNI层获取到UART数据的后,主动回调该方法。`getUartDataHandler`方法拿到数据后,通过Handler发送给**MainActivity**显示出来。 3. `uartToolStart`功能是打开和读取UART的数据,`uartToolStop`功能是关闭UART,`writeUartData`功能是发送固定的数据验证APP是否可以操作UART。 ```java public class UartJniTool { private static final String TAG= "UartJniTool"; private static final int UARTDATA= 8001; private Handler mhandler; static { System.loadLibrary("UartToothJni"); } public UartJniTool(Handler mhandler) { this.mhandler = mhandler; } public void getUartDataHandler(int data) { Log.i(TAG, "getUartDataHandler---->"+data); Message msg = new Message(); msg.what = UARTDATA; Bundle bundle = new Bundle(); bundle.putInt("UARTDATA", data); msg.setData(bundle);//mes利用Bundle传递数据 mhandler.sendMessage(msg);//用activity中的handler发送消息 } public native int uartToolStart(); public native int uartToolStop(); //public native String getUartData(); public native int writeUartData(); } ``` # JNI native层代码分析 我们需要定义**JNI**的头文件,一般都是通过命令自动生成的,如下(具体操作百度吧,很多的): > ` javah -jni package name.class name` 如果你比较熟悉**JNI**了,其实也不用`javah`命令生成头文件也可以。**JNI**函数的名称定义是有一定的规律: > **Java flag + package name + class name + method name** > For example: `Java_com_lututong_uarttools_UartJniTool_uartToolStart` - `Java_com_lututong_uarttools_UartJniTool_uartToolStart`对应app层`UartJniTool`类的`uartToolStart`方法,主要功能如下: 1. 获取并且保存Java虚拟机和调用**JNI**的对象,为`read thread`主动调用app层方法做好准备。 2. `uart_tool_start`主要open和init uart; - `Java_com_lututong_uarttools_UartJniTool_uartToolStop`主要功能是停止获取数据,并且关闭UART。 - `Java_com_lututong_uarttools_UartJniTool_writeUartData`主要功能是发送固定的数据验证APP是否可以操作UART。 ```cpp /* * Class: com_lututong_uarttools_UartJniTool * Method: uartToolStart * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_uartToolStart(JNIEnv *env, jobject obj) { int ret; //get java VM and object env->GetJavaVM(&gs_jvm); gs_object = env->NewGlobalRef(obj); // open and init uart ret = uart_tool_start(); return ret; } /* * Class: com_lututong_uarttools_UartJniTool * Method: uartToolStop * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_uartToolStop(JNIEnv *env, jobject obj) { uart_tool_stop(); return 0; } /* * Class: com_lututong_uarttools_UartJniTool * Method: writeUartData * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_lututong_uarttools_UartJniTool_writeUartData(JNIEnv *env, jobject obj) { char *buffer = "cai.zhong!!!"; int ret = -1; if(serial_fd >0){ ret = write(serial_fd, buffer, 9); //for (int i = 0; i < 7; i++) //ret = write(serial_fd, &buffer[0], 1); } if(ret > 0) LOGE("[cai.zhong]Write data successfully!\n"); return 0; } ``` - 打开**UART**并且设置**波特率**和其它属性。 ```cpp int uart_speed(int speed) { switch (speed) { case 9600: return B9600; case 19200: return B19200; case 38400: return B38400; case 57600: return B57600; case 115200: return B115200; case 230400: return B230400; case 460800: return B460800; case 500000: return B500000; case 576000: return B576000; case 921600: return B921600; default: return B57600; } } int init_serial(int speed) { struct termios ti; int baudenum; int fd_bt; // fd_bt = open(UART_DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK); fd_bt = open(UART_DEVICE, O_RDWR | O_NOCTTY ); if (fd_bt < 0) { LOGE("Can't open serial port %s\n", UART_DEVICE); return -1; } else LOGI("open %s successfully!!! fd_bt=%d", UART_DEVICE, fd_bt); tcflush(fd_bt, TCIOFLUSH); if (tcgetattr(fd_bt, &ti) < 0) { LOGE("Can't get serial port setting\n"); close(fd_bt); fd_bt=-1; return -1; } cfmakeraw(&ti); ti.c_cflag |= CLOCAL; ti.c_lflag = 0; ti.c_cflag &= ~CRTSCTS; ti.c_iflag &= ~(IXON | IXOFF | IXANY); /* Set baudrate */ baudenum = uart_speed(speed); // if ((baudenum == B115200) && (speed != 115200)) { if ((baudenum == B9600) && (speed != 9600)) { LOGE("Serial port baudrate not supported!\n"); close(fd_bt); fd_bt=-1; return -1; } cfsetospeed(&ti, baudenum); cfsetispeed(&ti, baudenum); if (tcsetattr(fd_bt, TCSANOW, &ti) < 0) { LOGE("Can't set serial port setting\n"); close(fd_bt); fd_bt=-1; return -1; } tcflush(fd_bt, TCIOFLUSH); return fd_bt; } ``` - `thread_exit` 关闭read线程。 - `bt_rx_monitor`read线程将获取到的数据发送给APP层,主要的步骤如下: 1. 通过`gs_jvm`获取`JNIEnv`环境变量,通过`gs_object`获取`jclass`对象类; 2. 调用`GetMethodID`方法拿到`UartJniTool`类的`getUartDataHandler`方法; 3. 通过`read`获取**UART**接收到的数据; 4. 通过`CallVoidMethod`方法来将数据传给`UartJniTool`类的`getUartDataHandler`方法; - `uart_tool_start`初始化串口、创建和启动`read thread`; - `uart_tool_stop`杀掉`read thread`和关闭**UART**; ```cpp static void thread_exit(int signo) { pthread_t tid = pthread_self(); LOGI("Thread %lu exits\n", tid); pthread_exit(0); } void *bt_rx_monitor(void *ptr) { char ucRxBuf; int ret; JNIEnv *env; jclass ClassCJM; jmethodID MethodGetUartDataHandler; jobject getUartDataHandlerDescriptor; LOGI("Thread %lu starts\n", rxThread); #if 1 if (gs_jvm != NULL) { gs_jvm->AttachCurrentThread((JNIEnv **)&env, NULL); ClassCJM = env->GetObjectClass(gs_object); MethodGetUartDataHandler = env->GetMethodID(ClassCJM, "getUartDataHandler", "(I)V"); //etUartDataHandlerDescriptor = env->NewObject(ClassCJM, MethodGetUartDataHandler); } #endif while (1) { if(serial_fd >0) { ret = read(serial_fd, &ucRxBuf, 1); LOGE("[cai.zhong]Receive data= %d ret=%d\n",ucRxBuf,ret); if(ret < -1){ LOGE("Receive packet from external device fails\n"); break; }else if(ret >0){ if (gs_object) { env->CallVoidMethod(gs_object, MethodGetUartDataHandler, ucRxBuf); } } } } //#endif return 0; } int uart_tool_start(void) { serial_fd = init_serial(9600);//115200 if (serial_fd < 0) { LOGE("Initialize serial port to Device fails\n"); return -1; } //signal(SIGRTMIN, thread_exit); /* Create RX monitor thread */ pthread_create(&rxThread, NULL, bt_rx_monitor, NULL); LOGI("UART TOOL mode start\n"); return 0; } void uart_tool_stop(void) { /* Wait until thread exit */ pthread_kill(rxThread, 0); //pthread_join(rxThread, NULL); //signal(SIGRTMIN, SIG_DFL); close(serial_fd); serial_fd = -1; } ``` # 完整的工程代码 gitee地址: https://gitee.com/dianqi0901zc/UartTools