這篇文章來源于DevicePlus.com英語網站的翻譯稿。
今天,我們將討論arduino通信協議的有關內容。設備往往需要相互通信以中繼所處環境相關信息,顯示其狀態變化,或請求執行輔助操作。在進行任何電子設備愛好相關研究時,您將需要使用多個不同的傳感器或多個不同的模塊(如ESP8266),屆時必然會遇到一個或多個主流的通信協議。在本教程中,我們將會介紹電子設備使用的標準通信協議,并使用Arduino Uno對其進行詳細說明。
二進制數字系統
設備間的通信通過數字信號進行。在討論通信協議之前,我們將首先討論如何傳輸這些信號。
在數字信號中,數據由一系列的高電平到低電平或低電平到高電平的脈沖進行傳輸,并且切換非常迅速。數字信號中的高電平和低電平分別代表1和0,當按照順序連在一起時,就攜帶了可由微控制器編譯的信息。聽說過位和字節嗎?這些1和0是位,當它們以8個為一組時,就稱之為位,當它們以8個為一組時,就稱之為字節!
一個字節看起來大概是這樣:10111001
事實證明,這個由8位組成的序列代表了一個數字,就像597這個數字代表了597一樣。每個數字占據了一個位置值,該位置值中的1或者0表示該位置值被計數了多少次。
在597的示例中,5表示有5個100,9表示有9個10,7表示有7個1。加在一起,就表示5個100+9個10+7個1(500 + 90 + 7)……或597。因為1是100 ,10是101,100是102等等,因此這叫做基于10的系統!在基于10的系統中,每個數字的取值范圍為0到9(0到10-1)。
現在我們再來看10111001。我們可以看出這是基于2的系統,因為每個數字的取值范圍為0到1。我們可以說每個數字都是2的冪,這意味著10111001實際為1 * 27 + 0 * 26 + 1 * 25 + 1 * 24 + 1 * 23 + 0 * 22 + 0 * 21 + 1 * 20,或者說為基于10的系統中的185。
可以想象,您可以擁有基于任何數字的數字系統!常見的一些數字為2、8、10和16。為了簡單起見,數學家為這些常見的數字系統命了名——基于2的為二進制,基于8的為八進制,基于10的為十進制,基于16的為十六進制。每個數字系統都遵循相同的原理:每個數字代表該基數的冪被計數的次數,并且每個數字的值都只能在0到(基數-1)之間。
了解不同的基數系統很有用,因為字節和數據通常以不同的方式進行表達。可以看出,寫B9(十六進制)比寫10111001(二進制)容易。在軟件中,二進制數以0b作為前綴,八進制數以0作為前綴,十六進制數以0x作為前綴。十進制數沒有前綴。
知道如何在基數之間進行轉換也很有用,因為一些很酷的數學技巧通常用二進制數進行表達。但是,出于本教程的目的,我們僅講述到這里。請查看以下本教程的附錄來獲取有關這些技巧的文章!
3 種協議:UART,SPI和I2C
電氣工程行業使用三種通信協議對電子設備進行標準化,以確保設備之間的兼容性。將設備以幾種協議為中心進行標準化,意味著設計者可以通過掌握每個通信協議的一些基本概念實現任何設備之間的交互。UART,SPI和I2C這三種協議的實現方式不同,但都會達到相同的目的:將數據高速傳輸到任何兼容的設備上。
1. UART
我們將要介紹的第一個通信協議是通用異步收發器(UART)。UART是一種串行通信,因為數據是一位一位依次進行傳輸的(我們將在稍后介紹)。設置UART通信的接線非常簡單:一根用于傳輸數據(TX)的線,另一根用于接收數據(RX)的線。如您所料,TX線用于發送數據,RX線用于接收數據。使用串行通信的設備的TX線和RX線將一起形成一個串行端口,通過該端口可以進行通信。
圖1:UART的硬件連接圖
UART一詞實際上是指管理串行數據打包和轉換的板載硬件。如果希望設備能夠通過UART協議進行通信,就必須具有該硬件!在 Arduino Uno上,有一個專用于與Arduino所連接計算機之間進行通信的串行端口。對!通用串行總線USB正是一個串行端口!在Arduino Uno上,USB的連接通過板載硬件配置為成兩個數字引腳GPIO 0和GPIO 1,可用于涉及與計算機以外的電子設備進行串行通信的項目。
圖2:GPIO 0 和GPIO 1是串口RX和TX。任何GPIO引腳都可以通過SoftwareSerial庫用作串口RX或TX。
您還可以使用SoftwareSerial Arduino 庫(SoftwareSerial.h)將其他GPIO引腳用作串口RX和TX線。
UART之所以成為異步,是因為不使用試圖相互通信的兩個設備之間的同步時鐘信號進行通信。由于通信速率不是通過這種穩定信號定義的,“發送方”設備無法確定“接收方”設備是否獲取了正確的數據。因此,設備將數據分成了固定大小的塊,以確保接收到的數據與發送的數據相同。
UART數據包如下所示:
圖3:UART數據包視圖/ ?electric imp
通過UART進行通信的設備會發送預定義大小的數據包,其中包含有關消息的開始與結束,以及確認消息是否接收的附加信息。例如,為了開始通信,發送設備將發送線拉低,指示數據包開始發送。然而,目前遇到的問題是,與同步通信方式相比,UART速度較慢,因為傳輸的數據只有一部分用于設備的應用程序(其余部分用于通信本身!)。
在大多數嵌入式平臺(例如Arduino)上實現UART串行通信時,用戶不用在位的數據級別上進行通信,平臺通常提供更高級別的軟件庫,用戶只需在這些軟件庫中處理通信內容即可。在Arduino平臺上,用戶可以使用Serial和SoftwareSerial庫為自己的項目實現UART通信。
以下是有關Arduino Serial和SoftwareSerial的初始化和使用的C++簡要參考。
Serial 和 SoftwareSerial 方法(method) | 目的 | 代碼 | 釋義 |
Constructor (僅SoftwareSerial) | 定義GPIO引腳為UART RX線和TX線。 | SoftwareSerial comms (2 , 3); | 定義GPIO 2 上的RX線與GPIO 3上的TX線為串行連接 |
begin | 定義串行連接的波特率(傳輸速度)在范圍4800~115200之間 | comms.begin(9600); | “comms”串行端口上的通信將以9600波特率的速度進行 |
通過串行連接將字節數據轉換為可閱讀的字符 | comms.println(“Hello World”); | 寫入等效于Hello World (可讀字符)的字節 | |
write | 通過串行連接寫入原始字節數據 | comms.write(45); | 寫入值為45的字節 |
available | 當數據可通過串行連接獲取時評估為真(true) | if (comms.available()) | 如果可獲取通過串行連接讀取的數據,執行if語句 |
read | 讀取從串行連接獲得的數據 | comms.read(); | 從串行連接讀取數據 |
有關UART通信,有一個重要的點需要注意,該通信協議下一次只能實現兩個設備之間的通信。因為該協議僅發送指示消息的開始、消息內容和消息的結束的位,所以沒有辦法區分同一條線路上的多個發送和接收設備。如果有多個設備嘗試在同一條線路上傳輸數據,則會發生總線爭用,并且接收設備很可能會接收無法使用的垃圾數據!
此外,UART是半雙工的,這意味著即使可以在兩個方向上進行通信,兩個設備也無法同一時間相互傳輸數據。例如,在一個項目中,兩個Arduino通過串行連接相互通信,這意味著在給定的時刻,只有其中一個Arduino可以與另一個“交流”。對于大多數應用來說,這一特點相對來說并不重要,并且不會產生不利影響。
2. SPI
我們將介紹的下一個通信協議是串行外圍設備接口(SPI)。SPI與UART主要有以下不同點:
? 同步
? 遵循主從模式,包含一個主設備和多個從設備
? 應用中需要兩條以上的線
SPI的硬件連接圖稍微復雜一些,看起來像這樣:
圖4:SPI的硬件連接圖
MOSI (“Master Out Slave In”): 從主設備到從設備的數據傳輸線
SCK (“Clock”): 定義了傳輸速率和傳輸開始/結束特性的時鐘線
SS (“Slave Select”): 用于主設備選擇進行通信的從設備的線
MISO (“Master In Slave Out”):從設備到主設備的數據傳輸線
SPI的第一個特點是遵循主從模型。這意味著通信中將會有一個設備為主設備,而其他設備為從設備。在該模式下,會在設備之間創建層次結構,從而顯示出哪個設備有效地“控制”了其他設備。我們會在闡述一個主從設備之間的通信示例時簡單地討論一下此主從模型。
前面我們提到,多個從設備可以連接到一個主設備。這種系統的硬件圖如下所示:
圖5:連接到一個主設備的多個從設備
SPI不需要為連接到主設備的每個從設備提供單獨的發送線和接收線。在所有從設備和主設備之間連接了一條公共接收線(MISO)和一條公共發送線(MOSI),以及一條公共時鐘線(SCK)。主設備通過每個從設備分別配置的SS線來決定將與哪個從設備進行通信。這意味著每增加一個與主設備通信的從設備,都需要在主設備一側再使用一個GPIO引腳。
SPI是同步的,也就是說主設備和從設備之間的通信與主設備定義的時鐘信號(固定頻率的方波)緊密相關。從這里我們可以看出主從模型的直接影響之一,即主設備通過時鐘信號指定通信速率來驅動通信,而從設備在該速率下進行通信來響應主設備。所定義的速率適用于主設備所主導的任何通信過程(在從設備可以承受的最大速率范圍內)。
在SPI中,時鐘信號的兩個特征決定了數據傳輸的開始和結束:時鐘極性(CPOL)和時鐘相位(CPHA)。CPOL是指時鐘信號的空閑狀態(低電平或高電平)。為了節省功耗,設備在不與任何從設備通信時會將時鐘線置于空閑狀態,并且在該空閑狀態下可用的兩個選項為低電平或高電平。CPHA是指時鐘信號的跳變沿,決定何時對數據進行采樣。方波有兩種跳變沿(上升沿和下降沿),并且根據CPHA設置,可以對上升沿或下降沿進行采樣。
CPOL和CPHA有四種不同的組合方式,如下表所示:
CPHA = 0 (時鐘信號的“第一種跳變沿”) |
CPHA = 1 (時鐘信號的“第二種跳變沿”) |
|
CPOL = 0 (空閑狀態為0) |
“SPI 模式 0” ? 時鐘第一次從0(空閑)變為1時開始采樣。 ? 數據采樣發生在時鐘信號的上升沿(0→1) |
“SPI 模式 1” ? 時鐘第一次從0(空閑)變為1時開始采樣。 ? 數據采樣發生在時鐘信號的下降沿 (1→0) |
CPOL = 1 (空閑狀態為 1) |
“SPI 模式 2” ? 時鐘第一次從1(空閑)變為0時開始采樣。 ? 數據采樣發生在時鐘信號的上升沿(0→1) |
“SPI 模式 3” ? 時鐘第一次從1(空閑)變為0時開始采樣。 ? 數據采樣發生在時鐘信號的下降沿(1→0) |
維基百科以圖形方式很好地表示出了CPOL和CPHA的設置與其所傳輸數據的關系!
圖6:CPOL和CPHA的設置視圖/ ?Wikipedia
現在,我們將重點介紹使用Arduino作為主設備(SPI.h)在Arduino上實現SPI的方法。SCK、MOSI和MISO的SPI數字引腳連接要在Arduino開發板上進行預定義。對于Arduino Uno,連接如下:
SCK: GPIO 13 或 ICSP 3
MOSI: GPIO 11 或 ICSP 4
MISO: GPIO 12 或 ICSP 1
SS: GPIO 10
任何數字引腳都可以作為SS引腳。為了選擇設備,該數字引腳必須被驅動為低電平。
圖7:MOSP、MISO和SCK引腳分配在ICSP接頭以及GPIO 11、GPIO 12 和GPIO 13上
以下是有關Arduino SPI初始化和使用的C++簡要參考。
SPI 方法(Method) | 目的 | 代碼 | 釋義 |
Constructor | 定義時鐘速率、數據位順序(最高有效位在前或最低有效位在前)以及SPI模式 |
SPI.beginTransaction (SPISettings(14000000, MSBFIRST, SPI_MODE0)); |
在SPI模式0定義14MHz下的SPI連接,并以最高有效位在前的方式進行數據傳輸 |
digitalWrite | 選擇連接到該GPIO引腳的從設備 | digitalWrite(10, LOW); | 將GPIO引腳驅動為低電平以選擇從設備。然后將引腳驅動為高電平以取消選擇從設備。 |
transfer | 將字節傳送到所選擇的從設備 | SPI.transfer(0x00); | 發送值為0的字節 |
endTransaction | 結束SPI程序(應在SS線上調用digitalWrite(high)之后調用) | SPI.endTransaction(); | 結束SPI程序 |
SPI是全雙工的,這意味著即使在應用中只要求在一個方向上進行數據傳輸,通信也始種是雙向進行的。SPI全雙工數據傳輸通常通過移位寄存器來實現。(請參閱附錄中有關移位寄存器的教程!)。這表明在讀取一個位時,前面的位將被移動一位,隨后,最前邊的位將會被觸發,并通過SPI連接發送到另一臺設備上!
3. I2C
內部集成電路總線(I2C)的發音為“I方C”,是我們在本教程中介紹的最后一個通信協議。雖然該協議的實現是三種協議中最復雜的,但是I2C解決了其他通信協議中存在的一些問題,使其在某些應用程序中比其他通信協議更具有優勢。這些優勢包括:
能夠實現多個主設備與多個從設備之間的連接
同步(同SPI),具有更高的通信速率
簡易性:僅需要兩根線和一些電阻即可實現
從硬件層面來說,I2C是一種兩線接口—I2C連接中僅需要的兩根線是數據線(成為SDA)和時鐘線(稱為SCL)。數據線和時鐘線在空閑狀態下被拉高,而在需要通過連接發送數據時,這些線會通過一些MOSFET電路被拉低。在本教程中,我們將不討論I2C電路內部的MOSFET操作,這里需要說明的一個重要點是該系統是漏極開路的,即線路只能由設備驅動為低電平。因此,在項目中使用I2C時,上拉電阻(通常為4.7k?)這一步至關重要,以確保在空閑狀態下線路確實被拉高。
圖8:I2C硬件連接圖
I2C具有其獨特性,因為它通過尋址解決了與多個從設備之間的接口問題。與SPI通信一樣,I2C利用主從模型建立了通信的“層次結構”。但是,主設備不是通過單獨的數字線路選擇從設備,而是通過主設備中具有唯一性的字節地址來選擇從設備,這種字節地址大概類似于這樣:0x1B。這意味著將從設備連接到主設備不再需要添加數字線路。只要每個從設備都有唯一性的地址,應用程序就可以區分這些地址所對應的從設備。您可以將這些地址視為名稱。要調用從設備的功能,主設備僅調用其名稱即可,而只有具有該名稱的從設備會發生響應。
I2C通信線路中的地址和對應數據看起來像下圖這樣。
圖9:通信中的地址與對應數據樣圖/ ?tessel.io
請注意通信線上的ACK 和 NACK請注意通信線上的ACK和NACK位。這些位表示被尋址的從設備是否響應通信—這是一種定期檢查通信是否按照預期進行的方法。這些位當然與發送的地址位或數據位無關,但是在復雜的通信體系中,與包含許多開始和結束位并會發生停頓的類似UART這樣的協議相比,它們只增加了非常少的額外時間而已。
I2C 使從設備可以自由決定通信請求的方式。向不同的從設備寫入或發出請求需要在SDA線以不同的順序寫入不同的字節。例如,在某些加速計模塊中,在讀取請求發送之前,需要寫入指示主設備所要讀取的硬件寄存器的字節。對于這些規格,用戶需要參考從設備數據手冊中的設備地址、寄存器地址和設備設置。
在Arduino上,I2C通過Wire 庫(Wire.h)實現應用。Arduino可以配置為一個I2C主設備或從設備。在Arduino Uno上的連接如下所示:
SDA: 模擬引腳 4
SCL: 模擬引腳 5
圖9:I2C(Wire)SDA為模擬引腳4(A4),SCL為模擬引腳5(A5)
以下是有關Arduino I2C初始化和使用的C++簡要參考。
I2C (線) Method | 目的 | 代碼 | 釋義 |
begin | 啟動庫,并以主設備或從設備的身份加入I2C 總線。 | Wire.begin(); | 以主設備的身份加入I2C 總線。如果將地址指定為方法的參數,則Arduino將以該地址作為從設備加入總線。 |
beginTransmission | 對于配置為I2C 主設備的Arduino:啟動與具有給定地址的從設備之間的傳輸。 | Wire.beginTransmission(0x68); | 開始對具有十六進制地址Ox68的從設備進行傳輸。 |
write | 通過I2C總線寫入字節數據。 | Wire.write(0x6B); | 通過I2C總線寫入值Ox68的字節數據。 |
requestFrom |
向具有給定地址的從設備請求指定值的字節;可以選擇釋放I2C線或將其保留以進行進一步的通信。 所請求的字節被放入緩沖區,隨后通過Wire.read()調用讀取。 |
Wire.requestFrom(0x68, 6, true); |
向地址為Ox68的從設備請求6個字節。請求完成后釋放I2C線。 如果最后一個參數為false,則Arduino將保留I2C線以進行進一步的通信,而不允許其他設備通過該線進行通信。 |
read |
從設備發送數據后,讀取放入緩沖區的字節。 該方法將在調用 Wire.requestFrom之后調用。 |
Wire.read(); |
從緩沖區讀取一個字節(在調用requestFrom之后)。要從緩沖區讀取兩個字節,必須兩次調用該方法。 例: Wire.requestFrom(0x68, 2, true); Wire.read(); Wire.read(); |
endTransmission | 結束當前傳輸;可以選擇釋放I2C線或將其保留以進行進一步的通信。 | Wire.endTransmission(true); | 結束傳輸并釋放I2C線。 |
只需將SDA和SCL線連接到總線上,就可以通過I2C總線連接多個主設備。但是,一次只能有一個主設備與從設備通信,因為讓多個設備進行相互通信會導致總線爭用。同樣,不能同時進行主設備到從設備和從設備到主設備之間的雙向通信,因為這也會導致總線爭用。這使得I2C變成了半雙工,就像UART那樣!
總之,多個主設備無法通過同一個I2C總線實現相互通信。在將多個主設備連接到從設備的應用中,主設備可以通過單獨的總線或單獨的通信協議實現相互之間的通信。
如果您學完了本教程,那么應該已經擁有所有使用UART、SPI和I2C通信協議所需的工具了!查看我們的一些Arduino項目,可以獲取有關使用這些協議的更多示例!
審核編輯:湯梓紅
-
通信協議
+關注
關注
28文章
915瀏覽量
40439 -
I2C
+關注
關注
28文章
1495瀏覽量
124554 -
uart
+關注
關注
22文章
1243瀏覽量
101768 -
Arduino
+關注
關注
188文章
6477瀏覽量
187818
發布評論請先 登錄
相關推薦
評論