簡單介紹Java的串口通信(下)
串行接口是一種可以將接受來自CPU的并行數據字符轉換為連續的串行數據流發送出去,同時可將接受的串行數據流轉換為并行的數據字符供給CPU的器件。簡單介紹Java的串口通信。接上一篇>>
3.1 事件監聽模型
現在我們來看看事件監聽模型是如何運作的:
首先需要在你的端口控制類(例如SManager)加上“implements SerialPortEventListener”
在初始化時加入如下代碼:
- try {
- SerialPort sPort.addEventListener(SManager);
- } catch (TooManyListenersException e) {
- sPort.close();
- throw new SerialConnectionException("too many listeners added");
- }
- sPort.notifyOnDataAvailable(true);
覆寫public void serialEvent(SerialPortEvent e)方法,在其中對如下事件進行判斷:
- BI -通訊中斷.
- CD -載波檢測.
- CTS -清除發送.
- DATA_AVAILABLE -有數據到達.
- DSR -數據設備準備好.
- FE -幀錯誤.
- OE -溢位錯誤.
- OUTPUT_BUFFER_EMPTY -輸出緩沖區已清空.
- PE -奇偶校驗錯.
- RI - 振鈴指示.
一般最常用的就是DATA_AVAILABLE--串口有數據到達事件。也就是說當串口有數據到達時,你可以在serialEvent中接收并處理所收到的數據。然而在我的實踐中,遇到了一個十分嚴重的問題。
首先描述一下我的實驗:我的應用程序需要接收傳感器節點從串口發回的查詢數據,并將結果以圖標的形式顯示出來。串口設定的波特率是 115200,川口每隔128毫秒返回一組數據(大約是30字節左右),周期(即持續時間)為31秒。實測的時候在一個周期內應該返回4900多個字節,而用事件監聽模型我最多只能收到不到1500字節,不知道這些字節都跑哪里去了,也不清楚到底丟失的是那部分數據。值得注意的是,這是我將 serialEvent()中所有處理代碼都注掉,只剩下打印代碼所得的結果。數據丟失的如此嚴重是我所不能忍受的,于是我決定采用其他方法。
3.2 串口讀數據的線程模型
這個模型顧名思義,就是將接收數據的操作寫成一個線程的形式:
- public void startReadingDataThread() {
- Thread readDataProcess = new Thread(new Runnable() {
- public void run() {
- while (newData != -1) {
- try {
- newData = is.read();
- System.out.println(newData);
- //其他的處理過程
- ……….
- } catch (IOException ex) {
- System.err.println(ex);
- return;
- }
- }
- readDataProcess.start();
- }
在我的應用程序中,我將收到的數據打包放到一個緩存中,然后啟動另一個線程從緩存中獲取并處理數據。兩個線程以生產者—消費者模式協同工作。
這樣,我就圓滿解決了丟數據問題。然而,沒高興多久我就又發現了一個同樣嚴重的問題:雖然這回不再丟數據了,可是原本一個周期(31秒)之后,傳感器節電已經停止傳送數據了,但我的串口線程依然在努力的執行讀串口操作,在控制臺也可以看見收到的數據仍在不斷的打印。
原來,由于傳感器節點發送的數據過快,而我的接收線程處理不過來,所以InputStream就先把已到達卻還沒處理的字節緩存起來,于是就導致了明明傳感器節點已經不再發數據了,而控制臺卻還能看見數據不斷打印這一奇怪的現象。唯一值得慶幸的是最后收到數據確實是4900左右字節,沒出現丟失現象。然而當處理完最后一個數據的時候已經快1分半鐘了,這個時間遠遠大于節點運行周期。這一延遲對于一個實時的顯示系統來說簡直是災難!
后來我想,是不是由于兩個線程之間的同步和通信導致了數據接收緩慢呢?于是我在接收線程的代碼中去掉了所有處理代碼,僅保留打印收到數據的語句,結果依然如故??磥聿⒉皇蔷€程間的通信阻礙了數據的接收速度,而是用線程模型導致了對于發送端數據發送速率過快的情況下的數據接收延遲。這里申明一點,就是對于數據發送速率不是如此快的情況下前面者兩種模型應該還是好用的,只是特殊情況還是應該特殊處理。
3.3 第三種方法
痛苦了許久(Boss天天催我L)之后,偶然的機會,我聽說TinyOS中(又是開源的)有一部分是和我的應用程序類似的串口通信部分,于是我下載了它的1.x版的Java代碼部分,參考了它的處理方法。
解決問題的方法說穿了其實很簡單,就是從根源入手。根源不就是接收線程導致的嗎,那好,我就干脆取消接收線程和作為中介的共享緩存,而直接在處理線程中調用串口讀數據的方法來解決問題(什么,為什么不把處理線程也一并取消?----都取消應用程序界面不就鎖死了嗎?所以必須保留)于是程序變成了這樣:
- public byte[] getPack(){
- while (true) {
- // PacketLength為數據包長度
- byte[] msgPack = new byte[PacketLength];
- for(int i = 0; i < PacketLength; i++){
- if( (newData = is.read()) != -1){
- msgPack[i] = (byte) newData;
- System.out.println(msgPack[i]);
- }
- }
- return msgPack;
- }
- }
在處理線程中調用這個方法返回所需要的數據序列并處理之,這樣不但沒有丟失數據的現象行出現,也沒有數據接收延遲了。這里唯一需要注意的就是當串口停止發送數據或沒有數據的時候is.read()一直都返回-1,如果一旦在開始接收數據的時候發現-1就不要理它,繼續接收,直到收到真正的數據為止。
4 結束語
本文介紹了串口通信的基本知識,以及常用的幾種模式。通過實踐,提出了一些問題,并在最后加以解決。值得注意的是對于第一種方法,我曾將傳感器發送的時間由128毫秒增加到512毫秒,仍然有很嚴重的數據丟失現象發生,所以如果你的應用程序需要很精密的結果,傳輸數據的速率又很快的話,就最好不要用第一種方法。
對于第二種方法,由于是線程導致的問題,所以對于不同的機器應該會有不同的表現,對于那些處理多線程比較好的機器來說,應該會好一些。但是我的機器是Inter 奔四3.0雙核CPU+512DDR內存,這樣都延遲這么厲害,還得多強的CPU才行啊?
所以對于數據量比較大的傳輸來說,還是用第三種方法吧。不過這個世界問題是很多的,而且未知的問題比已知的問題多的多,說不定還有什么其他問題存在,歡迎你通過下面的聯系方式和我一起研究。