【Android应用开发】-(19)Android 串口编程原理和实现方式
【Android应用开发】-(19)Android 串口编程原理和实现方式
分类: Android 2011-11-28 23:40 11955人阅读 评论(81) 收藏 举报 android Android exception Exception jni JNI string String 编程目录(?)[+]
提到串口编程,就不得不提到JNI,不得不提到JavaAPI中的文件描述符类:FileDescriptor。下面我分别对JNI、FileDescriptor以及串口的一些知识点和实现的源码进行分析说明。这里主要是参考了开源项目android-serialport-api。
串口编程需要了解的基本知识点:对于串口编程,我们只需对串口进行一系列的设置,然后打开串口,这些操作我们可以参考串口调试助手的源码进行学习。在Java中如果要实现串口的读写功能只需操作文件设备类:FileDescriptor即可,其他的事都由驱动来完成不用多管!当然,你想了解,那就得看驱动代码了。这里并不打算对驱动进行说明,只初略阐述应用层的实现方式。
(一)JNI:
关于JNI的文章网上有很多,不再多做解释,想详细了解的朋友可以查看云中漫步的技术文章,写得很好,分析也很全面,那么在这篇拙文中我强调3点:
1、如何将编译好的SO文件打包到APK中?(方法很简单,直接在工程目录下新建文件夹 libs/armeabi,将SO文件Copy到此目录即可)
2、命名要注意的地方?(在编译好的SO文件中,将文件重命名为:libfilename.so即可。其中filename.so是编译好后生成的文件)
3、MakeFile文件的编写(不用多说,可以直接参考package/apps目录下用到JNI的相关项目写法)
这是关键的代码:
- <spanstyle="font-size:18px;">intfd;
- speed_tspeed;
- jobjectmFileDescriptor;
- /*Checkarguments*/
- {
- speed=getBaudrate(baudrate);
- if(speed==-1){
- /*TODO:throwanexception*/
- LOGE("Invalidbaudrate");
- returnNULL;
- }
- }
- /*Openingdevice*/
- {
- jbooleaniscopy;
- constchar*path_utf=(*env)->GetStringUTFChars(env,path,&iscopy);
- LOGD("Openingserialport%swithflags0x%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)
- {
- /*Throwanexception*/
- LOGE("Cannotopenport");
- /*TODO:throwanexception*/
- returnNULL;
- }
- }
- /*Configuredevice*/
- {
- structtermioscfg;
- LOGD("Configuringserialport");
- if(tcgetattr(fd,&cfg))
- {
- LOGE("tcgetattr()failed");
- close(fd);
- /*TODO:throwanexception*/
- returnNULL;
- }
- cfmakeraw(&cfg);
- cfsetispeed(&cfg,speed);
- cfsetospeed(&cfg,speed);
- if(tcsetattr(fd,TCSANOW,&cfg))
- {
- LOGE("tcsetattr()failed");
- close(fd);
- /*TODO:throwanexception*/
- returnNULL;
- }
- }
- </span>
(二)FileDescritor:
文件描述符类的实例用作与基础机器有关的某种结构的不透明句柄,该结构表示开放文件、开放套接字或者字节的另一个源或接收者。文件描述符的主要实际用途是创建一个包含该结构的FileInputStream
或FileOutputStream
。这是API的描述,不太好理解,其实可简单的理解为:FileDescritor就是对一个文件进行读写。
(三)实现串口通信细节
1) 建工程:SerialDemo包名:org.winplus.serial,并在工程目录下新建jni和libs两个文件夹和一个org.winplus.serial.utils,如下图:
2) 新建一个类:SerialPortFinder,添加如下代码:
- <spanstyle="font-size:18px;">packageorg.winplus.serial.utils;
- importjava.io.File;
- importjava.io.FileReader;
- importjava.io.IOException;
- importjava.io.LineNumberReader;
- importjava.util.Iterator;
- importjava.util.Vector;
- importandroid.util.Log;
- publicclassSerialPortFinder{
- privatestaticfinalStringTAG="SerialPort";
- privateVector<Driver>mDrivers=null;
- publicclassDriver{
- publicDriver(Stringname,Stringroot){
- mDriverName=name;
- mDeviceRoot=root;
- }
- privateStringmDriverName;
- privateStringmDeviceRoot;
- Vector<File>mDevices=null;
- publicVector<File>getDevices(){
- if(mDevices==null){
- mDevices=newVector<File>();
- Filedev=newFile("/dev");
- File[]files=dev.listFiles();
- inti;
- for(i=0;i<files.length;i++){
- if(files[i].getAbsolutePath().startsWith(mDeviceRoot)){
- Log.d(TAG,"Foundnewdevice:"+files[i]);
- mDevices.add(files[i]);
- }
- }
- }
- returnmDevices;
- }
- publicStringgetName(){
- returnmDriverName;
- }
- }
- Vector<Driver>getDrivers()throwsIOException{
- if(mDrivers==null){
- mDrivers=newVector<Driver>();
- LineNumberReaderr=newLineNumberReader(newFileReader(
- "/proc/tty/drivers"));
- Stringl;
- while((l=r.readLine())!=null){
- //Issue3:
- //Sincedrivernamemaycontainspaces,wedonotextract
- //drivernamewithsplit()
- Stringdrivername=l.substring(0,0x15).trim();
- String[]w=l.split("+");
- if((w.length>=5)&&(w[w.length-1].equals("serial"))){
- Log.d(TAG,"Foundnewdriver"+drivername+"on"
- +w[w.length-4]);
- mDrivers.add(newDriver(drivername,w[w.length-4]));
- }
- }
- r.close();
- }
- returnmDrivers;
- }
- publicString[]getAllDevices(){
- Vector<String>devices=newVector<String>();
- //Parseeachdriver
- Iterator<Driver>itdriv;
- try{
- itdriv=getDrivers().iterator();
- while(itdriv.hasNext()){
- Driverdriver=itdriv.next();
- Iterator<File>itdev=driver.getDevices().iterator();
- while(itdev.hasNext()){
- Stringdevice=itdev.next().getName();
- Stringvalue=String.format("%s(%s)",device,
- driver.getName());
- devices.add(value);
- }
- }
- }catch(IOExceptione){
- e.printStackTrace();
- }
- returndevices.toArray(newString[devices.size()]);
- }
- publicString[]getAllDevicesPath(){
- Vector<String>devices=newVector<String>();
- //Parseeachdriver
- Iterator<Driver>itdriv;
- try{
- itdriv=getDrivers().iterator();
- while(itdriv.hasNext()){
- Driverdriver=itdriv.next();
- Iterator<File>itdev=driver.getDevices().iterator();
- while(itdev.hasNext()){
- Stringdevice=itdev.next().getAbsolutePath();
- devices.add(device);
- }
- }
- }catch(IOExceptione){
- e.printStackTrace();
- }
- returndevices.toArray(newString[devices.size()]);
- }
- }
- </span>
上面这个类在“android-serialport-api串口工具测试随笔”中有详细的说明,我就不多说了。
3)新建SerialPort类,这个类主要用来加载SO文件,通过JNI的方式打开关闭串口
- <spanstyle="font-size:18px;">packageorg.winplus.serial.utils;
- importjava.io.File;
- importjava.io.FileDescriptor;
- importjava.io.FileInputStream;
- importjava.io.FileOutputStream;
- importjava.io.IOException;
- importjava.io.InputStream;
- importjava.io.OutputStream;
- importandroid.util.Log;
- publicclassSerialPort{
- privatestaticfinalStringTAG="SerialPort";
- /*
- *DonotremoveorrenamethefieldmFd:itisusedbynativemethod
- *close();
- */
- privateFileDescriptormFd;
- privateFileInputStreammFileInputStream;
- privateFileOutputStreammFileOutputStream;
- publicSerialPort(Filedevice,intbaudrate,intflags)
- throwsSecurityException,IOException{
- /*Checkaccesspermission*/
- if(!device.canRead()||!device.canWrite()){
- try{
- /*Missingread/writepermission,tryingtochmodthefile*/
- Processsu;
- su=Runtime.getRuntime().exec("/system/bin/su");
- Stringcmd="chmod666"+device.getAbsolutePath()+"\n"
- +"exit\n";
- su.getOutputStream().write(cmd.getBytes());
- if((su.waitFor()!=0)||!device.canRead()
- ||!device.canWrite()){
- thrownewSecurityException();
- }
- }catch(Exceptione){
- e.printStackTrace();
- thrownewSecurityException();
- }
- }
- mFd=open(device.getAbsolutePath(),baudrate,flags);
- if(mFd==null){
- Log.e(TAG,"nativeopenreturnsnull");
- thrownewIOException();
- }
- mFileInputStream=newFileInputStream(mFd);
- mFileOutputStream=newFileOutputStream(mFd);
- }
- //Gettersandsetters
- publicInputStreamgetInputStream(){
- returnmFileInputStream;
- }
- publicOutputStreamgetOutputStream(){
- returnmFileOutputStream;
- }
- //JNI
- privatenativestaticFileDescriptoropen(Stringpath,intbaudrate,
- intflags);
- publicnativevoidclose();
- static{
- System.loadLibrary("serial_port");
- }
- }
- </span>
4) 新建一个MyApplication 继承android.app.Application,用来对串口进行初始化和关闭串口
- <spanstyle="font-size:18px;">packageorg.winplus.serial;
- importjava.io.File;
- importjava.io.IOException;
- importjava.security.InvalidParameterException;
- importorg.winplus.serial.utils.SerialPort;
- importorg.winplus.serial.utils.SerialPortFinder;
- importandroid.content.SharedPreferences;
- publicclassMyApplicationextendsandroid.app.Application{
- publicSerialPortFindermSerialPortFinder=newSerialPortFinder();
- privateSerialPortmSerialPort=null;
- publicSerialPortgetSerialPort()throwsSecurityException,IOException,InvalidParameterException{
- if(mSerialPort==null){
- /*Readserialportparameters*/
- SharedPreferencessp=getSharedPreferences("android_serialport_api.sample_preferences",MODE_PRIVATE);
- Stringpath=sp.getString("DEVICE","");
- intbaudrate=Integer.decode(sp.getString("BAUDRATE","-1"));
- /*Checkparameters*/
- if((path.length()==0)||(baudrate==-1)){
- thrownewInvalidParameterException();
- }
- /*Opentheserialport*/
- mSerialPort=newSerialPort(newFile(path),baudrate,0);
- }
- returnmSerialPort;
- }
- publicvoidcloseSerialPort(){
- if(mSerialPort!=null){
- mSerialPort.close();
- mSerialPort=null;
- }
- }
- }
- </span>
5) 新建一个继承抽象的Activity类,主要用于读取串口的信息
- <spanstyle="font-size:18px;">packageorg.winplus.serial;
- importjava.io.IOException;
- importjava.io.InputStream;
- importjava.io.OutputStream;
- importjava.security.InvalidParameterException;
- importorg.winplus.serial.utils.SerialPort;
- importandroid.app.Activity;
- importandroid.app.AlertDialog;
- importandroid.content.DialogInterface;
- importandroid.content.DialogInterface.OnClickListener;
- importandroid.os.Bundle;
- publicabstractclassSerialPortActivityextendsActivity{
- protectedMyApplicationmApplication;
- protectedSerialPortmSerialPort;
- protectedOutputStreammOutputStream;
- privateInputStreammInputStream;
- privateReadThreadmReadThread;
- privateclassReadThreadextendsThread{
- @Override
- publicvoidrun(){
- super.run();
- while(!isInterrupted()){
- intsize;
- try{
- byte[]buffer=newbyte[64];
- if(mInputStream==null)
- return;
- /**
- *这里的read要尤其注意,它会一直等待数据,等到天荒地老,海枯石烂。如果要判断是否接受完成,只有设置结束标识,或作其他特殊的处理。
- */
- size=mInputStream.read(buffer);
- if(size>0){
- onDataReceived(buffer,size);
- }
- }catch(IOExceptione){
- e.printStackTrace();
- return;
- }
- }
- }
- }
- privatevoidDisplayError(intresourceId){
- AlertDialog.Builderb=newAlertDialog.Builder(this);
- b.setTitle("Error");
- b.setMessage(resourceId);
- b.setPositiveButton("OK",newOnClickListener(){
- publicvoidonClick(DialogInterfacedialog,intwhich){
- SerialPortActivity.this.finish();
- }
- });
- b.show();
- }
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- mApplication=(MyApplication)getApplication();
- try{
- mSerialPort=mApplication.getSerialPort();
- mOutputStream=mSerialPort.getOutputStream();
- mInputStream=mSerialPort.getInputStream();
- /*Createareceivingthread*/
- mReadThread=newReadThread();
- mReadThread.start();
- }catch(SecurityExceptione){
- DisplayError(R.string.error_security);
- }catch(IOExceptione){
- DisplayError(R.string.error_unknown);
- }catch(InvalidParameterExceptione){
- DisplayError(R.string.error_configuration);
- }
- }
- protectedabstractvoidonDataReceived(finalbyte[]buffer,finalintsize);
- @Override
- protectedvoidonDestroy(){
- if(mReadThread!=null)
- mReadThread.interrupt();
- mApplication.closeSerialPort();
- mSerialPort=null;
- super.onDestroy();
- }
- }
- </span>
6)编写string.xml 以及baudrates.xml文件
在string.xml文件中添加:- <spanstyle="font-size:18px;"><stringname="error_configuration">Pleaseconfigureyourserialportfirst.</string>
- <stringname="error_security">Youdonothaveread/writepermissiontotheserialport.</string>
- <stringname="error_unknown">Theserialportcannotbeopenedforanunknownreason.</string>
- </span>
在baudrates.xml文件中添加
- <spanstyle="font-size:18px;"><?xmlversion="1.0"encoding="utf-8"?>
- <resources>
- <string-arrayname="baudrates_name">
- <item>50</item>
- <item>75</item>
- <item>110</item>
- <item>134</item>
- <item>150</item>
- <item>200</item>
- <item>300</item>
- <item>600</item>
- <item>1200</item>
- <item>1800</item>
- <item>2400</item>
- <item>4800</item>
- <item>9600</item>
- <item>19200</item>
- <item>38400</item>
- <item>57600</item>
- <item>115200</item>
- <item>230400</item>
- <item>460800</item>
- <item>500000</item>
- <item>576000</item>
- <item>921600</item>
- <item>1000000</item>
- <item>1152000</item>
- <item>1500000</item>
- <item>2000000</item>
- <item>2500000</item>
- <item>3000000</item>
- <item>3500000</item>
- <item>4000000</item>
- </string-array>
- <string-arrayname="baudrates_value">
- <item>50</item>
- <item>75</item>
- <item>110</item>
- <item>134</item>
- <item>150</item>
- <item>200</item>
- <item>300</item>
- <item>600</item>
- <item>1200</item>
- <item>1800</item>
- <item>2400</item>
- <item>4800</item>
- <item>9600</item>
- <item>19200</item>
- <item>38400</item>
- <item>57600</item>
- <item>115200</item>
- <item>230400</item>
- <item>460800</item>
- <item>500000</item>
- <item>576000</item>
- <item>921600</item>
- <item>1000000</item>
- <item>1152000</item>
- <item>1500000</item>
- <item>2000000</item>
- <item>2500000</item>
- <item>3000000</item>
- <item>3500000</item>
- <item>4000000</item>
- </string-array>
- </resources>
- </span>
7)开始编写界面了:在main.xml布局文件中添加两个编辑框,一个用来发送命令,一个用来接收命令:
- <spanstyle="font-size:18px;"><?xmlversion="1.0"encoding="utf-8"?>
- <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
- <EditText
- android:id="@+id/EditTextReception"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:gravity="top"
- android:hint="Reception"
- android:isScrollContainer="true"
- android:scrollbarStyle="insideOverlay">
- </EditText>
- <EditText
- android:id="@+id/EditTextEmission"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:hint="Emission"
- android:lines="1">
- </EditText>
- </LinearLayout>
- </span>
8) SerialDemoActivity类的实现:
- <spanstyle="font-size:18px;">packageorg.winplus.serial;
- importjava.io.IOException;
- importandroid.os.Bundle;
- importandroid.view.KeyEvent;
- importandroid.widget.EditText;
- importandroid.widget.TextView;
- importandroid.widget.TextView.OnEditorActionListener;
- publicclassSerialDemoActivityextendsSerialPortActivity{
- EditTextmReception;
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //setTitle("Loopbacktest");
- mReception=(EditText)findViewById(R.id.EditTextReception);
- EditTextEmission=(EditText)findViewById(R.id.EditTextEmission);
- Emission.setOnEditorActionListener(newOnEditorActionListener(){
- publicbooleanonEditorAction(TextViewv,intactionId,KeyEventevent){
- inti;
- CharSequencet=v.getText();
- char[]text=newchar[t.length()];
- for(i=0;i<t.length();i++){
- text[i]=t.charAt(i);
- }
- try{
- mOutputStream.write(newString(text).getBytes());
- mOutputStream.write('\n');
- }catch(IOExceptione){
- e.printStackTrace();
- }
- returnfalse;
- }
- });
- }
- @Override
- protectedvoidonDataReceived(finalbyte[]buffer,finalintsize){
- runOnUiThread(newRunnable(){
- publicvoidrun(){
- if(mReception!=null){
- mReception.append(newString(buffer,0,size));
- }
- }
- });
- }
- }
- </span>
写到这里,代码基本上写完了。下面就是要实现JNI层的功能了,要实现JNI,必须首先生成头文件,头文件的生成方式也很简单, 我们编译工程,在终端输入 javah org.winplus.serial.utils.SerialPort 则会生成头文件:org_winplus_serial_utils_SerialPort.h,这个头文件的名字可以随意命名。我们将它命名为:SerialPort.h拷贝到新建的目录jni中,新建SerialPort.c 文件,这两个文件的代码就不贴出来了。直接到上传的代码中看吧。
(四)串口的应用,可实现扫描头,指纹识别等外围USB转串口的特色应用。
原创文章,转载请注明出处:http://www.blog.csdn.net/tangcheng_ok
还蛮繁琐的,以上只是对开源项目android-serialport-api 进行精简想了解此项目请点击此处!就这样吧,晚了准备见周公去!
更多相关文章
- Android 打开相机、相册获取图片文件,支持Android 9.0系统
- 解决 android 在sd卡新建文件后需要重启才能找到
- Android遍历文件Listfile返回值为null问题解决方法适用Android8.
- Android上传文件至PHP服务器
- 【知识点】android代码中设置margin
- Gradle 修改生成apk时的文件名
- Android 实现apk文件下载并自动安装
- Android客户端上传文件到服务器端
- Android蓝牙通信代码