一、 軟件平臺與硬件平臺
軟件平臺:
1、操作系統(tǒng):Windows-8.1
2、開發(fā)套件:ISE14.7
3、仿真工具:ModelSim-10.4-SE 、ChipScope
硬件平臺:
1、 FPGA型號:Xilinx公司的XC6SLX45-2CSG324
2、 EEPROM型號:Microchip公司的AT24LC04B
二、 原理介紹
IIC(Inter-Integrated Circuit)總線是一種由PHILIPS公司開發(fā)的兩線式串行總線,用于連接微控制器及其外圍設(shè)備。I2C總線產(chǎn)生于在80年代,最初為音頻和視頻設(shè)備開發(fā),如今主要在服務(wù)器管理中使用,其中包括單個組件狀態(tài)的通信。例如管理員可對各個組件進行查詢,以管理系統(tǒng)的配置或掌握組件的功能狀態(tài),如電源和系統(tǒng)風(fēng)扇。可隨時監(jiān)控內(nèi)存、硬盤、網(wǎng)絡(luò)、系統(tǒng)溫度等多個參數(shù),增加了系統(tǒng)的安全性,方便了管理。IIC數(shù)據(jù)傳輸速率有標準模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些變種實現(xiàn)了低速模式(10 kbps)和快速+模式(1 Mbps)。
下圖是一個嵌入式系統(tǒng)中處理器僅通過2根線的IIC總線控制多個IIC外設(shè)的典型應(yīng)用圖
圖中處理器是IIC主機,它僅僅通過兩根信號就可以控制IO擴展器,各種不同的傳感器,EEPROM,AD/DAs等設(shè)備,這也是IIC總線協(xié)議相較于其他協(xié)議最有優(yōu)勢的地方。
IIC總線的特點:
1、 簡單性和有效性。由于接口直接在組件之上,因此I2C總線占用的空間非常小,減少了電路板的空間和芯片管腳的數(shù)量,降低了互聯(lián)成本。總線的長度可高達25英尺,并且能夠以10Kbps的最大傳輸速率支持40個組件。
2、 支持多主控(multimastering), 其中任何能夠進行發(fā)送和接收的設(shè)備都可以成為主總線。一個主控能夠控制信號的傳輸和時鐘頻率。當(dāng)然,在任何時間點上只能有一個主控占用IIC總線。
IIC總線協(xié)議詳解:
IIC總線接口是一個標準的雙向傳輸接口,一次數(shù)據(jù)傳輸需要主機和從機按照IIC協(xié)議的標準進行。I2C總線是由數(shù)據(jù)線SDA和時鐘SCL構(gòu)成的串行總線,可發(fā)送和接收數(shù)據(jù),并且在硬件上都需要接一個上拉電阻到VCC。各種被控制電路均并聯(lián)在這條總線上,但就像電話機一樣只有撥通各自的號碼才能工作,所以每個電路和模塊都有唯一的地址,這樣,各控制電路雖然掛在同一條總線上,卻彼此獨立,互不相關(guān)。
IIC主機往從機里面寫入數(shù)據(jù)的步驟如下:
1、 主機發(fā)送一個起始信號和從機的設(shè)備地址給從機
2、 主機發(fā)送數(shù)據(jù)給從機
3、 主機發(fā)送一個停止信號結(jié)束發(fā)送過程
IIC主機從從機里面讀出數(shù)據(jù)的步驟如下:
1、 主機發(fā)送一個起始信號和從機的設(shè)備地址給從機
2、 主機發(fā)送一個要讀取的地址給從機
3、 主機從從機接收數(shù)據(jù)
4、 主機發(fā)送一個停止信號給從機結(jié)束整個接收過程
總的來說,IIC總線在通信的過程中一共有一下幾種狀態(tài):
1、空閑狀態(tài)
IIC 總線的 SDA 和 SCL 兩條信號線同時處于高電平時,規(guī)定為總線的空閑狀態(tài)。此時各個器件的輸出級場效應(yīng)管均處在截止?fàn)顟B(tài),即釋放總線,由兩條信號線各自的上拉電阻把電平拉高。
2、起始狀態(tài)和結(jié)束狀態(tài)
在時鐘線 SCL 保持高電平期間,數(shù)據(jù)線 SDA 上的電平被拉低(即負跳變),定義為 I2C 總線總線的起始信號,它標志著一次數(shù)據(jù)傳輸?shù)拈_始。起始信號是由主控器主動建立的,在建立該信號之前 I2C 總線必須處于空閑狀態(tài)。
在時鐘線 SCL 保持高電平期間,數(shù)據(jù)線 SDA 被釋放,使得 SDA 返回高電平(即正跳變),稱為 I2C 總線的停止信號,它標志著一次數(shù)據(jù)傳輸?shù)慕K止。停止信號也是由主控器主動建立的,建立該信號之后,I2C 總線將返回空閑狀態(tài)。
起始信號和結(jié)束信號如下圖所示
3、有效的數(shù)據(jù)位傳輸
在 IIC 總線上傳送的每一位數(shù)據(jù)都有一個時鐘脈沖相對應(yīng)(或同步控制),即在 SCL 串行時鐘的配合下,數(shù)據(jù)在 SDA 上從高位向低位依次串行傳送每一位的數(shù)據(jù)。進行數(shù)據(jù)傳送時,在 SCL 呈現(xiàn)高電平期間,SDA 上的電平必須保持穩(wěn)定,低電平為數(shù)據(jù) 0,高電平為數(shù)據(jù) 1。只有在 SCL 為低電平期間,才允許 SDA 上的電平改變狀態(tài)。下圖是0xaa在IIC總線上有效傳輸(有效傳輸是指第9個時鐘的高電平期間,從機給主機反饋了一個有效的應(yīng)答位0)的圖示
4、應(yīng)答信號與非應(yīng)答信號
I2C 總線上的所有數(shù)據(jù)都是以 8 位字節(jié)傳送的,發(fā)送器(主機)每發(fā)送一個字節(jié),就在第9個時鐘脈沖期間釋放數(shù)據(jù)線,由接收器(從機)反饋一個應(yīng)答信號。應(yīng)答信號為低電平時,規(guī)定為有效應(yīng)答位(ACK簡稱應(yīng)答位),表示接收器已經(jīng)成功地接收了該字節(jié);應(yīng)答信號為高電平時,規(guī)定為非應(yīng)答位(NACK),一般表示接收器接收該字節(jié)沒有成功。對于反饋有效應(yīng)答位 ACK 的要求是,接收器在第 9 個時鐘脈沖之前的低電平期間將 SDA 線拉低,并且確保在該時鐘的高電平期間為穩(wěn)定的低電平。
對非應(yīng)答位(NACK)還要特別說明的是,還有以下四種情況IIC通信過程中會產(chǎn)生非應(yīng)答位:
1、接收器(從機)正在處理某些實時的操作無法與主機實現(xiàn)IIC通信的時候,接收器(從機)會給主機反饋一個非應(yīng)答位(NACK)
2、主機發(fā)送數(shù)據(jù)的過程中,從機無法解析發(fā)送的數(shù)據(jù),接收器(從機)也會給主機反饋一個非應(yīng)答位(NACK)
3、主機發(fā)送數(shù)據(jù)的過程中,從機無法再繼續(xù)接收數(shù)據(jù),接收器(從機)也會給主機反饋一個非應(yīng)答位(NACK)
4、主機從從機中讀取數(shù)據(jù)的過程中,主機不想再接收數(shù)據(jù),主機會給從機反饋一個非應(yīng)答位(NACK),注意,這種情況是主機給從機反饋一個非應(yīng)答位(NACK)
關(guān)于有效應(yīng)答位的圖示在上一傳輸0xaa的圖中可以清楚的看到,關(guān)于非應(yīng)答位的圖示見下圖
了解清楚IIC總線在通信的過程中的幾種狀態(tài)以后接下來就具體看看IIC總線的讀寫過程吧。
1、主機通過IIC總線往從機里面寫數(shù)據(jù)
主機通過IIC總線往從機中寫數(shù)據(jù)的時候,主機首先會發(fā)送一個起始信號,接著把IIC從機的7位設(shè)備地址后面添一個0(設(shè)備地址后面的0表示主機向從機寫數(shù)據(jù),1表示主機從從機中讀數(shù)據(jù))組成一個8位的數(shù)據(jù),把這個8位的數(shù)據(jù)發(fā)給從機,發(fā)完這8位的數(shù)據(jù)以后主機馬上釋放SDA信號線等待從機的應(yīng)答,如果從機正確收到這個數(shù)據(jù),從機就會發(fā)送一個有效應(yīng)答位0給主機告訴主機自己已經(jīng)收到了數(shù)據(jù),主機收到從機的有效應(yīng)答位以后 ,接下來主機會發(fā)送想要寫入的寄存器地址,寄存器發(fā)送完畢以后主機同樣會釋放SDA信號線等待從機的應(yīng)答,從機如果正確收到了主機發(fā)過來的寄存器地址,從機會再次發(fā)送一個有效應(yīng)答位給主機,主機收到從機的有效應(yīng)答位0以后,接下來主機就會給從機發(fā)送想要寫入從機的數(shù)據(jù),從機正確收到這個數(shù)據(jù)以后仍然像之前兩次一樣會給主機發(fā)送一個有效應(yīng)答位,主機收到這個有效應(yīng)答位以后給從機發(fā)送一個停止信號,整個傳輸過程就結(jié)束了。下圖是整個傳輸過程的示意圖:
特別注意:上圖中灰色的地方表示主機正在控制SDA信號線,白色的地方表示從機正在控制SDA信號線。
2、主機通過IIC總線從從機里面讀數(shù)據(jù)
主機通過IIC總線從從機中讀數(shù)據(jù)的過程與寫數(shù)據(jù)的過程有相似之處,但是讀數(shù)據(jù)的過程還多了一些額外的步驟。主機從從機讀數(shù)據(jù)時主機首先會發(fā)送一個起始信號,接著把IIC從機的7位設(shè)備地址后面添一個0(設(shè)備地址后面的0表示主機向從機寫數(shù)據(jù),1表示主機從從機中讀數(shù)據(jù)),把這個8位的數(shù)據(jù)發(fā)給從機,發(fā)完這8位的數(shù)據(jù)以后主機馬上釋放SDA信號線等待從機的應(yīng)答,如果從機正確收到這個數(shù)據(jù),從機就會發(fā)送一個有效應(yīng)答位0給主機告訴主機自己已經(jīng)收到了數(shù)據(jù),主機收到從機的有效應(yīng)答位以后 ,接下來主機會發(fā)送想要讀的寄存器地址,寄存器發(fā)送完畢以后主機同樣會釋放SDA信號線等待從機的應(yīng)答,從機如果正確收到了主機發(fā)過來的寄存器地址,從機會再次發(fā)送一個有效應(yīng)答位給主機,主機收到從機的有效應(yīng)答位0以后,主機會給從機再次發(fā)送一次起始信號,接著把IIC從機的7位設(shè)備地址后面添一個1(設(shè)備地址后面的0表示主機向從機寫數(shù)據(jù),1表示主機從從機中讀數(shù)據(jù)),注意,第一次是在設(shè)備地址后面添0,這一次是在設(shè)備地址后面添1,把這個8位的數(shù)據(jù)發(fā)給從機,發(fā)完這8位的數(shù)據(jù)以后主機馬上釋放SDA信號線等待從機的應(yīng)答,如果從機正確收到這個數(shù)據(jù),從機就會發(fā)送一個有效應(yīng)答位0給主機告訴主機自己已經(jīng)收到了數(shù)據(jù),接著從機繼續(xù)占用SDA信號線給主機發(fā)送寄存器中的數(shù)據(jù),發(fā)送完畢以后,主機再次占用SDA信號線發(fā)送一個非應(yīng)答信號1給從機,主機發(fā)送一個停止信號給從機結(jié)束整個讀數(shù)據(jù)的過程。下圖是整個讀數(shù)據(jù)過程的示意圖
特別注意:上圖中灰色的地方表示主機正在控制SDA信號線,白色的地方表示從機正在控制SDA信號線。
三、 目標任務(wù)
1、編寫IIC總線主機給從機發(fā)送數(shù)據(jù)的代碼,實現(xiàn)FPGA(主機)往EEPROM(從機)的0x23這個地址寫入0x45這個數(shù)據(jù)
2、編寫IIC總線主機從從機接收數(shù)據(jù)的代碼,實現(xiàn)FPGA(主機)從EEPROM(從機)的0x23這個地址讀出0x45這個數(shù)據(jù),并用0x45這個數(shù)據(jù)的低四位驅(qū)動4個LED
四、 設(shè)計思路與Verilog代碼編寫
4.1、 IIC發(fā)送模塊的接口定義與整體設(shè)計
Verilog編寫的IIC發(fā)送模塊除了進行IIC通信的兩根信號線(SCL和SDA)以外還要包括一些時鐘、復(fù)位、使能、并行的輸入輸出以及完成標志位。其框圖如下所示
其中:
I_clk是系統(tǒng)時鐘;
I_rst_n是系統(tǒng)復(fù)位;
I_iic_send_en發(fā)送使能信號,當(dāng)I_iic_send_en為1時IIC主機(FPGA)才能給IIC從機發(fā)送數(shù)據(jù);
I_dev_addr[6:0]是IIC從機的設(shè)備地址;
I_word_addr[7:0]是字地址,也就是我們想要操作的IIC設(shè)備的內(nèi)部存儲地址;
I_write_data[7:0]是主機(FPGA)要往IIC字地址中寫入的數(shù)據(jù);
O_done_flag是主機(FPGA)發(fā)送一個字節(jié)完成標志位,發(fā)送完成后會產(chǎn)生一個高脈沖;
O_scl是IIC總線的串行時鐘線;
IO_sda是IIC總線的串行數(shù)據(jù)線;
要想實現(xiàn)iic_send模塊的功能,還是先得抽象出發(fā)送一個字節(jié)數(shù)據(jù)時序的狀態(tài)機,這里把24LC04B發(fā)送過程的時序貼一遍
注意,上圖中的控制字節(jié)(CONTROL BYTE)實際上就是代碼里面定義的7-bit設(shè)備物理地址與最后1-bit的讀寫控制位拼接組成的。
通過觀察上面的時序圖可以看出,發(fā)送一個字節(jié)的數(shù)據(jù)之前必須要先發(fā)送起始位,然后發(fā)送控制字節(jié),接著等待應(yīng)答,然后在發(fā)送字地址,接著在等待應(yīng)答。數(shù)據(jù)發(fā)送完畢以后,在等待最后一個應(yīng)答,應(yīng)答成功后發(fā)送停止信號結(jié)束整個過程。所以,根據(jù)這個流程,可以歸納出如下幾個狀態(tài):
狀態(tài)0:空閑狀態(tài),用來初始化各個寄存器的值
狀態(tài)1:加載IIC設(shè)備的物理地址
狀態(tài)2:加載IIC設(shè)備的字地址
狀態(tài)3:加載要發(fā)送的數(shù)據(jù)
狀態(tài)4:發(fā)送起始信號
狀態(tài)5:發(fā)送一個字節(jié),從高位開始發(fā)送
狀態(tài)6:接收應(yīng)答狀態(tài)的應(yīng)答位
狀態(tài)7:校驗應(yīng)答位
狀態(tài)8:發(fā)送停止信號
狀態(tài)9:IIC寫操作結(jié)束
需要注意的是上面的各個狀態(tài)并不是按照順序執(zhí)行的,有些狀態(tài)要復(fù)用多次,比如狀態(tài)5發(fā)送字節(jié)的狀態(tài)就需要復(fù)用三次用來發(fā)送三個8-bit的數(shù)據(jù);同樣,狀態(tài)6和狀態(tài)7也要復(fù)用多次。
抽象出狀態(tài)機以后,寫代碼之前先分析一下代碼中要注意的一些關(guān)鍵點:
1、由于IIC時序要求數(shù)據(jù)線SDA在串行時鐘線的高電平保持不變,在串行時鐘線的低電平才能變化,所以代碼里面必須在串行時鐘線低電平的正中間產(chǎn)生一個標志位,寫代碼的時候在這個標志位處改變SDA的值,這樣就可以保證SDA在SCL的高電平期間保持穩(wěn)定了。同理,由于IIC從機(24LC04)在接收到主機(FPGA)發(fā)送的有效數(shù)據(jù)以后會在SCL高電平期間產(chǎn)生一個有效應(yīng)答信號0,所以為了保證采到的應(yīng)答信號準確,必須在SCL高電平期間的正中間判斷應(yīng)答信號是否滿足條件(0為有效應(yīng)答,1為無效應(yīng)答),因此代碼里面還必須在串行時鐘線高電平的正中間產(chǎn)生一個標志位,在這個標志下接收應(yīng)答位并進行校驗。
這部分的代碼通過一個計數(shù)器就很容易實現(xiàn),代碼如下:
?
?
parameter C_DIV_SELECT = 10'd500 ; // 分頻系數(shù)選擇 parameter C_DIV_SELECT0 = (C_DIV_SELECT >> 2) - 1 , // 用來產(chǎn)生IIC總線SCL低電平最中間的標志位 C_DIV_SELECT1 = (C_DIV_SELECT >> 1) - 1 , C_DIV_SELECT2 = (C_DIV_SELECT0 + C_DIV_SELECT1) + 1 , // 用來產(chǎn)生IIC總線SCL高電平最中間的標志位 C_DIV_SELECT3 = (C_DIV_SELECT >> 1) + 1 ; // 用來產(chǎn)生IIC總線SCL下降沿標志位 always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_scl_cnt <= 10'd0 ; else if(R_scl_en) begin if(R_scl_cnt == C_DIV_SELECT - 1'b1) R_scl_cnt <= 10'd0 ; else R_scl_cnt <= R_scl_cnt + 1'b1 ; end else R_scl_cnt <= 10'd0 ; end assign O_scl = (R_scl_cnt <= C_DIV_SELECT1) ? 1'b1 : 1'b0 ; // 產(chǎn)生串行時鐘信號O_scl assign W_scl_low_mid = (R_scl_cnt == C_DIV_SELECT2) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl低電平正中間標志位 assign W_scl_high_mid = (R_scl_cnt == C_DIV_SELECT0) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl高電平正中間標志位
?
2、有了SCL信號低電平正中間標志位和高電平正中間標志位以后最好還產(chǎn)生一個下降沿的標志位。原因是在發(fā)送第一個8-bit數(shù)據(jù)以后,處理這個8-bit數(shù)據(jù)應(yīng)答位的位置在SCL信號高電平的正中間,由于要復(fù)用發(fā)送8-bit數(shù)據(jù)的那個狀態(tài),所以必須在第二次進入發(fā)送8-bit數(shù)據(jù)的狀態(tài)時必須提前把數(shù)據(jù)再次加載好,因此可以在這個下降沿的標志來加載第二次要發(fā)送的數(shù)據(jù),然后在SCL下降沿的正中間把8-bit數(shù)據(jù)發(fā)出去。這里必須結(jié)合代碼來理解,這里可以暫時有個印象。
3、IIC總線的SDA數(shù)據(jù)線是一個雙向IO口,關(guān)于雙向IO在Verilog代碼中如何進行處理,我在《QSPI Flash的原理與QSPI時序的實現(xiàn)》這篇博客已經(jīng)做了說明,這里不再贅述,直接給出代碼如下:
?
module Test_inout ( input I_clk, input I_rst_n, . . . inout IO_data, . . . ) reg R_data_out ; wire I_data_in ; assign IO_data = Control ? R_data_out : 1'bz ; assign I_data_in = IO_data ; always @(posedge I_clk or negedge I_rst_n) begin . . . ; end endmodule
?
4、發(fā)送8-bit數(shù)據(jù)的整個過程如下:加載8-bit數(shù)據(jù)->發(fā)送8-bit數(shù)據(jù)->接收應(yīng)答位->校驗應(yīng)答位->加載第二個8-bit數(shù)據(jù)……....。所以為了復(fù)用中間標紅的這幾個狀態(tài),必須在加載8-bit數(shù)據(jù)這個狀態(tài)提前設(shè)置好校驗應(yīng)答位狀態(tài)執(zhí)行完畢以后的后一個狀態(tài)的位置,這在代碼里面通過R_jump_state這個變量來完成。這一點也必須對照著代碼來進行理解。
思路理清楚以后就可以直接編寫Verilog代碼了,iic_send模塊的代碼如下:
?
module iic_send ( input I_clk , // 系統(tǒng)50MHz時鐘 input I_rst_n , // 系統(tǒng)全局復(fù)位 input I_iic_send_en , // IIC發(fā)送使能位 input [6:0] I_dev_addr , // IIC設(shè)備的物理地址 input [7:0] I_word_addr , // IIC設(shè)備的字地址,即我們想操作的IIC的內(nèi)部地址 input [7:0] I_write_data , // 往IIC設(shè)備的字地址寫入的數(shù)據(jù) output reg O_done_flag , // 讀或?qū)慖IC設(shè)備結(jié)束標志位 // 標準的IIC設(shè)備總線 output O_scl , // IIC總線的串行時鐘線 inout IO_sda // IIC總線的雙向數(shù)據(jù)線 ); parameter C_DIV_SELECT = 10'd500 ; // 分頻系數(shù)選擇 parameter C_DIV_SELECT0 = (C_DIV_SELECT >> 2) - 1 , // 用來產(chǎn)生IIC總線SCL低電平最中間的標志位 C_DIV_SELECT1 = (C_DIV_SELECT >> 1) - 1 , C_DIV_SELECT2 = (C_DIV_SELECT0 + C_DIV_SELECT1) + 1 , // 用來產(chǎn)生IIC總線SCL高電平最中間的標志位 C_DIV_SELECT3 = (C_DIV_SELECT >> 1) + 1 ; // 用來產(chǎn)生IIC總線SCL下降沿標志位 reg [9:0] R_scl_cnt ; // 用來產(chǎn)生IIC總線SCL時鐘線的計數(shù)器 reg R_scl_en ; // IIC總線SCL時鐘線使能信號 reg [3:0] R_state ; reg R_sda_mode ; // 設(shè)置SDA模式,1位輸出,0為輸入 reg R_sda_reg ; // SDA寄存器 reg [7:0] R_load_data ; // 發(fā)送/接收過程中加載的數(shù)據(jù),比如設(shè)備物理地址,字地址和數(shù)據(jù)等 reg [3:0] R_bit_cnt ; // 發(fā)送字節(jié)狀態(tài)中bit個數(shù)計數(shù) reg R_ack_flag ; // 應(yīng)答標志 reg [3:0] R_jump_state ; // 跳轉(zhuǎn)狀態(tài),傳輸一個字節(jié)成功并應(yīng)答以后通過這個變量跳轉(zhuǎn)到導(dǎo)入下一個數(shù)據(jù)的狀態(tài) wire W_scl_low_mid ; // SCL的低電平中間標志位 wire W_scl_high_mid ; // SCL的高電平中間標志位 wire W_scl_neg ; // SCL的下降沿標志位 assign IO_sda = (R_sda_mode == 1'b1) ? R_sda_reg : 1'bz ; always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_scl_cnt <= 10'd0 ; else if(R_scl_en) begin if(R_scl_cnt == C_DIV_SELECT - 1'b1) R_scl_cnt <= 10'd0 ; else R_scl_cnt <= R_scl_cnt + 1'b1 ; end else R_scl_cnt <= 10'd0 ; end assign O_scl = (R_scl_cnt <= C_DIV_SELECT1) ? 1'b1 : 1'b0 ; // 產(chǎn)生串行時鐘信號O_scl assign W_scl_low_mid = (R_scl_cnt == C_DIV_SELECT2) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl低電平正中間標志位 assign W_scl_high_mid = (R_scl_cnt == C_DIV_SELECT0) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl高電平正中間標志位 assign W_scl_neg = (R_scl_cnt == C_DIV_SELECT3) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl下降沿標志位 always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_state <= 4'd0 ; R_sda_mode <= 1'b1 ; R_sda_reg <= 1'b1 ; R_bit_cnt <= 4'd0 ; O_done_flag <= 1'b0 ; R_jump_state <= 4'd0 ; R_ack_flag <= 1'b0 ; end else if(I_iic_send_en) // 往IIC設(shè)備發(fā)送數(shù)據(jù) begin case(R_state) 4'd0 : // 空閑狀態(tài)設(shè)置SCL與SDA均為高 begin R_sda_mode <= 1'b1 ; // 設(shè)置SDA為輸出 R_sda_reg <= 1'b1 ; // 設(shè)置SDA為高電平 R_scl_en <= 1'b0 ; // 關(guān)閉SCL時鐘線 R_state <= 4'd1 ; // 下一個狀態(tài)是加載設(shè)備物理地址狀態(tài) R_bit_cnt <= 4'd0 ; // 發(fā)送字節(jié)狀態(tài)中bit個數(shù)計數(shù)清零 O_done_flag <= 1'b0 ; R_jump_state <= 4'd0 ; end 4'd1 : // 加載IIC設(shè)備物理地址 begin R_load_data <= {I_dev_addr, 1'b0} ; R_state <= 4'd4 ; R_jump_state <= 4'd2 ; end 4'd2 : // 加載IIC設(shè)備字地址 begin R_load_data <= I_word_addr ; R_state <= 4'd5 ; R_jump_state <= 4'd3 ; end 4'd3 : // 加載要發(fā)送的數(shù)據(jù) begin R_load_data <= I_write_data ; R_state <= 4'd5 ; R_jump_state <= 4'd8 ; end 4'd4 : // 發(fā)送起始信號 begin R_scl_en <= 1'b1 ; // 打開SCL時鐘線 R_sda_mode <= 1'b1 ; // 設(shè)置SDA為輸出 if(W_scl_high_mid) begin R_sda_reg <= 1'b0 ; // 在SCL高電平中間把SDA信號拉低,產(chǎn)生起始信號 R_state <= 4'd5 ; end else R_state <= 4'd4 ; // 如果SCL高電平中間標志沒出現(xiàn)就一直在這個狀態(tài)等著 end 4'd5 : // 發(fā)送1個字節(jié),從高位開始發(fā) begin R_scl_en <= 1'b1 ; // 打開SCL時鐘線 R_sda_mode <= 1'b1 ; // 設(shè)置SDA為輸出 if(W_scl_low_mid) begin if(R_bit_cnt == 4'd8) begin R_bit_cnt <= 4'd0 ; R_state <= 4'd6 ; // 字節(jié)發(fā)完以后進入應(yīng)答狀態(tài) end else begin R_sda_reg <= R_load_data[7-R_bit_cnt] ; // 先發(fā)送高位 R_bit_cnt <= R_bit_cnt + 1'b1 ; end end else R_state <= 4'd5 ; // 字節(jié)沒發(fā)完時在這個狀態(tài)一直等待 end 4'd6 : // 接收應(yīng)答狀態(tài)的應(yīng)答位 begin R_scl_en <= 1'b1 ; // 打開SCL時鐘線 R_sda_mode <= 1'b0 ; // 設(shè)置SDA為輸入 if(W_scl_high_mid) begin R_ack_flag <= IO_sda ; R_state <= 4'd7 ; end else R_state <= 4'd6 ; end 4'd7 : // 校驗應(yīng)答位 begin R_scl_en <= 1'b1 ; // 打開SCL時鐘線 if(R_ack_flag == 1'b0) // 校驗通過 begin if(W_scl_neg == 1'b1) begin R_state <= R_jump_state ; R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸出 R_sda_reg <= 1'b0 ; // 讀取完應(yīng)答信號以后要把SDA信號設(shè)置成輸出并拉低,因為如果這個狀 // 態(tài)后面是停止?fàn)顟B(tài)的話,需要SDA信號的上升沿,所以這里提前拉低它 end else R_state <= 4'd7 ; end else R_state <= 4'd0 ; end 4'd8 : // 發(fā)送停止信號 begin R_scl_en <= 1'b1 ; // 打開SCL時鐘線 R_sda_mode <= 1'b1 ; // 設(shè)置SDA為輸出 if(W_scl_high_mid) begin R_sda_reg <= 1'b1 ; R_state <= 4'd9 ; end end 4'd9 : // IIC寫操作結(jié)束 begin R_scl_en <= 1'b0 ; // 關(guān)閉SCL時鐘線 R_sda_mode <= 1'b1 ; // 設(shè)置SDA為輸出 R_sda_reg <= 1'b1 ; // 拉高SDA保持空閑狀態(tài)情況 O_done_flag <= 1'b1 ; R_state <= 4'd0 ; R_ack_flag <= 1'b0 ; end default : R_state <= 4'd0 ; endcase end else begin R_state <= 4'd0 ; R_sda_mode <= 1'b1 ; R_sda_reg <= 1'b1 ; R_bit_cnt <= 4'd0 ; O_done_flag <= 1'b0 ; R_jump_state <= 4'd0 ; R_ack_flag <= 1'b0 ; end end wire [35:0] CONTROL0 ; wire [54:0] TRIG0 ; icon icon_inst ( .CONTROL0(CONTROL0) // INOUT BUS [35:0] ); ila ila_inst ( .CONTROL(CONTROL0), // INOUT BUS [35:0] .CLK(I_clk), // IN .TRIG0(TRIG0) // IN BUS [49:0] ); assign TRIG0[0] = O_scl ; assign TRIG0[1] = IO_sda ; assign TRIG0[11:2] = R_scl_cnt ; assign TRIG0[12] = R_scl_en ; assign TRIG0[16:13] = R_state ; assign TRIG0[17] = R_sda_mode ; assign TRIG0[18] = R_sda_reg ; assign TRIG0[26:19] = R_load_data ; assign TRIG0[30:27] = R_bit_cnt ; assign TRIG0[31] = R_ack_flag ; assign TRIG0[36:32] = R_jump_state ; assign TRIG0[37] = W_scl_low_mid ; assign TRIG0[38] = W_scl_high_mid ; assign TRIG0[39] = O_done_flag ; assign TRIG0[40] = I_rst_n ; endmodule
?
整個代碼的流程與之前分析的流程完全一致。本來想寫一個測試文件用ModelSim進行基本的仿真,但是由于應(yīng)答信號是取決于IIC從設(shè)備的,所以還是決定用ChipScope直接抓。在用ChipScope抓之前先寫一個頂層文件把上面的代碼例化進去,頂層代碼如下:
?
module iic_send_top ( input I_clk , // 系統(tǒng)50MHz時鐘 input I_rst_n , // 系統(tǒng)全局復(fù)位 // 標準的IIC設(shè)備總線 output O_scl , // IIC總線的串行時鐘線 inout IO_sda // IIC總線的雙向數(shù)據(jù)線 ); wire W_done_flag ; iic_send U_iic_send ( .I_clk (I_clk ), // 系統(tǒng)50MHz時鐘 .I_rst_n (I_rst_n ), // 系統(tǒng)全局復(fù)位 .I_iic_send_en (1'b1 ), // 發(fā)送使能位,高電平有效 .I_dev_addr (7'b1010_000 ), // IIC設(shè)備的物理地址 .I_word_addr (8'h23 ), // IIC設(shè)備的字地址,即我們想操作的IIC的內(nèi)部地址 .I_write_data (8'h45 ), // 往IIC設(shè)備的字地址寫入的數(shù)據(jù) .O_done_flag (W_done_flag ), // 讀或?qū)慖IC設(shè)備結(jié)束標志位 // 標準的IIC設(shè)備總線 .O_scl (O_scl ), // IIC總線的串行時鐘線 .IO_sda (IO_sda ) // IIC總線的雙向數(shù)據(jù)線 ); endmodule
?
綁定好管腳以后就可以生成bit文件下載到FPGA里面用ChipScope抓時序了,下面是我抓到的時序圖:
為了更清晰的說明上面的時序,我把起始信號,停止信號,每個比特以及應(yīng)答位全部框出來進一步解釋如下:
通過上面的時序圖可以清楚的看到:
1號紅框是起始信號,在SCL高電平期間SDA有一個下降沿
2~9號紅框是發(fā)送設(shè)備物理地址8’ha0(8’b1010_0000)
10號紅框是應(yīng)答位,在這個期間R_sda_mode保持低電平,SDA為輸入
11~18號紅框是發(fā)送字地址8’h23(8’b0010_0011)
19號紅框是應(yīng)答位,在這個期間R_sda_mode保持低電平,SDA為輸入
20~27號紅框是發(fā)送數(shù)據(jù)8’h45(8’b0100_0101)
28號紅框是應(yīng)答位,在這個期間R_sda_mode保持低電平,SDA為輸入
29號紅框是停止信號,在SCL高電平期間SDA有一個上升沿
其他變量的時序細節(jié)這里不再展開,大家可以自己抓出來。至此,IIC發(fā)送模塊全部設(shè)計完畢。
4.2、 IIC接收模塊的接口定義與整體設(shè)計
Verilog編寫的IIC接收模塊除了進行IIC通信的兩根信號線(SCL和SDA)以外還要包括一些時鐘、復(fù)位、使能、并行的輸入輸出以及完成標志位。其框圖如下所示
其中:
I_clk是系統(tǒng)時鐘;
I_rst_n是系統(tǒng)復(fù)位;
I_iic_recv_en接收使能信號,當(dāng)I_iic_recv_en為1時IIC主機(FPGA)才能從IIC從機接收數(shù)據(jù);
I_dev_addr[6:0]是IIC從機的設(shè)備地址;
I_word_addr[7:0]是字地址,也就是我們想要讀取的IIC設(shè)備的內(nèi)部存儲地址;
O_read_data[7:0]是主機(FPGA)從IIC設(shè)備字地址中讀取的數(shù)據(jù);
O_done_flag是主機(FPGA)接收一個字節(jié)完成標志位,接收完成后會產(chǎn)生一個高脈沖;
O_scl是IIC總線的串行時鐘線;
IO_sda是IIC總線的串行數(shù)據(jù)線;
要想實現(xiàn)iic_send模塊的功能,還是先得抽象出發(fā)送一個字節(jié)數(shù)據(jù)時序的狀態(tài)機,這里把24LC04B接收過程的時序貼一遍
注意,上圖中的控制字節(jié)(CONTROL BYTE)實際上就是代碼里面定義的7-bit設(shè)備物理地址與最后1-bit讀寫控制位組成的。
通過觀察上面的時序圖可以看出,接收一個字節(jié)的數(shù)據(jù)的過程與發(fā)送一個字節(jié)數(shù)據(jù)相比多了一個第二次的起始信號與控制字節(jié)(CONTROL BYTE),而且第二個控制字節(jié)(CONTROL BYTE)的最低位應(yīng)該為1,表示IIC主機(FPGA)從IIC從機(24LC04)中讀數(shù)據(jù),當(dāng)主機(FPGA)想結(jié)束讀數(shù)據(jù)的過程時,它會給IIC設(shè)備發(fā)送一個非應(yīng)答位1,最后在發(fā)送停止信號結(jié)束整個讀數(shù)據(jù)的過程。所以,根據(jù)這個流程,可以歸納出如下幾個狀態(tài):
狀態(tài)0:空閑狀態(tài),用來初始化各個寄存器的值
狀態(tài)1:加載IIC設(shè)備的物理地址
狀態(tài)2:加載IIC設(shè)備的字地址
狀態(tài)3:發(fā)送第一個起始信號(讀過程要求發(fā)送兩次起始信號)
狀態(tài)4:發(fā)送一個字節(jié)數(shù)據(jù),從高位開始發(fā)送
狀態(tài)5:接收應(yīng)答狀態(tài)的應(yīng)答位
狀態(tài)6:校驗應(yīng)答位
狀態(tài)7:發(fā)送第二個起始信號(讀過程要求發(fā)送兩次起始信號
狀態(tài)8:再次加載IIC設(shè)備的物理地址,但這次物理地址最后一位應(yīng)該為1,表示讀操作
狀態(tài)9:接收一個字節(jié)數(shù)據(jù),從高位開始接收
狀態(tài)10:主機發(fā)送一個非應(yīng)答信號1給從機
狀態(tài)11:等確定從機收到這個非應(yīng)答信號1以后,初始化SDA的值為0,準備產(chǎn)生停止信號
狀態(tài)12:發(fā)送停止信號
狀態(tài)13:讀操作結(jié)束
需要注意的是上面的各個狀態(tài)和發(fā)送模塊一樣,并不是按照順序執(zhí)行的,有些狀態(tài)也要復(fù)用多次。
接收模塊有以下幾個關(guān)鍵點要注意:
1、和發(fā)送模塊一樣,需要產(chǎn)生SCL信號高電平中間標志位,低電平中間標志位以及下降沿標志位
2、由于讀數(shù)據(jù)的過程需要發(fā)送第二次起始位,而起始位的條件是在SCL高電平期間SDA有一個下降沿,所以一定要在處理完寫設(shè)備地址與寫字地址的應(yīng)答位之后,在SCL的下降沿標志處把SDA信號設(shè)置成輸出并拉高方便產(chǎn)生第二次起始信號。具體細節(jié)對照著代碼理解。
3、第一次發(fā)送的設(shè)備物理地址的最低位是0,表示寫數(shù)據(jù);第二次發(fā)送的設(shè)備物理地址的最低位是1,表示讀數(shù)據(jù)
4、讀完一個字節(jié)數(shù)據(jù)以后,一定要記住是主機(FPGA)給從機(24LC04)發(fā)送一個非應(yīng)答信號1
有了上面這些儲備以后就可以編寫接收模塊的代碼了,接收模塊的代碼如下:
?
module iic_recv ( input I_clk , // 系統(tǒng)50MHz時鐘 input I_rst_n , // 系統(tǒng)全局復(fù)位 input I_iic_recv_en , // IIC發(fā)送使能位 input [6:0] I_dev_addr , // IIC設(shè)備的物理地址 input [7:0] I_word_addr , // IIC設(shè)備的字地址,即我們想操作的IIC的內(nèi)部地址 output reg [7:0] O_read_data , // 從IIC設(shè)備的字地址讀出來的數(shù)據(jù) output reg O_done_flag , // 讀或?qū)慖IC設(shè)備結(jié)束標志位 // 標準的IIC設(shè)備總線 output O_scl , // IIC總線的串行時鐘線 inout IO_sda // IIC總線的雙向數(shù)據(jù)線 ); parameter C_DIV_SELECT = 10'd500 ; // 分頻系數(shù)選擇 parameter C_DIV_SELECT0 = (C_DIV_SELECT >> 2) - 1 , // 用來產(chǎn)生IIC總線SCL低電平最中間的標志位 C_DIV_SELECT1 = (C_DIV_SELECT >> 1) - 1 , // 用來產(chǎn)生IIC串行時鐘線 C_DIV_SELECT2 = (C_DIV_SELECT0 + C_DIV_SELECT1) + 1 , // 用來產(chǎn)生IIC總線SCL高電平最中間的標志位 C_DIV_SELECT3 = (C_DIV_SELECT >> 1) + 1 ; // 用來產(chǎn)生IIC總線SCL下降沿標志位 reg [9:0] R_scl_cnt ; // 用來產(chǎn)生IIC總線SCL時鐘線的計數(shù)器 reg R_scl_en ; // IIC總線SCL時鐘線使能信號 reg [3:0] R_state ; reg R_sda_mode ; // 設(shè)置SDA模式,1位輸出,0為輸入 reg R_sda_reg ; // SDA寄存器 reg [7:0] R_load_data ; // 發(fā)送/接收過程中加載的數(shù)據(jù),比如設(shè)備物理地址,字地址和數(shù)據(jù)等 reg [3:0] R_bit_cnt ; // 發(fā)送字節(jié)狀態(tài)中bit個數(shù)計數(shù) reg R_ack_flag ; // 應(yīng)答標志 reg [3:0] R_jump_state ; // 跳轉(zhuǎn)狀態(tài),傳輸一個字節(jié)成功并應(yīng)答以后通過這個變量跳轉(zhuǎn)到導(dǎo)入下一個數(shù)據(jù)的狀態(tài) reg [7:0] R_read_data_reg ; wire W_scl_low_mid ; // SCL的低電平中間標志位 wire W_scl_high_mid ; // SCL的高電平中間標志位 assign IO_sda = (R_sda_mode == 1'b1) ? R_sda_reg : 1'bz ; always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_scl_cnt <= 10'd0 ; else if(R_scl_en) begin if(R_scl_cnt == C_DIV_SELECT - 1'b1) R_scl_cnt <= 10'd0 ; else R_scl_cnt <= R_scl_cnt + 1'b1 ; end else R_scl_cnt <= 10'd0 ; end assign O_scl = (R_scl_cnt <= C_DIV_SELECT1) ? 1'b1 : 1'b0 ; // 產(chǎn)生串行時鐘信號O_scl assign W_scl_low_mid = (R_scl_cnt == C_DIV_SELECT2) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl低電平正中間標志位 assign W_scl_high_mid = (R_scl_cnt == C_DIV_SELECT0) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl高電平正中間標志位 assign W_scl_neg = (R_scl_cnt == C_DIV_SELECT3) ? 1'b1 : 1'b0 ; // 產(chǎn)生scl下降沿標志位 always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_state <= 4'd0 ; R_sda_mode <= 1'b1 ; R_sda_reg <= 1'b1 ; R_bit_cnt <= 4'd0 ; O_done_flag <= 1'b0 ; R_jump_state <= 4'd0 ; R_read_data_reg <= 8'd0 ; R_ack_flag <= 1'b0 ; O_read_data <= 8'd0 ; end else if(I_iic_recv_en) // 往IIC設(shè)備發(fā)送數(shù)據(jù) begin case(R_state) 4'd0 : // 空閑狀態(tài),用來初始化相關(guān)所有信號 begin R_sda_mode <= 1'b1 ; // 設(shè)置SDA為輸出 R_sda_reg <= 1'b1 ; // 設(shè)置SDA為高電平 R_scl_en <= 1'b0 ; // 關(guān)閉SCL時鐘線 R_state <= 4'd1 ; // 下一個狀態(tài)是加載設(shè)備物理地址狀態(tài) R_bit_cnt <= 4'd0 ; O_done_flag <= 1'b0 ; R_jump_state <= 5'd0 ; R_read_data_reg <= 8'd0 ; end 4'd1 : // 加載IIC設(shè)備物理地址 begin R_load_data <= {I_dev_addr, 1'b0} ; R_state <= 4'd3 ; // 加載完設(shè)備物理地址以后進入起始狀態(tài) R_jump_state <= R_state + 1'b1 ; end 4'd2 : // 加載IIC設(shè)備字地址 begin R_load_data <= I_word_addr ; R_state <= 4'd4 ; R_jump_state <= R_state + 5'd5 ; // 設(shè)置這里是為了這一輪發(fā)送并應(yīng)答后跳到第二次啟始位 end 4'd3 : // 發(fā)送第一個起始信號 begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸出 if(W_scl_high_mid) begin R_sda_reg <= 1'b0 ; // 在SCL高電平的正中間把SDA引腳拉低產(chǎn)生一個下降沿 R_state <= 4'd4 ; // 下一個狀態(tài)是發(fā)送一個字節(jié)數(shù)據(jù)(IIC設(shè)備的物理地址) end else R_state <= 4'd3 ; end 4'd4 : // 發(fā)送一個字節(jié) begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸出 if(W_scl_low_mid) // 在SCL低電平的最中間改變數(shù)據(jù) begin if(R_bit_cnt == 4'd8) begin R_bit_cnt <= 4'd0 ; R_state <= 4'd5 ; end else begin R_sda_reg <= R_load_data[7-R_bit_cnt] ; R_bit_cnt <= R_bit_cnt + 1'b1 ; end end else R_state <= 4'd4 ; end 4'd5 : // 接收應(yīng)答狀態(tài)應(yīng)答位 begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_reg <= 1'b0 ; R_sda_mode <= 1'b0 ; // 設(shè)置SDA的模式為輸入 if(W_scl_high_mid) begin R_ack_flag <= IO_sda ; R_state <= 4'd6 ; end else R_state <= 4'd5 ; end 4'd6 : // 校驗應(yīng)答位 begin R_scl_en <= 1'b1 ; // 打開時鐘 if(R_ack_flag == 1'b0) // 校驗通過 begin if(W_scl_neg == 1'b1) begin R_state <= R_jump_state ; R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸出 R_sda_reg <= 1'b1 ; // 設(shè)置SDA的引腳電平拉高,方便后面產(chǎn)生第二次起始位 end else R_state <= 4'd6 ; end else R_state <= 4'd0 ; end 4'd7 : // 第二次起始位(IIC讀操作要求有2次起始位) begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸出 if(W_scl_high_mid) begin R_sda_reg <= 1'b0 ; R_state <= 4'd8 ; end else R_state <= 4'd7 ; end 4'd8 : // 再次加載IIC設(shè)備物理地址 ,但這次地址最后一位應(yīng)該為1,表示讀操作 begin R_load_data <= {I_dev_addr, 1'b1} ; // 前7bit是設(shè)備物理地址,最后一位1表示讀操作 R_state <= 4'd4 ; R_jump_state <= 4'd9 ; // 設(shè)置這里是為了這一輪發(fā)送并應(yīng)答后跳到第二次啟始位 end 4'd9 : // 讀一個字節(jié)數(shù)據(jù) begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_mode <= 1'b0 ; // 設(shè)置SDA的模式為輸入 if(W_scl_high_mid) begin if(R_bit_cnt == 4'd7) begin R_bit_cnt <= 4'd0 ; R_state <= 4'd10 ; O_read_data <= {R_read_data_reg[6:0],IO_sda} ; end else begin R_read_data_reg <= {R_read_data_reg[6:0],IO_sda} ; R_bit_cnt <= R_bit_cnt + 1'b1 ; end end else R_state <= 4'd9 ; end 4'd10 : // 讀完一個字節(jié)數(shù)據(jù)以后進入10,主機發(fā)送一個非應(yīng)答信號1 begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸入 if(W_scl_low_mid) begin R_state <= 4'd11 ; R_sda_reg <= 1'b1 ; end else R_state <= 4'd10 ; end 4'd11 : begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸入 if(W_scl_low_mid) begin R_state <= 4'd12 ; R_sda_reg <= 1'b0 ; end else R_state <= 4'd11 ; end 4'd12 : //停止位Stop begin R_scl_en <= 1'b1 ; // 打開時鐘 R_sda_mode <= 1'b1 ; // 設(shè)置SDA的模式為輸出 if(W_scl_high_mid) begin R_sda_reg <= 1'b1 ; R_state <= 4'd13 ; end else R_state <= 4'd12 ; end 4'd13 : begin R_scl_en <= 1'b0 ; // 關(guān)閉SCL時鐘線 R_sda_mode <= 1'b1 ; // 設(shè)置SDA為輸出 R_sda_reg <= 1'b1 ; // 拉高SDA保持空閑狀態(tài)情況 O_done_flag <= 1'b1 ; R_state <= 4'd0 ; R_read_data_reg <= 8'd0 ; end default: R_state <= 4'd0 ; endcase end else begin R_state <= 4'd0 ; R_sda_mode <= 1'b1 ; R_sda_reg <= 1'b1 ; R_bit_cnt <= 4'd0 ; O_done_flag <= 1'b0 ; R_jump_state <= 4'd0 ; R_read_data_reg <= 8'd0 ; R_ack_flag <= 1'b0 ; end end wire [35:0] CONTROL0 ; wire [54:0] TRIG0 ; icon icon_inst ( .CONTROL0(CONTROL0) // INOUT BUS [35:0] ); ila ila_inst ( .CONTROL(CONTROL0), // INOUT BUS [35:0] .CLK(I_clk), // IN .TRIG0(TRIG0) // IN BUS [49:0] ); assign TRIG0[0] = O_scl ; assign TRIG0[1] = IO_sda ; assign TRIG0[11:2] = R_scl_cnt ; assign TRIG0[12] = R_scl_en ; assign TRIG0[16:13] = R_state ; assign TRIG0[17] = R_sda_mode ; assign TRIG0[18] = R_sda_reg ; assign TRIG0[26:19] = R_load_data ; assign TRIG0[30:27] = R_bit_cnt ; assign TRIG0[31] = R_ack_flag ; assign TRIG0[36:32] = R_jump_state ; assign TRIG0[37] = W_scl_low_mid ; assign TRIG0[38] = W_scl_high_mid ; assign TRIG0[39] = O_done_flag ; assign TRIG0[40] = I_rst_n ; assign TRIG0[48:41] = O_read_data ; assign TRIG0[49] = W_scl_neg ; endmodule
?
整個代碼的流程與之前分析的流程完全一致。在用ChipScope抓之前先寫一個頂層文件把上面的代碼例化進去,頂層代碼如下:
?
module iic_recv_top ( input I_clk , // 系統(tǒng)50MHz時鐘 input I_rst_n , // 系統(tǒng)全局復(fù)位 output [3:0] O_led_out , // 從IIC設(shè)備的字地址讀出來的數(shù)據(jù) // 標準的IIC設(shè)備總線 output O_scl , // IIC總線的串行時鐘線 inout IO_sda // IIC總線的雙向數(shù)據(jù)線 ); wire W_done_flag ; wire [7:0] W_read_data ; // 從IIC設(shè)備的字地址讀出來的數(shù)據(jù) assign O_led_out = W_read_data[3:0] ; iic_recv U_iic_recv ( .I_clk (I_clk ), // 系統(tǒng)50MHz時鐘 .I_rst_n (I_rst_n ), // 系統(tǒng)全局復(fù)位 .I_iic_recv_en (1'b1 ), // 接收使能位,高電平有效 .I_dev_addr (7'b1010_000 ), // IIC設(shè)備的物理地址 .I_word_addr (8'h23 ), // IIC設(shè)備的字地址,即我們想操作的IIC的內(nèi)部地址 .O_read_data (W_read_data ), // 從IIC設(shè)備的字地址讀出來的數(shù)據(jù) .O_done_flag (W_done_flag ), // 讀或?qū)慖IC設(shè)備結(jié)束標志位 // 標準的IIC設(shè)備總線 .O_scl (O_scl ), // IIC總線的串行時鐘線 .IO_sda (IO_sda ) // IIC總線的雙向數(shù)據(jù)線 ); endmodule
?
綁定好管腳以后就可以生成bit文件下載到FPGA里面用ChipScope抓時序了,由于EEPROM是一種非易失性存儲器,所以做在IIC發(fā)送數(shù)據(jù)的實驗中往24LC04的0x23地址中的0x45這個數(shù)據(jù)在掉電以后并不會丟失。剛好可以通過這個接收模塊給讀出來,并用讀出數(shù)據(jù)的最低位驅(qū)動四個LED燈,如果時序正確的話,四個LED燈會間隔亮起來。下面是我抓到的接收數(shù)據(jù)時序圖:
通過上面的時序圖可以清楚的看到成功讀出了EEPROM中的0x45這個數(shù)據(jù),并且我板子上的四個LED燈也間隔亮了起來。
為了更清晰的說明上面的時序,我把起始信號,停止信號,每個比特,應(yīng)答位和非應(yīng)答位全部框出來進一步解釋如下:
1號紅框是起始信號,在SCL高電平期間SDA有一個下降沿
2~9號紅框是發(fā)送設(shè)備物理地址8’ha0(8’b1010_0000)
10號紅框是應(yīng)答位,在這個期間R_sda_mode保持低電平,SDA為輸入
11~18號紅框是發(fā)送字地址8’h23(8’b0010_0011)
19號紅框是應(yīng)答位,在這個期間R_sda_mode保持低電平,SDA為輸入
20號紅框是第二次起始位,在SCL高電平期間SDA有一個下降沿
21~28號紅框是發(fā)送數(shù)據(jù)8’ha1(8’b1010_0001)
29號紅框是應(yīng)答位,在這個期間R_sda_mode保持低電平,SDA為輸入
30~37號紅框是讀出的8-bit數(shù)據(jù)8’h45(8’b0100_0101),在這個期間R_sda_mode保持低電平,SDA為輸入
38號紅框是非應(yīng)答位,在這個期間R_sda_mode保持高電平,主機(FPGA)通過SDA輸出一個非應(yīng)答位1
39號紅框是停止信號,在SCL高電平期間SDA有一個上升沿
其他變量的時序細節(jié)這里不再展開,大家可以自己抓出來。至此,IIC接收模塊全部設(shè)計完畢。
五、 進一步思考
5.1、 24LC04寫數(shù)據(jù)操作要注意的地方
Following the start condition from the master, the device code (4 bits), the block address (3 bits), and the R/W bit which is a logic low is placed onto the bus by the master transmitter. This indicates to the addressed slave receiver that a byte with a word address will follow after it has generated an acknowledge bit during the ninth clock cycle. Therefore the next byte transmitted by the master is the word address and will be written into the address pointer of the 24LC04B/08B. After receiving another acknowledge signal from the 24LC04B/08B the master device will transmit the data word to be written into the addressed memory location.The 24LC04B/08B acknowledges again and the master generates a stop condition. This initiates the internal write cycle, and during this time the 24LC04B/08B will not generate acknowledge signals。
這是24LC04芯片手冊對它的寫操作的描述, 所以我們寫進去的數(shù)據(jù)其實是放在24LC04的一個緩沖區(qū)中,等主機(FPGA)發(fā)送停止信號以后24LC04內(nèi)部才開始工作把緩沖區(qū)中的數(shù)據(jù)寫入它內(nèi)部的ROM中,在這個過程中24LC04將不發(fā)送有效應(yīng)答信號,所以當(dāng)發(fā)送完停止信號又立馬給一個起始信號重新發(fā)送時會出現(xiàn)下面的時序
這種情況由于24LC04內(nèi)部還在處理緩沖區(qū)中的數(shù)據(jù),所以即使主機(FPGA)發(fā)送了正確的時序,從機(24LC04)也不會有效應(yīng)答。
5.2、 IIC設(shè)備多字節(jié)連續(xù)讀寫操作
24LC04支持16-Bytes的連續(xù)寫操作,當(dāng)超過16-Bytes是后面寫入的數(shù)據(jù)會覆蓋先前寫入的數(shù)據(jù),下面是關(guān)于這一段的描述:
The write control byte, word address and the first data byte are transmitted to the 24LC04B/08B in the same way as in a byte write. But instead of generating a stop condition the master transmits up to 16 data bytes to the 24LC04B/08B which are temporarily stored in the on-chip page buffer and will be written into the memory after the master has transmitted a stop condition. After the receipt of each word, the four lower order address pointer bits are internally incremented by one. The higher order seven bits of the word address remains constant. If the master should transmit more than 16 words prior to generating the stop condition, the address counter will roll over and the previously received data will be overwritten. As with the byte write operation, once the stop condition is received an internal write cycle will begin.
時序圖如下所示:
其實要實現(xiàn)這個時序并不是難事,只要多增加幾個加載數(shù)據(jù)的狀態(tài)就可以了,大家可以直接在上面發(fā)送數(shù)據(jù)模塊的基礎(chǔ)上改。
24LC04支持整塊存儲器的連續(xù)讀操作,下面是關(guān)于這一段的描述:
Sequential reads are initiated in the same way as a random read except that after the 24LC04B/08B transmits the first data byte, the master issues an acknowledge as opposed to a stop condition in a random read. This directs the 24LC04B/08B to transmit the next sequentially addressed 8-bit word (Figure 7-3).To provide sequential reads the 24LC04B/08B contains an internal address pointer which is incremented by one at the completion of each operation. This address pointer allows the entire memory contents to be serially read during one operation.
時序圖如下所示:
有了上面接收模塊的基礎(chǔ),實現(xiàn)這段時序應(yīng)該也不算困難。以后有空再實現(xiàn)。
審核編輯:劉清
評論
查看更多