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ù)位,默認(rèn)8;可選值為5~8
.stopBits(stopBits) // 停止位,默認(rèn)1;1:1位停止位;2:2位停止位
.parity(parity) // 校驗位;0:無校驗位(NONE,默認(rèn));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();
}
}
/**
* 把十六進(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](含大小寫)轉(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.多串口的使用
使用思想:一個單例對象控制一個串口,多串口時,寫多個“SerialManage”就可以了。這里僅僅做舉例不去考慮代碼是否優(yōu)雅,可以自行優(yōu)化這段代碼。(此案例中的SerialManage1、SerialManage2、SerialManage3、SerialManage4需要自己去復(fù)制,參照上面的SerialManage)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化串口1
SerialManage1.getInstance().init(new SerialInter(){
@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);
}
});
//開啟串口1
SerialManage1.getInstance().open();
//初始化串口2
SerialManage2.getInstance().init(new SerialInter(){
@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);
}
});
//打開串口2
SerialManage2.getInstance().open();
//初始化串口3
SerialManage3.getInstance().init(new SerialInter(){
@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);
}
});
//打開串口3
SerialManage3.getInstance().open();
//初始化串口4
SerialManage4.getInstance().init(new SerialInter(){
@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);
}
});
//打開串口4
SerialManage4.getInstance().open();
findViewById(R.id.send_but1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SerialManage1.getInstance().send("Z");//給串口1發(fā)送指令 Z
}
});
findViewById(R.id.send_but2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SerialManage2.getInstance().send("Z");//給串口2發(fā)送指令 Z
}
});
findViewById(R.id.send_but3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SerialManage3.getInstance().send("Z");//給串口3發(fā)送指令 Z
}
});
findViewById(R.id.send_but4).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SerialManage4.getInstance().send("Z");//給串口4發(fā)送指令 Z
}
});
}
}
?文章來源:http://www.zghlxwxcb.cn/news/detail-798925.html
6.總結(jié)
串口通訊對于Android開發(fā)者來說,僅需關(guān)注如何連接、操作(發(fā)送指令)、讀取數(shù)據(jù);無論是232、485還是422,對于開發(fā)者來說連接、操作、讀取代碼都是一樣的文章來源地址http://www.zghlxwxcb.cn/news/detail-798925.html
到了這里,關(guān)于Android串口通訊SerialPort(使用篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!