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

发布于:2025-07-18 ⋅ 阅读:(15) ⋅ 点赞:(0)

基于FPGA的IIC控制EEPROM读写

一、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)begin
    if(!rst_n)begin
        ctrl_data <= 8'd0;
    end
    else if((state_c == CTRL_BYTE) && (!w_mod) )begin
        ctrl_data <= data_in;
    end
    else if(state_c == CTRL_BYTE && w_mod ==1 && ctrl_flg)begin
        ctrl_data <= {data_in[7:1],1'b0};
    end
    else if(state_c == CTRL_BYTE && w_mod ==1 && !ctrl_flg)begin
        ctrl_data <= {ctrl_data[7:1],1'b1};
    end
    else begin
        ctrl_data <= ctrl_data;
    end
end

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

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

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

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        ctrl_flg <= 1'b1;
    end
    else if(ACK2_2_START)begin
        ctrl_flg <= 1'b0;
    end
    else if(STOP_2_IDLE)begin
        ctrl_flg <= 1'b1;
    end
    else begin
        ctrl_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 @(*)begin
    case(state_c)
        ACK1,ACK2,ACK3:    begin ack = (sda_in == 0) ? 1'b0 : ack; 
        end
        default:     ack = 1'b1;
    endcase
end 

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

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        data <= 8'd0;
    end
    else if(rx_vld )begin
        data <= data_in;
    end
end

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

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

always @(*) begin
    if((state_c == CTRL_BYTE) || (state_c == SLAVE_ADDR) || (state_c == W_DATA) || (state_c == R_DATA))begin
        max_bit <= 8;
    end
    else begin
        max_bit <= 1;
    end
end

//速度计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_dp <= 0;
    end
    else if(add_cnt_dp)begin
        if(end_cnt_dp)begin
            cnt_dp <= 0;
        end
        else begin
            cnt_dp <= cnt_dp + 1;
        end
    end
    else begin
        cnt_dp <= cnt_dp;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        add_cnt_dp <= 0;
    end
    else 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)))begin
        add_cnt_dp <= 1;
    end
    else if((state_c == START || state_c == ACK1 || state_c == ACK2 || state_c == ACK3 || state_c == R_DATA || state_c == STOP) && (end_cnt_bit))begin
        add_cnt_dp <= 0;
    end
    else begin
        add_cnt_dp <= add_cnt_dp;
    end
end

assign end_cnt_dp = add_cnt_dp && cnt_dp == depth-1; 

//bit计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_bit <= 0;
    end
    else if(add_cnt_bit)begin
        if(end_cnt_bit)begin
            cnt_bit <= 0;
        end
        else begin
            cnt_bit <= cnt_bit + 1;
        end
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end

assign 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) begin
    if(!rst_n)begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

//状态机二段
always @(*) begin
        case(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:       begin
                    if(ACK3_2_STOP)begin
                        state_n <= STOP;
                    end
                    else if(ACK3_2_R_DATA)begin
                        state_n <= R_DATA;
                    end
                    else if(ACK3_2_W_DATA)begin
                        state_n <= W_DATA;
                    end
                    else begin
                        state_n <= state_c;
                    end
                end
                R_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) begin
    if(!rst_n)begin
        scl <= 1'b1;
    end
    else begin
        case(state_c) 
            IDLE :scl <= 1'b1;
            CTRL_BYTE,SLAVE_ADDR,W_DATA,R_DATA,ACK1,ACK2,ACK3   :begin
                if((cnt_dp < 7'd31) || (cnt_dp > 7'd93))begin
                    scl <= 1'b0;
                end
                else begin
                    scl <= 1'b1;
                end
            end
            STOP :begin
                if(cnt_dp < 7'd31)begin
                    scl <= 1'b0;
                end
                else begin
                    scl <= 1'b1;
                end
            end
            default:begin
                scl <= 1'b1;
            end
        endcase
    end
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        sda_out <= 1'b1;
    end
    else begin
        case(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)begin
                    sda_out <= 1'b0;
                end
            end
            SLAVE_ADDR,W_DATA: begin sda_out <= data[7-cnt_bit];
                if(cnt_dp == 0 )begin
                    sda_out <= 1'b0;
                end
            end
            R_DATA:  sda_out <= 1'b0;
            ACK1,ACK2:     sda_out <= 1'b0;
            ACK3:    begin
                if(w_mod && (data == 8'h03))begin 
                    sda_out <= 1'b1;
                end
                else if(!w_mod && (data == 8'h03))begin
                    sda_out <= 1'b0;
                end
                else begin
                    sda_out <= 1'b0;
                end   
            end
            STOP:    sda_out <= (cnt_dp <= ((depth-1)>>1)) ?  1'b0 :  1'b1;
            default:  sda_out <= 1'b1;
        endcase
    end
end
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        sda_en <= 1'b0;
    end
    else begin
        case(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;
        endcase
    end
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data_out <= 8'b0;
    end
    else if((state_c == R_DATA) && (cnt_dp == (depth -1)>>1))begin
        data_out[7-cnt_bit]  <= sda_in;
    end
end

assign 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 @(*)begin
    if(state_c == WRITE)begin
        case (cnt_byte)
            0 : begin  rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end
            1 : begin  rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr;  end
            2 : begin  rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; end
            default : begin
                wr_data = 8'd0;
                rw_flag = 1'd0;
                cmd     = 4'd0; 
            end
        endcase
    end
    else if(state_c == READ)begin
        case (cnt_byte)
            0: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end
            1: begin   rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end
            2: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end
            3: begin   rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; end
            default : begin
                wr_data = 8'd0;
                rw_flag = 1'd0;
                cmd     = 4'd0; 
            end
        endcase
    end
    else begin
        wr_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,
    //key
    input               key_down,
    //uart_rx
    input  [7:0]        rx_data,
    input               rx_vld,
    //uart_tx
    input               ready,
    output reg [7:0]    tx_data,
    output reg          tx_vld,
    //iic_driver
    input  [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)begin
        state_c <= IDLE;
    end 
    else begin 
        state_c <= state_n;
    end 
end
    
//第二段:组合逻辑判断状态转移条件,描述状态转移规律
always @(*) begin
    case(state_c)
        IDLE    : begin
            if(IDLE_2_WRITE)begin
                state_n = WRITE;
            end
            else if(IDLE_2_READ)begin
                state_n = READ;
            end
            else begin
                state_n = state_c;
            end
        end
        WRITE   : state_n = (WRITE_2_DONE) ? DONE : state_c;
        READ    : state_n = (READ_2_DONE) ? DONE : state_c;
        DONE    : state_n = IDLE;
        default : ;
    endcase
end

assign  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)begin
        cnt_byte <= 'd0;
    end 
    else if(add_cnt_byte)begin 
        if(end_cnt_byte)begin 
            cnt_byte <= 'd0;
        end
        else 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)begin
        cnt_wr_addr <= 'd0;
    end 
    else if(add_cnt_wr_addr)begin 
        if(end_cnt_wr_addr)begin 
            cnt_wr_addr <= 'd0;
        end
        else 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)begin
        cnt_rd_addr <= 'd0;
    end 
    else if(add_cnt_rd_addr)begin 
        if(end_cnt_rd_addr)begin 
            cnt_rd_addr <= 'd0;
        end
        else 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 @(*)begin
    if(state_c == WRITE)begin
        case (cnt_byte)
            0 : begin  rw_flag = 1'b1; cmd = (CMD_START | CMD_SEND) ; wr_data = WR_ID; end
            1 : begin  rw_flag = 1'b1; cmd = CMD_SEND ; wr_data = cnt_wr_addr;  end
            2 : begin  rw_flag = 1'b1; cmd = (CMD_SEND | CMD_STOP) ; wr_data = rx_data ; end
            default : begin
                wr_data = 8'd0;
                rw_flag = 1'd0;
                cmd     = 4'd0; 
            end
        endcase
    end
    else if(state_c == READ)begin
        case (cnt_byte)
            0: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = WR_ID; end
            1: begin   rw_flag = 1'b1; cmd =(CMD_SEND) ; wr_data = cnt_rd_addr; end
            2: begin   rw_flag = 1'b1; cmd =(CMD_START | CMD_SEND) ; wr_data = RD_ID; end
            3: begin   rw_flag = 1'b1; cmd =(CMD_RECV | CMD_STOP) ; wr_data = 8'h0; end
            default : begin
                wr_data = 8'd0;
                rw_flag = 1'd0;
                cmd     = 4'd0; 
            end
        endcase
    end
    else begin
        wr_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)begin
        tx_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 
end
    
endmodule
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) begin
    if (!rst_n) begin
        cnt_scl <= 0;
    end 
    else if(add_cnt_scl)begin
        if (end_cnt_scl) begin
            cnt_scl <= 0;
        end
        else begin
            cnt_scl <= cnt_scl + 1'b1;
        end 
    end
    else begin
        cnt_scl <= cnt_scl;
    end
end

assign 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)begin
        cnt_bit <= 0;
    end
    else if(add_cnt_bit)begin
        if(end_cnt_bit)begin
            cnt_bit <= 0;
        end
        else begin
            cnt_bit <= cnt_bit + 1'b1;
        end
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end

assign 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) begin
    if (!rst_n) begin
        state_c <= IDLE;
    end else begin
        state_c <= state_n;
    end
end

//状态机二段
always @(*)begin
    case(state_c)
        IDLE: begin
            if (IDLE_2_START) begin
                state_n = START;
            end 
            else if(IDLE_2_SEND)begin
                state_n = SEND;
            end
            else if(IDLE_2_RECV)begin
                state_n = RECV;
            end
            else begin
                state_n = state_c;
            end
        end
        START: begin 
            if (START_2_SEND) begin
                state_n = SEND;
            end 
            else if(START_2_RECV)begin
                state_n = RECV;
            end
            else begin
                state_n = state_c;
            end
        end
        SEND: state_n = (SEND_2_RACK) ? R_ACK : state_c;
        RECV: state_n = (RECV_2_SACK) ? S_ACK : state_c;
        R_ACK: begin
            if (RACK_2_IDLE) begin
                state_n = IDLE;
            end
            else if(RACK_2_STOP)begin
                state_n = STOP;
            end
            else begin
                state_n = state_c;
            end
        end
        S_ACK: begin
            if (SACK_2_IDLE) begin
                state_n = IDLE;
            end
            else if(SACK_2_STOP)begin
                state_n = STOP;
            end
            else begin
                state_n = state_c;
            end
        end
        STOP: 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) begin
    if(!rst_n)begin
        scl <= 1'b1;
    end 
    else if(state_c != IDLE)begin
        if(cnt_scl < ((CNT_SCL_MAX-1) >> 1))begin
            scl <= 1'b0;
        end
        else begin
            scl <= 1'b1;
        end
    end
    else begin
        scl <= scl;
    end
end

//描述数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        sda_out <= 1'b1;
        sda_en <= 1'b0;
        rd_data <= 0;
        slave_ack <= 1;
    end
    else begin
        case(state_c)
            IDLE: begin
                sda_out <= 1'b1;
                sda_en <= 1'b0;
            end
            START: begin
                sda_en <= 1'b1;
                if(cnt_scl >= (((CNT_SCL_MAX-1) >> 1) + ((CNT_SCL_MAX-1) >>2)))begin
                    sda_out <= 1'b0;
                end
                else begin
                    sda_out <= 1'b1;
                end
            end
            SEND: begin 
                sda_en <= 1'b1;
                if(cnt_scl == ((CNT_SCL_MAX-1)>>2))
                    sda_out <= wr_data[7-cnt_bit];
            end
            RECV: begin
                sda_en <= 1'b0;
                sda_out <= 1'b0;
                if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))begin
                    rd_data[7-cnt_bit] <= sda_in;
                end
            end
            R_ACK: begin
                sda_en <= 1'b0;
                sda_out <= 1'b0;
                if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))
                    slave_ack <= sda_in;
            end
            S_ACK: begin
                sda_en <= 1'b1;
                sda_out <= cmd[3] ? 1'b1 : 1'b0;
            end
            STOP: begin
                sda_en <= 1'b1;
                if(cnt_scl == ((CNT_SCL_MAX-1) >>2))begin
                    sda_out <= 1'b0;
                end
                else if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))begin
                    sda_out <= 1'b1;
                end
                else begin
                    sda_out <= sda_out;
                end
            end
        endcase 
    end
end

assign 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)begin
            state_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;
                end
                else begin 
                    // state_n = IDLE;
                    state_n = state_c;
                end
            end
            FILTER_DOWN : begin 
                if(filter_down2idle)begin 
                    state_n = IDLE;
                end
                else if(filter_down2hold)begin 
                    state_n = HOLD;
                end
                else begin 
                    state_n = state_c;
                end
            end
            HOLD        : begin 
                if(hold2filter_up)begin 
                    state_n = FILTER_UP;
                end
                else begin 
                    state_n = state_c;
                end
            end
            FILTER_UP   : begin 
                if(filter_up2hold)begin 
                    state_n = HOLD;
                end
                else if(filter_up2idle)begin 
                    state_n = IDLE;
                end
                else begin 
                    state_n = state_c;
                end
            end
            default: state_n = IDLE;
        endcase
    end

    assign 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)begin
            key_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 
    end
    
    assign 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)begin
            cnt_20ms <= 'd0;
        end 
        else if(add_cnt_20ms)begin 
            if(end_cnt_20ms || filter_down2idle || filter_up2hold)begin 
                cnt_20ms <= 'd0;
            end
            else begin 
                cnt_20ms <= cnt_20ms + 1'b1;
            end 
        end
    end 
    
    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)begin
            key_down <= 'd0;
        end 
        else begin 
            key_down <= filter_down2hold ? ~key_r2 : 1'b0;
        end
    end

endmodule
uart

详见uart详解

四、总结

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


网站公告

今日签到

点亮在社区的每一天
去签到