0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)

FPGA之家 ? 來源:FPGA之家 ? 作者:FPGA之家 ? 2022-07-15 09:01 ? 次閱讀

本系列將帶來FPGA的系統(tǒng)性學(xué)習(xí),從最基本的數(shù)字電路基礎(chǔ)開始,最詳細(xì)操作步驟,最直白的言語描述,手把手的“傻瓜式”講解,讓電子、信息、通信類專業(yè)學(xué)生、初入職場小白及打算進(jìn)階提升的職業(yè)開發(fā)者都可以有系統(tǒng)性學(xué)習(xí)的機(jī)會(huì)。

系統(tǒng)性的掌握技術(shù)開發(fā)以及相關(guān)要求,對個(gè)人就業(yè)以及職業(yè)發(fā)展都有著潛在的幫助,希望對大家有所幫助。后續(xù)會(huì)陸續(xù)更新 Xilinx 的 Vivado、ISE 及相關(guān)操作軟件的開發(fā)的相關(guān)內(nèi)容,學(xué)習(xí)FPGA設(shè)計(jì)方法及設(shè)計(jì)思想的同時(shí),實(shí)操結(jié)合各類操作軟件,會(huì)讓你在技術(shù)學(xué)習(xí)道路上無比的順暢,告別技術(shù)學(xué)習(xí)小BUG卡破腦殼,告別目前忽悠性的培訓(xùn)誘導(dǎo),真正的去學(xué)習(xí)去實(shí)戰(zhàn)應(yīng)用,這種快樂試試你就會(huì)懂的。話不多說,上貨。

IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)

I2C即 Inter-Integrated Circuit(集成電路總線),這種總線類型是由飛利浦半導(dǎo)體公司在八十年代初設(shè)計(jì)出來的一種簡單、雙向、二線制、同步串行總線,主要是用來連接整體電路(ICS) ,I2C是一種多向控制總線,也就是說多個(gè)芯片可以連接到同一總線結(jié)構(gòu)下,同時(shí)每個(gè)芯片都可以作為實(shí)時(shí)數(shù)據(jù)傳輸?shù)目刂圃础_@種方式簡化了信號(hào)傳輸總線接口。

I2C線是一種串行數(shù)據(jù)總線,只有二根信號(hào)線,一根是雙向的數(shù)據(jù)線SDA,另一根是時(shí)鐘SCL,兩條線可以掛多個(gè)設(shè)備。I2C設(shè)備(絕大多數(shù))里有個(gè)固化的地址,只有在兩條線上傳輸?shù)闹档扔?/span>I2C設(shè)備的固化地址時(shí),其才會(huì)作出響應(yīng)。通常我們?yōu)榱朔奖惆?/span>I2C設(shè)備分為主設(shè)備和從設(shè)備,基本上誰控制時(shí)鐘線(即控制SCL的電平高低變換)誰就是主設(shè)備。

ceb4cd6a-03d4-11ed-ba43-dac502259ad0.png

A段為空閑段,在不進(jìn)行數(shù)據(jù)傳輸時(shí),SCL 和 SDA 保持為高電平。

B段為起始段(START CONDITION),在進(jìn)行任何數(shù)據(jù)傳輸前,都應(yīng)該先有一個(gè)起始段。當(dāng) SCL 為高電平期間,SDA有一個(gè)從高到低的變化視為起始段。

C段為結(jié)束段(STOP CONDITION),在數(shù)據(jù)傳輸結(jié)束后,都應(yīng)該有一個(gè)結(jié)束段。當(dāng)SCL為高電平期間,SDA有一個(gè)從低到高的變化視為結(jié)束段。

D段分為兩段數(shù)據(jù)有效段(ADDRESS OR ACKNOWLEDGE)和數(shù)據(jù)修改段(DATA ALLOWED TO CHANGE)。在傳輸數(shù)據(jù)時(shí),在 SCL 為高電平期間,SDA 不能夠發(fā)生改變,視為數(shù)據(jù)有效段;在傳輸數(shù)據(jù)時(shí),在 SCL 為低電平期間,SDA 可以發(fā)生改變(修改為下一個(gè)要發(fā)送的bit),視為數(shù)據(jù)修改段。

IIC的傳輸總結(jié)為:空閑時(shí),SCL 和 SDA 都是高電平;發(fā)送信息前,首先發(fā)送起始段(SCL為高時(shí),SDA有一個(gè)從高到低的變化);在發(fā)送信息時(shí),每一個(gè) SCL 脈沖,發(fā)送或者接收1bit,該bit在SCL為低電平時(shí)發(fā)生改變,在SCL為高電平時(shí),保持穩(wěn)定;發(fā)送信息完成后,發(fā)送結(jié)束段(SCL 為高時(shí),SDA 有一個(gè)從低到高的變化)。

SDA的數(shù)據(jù)線是雙向傳輸?shù)?。在讀取時(shí),SDA 從從設(shè)備到主設(shè)備;在寫入時(shí),SDA從主設(shè)備到從設(shè)備。

在 IIC 傳輸時(shí),為了保證通信的成功。發(fā)送方每發(fā)送8 bit 數(shù)據(jù)后,要求接收方回復(fù)一個(gè)應(yīng)答位。

cec38f26-03d4-11ed-ba43-dac502259ad0.png

應(yīng)答位為低電平有效。

  • EEPROM(24LC64)簡介

24LC64是容量為8K*8bit(64Kbit)通信接口為IIC的EEPROM。EEPROM擁有掉電不丟失的功能,經(jīng)常用來保存一些配置信息等等。這個(gè)器件可以一次進(jìn)行最多32字節(jié)的頁寫入和一次進(jìn)行整片讀出的功能。

ceeb37ba-03d4-11ed-ba43-dac502259ad0.png

cefee53a-03d4-11ed-ba43-dac502259ad0.png

A0、A1、A2為用戶可配置的芯片選擇端。該管腳一般都連接電源或者地,在通信時(shí),24LC64作為從機(jī),主機(jī)在發(fā)送的命令中存在地址號(hào),當(dāng)?shù)刂诽?hào)與A0、A1、A2管腳的電平相同時(shí),該芯片視為被選中。

SDA和SCL為IIC協(xié)議中的時(shí)鐘線和數(shù)據(jù)線。

WP為寫保護(hù)端,當(dāng)連接高電平時(shí),該芯片只能夠被讀,不能夠?qū)懭?;?dāng)連接低電平或者空置時(shí),該芯片可以被讀寫。

24LC64的寫入方式有兩種:單字節(jié)寫入和多字節(jié)寫入。

單字節(jié)寫入時(shí)序圖:

cf118db6-03d4-11ed-ba43-dac502259ad0.png

在控制字節(jié)中,1010為EEPROM的標(biāo)號(hào);A2、A1、A0為主機(jī)發(fā)送的地址號(hào),接收設(shè)備接收到此信息后,會(huì)與本身的A2、A1、A0相對比,相同時(shí),視為被選中;最后1bit,0表示寫入、1表示讀出。

在高字節(jié)地址中,高三位地址無效。24LC64的地址范圍為8K,地址位13位即可。13位地址分為高五低八,在IIC中規(guī)定每次發(fā)送8bit,所以高五的前面必須發(fā)送任意3bit。

多字節(jié)寫入時(shí)序圖:

cf2a0bf2-03d4-11ed-ba43-dac502259ad0.png

DATA BYTE 0被寫入到指定的地址中,后續(xù)的數(shù)據(jù)寫入到指定地址的后續(xù)地址中。EEPROM中每32個(gè)字節(jié)分為一塊,寫入時(shí)不能跨塊寫入。

24LC64的讀出方式有三種:當(dāng)前地址單字節(jié)讀、任意地址單字節(jié)讀和任意地址多字節(jié)讀。

當(dāng)前地址單字節(jié)讀的時(shí)序圖如下:

cf40941c-03d4-11ed-ba43-dac502259ad0.png

當(dāng)主機(jī)接收24LC64的數(shù)據(jù)后,不發(fā)送ACK,而是發(fā)送高電平。

任意地址單字節(jié)讀取時(shí)序圖,如下:

cf5012d4-03d4-11ed-ba43-dac502259ad0.png

首先寫入指定地址,然后按照當(dāng)前地址讀即可。

任意地址多字節(jié)讀,時(shí)序如下:

cf642382-03d4-11ed-ba43-dac502259ad0.png

任意地址多字節(jié)讀就是在任意地址單字節(jié)讀的時(shí)序中,接收到從機(jī)發(fā)送的數(shù)據(jù)后,不要發(fā)送NO ACK而是發(fā)送ACK,此時(shí)從機(jī)就會(huì)繼續(xù)發(fā)送后續(xù)地址的數(shù)據(jù),直到讀取的長度符合設(shè)計(jì)要求,然后發(fā)送NO ACK以及STOP結(jié)束傳輸。

下圖為24LC64的時(shí)序參數(shù)圖。

cf75cdb2-03d4-11ed-ba43-dac502259ad0.png

cf89be8a-03d4-11ed-ba43-dac502259ad0.png

cf97eef6-03d4-11ed-ba43-dac502259ad0.png

根據(jù)時(shí)序參數(shù),決定將IIC的速率定為50KHz。發(fā)送時(shí),數(shù)據(jù)改變在SCL的低電平的正中間;讀取時(shí),在SCL高電平的正中間進(jìn)行讀取。

IIC一般用在配置或者讀取少量的信息,對于速率來說要求相對較低。

在開發(fā)板上,搭載了一個(gè)24LC64。

電路原理圖如下:

cfb7d2ac-03d4-11ed-ba43-dac502259ad0.png

A0、A1、A2都被接地,主機(jī)再發(fā)送控制字節(jié)時(shí),需要將此三位發(fā)送低電平。

WP管腳被接地,24LC64的整個(gè)片子都可以被讀寫。

  • 雙向IO介紹

在IIC協(xié)議中,SDA作為數(shù)據(jù)線使用,每一個(gè)SCL的脈沖,傳遞一bit的數(shù)據(jù)。在讀取時(shí),SDA由24LC64發(fā)送,F(xiàn)PGA接收;在寫入時(shí),SDA由FPGA發(fā)送,24LC64接收。所以IIC協(xié)議只能實(shí)現(xiàn)半雙工通信。

在硬件電路中,一般是通過三態(tài)門來進(jìn)行控制雙向IO。

cfd7f06e-03d4-11ed-ba43-dac502259ad0.png

此電路在FPGA的IO中也是存在的。下面來分析具體的工作原理

三態(tài)門有三個(gè)端子,分別是輸入端(obuf),輸出端(SDA)和控制端(out_en)。工作方式為:當(dāng)out_en有效時(shí),obuf的值就可以輸出到SDA;當(dāng)out_en無效時(shí),obuf的值不能輸出到SDA,那么三態(tài)門就會(huì)輸出高阻態(tài)。

高阻態(tài),字節(jié)理解為阻值特別大,也可以理解為斷開。高阻態(tài)與任何電平值相連接,所連接的線上的電平值都是和對方保持一致。在此可以延伸一下:所有的端口都是輸出,包括輸入端口在內(nèi),也會(huì)對外呈現(xiàn)出一種狀態(tài),所以輸入端口對外呈現(xiàn)的狀態(tài)就是高阻態(tài)。對于雙向IO來說,輸出時(shí),正常輸出即可;輸入時(shí),就要呈現(xiàn)為高阻態(tài)。

  • 設(shè)計(jì)要求

設(shè)計(jì)任意地址的單字節(jié)讀寫控制器。大多數(shù)是配置或者讀取少量的信息,對于多字節(jié)的讀寫,可以采用多次單字節(jié)的讀寫完成。

  • 設(shè)計(jì)分析

24LC64的尋址范圍為8K,地址bit共計(jì)13bit,所以需要高5bit和低8bit。在有些兼容IIC協(xié)議接口的器件中,尋址范圍較少,8bit足夠表示,所以在設(shè)計(jì)時(shí),設(shè)計(jì)出適合兩種尋址方式的驅(qū)動(dòng)。

SCL的實(shí)現(xiàn)采用計(jì)數(shù)器控制,根據(jù)規(guī)定好的頻率,在合適的地方進(jìn)行拉高或者拉低。由于SDA的變化都發(fā)生在SCL為高或者為低的中間,所以還需要產(chǎn)生對應(yīng)的脈沖。

SDA根據(jù)協(xié)議和對應(yīng)的脈沖進(jìn)行發(fā)送對應(yīng)的協(xié)議段。

  • 架構(gòu)設(shè)計(jì)和信號(hào)說明

本模塊命名為iic_drive。

cfe7a900-03d4-11ed-ba43-dac502259ad0.png

cff9962e-03d4-11ed-ba43-dac502259ad0.png

暫不連接的端口,在后續(xù)測試時(shí),會(huì)有其他模塊來控制或者讀取。

  • iic_drive設(shè)計(jì)實(shí)現(xiàn)

iic_scl采用計(jì)數(shù)器的方式實(shí)現(xiàn)。外部的時(shí)鐘為50MHz,iic_scl為100KHz,所以計(jì)數(shù)器需要記錄500個(gè)時(shí)鐘周期。

計(jì)數(shù)器由cnt_en控制。cnt_en有效時(shí),cnt從0到500不斷循環(huán);當(dāng)cnt_en無效時(shí),cnt保持等于0。

iic_scl在cnt等于0至249時(shí),保持高電平;在250至499時(shí),保持低電平。cnt等于125時(shí),正好是iic_scl高電平的中間,用脈沖flag_high表示;cnt等于375時(shí),正好是iic_scl低電平的中間,用脈沖flag_low表示。

其他信號(hào)采用狀態(tài)機(jī)作為輸出。out_en為三態(tài)門控制信號(hào),sda_obuf為三態(tài)門輸出端,drive_cnt為發(fā)送或者接收的計(jì)數(shù)器,temp為發(fā)送或者接收的中間寄存器。

狀態(tài)轉(zhuǎn)移圖如下:

d00e2274-03d4-11ed-ba43-dac502259ad0.png

設(shè)計(jì)代碼為:

module iic_drive (
  input   wire                clk,  input   wire                rst_n,    input   wire                start_flag,  input   wire    [6:0]       slave_addr,  input   wire                wren,  input   wire                addr_sel,  input   wire    [15:0]      addr,  input   wire    [7:0]       wdata,    output  reg                 iic_scl,  inout   wire                iic_sda,    output  reg                 iic_done,  output  reg     [7:0]       rdata);    parameter       FREQ_clk    =   50_000_000;  parameter       FREQ_iic    =   100_000;    localparam      T_SCL       =   FREQ_clk/FREQ_iic;    localparam      IDLE        =   16'b0000_0000_0000_0001;  localparam      START       =   16'b0000_0000_0000_0010;  localparam      CTRL        =   16'b0000_0000_0000_0100;  localparam      CTRL_ACK    =   16'b0000_0000_0000_1000;  localparam      HADDR       =   16'b0000_0000_0001_0000;  localparam      HADDR_ACK   =   16'b0000_0000_0010_0000;  localparam      LADDR       =   16'b0000_0000_0100_0000;  localparam      LADDR_ACK   =   16'b0000_0000_1000_0000;  localparam      WR          =   16'b0000_0001_0000_0000;  localparam      WR_ACK      =   16'b0000_0010_0000_0000;  localparam      RD_START    =   16'b0000_0100_0000_0000;  localparam      RD_CTRL     =   16'b0000_1000_0000_0000;  localparam      RD_CTRL_ACK =   16'b0001_0000_0000_0000;  localparam      RD          =   16'b0010_0000_0000_0000;  localparam      NO_ACK      =   16'b0100_0000_0000_0000;  localparam      STOP        =   16'b1000_0000_0000_0000;    reg             [15:0]      c_state;  reg             [15:0]      n_state;  reg             [31:0]      cnt;  reg                         cnt_en;  reg                         flag_high;  reg                         flag_low;  reg                         out_en;  reg                         sda_obuf;  reg             [3:0]       drive_cnt;  reg             [7:0]       temp;    assign iic_sda = (out_en == 1'b1) ? sda_obuf : 1'bz;    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      cnt <= 32'd0;    else      if (cnt < T_SCL - 1'b1 && cnt_en == 1'b1)        cnt <= cnt + 1'b1;      else        cnt <= 32'd0;  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      iic_scl <= 1'b1;    else      if (cnt < T_SCL/2)        iic_scl <= 1'b1;      else        iic_scl <= 1'b0;  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      flag_high <= 1'b0;    else        if (cnt == T_SCL/4 - 1'b1)        flag_high <= 1'b1;      else          flag_high <= 1'b0;  end
  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      flag_low <= 1'b0;    else      if (cnt == (T_SCL * 3)/4 - 1'b1)        flag_low <= 1'b1;      else        flag_low <= 1'b0;  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= IDLE;    else      c_state <= n_state;  end    always @ * begin    case (c_state)      IDLE        :     begin        if (start_flag == 1'b1)          n_state = START;        else          n_state = IDLE;      end          START       :     begin        if (flag_high == 1'b1)          n_state = CTRL;        else          n_state = START;      end            CTRL        :     begin        if (flag_low == 1'b1 && drive_cnt == 4'd8)          n_state = CTRL_ACK;        else          n_state = CTRL;      end            CTRL_ACK    :     begin        if (flag_high == 1'b1)          if (iic_sda == 1'b0)            if (addr_sel == 1'b1)              n_state = HADDR;            else              n_state = LADDR;          else            n_state = START;        else          n_state = CTRL_ACK;      end            HADDR       :     begin        if (flag_low == 1'b1 && drive_cnt == 4'd8)          n_state = HADDR_ACK;        else          n_state = HADDR;      end            HADDR_ACK   :     begin        if (flag_high == 1'b1)          if (iic_sda == 1'b0)            n_state = LADDR;          else            n_state = START;        else          n_state = HADDR_ACK;      end            LADDR       :     begin        if (flag_low == 1'b1 && drive_cnt == 4'd8)          n_state = LADDR_ACK;        else          n_state = LADDR;      end            LADDR_ACK   :     begin        if (flag_high == 1'b1)          if (iic_sda == 1'b0)            if (wren == 1'b1)              n_state = WR;            else              n_state = RD_START;          else            n_state = START;        else          n_state = LADDR_ACK;      end            WR          :     begin        if (flag_low == 1'b1 && drive_cnt == 4'd8)          n_state = WR_ACK;        else          n_state = WR;      end            WR_ACK      :     begin        if (flag_high == 1'b1)          if (iic_sda == 1'b0)            n_state = STOP;          else            n_state = START;        else          n_state = WR_ACK;      end            STOP        :     begin        if (flag_high == 1'b1)          n_state = IDLE;        else          n_state = STOP;      end            RD_START    :     begin        if (flag_high == 1'b1)          n_state = RD_CTRL;        else          n_state = RD_START;      end            RD_CTRL     :     begin        if (flag_low == 1'b1 && drive_cnt == 4'd8)          n_state = RD_CTRL_ACK;        else          n_state = RD_CTRL;      end            RD_CTRL_ACK :     begin        if (flag_high == 1'b1)          if (iic_sda == 1'b0)            n_state = RD;          else            n_state = START;        else          n_state = RD_CTRL_ACK;      end            RD          :     begin        if (flag_low == 1'b1 && drive_cnt == 4'd8)          n_state = NO_ACK;        else          n_state = RD;      end            NO_ACK      :     begin        if (flag_high == 1'b1)          n_state = STOP;        else          n_state = NO_ACK;      end            default     :     n_state = IDLE;    endcase  end
  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      cnt_en <= 1'b0;    else      case (c_state)        IDLE          : cnt_en <= 1'b0;        CTRL_ACK,        HADDR_ACK,        LADDR_ACK,        WR_ACK,        RD_CTRL_ACK   : begin          if (flag_high == 1'b1)            if (iic_sda == 1'b0)              cnt_en <= 1'b1;            else              cnt_en <= 1'b0;          else            cnt_en <= cnt_en;        end        default       :   cnt_en <= 1'b1;      endcase  end
  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      out_en <= 1'b0;    else      case (c_state)        IDLE          :   out_en <= 1'b0;        START         :   out_en <= 1'b1;        CTRL          :   begin          if (flag_low == 1'b1 && drive_cnt == 4'd8)            out_en <= 1'b0;          else            out_en <= 1'b1;        end        CTRL_ACK      :   out_en <= 1'b0;        HADDR         :   begin          if (flag_low == 1'b1 && drive_cnt == 4'd8)            out_en <= 1'b0;          else            if (flag_low == 1'b1)              out_en <= 1'b1;            else              out_en <= out_en;        end        HADDR_ACK     :   out_en <= 1'b0;        LADDR         :   begin          if (flag_low == 1'b1 && drive_cnt == 4'd8)            out_en <= 1'b0;          else            if (flag_low == 1'b1)              out_en <= 1'b1;            else              out_en <= out_en;        end        LADDR_ACK     :   out_en <= 1'b0;        WR            :   begin          if (flag_low == 1'b1 && drive_cnt == 4'd8)            out_en <= 1'b0;          else            if (flag_low == 1'b1)              out_en <= 1'b1;            else              out_en <= out_en;        end        WR_ACK        :   out_en <= 1'b0;        STOP          :   begin          if (flag_low == 1'b1)            out_en <= 1'b1;          else            out_en <= out_en;        end        RD_START      :   begin          if (flag_low == 1'b1)            out_en <= 1'b1;          else              out_en <= out_en;        end                RD_CTRL       :   begin          if (flag_low == 1'b1 && drive_cnt == 4'd8)            out_en <= 1'b0;          else            out_en <= 1'b1;        end        RD_CTRL_ACK   :   out_en <= 1'b0;        RD            :   begin          if (flag_low == 1'b1 && drive_cnt == 4'd8)            out_en <= 1'b1;          else            out_en <= 1'b0;        end                NO_ACK        :   out_en <= 1'b1;                default       :   out_en <= 1'b0;      endcase  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      sda_obuf <= 1'b1;    else      case (c_state)        IDLE          :     sda_obuf <= 1'b1;        START         :     begin          if (flag_high == 1'b1)            sda_obuf <= 1'b0;          else            sda_obuf <= 1'b1;        end                CTRL          :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            sda_obuf <= temp[7];          else            sda_obuf <= sda_obuf;        end                CTRL_ACK      :     sda_obuf <= 1'b0;        HADDR         :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            sda_obuf <= temp[7];          else            sda_obuf <= sda_obuf;        end                HADDR_ACK     :     sda_obuf <= 1'b0;        LADDR         :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            sda_obuf <= temp[7];          else            sda_obuf <= sda_obuf;        end        LADDR_ACK     :     sda_obuf <= 1'b0;        WR            :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            sda_obuf <= temp[7];          else            sda_obuf <= sda_obuf;        end        WR_ACK        :     sda_obuf <= 1'b0;        STOP          :     begin          if (flag_low == 1'b1)            sda_obuf <= 1'b0;          else            if (flag_high == 1'b1)              sda_obuf <= 1'b1;            else              sda_obuf <= sda_obuf;        end        RD_START      :     begin          if (flag_low == 1'b1)            sda_obuf <= 1'b1;          else            if (flag_high == 1'b1)              sda_obuf <= 1'b0;            else              sda_obuf <= sda_obuf;        end        RD_CTRL       :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            sda_obuf <= temp[7];          else            sda_obuf <= sda_obuf;        end        RD_CTRL_ACK   :     sda_obuf <= 1'b0;        RD            :     begin          if (flag_low == 1'b1 && drive_cnt == 4'd8)            sda_obuf <= 1'b1;          else            sda_obuf <= sda_obuf;        end        NO_ACK        :     sda_obuf <= sda_obuf;        default       :     sda_obuf <= 1'b1;      endcase  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      drive_cnt <= 4'd0;    else      case (c_state)        IDLE        :     drive_cnt <= 4'd0;        START       :     drive_cnt <= 4'd0;        CTRL        :     begin          if (flag_low == 1'b1)            if (drive_cnt < 4'd8)              drive_cnt <= drive_cnt + 1'b1;            else              drive_cnt <= 4'd0;          else            drive_cnt <= drive_cnt;        end        CTRL_ACK    :     drive_cnt <= 4'd0;        HADDR       :     begin          if (flag_low == 1'b1)            if (drive_cnt < 4'd8)              drive_cnt <= drive_cnt + 1'b1;            else              drive_cnt <= 4'd0;          else            drive_cnt <= drive_cnt;        end        HADDR_ACK   :     drive_cnt <= 4'd0;        LADDR       :     begin          if (flag_low == 1'b1)            if (drive_cnt < 4'd8)              drive_cnt <= drive_cnt + 1'b1;            else              drive_cnt <= 4'd0;          else            drive_cnt <= drive_cnt;        end        LADDR_ACK   :     drive_cnt <= 4'd0;        WR          :     begin          if (flag_low == 1'b1)            if (drive_cnt < 4'd8)              drive_cnt <= drive_cnt + 1'b1;            else              drive_cnt <= 4'd0;          else            drive_cnt <= drive_cnt;        end        WR_ACK      :     drive_cnt <= 4'd0;        STOP        :     drive_cnt <= 4'd0;        RD_START    :     drive_cnt <= 4'd0;        RD_CTRL     :     begin          if (flag_low == 1'b1)            if (drive_cnt < 4'd8)              drive_cnt <= drive_cnt + 1'b1;            else              drive_cnt <= 4'd0;          else            drive_cnt <= drive_cnt;        end        RD_CTRL_ACK :     drive_cnt <= 4'd0;        RD          :     begin          if (flag_high == 1'b1 && drive_cnt < 4'd8)            drive_cnt <= drive_cnt + 1'b1;          else            if (flag_low == 1'b1 && drive_cnt == 4'd8)              drive_cnt <= 4'd0;            else              drive_cnt <= drive_cnt;        end        NO_ACK      :     drive_cnt <= 4'd0;        default     :     drive_cnt <= 4'd0;      endcase  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      temp <= 8'd0;    else      case (c_state)        IDLE          :     temp <= 8'd0;        START         :     begin          if (flag_high == 1'b1)            temp <= {slave_addr, 1'b0};          else              temp <= temp;        end        CTRL          :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            temp <= temp << 1'b1;          else            temp <= temp;        end        CTRL_ACK      :     begin          if (flag_high == 1'b1 && iic_sda == 1'b0)            if (addr_sel == 1'b1)              temp <= addr[15:8];            else              temp <= addr[7:0];          else            temp <= temp;        end        HADDR         :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            temp <= temp << 1'b1;          else            temp <= temp;        end        HADDR_ACK     :     begin          if (flag_high == 1'b1 && iic_sda == 1'b0)            temp <= addr[7:0];          else            temp <= temp;        end        LADDR         :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            temp <= temp << 1'b1;          else            temp <= temp;        end        LADDR_ACK     :     begin          if (flag_high == 1'b1 && iic_sda == 1'b0)            if (wren == 1'b1)              temp <= wdata;            else              temp <= {slave_addr, 1'b1};          else            temp <= temp;        end        WR            :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            temp <= temp << 1'b1;          else            temp <= temp;        end        WR_ACK        :     temp <= 8'd0;        STOP          :     temp <= 8'd0;        RD_START      :     temp <= temp;        RD_CTRL       :     begin          if (flag_low == 1'b1 && drive_cnt < 4'd8)            temp <= temp << 1'b1;          else            temp <= temp;        end        RD_CTRL_ACK   :     temp <= 8'd0;        RD            :     begin          if (flag_high == 1'b1 && drive_cnt < 4'd8)            temp <= {temp[6:0], iic_sda};          else            temp <= temp;        end        NO_ACK        :     temp <= 8'd0;                default       :     temp <= 8'd0;      endcase  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      iic_done <= 1'b0;    else      if (c_state == STOP && flag_high == 1'b1)        iic_done <= 1'b1;      else        iic_done <= 1'b0;  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      rdata <= 8'd0;    else      if (c_state == RD && flag_low == 1'b1 && drive_cnt == 4'd8)        rdata <= temp;      else        rdata <= rdata;  end  endmodule

在仿真時(shí),需要外部提供ACK。如果直接仿真的話,由于外部沒有提供ACK,導(dǎo)致一直都會(huì)重復(fù)發(fā)送一小段。在仿真中,我們自己給出ACK的難度也比較大。

24LC64的廠商幫我們解決了這個(gè)問題,他們在造出這個(gè)芯片的同時(shí),還設(shè)計(jì)出了可以幫助我們仿真調(diào)試的verliog模型。此模型放在msim->24LC64_sim_module中,文件名字為24LC64.v。此模型的sda也是雙向IO,所以在硬件設(shè)計(jì)時(shí),會(huì)在此IO上加上拉電阻,在仿真時(shí),我們也要模擬上拉電阻。

仿真設(shè)計(jì)如下:

`timescale 1ns/1ps
module iic_drive_tb;
  reg               clk;  reg               rst_n;    reg               start_flag;  reg     [6:0]     slave_addr;  reg               wren;  reg               addr_sel;  reg     [15:0]    addr;  reg     [7:0]     wdata;    wire              iic_scl;  wire              iic_sda;    wire              iic_done;  wire    [7:0]     rdata;    pullup(iic_sda);    iic_drive iic_drive_inst(
      .clk            (clk),      .rst_n          (rst_n),            .start_flag     (start_flag),      .slave_addr     (slave_addr),      .wren           (wren),      .addr_sel       (addr_sel),      .addr           (addr),      .wdata          (wdata),            .iic_scl        (iic_scl),      .iic_sda        (iic_sda),            .iic_done       (iic_done),      .rdata          (rdata)    );      M24LC64 M24LC64_inst(      .A0             (1'b0),       .A1             (1'b0),       .A2             (1'b0),       .WP             (1'b0),      .SDA            (iic_sda),       .SCL            (iic_scl)    );
  initial clk = 1'b0;  always # 10 clk = ~clk;    initial begin    rst_n = 1'b0;    start_flag = 1'b0;    slave_addr = 7'b1010_000;    wren = 1'b0;    addr_sel = 1'b1;    addr = 16'h5555;    wdata = 8'haa;    # 201    rst_n = 1'b1;    # 200;    @ (posedge clk);    # 2;    start_flag = 1'b1;    wren = 1'b1;    @ (posedge clk);    # 2;    start_flag = 1'b0;        @ (posedge iic_done);    # 200;    @ (posedge clk);    # 2;    start_flag = 1'b1;    wren = 1'b0;    @ (posedge clk);    # 2;    start_flag = 1'b0;        @ (posedge iic_done);    # 200    $stop;  end
endmodule

pullup(iic_sda):將iic_sda上加上拉電阻。

仿真時(shí),在地址16’h5555(雖然高三位沒有用),寫入了8’ha;寫入完成后,又將此數(shù)據(jù)讀出。

當(dāng)對24LC64進(jìn)行寫入之后,它會(huì)進(jìn)入到一個(gè)內(nèi)部寫循環(huán)(和flash類似),在此期間,不接受任何指令。所以在仿真時(shí),需要等待寫循環(huán)完成,時(shí)間為5ms(此參數(shù)在仿真模型中,可以進(jìn)行修改)。

寫入的仿真圖如下:

d036c1ca-03d4-11ed-ba43-dac502259ad0.png

讀出的仿真圖如下:

d0444660-03d4-11ed-ba43-dac502259ad0.png

  • 板級測試

上述的設(shè)計(jì)在使用時(shí)會(huì)有上游控制器進(jìn)行控制,所以板級測試還需要設(shè)計(jì)控制器。在控制器中只需要某一個(gè)地址寫入數(shù)據(jù),然后讀出即可。測試時(shí),可以多次更換地址和數(shù)據(jù),保證設(shè)計(jì)的正確性。

在測試時(shí),將slave_addr、addr_sel、addr、wdata設(shè)置為定值。slave_addr為7’b1010_000,addr_sel為1’b1,addr為16’h5555,wdata為8’haa。

將上有控制器命名為test_ctrl,頂層命名為test。

d051aa58-03d4-11ed-ba43-dac502259ad0.png

test_ctrl模塊采用狀態(tài)機(jī)設(shè)計(jì)實(shí)現(xiàn)。

d05e030c-03d4-11ed-ba43-dac502259ad0.png

test_ctrl的設(shè)計(jì)代碼如下:
module test_ctrl (
  input   wire                clk,  input   wire                rst_n,    output  reg                 start_flag,  output  reg                 wren,    input   wire                iic_done,  input   wire    [7:0]       rdata);
  localparam      WR           =   4'b0001;  localparam      WR_WAIT      =   4'b0010;  localparam      RD_WAIT      =   4'b0100;  localparam      TESTDONE     =   4'b1000;    reg             [3:0]       c_state;  reg             [3:0]       n_state;    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      c_state <= WR;    else      c_state <= n_state;  end    always @ * begin    case (c_state)      WR        :   n_state = WR_WAIT;      WR_WAIT   :   begin        if (iic_done == 1'b1)          n_state = RD_WAIT;        else          n_state = WR_WAIT;      end            RD_WAIT   :   begin        if (iic_done == 1'b1 && rdata == 8'haa)          n_state = TESTDONE;        else          n_state = RD_WAIT;      end            TESTDONE  :   n_state = TESTDONE;          default   :   n_state = WR;    endcase  end
  always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      start_flag <= 1'b0;    else      if ((c_state == WR) || (c_state == WR_WAIT && iic_done == 1'b1))        start_flag <= 1'b1;      else        start_flag <= 1'b0;  end    always @ (posedge clk, negedge rst_n) begin    if (rst_n == 1'b0)      wren <= 1'b0;    else      if (c_state == WR || c_state == WR_WAIT)        wren <= 1'b1;      else        wren <= 1'b0;  end   endmodule

test模塊負(fù)責(zé)將test_ctrl和iic_drive模塊進(jìn)行例化連接。將test模塊設(shè)置為頂層。

test模塊設(shè)計(jì)代碼如下:

module test (
  input     wire              clk,  input     wire              rst_n,    output    wire              iic_scl,  inout     wire              iic_sda);
  wire                        start_flag;  wire                        wren;  wire                        iic_done;  wire        [7:0]           rdata;    test_ctrl test_ctrl_inst(
      .clk          (clk),      .rst_n        (rst_n),            .start_flag   (start_flag),      .wren         (wren),            .iic_done     (iic_done),      .rdata        (rdata)    );      iic_drive iic_drive_inst(
      .clk          (clk),      .rst_n        (rst_n),            .start_flag   (start_flag),      .slave_addr   (7'b1010_000),      .wren         (wren),      .addr_sel     (1'b1),      .addr         (16'h5555),      .wdata        (8'haa),            .iic_scl      (iic_scl),      .iic_sda      (iic_sda),            .iic_done     (iic_done),      .rdata        (rdata)    );    endmodule

板級測試采用邏輯分析儀測試。

采樣時(shí)鐘選擇外部的50MHz,采樣深度選擇32K。

d06c93c2-03d4-11ed-ba43-dac502259ad0.png

觸發(fā)位置選擇post。

d076d350-03d4-11ed-ba43-dac502259ad0.png

將iic_sda、iic_scl、test_ctrl模塊中的RD_WAIT和TESTDONE兩個(gè)狀態(tài),共計(jì)四個(gè)信號(hào)進(jìn)行觀測。

d088ab8e-03d4-11ed-ba43-dac502259ad0.png

將RD_WAIT設(shè)置為上升沿觸發(fā)。進(jìn)入此狀態(tài)時(shí),剛剛寫入完成。

d0abb85e-03d4-11ed-ba43-dac502259ad0.png

點(diǎn)擊觸發(fā)后,按一下復(fù)位鍵。

d0b9bf9e-03d4-11ed-ba43-dac502259ad0.png

從波形圖中,可以分析出,在地址16’h5555中寫入了8’haa。

將RD_WAIT設(shè)置為donot care,將TESTDONE設(shè)置為上升沿,此時(shí)讀出剛剛完成。

d0d09764-03d4-11ed-ba43-dac502259ad0.png

觸發(fā)后,按一下復(fù)位按鍵。

d0eb0310-03d4-11ed-ba43-dac502259ad0.png

可以看出在地址16’h5555的位置,讀出aa。

注意發(fā)送時(shí),是在SCL的低電平的正中間;接收是在SCL的高電平的正中間;觀看波形時(shí),要和標(biāo)準(zhǔn)的時(shí)序圖進(jìn)行對比。

證明設(shè)計(jì)正確。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • FPGA
    +關(guān)注

    關(guān)注

    1620

    文章

    21510

    瀏覽量

    598994
  • 總線
    +關(guān)注

    關(guān)注

    10

    文章

    2817

    瀏覽量

    87712
  • IIC
    IIC
    +關(guān)注

    關(guān)注

    11

    文章

    292

    瀏覽量

    38147

原文標(biāo)題:FPGA零基礎(chǔ)學(xué)習(xí)精選 | IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)

文章出處:【微信號(hào):zhuyandz,微信公眾號(hào):FPGA之家】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)

    I2C 即 Inter-Integrated Circuit(集成電路總線),這種總線類型是由飛利浦半導(dǎo)體公司在八十年代初設(shè)計(jì)出來的一種簡單、雙向、二線制、同步串行總線,主要是用來連接整體電路(ICS) ,I2C是一種多向控制總線,也就是說多個(gè)芯片可以連接到同一總線結(jié)構(gòu)下,同時(shí)每個(gè)芯片都可以作為實(shí)時(shí)數(shù)據(jù)傳輸?shù)目刂圃?。這種方式簡化了信號(hào)傳輸總線接口。
    發(fā)表于 07-17 09:17 ?478次閱讀
    <b class='flag-5'>IIC</b><b class='flag-5'>協(xié)議</b><b class='flag-5'>驅(qū)動(dòng)</b>設(shè)計(jì)

    fpga入門的基礎(chǔ)知識(shí) FPGA零基礎(chǔ)學(xué)習(xí)IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)

    根據(jù)時(shí)序參數(shù),決定將IIC的速率定為50KHz。發(fā)送時(shí),數(shù)據(jù)改變在SCL的低電平的正中間;讀取時(shí),在SCL高電平的正中間進(jìn)行讀取。
    發(fā)表于 07-18 09:33 ?586次閱讀
    fpga入門的基礎(chǔ)知識(shí) FPGA零基礎(chǔ)學(xué)習(xí)<b class='flag-5'>IIC</b><b class='flag-5'>協(xié)議</b><b class='flag-5'>驅(qū)動(dòng)</b>設(shè)計(jì)

    FPGA實(shí)現(xiàn)IIC協(xié)議的設(shè)計(jì)

    今天給大家?guī)淼氖?b class='flag-5'>IIC通信,IIC協(xié)議應(yīng)用非常廣泛,例如與MPU6050進(jìn)行通信,配置OV5640攝像頭、驅(qū)動(dòng)OLED屏幕等等,都需要使用到IIC
    的頭像 發(fā)表于 03-04 10:49 ?1054次閱讀
    FPGA實(shí)現(xiàn)<b class='flag-5'>IIC</b><b class='flag-5'>協(xié)議</b>的設(shè)計(jì)

    【OneNET麒麟座試用體驗(yàn)】5.IIC協(xié)議與OLED的使用

    尤為重要。OLED是一種比較好的解決方案,空間小,卻可以顯示很多的內(nèi)容,而且相比于TFT,功耗更低。 之前在某寶上買的OLED屏幕。 可以看到,這個(gè)屏幕是用IIC協(xié)議驅(qū)動(dòng)的,所以我們應(yīng)該重點(diǎn)學(xué)習(xí)
    發(fā)表于 04-01 10:02

    STM32的IIC協(xié)議簡介

    文章目錄(一)IIC協(xié)議簡介(二)物理層和協(xié)議層簡介(三)IIC物理層:3.1、物理層的特點(diǎn)(四)IIC
    發(fā)表于 01-05 06:13

    如何使用代碼實(shí)現(xiàn)的IIC協(xié)議

    娘,具體功能無非就是讓單片機(jī)中能夠起到獲取時(shí)間的作用。該模塊是可以由IIC協(xié)議驅(qū)動(dòng)的,再加上所要的操作也是比較簡單,部分剛接觸IIC協(xié)議
    發(fā)表于 02-21 06:36

    請問IIC的設(shè)備驅(qū)動(dòng)兼容SMbus協(xié)議嗎?

    請問IIC的設(shè)備驅(qū)動(dòng)兼容SMbus協(xié)議么?
    發(fā)表于 02-21 10:38

    FPGA零基礎(chǔ)學(xué)習(xí):IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)

    不多說,上貨。IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)本篇實(shí)現(xiàn)基于叁芯智能科技的SANXIN -B01 FPGA開發(fā)板,以下為配套的教程,如有入手開發(fā)板,可以登錄官方淘寶店購買,還有配套的學(xué)習(xí)視頻。叁芯智能科技 FPGA
    發(fā)表于 03-21 17:56

    IIC總線協(xié)議

    IIC總線協(xié)議,感興趣的小伙伴們可以看看。
    發(fā)表于 07-26 16:29 ?58次下載

    IIC總線協(xié)議中文版

    IIC協(xié)議的介紹,中文版,易懂,可以在里面了解到IIC是何物,如何使用以及定義
    發(fā)表于 08-09 15:08 ?0次下載

    iic通信協(xié)議是什么

    IIC協(xié)議是二線制,信號(hào)線包含SDA和SCL,且信號(hào)線是雙向的,開路結(jié)構(gòu),需要通過上拉電阻到VCC,具體的電阻值影響的是信號(hào)反應(yīng)速度和驅(qū)動(dòng)能力。
    發(fā)表于 09-13 16:01 ?2w次閱讀
    <b class='flag-5'>iic</b>通信<b class='flag-5'>協(xié)議</b>是什么

    基于叁芯智能科技的SANXIN -B01 FPGA開發(fā)板的IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)

    IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì) 作者:郝旭帥校對:陸輝 本篇實(shí)現(xiàn)基于叁芯智能科技的SANXIN -B01FPGA開發(fā)板,以下為配套的教程,如有入手開發(fā)板,可以登錄官方淘寶店購買,還有配套的學(xué)習(xí)視頻。 叁芯智能
    的頭像 發(fā)表于 09-28 11:07 ?2925次閱讀
    基于叁芯智能科技的SANXIN -B01 FPGA開發(fā)板的<b class='flag-5'>IIC</b><b class='flag-5'>協(xié)議</b><b class='flag-5'>驅(qū)動(dòng)</b>設(shè)計(jì)

    FPGA基礎(chǔ)學(xué)習(xí)IIC協(xié)議驅(qū)動(dòng)設(shè)計(jì)詳解

    本系列將帶來FPGA的系統(tǒng)性學(xué)習(xí),從最基本的數(shù)字電路基礎(chǔ)開始,最詳細(xì)操作步驟,最直白的言語描述,手把手的“傻瓜式”講解,讓電子、信息、通信類專業(yè)學(xué)生、初入職場小白及打算進(jìn)階提升的職業(yè)開發(fā)者都可以有系統(tǒng)性學(xué)習(xí)的機(jī)會(huì)。
    的頭像 發(fā)表于 05-02 09:15 ?2772次閱讀
    FPGA基礎(chǔ)學(xué)習(xí)<b class='flag-5'>IIC</b><b class='flag-5'>協(xié)議</b><b class='flag-5'>驅(qū)動(dòng)</b>設(shè)計(jì)詳解

    使用IIC驅(qū)動(dòng)MPU6050時(shí)為什么總讀取失敗

    01概述 在之前的文章中《I2C基礎(chǔ)原理及協(xié)議》中詳細(xì)講解了IIC協(xié)議,并且使用是NXP的官方手冊,demo示例使用IIC讀取RTC芯片,運(yùn)行正常,沒有任何問題。并且更新了《
    的頭像 發(fā)表于 11-06 10:10 ?6779次閱讀
    使用<b class='flag-5'>IIC</b>去<b class='flag-5'>驅(qū)動(dòng)</b>MPU6050時(shí)為什么總讀取失敗

    振弦采集模塊的通訊協(xié)議IIC

    振弦采集模塊的通訊協(xié)議IICIIC 通訊協(xié)議本身即是基于設(shè)備地址和寄存器的物理層通訊協(xié)議, VMXXX 使用
    的頭像 發(fā)表于 11-17 10:02 ?744次閱讀
    振弦采集模塊的通訊<b class='flag-5'>協(xié)議</b>( <b class='flag-5'>IIC</b>)