1. 程式人生 > >Android 串列埠通訊開發筆記3

Android 串列埠通訊開發筆記3

Android串列埠開發 延伸和擴充套件,
1.使用JNI Cmake 自己編譯串列埠通訊 的so庫:Android Studio 3.0 實現方式。
2.CRC校驗 以及擴充套件設計:

a.一(串列埠)對多(硬體通訊);
b.多(串列埠)對多(硬體)的實現。

1.以串列埠除錯工具為例,使用其原本的原始碼使用JNI Cmake Android Studio 3.0 實現方式。

creat project
creat project

勾選 include C++ support 沒有下載ndk 的要下載。

①.延續使用jni 的方式


image.png
image.png

把相關的 been 和實現方法 都複製過來如圖。

建立.h 檔案 注:一定要現進入到app/main/java/ 目錄下

然後 javah -classpath -jni +完整路徑到類名

image.png
image.png

在main目錄下建立jni 資料夾,把生成的.h 檔案複製進去 ,新建同名的.c檔案,把實現程式碼拷進去--注意需要修改 open 和close方法的名字 和.h 檔案裡改為一致。

.c.png
.c.png

這是.h 檔案的

image.png
image.png

修改 cmakelist.txt 中 add_library 的so檔名 和路徑

add_library( # Sets the name of the library.
  # 設定so檔名稱.
   serial_port

   # Sets the library as a shared library.
SHARED # 設定這個so檔案為共享. # Provides a relative path to your source file(s). # 設定這個so檔案為共享. src/main/jni/com_silencefun_comtest_serialport_SerialPort.c) // .......省略註釋部分 target_link_libraries( # Specifies the target library. # 制定目標庫. serial_port # Links the target library to the log library
# included in the NDK. ${log-lib} )

注: “serial_port” 這個 so庫名稱要和你要載入的要保持一致
在SerialPortJava類中,

image.png
image.png

在app的build.gradle中 defaultConfig中配置生成平臺so包

  defaultConfig {
    applicationId "com.silencefun.comtest"
    minSdkVersion 19
    targetSdkVersion 26
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    externalNativeBuild {
        cmake {
            cppFlags ""
            //生成多個版本的so檔案
            abiFilters 'arm64-v8a','armeabi-v7a','x86','x86_64'
        }
    }
}

然後 同步,build-make project 執行完畢之後切換檢視

如圖已經生成了so:

image.png
image.png

②直接使用cmake方式 不得不說新支援方式的簡單了好多。

建立完支援C的專案後,

在需要呼叫java 的Native 類中宣告方法,其實還是直接複製SerialPort把載入的so 檔名字改一下:


image.png
image.png

直接在自動生成的Native-lib.c中完善實現 (其他都不用改,方便快捷)

把 原來.c檔案中的實現方法 直接拷過去然後修改方法名:
注意 要對應好路徑


image.png
image.png
出現問題反思:採

用jni方式在Android 5.1的板子沒有問題 換到4.4結果就不行 總是 LOGE("tcgetattr() failed");

後來 更新ndk 、cmake、 LLDB到最新,完全解決問題。

github 地址歡迎 star❤

2.擴充套件設計:一(串列埠)對多(硬體通訊)、多(串列埠)對多(硬體)的實現。

因為 有些智慧終端硬體限制有些可能只開放一個通訊口來接多個硬體模組資料,當然前提是 掛在這一個通訊口上的 硬體模組 是在同一波特率,因為開啟初始化的時候已經設定好了波特率(嘗試動態更改,不太理解底層實現過程程式碼,屢敗屢試,最終放棄)。

相似的某些硬體模組要採集的資料可能是多條資料(多個命令下返回多條資料 最後在封裝)所以設定一個【命令組】的概念:把硬體模組對應的命令放進一個數組或者list中。

命令組,即 該硬體所需要的資料是需要連續傳送一組 若干個命令,根據接收到的多條資料來解析--- 此處只是解析方式不同,可以根據每次傳遞標誌位來區分,待所有所需所有數值都有,即執行一輪次之後 完全解析再更新資料。

同理多串列埠情況下就是多個通訊串列埠,每一個口上邊都掛了N(N>=1)個硬體模組(當然這麼殘暴的情形是有的:比如波特率不同必須多個串列埠)。

在之前一篇筆記中 Android 串列埠通訊筆記2 的SerialHelper,即控制類---每個硬體串列埠物件的管理控制例項 中新增 相應的 成員變數 來區分 目前 具體是哪一個 硬體模組 的哪一個命令 響應的 值。

所以對應的 封裝 讀取到的 資訊 也要新增上 當前 對應的 命令 和硬體 模組標誌。

Combean 新增欄位

private String scmd = "";
private String sflag = "";

對應的 硬體模組 資料結構大概可以:

MeterInfo 
private String name;//名稱
private String modenname;//型號
private String modertype;//型別
private List<String>  cmdlist;//傳送命令
private String cleandatacmd;//清除命令
private String decimal;//小數位置 (可能解析能用到)
private String portname;//port路徑 類似 /dev/ttys1
private String sFlag;//flag 可取 模組name

SerialHelper 類要在原有基礎之上 新增 部分欄位

....
private String scmd = "";
private String sflag = "";
private List<MeterInfo> meterlist = new ArrayList<>(); //一個串列埠肯能要和多個 硬體通訊
.....

所以初始化串列埠控制類SerialHelper的例項時候,要先set List<MeterInfo> ,
 在send 命令執行緒中,兩層迴圈:

for (int i = 0; i < meterlist .size(); i++) {
        List<String> cmdlist = meterlist .get(i).getGetdatacmdlist();

         for (int j = 0; j < cmdlist.size(); j++) {
                 setHexLoopData(cmdlist.get(j));
                  setSflag(meterlist .get(i).getsFlag());
                   setScmd(cmdlist.get(j));
                        //設定兩次的時間間隔
                   setTimespace(meterlist .get(i));
                    send(getbLoopData());//傳送命令
                  try {
                    Thread.sleep(iDelay);
                   } catch (InterruptedException e) {
                     e.printStackTrace();
                   }
           }
  }

對應的在 傳送執行緒傳送後 傳送執行緒sleep 的時候 ,讀的執行緒一直在跑,讀取到資料 封裝Combean 物件:

int size = mInputStream.read(buffer);
       if (size > 0) {
         ComBean ComRecData = new ComBean(sPort, buffer, size);
         ComRecData.setScmd(scmd);
         ComRecData.setSflag(sflag);
         onDataReceived(ComRecData); //呼叫抽象方法傳遞
       }

這樣在業務處理部分實現 onDataReceived 方法時候就能判斷是哪個模組哪個命令對應的值。

關於CRC校驗:

   /**
 * 獲取 crc校驗碼
 *
 * @param hextext 16進位制
 * @return 低位高位順序
 */
public static String getCrc16(String hextext) {
    byte[] arr_buff = SerialFunc.HexToByteArr(hextext);

    int len = arr_buff.length;

    // 預置 1 個 16 位的暫存器為十六進位制FFFF, 稱此暫存器為 CRC暫存器。
    int crc = 0xFFFF;
    int i, j;
    for (i = 0; i < len; i++) {
        // 把第一個 8 位二進位制資料 與 16 位的 CRC暫存器的低 8 位相異或, 把結果放於 CRC暫存器
        crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (arr_buff[i] & 0xFF));
        for (j = 0; j < 8; j++) {
            // 把 CRC 暫存器的內容右移一位( 朝低位)用 0 填補最高位, 並檢查右移後的移出位
            if ((crc & 0x0001) > 0) {
                // 如果移出位為 1, CRC暫存器與多項式A001進行異或
                crc = crc >> 1;
                crc = crc ^ 0xA001;
            } else
                // 如果移出位為 0,再次右移一位
                crc = crc >> 1;
        }
    }
    String c = Integer.toHexString(crc);
    if (c.length() == 4) {
        c = c.substring(2, 4) + c.substring(0, 2);
    } else if (c.length() == 3) {
        c = "0" + c;
        c = c.substring(2, 4) + c.substring(0, 2);
    } else if (c.length() == 2) {
        c = "0" + c.substring(1, 2) + "0" + c.substring(0, 1);
    }
    return c.toUpperCase();
}