1. 程式人生 > 實用技巧 >Android串列埠通訊(一)

Android串列埠通訊(一)

前言

做了一些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流讀取,寫入檔案資料。串列埠使用完畢需要關閉(檔案關閉)。串列埠關閉,即是檔案流關閉。

開發流程

  1. 獲取裝置串列埠地址;
  2. 配置(波特率,校驗位等),建立指定串列埠通訊;
  3. 串列埠寫入及接收返回的資料;
  4. 結束通訊,串列埠關閉。

使用過程

基於谷歌官方android-serialport-api 編譯修改,主要包含SerialPortFinderSerialPort,進行串列埠地址的獲取和串列埠內容的開啟、寫入、讀取及關閉。

  1. 通過SerialPortFinder獲取所有串列埠地址,進行串列埠的選取(使用中通常來說如果插入Usb串列埠裝置如:USB轉485/442USB轉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()]);
    }
}
  1. 核心庫libserial_port.so載入,通過使用谷歌官方android-serialport-api庫 libs中已經編譯生成的libserial_port.so檔案或對jni資料夾下c檔案進行重新編譯生成所需,不要忘記在.gradle檔案中配置jni路徑。

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
    
  2. 選取串列埠地址後,結合位元率及標誌位,即可通過已載入核心庫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");
        }
    }
    
  3. 編寫的控制類,統一進行串列埠地址獲取,開啟,讀取,寫入及關閉操作:

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

歡迎點贊/評論,你們的贊同和鼓勵是我寫作的最大動力!