FPGA零基础学习精选|IIC协议驱动设计

原创 FPGA技术江湖 2025-04-07 09:00

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。


本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。

系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。


IIC协议驱动设计


作者:郝旭帅  校对:陆辉


本篇实现基于叁芯智能科技的SANXIN -B01 FPGA开发板,以下为配套的教程,如有入手开发板,可以登录官方淘宝店购买,还有配套的学习视频。

SANXIN-B01开发板verilog教程V3电子版


FPGA工程师线上研修班,常年开班,随时报名,新增高速接口、光纤通信等内容!


C 即 Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,C是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。这种方式简化了信号传输总线接口。


线是一种串行数据总线,只有二根信号线,一根是双向的数据线 SDA,另一根是时钟线 SCL,两条线可以挂多个设备。 C 设备(绝大多数)里有个固化的地址,只有在两条线上传输的值等于 C 设备的固化地址时,其才会作出响应。通常我们为了方便把 C 设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。


A段为空闲段,在不进行数据传输时,SCL 和 SDA 保持为高电平。


B段为起始段(START CONDITION),在进行任何数据传输前,都应该先有一个起始段。当 SCL 为高电平期间,SDA有一个从高到低的变化视为起始段。


C段为结束段(STOP CONDITION),在数据传输结束后,都应该有一个结束段。当SCL为高电平期间,SDA有一个从低到高的变化视为结束段。


D段分为两段数据有效段(ADDRESS OR ACKNOWLEDGE)和数据修改段(DATA ALLOWED TO CHANGE)。在传输数据时,在 SCL 为高电平期间,SDA 不能够发生改变,视为数据有效段;在传输数据时,在 SCL 为低电平期间,SDA 可以发生改变(修改为下一个要发送的bit),视为数据修改段。


IIC的传输总结为:空闲时,SCL 和 SDA 都是高电平;发送信息前,首先发送起始段(SCL为高时,SDA有一个从高到低的变化);在发送信息时,每一个 SCL 脉冲,发送或者接收1bit,该bit在SCL为低电平时发生改变,在SCL为高电平时,保持稳定;发送信息完成后,发送结束段(SCL 为高时,SDA 有一个从低到高的变化)。


SDA的数据线是双向传输的。在读取时,SDA 从从设备到主设备;在写入时,SDA从主设备到从设备。


在 IIC 传输时,为了保证通信的成功。发送方每发送8 bit 数据后,要求接收方回复一个应答位。 


应答位为低电平有效。




  • EEPROM(24LC64)简介

24LC64是容量为8K*8bit(64Kbit)通信接口为IIC的EEPROM。EEPROM拥有掉电不丢失的功能,经常用来保存一些配置信息等等。这个器件可以一次进行最多32字节的页写入和一次进行整片读出的功能。



A0、A1、A2为用户可配置的芯片选择端。该管脚一般都连接电源或者地,在通信时,24LC64作为从机,主机在发送的命令中存在地址号,当地址号与A0、A1、A2管脚的电平相同时,该芯片视为被选中。


SDA和SCL为IIC协议中的时钟线和数据线。


WP为写保护端,当连接高电平时,该芯片只能够被读,不能够写入;当连接低电平或者空置时,该芯片可以被读写。


24LC64的写入方式有两种:单字节写入和多字节写入。


单字节写入时序图:


在控制字节中,1010为EEPROM的标号;A2、A1、A0为主机发送的地址号,接收设备接收到此信息后,会与本身的A2、A1、A0相对比,相同时,视为被选中;最后1bit,0表示写入、1表示读出。


在高字节地址中,高三位地址无效。24LC64的地址范围为8K,地址位13位即可。13位地址分为高五低八,在IIC中规定每次发送8bit,所以高五的前面必须发送任意3bit。


多字节写入时序图: 


DATA BYTE 0被写入到指定的地址中,后续的数据写入到指定地址的后续地址中。EEPROM中每32个字节分为一块,写入时不能跨块写入。


24LC64的读出方式有三种:当前地址单字节读、任意地址单字节读和任意地址多字节读。


当前地址单字节读的时序图如下:


当主机接收24LC64的数据后,不发送ACK,而是发送高电平。


任意地址单字节读取时序图,如下:


首先写入指定地址,然后按照当前地址读即可。


任意地址多字节读,时序如下:


任意地址多字节读就是在任意地址单字节读的时序中,接收到从机发送的数据后,不要发送NO ACK而是发送ACK,此时从机就会继续发送后续地址的数据,直到读取的长度符合设计要求,然后发送NO ACK以及STOP结束传输。


下图为24LC64的时序参数图。



根据时序参数,决定将IIC的速率定为50KHz。发送时,数据改变在SCL的低电平的正中间;读取时,在SCL高电平的正中间进行读取。


IIC一般用在配置或者读取少量的信息,对于速率来说要求相对较低。





  • 硬件介绍

在开发板上,搭载了一个24LC64。


电路原理图如下:


A0、A1、A2都被接地,主机再发送控制字节时,需要将此三位发送低电平。


WP管脚被接地,24LC64的整个片子都可以被读写。




  • 双向IO介绍

在IIC协议中,SDA作为数据线使用,每一个SCL的脉冲,传递一bit的数据。在读取时,SDA由24LC64发送,FPGA接收;在写入时,SDA由FPGA发送,24LC64接收。所以IIC协议只能实现半双工通信。


在硬件电路中,一般是通过三态门来进行控制双向IO。

此电路在FPGA的IO中也是存在的。下面来分析具体的工作原理。


三态门有三个端子,分别是输入端(obuf),输出端(SDA)和控制端(out_en)。工作方式为:当out_en有效时,obuf的值就可以输出到SDA;当out_en无效时,obuf的值不能输出到SDA,那么三态门就会输出高阻态。


高阻态,字节理解为阻值特别大,也可以理解为断开。高阻态与任何电平值相连接,所连接的线上的电平值都是和对方保持一致。在此可以延伸一下:所有的端口都是输出,包括输入端口在内,也会对外呈现出一种状态,所以输入端口对外呈现的状态就是高阻态。对于双向IO来说,输出时,正常输出即可;输入时,就要呈现为高阻态。


  • 设计要求

设计任意地址的单字节读写控制器。大多数是配置或者读取少量的信息,对于多字节的读写,可以采用多次单字节的读写完成。


  • 设计分析

24LC64的寻址范围为8K,地址bit共计13bit,所以需要高5bit和低8bit。在有些兼容IIC协议接口的器件中,寻址范围较少,8bit足够表示,所以在设计时,设计出适合两种寻址方式的驱动。


SCL的实现采用计数器控制,根据规定好的频率,在合适的地方进行拉高或者拉低。由于SDA的变化都发生在SCL为高或者为低的中间,所以还需要产生对应的脉冲。


SDA根据协议和对应的脉冲进行发送对应的协议段。


  • 架构设计和信号说明

本模块命名为iic_drive。


暂不连接的端口,在后续测试时,会有其他模块来控制或者读取。


  • iic_drive设计实现

iic_scl采用计数器的方式实现。外部的时钟为50MHz,iic_scl为100KHz,所以计数器需要记录500个时钟周期。


计数器由cnt_en控制。cnt_en有效时,cnt从0到500不断循环;当cnt_en无效时,cnt保持等于0。


iic_scl在cnt等于0至249时,保持高电平;在250至499时,保持低电平。cnt等于125时,正好是iic_scl高电平的中间,用脉冲flag_high表示;cnt等于375时,正好是iic_scl低电平的中间,用脉冲flag_low表示。


其他信号采用状态机作为输出。out_en为三态门控制信号,sda_obuf为三态门输出端,drive_cnt为发送或者接收的计数器,temp为发送或者接收的中间寄存器。


状态转移图如下:


设计代码为:

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




  • RTL仿真设计

在仿真时,需要外部提供ACK。如果直接仿真的话,由于外部没有提供ACK,导致一直都会重复发送一小段。在仿真中,我们自己给出ACK的难度也比较大。


24LC64的厂商帮我们解决了这个问题,他们在造出这个芯片的同时,还设计出了可以帮助我们仿真调试的verliog模型。此模型放在msim->24LC64_sim_module中,文件名字为24LC64.v。此模型的sda也是双向IO,所以在硬件设计时,会在此IO上加上拉电阻,在仿真时,我们也要模拟上拉电阻。


仿真设计如下:

`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上加上拉电阻。

仿真时,在地址16’h5555(虽然高三位没有用),写入了8’ha;写入完成后,又将此数据读出。

当对24LC64进行写入之后,它会进入到一个内部写循环(和flash类似),在此期间,不接受任何指令。所以在仿真时,需要等待写循环完成,时间为5ms(此参数在仿真模型中,可以进行修改)。

写入的仿真图如下:


读出的仿真图如下:


  • 板级测试

上述的设计在使用时会有上游控制器进行控制,所以板级测试还需要设计控制器。在控制器中只需要某一个地址写入数据,然后读出即可。测试时,可以多次更换地址和数据,保证设计的正确性。


在测试时,将slave_addr、addr_sel、addr、wdata设置为定值。slave_addr为7’b1010_000,addr_sel为1’b1,addr为16’h5555,wdata为8’haa。


将上有控制器命名为test_ctrl,顶层命名为test。


test_ctrl模块采用状态机设计实现。


test_ctrl的设计代码如下:
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模块负责将test_ctrl和iic_drive模块进行例化连接。将test模块设置为顶层。


test模块设计代码如下:

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


板级测试采用逻辑分析仪测试。


采样时钟选择外部的50MHz,采样深度选择32K。


触发位置选择post。


将iic_sda、iic_scl、test_ctrl模块中的RD_WAIT和TESTDONE两个状态,共计四个信号进行观测。

将RD_WAIT设置为上升沿触发。进入此状态时,刚刚写入完成。


点击触发后,按一下复位键。


从波形图中,可以分析出,在地址16’h5555中写入了8’haa。

将RD_WAIT设置为donot care,将TESTDONE设置为上升沿,此时读出刚刚完成。


触发后,按一下复位按键。


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


注意发送时,是在SCL的低电平的正中间;接收是在SCL的高电平的正中间;观看波形时,要和标准的时序图进行对比。


证明设计正确。



- End -

福利】:QQ交流群173560979,进群备注名字+学校/企业。
淘宝店铺:https://shop588964188.taobao.com
论坛网址:www.sxznfpga.com
叁芯智能FPGA课程


往期精选 

【免费】FPGA工程师人才招聘平台

FPGA人才招聘,企业HR,看过来!

系统设计精选 | 基于FPGA的实时图像边缘检测系统设计(附代码)

基于原语的千兆以太网RGMII接口设计

时序分析理论和timequest使用_中文电子版

求职面试 | FPGA或IC面试题最新汇总篇

资料汇总|FPGA软件安装包、书籍、源码、技术文档…(2025.02.19更新)

FPGA技术江湖广发江湖帖

无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。


FPGA技术江湖微信交流群

加群主微信,备注姓名+学校/公司+专业/岗位进群


FPGA技术江湖QQ交流群

备注姓名+学校/公司+专业/岗位进群

FPGA技术江湖 任何技术的学习就好比一个江湖,对于每一位侠客都需要不断的历练,从初入江湖的小白到归隐山林的隐世高人,需要不断的自我感悟自己修炼,让我们一起仗剑闯FPGA乃至更大的江湖。
评论 (0)
  • 导读在当今快速发展的智能通讯领域,时间敏感网络(TSN)已成为确保网络通信高可靠性和低延迟的关键技术。IEEE 802.1 Qci作为TSN的一个重要组成部分,提供了一套强大的机制来管理网络流量,确保关键数据流的优先级和带宽得到保障。本文将深入探讨IEEE 802.1 Qci协议的基本概念、工作原理以及虹科提供的Qci解决方案,帮您理解如何通过精确的流量控制来提升网络的稳定性和效率。虹科TSN解决方案01# 技术简介时间敏感网络(TSN)通过IEEE 802.1 Qci标准定义了一种关
    虹科工业智能互联 2025-04-21 16:17 76浏览
  • 导读Linux驱动程序领域再添新成员,PLIN驱动程序现已正式发布。这一新驱动程序为使用LIN接口的用户提供了一个便捷、高效的解决方案。本文将展示如何安装PLIN驱动程序,以及如何在Linux环境下进行基本的PLIN通信操作,确保您能够快速掌握并应用这一新工具。继我们在Linux环境下成功推出CAN/CAN FD接口驱动程序后,现在我们为LIN接口带来了同样兼容Linux的驱动程序。免费软件包中不仅包含了驱动程序本身,还提供实用工具和一份易于理解的快速入门指南。用户下载后,需要根据当前使用的Li
    虹科汽车智能互联 2025-04-21 14:56 58浏览
  •   海上安全事件应急处置系统平台深度解析   一、平台概述   北京华盛恒辉海上安全事件应急处置系统平台融合现代信息技术、通信技术、GIS、大数据分析及 AI 等技术,旨在快速响应船舶碰撞、火灾、溢油等海上突发事件,实现科学决策与高效资源调配,保障海上生命财产安全、减少环境污染。   应用案例   目前,已有多个海上安全事件应急处置系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润海上安全事件应急处置系统。这些成功案例为海上安全事件应急处置系统的推广和应用提供了有力支持
    华盛恒辉l58ll334744 2025-04-21 15:21 85浏览
  •   海上安全事件应急处置系统解析   北京华盛恒辉海上安全事件应急处置系统是为应对船舶碰撞、火灾等海上突发事件打造的综合管理体系,通过技术与协同机制,实现快速响应救援、优化资源配置,守护海上生命、财产与环境安全。以下从系统构成、功能、技术、应用及趋势展开阐述。   应用案例   目前,已有多个海上安全事件应急处置系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润海上安全事件应急处置系统。这些成功案例为海上安全事件应急处置系统的推广和应用提供了有力支持。   一、系统构成
    华盛恒辉l58ll334744 2025-04-21 15:50 68浏览
  • 北京贞光科技有限公司作为紫光同芯授权代理商,深耕电子元器件领域数十载,专为汽车与工业客户提供车规级安全芯片及配套服务。公司整合硬件供应、软件SDK与技术支持为一体,配备专业团队提供选型咨询与现场指导,助力客户实现完整的芯片应用解决方案。在全球芯片供应链重构的大背景下,我国车规级芯片产业正迎来前所未有的发展机遇。北京贞光科技有限公司作为紫光同芯授权代理商,深耕电子元器件领域数十载,专为汽车与工业客户提供车规级安全芯片及配套服务。公司整合硬件供应、软件SDK与技术支持为一体,配备专业团队提供选型咨询
    贞光科技 2025-04-21 16:10 75浏览
  • 导读在智能汽车技术发展浪潮中,车辆控制系统的智能化、网络化已成为行业发展的必然趋势。虹科PEAK智行定位车控系统,集成了尖端科技,能够实现车辆全方位监控与控制。从实时GPS定位到CAN/CAN FD信号处理,虹科方案不仅提升了车辆的智能化水平,更在安全性和效率上迈出了革命性的一步。虹科PEAK智行定位车控系统,通过CAN/CAN FD信号实现车辆的精准控制,包括加减速、转弯、倒退等动作,模拟真实车辆平台的动态表现。该系统搭载了虹科各型号设备,通过紧密协作,实时反映车辆位置、总线报文等信息,实现车
    虹科汽车智能互联 2025-04-21 16:04 72浏览
  •   有效数据智能分拣系统平台深度解析   一、系统概述   北京华盛恒辉有效数据智能分拣系统平台融合人工智能、机器视觉、物联网及大数据分析技术,为物流包裹、数据信息等提供高效精准的智能化分拣处理方案。通过自动化设备与智能算法协同运作,取代传统人工分拣模式,显著提升分拣效率、降低错误率,满足电商、快递及供应链不断增长的业务需求。   应用案例   目前,已有多个有效数据智能分拣系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润有效数据智能分拣系统。这些成功案例为有效数据智能分
    华盛恒辉l58ll334744 2025-04-21 16:22 118浏览
  • 导读在汽车测试和现代工业领域,功耗控制与效率优化是工程师们不断追求的目标。虹科PCAN Router系列设备以其卓越的性能和灵活性,为CAN/CAN FD网络中的报文转换提供了高效解决方案。本文将探讨虹科PCAN Router系列设备如何在保持高效工作的同时,通过低功耗模式和高效唤醒功能,满足对能耗有严格要求的应用场景。虹科PCAN Router系列网关1 低功耗模式的优势与实现在实际的工作场景中,可能会出现一些对功耗要求存在限制的情况。鉴于此,可以灵活设置虹科PCAN Router系
    虹科汽车智能互联 2025-04-21 15:45 64浏览
  •   电磁信号模拟平台解析   北京华盛恒辉电磁信号模拟平台作为模拟复杂电磁环境的系统,在无线通信、电子对抗等多领域广泛应用。以下从功能、技术特性、应用场景及发展趋势展开详细解读。   应用案例   目前,已有多个电磁信号模拟平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润电磁信号模拟平台。这些成功案例为电磁信号模拟平台的推广和应用提供了有力支持。   一、核心功能   复杂电磁环境建模:构建贴近真实的电磁环境,涵盖各类干扰因素。   多通道信号模拟:模拟多通道电磁信号
    华盛恒辉l58ll334744 2025-04-21 15:10 94浏览
  •   有效数据智能分拣系统详解   北京华盛恒辉有效数据智能分拣系统融合人工智能、大数据分析与机器学习等前沿技术,实现海量数据自动化分类、筛选、整理及分配。凭借强大的数据处理效能,助力企业精准提取关键信息,优化决策流程,提升运营效率。以下从系统架构、核心功能、技术特性、应用场景及发展趋势展开解读。   应用案例   目前,已有多个有效数据智能分拣系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润有效数据智能分拣系统。这些成功案例为有效数据智能分拣系统的推广和应用提供了有力支持。
    华盛恒辉l58ll334744 2025-04-21 16:46 105浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦