安卓與串列埠通訊-基礎篇
theme: channing-cyan
前言
安卓並不僅僅只是一個手機作業系統,在很多領域都能見到安卓的身影。
無論是車載系統、工控系統、屏控系統還是物聯網裝置基本都有安卓的一席之地。
在所謂的寒冬之下,純粹的安卓開發似乎已經不再吃香,於是越來越多的安卓開發者轉向了車載、工控等領域。
而在這些領域,繞不開串列埠通訊相關知識,無論是車載系統與車身各個感測器或者 ECU 通訊還是工控系統與 PLC 互動,都離不開串列埠。
說來慚愧,雖然我勉強算是一個做安卓工控的吧,但是實際上乾的活卻是調輪子,調引數,對於其中的基本原理一直是一知半解的。
正好最近有空,我決定先從串列埠通訊學起,慢慢夯實我的基礎知識。
在學習過程中也會把自己學到的東西結合自己工作中積累的為數不多的經驗分享出來。
因為對這方面其實我也不甚瞭解,所以難免會有紕漏,如有錯誤還望各位大佬指正。
本文中的部分圖表來自於文末標註的參考資料
串列埠基礎知識
序列與並行
如果我們想讓兩臺裝置(微控制器、電腦等)相互通訊,一般可以有兩種方式:序列與並行。
例如我們想從裝置A中傳送一條 8 bit 的資料到裝置 B。
如果是序列的方式則會從第1位到第8位依次排隊從一根線(注意,這裡的一根線不是物理意義上的一根線,可以理解成一根完整的資料線)上傳送過去。
如果使用並行的方式,則可能裝置A和裝置B之間有8條線相連線,傳送時這 8 bit 資料會同時從8根線上傳送到裝置B。
直接說可能不太好理解,但是看這張圖就能秒懂:
從上面的圖也不難看出,並行傳輸相比於序列傳輸速度會更快,但是相應的線路成本也更高,而且抗干擾能力也沒有序列強,所以並行的通訊距離沒有序列的距離長。
同時,由於序列協議的外設簡單,成本低通訊距離遠,所以我們在微控制器或者說工控裝置之間通常使用的都是序列傳輸。
而序列傳輸又可以分為同步序列和非同步序列。
同步序列與非同步序列
同步序列指的是通訊雙方的時鐘頻率保持一致,接收方時刻準備接收資料,並且只需要在接受到開始傳輸的訊號後即可連續傳輸資料,資料之間沒有間隙,不需要起始位和結束位,傳輸效率高,可以進行一對多通訊。常用的同步序列有 I2C 和 SPI。
而非同步序列指的是雙方時鐘頻率不一致,傳輸資料時採用幀格式傳輸,傳送方傳送每一幀資料時需要附加起始位和結束位,接收方通過讀取起始位和結束位來實現與傳送方的資訊同步,傳輸效率低,只能1對1通訊。通常一幀資料由起始位、資料位、校驗位、停止位組成。常用的非同步序列有 UART ,即我們俗稱的串列埠通訊。
串列埠通訊(UART)
為了實現串列埠通訊,我們需要在裝置之間連線三條線:TX(傳送)、RX(接收)、GND(接地)。
分別將裝置A的TX和裝置B的RX相連;裝置A的RX和裝置B的TX相連;裝置A、B的GND相連。
由於每個裝置之間都同時有傳送和接收端,所以串列埠通訊是全雙工通訊,即支援同時傳送和接收資料。
假如我們想要由裝置A向裝置B傳送一個字元 “E”,則傳送效果如圖:
即通過裝置A的傳送端(TX)向裝置B的接收端(RX)傳送一段資料。
此時如果測量傳送資料時的這條線上的電平,將得到這麼一個波形圖:
此即二進位制的 “01000101” 換算成十進位制則為 69 ,在 ASCII 中表示字母 “E” :
而這裡電平中讀到的 0 和 1 是怎麼定義呢?一般來說我們會定義高電平為1,低電平為0,具體怎麼定義下面會講,現在先按下不表。
事實上,這裡的波形圖並不完整,還記得我們上面介紹非同步序列時說過的嗎?它是採用幀傳輸的方式來傳輸資料的。也就是說,一個完整的資料幀至少還應該包含前面的起始位(1位低電平)和最後的結束位(1-2位高電平):
另外,一幀資料中資料位可選5-8位,一般來說使用的是 8 位;在資料位之後結束位之前還可以有一個校驗位用於校驗資料傳輸是否正確(奇偶校驗、CRC校驗等),當空閒時資料線輸出高電平,稱為空閒位:
那麼問題來了,接收端怎麼知道使用的資料位是多少位、是否使用校驗、結束位有幾位?
答案是不知道,也無法知道。
所以通常在串列埠通訊開始之前,傳輸雙方需要約定好使用的 資料位、校驗位、停止位。
一般使用時都是 8位資料位、無校驗、1位停止位,即俗稱的 “8N1”。
除了上面說的三個引數外,在開始通訊前還需要約定一個引數,那就是“波特率”。
波特率即每秒傳輸多少個bit的資料,單位為 bps(bits per second)。
回到我們上面說的傳輸字母 “E” 的圖上,雖然從圖中可以很直觀的看出電平的變化,但是,接收方應該怎麼知道“多長一段”是一個 bit 呢?這就需要用到我們的波特率了。
例如我們上面設定的波特率是 9600 bps,則表示每秒傳送 9600 bit,即一個 bit 寬度為 104 微秒。
此時接收方在讀取到起始位後,會開始每間隔104微秒讀取一次資料,直至讀取到結束位。
(事實上,在讀取到起始位後通常還會延遲 52 微秒才會開始讀取,此後每隔 104 微秒讀取一次,這是為了避免開始讀取時資料不穩定)
自此,串列埠通訊的基本原理就全部講完了。
是不是覺得少了點什麼呢?
對的,上面還留了一個問題,就是我們怎麼去界定高電平和低電平呢?
這就引出了我們的下一個主角 RS232 。
RS232
一般來說,串列埠通訊使用的是 TTL 電平。
TTL 即 Transistor-transistor logic ,電晶體邏輯,其電路由雙極電晶體構成,工作電壓為 5V。所以我們一般將 +5V 電壓認為是高電平(1),0V認為是低電平(0)。但是,TTL電路輸出的高電平最小為 2.4V、低電平最大為0.4V,這就導致高低電平之間的差值最小可能只有2V,所以其傳輸資料時極易受到干擾,例如此時受到靜電干擾,可能就會把低電平變為了高電平。所以此種傳輸方式的距離非常短,一般在1米以內,只適用於晶片和晶片之間或裝置與電腦之間的資料傳輸。
為了改善這些缺點,美國電子工業聯盟制定了一個標準:RS232 。
RS232改善的方法非常簡單,就是將電平提高。
為了提高傳輸的電平,我們只需要在原本的串列埠傳輸裝置之間增加一個電平轉換晶片即可。
此時,如果傳送裝置傳送一個 5V 的高電平給轉換晶片,會被提高到 -15V 後傳送給接收裝置;反之,如果這個晶片接收到 -15V 的高電平,又會將其轉換為 5V 傳送給裝置。
這就完成了將 TTL 電平轉換為 RS232 電平的操作。
在 RS232 中,高電平(1)為 -3V至-15V;低電平(0)為 +3V至+15V。
RS232 只是改變了電平大小,使得高低電平之間相差了將近 20V ,這樣一來就讓 RS232 的抗干擾能力大大增強,傳輸距離也達到了理論 15 米,同時傳輸資料格式也沒有改變。但是這也導致 RS232 的最大波特率只能使用 19200 bps,使得傳輸速率有限,且抗共模干擾能力依舊沒有改善。
所以,又誕生了 RS485 標準。
RS485
與 RS232 相同的是,RS485也是通過在原本的串列埠通訊基礎上添加了電平轉換晶片來實現。
不同的是, RS485 不再採用固定的電平高低判斷方式,而是使用差分訊號來表示資料。
RS485 接線只需要兩根線A與B。當A與B之間的電壓差在2V到6V之間時表示邏輯1;差值在-6V到-2V之間時表示邏輯0 。
同時 RS485 通常採用雙絞線的形式接線,這就使得它的抗共模干擾能力大大增強,因為當受到干擾時 A和B的電壓都會改變,此時他們的電壓差值就基本不會受到影響。
由於 RS485 使用差分訊號的特點,使得它的可傳輸訊號最低能達到 200mV、資料最高傳輸率能達到 10Mbps、傳輸距離可以達到1200米。
不過由於 RS485 只使用了兩根線,且兩根線都用於傳輸同一個資料的特性,使得它只支援半雙工通訊,即同一時間只能傳送或接收資料,不能同時傳送與接收資料;但是也因為這個特點,使得 RS485 不同於 RS232 只能兩個裝置點對點通訊,而是可以一對多通訊,即一個主機可以同時與多個從機通訊。
總結
自此我們已經對串列埠通訊有了一個基本的認識。
從上文中我們可以發現,UART定義了資料傳輸格式標準,RS232和RS485定義物理層面的資料傳輸方式與接線方式:
也就是說,無論是串列埠通訊還是 RS232 還是 RS485,其實它們的資料格式與內容都是一樣的。
對於我們安卓開發人員來說,無論使用什麼標準幾乎都對我們沒有影響,我們需要做的只是解析好通過串列埠傳輸過來的資料即可。
如何進行測試
講解完了上述的串列埠通訊原理,那麼我們應該怎麼測試呢?
最好的方式當然是有一個實機能夠測試,但是有時候我們手頭暫時沒有裝置或者不方便使用真實裝置進行測試,此時我們就可以使用虛擬串列埠進行模擬測試。
概述
在 Windows 上進行模擬測試相對於 MAC 上簡單的多。
大致流程為:使用軟體建立虛擬串列埠-啟動安卓模擬器並與建立的虛擬串列埠連線-完成安卓與虛擬串列埠的通訊。
由於我目前主力機使用的是 MAC ,所以我嘗試按照上述步驟在 MAC 上進行模擬測試。
但是卻卡死在了第一步,即建立虛擬串列埠。
我嘗試查找了一番,發現在 MAC 上並沒有可用的虛擬串列埠軟體,好不容易找到一款,卻已經停止維護,因為新版本的 MAC 許可權收緊,無法再建立虛擬串列埠。
好在,我手頭還有兩塊 ESP32 的開發版,所以其實不使用虛擬串列埠模擬也行,我可以直接使用 ESP32 開發版進行測試。
那麼,現在問題又來到了第二個步驟。
但是經過我的嘗試,MAC 始終無法在啟動安卓模擬器與串列埠裝置連線。
不過雖然無法使用,但是過程中踩了一些坑,也找到了解決方案,所以這裡還是大致講一下。
MAC 啟動模擬器與串列埠連線
首先,我們需要建立好安卓模擬器,這一步直接在 Android Studio 中按照常規方式建立就可以了
建立好模擬器後,我們不能直接在 Android Studio 中啟動,而是必須在終端中啟動,因為我們需要指定啟動引數。
啟動需要執行 emulator
命令,這個命令的可執行為檔案在 AndroidSdk/tools
中,例如我的路徑是 /Users/equationl/Library/Android/sdk/tools
。如果你不知道你的 AndroidSdk 目錄在哪兒,你可以直接在 Android Studio 的設定中搜索 “sdk” 後,在 “Android SDK Location” 選項中看到這個路徑:
找到你的路徑後執行以下命令即可啟動模擬器:
```shell cd /Users/equationl/Library/Android/sdk/tools
./emulator -avd Pixel_6_Pro_API_31 ```
其中的 “Pixel_6_Pro_API_31” 就是你之前建立的模擬器的名稱。
當然,這裡只是單純的啟動了模擬器,並沒有連線到串列埠,所以此時你啟動的模擬器是找不到你插入裝置的串列埠的。
如果我們需要連線串列埠,則需要執行:
./emulator -avd Pixel_6_Pro_API_31 -qemu -serial /dev/tty.wchusbserial544C0047221
其中的 /dev/tty.wchusbserial544C0047221
即為你的串列埠路徑。
在 MAC 上檢視可用串列埠的命令為:
ls /dev/tty.*
然而,如果你的 MAC 是 ARM CPU的話,大概率啟動模擬器時會報錯:
這應該是谷歌的一個小 BUG,從錯誤資訊也能看出,錯誤的原因是不存在 darwin-x86_64/qemu-system-aarch64
這個檔案。
我們來到報錯的路徑,可以看到,壓根沒有叫 darwin-x86_64
的資料夾,但是有一個叫 darwin-aarch64
的資料夾,裡面就有 qemu-system-aarch64
檔案。
哈哈哈,其實到這裡估計已經有人反應過來了,我們明明是 ARM 的處理器,怎麼會有 x86 資料夾嘛。
所以我們直接把 darwin-aarch64
軟連線到 darwin-x86_64
就行了。
執行命令:
```shell cd /Users/equationl/Library/Android/sdk/emulator/qemu
ln -s darwin-aarch64 darwin-x86_64 ```
然後再返回 tools 目錄,執行啟動模擬器的命令,可以看到,啟動成功了。
但是,連線串列埠失敗,並報錯:
可以看到,有兩個錯誤,一個是無法識別 serial
命令;另一個是無法連線到我們指定的串列埠。
關於這個錯誤,我也是搜尋了好久,也嘗試了各種方法,但是都無法解決。
不過我發現另外一種實現方法,那就是使用單獨的虛擬機器開啟一個安卓的模擬器,然後由這個虛擬機器建立一個虛擬串列埠給安卓模擬器使用,並將這個虛擬串列埠連線至物理裝置。
啊這?擱這套娃呢?太麻煩了!果斷放棄!
感興趣的可以去嘗試一下,教程地址:【Android】mac下Android的串列埠及NDK除錯探索
總結
因為上面的原因,最終我選擇的測試方案是 使用安卓真機+ESP32開發版進行測試。
對於同樣是使用 MAC 的使用者,我的建議是直接使用真機+ESP32開發版,或者其他更便宜的開發版進行測試學習,畢竟一塊開發版也就十幾塊錢,真沒必要為了省這十幾塊錢而去折騰這個神坑的無底洞。
而對於 Windows 使用者就方便多了,不僅可以使用和 MAC 一樣的真機測試方案,還可以很方便的使用虛擬串列埠以及安卓模擬器進行測試。
總結
我們今天主要講解了串列埠的一些基本知識,並且大概介紹了應該怎麼去測試串列埠通訊。
所有知識點都是循序漸進的,裝置間的通訊有序列通訊和並行通訊,一般我們在實際中使用的是序列比較多,而序列分為同步序列與非同步序列,常用的是非同步序列,非同步序列常用協議是 UART 即常說的串列埠,為了解決串列埠通訊的問題,又衍生出了 RS232、RS485等協議。
參考資料
- How can I launch Android Emulator without android studio on Mac M1
- 5分鐘看懂!串列埠RS232 RS485最本質的區別!
- 序列通訊介面之RS-232、RS-485與RS-422
- 3分鐘理解通訊協議之串列埠UART到底是個啥?
- 超簡單的一種通訊,2分鐘搞懂,串列埠通訊的工作原理!
- UART協議詳解
- UART介面的基礎知識詳解
- 串列埠通訊(UART)介紹
- 什麼是串列埠通訊?
- 串列埠通訊詳解
- 串列埠通訊詳解
本文正在參加「金石計劃 . 瓜分6萬現金大獎」
- 安卓與串列埠通訊-校驗篇
- 安卓與串列埠通訊-實踐篇
- 為 Kotlin 的函式新增作用域限制(以 Compose 為例)
- 安卓與串列埠通訊-基礎篇
- Compose For Desktop 實踐:使用 Compose-jb 做一個時間水印助手
- 初探 Compose for Wear OS:實現一個簡易選擇APP
- Compose太香了,不想再寫傳統 xml View?教你如何在已有View專案中混合使用Compose
- 在安卓中壓縮GIF的幾種方法(附例項程式碼)
- 魔改車鑰匙實現遠端控車:(1)整體思路及控制方案實現
- 跟我一起使用 compose 做一個跨平臺的黑白棋遊戲(3)狀態與遊戲控制邏輯
- 跟我一起使用 compose 做一個跨平臺的黑白棋遊戲(2)介面佈局
- 以不同的形式在安卓中建立GIF動圖
- 跟我一起使用 compose 做一個跨平臺的黑白棋遊戲(4)移植到compose-jb實現跨平臺
- 羨慕大勞星空頂?不如跟我一起使用 Jetpack compose 繪製一個星空背景(帶流星動畫)
- 魔改車鑰匙實現遠端控車:(4)基於compose和經典藍芽編寫一個控制APP