步驟1:什么是I2C-1
I2C(內部集成電路總線),最初是由Phillips(現為NXP Semiconductor)開發的,并且通常由Atmel和其他有條件的公司稱為TWI(兩線接口) 兩線式同步串行總線是我們不想涉及的商標問題。讓我們看一下每個單詞的含義:
兩根線-這很簡單,I2C使用兩根線(當然,除了地線!),它們被稱為 SDA (串行數據)和 SCL (串行時鐘)。它們以漏極開路配置進行接線,這意味著所有連接設備的輸出不能直接輸出邏輯電平1(高電平),而只能拉低(接地),輸出低電平。 0)。為了使線路變高,所有設備都釋放其對線路的拉力,并在線路和正軌之間的上拉電阻器將電壓上拉。好的上拉電阻應為 1-10K歐姆,該電阻應足夠低,以至于所有設備都可以將信號視為高電平,但又足夠高,可以很容易地將其短路(拉低),而不會導致損壞或大量用電。 SDA上有一個上拉電阻,SCL上有一個上拉電阻。
同步-這意味著數據傳輸通過存在于所有連接的時鐘信號進行同步設備。這是由主機生成的。相比之下,異步串行系統沒有時鐘信號。相反,它使用預定的時基或波特率。異步串行的一個示例是RS-232(許多計算機上的通用串行端口)。
串行-串行傳輸的數據表示一次通過一根電線傳輸一位數據。相比之下,并行數據傳輸有多條導線,每條導線都包含一個位,所有這些導線都被一次采樣以并行傳輸多個位。
總線-總線是一種允許許多設備執行以下操作的系統:通過單條電線相互通信。盡管可以將其稱為總線,但是USB在硬件級別上并不是真正的總線,因為連接多個設備需要集線器。諸如I2C之類的總線允許簡單地通過將它們的SDA和SCL連接連接到現有線路來添加新設備。總線(I2C,USB,PCI等)均使用尋址系統,其中每個設備都具有唯一地址。在這種情況下,地址只是一個二進制數,到該設備的所有消息都必須使用該地址發送。
步驟2:什么是I2C-2
在I2C總線上,有主機和從機。主機啟動連接,而從機必須等待主機尋址才能發送或接收任何內容。 I2C具有多主機功能,這意味著可能存在多個主機,并且如果兩個主機同時嘗試傳輸,則它們必須執行仲裁以糾正問題。本教程不會介紹多主機配置,但應注意它們確實存在。
主機可以請求從主機發送或接收數據。在發送期間,主機將數據寫入總線,而從機則從總線讀取數據并將其存儲在其內存中。在接收期間,主機讀取總線上從機發送的數據。在這兩種情況下,主設備都會在SCK上提供時鐘信號。
在I2C總線上傳輸的每個字節(即8位)的末尾,接收設備必須提供一個確認(ACK)。唯一不會發生這種情況的時間是主機從從機接收數據時,主機會以不確認(NACK或NAK)結束傳輸,指示從機應停止發送數據。 ACK由低(下拉或0)狀態表示,而NACK由高(下拉或1)狀態表示。由于總線的默認狀態為高,因此ACK確認存在另一個設備并已成功處理了傳輸。
除了ACK和NACK之外,I2C還具有兩個另外的成幀條件,稱為起始條件和停止條件。主機發送開始條件以指示傳輸開始。在開始轉換期間,SDA線首先從高電平轉換為低電平,然后在一段明顯的時間后,SCL執行相同的操作。主機在傳輸結束時發出的停止條件是相反的。首先,SCL線從低變高,然后,SDA執行相同的操作。請注意,當總線不活動時,SDA和SCL線都為高電平。
I2C傳輸中的第一個字節是地址字節。這是由主機發送的,用于確定與哪個從機通信以及是否執行發送或接收(分別稱為寫和讀)。從機地址是7位長,并且有幾個保留地址。這樣的保留地址之一是0x00,通常被認為是全局寫入(寫入所有從站)。通常,您可以通過將地址選擇引腳連接到高電平或低電平來配置從設備的地址,盡管在微控制器上可以像在ATTiny2313上那樣通過編程方式設置地址。地址字節的最低有效位是“讀/寫”位,它指示執行讀還是寫操作。如果為1,則該操作為讀取,如果為0,則為寫入。
步驟3:什么是I2C-3
基本上涵蓋了I2C協議本身,因為主機可以啟動讀取或寫入,然后繼續傳輸,直到主機發送停止條件為止。當主機從從機讀取數據時,它將在停止條件之前在最后一個字節上發出NACK而不是ACK,以指示已完成接收。從這里開始,通過正確的實施,您可以與所需的所有設備進行通信。但是,我還需要指出一件事,因為它經常使用。
在某些I2C設備上(或者我應該說最多,這很常見),訪問協議設置為注冊銀行。要從這些寄存器讀取或寫入,必須首先編寫一個內部地址,該地址是您希望讀取或寫入的寄存器的地址。寫入內部地址后,您可以讀取或寫入多個字節,并且內部地址將隨每個字節遞增。這是幾乎所有I2C存儲設備以及大多數傳感器和I/O擴展器的首選協議。雖然可能有一個不遵循寄存器組協議的協議,但絕大多數設備都遵循該協議,并且圍繞它構建了許多I2C工具。因此,值得指出。這也是我將在ATTiny2313上實現的協議。
如前所述,在讀取或寫入任何寄存器之前,您必須發送設備內部地址,這是通過執行以下操作來完成的:一個字節,其中包含內部地址。對于寫操作,傳輸可以繼續進行數據值,數據值中的第一個將存儲在所需的地址中,并且任何其他字節每次都將遞增一個。對于讀取,主機將發送停止條件,然后開始新的傳輸以進行讀取。這是因為您不能在同一傳輸中同時進行寫操作和讀操作。在某些情況下,可以發送重復開始而不是先停止再開始。 重復開始是SCL為高時SDA上的從高到低過渡。
步驟4:ATTiny USI I2C代碼實現-概述
在本教程的這一點上,您至少應該基本熟悉I2C協議。現在,我將詳細介紹ATTiny USI硬件的實際I2C協議實現。
為此,本步驟主要圖片中顯示的板是定制的單極步進電機控制器,我將其設計為自己的一部分。今年春季的高級項目。該板能夠以PWM,可變速度和三種不同的步進模式(單步,功率步進和半步)驅動單個單極步進電機。它也可以突發運行,因為控制器將僅以給定的步數運行電動機。對于連續操作,步進計數器必須在其達到零之前通過驅動板的任何東西重新加載。這對本教程來說都不重要。
這里的重要部分是這些板提供動力的機器人具有三個輪子(全向輪子,以三角形模式排列)。我想構建三個相同的板,但僅使用機器人主計算機(筆記本電腦)中的單個RS-232串行接口來控制全部三個。我想到的想法是使用串行端口連接計算機接口和I2C總線來連接所有三個板。在此設置中,連接到PC的板除了充當從節點之外,還承擔著主角色。然后,PC將I2C格式的消息發送到總線上,以使這三塊板得以運行。
對于此任務,我的板將必須支持兩種 I2C模式,并且必須能夠同時使用 slave 和 master ,具體取決于串行端口的操作。一般而言,我對USI硬件了解甚少,而對I2C協議了解甚少,因此我著手掌握I2C協議,使其成為我的從屬設備并命令它進行數據傳輸。而且我做到了,并且在該項目中效果很好。
直到我至少擁有Raspberry Pi為止,因為當我終于開始玩Pi時,我嘗試將I2C電機板連接到其I2C端口以嘗試使用Pi驅動的機器人。不幸的是,無論我發送了什么命令,Pi都無法進行通信。由于我從未在自己的主代碼與自己的從代碼進行對話之外驗證過協議,因此我認為自己并不是在正確地實現協議,因此坐下來使其全部正常工作。我做到了,新代碼更加精簡,井井有條,而且易于理解(任何想學習的人都贊不絕口!)。由于進入I2C世界的旅程很艱難,因此我決定在此處發布消息,以供所有人查看,并盡我所能詳細說明I2C的功能。
在接下來的幾步中,我將討論USI硬件及其作為主機和從機的工作方式。我還附加了我的USI代碼文件。我希望人們擁有良好的USI實現,也希望他們閱讀它的工作原理,確切地了解發生的情況對于處理復雜的低級系統至關重要,因此,我對文件進行了完整的評論。
步驟5:ATTiny USI I2C代碼實現-USI硬件
因此,在看代碼之前,讓我們看一下數據表即可。具體來說,我正在查看ATTiny2313數據表,因為這是我使用的芯片,但是在許多不同的ATTiny型號中都可以找到相同的USI硬件。請注意,芯片之間的輸出引腳可能有所不同,但否則硬件的工作方式相同,寄存器也相同。
USI硬件具有三個引腳:
DO-數據輸出,僅用于三線(SPI)通信模式
DI/SDA-數據輸入/串行數據,在I2C配置中用作SDA
USCK/SCL-時鐘,在I2C配置中用作SCK
,USI硬件具有三個寄存器:
USIDR-USI數據移位寄存器-將數據移入和移出USI硬件
USISR-USI狀態寄存器-具有狀態標志和 4位計數器(詳情請參見下文)
USICR-USI控制寄存器-具有中斷允許,時鐘模式和軟件時鐘選通功能
4位計數器 《該4位計數器占用USISR的低4位,用于在從模式下操作時為溢出中斷計時,并幫助在主模式下生成SCK時鐘脈沖。作為4位計數器,它在溢出之前從0遞增到15。溢出時,它可以觸發中斷(USI_OVERFLOW_vect,由USICR中的某個位啟用)。當USI從狀態表在傳輸狀態之間切換時,它用于跟蹤傳輸(有關詳細信息,請參見從代碼部分)。
當用作主設備時,4位計數器與計數器一起使用。 USICR中的時鐘選通位產生SCK時鐘。您將計數器設置為希望產生的時鐘脈沖數溢出(通常為8或1,其中8為數據傳輸,1為ACK/NACK傳輸)。然后循環,直到計數器溢出,連續設置時鐘選通位并執行延遲等待。有關更多信息,請參見主代碼部分。
兩線制時鐘控制單元(啟動條件檢測器)
TWI時鐘控制單元是USI中的模塊,用于監視SCK。開始和停止條件的行。它的主要目的是啟動條件檢測器,啟用該功能后,只要檢測到有效的啟動條件,就會生成USI_START_vect中斷。此中斷處理程序是從機模式USI I2C傳輸處理的起點,并且必須將4位計數器設置為在發生地址傳輸后溢出。從那以后,溢出中斷將管理該I2C消息的其余部分,并為下一條消息重置啟動條件檢測器。
閱讀數據表
我將不對每個消息進行詳細介紹這些寄存器中每個位的位數,但是如果您要編寫一些USI代碼,則必須閱讀數據表的這些部分。我建議閱讀整個通用串行接口-USI部分(ATTiny2313完整數據表的第142-150頁)。除了我在這里指出的內容之外,這還將為您提供所有您需要的信息。
步驟6:ATTiny USI I2C代碼實現-USI I2C主控
USI I2C Master(usi_i2c_master.c/h)庫使用USI硬件提供I2C master模式功能。用戶應該熟悉兩個重要功能。第一個是初始化功能,用于設置SDA/SCL引腳和USI硬件,第二個是傳輸功能,該功能對I2C消息執行讀取或寫入操作,返回1(如果成功,則返回true(真),如果發生錯誤(接收到NACK),則返回0(假)。收發器功能使用第三個功能傳輸功能來發送和接收數據。傳遞函數不應在usi_i2c_master庫的外部使用。
傳遞函數帶有兩個參數。第一個是指向數據緩沖區的指針,數據將在其中發送或接收。假定該緩沖區的第一個字節是ADDRESS + R/W字節(高7位地址,LSB是R/W)。根據協議,該字節始終被發送,而從未被接收。其余的緩沖區將根據R/W位發送出去或由接收到的數據填充(讀為1,寫為0)。第二個參數是緩沖區的總大小(包括地址字節)。
這是一個簡短的示例。假設我們要將值 0x70 輸出到地址為 0x40 的設備的內部地址 0x12 。首先,我們必須創建一個緩沖區來存儲我們的傳輸:
char i2c_transmit_buffer [3];
char i2c_transmit_buffer_len = 3;
i2c_transmit_buffer [0] =(0x40 《《1)| 0//不需要與0進行“或”運算,但為清楚起見,這會將R/W位置1進行寫操作。
i2c_transmit_buffer [1] = 0x12;//內部地址
i2c_transmit_buffer [2] = 0x70;//要寫入的值
//傳輸I2C消息
USI_I2C_Master_Start_Transmission(i2c_transmit_buffer,i2c_transmit_buffer_size);
有,消息傳輸完成!那很簡單!如果您想了解有關USI_I2C_Master代碼的內部工作的更多信息,只需瀏覽 usi_i2c_master.c 文件,在其中我對狀態,傳遞函數和其他有趣的部分進行了注釋。我使用了單行#define宏,以便更清楚地說明每行的用途。
下一步,我將介紹從屬模式代碼,該代碼要復雜得多,但也易于使用從最終用戶的角度來看。我采用了另一種方法來實現從屬代碼,這是我在其他任何教程中都沒有看到的,這很有趣且有用!
步驟7:ATTiny USI I2C代碼實現-USI I2C從站
與主代碼不同,USI I2C從站代碼(usi_i2c_slave.c/h)使用USI中斷幾乎完全實現。如前所述,USI模塊有兩個中斷,一個中斷是在檢測到START條件時產生的,另一個是基于4位計數器的溢出而產生的。計數器對于從屬代碼正常工作至關重要,在我閱讀的教程和代碼中并沒有很好地說明。在流程圖中,我注意到了邏輯中每個狀態的數字。這些數字(8、1和0)是計數器計數值,指示在轉換到下一個狀態之前計數器應計數的滴答數。由于使用SCL時鐘為計數器提供時鐘,因此這些值表明在下一個狀態之前必須發生多少個SCL時鐘脈沖。通常,等待8個時鐘脈沖的事物正在等待數據字節的發送/接收,而等待1個時鐘脈沖的事物正在等待ACK/NACK的發送/接收。有些事情等待0個時鐘,這意味著它們會立即繼續到下一個狀態,或者是前一個狀態的擴展部分(對于Write或Read?)。
因此,作為最終用戶,您可能是對如何將庫與自己的代碼接口更感興趣!這簡單,這就是原因。我已經取消了其他USI I2C實現(主要是基于AVR312應用筆記的實現)中使用的接收/發送緩沖區,而是實現了本教程開始時所述的注冊銀行協議 。銀行存儲為指針數組,而不是數據值,因此您必須在代碼中附加局部變量到I2C寄存器組中的存儲器地址設置指針以指向變量。這意味著您的主線代碼不必輪詢I2C緩沖區或處理數據到達,只要它們到達,這些值就會立即更新。它還允許通過I2C接口在任何時間輪詢程序變量,而不會影響主代碼(除了由于中斷引起的延遲之外)。這是一個非常整潔的系統。讓我們再舉一個簡短的例子。例如,我們有一個非常基本的軟件PWM發生器來驅動LED。我們希望能夠在不使主循環復雜的情況下更改PWM值( 16位值,僅用于學習指針)。借助異步I2C從設備的魔力,我們可以做到這一點!
#include“ usi_i2c_slave.h”
//定義對I2C從設備寄存器組指針數組的引用extern char * USI_Slave_register_buffer [];
int main()
{
//創建16位PWM值無符號int pwm_val = 0;
//將pwm值低字節分配給I2C內部地址0x00
//將pwm值高字節分配給I2C內部地址0x01
USI_Slave_register_buffer [0] =(unsigned char *)&pwm_val;
USI_Slave_register_buffer [1] =(unsigned char *)(&pwm_val)+1;
//使用從設備地址為0x40的I2C從設備初始化
USI_I2C_Init(0x40);
//將引腳A0設置為LED的輸出(我們假設所使用的任何芯片都具有引腳A0)
DDRA | = 0x01;
while(1)
{
PORTA | = 0x01;//為(unsigned int i = 0; i {
PORTA&=?(0x01);//關閉LED燈
}
}
}
就可以了!主循環完全不引用I2C,但是將PWM值發送到I2C內部地址0x00/0x01中的16位位置后,我們可以完全控制LED的PWM!為了增加穩定性(確保僅使用您正在使用的指針值并防止出現雜散指針),我建議您將#define USI_SLAVE_REGISTER_COUNT更改為所需的寄存器指針的數量,不要更多,不少。嘗試對范圍從0x00到USI_SLAVE_REGISTER_COUNT-1之外的寄存器索引進行訪問(讀取或寫入)時,不寫入任何內容,并且返回零。
步驟8:ATTiny USI I2C代碼實現-編寫代碼!
要獲取(git)代碼,請轉到我的GitHub頁面!該代碼是我的高級項目步進電機控制器的一部分,因此您可以使用I2C驅動程序檢查我對電機控制器的實現。我也有一個不錯的中斷驅動的USART串行驅動程序,您也可以在其中使用。
https://github.com/CalcProgrammer1/Stepper-Motor-Controller/tree/master/UnipolarStepperDriver
請注意,名稱由于我實施的錯誤修復和更新,I2C驅動程序中使用的功能和功能可能會略有不同。如果有什么嚴重的問題,我將編輯該教程,但是現在它應該非常準確。
現在,您已經掌握了I2C的知識,那么您就可以準備開始討論任何東西!借助I2C的總線設計,您可以將許多設備(理論上最多為128,但受地址限制的限制)連接到網絡!
責任編輯:wv
-
I2C
+關注
關注
28文章
1490瀏覽量
124005
發布評論請先 登錄
相關推薦
評論