赞
踩
继上一篇FPGA学习_I2C总线协议内容,本文将基于FPGA通过I2C控制AT24C64(EEPROM芯片)芯片。
AT24C64:256pages 32bitbyte each pages,存储大小为32 * 256 * 8 = 64Kb = 8KB
完整代码GitHub连接:https://github.com/shun6-6/IIC_EEPROM_Pro
module iic_drive#( parameter P_ADDR_WIDTH = 16 )( input i_clk , input i_rst , /*----user interface----*/ input [6 :0] i_device_addr ,//用户输入设备地址 input [15:0] i_operation_addr ,//用户输入读写数据地址 input [7 :0] i_operation_len ,//用户输入读写数据长度 input [1 :0] i_operation_type ,//用户输入读写类型 input i_operation_valid ,//用户输入操作有效信号 output o_operation_ready ,//用户输出操作准备信号 input [7 :0] i_write_date ,//用户写入数据 output o_write_req ,//用户写数据请求 output [7 :0] o_read_date ,//输出IIC读到的数据 output o_read_valid ,//数据有效信号 /*----IIC interface----*/ output o_iic_scl ,//IIC时钟线 inout io_iic_sda //IIC双向数据线 );
该模块将输入的指令以及相关地址和数据通过IIC协议传递给EEPROM芯片,其中状态机一共有11个状态:
localparam P_ST_IDLE = 0 ,//状态机-空闲
P_ST_START = 1 ,//状态机-起始位
P_ST_UADDR = 2 ,//状态机-设备地址
P_ST_DADDR1 = 3 ,//状态机-数据地址高位
P_ST_DADDR2 = 4 ,//状态机-数据地址低位
P_ST_WRITE = 5 ,//状态机-写数据
P_ST_REATART = 6 ,//状态机-重启iic总线
P_ST_READ = 7 ,//状态机-读数据
P_ST_WATI = 8 ,//等待应答后再发生停止位
P_ST_STOP = 9 ,//状态机-停止
P_ST_EMPTY = 10 ;//空状态
状态转移过程:
P_ST_IDLE :一次操作指令握手成功后,进入START状态;
P_ST_START :该状态下控制SCL和SDA线启动IIC,随后进入写设备地址UADDR状态,
P_ST_UADDR :w_st_turn 信号表示一个byte写入结束,即设备地址和读写指令写入(俩个一共为8bit)结束,随后会判断r_st_restart 信号,该信号表示当前操作为读数据操作,因为只有在读数据时,我们采用随机地址读操作,有一次虚写操作,虚写结束后需要重新启动总线,因此若此信号为高需要进入READ读阶段,否则进入写数据地址1(P_ST_DADDR1)阶段;
P_ST_DADDR1 :r_slave_ack 信号表示从机响应信号,此时响应的是写设备地址阶段的响应信号,如果响应为0,即未响应,则直接进入STOP停止阶段,以重新启动总线,这是由于刚刚写完数据,如果立马进行操作总线,总线处于忙状态,不会响应,若正常响应,则会在顺利写完8bit数据后进入P_ST_DADDR2 阶段。
P_ST_DADDR2 :在写完8bit的低8为地址后(即w_st_turn 拉高),判断当前是读操作还是写操作,写操作则顺利进入写数据WRITE阶段,读操作则需要进入总线重新启动阶段,即P_ST_REATART 。
P_ST_WRITE :当写如byte字节数与用户输入的写数据长度一致时,进入等待WAIT状态,否则继续写;
P_ST_REATART :重启总线,先进入停止STOP阶段,执行停止操作。
P_ST_READ :字节读,此模块只支持字节读操作,但用户要实现多字节读操作也是可以的,该过程会在EEPROM_drive当中被实现,因为EEPROM_drive模块会执行多次字节读。以此实现连续读操作。
P_ST_WATI :等待一拍,等从机ACK结束后进入停止位STOP阶段。
P_ST_STOP :控制SCL和SDA线结束IIC操作
注:这里需要注意的是接收完ACK后,不可以直接停止时钟产生,然后直接拉高SDA,需要多产生一个周期SCL,然后在SCL高电平期间拉高SDA以产生停止信号。
P_ST_EMPTY :emmmmm没啥用的一个状态,完全可以将停止位多计数一个时钟执行响应的判断,不过这样写清晰一点,主要是判断是不是需要重启总线(r_st_restart说明是由于读操作导致的重启操作,r_ack_lock说明是由于从机忙没有回应导致的重启总线),如果是则进入START状态,不是就回到IDLE状态。
always @(*)begin case (r_st_cur) P_ST_IDLE : r_st_nxt = w_operation_active ? P_ST_START : P_ST_IDLE; P_ST_START : r_st_nxt = P_ST_UADDR; P_ST_UADDR : r_st_nxt = w_st_turn ? r_st_restart ? P_ST_READ : P_ST_DADDR1 : P_ST_UADDR; P_ST_DADDR1 : r_st_nxt = r_slave_ack ? P_ST_STOP : w_st_turn ? P_ST_DADDR2 : P_ST_DADDR1; P_ST_DADDR2 : r_st_nxt = w_st_turn && ri_operation_type == P_W ? P_ST_WRITE : w_st_turn && ri_operation_type == P_R ? P_ST_REATART : P_ST_DADDR2; P_ST_WRITE : r_st_nxt = w_st_turn && r_wr_cnt == ri_operation_len - 1 ? P_ST_WATI : P_ST_WRITE; P_ST_REATART : r_st_nxt = P_ST_STOP; P_ST_READ : r_st_nxt = w_st_turn ? P_ST_WATI : P_ST_READ;//随机读,一次一个byte P_ST_WATI : r_st_nxt = P_ST_STOP; P_ST_STOP : r_st_nxt = r_st_cnt == 1 ? P_ST_EMPTY : P_ST_STOP; P_ST_EMPTY : r_st_nxt = r_st_restart | r_ack_lock ? P_ST_START : P_ST_IDLE; default : r_st_nxt = P_ST_IDLE; endcase end
过程和之前介绍的SPI总线协议类似,通过维护一个计数器,来使数据在SCL时钟下降沿改变,在SCL时钟上升沿被采样
主要难点是停止位的部分:
module eeprom_ctrl( input i_clk , input i_rst , /*----user interface----*/ input [2 :0] i_eeprom_addr , input [15:0] i_user_operation_addr , input [1 :0] i_user_operation_type , input [7 :0] i_user_operation_len , input i_user_operation_valid , output o_user_operation_ready , input [7 :0] i_user_write_date , input i_user_write_valid , input i_user_write_sop , input i_user_write_eop , output [7 :0] o_user_read_date , output o_user_read_valid , /*----iic drive interface----*/ output [6 :0] o_device_addr , output [15:0] o_operation_addr , output [7 :0] o_operation_len , output [1 :0] o_operation_type , output o_operation_valid , input i_operation_ready , output [7 :0] o_write_date , input i_write_req , input [7 :0] i_read_date , input i_read_valid );
该模块主要讲用户输入的读写指令转化为驱动读写指令,并且将用户输入的写数据存入FIFO,一个一个把8bit数据吐给IIC驱动,因为IIC_drive是串行传输的,同时也把从EEPROM读出的数据暂存入FIFO,读完后一次性连续的传递给用户模块。同时在该模块当中完成了一个上面刚刚提到的操作,连续读 :IIC_drive模块只支持字节读操作,但用户要实现多字节读操作也是可以的,因为EEPROM_drive模块会执行多次字节读。以此实现连续读操作。
localparam P_ST_IDLE = 0 ,
P_ST_WRITE = 1 ,
P_ST_WAIT = 2 ,
P_ST_READ = 3 ,
P_ST_REREAD = 4 ,
P_ST_OUT_DATA = 5 ;
always @(*)begin case (r_st_cur) P_ST_IDLE : r_st_nxt = w_user_active && i_user_operation_type == P_W ? P_ST_WRITE : w_user_active && i_user_operation_type == P_R ? P_ST_WAIT : P_ST_IDLE; P_ST_WRITE : r_st_nxt = w_drive_end && ri_user_operation_type == P_W ? P_ST_IDLE : P_ST_WRITE; P_ST_WAIT : r_st_nxt = P_ST_READ; P_ST_READ : r_st_nxt = w_drive_end ? r_read_cnt == ri_user_operation_len - 1 ? P_ST_OUT_DATA : P_ST_REREAD : P_ST_READ; P_ST_REREAD : r_st_nxt = P_ST_READ; P_ST_OUT_DATA : r_st_nxt = w_fifo_read_empty ? P_ST_IDLE : P_ST_OUT_DATA; default : r_st_nxt = P_ST_IDLE; endcase end
该模块状态机以及相应跳转条件
该功能较为简单,
P_ST_IDLE :根据输入操作类型分别进入写WRITE状态或者是等待WAIT状态,这是因为用户输入的操作指令都会在本地先打一拍然后寄存下来,对于写数据而言,会先将数据存入FIFO然后再进行握手操作,然后再把本地寄存的用户指令再发给IIC驱动,这期间因为FIFO存数据所以指令赋值推后了好多拍,因此指令赋值不会出错,但是进行读数据操作时,用户指令i_user_operation_type先赋值给ri_user_operation_type以寄存,然后将ri_user_operation_type赋值给驱动ro_operation_type,在这个过程中就需要先等待一拍,否则就会出现上一次操作的的ri_user_operation_type给了ro_operation_type,错过了本次正确的i_user_operation_type。 这就是为什么进入读操作时先要进入一次WAIT状态以打一排。
之后的状态都很简单了!!!
该模块就是例化了上述俩个模块
该模块则是简单实现了一个不断翻转读写状态的的用户模块。
module iic_top( input i_clk , output o_iic_scl ,//IIC时钟线 inout io_iic_sda //IIC双向数据线 ); localparam P_WRITE_NUM = 8; localparam P_W = 1 ,//写数据 P_R = 2 ;//读数据 reg [2 :0] ri_eeprom_addr ; reg [15:0] ri_user_operation_addr ; reg [1 :0] ri_user_operation_type ; reg [7 :0] ri_user_operation_len ; reg ri_user_operation_valid ; wire o_user_operation_ready ; reg [7 :0] ri_user_write_date ; reg ri_user_write_valid ; reg ri_user_write_sop ; reg ri_user_write_eop ; wire [7 :0] o_user_read_date ; wire o_user_read_valid ; reg [7 :0] r_write_cnt ; reg r_wr_st ; wire w_user_active ; wire w_clk_5mhz ; wire w_clk_5mhz_lock ; wire w_clk_125khz ; wire w_clk_125khz_rst ; assign w_user_active = ri_user_operation_valid & o_user_operation_ready; SYSCLK_div SYSCLK_div_5mhz ( .clk_out1 (w_clk_5mhz ), .locked (w_clk_5mhz_lock), .clk_in1 (i_clk ) ); CLK_DIV_module#( .P_CLK_DIV_CNT (40) //MAX = 65535 )CLK_DIV_module_U( .i_clk (w_clk_5mhz ), .i_rst (~w_clk_5mhz_lock), .o_clk_div (w_clk_125khz ) ); rst_gen_module#( .P_RST_CYCLE (1) )rst_gen_module_u0( .i_clk (w_clk_125khz ), .o_rst (w_clk_125khz_rst) ); eeprom_drive eeprom_drive_u0( .i_clk (w_clk_125khz ), .i_rst (w_clk_125khz_rst), .i_eeprom_addr (ri_eeprom_addr ), .i_user_operation_addr (ri_user_operation_addr ), .i_user_operation_type (ri_user_operation_type ), .i_user_operation_len (ri_user_operation_len ), .i_user_operation_valid (ri_user_operation_valid), .o_user_operation_ready (o_user_operation_ready ), .i_user_write_date (ri_user_write_date ), .i_user_write_valid (ri_user_write_valid ), .i_user_write_sop (ri_user_write_sop ), .i_user_write_eop (ri_user_write_eop ), .o_user_read_date (o_user_read_date ), .o_user_read_valid (o_user_read_valid ), .o_iic_scl (o_iic_scl ),//IIC时钟线 .io_iic_sda (io_iic_sda) //IIC双向数据线 ); always @(posedge w_clk_125khz or posedge w_clk_125khz_rst)begin if(w_clk_125khz_rst)begin ri_eeprom_addr <= 'd0; ri_user_operation_addr <= 'd0; ri_user_operation_type <= 'd0; ri_user_operation_len <= 'd0; ri_user_operation_valid <= 'd0; end else if(o_user_operation_ready && r_wr_st == 0)begin ri_eeprom_addr <= 3'b011; ri_user_operation_addr <= 'd0; ri_user_operation_type <= P_W; ri_user_operation_len <= P_WRITE_NUM; ri_user_operation_valid <= 'd1; end else if(o_user_operation_ready && r_wr_st == 1)begin ri_eeprom_addr <= 3'b011; ri_user_operation_addr <= 'd0; ri_user_operation_type <= P_R; ri_user_operation_len <= P_WRITE_NUM; ri_user_operation_valid <= 'd1; end else begin ri_eeprom_addr <= 'd0; ri_user_operation_addr <= 'd0; ri_user_operation_type <= 'd0; ri_user_operation_len <= 'd0; ri_user_operation_valid <= 'd0; end end always @(posedge w_clk_125khz or posedge w_clk_125khz_rst)begin if(w_clk_125khz_rst) ri_user_write_date <= 'd0; else if(ri_user_write_valid) ri_user_write_date <= ri_user_write_date + 1; else ri_user_write_date <= ri_user_write_date; end always @(posedge w_clk_125khz or posedge w_clk_125khz_rst)begin if(w_clk_125khz_rst) ri_user_write_sop <= 'd0; else if(w_user_active && ri_user_operation_type == P_W) ri_user_write_sop <= 'd1; else ri_user_write_sop <= 'd0; end always @(posedge w_clk_125khz or posedge w_clk_125khz_rst)begin if(w_clk_125khz_rst) ri_user_write_valid <= 'd0; else if(ri_user_write_eop) ri_user_write_valid <= 'd0; else if(w_user_active && ri_user_operation_type == P_W) ri_user_write_valid <= 'd1; else ri_user_write_valid <= ri_user_write_valid; end always @(posedge w_clk_125khz or posedge w_clk_125khz_rst)begin if(w_clk_125khz_rst) ri_user_write_eop <= 'd0; // else if((w_user_active || ri_user_write_valid) && r_write_cnt == P_WRITE_NUM - 2) // ri_user_write_eop <= 'd1; else if(w_user_active && P_WRITE_NUM == 1) ri_user_write_eop <= 'd1;//write 1 byte else if(ri_user_write_valid && r_write_cnt == P_WRITE_NUM - 2) ri_user_write_eop <= 'd1;//write over 1 byte else ri_user_write_eop <= 'd0; end always @(posedge w_clk_125khz or posedge w_clk_125khz_rst)begin if(w_clk_125khz_rst) r_write_cnt <= 'd0; else if(r_write_cnt == P_WRITE_NUM - 1) r_write_cnt <= 'd0; else if(ri_user_write_valid) r_write_cnt <= r_write_cnt + 1'd1; else r_write_cnt <= r_write_cnt; end always @(posedge w_clk_125khz or posedge w_clk_125khz_rst)begin if(w_clk_125khz_rst) r_wr_st <= 'd0; else if(w_user_active) r_wr_st <= r_wr_st + 1'd1; else r_wr_st <= r_wr_st; end endmodule
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。