当前位置: 首页 > news >正文

基于FPGA的IIC控制EEPROM读写(2)

基于FPGA的IIC控制EEPROM读写

文章目录

  • 基于FPGA的IIC控制EEPROM读写
    • 一、EEPROM简介
    • 二、代码实现——个人理解
      • 1、状态机
      • 2、仿真效果
      • 3、上板验证
      • 4、代码
        • top.v
        • iic_master.v
        • uart
    • 三、代码实现——复用性较高的IIC模块
      • 1、框架设计
      • 2、状态机设计
      • 3、仿真效果
      • 4、上板验证
      • 5、代码
        • top.v
        • iic_top.v
        • iic_ctrl.v
        • iic_driver.v
        • fsm_key.v
        • uart
    • 四、总结

一、EEPROM简介

Microchip Technology公司生产的24XX04*型微芯片技术(编号24AA04/24LC04B)是一款4千比特电可擦可编程只读存储器(EPROM)。该器件采用双存储块结构,每个存储块包含256×8位内存,并配备双线串行接口。其低压设计支持1.7V以下工作电压,待机电流仅为1 μA,工作电流仅1 mA。该芯片还具备最多16字节的页写入功能。产品提供标准8引脚PDIP封装、表面贴装SOIC封装、TSSOP封装、2x3 DFN封装和MSOP封装等多种规格,同时也有5引脚SOT-23封装可供选择。
在这里插入图片描述
其可以兼容iic读写,iic基础知识以及实现过程详见基于FPGA的IIC控制EEPROM读写(1)

控制字节(iic中的地址)是主设备在接收到开始条件后发送的第一个字节,由四位控制码构成。对于24XX04芯片而言,读写操作时该位设置为二进制‘1010’。控制字节的后两位对24XX04芯片来说是“无关紧要”的,而最后一位B0位则由主设备用来选择要访问的两个256字节内存块中的哪一个——这个位实际上就是地址字的最高有效位。控制字节的最后一位决定了操作类型:当设置为‘1’时选择读取操作,当设置为‘0’时选择写入操作。在开始条件触发后,24XX04芯片会持续监控SDA总线,检查传输的设备类型标识符。一旦接收到‘1010’代码,从设备就会在SDA线上输出确认信号。根据读/写位的状态,24XX04芯片会选择执行读取或写入操作。
在这里插入图片描述
在这里插入图片描述
字节写:在主设备发出启动条件后,主设备发送器会将设备代码(4位)、块地址(3位)以及处于低电平的读/写位(R/W)同步传输至总线。这向被寻址的从设备接收器发出信号:当其在第九个时钟周期生成确认信号后,将接收到包含字地址的字节数据。因此,主设备接下来传输的字节即为该字地址,并会被写入24XX04的地址指针。当从设备再次返回确认信号后,主设备会将待写入目标内存位置的数据字节发送出去。此时24XX04会再次确认,主设备随即触发停止条件。这一操作启动内部写入周期,在此期间24XX04将不再发送确认信号

在这里插入图片描述
页写写控制字节、字地址和首个数据字节的传输方式与字节写入操作相同。但主设备不会触发停止条件,而是向24XX04发送最多16个数据字节,这些数据会暂存于片上页缓冲区,并在主设备发出停止条件后写入内存。每当接收到一个字时,地址指针的四个低位地址位会在内部递增‘1’,而字地址的高位7位保持不变。若主设备在触发停止条件前发送超过16个字,地址计数器将发生溢出,先前接收的数据会被覆盖。与字节写入操作类似,一旦接收到停止条件,系统就会启动内部写入周期。

在这里插入图片描述
读取操作的启动方式与写入操作相同,唯一的区别是将从地址的R/W位设置为“1”。有三种基本类型的读取操作:当前地址读取、随机读取和顺序读取。

当前地址读取 :24XX04芯片内置地址计数器,用于记录最近一次访问的地址,并通过内部递增‘1’来保持该地址。因此,如果前次访问(无论是读取还是写入操作)的目标地址是n,那么下一次当前地址读取操作将访问地址n + 1的数据。当接收到从设备地址且R/W位设置为‘1’时,24XX04会发出确认信号并传输8位数据字。主设备不会对此次传输进行确认,但会生成停止条件,此时24XX04将终止传输。

在这里插入图片描述

随机读取 :随机读取操作允许主设备以随机方式访问任意内存位置。执行此类读取操作时,首先需要设置字地址。该操作通过将字地址作为写入操作的一部分发送至24XX04芯片来实现。当字地址发送完成后,主设备会在收到确认信号后触发启动条件。这会终止写入操作,但在此之前内部地址指针已被设置。随后主设备再次发出控制字节,但此时R/W位被设置为‘1’。24XX04芯片随即发出确认信号并传输8位数据字。虽然主设备不会对传输进行确认,但会生成停止条件,此时24XX04芯片将停止传输。

在这里插入图片描述
顺序读取:顺序读取的启动方式与随机读取相同,但不同之处在于:当24XX04开始传输首个数据字节时,主设备会发出确认信号,而随机读取则会触发停止条件。这使得24XX04能够继续传输下一个按顺序寻址的8位字(图7-3)。为实现顺序读取功能,24XX04内置了一个地址指针,每次操作完成后该指针都会递增1。通过这个地址指针,整个内存内容可以在单次操作中完成串行读取。
在这里插入图片描述

二、代码实现——个人理解

1、状态机

在这里插入图片描述
基本实现流程与IIC协议类似,只不过在设计的时候将发送各个数据的时候多加入了几个应答状态;根据EEPROM的读写规则,在进行读操作时,要先进行发送写指令,并将要读取数据的地址写入,再次发送开始信号,再进行发送读指令,所以再发送字地址之后的ACK2应答状态完成后,跳回START状态,发送完控制字节后,在ACK1跳转到读状态,进行读操作。

2、仿真效果

在这里插入图片描述
接收到02开始信号,在4‘d1即开始状态,scl与sda符合iic协议的开始信号描述。
在这里插入图片描述

开始信号结束,跳转到发送控制字节的状态,信号有效时,依据iic的发送数据规则发送控制字节,由eeprom的简介可知,其内部地址为1010,最后一位是读写命令,倒数第二个为所操作的寄存器块,其他两位数据无关紧要,所以在此发送的控制字节为1010_0000为8’ha0,由仿真可看出符合iic发送数据的规则(scl高电平数据有效,低电平数据改变);发送完成进入应答位,等待应答,由于是仿真,所以应答是仿真所给的激励信号sda_in,一直为低,表有效,等待计数器计数完成,跳转至发送字地址。

在这里插入图片描述

这里设置操作的字地址为8’h10,所发数据正确,ack有应答,跳转至发送数据状态。

在这里插入图片描述
发送数据由随机数函数产生,此时为e8对应二进制位1110_1000发送正确,应答有效,跳转发送数据状态,继续发送;这里一共发送了6次随机数,不在展示后面的数据。

在这里插入图片描述
接收到8‘h03停止信号,发送正确,应答有效,之后跳到STOP状态,scl高电平期间sda产生由低到高的跳变,发送停止信号。发送完成跳回IDLE空闲状态。

接下来进行读操作,开始操作和写相同
在这里插入图片描述
由于仿真文件编写有瑕疵,控制字节发送时并未将真实的1010_0001写入,写入的为1111_1111,但不影响后续仿真。根据eeprom简介可知,要进行顺序读操作,第一次要发送写操作,所以在这里对真是发送过来的控制字节进行操作

always @(posedge clk or negedge rst_n)beginif(!rst_n)beginctrl_data <= 8'd0;endelse if((state_c == CTRL_BYTE) && (!w_mod) )beginctrl_data <= data_in;endelse if(state_c == CTRL_BYTE && w_mod ==1 && ctrl_flg)beginctrl_data <= {data_in[7:1],1'b0};endelse if(state_c == CTRL_BYTE && w_mod ==1 && !ctrl_flg)beginctrl_data <= {ctrl_data[7:1],1'b1};endelse beginctrl_data <= ctrl_data;end
end

设置一个控制字节寄存器,当为读模式,且第一次进入发送控制字节状态(由ctrl_flg)控制,真实发送给从机的控制字节为数据前七位拼一个0发送给从机,收到应答后发送字地址。

always@(posedge clk or negedge rst_n)beginif(!rst_n)beginw_mod <= 1'b0;endelse if((state_c == CTRL_BYTE) && rx_vld )beginw_mod <= data_in[0];end
end

在接收到由电脑发送来的控制字节时,将“真”读写模式寄存下来,当发送完字地址,有应答后,检测到真读写模式为读后跳转到START状态发送起始信号,之后再次进入发送控制字节状态。

always @(posedge clk or negedge rst_n)beginif(!rst_n)beginctrl_flg <= 1'b1;endelse if(ACK2_2_START)beginctrl_flg <= 1'b0;endelse if(STOP_2_IDLE)beginctrl_flg <= 1'b1;endelse beginctrl_flg <= ctrl_flg;end 
end

当从ACK2跳转到START时,将ctrl_flg改变,表示为第二次进入发送控制字节状态。
在这里插入图片描述
此时发送的控制字节为寄存的地址数据前七为拼一个1,发送给从机,发送完成,检测时第二次跳到发送控制字节状态,有应答后跳转到读数据模式。

在这里插入图片描述
由仿真可知,此时读出的数据为0011_1011为3b,接收数据有效,tx_vld信号拉高,提示uart_tx模块开始发送数据。

在这里插入图片描述
等待tx发送完数据(tx_done),再继续读操作。

在这里插入图片描述
当接收到tx发送的tx_done跳转至应答状态,由FPGA发送给从机应答,0表示应答有效,计数完成再次进入读数据模式。

在这里插入图片描述

对比数据,发送全部正确。

在这里插入图片描述
停止时由FPGA发送NACK,此时sda_out拉高,表示无应答;计数完成进入STOP状态,发送停止信号,结束。

3、上板验证

在这里插入图片描述

上电直接进行读操作 02(开始),a1(器件地址+1读命令),01(字地址),03(结束)。读出的数据为01.

在这里插入图片描述

改变字地址,读出的数据为02。

在这里插入图片描述
改变字地址,读出数据为23。

在这里插入图片描述

进行写操作,再字地址01,依次写入23 34 45 56 。

在这里插入图片描述

依次将数读出,数据正确,上板验证成功。

4、代码

top.v
module top (input clk,input rst_n,input rx,output tx,// input sda_in,// output sda_out,output scl,inout sda,input [1:0] sw
);
wire [7:0] rx_data;
wire rx_done;
wire tx_done;wire [7:0]data_out_m;
wire tx_vld_m;uart_rx inst_uart_rx (.clk(clk),.rst_n(rst_n),.rx(rx),.sw(sw),.rx_data(rx_data),.rx_done(rx_done)
);iic_master inst_iic_master (.clk        (clk)       ,.rst_n      (rst_n)     ,.data_in    (rx_data)   ,.rx_vld     (rx_done)   ,// .sda_in     (sda_in)    ,// .sda_out    (sda_out)   ,.data_out   (data_out_m),.tx_vld     (tx_vld_m)  ,.sda        (sda)       ,.tx_done    (tx_done)   ,.scl        (scl)
);uart_tx inst_uart_tx (.clk        (clk),.rst_n      (rst_n),.tx_data    (data_out_m),.tx_start   (tx_vld_m),.sw         (sw),.tx         (tx),.tx_done    (tx_done)
);endmodule
iic_master.v
module iic_master (input clk,input rst_n,input [7:0] data_in,input rx_vld,input tx_done,inout sda,// input sda_in,// output reg sda_out,output reg [7:0] data_out,output  tx_vld,output reg scl
);//三态门数据
wire sda_in;
reg  sda_out;
reg  sda_en;assign sda_in = sda;
assign sda    = sda_en ? sda_out : 1'bz;localparam              IDLE        =     4'd0,START       =     4'd1,CTRL_BYTE   =     4'd2,ACK1        =     4'd3,SLAVE_ADDR  =     4'd4,ACK2        =     4'd5,W_DATA      =     4'd6,R_DATA      =     4'd7,ACK3        =     4'd8,STOP        =     4'd9;reg [3:0] state_c;      //现态
reg [3:0] state_n;      //次态//数据缓存
reg [7:0] data;  
reg [7:0] ctrl_data;
reg ctrl_flg;       wire                    IDLE_2_START            ,START_2_CTRL_BYTE       ,CTRL_BYTE_2_ACK1        ,ACK1_2_SLAVE_ADDR       ,SLAVE_ADDR_2_ACK2       ,ACK2_2_START            ,ACK2_2_W_DATA           ,W_DATA_2_ACK3           ,ACK3_2_W_DATA           ,ACK1_2_R_DATA           ,R_DATA_2_ACK3           ,ACK3_2_R_DATA           ,ACK3_2_STOP             ,STOP_2_IDLE             ;reg                     w_mod;//400_000深度
reg           [6:0]     cnt_dp                  ;
reg                     add_cnt_dp              ;
wire                    end_cnt_dp              ;
parameter               depth       =     7'd125;//bit计数
reg           [2:0]     cnt_bit                 ;
wire                    add_cnt_bit             ;
wire                    end_cnt_bit             ;
reg [7:0]               max_bit;reg ack;//ack
always @(*)begincase(state_c)ACK1,ACK2,ACK3:    begin ack = (sda_in == 0) ? 1'b0 : ack; enddefault:     ack = 1'b1;endcase
end always@(posedge clk or negedge rst_n)beginif(!rst_n)beginw_mod <= 1'b0;endelse if((state_c == CTRL_BYTE) && rx_vld )beginw_mod <= data_in[0];end
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)begindata <= 8'd0;endelse if(rx_vld )begindata <= data_in;end
endalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginctrl_flg <= 1'b1;endelse if(ACK2_2_START)beginctrl_flg <= 1'b0;endelse if(STOP_2_IDLE)beginctrl_flg <= 1'b1;endelse beginctrl_flg <= ctrl_flg;end 
endalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginctrl_data <= 8'd0;endelse if((state_c == CTRL_BYTE) && (!w_mod) )beginctrl_data <= data_in;endelse if(state_c == CTRL_BYTE && w_mod ==1 && ctrl_flg)beginctrl_data <= {data_in[7:1],1'b0};endelse if(state_c == CTRL_BYTE && w_mod ==1 && !ctrl_flg)beginctrl_data <= {ctrl_data[7:1],1'b1};endelse beginctrl_data <= ctrl_data;end
endalways @(*) beginif((state_c == CTRL_BYTE) || (state_c == SLAVE_ADDR) || (state_c == W_DATA) || (state_c == R_DATA))beginmax_bit <= 8;endelse beginmax_bit <= 1;end
end//速度计数器
always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_dp <= 0;endelse if(add_cnt_dp)beginif(end_cnt_dp)begincnt_dp <= 0;endelse begincnt_dp <= cnt_dp + 1;endendelse begincnt_dp <= cnt_dp;end
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)beginadd_cnt_dp <= 0;endelse if(rx_vld && (state_c != IDLE && state_c != R_DATA)|| IDLE_2_START || ACK1_2_R_DATA || ACK3_2_R_DATA || ACK3_2_STOP || R_DATA_2_ACK3 || ACK2_2_START || ((state_c == CTRL_BYTE) && (ctrl_flg == 0)))beginadd_cnt_dp <= 1;endelse if((state_c == START || state_c == ACK1 || state_c == ACK2 || state_c == ACK3 || state_c == R_DATA || state_c == STOP) && (end_cnt_bit))beginadd_cnt_dp <= 0;endelse beginadd_cnt_dp <= add_cnt_dp;end
endassign end_cnt_dp = add_cnt_dp && cnt_dp == depth-1; //bit计数器
always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_bit <= 0;endelse if(add_cnt_bit)beginif(end_cnt_bit)begincnt_bit <= 0;endelse begincnt_bit <= cnt_bit + 1;endendelse begincnt_bit <= cnt_bit;end
endassign add_cnt_bit = end_cnt_dp && (state_c != IDLE);
assign end_cnt_bit = add_cnt_bit && cnt_bit == (max_bit-1);//状态机一段
always @(posedge clk or negedge rst_n) beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;end
end//状态机二段
always @(*) begincase(state_c)IDLE:       state_n <= (IDLE_2_START)        ? START      : state_c;START:      state_n <= (START_2_CTRL_BYTE)   ? CTRL_BYTE  : state_c;CTRL_BYTE:  state_n <= (CTRL_BYTE_2_ACK1)    ? ACK1       : state_c;ACK1:       state_n <= (ACK1_2_SLAVE_ADDR)   ? SLAVE_ADDR : ((ACK1_2_R_DATA) ? R_DATA : state_c);    SLAVE_ADDR: state_n <= (SLAVE_ADDR_2_ACK2)   ? ACK2        : state_c;ACK2:       state_n <= (ACK2_2_W_DATA)       ? W_DATA      : ((ACK2_2_START) ? START : state_c);W_DATA:     state_n <= (W_DATA_2_ACK3)       ? ACK3        : state_c;ACK3:       beginif(ACK3_2_STOP)beginstate_n <= STOP;endelse if(ACK3_2_R_DATA)beginstate_n <= R_DATA;endelse if(ACK3_2_W_DATA)beginstate_n <= W_DATA;endelse beginstate_n <= state_c;endendR_DATA:     state_n <= (R_DATA_2_ACK3)       ? ACK3        : state_c;STOP:       state_n <= (STOP_2_IDLE)         ? IDLE        : state_c;default:    state_n <= IDLE;endcase
end//描述跳转条件
assign IDLE_2_START         = (state_c == IDLE      ) && (data== 8'h02);
assign START_2_CTRL_BYTE    = (state_c == START     ) && (end_cnt_bit);
assign CTRL_BYTE_2_ACK1     = (state_c == CTRL_BYTE ) && (end_cnt_bit);
assign ACK1_2_SLAVE_ADDR    = (state_c == ACK1      ) && (end_cnt_bit) && (!ack) && (ctrl_flg);
assign SLAVE_ADDR_2_ACK2    = (state_c == SLAVE_ADDR) && (end_cnt_bit);
assign ACK2_2_W_DATA        = (state_c == ACK2      ) && (end_cnt_bit) && (!ack ) && (!w_mod); 
assign W_DATA_2_ACK3        = (state_c == W_DATA    ) && (cnt_bit == (max_bit-1)&& cnt_dp == 100);
assign ACK3_2_W_DATA        = (state_c == ACK3      ) && (end_cnt_bit) && (!ack ) && (!w_mod);assign ACK2_2_START         = (state_c == ACK2      ) && (end_cnt_bit) && (!ack) && w_mod;
assign ACK1_2_R_DATA        = (state_c == ACK1      ) && (end_cnt_bit) && (!ack ) && (w_mod) && (!ctrl_flg);
assign ACK3_2_STOP          = (state_c == ACK3      ) && (end_cnt_bit) && ((!ack ) && (!w_mod) || ((w_mod) && (sda_out == 1))) && (data == 8'h03);
assign R_DATA_2_ACK3        = (state_c == R_DATA    ) && tx_done;
assign ACK3_2_R_DATA        = (state_c == ACK3      ) && (end_cnt_bit) && (w_mod) && (data != 8'h03);
assign STOP_2_IDLE          = (state_c == STOP      ) && (end_cnt_bit)                  ;//scl描述
always @(posedge clk or negedge rst_n) beginif(!rst_n)beginscl <= 1'b1;endelse begincase(state_c) IDLE :scl <= 1'b1;CTRL_BYTE,SLAVE_ADDR,W_DATA,R_DATA,ACK1,ACK2,ACK3   :beginif((cnt_dp < 7'd31) || (cnt_dp > 7'd93))beginscl <= 1'b0;endelse beginscl <= 1'b1;endendSTOP :beginif(cnt_dp < 7'd31)beginscl <= 1'b0;endelse beginscl <= 1'b1;endenddefault:beginscl <= 1'b1;endendcaseend
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_out <= 1'b1;endelse begincase(state_c)IDLE:       sda_out <= 1'b1;START:      sda_out <= (cnt_dp <= ((depth-1)>>1) && add_cnt_dp) ?  1'b1 :  1'b0;CTRL_BYTE: begin sda_out <= ctrl_data[7-cnt_bit];if(cnt_dp == 0)beginsda_out <= 1'b0;endendSLAVE_ADDR,W_DATA: begin sda_out <= data[7-cnt_bit];if(cnt_dp == 0 )beginsda_out <= 1'b0;endendR_DATA:  sda_out <= 1'b0;ACK1,ACK2:     sda_out <= 1'b0;ACK3:    beginif(w_mod && (data == 8'h03))begin sda_out <= 1'b1;endelse if(!w_mod && (data == 8'h03))beginsda_out <= 1'b0;endelse beginsda_out <= 1'b0;end   endSTOP:    sda_out <= (cnt_dp <= ((depth-1)>>1)) ?  1'b0 :  1'b1;default:  sda_out <= 1'b1;endcaseend
end
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_en <= 1'b0;endelse begincase(state_c)IDLE :sda_en <= 1'b1;START,CTRL_BYTE,SLAVE_ADDR,W_DATA,STOP: sda_en <= 1'b1;ACK1,ACK2:  sda_en <=1'b0  ;// sda_en <= (!w_mod) ? 1'b0 : (first_ack) ? 1'b0 :1'b1;ACK3:    sda_en <= (!w_mod) ? 1'b0 : 1'b1;R_DATA:  sda_en <= 1'b0;default: sda_en <= 1'b1;endcaseend
endalways@(posedge clk or negedge rst_n)beginif(!rst_n)begindata_out <= 8'b0;endelse if((state_c == R_DATA) && (cnt_dp == (depth -1)>>1))begindata_out[7-cnt_bit]  <= sda_in;end
endassign tx_vld =  (state_c == R_DATA)&& end_cnt_bit && w_mod;//(state_c == ACK) && end_cnt_bit && (!first_ack) && w_mod;
endmodule
uart

详见uart详解

三、代码实现——复用性较高的IIC模块

1、框架设计

在这里插入图片描述
同样由PC通过uart串口发送数据到FPGA,在iic协议描述时分为两个模块,一个为对数据以及命令操作的ctrl模块,和iic驱动模块。写操作时,PC只要发送需要写入的数据。读操作时,通过按键控制,按下一次,读出一位数据。
rw_flag为控制iic_driver读写开始的信号。
cmd为执行何种操作的指令。
wr_data为写入的数据。
rd_data为所读出的数据。
trans_done为读出数据结束的标志。
slave_ack为从机给出的应答信号。

2、状态机设计

在这里插入图片描述
在这里插入图片描述
考虑到复用性,在iic驱动编写时采用这种一帧一帧的数据传输,其分为以下几种情况:
1、带起始信号的写;
2、普通写;
3、带停止信号的读;
4、普通读;
5、带停止信号的读。
由eeprom的介绍可在iic的控制模块中,通过设置cmd来表示所发的数据具体有哪些。
例如,要进行字节写时,要发送3帧数据,分别为带起始信号写,普通写,带停止信号的写。
相对应的为起始信号+控制字节,字地址,数据+停止信号。对应的cmd为0011、0010、1010。

控制模块设计
iic_ctrl模块能够实现对驱动的简单控制,实现字节写操作和随机地址读。

//---------<rw_flag cmd wr_data>------------------------------------------------- always @(*)beginif(state_c == WRITE)begincase (cnt_byte)0 : begin  rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end1 : begin  rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr;  end2 : begin  rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd     = 4'd0; endendcaseendelse if(state_c == READ)begincase (cnt_byte)0: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end1: begin   rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end2: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end3: begin   rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd     = 4'd0; endendcaseendelse beginwr_data = 8'd0;rw_flag = 1'd0; cmd     = 4'd0;end
end

为了方便,这里运用了一个简单的状态机,进行读和写的区分,这是第二段状态机,当进入到不同的读写模式时,进行相应的操作。

3、仿真效果

仿真逻辑:先发两个字节的数据,再依次读出

在这里插入图片描述

当检测到有数据输入,就有空闲状态跳转到start状态,由仿真可知开始信号发送正确,时钟计数器计满一个周期后跳转到写数据状态。

在这里插入图片描述
写8bit数据,由仿真波形可知数据发送成功。进入w_ack应答状态等待从机响应。

在这里插入图片描述
4状态为应答状态,从机模型给出应答,跳转至写状态,进行写字地址操作。写完继续跳转应答状态,有应答后继续跳转最后一带停止信号的写。

在这里插入图片描述
此时发送的数据就为写入的数据0010_0100为8’h24,发送完成跳转至停止状态,发送停止信号,完成后跳转至IDLE
下一个数据到来时继续以上操作。

在这里插入图片描述
进行读操作,起始信号之后发虚写的a0控制字节+0。

在这里插入图片描述
应答有效后,继续写入字地址0000_0000,有应答,发送带起始信号的写,此时写的数据为控制数据+1,有应答后跳转。

在这里插入图片描述
跳转到读数据模式,此时总线被主机拉低,接收从机发来的数据0010_0100,接收完成跳转到ack应答状态,由主机发送NACK给从机,发送完成,发送停止信号,此时数据传给uart_tx模块。

在这里插入图片描述
当tx将数据发送完成才继续读下一字节数据。仿真验证成功。

4、上板验证

在这里插入图片描述
连续按下按键,数据从0000_0000地址开始向后将读数据读出。

在这里插入图片描述
连续写入数据,写入也从0000_0000地址开始写入。

在这里插入图片描述
不按复位键继续按下读数据按键,其会继续上一个读地址加一继续向后读,按下复位,读取数据,其读取的三个地址的数据为刚刚覆写的数据。上板验证成功。

5、代码

top.v
module top (input               clk,input               rst_n,input               uart_rx,output              uart_tx,input     [0:0]     key_in,output              scl,inout               sda
);
wire      [7:0]         rx_data;
wire                    rx_vld;
wire                    ready;
wire      [7:0]         tx_data;
wire                    tx_vld;
wire      [0:0]         key_down;
wire                    sda_en;
wire                    sda_in;
wire                    sda_out;
wire      [1:0]         sw;
wire                    tx_done;assign  sw=2'b11;assign  sda_in = sda;
assign  sda    = sda_en ? sda_out : 1'bz;iic_top inst_iic_top(.clk        (clk     ),.rst_n      (rst_n   ),.rx_data    (rx_data ),.rx_vld     (rx_vld  ),.ready      (ready   ),.tx_data    (tx_data ),.tx_vld     (tx_vld  ),.key_down   (key_down),.sda_in     (sda_in  ),.sda_out    (sda_out ),.sda_en     (sda_en  ),.scl        (scl     )
);fsm_key inst_fsm_key(.clk	    (clk),.rst_n	    (rst_n),.key_in	    (key_in),.key_down   (key_down)
);uart_rx inst_uart_rx(.clk        (clk),.rst_n      (rst_n),.rx         (uart_rx),.sw         (sw),.rx_data    (rx_data),.rx_done    (rx_vld)
);uart_tx inst_uart_tx(.clk        (clk),.rst_n      (rst_n),.tx_data    (tx_data),.tx_start   (tx_vld),.sw         (sw),.tx         (uart_tx),.tx_done    (tx_done)
);
endmodule
iic_top.v
module iic_top (input           clk,input           rst_n,//---------<rx>------------------------------------------------- input   [7:0]   rx_data,input           rx_vld,//---------<tx>------------------------------------------------- input           ready,output  [7:0]   tx_data,output          tx_vld,//---------<key>------------------------------------------------- input           key_down,//---------<iic>------------------------------------------------- input           sda_in,output          sda_out,output          sda_en,output          scl
);
wire [7:0] rd_data;
wire       trans_done;
wire       slave_ack;
wire       rw_flag;
wire [3:0] cmd;
wire [7:0] wr_data;iic_ctrl    inst_iic_ctrl(.clk            (clk        ),.rst_n          (rst_n      ),.key_down       (key_down   ),.rx_data        (rx_data    ),.rx_vld         (rx_vld     ),.ready          (ready      ),.tx_data        (tx_data    ),.tx_vld         (tx_vld     ),.rd_data        (rd_data    ),.trans_done     (trans_done ),.slave_ack      (slave_ack  ),.rw_flag        (rw_flag    ),.cmd            (cmd        ),.wr_data        (wr_data    )
);iic_driver  inst_iic_driver  (.clk            (clk       ),.rst_n          (rst_n     ),.wr_data        (wr_data   ),.cmd            (cmd       ),.rw_flag        (rw_flag   ),.sda_in         (sda_in    ),.sda_out        (sda_out   ),.sda_en         (sda_en    ),.scl            (scl       ),.rd_data        (rd_data   ),.slave_ack      (slave_ack ),.trans_done     (trans_done)
);endmodule
iic_ctrl.v
module iic_ctrl (input               clk,input               rst_n,//keyinput               key_down,//uart_rxinput  [7:0]        rx_data,input               rx_vld,//uart_txinput               ready,output reg [7:0]    tx_data,output reg          tx_vld,//iic_driverinput  [7:0]        rd_data,input               trans_done,input               slave_ack,output reg          rw_flag,output reg [3:0]    cmd,output reg [7:0]    wr_data  
);//简易读写状态机
localparam  IDLE    = 2'b00,WRITE   = 2'b01,READ    = 2'b10,DONE    = 2'b11;wire        IDLE_2_WRITE,IDLE_2_READ,WRITE_2_DONE,READ_2_DONE;reg     [1:0]   state_c ;
reg     [1:0]   state_n ;//字节计数器
reg		[7:0]	cnt_byte;
wire			add_cnt_byte;
wire			end_cnt_byte;//写地址计数器
reg		[7:0]	cnt_wr_addr	   ;
wire			add_cnt_wr_addr;
wire			end_cnt_wr_addr;//读地址计数器
reg		[7:0]	cnt_rd_addr	   ;
wire			add_cnt_rd_addr;
wire			end_cnt_rd_addr;localparam  CMD_START = 4'b0001, //开始命令CMD_SEND  = 4'b0010, //发送命令CMD_RECV  = 4'b0100, //接收命令CMD_STOP  = 4'b1000; //停止命令localparam WR_ID = 8'ha0;
localparam RD_ID = 8'ha1;//---------<State Machine>------------------------------------------------- //第一段:同步时序描述状态转移
always @(posedge clk or negedge rst_n)begin if(!rst_n)beginstate_c <= IDLE;end else begin state_c <= state_n;end 
end//第二段:组合逻辑判断状态转移条件,描述状态转移规律
always @(*) begincase(state_c)IDLE    : beginif(IDLE_2_WRITE)beginstate_n = WRITE;endelse if(IDLE_2_READ)beginstate_n = READ;endelse beginstate_n = state_c;endendWRITE   : state_n = (WRITE_2_DONE) ? DONE : state_c;READ    : state_n = (READ_2_DONE) ? DONE : state_c;DONE    : state_n = IDLE;default : ;endcase
endassign  IDLE_2_WRITE  =  (state_c == IDLE ) && rx_vld;
assign  IDLE_2_READ   =  (state_c == IDLE ) && key_down;
assign  WRITE_2_DONE  =  (state_c == WRITE) && end_cnt_byte ;
assign  READ_2_DONE   =  (state_c == READ ) && end_cnt_byte ;//---------<cnt_byte>------------------------------------------------- always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_byte <= 'd0;end else if(add_cnt_byte)begin if(end_cnt_byte)begin cnt_byte <= 'd0;endelse begin cnt_byte <= cnt_byte + 1'b1;end end
end assign add_cnt_byte = trans_done && (state_c == WRITE || state_c == READ);
assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c ==WRITE) ? (3-1) : (4-1));//---------<cnt_wr_addr>------------------------------------------------- always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_wr_addr <= 'd0;end else if(add_cnt_wr_addr)begin if(end_cnt_wr_addr)begin cnt_wr_addr <= 'd0;endelse begin cnt_wr_addr <= cnt_wr_addr + 1'b1;end end
end assign add_cnt_wr_addr = WRITE_2_DONE;
assign end_cnt_wr_addr = add_cnt_wr_addr && cnt_wr_addr == (8'hff-1);//---------<cnt_rd_addr>------------------------------------------------- always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_rd_addr <= 'd0;end else if(add_cnt_rd_addr)begin if(end_cnt_rd_addr)begin cnt_rd_addr <= 'd0;endelse begin cnt_rd_addr <= cnt_rd_addr + 1'b1;end end
end assign add_cnt_rd_addr = READ_2_DONE;
assign end_cnt_rd_addr = add_cnt_rd_addr && cnt_rd_addr == (8'hff-1);//---------<rw_flag cmd wr_data>------------------------------------------------- always @(*)beginif(state_c == WRITE)begincase (cnt_byte)0 : begin  rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end1 : begin  rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr;  end2 : begin  rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd     = 4'd0; endendcaseendelse if(state_c == READ)begincase (cnt_byte)0: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end1: begin   rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end2: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end3: begin   rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; enddefault : beginwr_data = 8'd0;rw_flag = 1'd0;cmd     = 4'd0; endendcaseendelse beginwr_data = 8'd0;rw_flag = 1'd0; cmd     = 4'd0;end
end//---------<tx_data tx_vld>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin if(!rst_n)begintx_data <= 'd0;tx_vld <= 'd0;end else if(READ_2_DONE)begin tx_data <= rd_data;tx_vld <= 1'b1;end else begin tx_data <= tx_data;tx_vld <=1'b0;end 
endendmodule
iic_driver.v
module iic_driver #(parameter SYS_CLK = 50_000_000,IIC_RATE = 200_000)(input clk,input rst_n,input [7:0] wr_data,input [3:0] cmd,input rw_flag,input sda_in,output reg sda_out,output reg sda_en,output reg scl,output reg [7:0] rd_data,output reg slave_ack,output trans_done
);localparam  CMD_START = 4'b0001, //开始命令CMD_SEND  = 4'b0010, //发送命令CMD_RECV  = 4'b0100, //接收命令CMD_STOP  = 4'b1000; //停止命令
//状态描述
localparam  IDLE    = 3'd0,START   = 3'd1,SEND    = 3'd2,RECV    = 3'd3,R_ACK   = 3'd4,S_ACK   = 3'd5,STOP    = 3'd6;wire        IDLE_2_START,IDLE_2_SEND,IDLE_2_RECV,START_2_SEND,START_2_RECV,SEND_2_RACK,RECV_2_SACK,RACK_2_IDLE,RACK_2_STOP,SACK_2_IDLE,SACK_2_STOP,STOP_2_IDLE;reg [2:0] state_c;  //现态
reg [2:0] state_n;  //次态//时钟计数
reg [7:0] cnt_scl;
wire      add_cnt_scl;
wire      end_cnt_scl;
parameter CNT_SCL_MAX = SYS_CLK/IIC_RATE;//bit计数
reg [2:0] cnt_bit;
wire      add_cnt_bit;
wire      end_cnt_bit;
parameter CNT_BIT_MAX = 4'd8;//时钟计数器
always @(posedge clk or negedge rst_n) beginif (!rst_n) begincnt_scl <= 0;end else if(add_cnt_scl)beginif (end_cnt_scl) begincnt_scl <= 0;endelse begincnt_scl <= cnt_scl + 1'b1;end endelse begincnt_scl <= cnt_scl;end
endassign add_cnt_scl = (state_c !== IDLE);
assign end_cnt_scl = add_cnt_scl && (cnt_scl == (CNT_SCL_MAX -1));//bit计数器
always @(posedge clk or negedge rst_n) begin if(!rst_n)begincnt_bit <= 0;endelse if(add_cnt_bit)beginif(end_cnt_bit)begincnt_bit <= 0;endelse begincnt_bit <= cnt_bit + 1'b1;endendelse begincnt_bit <= cnt_bit;end
endassign add_cnt_bit = end_cnt_scl && (state_c == SEND || state_c == RECV);
assign end_cnt_bit = add_cnt_bit && (cnt_bit == (CNT_BIT_MAX-1));//状态机一段
always @(posedge clk or negedge rst_n) beginif (!rst_n) beginstate_c <= IDLE;end else beginstate_c <= state_n;end
end//状态机二段
always @(*)begincase(state_c)IDLE: beginif (IDLE_2_START) beginstate_n = START;end else if(IDLE_2_SEND)beginstate_n = SEND;endelse if(IDLE_2_RECV)beginstate_n = RECV;endelse beginstate_n = state_c;endendSTART: begin if (START_2_SEND) beginstate_n = SEND;end else if(START_2_RECV)beginstate_n = RECV;endelse beginstate_n = state_c;endendSEND: state_n = (SEND_2_RACK) ? R_ACK : state_c;RECV: state_n = (RECV_2_SACK) ? S_ACK : state_c;R_ACK: beginif (RACK_2_IDLE) beginstate_n = IDLE;endelse if(RACK_2_STOP)beginstate_n = STOP;endelse beginstate_n = state_c;endendS_ACK: beginif (SACK_2_IDLE) beginstate_n = IDLE;endelse if(SACK_2_STOP)beginstate_n = STOP;endelse beginstate_n = state_c;endendSTOP: state_n = (STOP_2_IDLE) ? IDLE : state_c;default : state_n = state_c;endcase
end//描述转移条件
assign  IDLE_2_START = (state_c == IDLE) && (rw_flag) && cmd[0];
assign  IDLE_2_SEND  = (state_c == IDLE) && (rw_flag) && !cmd[0] && cmd[1];
assign  IDLE_2_RECV  = (state_c == IDLE) && (rw_flag) && !cmd[0] && cmd[2];
assign  START_2_SEND = (state_c == START) && (end_cnt_scl) && cmd[1];
assign  START_2_RECV = (state_c == START) && (end_cnt_scl) && cmd[2];
assign  SEND_2_RACK  = (state_c == SEND) && (end_cnt_bit) ;
assign  RECV_2_SACK  = (state_c == RECV) && (end_cnt_bit) ;
assign  RACK_2_IDLE  = (state_c == R_ACK) && (end_cnt_scl) && !cmd[3];
assign  SACK_2_IDLE  = (state_c == S_ACK) && (end_cnt_scl) && !cmd[3];
assign  RACK_2_STOP  = (state_c == R_ACK) && (end_cnt_scl) &&  cmd[3];
assign  SACK_2_STOP  = (state_c == S_ACK) && (end_cnt_scl) &&  cmd[3];
assign  STOP_2_IDLE  = (state_c == STOP) && (end_cnt_scl) ;//scl描述
always @(posedge clk or negedge rst_n) beginif(!rst_n)beginscl <= 1'b1;end else if(state_c != IDLE)beginif(cnt_scl < ((CNT_SCL_MAX-1) >> 1))beginscl <= 1'b0;endelse beginscl <= 1'b1;endendelse beginscl <= scl;end
end//描述数据
always @(posedge clk or negedge rst_n) beginif(!rst_n)beginsda_out <= 1'b1;sda_en <= 1'b0;rd_data <= 0;slave_ack <= 1;endelse begincase(state_c)IDLE: beginsda_out <= 1'b1;sda_en <= 1'b0;endSTART: beginsda_en <= 1'b1;if(cnt_scl >= (((CNT_SCL_MAX-1) >> 1) + ((CNT_SCL_MAX-1) >>2)))beginsda_out <= 1'b0;endelse beginsda_out <= 1'b1;endendSEND: begin sda_en <= 1'b1;if(cnt_scl == ((CNT_SCL_MAX-1)>>2))sda_out <= wr_data[7-cnt_bit];endRECV: beginsda_en <= 1'b0;sda_out <= 1'b0;if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))beginrd_data[7-cnt_bit] <= sda_in;endendR_ACK: beginsda_en <= 1'b0;sda_out <= 1'b0;if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))slave_ack <= sda_in;endS_ACK: beginsda_en <= 1'b1;sda_out <= cmd[3] ? 1'b1 : 1'b0;endSTOP: beginsda_en <= 1'b1;if(cnt_scl == ((CNT_SCL_MAX-1) >>2))beginsda_out <= 1'b0;endelse if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))beginsda_out <= 1'b1;endelse beginsda_out <= sda_out;endendendcase end
endassign trans_done = RACK_2_IDLE || SACK_2_IDLE ||STOP_2_IDLE;
endmodule
fsm_key.v
/**************************************功能介绍***********************************
Description:	状态机实现按键消抖模板	
Change history:    
*********************************************************************************///---------<模块及端口声名>------------------------------------------------------
module fsm_key#(parameter WIDTH = 1,TIME_20MS = 1000_000)( input				    clk		,input				    rst_n	,input       [WIDTH-1:0]	key_in	,output  reg [WIDTH-1:0] key_down
);								 
//---------<参数定义>--------------------------------------------------------- //状态机参数        独热码编码localparam  IDLE        = 4'b0001,//空闲状态FILTER_DOWN = 4'b0010,//按键按下抖动状态HOLD        = 4'b0100,//按键稳定按下状态FILTER_UP   = 4'b1000;//按键释放抖动状态//---------<内部信号定义>-----------------------------------------------------reg     [3:0]       state_c         ;//现态reg     [3:0]       state_n         ;//次态reg     [WIDTH-1:0] key_r0          ;//同步打拍reg     [WIDTH-1:0] key_r1          ;reg     [WIDTH-1:0] key_r2          ;wire    [WIDTH-1:0] n_edge          ;//下降沿wire    [WIDTH-1:0] p_edge          ;//上升沿   reg	    [19:0]	    cnt_20ms	   	;//延时计数器(20ms)wire				add_cnt_20ms	;wire				end_cnt_20ms	;  //状态转移条件信号wire                idle2filter_down    ;wire                filter_down2idle    ;wire                filter_down2hold    ;wire                hold2filter_up      ;wire                filter_up2hold      ;wire                filter_up2idle      ;  //****************************************************************
//--状态机
//****************************************************************//第一段:时序逻辑描述状态转移always @(posedge clk or negedge rst_n)begin if(!rst_n)beginstate_c <= IDLE;end else begin state_c <= state_n;end end//第二段:组合逻辑描述状态转移规律和状态转移条件always @(*)begin case (state_c)IDLE        : begin if(idle2filter_down)begin state_n = FILTER_DOWN;endelse begin // state_n = IDLE;state_n = state_c;endendFILTER_DOWN : begin if(filter_down2idle)begin state_n = IDLE;endelse if(filter_down2hold)begin state_n = HOLD;endelse begin state_n = state_c;endendHOLD        : begin if(hold2filter_up)begin state_n = FILTER_UP;endelse begin state_n = state_c;endendFILTER_UP   : begin if(filter_up2hold)begin state_n = HOLD;endelse if(filter_up2idle)begin state_n = IDLE;endelse begin state_n = state_c;endenddefault: state_n = IDLE;endcaseendassign idle2filter_down = (state_c == IDLE) && n_edge;assign filter_down2idle = (state_c == FILTER_DOWN) && p_edge;assign filter_down2hold = (state_c == FILTER_DOWN) && end_cnt_20ms && !p_edge;assign hold2filter_up   = (state_c == HOLD) && p_edge;assign filter_up2hold   = (state_c == FILTER_UP) && n_edge;assign filter_up2idle   = (state_c == FILTER_UP) && end_cnt_20ms;//****************************************************************
//--n_edge、p_edge 
//****************************************************************             always @(posedge clk or negedge rst_n)begin if(!rst_n)beginkey_r0 <= {WIDTH{1'b1}};key_r1 <= {WIDTH{1'b1}};key_r2 <= {WIDTH{1'b1}};end else begin key_r0 <= key_in;key_r1 <= key_r0;key_r2 <= key_r1; end endassign n_edge = ~key_r1 & key_r2;//下降沿assign p_edge = ~key_r2 & key_r1;//上升沿//****************************************************************
//--cnt_20ms
//****************************************************************always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_20ms <= 'd0;end else if(add_cnt_20ms)begin if(end_cnt_20ms || filter_down2idle || filter_up2hold)begin cnt_20ms <= 'd0;endelse begin cnt_20ms <= cnt_20ms + 1'b1;end endend assign add_cnt_20ms = (state_c == FILTER_DOWN) || (state_c == FILTER_UP);assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;//****************************************************************
//--key_down
//****************************************************************always @(posedge clk or negedge rst_n)begin if(!rst_n)beginkey_down <= 'd0;end else begin key_down <= filter_down2hold ? ~key_r2 : 1'b0;endendendmodule
uart

详见uart详解

四、总结

两篇文章主要介绍了iic协议,以及去控制eeprom读写的操作,其中包括自己在学习中的个人理解,和一种复用性和可读性较高的iic写法,前者在学习的过程中较为容易去理解协议,但复用性与可读性较低,后者相反。

http://www.lryc.cn/news/591009.html

相关文章:

  • AI算法之图像识别与分类
  • 深入理解Java中的Collections.max()方法
  • 贪心算法(排序)
  • GLM(General Language Model,通用语言模型)
  • 2020717零碎写写
  • 学习OpenCV---显示图片
  • Java集合框架中List常见问题
  • Python爬虫实战:Requests与Selenium详解
  • ESLint 完整功能介绍和完整使用示例演示
  • 产品经理如何描述用户故事
  • Rocky Linux 9 源码包安装php7
  • API开发提速新方案:SmartBear API Hub与ReadyAPI虚拟化整合实践
  • 学习日志预告
  • 学习设计模式《十八》——备忘录模式
  • ThinkPHP8 Windows开发全流程:从搭建到上线
  • TASK01【datawhale组队学习】地瓜机器人具身智能概述
  • 设计模式笔记_结构型_装饰器模式
  • 【后端】.NET Core API框架搭建(9) --配置使用Log4Net日志
  • 人工智能之数学基础:概率论和数理统计在机器学习的地位
  • 使用Proxy设计模式来增强类的功能:ToastProxy和DesktopToast的设计关系
  • 力扣119:杨辉三角Ⅱ
  • UGUI 性能优化系列:第一篇——基础优化与资源管理
  • 取消office word中的段落箭头标记
  • 【图像处理基石】如何入门色彩评估?
  • Python暑期学习笔记3
  • Redis:哨兵(Sentinel)
  • 20250717 Ubuntu 挂载远程 Windows 服务器上的硬盘
  • 7.事务操作
  • 自动化技术在造纸行业的应用:EtherCAT转PROFIBUS DP解决方案
  • 简单手写一个Spring boot starter