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

評論