前言
做了一些Android驅動板的串口通信,對控制卡,繼電器開關,麥克風,PWM風機等進行操作,進行一下記錄分享。其中,包含了Android自身的串口操作及
Usb
轉串口的操作。本篇主要介紹串口基礎內容和基於谷歌官方android-serialport-api庫 對Android設備進行的串口操作,下一篇中將給出基於Usb轉串口驅動庫usb-serial-for-android 的相關內容及操作。
串口通信
串行接口是一種可以將接收來自CPU的並行數據字符轉換為連續的串行數據流發送出去,同時可將接收的串行數據流轉換為並行的數據字符供給CPU的器件。
一般完成這種功能的電路,我們稱為串行接口電路。
串口按位(bit)發送和接收字節的通信方式即串口通信。盡管比按 字節 (byte)的 並行通信 慢,但是串口可以在使用一根線發送數據的同時用另一根線接收數據。它很簡單並且能夠實現遠距離通信(RS232是要用在近距離傳輸上最大距離為30M,RS485用在長距離傳輸最大距離1200M)。通信使用3根線完成,分別是地線、發送、接收。
串口參數
串口通信最重要的參數是波特率、數據位、停止位和奇偶校驗。對於兩個進行通行的端口,這些參數必須匹配。
波特率
這是一個衡量符號傳輸速率的參數。指的是信號被調制以后在單位時間內的變化,即單位時間內載波參數變化的次數,如每秒鍾傳送240個字符,而每個字符格式包含10位(1個起始位,1個停止位,8個數據位),這時的波特率為240Bd,比特率為10位*240個/秒=2400bps。一般調制速率大於波特率,比如曼徹斯特編碼)。通常電話線的波特率為14400,28800和36600。波特率可以遠遠大於這些值,但是波特率和距離成反比。高波特率常常用於放置的很近的儀器間的通信,典型的例子就是GPIB設備的通信。
數據位
這是衡量通信中實際數據位的參數。當計算機發送一個信息包,實際的數據往往不會是8位的,標准的值是6、7和8位。如何設置取決於你想傳送的信息。比如,標准的ASCII碼是0~127(7位)。擴展的ASCII碼是0~255(8位)。如果數據使用簡單的文本(標准 ASCII碼),那么每個數據包使用7位數據。每個包是指一個字節,包括開始/停止位,數據位和奇偶校驗位。由於實際數據位取決於通信協議的選取,術語“包”指任何通信的情況。
停止位
用於表示單個包的最后一位。典型的值為1,1.5和2位。由於數據是在傳輸線上定時的,並且每一個設備有其自己的時鍾,很可能在通信中兩台設備間出現了小小的不同步。因此停止位不僅僅是表示傳輸的結束,並且提供計算機校正時鍾同步的機會。適用於停止位的位數越多,不同時鍾同步的容忍程度越大,但是數據傳輸率同時也越慢。
奇偶校驗位
在串口通信中一種簡單的檢錯方式。有四種檢錯方式:偶、奇、高和低。當然沒有校驗位也是可以的。對於偶和奇校驗的情況,串口會設置校驗位(數據位后面的一位),用一個值確保傳輸的數據有偶個或者奇個邏輯高位。例如,如果數據是011,那么對於偶校驗,校驗位為0,保證邏輯高的位數是偶數個。如果是奇校驗,校驗位為1,這樣就有3個邏輯高位。高位和低位不真正的檢查數據,簡單置位邏輯高或者邏輯低校驗。這樣使得接收設備能夠知道一個位的狀態,有機會判斷是否有噪聲干擾了通信或者是否傳輸和接收數據是否不同步。
串口開發
在Android開發中,對串口數據的讀取和寫入,實際上是是通過I/O流讀取,寫入文件數據。串口使用完畢需要關閉(文件關閉)。串口關閉,即是文件流關閉。
開發流程
- 獲取設備串口地址;
- 配置(波特率,校驗位等),建立指定串口通信;
- 串口寫入及接收返回的數據;
- 結束通信,串口關閉。
使用過程
基於谷歌官方android-serialport-api 編譯修改,主要包含SerialPortFinder
和SerialPort
,進行串口地址的獲取和串口內容的開啟、寫入、讀取及關閉。
- 通過
SerialPortFinder
獲取所有串口地址,進行串口的選取(使用中通常來說如果插入Usb
串口設備如:USB轉485/442
、USB轉TTL串口線
,會顯示為/dev/ttyUSB0
之類的串口地址。);
public class SerialPortFinder {
public class Driver {
public Driver(String name, String root) {
mDriverName = name;
mDeviceRoot = root;
}
private String mDriverName;
private String mDeviceRoot;
Vector<File> mDevices = null;
Vector<File> getDevices() {
if (mDevices == null) {
mDevices = new Vector<>();
File dev = new File("/dev");
File[] files = dev.listFiles();
if (files == null) {
return mDevices;
}
int i;
for (i = 0; i < files.length; i++) {
if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
Log.d(TAG, "Found new device: " + files[i]);
mDevices.add(files[i]);
}
}
}
return mDevices;
}
public String getName() {
return mDriverName;
}
}
private static final String TAG = "SerialPort";
private Vector<Driver> mDrivers = null;
Vector<Driver> getDrivers() throws IOException {
if (mDrivers == null) {
mDrivers = new Vector<Driver>();
LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers"));
String l;
while ((l = r.readLine()) != null) {
// Issue 3:
// Since driver name may contain spaces, we do not extract driver name with split()
String drivername = l.substring(0, 0x15).trim();
String[] w = l.split(" +");
if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {
Log.d(TAG, "Found new driver " + drivername + " on " + w[w.length - 4]);
mDrivers.add(new Driver(drivername, w[w.length - 4]));
}
}
r.close();
}
return mDrivers;
}
public String[] getAllDevices() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while (itdev.hasNext()) {
String device = itdev.next().getName();
String value = String.format("%s (%s)", device, driver.getName());
devices.add(value);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
public String[] getAllDevicesPath() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while (itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while (itdev.hasNext()) {
String device = itdev.next().getAbsolutePath();
devices.add(device);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
}
-
核心庫
libserial_port.so
加載,通過使用谷歌官方android-serialport-api庫 libs中已經編譯生成的libserial_port.so
文件或對jni
文件夾下c文件進行重新編譯生成所需,不要忘記在.gradle
文件中配置jni
路徑。sourceSets { main { jniLibs.srcDirs = ['libs'] } }
-
選取串口地址后,結合比特率及標志位,即可通過已加載核心庫
libserial_port.so
進行串口打開操作;public class SerialPort { private FileDescriptor mFd; private FileInputStream mFileInputStream; private FileOutputStream mFileOutputStream; public SerialPort(File device, int baudRate, int flags) throws SecurityException, IOException { mFd = open(device.getAbsolutePath(), baudRate, flags); if (mFd == null) { throw new IOException(); } mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); } public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } private native static FileDescriptor open(String path, int baudrate, int flags); public native void close(); static { System.loadLibrary("serial_port"); } }
-
編寫的控制類,統一進行串口地址獲取,打開,讀取,寫入及關閉操作:
public class SerialController {
private ExecutorService mThreadPoolExecutor = Executors.newCachedThreadPool();
private InputStream inputStream;
private OutputStream outputStream;
private boolean isOpened = false;
private OnSerialListener mOnSerialListener;
/**
* 獲取所有串口路徑
*
* @return 串口路徑集合
*/
public List<String> getAllSerialPortPath() {
SerialPortFinder mSerialPortFinder = new SerialPortFinder();
String[] deviceArr = mSerialPortFinder.getAllDevicesPath();
return new ArrayList<>(Arrays.asList(deviceArr));
}
/**
* 打開串口
*
* @param serialPath 串口地址
* @param baudRate 波特率
* @param flags 標志位
*/
public void openSerialPort(String serialPath, int baudRate, int flags) {
try {
SerialPort serialPort = new SerialPort(new File(serialPath), baudRate, flags);
inputStream = serialPort.getInputStream();
outputStream = serialPort.getOutputStream();
isOpened = true;
if (mOnSerialListener != null) {
mOnSerialListener.onSerialOpenSuccess();
}
mThreadPoolExecutor.execute(new ReceiveDataThread());
} catch (Exception e) {
if (mOnSerialListener != null) {
mOnSerialListener.onSerialOpenException(e);
}
}
}
/**
* 關閉串口
*/
public void closeSerialPort() {
try {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
isOpened = false;
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 發送串口數據
*
* @param bytes 發送數據
*/
public void sendSerialPort(byte[] bytes) {
if (!isOpened) {
return;
}
try {
if (outputStream != null) {
outputStream.write(bytes);
outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 返回串口是否開啟
*
* @return 是否開啟
*/
public boolean isOpened() {
return isOpened;
}
/**
* 串口返回數據內容讀取
*/
private class ReceiveDataThread extends Thread {
@Override
public void run() {
super.run();
while (isOpened) {
if (inputStream != null) {
byte[] readData = new byte[1024];
try {
int size = inputStream.read(readData);
if (size > 0) {
if (mOnSerialListener != null) {
mOnSerialListener.onReceivedData(readData, size);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
/**
* 設置串口監聽
*
* @param onSerialListener 串口監聽
*/
public void setOnSerialListener(OnSerialListener onSerialListener) {
this.mOnSerialListener = onSerialListener;
}
/**
* 串口監聽
*/
public interface OnSerialListener {
/**
* 串口數據返回
*/
void onReceivedData(byte[] data, int size);
/**
* 串口打開成功
*/
void onSerialOpenSuccess();
/**
* 串口打開異常
*/
void onSerialOpenException(Exception e);
}
}
以上就是本篇主要介紹的串口通信基礎知識及Android串口內容調用。除了Google官方驅動庫外,我們實際操作中可能還需要用到Usb
轉串口內容的操作,將在下篇給出具體內容。
訪問Github
項目查看具體代碼實現:
https://github.com/MickJson/AndroidUSBSerialPort
參考資料:
百度百科串口通信:https://baike.baidu.com/item/串口通信
谷歌串口通信庫:https://github.com/cepr/android-serialport-api
歡迎點贊/評論,你們的贊同和鼓勵是我寫作的最大動力!