最近接了個工程,需求是給特製的工程安卓板子做串口讀寫以實現一些特定外接設備的互動——是的你沒有看錯,安卓板子也是可以有串口的!
很多做移動開發的朋友可能沒接觸過——其實在這之前我也沒接觸過。踩了七八個小時的坑,終於爬出來了,這裡做一個總結,可能各個工程板子具體情況不同,大家一定要隨機應變。
首先你要知道的
跳線,工程板子為了節約 USB 接口,默認的 USB 接口都是對外的不能接電腦調試,根據我上手的這個板子來說,在接近 RJ45 也就是網線口的那個 USB 旁邊就有一個跳線,可以把它改為連接電腦的專用口。——當然了,別忘記開調試模式。
找對你的串口號,一般板子的說明書上會有,或者在示例程序裡查看也行。
Java 不能直接對安卓的串口進行讀寫,要用 C 或者 C++ 才行,所以要用到比較高級的 JNI 來橋接,把對串口讀寫的部分做成鏈接庫。
讀取串口不需要 root,至少我沒有遇到,之前踩坑的過程中有幾次是提示沒有權限的,但最後證明是我當時寫錯了路徑。
多個進程(線程)或者app是可以同時訪問同一個串口的,造成的結果就是串口的數據可能會丟失,體現為“丟包”。
任務目標
我的目標很簡單,甚至都不需要發送信息,只需要從外接設備讀取需要的信號變化即可,具體到項目,就理解為0和1就好了。
具體過程
我的板子系統是 4.4.4,我用 Android Studio 來做,首先還是來創建一個空項目。
添加NDK ,這個是給 Java 用的 C 庫:
自動下載安裝後,as會為你配置好一切,接下來在你工程的左上角把項目選為 “Project”:
然後在 應用/主要 上點擊鼠標右鍵,新建 JNI 目錄:
然後在目錄裡放入以下五個文件:
JNI ←點擊下載
這裡邊包含了橋接需要的文件,稍後會用到,接下來在 java 目錄下新建一個名為 android_serialport_api 的包,注意名稱一定要是這個。在這個包內新建一個 java 類,名字為 串行端口 ,內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
/* * Copyright 2009 Cedric Priscal * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android_serialport_api; 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; import android.util.Log; public class SerialPort { private static final String TAG = "SerialPort"; /* * Do not remove or rename the field mFd: it is used by native method close(); */ private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException { /* Check access permission */ if (!device.canRead() || !device.canWrite()) { try { /* Missing read/write permission, trying to chmod the file */ Process su; su = Runtime.getRuntime().exec("/system/bin/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 setters public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } // JNI private native static FileDescriptor open(String path, int baudrate, int flags); public native void close(); static { System.loadLibrary("serial_port"); } } |
添加之後即可給 Gradle 鏈接 C++ 了,這時候 as 會要你選構建系統,選擇 NDK-建立 ,然後根據提示去找剛才我們下載的五個文件中的 Android的.MK
鏈接之後,你就可以在 Build 菜單選擇 Make Project ,make 之後,檢查目錄看有沒有成功生成鏈接庫:
確認成功後,即可 clean 和 rebuild 整個項目了,哦對了,作為測試的話, MainActivity 可以這麼寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
package com.logcg.r0uter.myapplication; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android_serialport_api.SerialPort; public class MainActivity extends Activity { protected SerialPort mSerialPort; protected InputStream mInputStream; protected OutputStream mOutputStream; private ReadThread mReadThread; private class ReadThread extends Thread { @Override public void run() { super.run(); while(!isInterrupted()) { int size; Log.v("debug", "接收线程已经开启"); try { byte[] buffer = new byte[64]; if (mInputStream == null) return; size = mInputStream.read(buffer); if (size > 0) { onDataReceived(buffer, size); } } catch (IOException e) { e.printStackTrace(); return; } } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { mSerialPort = new SerialPort(new File("/dev/ttyS3"), 9600, 0);//这里串口地址和比特率记得改成你板子要求的值。 mInputStream = mSerialPort.getInputStream(); mOutputStream = mSerialPort.getOutputStream(); mReadThread = new ReadThread(); mReadThread.start(); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { Log.v("test", "启动失败"); e.printStackTrace(); } } protected void onDataReceived(final byte[] buffer, final int size) { runOnUiThread(new Runnable(){ public void run(){ String recinfo = new String(buffer, 0, size); Log.v("debug", "接收到串口信息======>" + recinfo.getBytes()); } }); } } |
值得注意的地方
這個項目看起來簡單,但實際上最坑的地方在於那個 C 庫寫的有問題,可偏偏在網上幾乎都是這一個文件……在 串行端口.C 的第 116 行,還有 128 行,你會看到我的版本里註釋掉了這兩個檢測,事實上這個問題坑了我三個小時!
在我拿到手的這個板子上,就是返回 -1 的!他就返回 -1 沒毛病!所以直到我懷疑人生……拼死一搏……才發現了人生的真蒂。
總之,如果你發現在實現過程中出現了奇怪的問題,那麼就把 這兩個if的語句塊反註釋就好了。
如果一次通過,那你得感謝我。 :)
參考閱讀
【Android應用開發】-(19)Android 串口編程原理和實現方式(附源碼)
本文由 落格博客 原創撰寫:落格博客 » Android 安卓開發板 讀取串口
轉載請保留出處和原文鏈接:https://www.logcg.com/archives/2831.html
終於搭好環境了,感謝!另外別人似乎有建立jnilibs也複製5個文件?
你好,我也是使用你用的這個jni進行串口讀寫,目前發送沒問題,跟串口工具可以聯調,但是接收很奇怪。請問你有沒有遇到設備接收數據的時候,只接收一部分數據,另一部分數據又被發送到對端設備的情況?比如,電腦給設備發asdfgh,通過mInputStream.read(緩衝)讀取,只讀到sdfgh,然後電腦端收到了a
我沒有遇到過,希望其他路過的大神能解答你的問題 :)
3q
3q,聖誕快樂?
最近在做大陸的電子班牌
恰好發現博主這篇文章
對我幫助甚大
在此感謝
:)