上半年最好的Android串口開發(fā)入門指南

特別說明

當前博客平臺賬號已廢棄,如果有使用細節(jié)問題請前往我新博客平臺進行討論交流。

個人博客平臺 HuRuWo的技術(shù)小站

文章首發(fā)于個人博客HuRuWo的技術(shù)小站,如果本文非vip用戶無法完全瀏覽或者圖片無法打開,可前往個人博客文章地址查看文章并留言討論。

個人博客文章地址上半年最好的Android串口開發(fā)入門指南)

更多技術(shù)文章訪問本人博客HuRuWo的技術(shù)小站,包括 Electron從零開發(fā) Android 逆向 app 微信數(shù)據(jù)抓取 抖音數(shù)據(jù)抓取 閑魚數(shù)據(jù)抓取 小紅書數(shù)據(jù)抓取 其他軟件爬蟲 等技術(shù)文章

一些預(yù)備知識

物聯(lián)網(wǎng)開發(fā)開發(fā)是時下熱門的行業(yè)。Android系統(tǒng)自然也能進行物聯(lián)網(wǎng)開發(fā)。除開Android本身自帶的模塊還有一類通過外部鏈接的設(shè)備需要通過串口來進行通信。本人在做完兩個相關(guān)的抓娃娃和寄存柜項目之后覺得需要總結(jié)一點東西給大家。

關(guān)于串口

串口通信指串口按位(bit)發(fā)送和接收字節(jié)。盡管比按字節(jié)(byte)的并行通信慢,但是串口可以在使用一根線發(fā)送數(shù)據(jù)的同時用另一根線接收數(shù)據(jù)。

在串口通信中,常用的協(xié)議包括RS-232、RS-422和RS-485。

當然具體是那種協(xié)議和你選擇的硬件有關(guān),將你的硬件插到對應(yīng)協(xié)議的串口口即可。

開發(fā)前的準備

1.檢查你的開發(fā)板設(shè)備,包括開發(fā)板信息,開發(fā)板上面包含的模塊信息。是否有Wifi模塊 藍牙模塊 指定接口等。還有一方面就是關(guān)于開發(fā)板系統(tǒng)的信息,開發(fā)板的系統(tǒng)版本。如果需要特別定制,可以和廠商商量。

關(guān)于系統(tǒng)定制
某些特殊的板塊需要隱藏狀態(tài)欄不能被下拉,否則會被退出應(yīng)用。還有一方面就是可以定制取消掉下導(dǎo)航欄。

2.檢查你的硬件裝備
正確連接你的設(shè)備,向你的硬件提供商索要開發(fā)資料?;镜馁Y料包括硬件的通訊命令格式。當然更好的是如果能要到開發(fā)程序資料。比如android程序或者源碼那就更好了。

3.正確的連接,測試你的硬件與系統(tǒng)
Android串口助手
下載一個串口調(diào)試助手,按照資料輸入命令。測試是否能夠成功的啟動設(shè)備。并且收到對應(yīng)的返回數(shù)據(jù)。

開發(fā)階段

需要一點點的JNI知識和一點點Android多線程開發(fā)經(jīng)驗

整體的開發(fā)流程如下:打開指定串口-->開啟接收數(shù)據(jù)線程(readthead)-->發(fā)送串口數(shù)據(jù)-->接收數(shù)據(jù)處理返回信息-->關(guān)閉接收數(shù)據(jù)線程(readthead)-->關(guān)閉串口。

導(dǎo)入so庫

谷歌開源serialPort api項目

里面封裝了c層代碼調(diào)用底層代碼的通信方式,如果你們喜歡改東西的話??梢宰约焊闹?,不過我覺得沒有必要,因為這些代碼已經(jīng)封裝的很好了。直接使用即可。

至于通過c代碼如何生成相應(yīng)的so文件,以及如何java層調(diào)用c層代碼都是很基礎(chǔ)的東西啦。
我不想在這里展開大篇幅的講JNI,因為串口通信其實用的JNI知識不多。

首先把JNI相關(guān)代碼導(dǎo)入到自己的工程里面:

先看下目錄結(jié)構(gòu)吧:

jni目錄

[圖片上傳失敗...(image-76edb2-1606101414169)]

java 目錄

[圖片上傳失敗...(image-df6e96-1606101414169)]

SerialPort.java

了解JNI的同學(xué)都知道的,這個SerialPort.h對應(yīng)的就是SerialPort.java層的native 方法。

這里用兩個方法

private native static FileDescriptor open(String path, int baudrate, int flags);
public native void close();

很顯然一個是打開串口 一個是 關(guān)閉串口 方法

打開串口之前,程序需要獲得最高權(quán)限,SerialPort.java的構(gòu)造函數(shù)里面需要獲得設(shè)備的超級root權(quán)限,也是通過輸入su命令完成。

if (!device.canRead() || !device.canWrite()) {
            try {
                /* Missing read/write permission, trying to chmod the file */
                Process su;
                su = Runtime.getRuntime().exec("/system/bin/su");
                String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
                        + "exit\n";
                su.getOutputStream().write(cmd.getBytes());
                if ((su.waitFor() != 0) || !device.canRead()
                        || !device.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new SecurityException();
            }
        }

最后記得調(diào)用生成的.so文件

static {
        System.loadLibrary("serial_port");
    }

SerialPortFinder

這個類很簡單,能用于獲取設(shè)備的串口信息。通常一個開發(fā)板會有幾個到十幾個的串口。
兩個public方法:

  • public String[] getAllDevices() 獲取所有串口名稱
  • public String[] getAllDevicesPath() 獲取所有串口地址

開始通信

[圖片上傳失敗...(image-3ca0db-1606101414169)]

整個信息發(fā)送接收步驟如下:

1.初始化SerialPort 獲得權(quán)限打開指定串口
2.打開ReadThread監(jiān)聽數(shù)據(jù)返回
3.使用SendThread發(fā)送數(shù)據(jù)
4.繼續(xù)發(fā)送或者關(guān)閉

為此我們需要寫一個SerialHelper來簡化代碼,以下是核心代碼:

構(gòu)造函數(shù)

 public SerialHelper(String sPort, int iBaudRate) {
        this.sPort = "/dev/ttyS3";
        this.iBaudRate = 9600;
        this._isOpen = false;
        this._bLoopData = new byte[]{48};
        this.iDelay = 500;
        this.sPort = sPort;
        this.iBaudRate = iBaudRate;
    }

打開 關(guān)閉 串口

//打開時打開監(jiān)聽線程
 public void open() throws SecurityException, IOException, InvalidParameterException {
        this.mSerialPort = new SerialPort(new File(this.sPort), this.iBaudRate, 0);
        this.mOutputStream = this.mSerialPort.getOutputStream();
        this.mInputStream = this.mSerialPort.getInputStream();
        this.mReadThread = new SerialHelper.ReadThread();
        this.mReadThread.start();
        this.mSendThread = new SerialHelper.SendThread();
        this.mSendThread.setSuspendFlag();
        this.mSendThread.start();
        this._isOpen = true;
    }

   // 關(guān)閉線程 釋放函數(shù)
    public void close() {
        if (this.mReadThread != null) {
            this.mReadThread.interrupt();
        }

        if (this.mSerialPort != null) {
            this.mSerialPort.close();
            this.mSerialPort = null;
        }

        this._isOpen = false;
    }

兩個線程 發(fā)送線程:

private class SendThread extends Thread {
        public boolean suspendFlag;

        private SendThread() {
            this.suspendFlag = true;
        }

        public void run() {
            super.run();

            while(!this.isInterrupted()) {
                synchronized(this) {
                    while(this.suspendFlag) {
                        try {
                            this.wait();
                        } catch (InterruptedException var5) {
                            var5.printStackTrace();
                        }
                    }
                }

                SerialHelper.this.send(SerialHelper.this.getbLoopData());

                try {
                    Thread.sleep((long)SerialHelper.this.iDelay);
                } catch (InterruptedException var4) {
                    var4.printStackTrace();
                }
            }

        }

        public void setSuspendFlag() {
            this.suspendFlag = true;
        }

        public synchronized void setResume() {
            this.suspendFlag = false;
            this.notify();
        }
    }

讀取線程

    private class ReadThread extends Thread {
        private ReadThread() {
        }

        public void run() {
            super.run();

            while(!this.isInterrupted()) {
                try {
                    if (SerialHelper.this.mInputStream == null) {
                        return;
                    }

                    byte[] buffer = new byte[512];
                    int size = SerialHelper.this.mInputStream.read(buffer);
                    if (size > 0) {
                        ComBean ComRecData = new ComBean(SerialHelper.this.sPort, buffer, size);
                        SerialHelper.this.onDataReceived(ComRecData);
                    }
                } catch (Throwable var4) {
                    Log.e("error", var4.getMessage());
                    return;
                }
            }

        }
    }

其他函數(shù)見代碼,包括數(shù)值和文本發(fā)送 波特率的設(shè)置等等。

實戰(zhàn)一個串口數(shù)據(jù)調(diào)試助手

下面使用封裝的SerialHelper來完成整個數(shù)據(jù)發(fā)送接收:

界面隨便搞一下:

[圖片上傳失敗...(image-be31d6-1606101414169)]

然后開始邏輯代碼:

首先實例化SerialPortFinder 實現(xiàn)數(shù)據(jù)接收 寫入列表

serialPortFinder = new SerialPortFinder();
        serialHelper = new SerialHelper() {
            @Override
            protected void onDataReceived(final ComBean comBean) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getBaseContext(), FuncUtil.ByteArrToHex(comBean.bRec), Toast.LENGTH_SHORT).show();
                        logListAdapter.addData(comBean.sRecTime+":   "+FuncUtil.ByteArrToHex(comBean.bRec));
                        recy.smoothScrollToPosition(logListAdapter.getData().size());
                    }
                });
            }
        };

然后利用SerialPortFinder找到所有的串口號,列出來所有的波特率 ,都傳給spinner

final String[] ports = serialPortFinder.getAllDevicesPath();
final String[] botes = new String[]{"0", "50", "75", "110", "134", "150", "200", "300", "600", "1200", "1800", "2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800", "500000", "576000", "921600", "1000000", "1152000", "1500000", "2000000", "2500000", "3000000", "3500000", "4000000"};

SpAdapter spAdapter = new SpAdapter(this);
        spAdapter.setDatas(ports);
        spSerial.setAdapter(spAdapter);

SpAdapter spAdapter2 = new SpAdapter(this);
        spAdapter2.setDatas(botes);
        spBote.setAdapter(spAdapter2)

打開串口:

btOpen.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    serialHelper.open();
                    btOpen.setEnabled(false);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

數(shù)據(jù)發(fā)送(兩種數(shù)據(jù)發(fā)送格式):

文本類型

 if (serialHelper.isOpen()) {
                            serialHelper.sendTxt(edInput.getText().toString());
                        } else {
                            Toast.makeText(getBaseContext(), "搞毛啊,串口都沒打開", Toast.LENGTH_SHORT).show();
                        }

Hex類型

if (serialHelper.isOpen()) {
                            serialHelper.sendHex(edInput.getText().toString());
                        } else {
                            Toast.makeText(getBaseContext(), "搞毛啊,串口都沒打開", Toast.LENGTH_SHORT).show();
                        }

最后記得關(guān)閉一下串口咯:

 @Override
    protected void onDestroy() {
        super.onDestroy();
        serialHelper.close();
    }

好的 ,完事了。 測試一下


連線開機:

[圖片上傳失敗...(image-82778a-1606101414169)]

發(fā)串口信息:

[圖片上傳失敗...(image-b8ebf1-1606101414169)]

同時設(shè)備也滴塌滴塌的響了,完美。

代碼我也放上去把Android串口助手

一些要說的

雖然整個JNI移植過程非常簡單,但是問題出現(xiàn)了。如果大家使用的3.0版本的AS、會發(fā)現(xiàn)默認的JNI使用Cmake而不是.mk文件配置的。

所以又增加了一個難度,為了方便大家。我把所有關(guān)于串口的資源打包成aar 文件,大家直接使用即可。

谷歌android串口開發(fā) aar文件

使用過程:
aar文件導(dǎo)入lib文件夾
gradle文件

 repositories {
        flatDir {
            dirs 'libs'
        }
    }

dependencies {
    implementation(name: 'serialport-1.0.1', ext: 'aar')
}

完成。

總結(jié)一下

基本的串口通信到此結(jié)束。到了實際生產(chǎn),更多的要解決多線程上的邏輯問題。設(shè)備的各種狀態(tài)以及突發(fā)狀況的處理等等。所以串口通信成功只是一個小小的開始,更多的問題還在后面。

再放一次本篇文章的代碼 Android串口助手

再次編輯一下:

!this.isInterrupted() 這個方法是谷歌官方demo里面的代碼,我沒有做修改。當然實際生產(chǎn)的時候你會碰到多個發(fā)送界面問題 需要結(jié)合 available 方法判斷是否有數(shù)據(jù)

try {
while (inputStream.available() > 0) {
int numBytes = inputStream.read(readBuffer);
 }
System.out.print(new String(readBuffer));
} catch (IOException e) {
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容