Android驱动(二)串口操作之tiny4412开发板实现串口通讯的两种方法

编程入门 行业动态 更新时间:2024-10-08 02:18:22

Android驱动(二)<a href=https://www.elefans.com/category/jswz/34/1769224.html style=串口操作之tiny4412开发板实现串口通讯的两种方法"/>

Android驱动(二)串口操作之tiny4412开发板实现串口通讯的两种方法

  • 硬件平台:tiny4412
  • 系统:Android  5.0.2
  • 编译器: arm-linux-gcc-4.5.1

本文通过对网上资源的学习,现两种方法实现tiny4412串口通讯的APP。

方法1:JNI:使用开源框架:android-serialport-api

方法2:硬件访问服务:编译使用android5.0.2源码自带的APP代码

一、方法1-android-serialport-api

本节使用开源的串口类android-serialport-api,在tiny4412上实现串口操作。在tiny4412平台一共有四个串口,对应的设备节点是/dev/ttySAC0、/dev/ttySAC1、/dev/ttySAC2、/dev/ttySAC3。

JNI技术:

Java中可以调用C语言写成的库。为可在android中使用串口,android-serialport-api的作者自己写了一个c语言的动态链接库serial_port.so(自动命名成libserial_port.so),并把它放在了libs/aemeabi 里,其c源文件在JNI中,大家在下载了android-serialport-api的源代码后,将这两个文件夹copy到自己新建的工程中即可。

android-serialport-api主页:/

然后将调用c语言写成的动态链接库的java类放入到\app\src\main\java文件夹下的android.serialport包下,这里一定要将包名命名成这个,因为对JNI有一定了解的人就会知道,在写c语言链接库时候,函数的命名是和调用它的类所在的包名相关的,一旦包名与链接库中函数的命名不相符,就不能调用链接库的函数。这里可以打开jni中的.c文件(就是动态链接库的源文件),可以看到源码。下面看一下几个重要的代码:

SerialPort.java:接口类,加载C库并申明本地方法
内涵静态方法加载C库。
申明了几个本地方法,如串口属性设置方法,设备节点权限查看方法。open、close方法。
package android_serialport_api;import android.util.Log;import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;public class SerialPort {private static final String TAG = "SerialPort";private FileDescriptor mFd;private FileInputStream mFileInputStream;private FileOutputStream mFileOutputStream;public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {//检查访问权限,如果没有读写权限,进行文件操作,修改文件访问权限if (!device.canRead() || !device.canWrite()) {try {//通过挂在到linux的方式,修改文件的操作权限Process su = Runtime.getRuntime().exec("/system/xbin/su");String cmd = "chmod 777 " + device.getAbsolutePath() + "\n" + "exit\n";su.getOutputStream().write(cmd.getBytes());if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {throw new SecurityException();}} catch (Exception e) {e.printStackTrace();throw new SecurityException();}}mFd = open(device.getAbsolutePath(), baudrate, flags);if (mFd == null) {Log.e(TAG, "native open returns null");throw new IOException();}mFileInputStream = new FileInputStream(mFd);mFileOutputStream = new FileOutputStream(mFd);}// Getters and setterspublic InputStream getInputStream() {return mFileInputStream;}public OutputStream getOutputStream() {return mFileOutputStream;}// JNI(调用java本地接口,实现串口的打开和关闭)
/**串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1*//*** @param path     串口设备的据对路径* @param baudrate 波特率* @param flags    校验位*/private native static FileDescriptor open(String path, int baudrate, int flags);public native void close();static {//加载jni下的C文件库System.loadLibrary("serial_port");}
}
(2)SerialPort.c:jni文件
最后编译成SO库,内部实现串口操作,供上层java接口掉用。
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>
#include "SerialPort.h"
#include "android/log.h"
static const char *TAG="serial_port";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
static speed_t getBaudrate(jint baudrate)
{switch(baudrate) {case 0: return B0;case 50: return B50;case 75: return B75;case 110: return B110;case 134: return B134;case 150: return B150;case 200: return B200;case 300: return B300;case 600: return B600;case 1200: return B1200;case 1800: return B1800;case 2400: return B2400;case 4800: return B4800;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;case 1000000: return B1000000;case 1152000: return B1152000;case 1500000: return B1500000;case 2000000: return B2000000;case 2500000: return B2500000;case 3000000: return B3000000;case 3500000: return B3500000;case 4000000: return B4000000;default: return -1;}
}
/** Class:     android_serialport_SerialPort* Method:    open* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;*/
JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
{int fd;speed_t speed;jobject mFileDescriptor;/* Check arguments */{speed = getBaudrate(baudrate);if (speed == -1) {/* TODO: throw an exception */LOGE("Invalid baudrate");return NULL;}}/* Opening device */{jboolean iscopy;const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);fd = open(path_utf, O_RDWR | flags);LOGD("open() fd = %d", fd);(*env)->ReleaseStringUTFChars(env, path, path_utf);if (fd == -1){/* Throw an exception */LOGE("Cannot open port");/* TODO: throw an exception */return NULL;}}/* Configure device */{struct termios cfg;LOGD("Configuring serial port");if (tcgetattr(fd, &cfg)){LOGE("tcgetattr() failed");close(fd);/* TODO: throw an exception */return NULL;}cfmakeraw(&cfg);cfsetispeed(&cfg, speed);cfsetospeed(&cfg, speed);if (tcsetattr(fd, TCSANOW, &cfg)){LOGE("tcsetattr() failed");close(fd);/* TODO: throw an exception */return NULL;}}/* Create a corresponding file descriptor */{jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);}return mFileDescriptor;
}/** Class:     cedric_serial_SerialPort* Method:    close* Signature: ()V*/
JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close(JNIEnv *env, jobject thiz)
{jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);LOGD("close(fd = %d)", descriptor);close(descriptor);
}
(3)SerialPort.h
#ifdef __cplusplus
extern "C" {
#endif
/** Class:     android_serialport_api_SerialPort* Method:    open* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;*/
JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open(JNIEnv *, jclass, jstring, jint, jint);/** Class:     android_serialport_api_SerialPort* Method:    close* Signature: ()V*/
JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif
(4)Application.mk:决定了编译出哪些架构的SO文件
APP_ABI := armeabi armeabi-v7a x86
(5)Android.mk 通过NDK编译出SO文件。
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)TARGET_PLATFORM := android-3
LOCAL_MODULE    := serial_port
LOCAL_SRC_FILES := SerialPort.c
LOCAL_LDLIBS    := -lloginclude $(BUILD_SHARED_LIBRARY)
(6)MainActivity.java 加载界面,并实现串口的收发功能
package com.yang.serial201706;import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;import android_serialport_api.SerialPort;
public class MainActivity extends Activity {protected SerialPort mSerialPort;protected InputStream mInputStream;protected OutputStream mOutputStream;private TextView text;private String prot = "ttySAC2";private int baudrate = 9600;private static int i = 0;private StringBuilder sb;Handler handler = new Handler() {public void handleMessage(android.os.Message msg) {if (msg.what == 1) {text.setText(text.getText().toString().trim()+sb.toString());}}};private Thread receiveThread;private Thread sendThread;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);sb = new StringBuilder();text = (TextView) findViewById(R.id.text_receive);Button btn_set = (Button) findViewById(R.id.btn_set);btn_set.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {EditText et_prot = (EditText) findViewById(R.id.et_prot);EditText et_num = (EditText) findViewById(R.id.et_num);prot = TextUtils.isEmpty(et_prot.getText().toString().trim()) ? "ttyS0": et_prot.getText().toString().trim();baudrate = Integer.parseInt(TextUtils.isEmpty(et_num.getText().toString().trim()) ? "9600" : et_num.getText().toString().trim());}});Button btn_open = (Button) findViewById(R.id.btn_open);btn_open.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 打开try {mSerialPort = new SerialPort(new File("/dev/" + prot), baudrate,0);mInputStream = mSerialPort.getInputStream();mOutputStream = mSerialPort.getOutputStream();receiveThread();} catch (SecurityException e) {e.printStackTrace();} catch (IOException e) {Log.i("test", "打开失败");e.printStackTrace();}}});Button btn_send = (Button) findViewById(R.id.btn_send);btn_send.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 发送sendThread = new Thread() {@Overridepublic void run() {while (true) {try {i++;mOutputStream.write(("1").getBytes());Log.i("test", "发送成功:1" + i);Thread.sleep(1000);} catch (Exception e) {Log.i("test", "发送失败");e.printStackTrace();}}}};sendThread.start();}});Button btn_receive = (Button) findViewById(R.id.btn_receive);btn_receive.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {closeSerialPort();}});}private void receiveThread() {// 接收receiveThread = new Thread() {@Overridepublic void run() {while (true) {int size;try {byte[] buffer = new byte[1024];if (mInputStream == null)return;size = mInputStream.read(buffer);if (size > 0) {String recinfo = new String(buffer, 0,size);Log.i("test", "接收到串口信息:" + recinfo);sb.append(recinfo).append(",");handler.sendEmptyMessage(1);}} catch (IOException e) {e.printStackTrace();}}}};receiveThread.start();}/*** 关闭串口*/public void closeSerialPort() {if (mSerialPort != null) {mSerialPort.close();}if (mInputStream != null) {try {mInputStream.close();} catch (IOException e) {e.printStackTrace();}}if (mOutputStream != null) {try {mOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}}

开发流程:

(1)新建一个工程

实现界面、已经java操作逻辑。

(2)在main目录创建jni和Libs的两个文件夹

SO文件需要放在Libs下,将MK和C文件放在jni下。如图:


(3)build.gradle中指定JNI库的路径

sourceSets {main {jniLibs.srcDirs  'src/main/libs'jni.srcDirs = []}
}



(4)新建android_serialport_api目录,放入SerialPort.java


(5)安装NKD编译环境,解压android-ndk-r9d-windows-x86_64

配置PATH:

F:\eclipse\adt-bundle-windows-x86_64-20140702\android-ndk-r9d-windows-x86_64\android-ndk-r9d

(6)准备JNI文件,放到创建的jni目录下


(7) cmd进入jni所在目录,执行ndk-build(AS编译没自动产生,还在找原因,暂时采用这种方法)

(8)工程下自动生成libs目录和obj目录,并将产生的SO存入到了之前创建的libs目录。


(9)下载到tiny4412单板,启动应用程序


二、方法2,:Android内核中自带的APP代码

(1)所涉及内核源代码路径

APP:
frameworks/base/tests/SerialChat/
SerialManager:
frameworks/base/core/java/android/hardware/SerialManager.java
SerialService:
frameworks/base/services/core/java/com/android/server/SerialService.java
SerialPort:
frameworks/base/core/java/android/hardware/SerialPort.java
JNI:
frameworks/base/services/core/jni/com_android_server_SerialService.cpp
frameworks/base/core/jni/android_hardware_SerialPort.cpp
AIDL:
frameworks/base/core/java/android/hardware/ISerialManager.aidl

(2)对源代码的修改以支持tiny4412

修改frameworks/base/tests/SerialChat/Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := SerialChat
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(BUILD_PACKAGE)
修改frameworks/base/tests/SerialChat/AndroidManifest.xml

<manifest xmlns:android=""
    package="com.android.serialchat"
    android:sharedUserId="android.uid.system">
    <uses-permission android:name="android.permission.SERIAL_PORT"/>
    <application android:label="Serial Chat">
        <activity android:name="SerialChat" android:label="Serial Chat">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

修改frameworks/base/core/res/res/values/config.xml
<string-array translatable="false" name="config_serialPorts"><item>"/dev/ttySAC0"</item><item>"/dev/ttySAC1"</item><item>"/dev/ttySAC2"</item><item>"/dev/ttySAC3"</item>
</string-array>
修改frameworks/base/tests/SerialChat/src/com/android/serialchat/SerialChat.java
    public void onResume() {         super.onResume();         String[] ports = mSerialManager.getSerialPorts();         if (ports != null && ports.length > 0) {             try {                 mSerialPort = mSerialManager.openSerialPort(ports[0], 115200);                 if (mSerialPort != null) {                     new Thread(this).start();                 }             } catch (IOException e) {             }         }     } 
(3)编译
mmm frameworks/base/tests/SerialChat/
make snod
./gen-img.sh
 
(4)安装
将编译好的system.image更新到单板
将编译产生的.apk拷贝到windows,启动adb下载
 
adb install   F:\SerialChat.apk

(5)测试
 
 
 
 
 
 
 
 
 
 

显然使用内核自带的APP简陋的一点,但是收发功能都是具备的!

三、方法对比

显然第一种是最传统的方法,因为java可以通过JNI访问C库,通过这个思路,直接使用JNI来访问C库,C库中实现对串口的open、close、通讯等操作。

第二中方法则是使用了,硬件访问服务,来实现对硬件操作,APP通过获取服务,并使用服务里面的方法,来操作硬件,显然这种方法是google推荐的,因为当设备过多的时候,我们不能总是open、close。 同时可以对比学习: Android驱动(一)硬件访问服务学习之(一)Android通过JNI访问硬件: Android驱动(一)硬件访问服务学习之(二)Android通过硬件访问服务访问硬件:

四、代码下载

博客中涉及的APP代码,及内核源代码的修改,下载链接如下:
 


更多推荐

Android驱动(二)串口操作之tiny4412开发板实现串口通讯的两种方法

本文发布于:2024-03-12 09:58:57,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1731257.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:串口   两种   操作   通讯   开发板

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!