Android串口通訊SerialPort的使用詳情
1.什么是串口?
在不會使用串口通訊之前,暫且可以把它理解為“一個可通訊的口”;使用篇不深入探討理論及原理。能理解串口如何使用之后,可以查看淺談Android串口通訊SerialPort原理
2.添加依賴
1.)在 module 中的 build.gradle 中的 dependencies 中添加以下依賴:
dependencies { //串口 implementation 'com.github.licheedev:Android-SerialPort-API:2.0.0' }
2.)低版本的 gradle 在Project 中的 build.gradle 中的 allprojects 中添加以下 maven倉庫 (不添加任然無法加載SerialPort);
allprojects { repositories { maven { url "https://jitpack.io" }//maven倉庫 } }
高版本的 gradle 已經(jīng)廢棄了 allprojects 在 settings.gradle 中 repositories 添加以下maven倉庫(不添加任然無法加載SerialPort);
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() jcenter() // Warning: this repository is going to shut down soon maven { url "https://jitpack.io" }//maven倉庫 } }
3.編寫串口處理類
1.)串口處理類:SerialHandle ;簡單概括這個類,就是通過串口對象去獲取兩個流(輸入流、輸出流),通過者兩個流來監(jiān)聽數(shù)據(jù)或者寫入指令,硬件收到后執(zhí)行。同時注意配置參數(shù)(只要支持串口通訊的硬件,一般說明書上都會有寫)
package com.chj233.serialmode.serialUtil; import android.serialport.SerialPort; import android.util.Log; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * 串口實處理類 */ public class SerialHandle implements Runnable { private static final String TAG = "串口處理類"; private String path = "";//串口地址 private SerialPort mSerialPort;//串口對象 private InputStream mInputStream;//串口的輸入流對象 private BufferedInputStream mBuffInputStream;//用于監(jiān)聽硬件返回的信息 private OutputStream mOutputStream;//串口的輸出流對象 用于發(fā)送指令 private SerialInter serialInter;//串口回調(diào)接口 private ScheduledFuture readTask;//串口讀取任務(wù) /** * 添加串口回調(diào) * * @param serialInter */ public void addSerialInter(SerialInter serialInter) { this.serialInter = serialInter; } /** * 打開串口 * * @param devicePath 串口地址(根據(jù)平板的說明說填寫) * @param baudrate 波特率(根據(jù)對接的硬件填寫 - 硬件說明書上"通訊"中會有標(biāo)注) * @param isRead 是否持續(xù)監(jiān)聽串口返回的數(shù)據(jù) * @return 是否打開成功 */ public boolean open(String devicePath, int baudrate, boolean isRead) { return open(devicePath, baudrate, 7, 1, 2, isRead); } /** * 打開串口 * * @param devicePath 串口地址(根據(jù)平板的說明說填寫) * @param baudrate 波特率(根據(jù)對接的硬件填寫 - 硬件說明書上"通訊"中會有標(biāo)注) * @param dataBits 數(shù)據(jù)位(根據(jù)對接的硬件填寫 - 硬件說明書上"通訊"中會有標(biāo)注) * @param stopBits 停止位(根據(jù)對接的硬件填寫 - 硬件說明書上"通訊"中會有標(biāo)注) * @param parity 校驗位(根據(jù)對接的硬件填寫 - 硬件說明書上"通訊"中會有標(biāo)注) * @param isRead 是否持續(xù)監(jiān)聽串口返回的數(shù)據(jù) * @return 是否打開成功 */ public boolean open(String devicePath, int baudrate, int dataBits, int stopBits, int parity, boolean isRead) { boolean isSucc = false; try { if (mSerialPort != null) close(); File device = new File(devicePath); mSerialPort = SerialPort // 串口對象 .newBuilder(device, baudrate) // 串口地址地址,波特率 .dataBits(dataBits) // 數(shù)據(jù)位,默認8;可選值為5~8 .stopBits(stopBits) // 停止位,默認1;1:1位停止位;2:2位停止位 .parity(parity) // 校驗位;0:無校驗位(NONE,默認);1:奇校驗位(ODD);2:偶校驗位(EVEN) .build(); // 打開串口并返回 mInputStream = mSerialPort.getInputStream(); mBuffInputStream = new BufferedInputStream(mInputStream); mOutputStream = mSerialPort.getOutputStream(); isSucc = true; path = devicePath; if (isRead) readData();//開啟識別 } catch (Throwable tr) { close(); isSucc = false; } finally { return isSucc; } } // 讀取數(shù)據(jù) private void readData() { if (readTask != null) { readTask.cancel(true); try { Thread.sleep(160); } catch (InterruptedException e) { e.printStackTrace(); } //此處睡眠:當(dāng)取消任務(wù)時 線程池已經(jīng)執(zhí)行任務(wù),無法取消,所以等待線程池的任務(wù)執(zhí)行完畢 readTask = null; } readTask = SerialManage .getInstance() .getScheduledExecutor()//獲取線程池 .scheduleAtFixedRate(this, 0, 150, TimeUnit.MILLISECONDS);//執(zhí)行一個循環(huán)任務(wù) } @Override//每隔 150 毫秒會觸發(fā)一次run public void run() { if (Thread.currentThread().isInterrupted()) return; try { int available = mBuffInputStream.available(); if (available == 0) return; byte[] received = new byte[1024]; int size = mBuffInputStream.read(received);//讀取以下串口是否有新的數(shù)據(jù) if (size > 0 && serialInter != null) serialInter.readData(path, received, size); } catch (IOException e) { Log.e(TAG, "串口讀取數(shù)據(jù)異常:" + e.toString()); } } /** * 關(guān)閉串口 */ public void close(){ try{ if (mInputStream != null) mInputStream.close(); }catch (Exception e){ Log.e(TAG,"串口輸入流對象關(guān)閉異常:" +e.toString()); } try{ if (mOutputStream != null) mOutputStream.close(); }catch (Exception e){ Log.e(TAG,"串口輸出流對象關(guān)閉異常:" +e.toString()); } try{ if (mSerialPort != null) mSerialPort.close(); mSerialPort = null; }catch (Exception e){ Log.e(TAG,"串口對象關(guān)閉異常:" +e.toString()); } } /** * 向串口發(fā)送指令 */ public void send(final String msg) { byte[] bytes = hexStr2bytes(msg);//字符轉(zhuǎn)成byte數(shù)組 try { mOutputStream.write(bytes);//通過輸出流寫入數(shù)據(jù) } catch (Exception e) { e.printStackTrace(); } } /** * 把十六進制表示的字節(jié)數(shù)組字符串,轉(zhuǎn)換成十六進制字節(jié)數(shù)組 * * @param * @return byte[] */ private byte[] hexStr2bytes(String hex) { int len = (hex.length() / 2); byte[] result = new byte[len]; char[] achar = hex.toUpperCase().toCharArray(); for (int i = 0; i < len; i++) { int pos = i * 2; result[i] = (byte) (hexChar2byte(achar[pos]) << 4 | hexChar2byte(achar[pos + 1])); } return result; } /** * 把16進制字符[0123456789abcde](含大小寫)轉(zhuǎn)成字節(jié) * @param c * @return */ private static int hexChar2byte(char c) { switch (c) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; default: return -1; } }
2.)串口回調(diào)SerialInter;簡單概括一下這個類,就是將SerialHandle類中產(chǎn)生的結(jié)果,返回給上一層的業(yè)務(wù)代碼,解偶合
package com.chj233.serialmode.serialUtil; /** * 串口回調(diào) */ public interface SerialInter { /** * 連接結(jié)果回調(diào) * @param path 串口地址(當(dāng)有多個串口需要統(tǒng)一處理時,可以用地址來區(qū)分) * @param isSucc 連接是否成功 */ void connectMsg(String path,boolean isSucc); /** * 讀取到的數(shù)據(jù)回調(diào) * @param path 串口地址(當(dāng)有多個串口需要統(tǒng)一處理時,可以用地址來區(qū)分) * @param bytes 讀取到的數(shù)據(jù) * @param size 數(shù)據(jù)長度 */ void readData(String path,byte[] bytes,int size); }
3.)串口統(tǒng)一管理SerialManage;簡單概括一下這個類,用于管理串口的連接以及發(fā)送等功能,尤其是發(fā)送指令,極短時間內(nèi)發(fā)送多個指令(例如:1毫秒內(nèi)發(fā)送10個指令),多個指令之間會相互干擾。可能執(zhí)行了第一個指令,可能一個都沒執(zhí)行。這個類不是必須的,如果有更好的方法可以自己定義。
package com.chj233.serialmode.serialUtil; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * 串口管理類 */ public class SerialManage { private static SerialManage instance; private ScheduledExecutorService scheduledExecutor;//線程池 同一管理保證只有一個 private SerialHandle serialHandle;//串口連接 發(fā)送 讀取處理對象 private Queue<String> queueMsg = new ConcurrentLinkedQueue<String>();//線程安全到隊列 private ScheduledFuture sendStrTask;//循環(huán)發(fā)送任務(wù) private boolean isConnect = false;//串口是否連接 private SerialManage() { scheduledExecutor = Executors.newScheduledThreadPool(8);//初始化8個線程 } public static SerialManage getInstance() { if (instance == null) { synchronized (SerialManage.class) { if (instance == null) { instance = new SerialManage(); } } } return instance; } /** * 獲取線程池 * * @return */ public ScheduledExecutorService getScheduledExecutor() { return scheduledExecutor; } /** * 串口初始化 * * @param serialInter */ public void init(SerialInter serialInter) { if (serialHandle == null) { serialHandle = new SerialHandle(); startSendTask(); } serialHandle.addSerialInter(serialInter); } /** * 打開串口 */ public void open() { isConnect = serialHandle.open("/dev/ttyS1", 9600, true);//設(shè)置地址,波特率,開啟讀取串口數(shù)據(jù) } /** * 發(fā)送指令 * * @param msg */ public void send(String msg) { /* 此處沒有直接使用 serialHandle.send(msg); 方法去發(fā)送指令 因為 某些硬件在極短時間內(nèi)只能響應(yīng)一個指令,232通訊一次發(fā)送多個指令會有物理干擾, 讓硬件接收到指令不準(zhǔn)確;所以 此處將指令添加到隊列中,排隊執(zhí)行,確保每個指令一定執(zhí)行. 若不相信可以試試用serialHandle.send(msg)方法循環(huán)發(fā)送10個不同的指令,看看10個指令 的執(zhí)行結(jié)果。 */ queueMsg.offer(msg);//向隊列添加指令 } /** * 關(guān)閉串口 */ public void colse() { serialHandle.close();//關(guān)閉串口 } //啟動發(fā)送發(fā)送任務(wù) private void startSendTask() { cancelSendTask();//先檢查是否已經(jīng)啟動了任務(wù) ? 若有則取消 //每隔100毫秒檢查一次 隊列中是否有新的指令需要執(zhí)行 sendStrTask = scheduledExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { if (!isConnect) return;//串口未連接 退出 if (serialHandle == null) return;//串口未初始化 退出 String msg = queueMsg.poll();//取出指令 if (msg == null || "".equals(msg)) return;//無效指令 退出 serialHandle.send(msg);//發(fā)送指令 } }, 0, 100, TimeUnit.MILLISECONDS); } //取消發(fā)送任務(wù) private void cancelSendTask() { if (sendStrTask == null) return; sendStrTask.cancel(true); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } sendStrTask = null; } }
4.使用串口
package com.chj233.serialmode; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import com.chj233.serialmode.serialUtil.SerialInter; import com.chj233.serialmode.serialUtil.SerialManage; public class MainActivity extends AppCompatActivity implements SerialInter { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SerialManage.getInstance().init(this);//串口初始化 SerialManage.getInstance().open();//打開串口 findViewById(R.id.send_but).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SerialManage.getInstance().send("Z");//發(fā)送指令 Z } }); } @Override public void connectMsg(String path, boolean isSucc) { String msg = isSucc ? "成功" : "失敗"; Log.e("串口連接回調(diào)", "串口 "+ path + " -連接" + msg); } @Override//若在串口開啟的方法中 傳入false 此處不會返回數(shù)據(jù) public void readData(String path, byte[] bytes, int size) { // Log.e("串口數(shù)據(jù)回調(diào)","串口 "+ path + " -獲取數(shù)據(jù)" + bytes); } }
5.總結(jié)
串口通訊對于Android開發(fā)者來說,僅需關(guān)注如何連接、操作(發(fā)送指令)、讀取數(shù)據(jù);無論是232、485還是422,對于開發(fā)者來說連接、操作、讀取代碼都是一樣的
到此這篇關(guān)于Android串口通訊SerialPort的使用詳情的文章就介紹到這了,更多相關(guān)Android SerialPort內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android 監(jiān)聽SD卡文件變化的實現(xiàn)代碼
這篇文章主要介紹了android 監(jiān)聽SD卡文件變化的實現(xiàn)代碼,需要的朋友可以參考下2017-11-11使用Kotlin開發(fā)Android應(yīng)用的初體驗
本篇文章主要介紹了使用Kotlin開發(fā)Android應(yīng)用的初體驗,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Android中一個應(yīng)用實現(xiàn)多個圖標(biāo)的幾種方式
這篇文章主要給大家介紹了在Android中一個應(yīng)用如何實現(xiàn)多個圖標(biāo)的幾種方式,其中包括了多Activity + intent-filter方式、activity-alias方式以及網(wǎng)頁標(biāo)簽-添加快捷方式,分別給出了詳細的示例代碼,需要的朋友可以參考借鑒。2017-05-05Android開發(fā)之完成登陸界面的數(shù)據(jù)保存回顯操作實例
這篇文章主要介紹了Android開發(fā)之完成登陸界面的數(shù)據(jù)保存回顯操作實現(xiàn)方法,結(jié)合完整實例形式較為詳細的分析了Android針對登錄數(shù)據(jù)的保存及回顯操作技巧,需要的朋友可以參考下2015-12-12Android Adapter里面嵌套ListView實例詳解
這篇文章主要介紹了Android Adapter里面嵌套ListView實例詳解的相關(guān)資料,這里提供實例代碼并說明如何實現(xiàn)該功能,需要的朋友可以參考下2017-07-07