Android串口通訊SerialPort的使用詳情
1.什么是串口?
在不會(huì)使用串口通訊之前,暫且可以把它理解為“一個(gè)可通訊的口”;使用篇不深入探討理論及原理。能理解串口如何使用之后,可以查看淺談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倉(cāng)庫(kù) (不添加任然無(wú)法加載SerialPort);
allprojects {
repositories {
maven { url "https://jitpack.io" }//maven倉(cāng)庫(kù)
}
}高版本的 gradle 已經(jīng)廢棄了 allprojects 在 settings.gradle 中 repositories 添加以下maven倉(cāng)庫(kù)(不添加任然無(wú)法加載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倉(cāng)庫(kù)
}
}3.編寫(xiě)串口處理類(lèi)
1.)串口處理類(lèi):SerialHandle ;簡(jiǎn)單概括這個(gè)類(lèi),就是通過(guò)串口對(duì)象去獲取兩個(gè)流(輸入流、輸出流),通過(guò)者兩個(gè)流來(lái)監(jiān)聽(tīng)數(shù)據(jù)或者寫(xiě)入指令,硬件收到后執(zhí)行。同時(shí)注意配置參數(shù)(只要支持串口通訊的硬件,一般說(shuō)明書(shū)上都會(huì)有寫(xiě))
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;
/**
* 串口實(shí)處理類(lèi)
*/
public class SerialHandle implements Runnable {
private static final String TAG = "串口處理類(lèi)";
private String path = "";//串口地址
private SerialPort mSerialPort;//串口對(duì)象
private InputStream mInputStream;//串口的輸入流對(duì)象
private BufferedInputStream mBuffInputStream;//用于監(jiān)聽(tīng)硬件返回的信息
private OutputStream mOutputStream;//串口的輸出流對(duì)象 用于發(fā)送指令
private SerialInter serialInter;//串口回調(diào)接口
private ScheduledFuture readTask;//串口讀取任務(wù)
/**
* 添加串口回調(diào)
*
* @param serialInter
*/
public void addSerialInter(SerialInter serialInter) {
this.serialInter = serialInter;
}
/**
* 打開(kāi)串口
*
* @param devicePath 串口地址(根據(jù)平板的說(shuō)明說(shuō)填寫(xiě))
* @param baudrate 波特率(根據(jù)對(duì)接的硬件填寫(xiě) - 硬件說(shuō)明書(shū)上"通訊"中會(huì)有標(biāo)注)
* @param isRead 是否持續(xù)監(jiān)聽(tīng)串口返回的數(shù)據(jù)
* @return 是否打開(kāi)成功
*/
public boolean open(String devicePath, int baudrate, boolean isRead) {
return open(devicePath, baudrate, 7, 1, 2, isRead);
}
/**
* 打開(kāi)串口
*
* @param devicePath 串口地址(根據(jù)平板的說(shuō)明說(shuō)填寫(xiě))
* @param baudrate 波特率(根據(jù)對(duì)接的硬件填寫(xiě) - 硬件說(shuō)明書(shū)上"通訊"中會(huì)有標(biāo)注)
* @param dataBits 數(shù)據(jù)位(根據(jù)對(duì)接的硬件填寫(xiě) - 硬件說(shuō)明書(shū)上"通訊"中會(huì)有標(biāo)注)
* @param stopBits 停止位(根據(jù)對(duì)接的硬件填寫(xiě) - 硬件說(shuō)明書(shū)上"通訊"中會(huì)有標(biāo)注)
* @param parity 校驗(yàn)位(根據(jù)對(duì)接的硬件填寫(xiě) - 硬件說(shuō)明書(shū)上"通訊"中會(huì)有標(biāo)注)
* @param isRead 是否持續(xù)監(jiān)聽(tīng)串口返回的數(shù)據(jù)
* @return 是否打開(kāi)成功
*/
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 // 串口對(duì)象
.newBuilder(device, baudrate) // 串口地址地址,波特率
.dataBits(dataBits) // 數(shù)據(jù)位,默認(rèn)8;可選值為5~8
.stopBits(stopBits) // 停止位,默認(rèn)1;1:1位停止位;2:2位停止位
.parity(parity) // 校驗(yàn)位;0:無(wú)校驗(yàn)位(NONE,默認(rèn));1:奇校驗(yàn)位(ODD);2:偶校驗(yàn)位(EVEN)
.build(); // 打開(kāi)串口并返回
mInputStream = mSerialPort.getInputStream();
mBuffInputStream = new BufferedInputStream(mInputStream);
mOutputStream = mSerialPort.getOutputStream();
isSucc = true;
path = devicePath;
if (isRead) readData();//開(kāi)啟識(shí)別
} 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ù)時(shí) 線程池已經(jīng)執(zhí)行任務(wù),無(wú)法取消,所以等待線程池的任務(wù)執(zhí)行完畢
readTask = null;
}
readTask = SerialManage
.getInstance()
.getScheduledExecutor()//獲取線程池
.scheduleAtFixedRate(this, 0, 150, TimeUnit.MILLISECONDS);//執(zhí)行一個(gè)循環(huán)任務(wù)
}
@Override//每隔 150 毫秒會(huì)觸發(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,"串口輸入流對(duì)象關(guān)閉異常:" +e.toString());
}
try{
if (mOutputStream != null) mOutputStream.close();
}catch (Exception e){
Log.e(TAG,"串口輸出流對(duì)象關(guān)閉異常:" +e.toString());
}
try{
if (mSerialPort != null) mSerialPort.close();
mSerialPort = null;
}catch (Exception e){
Log.e(TAG,"串口對(duì)象關(guān)閉異常:" +e.toString());
}
}
/**
* 向串口發(fā)送指令
*/
public void send(final String msg) {
byte[] bytes = hexStr2bytes(msg);//字符轉(zhuǎn)成byte數(shù)組
try {
mOutputStream.write(bytes);//通過(guò)輸出流寫(xiě)入數(shù)據(jù)
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 把十六進(jìn)制表示的字節(jié)數(shù)組字符串,轉(zhuǎn)換成十六進(jì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進(jìn)制字符[0123456789abcde](含大小寫(xiě))轉(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;簡(jiǎn)單概括一下這個(gè)類(lèi),就是將SerialHandle類(lèi)中產(chǎn)生的結(jié)果,返回給上一層的業(yè)務(wù)代碼,解偶合
package com.chj233.serialmode.serialUtil;
/**
* 串口回調(diào)
*/
public interface SerialInter {
/**
* 連接結(jié)果回調(diào)
* @param path 串口地址(當(dāng)有多個(gè)串口需要統(tǒng)一處理時(shí),可以用地址來(lái)區(qū)分)
* @param isSucc 連接是否成功
*/
void connectMsg(String path,boolean isSucc);
/**
* 讀取到的數(shù)據(jù)回調(diào)
* @param path 串口地址(當(dāng)有多個(gè)串口需要統(tǒng)一處理時(shí),可以用地址來(lái)區(qū)分)
* @param bytes 讀取到的數(shù)據(jù)
* @param size 數(shù)據(jù)長(zhǎng)度
*/
void readData(String path,byte[] bytes,int size);
}3.)串口統(tǒng)一管理SerialManage;簡(jiǎn)單概括一下這個(gè)類(lèi),用于管理串口的連接以及發(fā)送等功能,尤其是發(fā)送指令,極短時(shí)間內(nèi)發(fā)送多個(gè)指令(例如:1毫秒內(nèi)發(fā)送10個(gè)指令),多個(gè)指令之間會(huì)相互干擾??赡軋?zhí)行了第一個(gè)指令,可能一個(gè)都沒(méi)執(zhí)行。這個(gè)類(lèi)不是必須的,如果有更好的方法可以自己定義。
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;
/**
* 串口管理類(lèi)
*/
public class SerialManage {
private static SerialManage instance;
private ScheduledExecutorService scheduledExecutor;//線程池 同一管理保證只有一個(gè)
private SerialHandle serialHandle;//串口連接 發(fā)送 讀取處理對(duì)象
private Queue<String> queueMsg = new ConcurrentLinkedQueue<String>();//線程安全到隊(duì)列
private ScheduledFuture sendStrTask;//循環(huán)發(fā)送任務(wù)
private boolean isConnect = false;//串口是否連接
private SerialManage() {
scheduledExecutor = Executors.newScheduledThreadPool(8);//初始化8個(gè)線程
}
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);
}
/**
* 打開(kāi)串口
*/
public void open() {
isConnect = serialHandle.open("/dev/ttyS1", 9600, true);//設(shè)置地址,波特率,開(kāi)啟讀取串口數(shù)據(jù)
}
/**
* 發(fā)送指令
*
* @param msg
*/
public void send(String msg) {
/*
此處沒(méi)有直接使用 serialHandle.send(msg); 方法去發(fā)送指令
因?yàn)?某些硬件在極短時(shí)間內(nèi)只能響應(yīng)一個(gè)指令,232通訊一次發(fā)送多個(gè)指令會(huì)有物理干擾,
讓硬件接收到指令不準(zhǔn)確;所以 此處將指令添加到隊(duì)列中,排隊(duì)執(zhí)行,確保每個(gè)指令一定執(zhí)行.
若不相信可以試試用serialHandle.send(msg)方法循環(huán)發(fā)送10個(gè)不同的指令,看看10個(gè)指令
的執(zhí)行結(jié)果。
*/
queueMsg.offer(msg);//向隊(duì)列添加指令
}
/**
* 關(guān)閉串口
*/
public void colse() {
serialHandle.close();//關(guān)閉串口
}
//啟動(dòng)發(fā)送發(fā)送任務(wù)
private void startSendTask() {
cancelSendTask();//先檢查是否已經(jīng)啟動(dòng)了任務(wù) ? 若有則取消
//每隔100毫秒檢查一次 隊(duì)列中是否有新的指令需要執(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;//無(wú)效指令 退出
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();//打開(kāi)串口
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//若在串口開(kāi)啟的方法中 傳入false 此處不會(huì)返回?cái)?shù)據(jù)
public void readData(String path, byte[] bytes, int size) {
// Log.e("串口數(shù)據(jù)回調(diào)","串口 "+ path + " -獲取數(shù)據(jù)" + bytes);
}
}5.總結(jié)
串口通訊對(duì)于Android開(kāi)發(fā)者來(lái)說(shuō),僅需關(guān)注如何連接、操作(發(fā)送指令)、讀取數(shù)據(jù);無(wú)論是232、485還是422,對(duì)于開(kāi)發(fā)者來(lái)說(shuō)連接、操作、讀取代碼都是一樣的
到此這篇關(guān)于Android串口通訊SerialPort的使用詳情的文章就介紹到這了,更多相關(guān)Android SerialPort內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 淺談Android串口通訊SerialPort原理
- Android串口通信apk源碼詳解(附完整源碼)
- Android串口通信之串口讀寫(xiě)實(shí)例
- Android串口開(kāi)發(fā)之使用JNI實(shí)現(xiàn)ANDROID和串口通信詳解
- android串口開(kāi)發(fā)入門(mén)之搭建ndk開(kāi)發(fā)環(huán)境及第一個(gè)jni調(diào)用程序
- Android串口通信封裝之OkUSB的示例代碼
- 詳解Android USB轉(zhuǎn)串口通信開(kāi)發(fā)基本流程
- Android USB轉(zhuǎn)串口通信開(kāi)發(fā)實(shí)例詳解
相關(guān)文章
Android 一個(gè)日歷控件的實(shí)現(xiàn)代碼
本篇文章主要介紹了Android 一個(gè)日歷控件的實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
Intent傳遞對(duì)象之Serializable和Parcelable的區(qū)別
Intent在不同的組件中傳遞對(duì)象數(shù)據(jù)的應(yīng)用非常普遍,大家都知道在intent傳遞對(duì)象的方法有兩種:1、實(shí)現(xiàn)Serializable接口、2、實(shí)現(xiàn)Parcelable接口,接下來(lái)通過(guò)本文給大家介紹Intent傳遞對(duì)象之Serializable和Parcelable的區(qū)別,感興趣的朋友一起學(xué)習(xí)吧2016-01-01
Android 代碼寫(xiě)控件代替XML簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 代碼寫(xiě)控件代替XML簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05
Android Studio 中aidl的自定義類(lèi)的使用詳解
這篇文章主要介紹了Android Studio 中aidl的自定義類(lèi)的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Android為textView設(shè)置setText的時(shí)候報(bào)錯(cuò)的講解方案
今天小編就為大家分享一篇關(guān)于Android為textView設(shè)置setText的時(shí)候報(bào)錯(cuò)的講解方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
Android ListView滑動(dòng)刪除操作(SwipeListView)
這篇文章主要為大家詳細(xì)介紹了Android ListView滑動(dòng)刪除操作,主要是學(xué)習(xí)SwipeListView開(kāi)源框架。感興趣的小伙伴們可以參考一下2016-08-08
Android實(shí)現(xiàn)實(shí)時(shí)通信示例
本篇文章主要介紹了Android實(shí)時(shí)通信示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03
Android中RecyclerView實(shí)現(xiàn)滑動(dòng)刪除與拖拽功能
這篇文章主要使用了RecyclerView的ItemTouchHelper類(lèi)實(shí)現(xiàn)了Item的拖動(dòng)和刪除功能,ItemTouchHelper是v7包下的一個(gè)類(lèi),下面來(lái)看看詳細(xì)的介紹吧,需要的朋友可以參考學(xué)習(xí)。2017-02-02
淺談RecyclerView(完美替代ListView,GridView)
RecyclerView絕對(duì)是一款功能強(qiáng)大的控件,涵蓋了ListView,GridView,瀑布流等數(shù)據(jù)表現(xiàn)的形式。本文對(duì)其進(jìn)行系統(tǒng)介紹,有需要的朋友可以看下2016-12-12

