ZYNQ之FPGA学习----IIC协议驱动模块仿真实验
创始人
2024-03-14 19:50:02
0

1 IIC通信协议简介

IIC通信协议基础知识学习:硬件设计基础----通信协议IIC

2 实验任务

设计IIC驱动模块,并进行仿真验证,观察仿真波形

3 实验设计

3.1 创建工程

新建工程,操作如图所示:

在这里插入图片描述

输入工程名和路径,如图:

在这里插入图片描述

选择创建RTL工程,如图:

在这里插入图片描述

直接点击Next:

在这里插入图片描述

继续点击Next:

在这里插入图片描述

添加芯片型号,操作如图:

在这里插入图片描述

工程创建完成:

在这里插入图片描述

3.2 设计输入

创建工程文件,操作如图所示:

在这里插入图片描述

创建iic_drive文件:

在这里插入图片描述

创建完成:

在这里插入图片描述

双击打开,输入代码如下:

module iic_drive(
//系统接口input               clk,      //时钟信号,50MHZinput               rst_n,    //复位信号,低电平有效//IIC时序控制接口                   input                i2c_exec   ,  //IIC触发执行信号input                bit_ctrl   ,  //字地址位控制(16b/8b)input                i2c_rh_wl  ,  //IIC读写控制信号input        [15:0]  i2c_addr   ,  //IIC器件内地址input        [ 7:0]  i2c_data_w ,  //IIC要写的数据output  reg  [ 7:0]  i2c_data_r ,  //IIC读出的数据output  reg          i2c_done   ,  //IIC一次操作完成output  reg          i2c_ack    ,  //IIC应答标志 0:应答 1:未应答
//IIC物理接口output  reg          scl        ,  //IIC的SCL时钟信号inout                sda        ,  //IIC的SDA信号                                 //user interface                   output  reg          dri_clk       //驱动IIC操作的驱动时钟);
parameter   SLAVE_ADDR = 7'b1010000   ;  //IIC从机地址    
parameter   CLK_FREQ   = 26'd50_000_000; //模块输入的时钟频率
parameter   I2C_FREQ   = 18'd250_000;     //IIC_SCL的时钟频率 ,250k   
//状态机定义
localparam  st_idle     = 8'b0000_0001; //空闲状态
localparam  st_sladdr   = 8'b0000_0010; //发送器件地址(slave address)
localparam  st_addr16   = 8'b0000_0100; //发送16位字地址
localparam  st_addr8    = 8'b0000_1000; //发送8位字地址
localparam  st_data_wr  = 8'b0001_0000; //写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000; //发送器件地址读
localparam  st_data_rd  = 8'b0100_0000; //读数据(8 bit)
localparam  st_stop     = 8'b1000_0000; //结束IIC操作
//reg 定义
reg            sda_dir   ; //IIC数据(SDA)方向控制
reg            sda_out   ; //SDA输出信号
reg            st_done   ; //状态结束
reg            wr_flag   ; //写标志
reg    [ 6:0]  cnt       ; //计数
reg    [ 7:0]  cur_state ; //状态机当前状态
reg    [ 7:0]  next_state; //状态机下一状态
reg    [15:0]  addr_t    ; //地址
reg    [ 7:0]  data_r    ; //读取的数据
reg    [ 7:0]  data_wr_t ; //IIC需写的数据的临时寄存
reg    [ 9:0]  clk_cnt   ; //分频时钟计数//wire 定义
wire          sda_in     ; //SDA输入信号
wire   [8:0]  clk_divide ; //模块驱动时钟的分频系数
//SDA控制
assign  sda        = sda_dir ?  sda_out : 1'bz   ;  //SDA数据输出或高阻
assign  sda_in     = sda                         ;  //SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2 ;  //模块驱动时钟的分频系数//生成IIC的SCL的四倍频率的驱动时钟用于驱动IIC的操作
always @(posedge clk or negedge rst_n) beginif(!rst_n) begindri_clk <=  1'b0;clk_cnt <= 10'd0;endelse if(clk_cnt == clk_divide[8:1] - 1'd1) beginclk_cnt <= 10'd0;dri_clk <= ~dri_clk;endelseclk_cnt <= clk_cnt + 1'b1;
end//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) beginif(!rst_n)cur_state <= st_idle;elsecur_state <= next_state;
end//组合逻辑判断状态转移条件
always @(*) beginnext_state = st_idle;case(cur_state)st_idle: begin                          //空闲状态if(i2c_exec) beginnext_state = st_sladdr;endelsenext_state = st_idle;endst_sladdr: beginif(st_done) beginif(bit_ctrl)                    //判断是16位还是8位字地址next_state = st_addr16;elsenext_state = st_addr8 ;endelsenext_state = st_sladdr;endst_addr16: begin                        //写16位字地址if(st_done) beginnext_state = st_addr8;endelse beginnext_state = st_addr16;endendst_addr8: begin                         //8位字地址if(st_done) beginif(wr_flag==1'b0)               //读写判断next_state = st_data_wr;elsenext_state = st_addr_rd;endelse beginnext_state = st_addr8;endendst_data_wr: begin                       //写数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_wr;endst_addr_rd: begin                       //写地址以进行读数据if(st_done) beginnext_state = st_data_rd;endelse beginnext_state = st_addr_rd;endendst_data_rd: begin                       //读取数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_rd;endst_stop: begin                          //结束I2C操作if(st_done)next_state = st_idle;elsenext_state = st_stop ;enddefault: next_state= st_idle;endcase
end//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin//复位初始化if(!rst_n) beginscl       <= 1'b1;sda_out   <= 1'b1;sda_dir   <= 1'b1;                          i2c_done  <= 1'b0;                          i2c_ack   <= 1'b0;                          cnt       <= 1'b0;                          st_done   <= 1'b0;                          data_r    <= 1'b0;                          i2c_data_r<= 1'b0;                          wr_flag   <= 1'b0;                          addr_t    <= 1'b0;                          data_wr_t <= 1'b0;                          end                                              else begin                                       st_done <= 1'b0 ;                            cnt     <= cnt +1'b1 ;                       case(cur_state)                              st_idle: begin                          //空闲状态scl     <= 1'b1;                     sda_out <= 1'b1;                     sda_dir <= 1'b1;                     i2c_done<= 1'b0;                     cnt     <= 7'b0;               if(i2c_exec) begin                   wr_flag   <= i2c_rh_wl ;         addr_t    <= i2c_addr  ;         data_wr_t <= i2c_data_w;  i2c_ack   <= 1'b0;                      end                                  end                                      st_sladdr: begin                         //写地址(器件地址和字地址)case(cnt)                            7'd1 : sda_out <= 1'b0;          //开始IIC7'd3 : scl <= 1'b0;              7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= SLAVE_ADDR[5]; 7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= SLAVE_ADDR[4]; 7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= SLAVE_ADDR[3]; 7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= SLAVE_ADDR[2]; 7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= SLAVE_ADDR[1]; 7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= SLAVE_ADDR[0]; 7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: sda_out <= 1'b0;          //0:写7'd33: scl <= 1'b1;              7'd35: scl <= 1'b0;              7'd36: begin                     sda_dir <= 1'b0;             sda_out <= 1'b1;                         end                              7'd37: scl     <= 1'b1;            7'd38: begin                     //从机应答 st_done <= 1'b1;if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位     end                                          7'd39: begin                     scl <= 1'b0;                 cnt <= 1'b0;                 end                              default :  ;                     endcase                              end                                      st_addr16: begin                         case(cnt)                            7'd0 : begin                     sda_dir <= 1'b1 ;            sda_out <= addr_t[15];       //传送字地址end                              7'd1 : scl <= 1'b1;              7'd3 : scl <= 1'b0;              7'd4 : sda_out <= addr_t[14];    7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= addr_t[13];    7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= addr_t[12];    7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= addr_t[11];    7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= addr_t[10];    7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= addr_t[9];     7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= addr_t[8];     7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: begin                     sda_dir <= 1'b0;             sda_out <= 1'b1;   end                              7'd33: scl  <= 1'b1;             7'd34: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end        7'd35: begin                     scl <= 1'b0;                 cnt <= 1'b0;                 end                              default :  ;                     endcase                              end                                      st_addr8: begin                          case(cnt)                            7'd0: begin                      sda_dir <= 1'b1 ;             sda_out <= addr_t[7];         //字地址end                              7'd1 : scl <= 1'b1;              7'd3 : scl <= 1'b0;              7'd4 : sda_out <= addr_t[6];     7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= addr_t[5];     7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= addr_t[4];     7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= addr_t[3];     7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= addr_t[2];     7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= addr_t[1];     7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= addr_t[0];     7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: begin                     sda_dir <= 1'b0;         sda_out <= 1'b1;                    end                              7'd33: scl     <= 1'b1;          7'd34: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end   7'd35: begin                     scl <= 1'b0;                 cnt <= 1'b0;                 end                              default :  ;                     endcase                              end                                      st_data_wr: begin                        //写数据(8 bit)case(cnt)                            7'd0: begin                      sda_out <= data_wr_t[7];     //IIC写8位数据sda_dir <= 1'b1;             end                              7'd1 : scl <= 1'b1;              7'd3 : scl <= 1'b0;              7'd4 : sda_out <= data_wr_t[6];  7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= data_wr_t[5];  7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= data_wr_t[4];  7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= data_wr_t[3];  7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= data_wr_t[2];  7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= data_wr_t[1];  7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= data_wr_t[0];  7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: begin                     sda_dir <= 1'b0;           sda_out <= 1'b1;                              end                              7'd33: scl <= 1'b1;              7'd34: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end          7'd35: begin                     scl  <= 1'b0;                cnt  <= 1'b0;                end                              default  :  ;                    endcase                              end                                      st_addr_rd: begin                        //写地址以进行读数据case(cnt)                            7'd0 : begin                     sda_dir <= 1'b1;             sda_out <= 1'b1;             end                              7'd1 : scl <= 1'b1;              7'd2 : sda_out <= 1'b0;          //重新开始7'd3 : scl <= 1'b0;              7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址7'd5 : scl <= 1'b1;              7'd7 : scl <= 1'b0;              7'd8 : sda_out <= SLAVE_ADDR[5]; 7'd9 : scl <= 1'b1;              7'd11: scl <= 1'b0;              7'd12: sda_out <= SLAVE_ADDR[4]; 7'd13: scl <= 1'b1;              7'd15: scl <= 1'b0;              7'd16: sda_out <= SLAVE_ADDR[3]; 7'd17: scl <= 1'b1;              7'd19: scl <= 1'b0;              7'd20: sda_out <= SLAVE_ADDR[2]; 7'd21: scl <= 1'b1;              7'd23: scl <= 1'b0;              7'd24: sda_out <= SLAVE_ADDR[1]; 7'd25: scl <= 1'b1;              7'd27: scl <= 1'b0;              7'd28: sda_out <= SLAVE_ADDR[0]; 7'd29: scl <= 1'b1;              7'd31: scl <= 1'b0;              7'd32: sda_out <= 1'b1;          //1:读7'd33: scl <= 1'b1;              7'd35: scl <= 1'b0;              7'd36: begin                     sda_dir <= 1'b0;            sda_out <= 1'b1;                    end7'd37: scl     <= 1'b1;7'd38: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end   7'd39: beginscl <= 1'b0;cnt <= 1'b0;enddefault : ;endcaseendst_data_rd: begin                        //读取数据(8 bit)case(cnt)7'd0: sda_dir <= 1'b0;7'd1: begindata_r[7] <= sda_in;scl       <= 1'b1;end7'd3: scl  <= 1'b0;7'd5: begindata_r[6] <= sda_in ;scl       <= 1'b1   ;end7'd7: scl  <= 1'b0;7'd9: begindata_r[5] <= sda_in;scl       <= 1'b1  ;end7'd11: scl  <= 1'b0;7'd13: begindata_r[4] <= sda_in;scl       <= 1'b1  ;end7'd15: scl  <= 1'b0;7'd17: begindata_r[3] <= sda_in;scl       <= 1'b1  ;end7'd19: scl  <= 1'b0;7'd21: begindata_r[2] <= sda_in;scl       <= 1'b1  ;end7'd23: scl  <= 1'b0;7'd25: begindata_r[1] <= sda_in;scl       <= 1'b1  ;end7'd27: scl  <= 1'b0;7'd29: begindata_r[0] <= sda_in;scl       <= 1'b1  ;end7'd31: scl  <= 1'b0;7'd32: beginsda_dir <= 1'b1;             sda_out <= 1'b1;end7'd33: scl     <= 1'b1;7'd34: st_done <= 1'b1;          //非应答7'd35: beginscl <= 1'b0;cnt <= 1'b0;i2c_data_r <= data_r;enddefault  :  ;endcaseendst_stop: begin                           //结束IIC操作case(cnt)7'd0: beginsda_dir <= 1'b1;             //结束IICsda_out <= 1'b0;end7'd1 : scl     <= 1'b1;7'd3 : sda_out <= 1'b1;7'd15: st_done <= 1'b1;7'd16: begincnt      <= 1'b0;i2c_done <= 1'b1;            //向上层模块传递IIC结束信号enddefault  : ;endcaseendendcaseend
end
endmodule

如图所示:

在这里插入图片描述

3.3 分析与综合

对设计进行分析,操作如图:

在这里插入图片描述

分析后的设计,Vivado自动生成顶层原理图,如图:

在这里插入图片描述

对设计进行综合,操作如图:

在这里插入图片描述

综合完成后,弹出窗口如下,直接关闭:

在这里插入图片描述

3.4 约束输入

创建约束文件,操作如图所示:

在这里插入图片描述

创建约束文件,输入文件名:

在这里插入图片描述

双击打开,输入约束代码:

set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk] 
set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n] 

如图所示:

在这里插入图片描述

3.5 设计实现

点击 Flow Navigator 窗口中的 Run Implementation,如图所示:

在这里插入图片描述

点击OK:

在这里插入图片描述

完成后,关闭即可:

在这里插入图片描述

3.6 功能仿真

创建TestBench,操作如图所示:

在这里插入图片描述

创建激励文件,输入文件名:

在这里插入图片描述

创建完成:

在这里插入图片描述

双击打开,输入TestBench(激励)代码:

`timescale  1ns/1ns                     //定义仿真时间单位1ns和仿真时间精度为1nsmodule  tb_iic_drive;              //parameter  define
parameter  T = 20;                      //时钟周期为20ns
parameter  IIC_WR_CYCYLE  = 10_000;
parameter  SLAVE_ADDR = 7'b1010000 ;    //EEPROM从机地址
parameter  CLK_FREQ   = 26'd50_000_000; //模块输入的时钟频率
parameter  I2C_FREQ   = 18'd250_000;    //IIC_SCL的时钟频率//reg define
reg          sys_clk;                   //时钟信号
reg          sys_rst_n;                 //复位信号reg          i2c_exec  ;
reg          bit_ctrl  ;
reg          i2c_rh_wl ;
reg   [15:0] i2c_addr  ;
reg   [7:0]  i2c_data_w;
reg   [3:0]  flow_cnt  ;
reg   [13:0] delay_cnt ;//wire define
wire  [7:0]  i2c_data_r;
wire         i2c_done  ;
wire         i2c_ack   ;
wire         scl       ;
wire         sda       ;
wire         dri_clk   ;//给输入信号初始值
initial beginsys_clk            = 1'b0;sys_rst_n          = 1'b0;     //复位#(T+1)  sys_rst_n  = 1'b1;     //在第21ns的时候复位信号信号拉高
end//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(T/2) sys_clk = ~sys_clk;always @(posedge dri_clk or negedge sys_rst_n) beginif(!sys_rst_n) begini2c_exec <= 1'b0;bit_ctrl <= 1'b0;i2c_rh_wl <= 1'b0;i2c_addr <= 1'b0;i2c_data_w <= 1'b0;flow_cnt <= 1'b0;delay_cnt <= 1'b0;endelse begincase(flow_cnt)'d0 : flow_cnt <= flow_cnt + 1'b1;'d1 : begini2c_exec <= 1'b1;                //拉高触发信号bit_ctrl <= 1'b1;                //地址位选择信号   1: 16位i2c_rh_wl <= 1'b0;               //写操作i2c_addr <= 16'h0555;            //写地址i2c_data_w <= 8'hAA;             //写数据flow_cnt <= flow_cnt + 1'b1;end'd2 : begin i2c_exec <= 1'b0;flow_cnt <= flow_cnt + 1'b1;end    'd3 : beginif(i2c_done)flow_cnt <= flow_cnt + 1'b1;end'd4 : begindelay_cnt <= delay_cnt + 1'b1;if(delay_cnt == IIC_WR_CYCYLE - 1'b1)flow_cnt <= flow_cnt + 1'b1;end'd5 : begini2c_exec <= 1'b1;bit_ctrl <= 1'b1;                   i2c_rh_wl <= 1'b1;           //读操作     i2c_addr <= 16'h0555;i2c_data_w <= 8'hAA;        flow_cnt <= flow_cnt + 1'b1;                    end'd6 : begin i2c_exec <= 1'b0;flow_cnt <= flow_cnt + 1'b1;end 'd7 : beginif(i2c_done)flow_cnt <= flow_cnt + 1'b1;enddefault:;endcase    end
endpullup(sda);//例化
iic_drive #(.SLAVE_ADDR  (SLAVE_ADDR),  //EEPROM从机地址.CLK_FREQ    (CLK_FREQ  ),  //模块输入的时钟频率.I2C_FREQ    (I2C_FREQ  )   //IIC_SCL的时钟频率
) u_iic_drive(.clk          (sys_clk),.rst_n        (sys_rst_n), .i2c_exec     (i2c_exec  ), .bit_ctrl     (bit_ctrl  ), .i2c_rh_wl    (i2c_rh_wl ), .i2c_addr     (i2c_addr  ), .i2c_data_w   (i2c_data_w), .i2c_data_r   (i2c_data_r), .i2c_done     (i2c_done  ), .i2c_ack      (i2c_ack   ), .scl          (scl       ), .sda          (sda       ),.dri_clk      (dri_clk   )
);endmodule

如图所示:

在这里插入图片描述

开始进行仿真,操作如下:

在这里插入图片描述

开始仿真:

在这里插入图片描述

仿真波形如图:

在这里插入图片描述

致谢领航者ZYNQ开发板,开启FPGA学习之路!

希望本文对大家有帮助,上文若有不妥之处,欢迎指正

分享决定高度,学习拉开差距

相关内容

热门资讯

不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
报告实验.pdfbase.tt... 这个错误通常是由于找不到字体文件或者文件路径不正确导致的。以下是一些解决方法:确认字体文件是否存在:...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...