赞
踩
IIC总线是嵌入式领域较为重要的器件间通信总线,同样,FPGA也能通过模块的形式实现IIC的功能,其原理和STM32的模拟IIC总线一致,就是控制每个时间点的SCL、SDA总线电平。
IIC总线需要对应的从机机通信器件进行通信,故目前使用AT24C64【IIC总线的EEPROM】作为从机,通过实现对EEPROM的读、写,来学习IIC协议在FPGA的实现。
由于IIC本质就是2线通信方式,以从机地址作为鉴别依据,在IIC总线上进行点对点通信,故不再对细节的硬件进行描述,使用拓扑图即可说明问题,再IIC总线上,EEPROM的从机地址为7‘h50,也就是二进制的0b1010000。
IIC总线的通信波形图我就不过多赘述,大致电文思路就是:
开始–>器件地址输出–>读写功能输出–>等待反馈–>数据地址(读/写)–>等待反馈–>数据与内容(读/写)–>等待反馈–>结束
相关线路的引脚如下:
时序分为写时序、读时序,具体内容如下:
写时序:
连续写就是将8bit的数据与往后继续发送;
读时序:
若要实现连续读,将最后一个主机非应答变为主机应答即可,需要读几个就应答几次。
设计目标为将EEPROM中,地址从0开始到255顺序写入0 ~ 255的数值,写入完成后,将地址0 ~ 255的数据读取出来,并检查是否地址与数据相对应,若数据与地址完全一致,则使红色PL_LED闪烁、否则PL_LED常亮。
使用到的资源为:
1、IIC的SCL线和SDA线
2、用于指示的LED灯
3、复位和系统时钟
模块分析
由于需要使用到EEPROM外设,而外设使用IIC总线进行通信,故首先需要实现IIC总线驱动模块;
输入:
位控制信号【数据地址可能位0 ~ 255(8bit)或0 ~ 65535(16bit)】
系统时钟
IIC的数据地址
IIC需要写入的数据【8bit】
IIC使能信号
IIC读写控制信号
模块复位信号
输出:
上层模块的控制时钟【用于让上层模块使用匹配的频率向该模块发送数据】
IIC是否收到应答的标志
IIC读取到的数据【8bit】
IIC当前操作完成标志
IIC总线的SCL信号
IIC总线的SDA信号
当有了IIC的驱动模块后,上层的功能只需要将对应的地址、数据按一定的节拍发送给IIC驱动模块,即可实现IIC的通信,故需要实现EEPROM的读写给,需要有一个读写流程的逻辑模块实现这一功能;
输入:
IIC操作时钟
IIC是否收到应答的标志
IIC读取到的数据【8bit】
IIC当前操作完成标记
复位
输出:
需要操作的IIC地址
IIC写入的数据
IIC驱动模块的使能信号
IIC的读写标记
读写完成标记【用于驱动LED模块】
读写结果【用于驱动LED模块】
最后就是之前提到的用于描述实验是否成功的LED模块,若写入和读取内容一致,则LED闪烁,若不一致则LED长亮;
输入:
运行时钟
复位
读写完成标记
读写结果
输出:
LED灯控制信号
之后使用上层模块将上述子模块例化,即可得到整体设计;
输入:
系统时钟
复位
输出:
IIC_CLK
IIC_SDA
LED
模块原理图如下所示:
Verilog语言的代码包括以下几个部分:
一个用于例化子模块的顶层模块;
顶层模块代码(eeprom_top.v):
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/21 21:29:06 // Design Name: // Module Name: eeprom_top // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module eeprom_top( input sys_clk , //系统时钟 input sys_rst_n , //系统复位 //eeprom interface output iic_scl , //eeprom的时钟线scl inout iic_sda , //eeprom的数据线sda //user interface output led //led显示 ); //parameter define parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR) parameter BIT_CTRL = 1'b1 ; //字地址位控制参数(16b/8b) parameter CLK_FREQ = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ) parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率 parameter L_TIME = 17'd125_000 ; //led闪烁时间参数 //wire define wire dri_clk ; //I2C操作时钟 wire i2c_exec ; //I2C触发控制 wire [15:0] i2c_addr ; //I2C操作地址 wire [ 7:0] i2c_data_w; //I2C写入的数据 wire i2c_done ; //I2C操作结束标志 wire i2c_ack ; //I2C应答标志 0:应答 1:未应答 wire i2c_rh_wl ; //I2C读写控制 wire [ 7:0] i2c_data_r; //I2C读出的数据 wire rw_done ; //E2PROM读写测试完成 wire rw_result ; //E2PROM读写测试结果 0:失败 1:成功 //***************************************************** //** main code //***************************************************** //e2prom读写测试模块 eeprom_rw u_eeprom_rw( .clk (dri_clk ), //时钟信号 .rst_n (sys_rst_n ), //复位信号 //i2c interface .i2c_exec (i2c_exec ), //I2C触发执行信号 .i2c_rh_wl (i2c_rh_wl ), //I2C读写控制信号 .i2c_addr (i2c_addr ), //I2C器件内地址 .i2c_data_w (i2c_data_w), //I2C要写的数据 .i2c_data_r (i2c_data_r), //I2C读出的数据 .i2c_done (i2c_done ), //I2C一次操作完成 .i2c_ack (i2c_ack ), //I2C应答标志 //user interface .rw_done (rw_done ), //E2PROM读写测试完成 .rw_result (rw_result ) //E2PROM读写测试结果 0:失败 1:成功 ); //i2c驱动模块 i2c_driver #( .SLAVE_ADDR (SLAVE_ADDR), //EEPROM从机地址 .CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率 .I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率 ) u_i2c_driver( .clk (sys_clk ), .rst_n (sys_rst_n ), //i2c interface .i2c_exec (i2c_exec ), //I2C触发执行信号 .bit_ctrl (BIT_CTRL ), //器件地址位控制(16b/8b) .i2c_rh_wl (i2c_rh_wl ), //I2C读写控制信号 .i2c_addr (i2c_addr ), //I2C器件内地址 .i2c_data_w (i2c_data_w), //I2C要写的数据 .i2c_data_r (i2c_data_r), //I2C读出的数据 .i2c_done (i2c_done ), //I2C一次操作完成 .i2c_ack (i2c_ack ), //I2C应答标志 .scl (iic_scl ), //I2C的SCL时钟信号 .sda (iic_sda ), //I2C的SDA信号 //user interface .dri_clk (dri_clk ) //I2C操作时钟 ); //led指示模块 led_alarm #(.L_TIME(L_TIME ) //控制led闪烁时间 ) u_led_alarm( .clk (dri_clk ), .rst_n (sys_rst_n ), .rw_done (rw_done ), .rw_result (rw_result ), .led (led ) ); endmodule
顶层约束文件(eeprom_top.xdc):
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports iic_scl]
set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33} [get_ports iic_sda]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports led]
IIC驱动模块,负责将数据与地址操作变为IIC上的电平信号【i2c_driver.v】
该模块将IIC总线输出信号使用复杂的三段式状态机表述【同步时序表述状态转移、组合逻辑判断状态转移、各状态下正常功能执行】,具体含义可见代码注释表述;
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/21 21:36:55 // Design Name: // Module Name: i2c_driver // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module i2c_driver #( parameter SLAVE_ADDR = 7'b1010000 , //EEPROM从机地址 parameter CLK_FREQ = 26'd50_000_000, //模块输入的时钟频率 parameter I2C_FREQ = 18'd250_000 //IIC_SCL的时钟频率 ) ( input clk , input rst_n , //i2c interface input i2c_exec , //I2C触发执行信号 input bit_ctrl , //字地址位控制(16b/8b) input i2c_rh_wl , //I2C读写控制信号 input [15:0] i2c_addr , //I2C器件内地址 input [ 7:0] i2c_data_w , //I2C要写的数据 output reg [ 7:0] i2c_data_r , //I2C读出的数据 output reg i2c_done , //I2C一次操作完成 output reg i2c_ack , //I2C应答标志 0:应答 1:未应答 output reg scl , //I2C的SCL时钟信号 inout sda , //I2C的SDA信号 //user interface output reg dri_clk //驱动I2C操作的驱动时钟 ); //localparam define 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; //结束I2C操作 //reg define reg sda_dir ; //I2C数据(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 ; //I2C需写的数据的临时寄存 reg [ 9:0] clk_cnt ; //分频时钟计数 //wire define wire sda_in ; //SDA输入信号 wire [8:0] clk_divide ; //模块驱动时钟的分频系数 //***************************************************** //** main code //***************************************************** //SDA控制 assign sda = sda_dir ? sda_out : 1'bz ; //SDA数据输出或高阻 assign sda_in = sda ; //SDA数据输入 assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2 ; //模块驱动时钟的分频系数 //生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin dri_clk <= 1'b0; clk_cnt <= 10'd0; end else if(clk_cnt == clk_divide[8:1] - 1'd1) begin clk_cnt <= 10'd0; dri_clk <= ~dri_clk; end else clk_cnt <= clk_cnt + 1'b1; end //(三段式状态机)同步时序描述状态转移 always @(posedge dri_clk or negedge rst_n) begin if(!rst_n) cur_state <= st_idle; else cur_state <= next_state; end //组合逻辑判断状态转移条件 always @(*) begin next_state = st_idle; case(cur_state) st_idle: begin //空闲状态 if(i2c_exec) begin next_state = st_sladdr; end else next_state = st_idle; end st_sladdr: begin if(st_done) begin if(bit_ctrl) //判断是16位还是8位字地址 next_state = st_addr16; else next_state = st_addr8 ; end else next_state = st_sladdr; end st_addr16: begin //写16位字地址 if(st_done) begin next_state = st_addr8; end else begin next_state = st_addr16; end end st_addr8: begin //8位字地址 if(st_done) begin if(wr_flag==1'b0) //读写判断 next_state = st_data_wr; else next_state = st_addr_rd; end else begin next_state = st_addr8; end end st_data_wr: begin //写数据(8 bit) if(st_done) next_state = st_stop; else next_state = st_data_wr; end st_addr_rd: begin //写地址以进行读数据 if(st_done) begin next_state = st_data_rd; end else begin next_state = st_addr_rd; end end st_data_rd: begin //读取数据(8 bit) if(st_done) next_state = st_stop; else next_state = st_data_rd; end st_stop: begin //结束I2C操作 if(st_done) next_state = st_idle; else next_state = st_stop ; end default: next_state= st_idle; endcase end //时序电路描述状态输出 always @(posedge dri_clk or negedge rst_n) begin //复位初始化 if(!rst_n) begin scl <= 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; //开始I2C 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'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]; //I2C写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; 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_data_rd: begin //读取数据(8 bit) case(cnt) 7'd0: sda_dir <= 1'b0; 7'd1: begin data_r[7] <= sda_in; scl <= 1'b1; end 7'd3: scl <= 1'b0; 7'd5: begin data_r[6] <= sda_in ; scl <= 1'b1 ; end 7'd7: scl <= 1'b0; 7'd9: begin data_r[5] <= sda_in; scl <= 1'b1 ; end 7'd11: scl <= 1'b0; 7'd13: begin data_r[4] <= sda_in; scl <= 1'b1 ; end 7'd15: scl <= 1'b0; 7'd17: begin data_r[3] <= sda_in; scl <= 1'b1 ; end 7'd19: scl <= 1'b0; 7'd21: begin data_r[2] <= sda_in; scl <= 1'b1 ; end 7'd23: scl <= 1'b0; 7'd25: begin data_r[1] <= sda_in; scl <= 1'b1 ; end 7'd27: scl <= 1'b0; 7'd29: begin data_r[0] <= sda_in; scl <= 1'b1 ; end 7'd31: scl <= 1'b0; 7'd32: begin sda_dir <= 1'b1; sda_out <= 1'b1; end 7'd33: scl <= 1'b1; 7'd34: st_done <= 1'b1; //非应答 7'd35: begin scl <= 1'b0; cnt <= 1'b0; i2c_data_r <= data_r; end default : ; endcase end st_stop: begin //结束I2C操作 case(cnt) 7'd0: begin sda_dir <= 1'b1; //结束I2C sda_out <= 1'b0; end 7'd1 : scl <= 1'b1; 7'd3 : sda_out <= 1'b1; 7'd15: st_done <= 1'b1; 7'd16: begin cnt <= 1'b0; i2c_done <= 1'b1; //向上层模块传递I2C结束信号 end default : ; endcase end endcase end end endmodule
LED的报警模块【led_alarm.v】
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/21 22:03:45 // Design Name: // Module Name: led_alarm // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module led_alarm #(parameter L_TIME = 25'd25_000_000) ( input clk , //时钟信号 input rst_n , //复位信号 input rw_done , //错误标志 input rw_result , //E2PROM读写测试完成 output reg led //E2PROM读写测试结果 0:失败 1:成功 ); //reg define reg rw_done_flag; //读写测试完成标志 reg [24:0] led_cnt ; //led计数 //***************************************************** //** main code //***************************************************** always @(posedge clk or negedge rst_n)begin if(!rst_n) rw_done_flag <= 1'b0; else if(rw_done) rw_done_flag <= 1'b1; end //错误标志为1时PL_LED0闪烁,否则PL_LED0常亮 always @(posedge clk or negedge rst_n) begin if(!rst_n)begin led_cnt <= 25'd0; led <= 1'b0; end else begin if(rw_done_flag)begin if(rw_result) led <= 1'b1; else begin led_cnt <= led_cnt + 25'd1; if(led_cnt == L_TIME - 1'b1)begin led_cnt <= 25'd0; led <= ~led; end end end else led <= 1'b0; end end endmodule
EEPROM的读写判断模块【eeprom_rw.v】
该部分逻辑较为简单,使用单段式状态机实现(状态跳转及状态动作在单段代码实现)。
`timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2022/07/21 21:34:30 // Design Name: // Module Name: eeprom_rw // Project Name: // Target Devices: // Tool Versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module eeprom_rw( input clk , //时钟信号 input rst_n , //复位信号 //i2c interface output reg i2c_rh_wl , //I2C读写控制信号 output reg i2c_exec , //I2C触发执行信号 output reg [15:0] i2c_addr , //I2C器件内地址 output reg [ 7:0] i2c_data_w , //I2C要写的数据 input [ 7:0] i2c_data_r , //I2C读出的数据 input i2c_done , //I2C一次操作完成 input i2c_ack , //I2C应答标志 //user interface output reg rw_done , //E2PROM读写测试完成 output reg rw_result //E2PROM读写测试结果 0:失败 1:成功 ); //parameter define //EEPROM写数据需要添加间隔时间,读数据则不需要 parameter WR_WAIT_TIME = 14'd5000; //写入间隔时间 parameter MAX_BYTE = 16'd256 ; //读写测试的字节个数 //reg define reg [1:0] flow_cnt ; //状态流控制 reg [13:0] wait_cnt ; //延时计数器 //***************************************************** //** main code //***************************************************** //EEPROM读写测试,先写后读,并比较读出的值与写入的值是否一致 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin flow_cnt <= 2'b0; i2c_rh_wl <= 1'b0; i2c_exec <= 1'b0; i2c_addr <= 16'b0; i2c_data_w <= 8'b0; wait_cnt <= 14'b0; rw_done <= 1'b0; rw_result <= 1'b0; end else begin i2c_exec <= 1'b0; rw_done <= 1'b0; case(flow_cnt) 2'd0 : begin wait_cnt <= wait_cnt + 1'b1; //延时计数 if(wait_cnt == WR_WAIT_TIME - 1'b1) begin //EEPROM写操作延时完成 wait_cnt <= 1'b0; if(i2c_addr == MAX_BYTE) begin //256个字节写入完成 i2c_addr <= 1'b0; i2c_rh_wl <= 1'b1; flow_cnt <= 2'd2; end else begin flow_cnt <= flow_cnt + 1'b1; i2c_exec <= 1'b1; end end end 2'd1 : begin if(i2c_done == 1'b1) begin //EEPROM单次写入完成 flow_cnt <= 2'd0; i2c_addr <= i2c_addr + 1'b1; //地址0~255分别写入 i2c_data_w <= i2c_data_w + 1'b1; //数据0~255 end end 2'd2 : begin flow_cnt <= flow_cnt + 1'b1; i2c_exec <= 1'b1; end 2'd3 : begin if(i2c_done == 1'b1) begin //EEPROM单次读出完成 //读出的值错误或者I2C未应答,读写测试失败 if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin rw_done <= 1'b1; rw_result <= 1'b1; end else if(i2c_addr == MAX_BYTE - 1'b1) begin //读写测试成功 rw_done <= 1'b1; rw_result <= 1'b0; end else begin flow_cnt <= 2'd2; i2c_addr <= i2c_addr + 1'b1; end end end default : ; endcase end end endmodule
完成代码编辑后,将代码编译为二进制流,并下载到FPGA上。
按下复位后,经过一小段时间,EEPROM读写模块校正成功,LED开始闪烁
至此,实验成功,IIC功能模块能够移植使用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。