Verilog -- IIC總線協議


Verilog -- IIC總線協議

@

參考自 https://www.cnblogs.com/liujinggang/p/9656358.html
上面的博文寫的很好,下面只是摘錄一些重要知識點以及自己的理解。

簡介

IIC(Inter-Integrated Circuit)總線是一種由PHILIPS公司開發的兩線式串行總線,用於連接微控制器及其外圍設備。I2C總線產生於在80年代,最初為音頻和視頻設備開發,如今主要在服務器管理中使用,其中包括單個組件狀態的通信。例如管理員可對各個組件進行查詢,以管理系統的配置或掌握組件的功能狀態,如電源和系統風扇。可隨時監控內存、硬盤、網絡、系統溫度等多個參數,增加了系統的安全性,方便了管理。IIC數據傳輸速率有標准模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些變種實現了低速模式(10 kbps)和快速+模式(1 Mbps)。
需要注意的是IIC總線的數據采集是scl高電平期間,再0電平期間允許sda數據變化。因此是電平敏感的,不是邊沿觸發類型

  下圖是一個嵌入式系統中處理器僅通過2根線的IIC總線控制多個IIC外設的典型應用圖
  

讀寫時序

寫時序

主機通過IIC總線往從機中寫數據的時候,主機首先會發送一個起始信號,接着把IIC從機的7位設備地址后面添一個0(設備地址后面的0表示主機向從機寫數據,1表示主機從從機中讀數據)組成一個8位的數據,把這個8位的數據發給從機,發完這8位的數據以后主機馬上釋放SDA信號線等待從機的應答,如果從機正確收到這個數據,從機就會發送一個有效應答位0給主機告訴主機自己已經收到了數據,主機收到從機的有效應答位以后 ,接下來主機會發送想要寫入的寄存器地址,寄存器發送完畢以后主機同樣會釋放SDA信號線等待從機的應答,從機如果正確收到了主機發過來的寄存器地址,從機會再次發送一個有效應答位給主機,主機收到從機的有效應答位0以后,接下來主機就會給從機發送想要寫入從機的數據,從機正確收到這個數據以后仍然像之前兩次一樣會給主機發送一個有效應答位,主機收到這個有效應答位以后給從機發送一個停止信號,整個傳輸過程就結束了。下圖是整個傳輸過程的示意圖:

讀時序

主機通過IIC總線從從機中讀數據的過程與寫數據的過程有相似之處,但是讀數據的過程還多了一些額外的步驟。主機從從機讀數據時主機首先會發送一個起始信號,接着把IIC從機的7位設備地址后面添一個0(設備地址后面的0表示主機向從機寫數據,1表示主機從從機中讀數據),把這個8位的數據發給從機,發完這8位的數據以后主機馬上釋放SDA信號線等待從機的應答,如果從機正確收到這個數據,從機就會發送一個有效應答位0給主機告訴主機自己已經收到了數據,主機收到從機的有效應答位以后 ,接下來主機會發送想要讀的寄存器地址,寄存器發送完畢以后主機同樣會釋放SDA信號線等待從機的應答,從機如果正確收到了主機發過來的寄存器地址,從機會再次發送一個有效應答位給主機,主機收到從機的有效應答位0以后,主機會給從機再次發送一次起始信號,接着把IIC從機的7位設備地址后面添一個1(設備地址后面的0表示主機向從機寫數據,1表示主機從從機中讀數據),注意,第一次是在設備地址后面添0,這一次是在設備地址后面添1,把這個8位的數據發給從機,發完這8位的數據以后主機馬上釋放SDA信號線等待從機的應答,如果從機正確收到這個數據,從機就會發送一個有效應答位0給主機告訴主機自己已經收到了數據,接着從機繼續占用SDA信號線給主機發送寄存器中的數據,發送完畢以后,主機再次占用SDA信號線發送一個非應答信號1給從機,主機發送一個停止信號給從機結束整個讀數據的過程。下圖是整個讀數據過程的示意圖:

verilog代碼設計

IIC發送模塊的接口定義與整體設計

  Verilog編寫的IIC發送模塊除了進行IIC通信的兩根信號線(SCL和SDA)以外還要包括一些時鍾、復位、使能、並行的輸入輸出以及完成標志位。其框圖如下所示:
  


  其中:

  • I_clk是系統時鍾;
  • I_rst_n是系統復位;
  • I_iic_send_en發送使能信號,當I_iic_send_en為1時IIC主機(FPGA)才能給IIC從機發送數據;
  • I_dev_addr[6:0]是IIC從機的設備地址;
  • I_word_addr[7:0]是字地址,也就是我們想要操作的IIC設備的內部存儲地址;
  • I_write_data[7:0]是主機(FPGA)要往IIC字地址中寫入的數據;
  • O_done_flag是主機(FPGA)發送一個字節完成標志位,發送完成后會產生一個高脈沖;
  • O_scl是IIC總線的串行時鍾線;
  • IO_sda是IIC總線的串行數據線;

通過觀察上面的時序圖可以看出,發送一個字節的數據之前必須要先發送起始位,然后發送控制字節,接着等待應答,然后在發送字地址,接着在等待應答。數據發送完畢以后,在等待最后一個應答,應答成功后發送停止信號結束整個過程。所以,根據這個流程,可以歸納出如下幾個狀態:

  • 狀態0:空閑狀態,用來初始化各個寄存器的值
  • 狀態1:加載IIC設備的物理地址
  • 狀態2:加載IIC設備的字地址
  • 狀態3:加載要發送的數據
  • 狀態4:發送起始信號
  • 狀態5:發送一個字節,從高位開始發送
  • 狀態6:接收應答狀態的應答位
  • 狀態7:校驗應答位
  • 狀態8:發送停止信號
  • 狀態9:IIC寫操作結束

  需要注意的是上面的各個狀態並不是按照順序執行的,有些狀態要復用多次,比如狀態5發送字節的狀態就需要復用三次用來發送三個8-bit的數據;同樣,狀態6和狀態7也要復用多次。
下面上代碼:

SCL標志位創建邏輯

由於IIC時序要求數據線SDA在串行時鍾線的高電平保持不變,在串行時鍾線的低電平才能變化,所以代碼里面必須在串行時鍾線低電平的正中間產生一個標志位,寫代碼的時候在這個標志位處改變SDA的值,這樣就可以保證SDA在SCL的高電平期間保持穩定了。同時,必須在SCL高電平期間的正中間判斷應答信號是否滿足條件(0為有效應答,1為無效應答),因此代碼里面還必須在串行時鍾線高電平的正中間產生一個標志位,在這個標志下接收應答位並進行校驗。

parameter   C_DIV_SELECT        =   10'd500 ; // 分頻系數選擇

parameter   C_DIV_SELECT0       =   (C_DIV_SELECT >> 2)  -  1           , // 用來產生IIC總線SCL高電平最中間的標志位
            C_DIV_SELECT1       =   (C_DIV_SELECT >> 1)  -  1           ,
            C_DIV_SELECT2       =   (C_DIV_SELECT0 + C_DIV_SELECT1) + 1 , // 用來產生IIC總線SCL低電平最中間的標志位
            C_DIV_SELECT3       =   (C_DIV_SELECT >> 1)  +  1           ; // 用來產生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 ; // 產生串行時鍾信號O_scl
assign W_scl_low_mid  = (R_scl_cnt == C_DIV_SELECT2) ? 1'b1 : 1'b0 ; // 產生scl高低平正中間標志位
assign W_scl_high_mid = (R_scl_cnt == C_DIV_SELECT0) ? 1'b1 : 1'b0 ; // 產生scl高電平正中間標志位

發送邏輯

發送8-bit數據的整個過程如下:加載8-bit數據->發送8-bit數據->接收應答位->校驗應答位->加載第二個8-bit數據……....。所以為了復用中間標紅的這幾個狀態,必須在加載8-bit數據這個狀態提前設置好校驗應答位狀態執行完畢以后的后一個狀態的位置,這在代碼里面通過R_jump_state這個變量來完成

module iic_send
(
    input                I_clk           , // 系統50MHz時鍾
    input                I_rst_n         , // 系統全局復位
    input                 I_iic_send_en   , // IIC發送使能位
    
    input        [6:0]   I_dev_addr      , // IIC設備的物理地址
    input        [7:0]   I_word_addr     , // IIC設備的字地址,即我們想操作的IIC的內部地址
    input        [7:0]   I_write_data    , // 往IIC設備的字地址寫入的數據
    output  reg          O_done_flag     , // 讀或寫IIC設備結束標志位
    
    // 標准的IIC設備總線
    output               O_scl           , // IIC總線的串行時鍾線
    inout                IO_sda            // IIC總線的雙向數據線
);          

parameter   C_DIV_SELECT        =   10'd500 ; // 分頻系數選擇

parameter   C_DIV_SELECT0       =   (C_DIV_SELECT >> 2)  -  1           , // 用來產生IIC總線SCL低電平最中間的標志位
            C_DIV_SELECT1       =   (C_DIV_SELECT >> 1)  -  1           ,
            C_DIV_SELECT2       =   (C_DIV_SELECT0 + C_DIV_SELECT1) + 1 , // 用來產生IIC總線SCL高電平最中間的標志位
            C_DIV_SELECT3       =   (C_DIV_SELECT >> 1)  +  1           ; // 用來產生IIC總線SCL下降沿標志位
 

reg     [9:0]   R_scl_cnt       ; // 用來產生IIC總線SCL時鍾線的計數器   
reg             R_scl_en        ; // IIC總線SCL時鍾線使能信號
reg     [3:0]   R_state         ; 
reg             R_sda_mode      ; // 設置SDA模式,1位輸出,0為輸入
reg             R_sda_reg       ; // SDA寄存器
reg     [7:0]   R_load_data     ; // 發送/接收過程中加載的數據,比如設備物理地址,字地址和數據等
reg     [3:0]   R_bit_cnt       ; // 發送字節狀態中bit個數計數
reg             R_ack_flag      ; // 應答標志
reg     [3:0]   R_jump_state    ; // 跳轉狀態,傳輸一個字節成功並應答以后通過這個變量跳轉到導入下一個數據的狀態

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 ; // 產生串行時鍾信號O_scl
assign W_scl_low_mid  = (R_scl_cnt == C_DIV_SELECT2) ? 1'b1 : 1'b0 ; // 產生scl低電平正中間標志位
assign W_scl_high_mid = (R_scl_cnt == C_DIV_SELECT0) ? 1'b1 : 1'b0 ; // 產生scl高電平正中間標志位
assign W_scl_neg       = (R_scl_cnt == C_DIV_SELECT3) ? 1'b1 : 1'b0 ; // 產生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設備發送數據
        begin
            case(R_state)
                4'd0   : // 空閑狀態設置SCL與SDA均為高
                    begin
                        R_sda_mode      <=  1'b1 ; // 設置SDA為輸出
                        R_sda_reg       <=  1'b1 ; // 設置SDA為高電平
                        R_scl_en        <=  1'b0 ; // 關閉SCL時鍾線
                        R_state         <=  4'd1 ; // 下一個狀態是加載設備物理地址狀態
                        R_bit_cnt       <=  4'd0 ; // 發送字節狀態中bit個數計數清零
                        O_done_flag     <=  1'b0 ;
                        R_jump_state    <=  4'd0 ;
                    end                               
                4'd1   :  // 加載IIC設備物理地址              
                    begin                             
                        R_load_data     <=  {I_dev_addr, 1'b0}  ;
                        R_state         <=  4'd4                ;
                        R_jump_state    <=  4'd2                ;
                    end                                     
                4'd2   :   // 加載IIC設備字地址                     
                    begin                                   
                        R_load_data     <=  I_word_addr         ; 
                        R_state         <=  4'd5                ;
                        R_jump_state    <=  4'd3                ;
                    end                                     
                4'd3   :    // 加載要發送的數據                    
                    begin                                   
                        R_load_data     <=  I_write_data        ; 
                        R_state         <=  4'd5                ;
                        R_jump_state    <=  4'd8                ;
                    end                                                         
                4'd4   :    // 發送起始信號                   
                    begin                                   
                        R_scl_en    <=  1'b1                ; // 打開SCL時鍾線
                        R_sda_mode  <=  1'b1                ; // 設置SDA為輸出
                        if(W_scl_high_mid)                  
                            begin                           
                                R_sda_reg   <=  1'b0        ; // 在SCL高電平中間把SDA信號拉低,產生起始信號
                                R_state     <=  4'd5        ; 
                            end
                        else
                            R_state <=  4'd4                ; // 如果SCL高電平中間標志沒出現就一直在這個狀態等着                          
                    end
                4'd5   :    // 發送1個字節,從高位開始發
                    begin
                        R_scl_en    <=  1'b1                ; // 打開SCL時鍾線
                        R_sda_mode  <=  1'b1                ; // 設置SDA為輸出
                        if(W_scl_low_mid)
                            begin
                                if(R_bit_cnt == 4'd8)
                                    begin
                                        R_bit_cnt   <=  4'd0            ;
                                        R_state     <=  4'd6            ; // 字節發完以后進入應答狀態
                                    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'd5 ; // 字節沒發完時在這個狀態一直等待 
                    end 
                4'd6   :    // 接收應答狀態的應答位
                    begin
                        R_scl_en    <=  1'b1  ; // 打開SCL時鍾線
                        R_sda_mode  <=  1'b0  ; // 設置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  :    // 校驗應答位
                    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 ; // 設置SDA的模式為輸出
                                        R_sda_reg   <=  1'b0 ; // 讀取完應答信號以后要把SDA信號設置成輸出並拉低,因為如果這個狀
                                                               // 態后面是停止狀態的話,需要SDA信號的上升沿,所以這里提前拉低它
                                    end
                                else
                                    R_state <= 4'd7    ;
                            end
                        else
                            R_state <=  4'd0 ;      
                    end
                4'd8   : // 發送停止信號
                    begin
                        R_scl_en    <=  1'b1        ; // 打開SCL時鍾線
                        R_sda_mode  <=  1'b1        ; // 設置SDA為輸出
                        if(W_scl_high_mid)
                            begin
                                R_sda_reg   <=  1'b1 ;
                                R_state     <=  4'd9 ;
                            end
                    end
                4'd9    :   // IIC寫操作結束
                    begin
                        R_scl_en    <=  1'b0 ; // 關閉SCL時鍾線
                        R_sda_mode  <=  1'b1 ; // 設置SDA為輸出
                        R_sda_reg   <=  1'b1 ; // 拉高SDA保持空閑狀態情況
                        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


讀邏輯

其中:

  I_clk是系統時鍾;

  I_rst_n是系統復位;

  I_iic_recv_en接收使能信號,當I_iic_recv_en為1時IIC主機(FPGA)才能從IIC從機接收數據;

  I_dev_addr[6:0]是IIC從機的設備地址;

  I_word_addr[7:0]是字地址,也就是我們想要讀取的IIC設備的內部存儲地址;

  O_read_data[7:0]是主機(FPGA)從IIC設備字地址中讀取的數據;

  O_done_flag是主機(FPGA)接收一個字節完成標志位,接收完成后會產生一個高脈沖;

  O_scl是IIC總線的串行時鍾線;

  IO_sda是IIC總線的串行數據線;

module iic_recv
(
    input                I_clk           , // 系統50MHz時鍾
    input                I_rst_n         , // 系統全局復位
    input                I_iic_recv_en   , // IIC發送使能位
    
    input        [6:0]   I_dev_addr      , // IIC設備的物理地址
    input        [7:0]   I_word_addr     , // IIC設備的字地址,即我們想操作的IIC的內部地址
    output  reg  [7:0]   O_read_data     , // 從IIC設備的字地址讀出來的數據   
    output  reg          O_done_flag     , // 讀或寫IIC設備結束標志位
    
    // 標准的IIC設備總線
    output               O_scl           , // IIC總線的串行時鍾線
    inout                IO_sda            // IIC總線的雙向數據線
);          

parameter   C_DIV_SELECT        =   10'd500 ; // 分頻系數選擇

parameter   C_DIV_SELECT0       =   (C_DIV_SELECT >> 2)  -  1           , // 用來產生IIC總線SCL高電平最中間的標志位
            C_DIV_SELECT1       =   (C_DIV_SELECT >> 1)  -  1           , // 用來產生IIC串行時鍾線
            C_DIV_SELECT2       =   (C_DIV_SELECT0 + C_DIV_SELECT1) + 1 , // 用來產生IIC總線SCL低電平最中間的標志位
            C_DIV_SELECT3       =   (C_DIV_SELECT >> 1) + 1             ; // 用來產生IIC總線SCL下降沿標志位
 

reg     [9:0]   R_scl_cnt       ; // 用來產生IIC總線SCL時鍾線的計數器   
reg             R_scl_en        ; // IIC總線SCL時鍾線使能信號
reg     [3:0]   R_state         ; 
reg             R_sda_mode      ; // 設置SDA模式,1位輸出,0為輸入
reg             R_sda_reg       ; // SDA寄存器
reg     [7:0]   R_load_data     ; // 發送/接收過程中加載的數據,比如設備物理地址,字地址和數據等
reg     [3:0]   R_bit_cnt       ; // 發送字節狀態中bit個數計數
reg             R_ack_flag      ; // 應答標志
reg     [3:0]   R_jump_state    ; // 跳轉狀態,傳輸一個字節成功並應答以后通過這個變量跳轉到導入下一個數據的狀態
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 ; // 產生串行時鍾信號O_scl
assign W_scl_low_mid  = (R_scl_cnt == C_DIV_SELECT2) ? 1'b1 : 1'b0 ; // 產生scl低電平正中間標志位
assign W_scl_high_mid = (R_scl_cnt == C_DIV_SELECT0) ? 1'b1 : 1'b0 ; // 產生scl高電平正中間標志位
assign W_scl_neg       = (R_scl_cnt == C_DIV_SELECT3) ? 1'b1 : 1'b0 ; // 產生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設備發送數據
        begin
            case(R_state)
                4'd0    :   // 空閑狀態,用來初始化相關所有信號
                    begin
                        R_sda_mode      <=  1'b1 ; // 設置SDA為輸出
                        R_sda_reg       <=  1'b1 ; // 設置SDA為高電平
                        R_scl_en        <=  1'b0 ; // 關閉SCL時鍾線
                        R_state         <=  4'd1 ; // 下一個狀態是加載設備物理地址狀態
                        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設備物理地址 
                    begin
                        R_load_data <=  {I_dev_addr, 1'b0}  ;
                        R_state     <=  4'd3                ; // 加載完設備物理地址以后進入起始狀態
                        R_jump_state <=  R_state + 1'b1     ; 
                    end
                4'd2   :   // 加載IIC設備字地址                     
                    begin                                   
                        R_load_data <=  I_word_addr         ; 
                        R_state     <=  4'd4                ;
                        R_jump_state <=  R_state + 5'd5      ; // 設置這里是為了這一輪發送並應答后跳到第二次啟始位
                    end 
                4'd3    :   // 發送第一個起始信號
                    begin
                        R_scl_en    <=  1'b1                ; // 打開時鍾
                        R_sda_mode  <=  1'b1                ; // 設置SDA的模式為輸出
                        if(W_scl_high_mid)
                            begin
                                R_sda_reg   <=  1'b0        ; // 在SCL高電平的正中間把SDA引腳拉低產生一個下降沿
                                R_state     <=  4'd4        ; // 下一個狀態是發送一個字節數據(IIC設備的物理地址) 
                            end
                        else
                            R_state <=  4'd3                ;    
                    end                      
                4'd4    :   // 發送一個字節
                    begin
                        R_scl_en    <=  1'b1                ; // 打開時鍾
                        R_sda_mode  <=  1'b1                ; // 設置SDA的模式為輸出
                        if(W_scl_low_mid)                     // 在SCL低電平的最中間改變數據
                            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    :   // 接收應答狀態應答位
                    begin
                        R_scl_en    <=  1'b1 ; // 打開時鍾
                        R_sda_reg   <=  1'b0 ;
                        R_sda_mode  <=  1'b0 ; // 設置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    :   // 校驗應答位
                    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 ; // 設置SDA的模式為輸出
                                        R_sda_reg   <=  1'b1 ; // 設置SDA的引腳電平拉高,方便后面產生第二次起始位
                                    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 ; // 設置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設備物理地址 ,但這次地址最后一位應該為1,表示讀操作                    
                    begin                                   
                        R_load_data     <=  {I_dev_addr, 1'b1}  ; // 前7bit是設備物理地址,最后一位1表示讀操作
                        R_state         <=  4'd4            ;
                        R_jump_state    <=  4'd9            ; // 設置這里是為了這一輪發送並應答后跳到第二次啟始位
                    end
                4'd9    :   // 讀一個字節數據
                    begin
                        R_scl_en    <=  1'b1 ; // 打開時鍾
                        R_sda_mode  <=  1'b0 ; // 設置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    :  // 讀完一個字節數據以后進入10,主機發送一個非應答信號1
                    begin
                        R_scl_en    <=  1'b1 ; // 打開時鍾
                        R_sda_mode  <=  1'b1 ; // 設置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 ; // 設置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 ; // 設置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 ; // 關閉SCL時鍾線
                        R_sda_mode      <=  1'b1 ; // 設置SDA為輸出
                        R_sda_reg       <=  1'b1 ; // 拉高SDA保持空閑狀態情況
                        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

endmodule

IIC設備多字節連續讀寫操作

有了上面接收模塊的基礎,實現這段時序不算困難。只要多增加幾個加載數據的狀態就可以了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM