0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

安卓端的串口通信實(shí)踐講解2

jf_78858299 ? 來源:Android技術(shù)之家 ? 作者:Android技術(shù)之家 ? 2023-04-23 17:59 ? 次閱讀

和原生 API 不同的是,因?yàn)槲覀兇藭r(shí)已經(jīng)知道了我們的 ESP32 主板的設(shè)備信息,以及使用的驅(qū)動(dòng)(CDC),所以我們就不使用原生的查找可用設(shè)備的方法了,我們這里直接指定我們已知的這個(gè)設(shè)備(當(dāng)然,你也可以繼續(xù)使用原生API的查找和連接方法):

private fun scanDevice(context: Context) {
    val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager


    val customTable = ProbeTable()
    // 添加我們的設(shè)備信息,三個(gè)參數(shù)分別為 vendroId、productId、驅(qū)動(dòng)程序
    customTable.addProduct(0x1a86, 0x55d3, CdcAcmSerialDriver::class.java)


    val prober = UsbSerialProber(customTable)
    // 查找指定的設(shè)備是否存在
    val drivers: List

連接到設(shè)備后,下一步就是和數(shù)據(jù)交互,這里封裝的十分方便,只需要獲取到 UsbSerialPort 后,直接調(diào)用它的 read()write() 即可讀寫數(shù)據(jù):

port = driver.ports[0] // 大多數(shù)設(shè)備都只有一個(gè) port,所以大多數(shù)情況下直接取第一個(gè)就行


port.open(connection)
// 設(shè)置連接參數(shù),波特率9600,以及 “8N1”
port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)


// 讀取數(shù)據(jù)
val responseBuffer = ByteArray(1024)
port.read(responseBuffer, 0)


// 寫入數(shù)據(jù)
val sendData = byteArrayOf(0x6F)
port.write(sendData, 0)

此時(shí),一個(gè)完整的,用于測(cè)試我們上述 ESP32 程序的代碼如下:

@Composable
fun SerialScreen() {
    val context = LocalContext.current




    Column(
        Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = { scanDevice(context) }) {
            Text(text = "查找并連接設(shè)備")
        }


        Button(onClick = { switchLight(true) }) {
            Text(text = "開燈")
        }
        Button(onClick = { switchLight(false) }) {
            Text(text = "關(guān)燈")
        }


    }
}


private fun scanDevice(context: Context) {
    val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager


    val customTable = ProbeTable()
    customTable.addProduct(0x1a86, 0x55d3, CdcAcmSerialDriver::class.java)


    val prober = UsbSerialProber(customTable)
    val drivers: List

運(yùn)行這個(gè)程序,并且連接設(shè)備。

可以看到輸出的是 byte 的 101,轉(zhuǎn)換為 ASCII 即為 “e”。

然后我們點(diǎn)擊 “開燈”、“關(guān)燈” 效果如下:

圖片

對(duì)了,這里發(fā)送的數(shù)據(jù) “0x6F” 即 ASCII “o” 的十六進(jìn)制,同理,“0x63” 即 “c”。

可以看到,可以完美的和我們的 ESP32 開發(fā)版進(jìn)行通信。

實(shí)例

無論使用什么方式與串口通信,我們?cè)诎沧緼PP的代碼層面能夠拿到的數(shù)據(jù)已經(jīng)是處理好了的數(shù)據(jù)。

即,在上一篇文章中我們說過串口通信的一幀數(shù)據(jù)包括起始位、數(shù)據(jù)位、校驗(yàn)位、停止位。但是我們?cè)诎沧恐惺褂脮r(shí)一般拿到的都只有 數(shù)據(jù)位 的數(shù)據(jù),其他數(shù)據(jù)已經(jīng)在底層被解析好了,無需我們?nèi)リP(guān)心怎么解析,或者使用。

我們可以直接拿到的就是可用數(shù)據(jù)。

這里舉一個(gè)我之前用過的某型號(hào)驅(qū)動(dòng)版的例子。

這塊驅(qū)動(dòng)版關(guān)于通信的信息如圖:

圖片

可以看到,它采用了 RS485 的通信方式,波特率支持 9600 或 38400,8位數(shù)據(jù)位,無校驗(yàn),1位停止位。

并且,它還規(guī)定了一個(gè)數(shù)據(jù)協(xié)議。

在它定義的協(xié)議中,第一位為地址;第二位為指令;第三位到第N位為數(shù)據(jù)內(nèi)容;最后兩位為CRC校驗(yàn)。

需要注意的是,這里定義的協(xié)議是基于串口通信的,不要把這個(gè)協(xié)議和串口通信搞混了,簡(jiǎn)單來說就是在串口通信協(xié)議的數(shù)據(jù)位中又定義了一個(gè)自己的協(xié)議。

而且可以看到,雖然定義串口參數(shù)時(shí)沒有指定校驗(yàn),但是在它自己的協(xié)議中指定了使用 CRC 校驗(yàn)。

另外,弱弱的吐槽一句,這個(gè)驅(qū)動(dòng)版的協(xié)議真的不好使。

在實(shí)際使用過程中,主機(jī)與驅(qū)動(dòng)版的通信數(shù)據(jù)無法保證一定會(huì)在同一個(gè)數(shù)據(jù)幀中發(fā)送完成,所以可能會(huì)造成“粘包”、“分包”現(xiàn)象,也就是說,數(shù)據(jù)可能會(huì)分幾次發(fā)過來,而且你不好判斷這數(shù)據(jù)是上次沒發(fā)送完的數(shù)據(jù)還是新的數(shù)據(jù)。

我使用過的另外一款驅(qū)動(dòng)版就方便的多,因?yàn)樗鼤?huì)在幀頭加上開始符號(hào)和數(shù)據(jù)長(zhǎng)度,幀尾加上結(jié)束符號(hào)。

這樣一來,即使出現(xiàn)“粘包”、“分包”我們也能很好的給它解析出來。

當(dāng)然,它這樣設(shè)計(jì)協(xié)議肯定是有它的道理的,無非就是減少通信代價(jià)之類的。

我還遇到過一款十分簡(jiǎn)潔的驅(qū)動(dòng)版,直接發(fā)送一個(gè)整數(shù)過去表示執(zhí)行對(duì)應(yīng)的指令。

驅(qū)動(dòng)版回傳的數(shù)據(jù)同樣非常簡(jiǎn)單,就是一個(gè)數(shù)字,然后事先約定各個(gè)數(shù)字表示什么意思……

說歸說,我們還是繼續(xù)來看這款驅(qū)動(dòng)版的通信協(xié)議:

圖片

這是它的其中一個(gè)指令內(nèi)容,我們發(fā)送指令 “1” 過去后,它會(huì)返回當(dāng)前驅(qū)動(dòng)版的型號(hào)和版本信息給我們。

因?yàn)槲覀兊闹靼迨嵌ㄖ乒た刂靼?,所以使用的通信方式是直接?android-serialport-api。

最終發(fā)送與接收回復(fù)也很簡(jiǎn)單:

/**
 * 將十六進(jìn)制字符串轉(zhuǎn)成 ByteArray
 * */
private fun hexStrToBytes(hexString: String): ByteArray {
    check(hexString.length % 2 == 0) { return ByteArray(0) }


    return hexString.chunked(2)
        .map { it.toInt(16).toByte() }
        .toByteArray()
}


private fun isReceivedLegalData(receiveBuffer: ByteArray): Boolean {


    val rcvData = receiveBuffer.copyOf()  //重新拷貝一個(gè)使用,避免原數(shù)據(jù)被清零


    if (cmd.cmdId.checkDataFormat(rcvData)) {  //檢查回復(fù)數(shù)據(jù)格式
        isPkgLost = false
        if (cmd.cmdId.isResponseBelong(rcvData)) {  //檢查回復(fù)命令來源
            if (!AdhShareData.instance.getIsUsingCrc()) {  //如果不開啟CRC檢驗(yàn)則直接返回 true
                resolveRcvData(cmdRcvDataCallback, rcvData, cmd.cmdId)
                coroutineScope.launch(Dispatchers.Main) {
                    cmdResponseCallback?.onCmdResponse(ResponseStatus.Success, rcvData, 0, rcvData.size, cmd.cmdId)
                }
                return true
            }


            if (cmd.cmdId.checkCrc(rcvData)) {  //檢驗(yàn)CRC
                 resolveRcvData(cmdRcvDataCallback, rcvData, cmd.cmdId)
                coroutineScope.launch(Dispatchers.Main) {
                    cmdResponseCallback?.onCmdResponse(ResponseStatus.Success, rcvData, 0, rcvData.size, cmd.cmdId)
                }


                return true
            }
            else {
                coroutineScope.launch(Dispatchers.Main) {
                    cmdResponseCallback?.onCmdResponse(ResponseStatus.FailCauseCrcError, ByteArray(0), -1, -1, cmd.cmdId)
                }


                return false
            }
        }
        else {
            coroutineScope.launch(Dispatchers.Main) {
                cmdResponseCallback?.onCmdResponse(ResponseStatus.FailCauseNotFromThisCmd, ByteArray(0), -1, -1, cmd.cmdId)
            }


            return false
        }
    }
    else {  //數(shù)據(jù)不符合,可能是遇到了分包,繼續(xù)等待下一個(gè)數(shù)據(jù),然后合并
        isPkgLost = true
        return isReceivedLegalData(cmd)
        /*coroutineScope.launch(Dispatchers.Main) {
            cmdResponseCallback?.onCmdResponse(ResponseStatus.FailCauseWrongFormat, ByteArray(0), -1, -1, cmd.cmdId)
        }


        return false  */
    }
}


// ……省略初始化和連接代碼


// 發(fā)送數(shù)據(jù)
val bytes = hexStrToBytes("0201C110")
outputStream.write(bytes, 0, bytes.size)


// 解析數(shù)據(jù)
val recvBuffer = ByteArray(0)
inputStream.read(recvBuffer)


while (receiveBuffer.isEmpty()) {
   delay(10)
}


isReceivedLegalData()

本來打算直接發(fā)我封裝好的這個(gè)驅(qū)動(dòng)版的協(xié)議庫的,想了想,好像不太合適,所以就大概抽出了這些不完整的代碼,懂這個(gè)意思就行了,哈哈。

總結(jié)

從上面介紹的兩種方式可以看出,兩種方式使用各有優(yōu)缺點(diǎn)。

使用 android-serialport-api 可以直接讀取串口數(shù)據(jù)內(nèi)容,不需要轉(zhuǎn)USB接口,不需要驅(qū)動(dòng)支持,但是需要 ROOT,適合于定制安卓主板上已經(jīng)預(yù)留了 RS232 或 RS485 接口且設(shè)備已 ROOT 的情況下使用。

而使用 USB host ,可以直接讀取USB接口轉(zhuǎn)接的串口數(shù)據(jù),不需要ROOT,但是只支持有驅(qū)動(dòng)的串口轉(zhuǎn)USB芯片,且只支持使用USB接口,不支持直接連接串口設(shè)備。

各位可以根據(jù)自己的實(shí)際情況靈活選擇使用什么方式來實(shí)現(xiàn)串口通信。

當(dāng)然,除了現(xiàn)在介紹的這些串口通信,其實(shí)還有一個(gè)通信協(xié)議在實(shí)際使用中用的非常多,那就是 MODBUS 協(xié)議。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • plc
    plc
    +關(guān)注

    關(guān)注

    5011

    文章

    13297

    瀏覽量

    463376
  • 串口通信
    +關(guān)注

    關(guān)注

    34

    文章

    1626

    瀏覽量

    55529
  • 安卓
    +關(guān)注

    關(guān)注

    5

    文章

    2130

    瀏覽量

    57202
  • ESP32
    +關(guān)注

    關(guān)注

    18

    文章

    971

    瀏覽量

    17276
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    LabVIEW串口通信實(shí)例:PC機(jī)與PC機(jī)串口通信實(shí)

    08、LabVIEW串口通信實(shí)例:PC機(jī)與PC機(jī)串口通信實(shí)
    發(fā)表于 06-28 20:07

    LabVIEW串口通信實(shí)例:PC機(jī)與51單片機(jī)串口通信實(shí)

    LabVIEW串口通信實(shí)例:PC機(jī)與51單片機(jī)串口通信實(shí)
    發(fā)表于 12-18 10:16

    STM32的USART串口通信實(shí)踐

    STM32的USART串口通信實(shí)踐
    發(fā)表于 08-16 07:20

    Matlab的串口通信實(shí)現(xiàn)

    第9章 Matlab的串口通信實(shí)現(xiàn)本章節(jié)主要為大家講解Matlab的串口方式波形數(shù)據(jù)傳輸和后期數(shù)據(jù)分析功能,非常實(shí)用。目錄第9章 Matlab的串口
    發(fā)表于 08-17 06:48

    與ESP8266串口WIFI模塊的通信實(shí)現(xiàn)相關(guān)資料推薦

    Button分別對(duì)應(yīng)相應(yīng)的功能鍵。通信實(shí)現(xiàn)原理使用基于tcp協(xié)議的socket通信串口WiFi模塊配置成tcp Service模式,然后
    發(fā)表于 01-18 08:34

    STM32 USART串口通信實(shí)踐

    STM32串口通訊USART串口通信實(shí)踐USART串口通信實(shí)踐1、實(shí)驗(yàn)環(huán)境參考資料 野火官方的 《零死角玩轉(zhuǎn)STM32—F103指南者》①
    發(fā)表于 02-10 07:06

    基于串口的LABVIEW與PLC的通信實(shí)現(xiàn)

    基于串口的LABVIEW與PLC的通信實(shí)現(xiàn):介紹使用ADAM5000的通信協(xié)議實(shí)現(xiàn)LABVIEW與ADAM5510通信。
    發(fā)表于 09-19 07:55 ?62次下載

    串口通信實(shí)例教程

    串口通信實(shí)例教程,感興趣的可以看看。
    發(fā)表于 06-23 17:56 ?0次下載

    STM32 串口通信實(shí)驗(yàn)

    USB轉(zhuǎn)RS485線在F103環(huán)境下進(jìn)行UART通信實(shí)驗(yàn)和RS232通信實(shí)驗(yàn)在F407環(huán)境下進(jìn)行RS485實(shí)驗(yàn)(1.我的103板子沒有485口,2.正好看下開發(fā)板環(huán)境不同的差異)UART
    發(fā)表于 12-20 19:26 ?15次下載
    STM32 <b class='flag-5'>串口</b><b class='flag-5'>通信實(shí)</b>驗(yàn)

    STM32下的USART串口通信程序

    STM32的USART串口通信實(shí)踐
    發(fā)表于 12-24 18:42 ?13次下載
    STM32下的USART<b class='flag-5'>串口</b><b class='flag-5'>通信</b>程序

    串口通信實(shí)驗(yàn)

    串口通信實(shí)驗(yàn)串口通信實(shí)驗(yàn)代碼圖像串口通信實(shí)驗(yàn)晶振12MHz,波特率1200, 程序啟動(dòng)后單片機(jī)主
    發(fā)表于 01-14 10:12 ?6次下載
    <b class='flag-5'>串口</b><b class='flag-5'>通信實(shí)</b>驗(yàn)

    串口通信入門之modbus(上)

    在之前的兩篇文章中,我們講解串口的基礎(chǔ)知識(shí)和在中使用串口通信的方法,如果還沒看過之前文章的
    的頭像 發(fā)表于 04-23 17:40 ?1928次閱讀
    <b class='flag-5'>安</b><b class='flag-5'>卓</b>與<b class='flag-5'>串口</b><b class='flag-5'>通信</b>入門之modbus(上)

    串口通信入門之modbus(下)

    在之前的兩篇文章中,我們講解串口的基礎(chǔ)知識(shí)和在中使用串口通信的方法,如果還沒看過之前文章的
    的頭像 發(fā)表于 04-23 17:40 ?5024次閱讀
    <b class='flag-5'>安</b><b class='flag-5'>卓</b>與<b class='flag-5'>串口</b><b class='flag-5'>通信</b>入門之modbus(下)

    串口通信實(shí)踐講解1

    這篇文章我們將講解串口通信實(shí)踐,即如何使用串口
    的頭像 發(fā)表于 04-23 17:59 ?2971次閱讀
    <b class='flag-5'>安</b><b class='flag-5'>卓</b><b class='flag-5'>端</b>的<b class='flag-5'>串口</b><b class='flag-5'>通信實(shí)踐</b><b class='flag-5'>講解</b>1

    串口通信實(shí)驗(yàn)資料分享

    串口通信實(shí)驗(yàn)資料分享
    發(fā)表于 06-29 15:03 ?5次下載