Skip to content

说明:

  1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
  2. 本文档主要介绍使用FPGA实现I2C协议读写AT24C64。

修订历史

文档名称版本作者时间备注
FPGA实现I2C协议读写AT24C64v1.0.0DuRuofu2024-02-07首次建立

FPGA实现I2C协议读写AT24C64

一、 IIC原理

原理方面不再赘述,可参考:IIC协议解析

二、 硬件设计

AT24C64 的引脚功能如下: A2,A1,A0:可编程地址输入端。 GND:电源地引脚 SDA:SDA(Serial Data,串行数据)是双向串行数据输入/输出端。 SCL:SCL(Serial clock,串行时钟)串行时钟输入端。 WP(写保护):AT24C64 有一个写保护引脚用于提供数据保护,当写保护引脚连接至 GND 时,芯片可以正常写,当写保护引脚连接至 VCC 时,使能写保护功能,此时禁止向芯片写入数据,只能进行读操 作。 VCC:电源输入引脚

开发板原理图:

由上图可知,开发板上的 EEPROM 可编程地址 A2、A1、A0 连接到地,所以 AT24C64 的器件地 址为 1010000。

三、程序框图

3.1 顶层模块:

顶层模块输入输出信号简介:

信号位宽类型功能描述
sys_clk1BitInput系统时钟 50MHz
sys_rst_n1BitInput复位信号,低有效
key_wr1BitInput写控制按键信号,高有效(1.5V)
key_rd1BitInput读控制按键信号,高有效(1.5V)
sda1BitinoutIIC串行数据线
scl1BitInputIIC串行时钟线
uart_tx1BitInput串口数据发送

3.2 子功能模块:

1、按键消抖模块

信号名称位宽类型功能描述
sys_clk1Input系统时钟 50MHz
sys_rst_n1Input复位信号,低有效
key_in1Input按键的输入
key_flag1output去抖后按键被按下的标志信号

2、串口发送及缓冲模块

信号名称位宽类型功能描述
sys_clk1Input系统时钟 50MHz
sys_rst_n1Input复位信号,低有效
data_in8Input发送数据写入
wr_en1Input发送数据写入使能
tx1output串口输出

用于将从eeprom里读出的输入输出到串口

3、IIC控制模块

I2C 驱动模块的主要功能是按照 I2C 协议对 EERPROM 存储芯片执行数据读写操作

I2C 驱动模块输入输出信号简介

信号名称位宽类型功能描述
sys_clk1Input系统时钟 50MHz
sys_rst_n1Input复位信号,低有效
wr_en1Input写使能信号
rd_en1Input读使能信号
i2c_start1Input单字节数据读/写开始信号
addr_num1Input数据存储地址字节数标志信号
byte_addr16Input数据存储地址
wr_data8Input待写入 EEPROM 字节数据
i2c_clk1Output工作时钟
i2c_end1Output单字节数据读/写结束信号
rd_data8Output自 EEPROM 中读出的单字节数据
i2c_scl1OutputI2C 串行时钟信号 SCL
i2c_sda1OutputI2C 串行数据信号 SDA

I2C 驱动模块包括 13 路输入输出信号,其中输入信号 8 路、输出信号 5 路。输入信号中,sys_clksys_rst_n 是必不可少的系统时钟和复位信号; wr_enrd_en 为 写使能信号,由数据收发模块生成并传入,高电平有效; i2c_start 信号为单字节数据读/写 开始信号;与 i2c_start 信号同时传入的还有数据存储地址 byte_addr 和待写入字节数据 wr_data; 当写使能 wr_en2c_start 信号同时有效,模块执行单字节数据写操作,按照数 据存储地址 byte_addr,向 EEPROM 对应地址写入数据 wr_data; 当读使能信号 rd_eni2c_start 信号同时有效,模块执行单字节数据读操作,按照数据存储地址 byte_addr 读取 EEPROM 对应地址中的数据;

I2C 设备存储地址有单字节和 2 字节两 种,为了应对这一情况,我们向模块输入 addr_num 信号,当信号为低电平时,表示 I2C 设备存储地址为单字节,在进行数据读写操作时只写入数据存储地址 byte_addr 的低 8 位;当 信号为高电平时,表示 I2C 设备存储地址为 2 字节,在进行数据读写操作时要写入数据存 储地址 byte_addr 的全部 16 位。

i2c_clk是本模块的工作时钟,由系统时钟 sys_clk 分频而来,它的时钟 频率为串行时钟 i2c_scl 频率的 4 倍,时钟信号 i2c_clk 要传入数据收发模块(i2c_rw_data)作 为模块的工作时钟;输出给数据收发模块(i2c_rw_data) 的单字节数据读/ 写结束信号 i2c_end,高电平有效,表示一次单字节数据读/写操作完成;rd_data 信号表示自 EEPROM 读出的单字节单字节数据,输出至数据收发模块(i2c_rw_data)i2c_scli2c_sda 分别是串 行时钟信号和串行数据信号,由模块产生传入 EEPROM 存储芯片。

4、IIC数据收发模块

数据收发模块的主要功能是:为 I2C 驱动模块提供读/写数据存储地址、待写入数据以 及作为 EEPROM 读出数据缓存,待数据读取完成后将读出数据发送给串口模块进行上位机数据显示。

I2C 数据收发模块输入输出信号简介:

信号名称位宽类型功能描述
sys_clk1Input系统时钟 50MHz
i2c_clk1Input模块工作时钟,频率 1MHz
sys_rst_n1Input复位信号,低有效
write1Input写触发信号
read1Input读触发信号
i2c_end1Input单字节数据读/写结束信号
rd_data8InputEEPROM 读出数据
wr_en1Output写使能信号
rd_en1Output读使能信号
i2c_start1Output单字节数据读/写开始信号
byte_addr16Output读/写数据存储地址
wr_data8Output待写入 EEPROM 数据
fifo_rd_data8Output读取数据

3.3 整体框图:

四、逻辑实现

1、按键消抖模块

verilog
`timescale  1ns/1ns

// 按键消抖模块 

module  key_filter
#(
    parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
    input   wire    sys_clk     ,   //系统时钟50Mhz
    input   wire    sys_rst_n   ,   //全局复位
    input   wire    key_in      ,   //按键输入信号

    output  reg     key_flag        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else    if(key_in == 1'b1)
        cnt_20ms <= 20'b0;
    else    if(cnt_20ms == CNT_MAX && key_in == 1'b0)
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 1'b1;

//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        key_flag <= 1'b0;
    else    if(cnt_20ms == CNT_MAX - 1'b1)
        key_flag <= 1'b1;
    else
        key_flag <= 1'b0;

endmodule

2、串口发送及缓冲模块

串口+ fifo缓冲输出:

verilog
`timescale 1ns / 1ns

// 串口输出 + 缓冲

module uart_fifo_tx
#(
    parameter UART_BSP = 'd9600,        // 波特率
    parameter CLK_FREQ = 'd50_000_000   // 时钟频率
)
(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire [7:0] data_in, // 串口输出缓冲区输入数据
    input wire wr_en,          // 串口输出缓冲区输入数据标志(写入使能)

    //output wire empty,          // 串口输出缓冲区空标志
    //output wire full,            // 串口输出缓冲区满标志
    output wire tx              // 串口发送数据
);

wire [7:0] data_out;  // 串口输出数据
wire rd_en;
wire empty;

// 缓冲区非空就使能缓冲区数据读出,并且和串口发送模块的写入使能相连
assign rd_en =!empty;

// 实例化UART发送模块
uart_tx
#(
    .UART_BSP(UART_BSP),   // 波特率
    .CLK_FREQ(CLK_FREQ)   // 时钟频率
)
uart_tx
(
    .sys_clk            (sys_clk),        
    .sys_rst_n          (sys_rst_n),      
    .pi_data            (data_out),  
    .pi_flag            (wr_en),

    .tx                 (tx)
);

// 实例化UART发送缓冲
dcfifo_uart_1024x8 dcfifo_uart_1024x8_tx (
  .rst(~sys_rst_n),        // input wire rst
  .wr_clk(sys_clk),  // input wire wr_clk
  .rd_clk(sys_clk),  // input wire rd_clk
  .din(data_in),        // input wire [7 : 0] din
  .wr_en(wr_en),    // input wire wr_en
  .rd_en(rd_en),    // input wire rd_en

  .dout(data_out),      // output wire [7 : 0] dout
  .full(),      // output wire full
  .empty(empty)    // output wire empty
);

endmodule

串口实现:

verilog
`timescale 1ns / 1ns
module uart_tx
#(
    parameter UART_BSP = 'd9600,        // 波特率
    parameter CLK_FREQ = 'd50_000_000   // 时钟频率
)
(
    input wire          sys_clk,        // 系统时钟
    input wire          sys_rst_n,      // 系统复位信号
    input wire   [7:0]  pi_data,        // 模块输入数据
    input wire          pi_flag,        // 串口输入数据标志

    output reg  tx             // 数据输出
);

parameter BAUD_CNT__MAX = CLK_FREQ/UART_BSP; // 波特率计数器最大值

reg         work_en;  // 工作使能
reg [15:0]  baud_cnt; // 波特计数器
reg         bit_flag; // 数据标志
reg [3:0]   bit_cnt;  // 比特计数器

// 使能信号
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        work_en <= 1'b0;
    end
    else if(pi_flag == 1'b1) begin
        work_en <= 1'b1;
    end
    else if((bit_cnt == 4'd9) && (bit_flag == 1'b1)) begin
        work_en <= 1'b0;
    end
    else 
        work_en <= work_en;
end

// 波特计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        baud_cnt <= 16'd0;
    end
    // 归零条件
    else if((work_en == 1'b0) || (baud_cnt == BAUD_CNT__MAX - 1'b1)) begin
        baud_cnt <= 16'd0;
    end
    // 计数条件
    else if(work_en == 1'b1) begin
        baud_cnt <= baud_cnt + 1'b1;
    end
    else 
        baud_cnt <= baud_cnt;
end

// 比特标志信号
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        bit_flag <= 1'b0;
    end
    else if(baud_cnt == 16'b1) begin
        bit_flag <= 1'b1;
    end
    else begin
        bit_flag <= 1'b0;
    end
end

// 比特计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        bit_cnt <= 4'd0;
    end
    // 归零条件
    else if((bit_flag == 1'b1) && (bit_cnt == 4'd9)) begin
        bit_cnt <= 4'd0;
    end
    // 计数条件
    else if((bit_flag == 1'b1) && (work_en == 1'b1)) begin
        bit_cnt <= bit_cnt + 1'b1;
    end
    else begin
        bit_cnt <= bit_cnt;
    end

end

// 输出信号
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        tx <= 1'b1; //空闲为高
    end
    else if(bit_flag == 1'b1)begin
        case(bit_cnt)
            4'd0: tx <= 1'b0; //起始位为低
            4'd1: tx <= pi_data[0];
            4'd2: tx <= pi_data[1];
            4'd3: tx <= pi_data[2];
            4'd4: tx <= pi_data[3];
            4'd5: tx <= pi_data[4];
            4'd6: tx <= pi_data[5];
            4'd7: tx <= pi_data[6];
            4'd8: tx <= pi_data[7];
            4'd9: tx <= 1'b1; //停止位为高
            default: tx <= 1'b1;
        endcase
    end
end

endmodule

3、IIC控制模块

3.1 IIC控制模块状态转移图

系统上电后,状态机处于 IDLE(初始状态),接收到有效的单字节数据读/写开始信号 i2c_start 后,状态机跳转到 START_1(起始状态);FPGA 向 EEPROM 存储芯片发送起始信 号;随后状态机跳转到 SEND_D_ADDR(发送器件地址状态),在此状态下向 EEPROM 存 储芯片写入控制指令,控制指令高 7 位为器件地址,最低位为读写控制字,写入“0”,表 示执行写操作;控制指令写入完毕后,状态机跳转到 ACK_1(应答状态)。

在 ACK_1(应答状态)状态下,要根据存储地址字节数进行不同状态的跳转。当 FPGA 接收到 EEPROM 回 传 的 应 答 信 号 且 存 储 地 址 字 节 为 2 字 节 , 状 态 机 跳 转 到 SEND_B_ADDR_H(发送高字节地址状态),将存储地址的高 8 位写入 EEPROM,写入完成 后 , 状 态 机 跳 转 到 ACK_2( 应 答 状 态 ) ;FPGA 接 收 到 应 答 信 号 后 , 状 态 机 跳 转 到 SEND_B_ADDR_L(发送低字节地址状态);当 FPGA 接收到 EEPROM 回传的应答信号且存 储地址字节为单字节,状态机状态机直接跳转到 SEND_B_ADDR_L(发送低字节地址状 态);在此状态低 8 位存储地址或单字节存储地址写入完成后,状态机跳转到 ACK_3(应答 状态)。

在 ACK_3(应答状态)状态下,要根据读/写使能信号做不同的状态跳转。当 FPGA 接收 到应答信号且写使能信号有效,状态机跳转到 WR_DATA(写数据状态);在写数据状态, 向 EEPROM 写入单字节数据后,状态机跳转到 ACK_4(应答状态);待 FPGA 接收到有效应 答信号后,状态机跳转到 STOP(停止状态);当 FPGA 接收到应答信号且读使能信号有效, 状 态 机 跳 转 到 START_2( 起始状态) ; 再 次 向 EEPROM 写 入 起 始 信 号 , 状 态 跳 转 到 SEND_RD_ADDR(发送读控制状态);再次向 EEPROM 写入控制字节,高 7 位器件地址不 变,读写控制位写入“1”,表示进行读操作,控制字节写入完毕后,状态机跳转到 ACK_5(应答状态);待 FPGA 接收到有效应答信号后,状态机跳转到 RD_DATA(读数据状 态);在 RD_DATA(读数据状态)状态,EEPROM 向 FPGA 发送存储地址对应存储单元下的 单字节数据,待数据读取完成户,状态机跳转到 N_ACK(无应答状态),在此状态下向 EEPROM 写入一个时钟的高电平,表示数据读取完成,随后状态机跳转到 STOP(停止状 态)。

在 STOP(停止状态)状态,FPGA 向 EEPROM 发送停止信号,一次单字节数据读/写操 作 完 成 , 随 后 状 态 机 跳 回 IDLE( 初 始 状 态 ), 等 待 下 一 次 单 字 节 数 据 读 /写 开 始 信 号 i2c_start。

3.2 IIC控制模块波形图

计数器分频产生i2c时钟,频率1MHZ:

写数据波形:

读数据波形:

3.2 IIC控制模块代码实现

1、模块代码:

verilog

`timescale 1ns / 1ns

// 按照 I2C 协议对 EERPROM 存储芯片执行数据读写操作

module i2c_ctrl
#(
    parameter DEVICE_ADDR  = 7'b1010_111,          // 设备地址
    parameter SYS_CLK_FREQ = 'd50_000_000,  // 系统时钟频率
    parameter I2C_SCL_FREQ = 'd250_000     // SCL 时钟频率
)
(
    input       wire            sys_clk ,
    input       wire            sys_rst_n,
    input       wire            wr_en,       // 写使能
    input       wire    [15:0]  byte_addr,   // 字节地址
    input       wire    [7:0]   wr_data,     // 写入数据
    input       wire            rd_en,       // 读使能
    input       wire            addr_num,    // 地址位数(0:8bit, 1:16bit)
    input       wire            i2c_start,   // I2C 开始信号

    output      reg             i2c_scl,     // 时钟(250Khz)
    inout       wire            i2c_sda,     // 数据
    output      reg    [7:0]    rd_data,     // 读取的数据
    output      reg             i2c_end,     // I2C 结束信号
    output      reg             i2c_clk      // I2C 时钟信号(1Mhz)
);

parameter   CNT_CLK_MAX     = (SYS_CLK_FREQ / I2C_SCL_FREQ) >> 3;  // 时钟分频计数器最大值

// I2C 控制状态机状态
parameter   IDLE            = 4'd00, //初始状态 
            START_1         = 4'd01, //开始状态 1 
            SEND_D_ADDR     = 4'd02, //设备地址写入状态 + 控制写 
            ACK_1           = 4'd03, //应答状态 1 
            SEND_B_ADDR_H   = 4'd04, //字节地址高八位写入状态
            ACK_2           = 4'd05, //应答状态 2 
            SEND_B_ADDR_L   = 4'd06, //字节地址低八位写入状态 
            ACK_3           = 4'd07, //应答状态 3 
            WR_DATA         = 4'd08, //写数据状态 
            ACK_4           = 4'd09, //应答状态 4 
            START_2         = 4'd10, //开始状态 2 
            SEND_RD_ADDR    = 4'd11, //设备地址写入状态 + 控制读 
            ACK_5           = 4'd12, //应答状态 5 
            RD_DATA         = 4'd13, //读数据状态 
            N_ACK           = 4'd14, //非应答状态 
            STOP            = 4'd15; //结束状态

reg [7:0] cnt_clk;  // 时钟分频计数器
reg [3:0] state;    // 状态机状态(二机制码)
reg [1:0] cnt_i2c_clk;      // I2C 时钟计数器
reg       cnt_i2c_clk_en;   // I2C 时钟计数器使能
reg [3:0] cnt_bit;  // 位计数器
reg       sda_out;  // SDA 输出
wire       sda_en;   // SDA 输出使能
reg       ack;      // 应答信号
wire       sda_in;   // SDA 输入
reg [7:0] rd_data_reg;  // 读取的数据暂存 

//分频计数器(cnt_clk:0~24)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        cnt_clk <= 8'd00;
    end
    else  if(cnt_clk == CNT_CLK_MAX - 1) begin
        cnt_clk <= 8'd00;
    end
    else begin
        cnt_clk <= cnt_clk + 1'b1;
    end
end

// 分频生成 I2C 时钟信号 (i2c_clk:1MHz)
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        i2c_clk <= 1'b1;
    end
    // 时钟产生条件
    else if(cnt_clk == CNT_CLK_MAX - 1) begin
        i2c_clk <= ~i2c_clk;
    end
end

// I2C 控制状态机
always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        state <= IDLE; // 空闲状态
    end
    else begin
        case(state)
            IDLE: begin  
                if (i2c_start == 1'b1) 
                    state <= START_1;
                else 
                    state <= state;  
            end
            START_1: begin
                if (cnt_i2c_clk == 2'd3) 
                    state <= SEND_D_ADDR;
                else 
                    state <= state;
            end
            SEND_D_ADDR: begin
                if ((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd7)) 
                    state <= ACK_1;
                else 
                    state <= state;
            end
            ACK_1: begin
                if ((cnt_i2c_clk == 2'd3) && (ack == 1'b0)) begin 
                    // 判断地址位数
                    if (addr_num == 1'b1) 
                        state <= SEND_B_ADDR_H;
                    else 
                        state <= SEND_B_ADDR_L;
                end
                else 
                    state <= state;
            end
            SEND_B_ADDR_H: begin
                if ((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd7)) 
                    state <= ACK_2;
                else 
                    state <= state;
            end
            ACK_2: begin
                if ((cnt_i2c_clk == 2'd3) && (ack == 1'b0))
                    state <= SEND_B_ADDR_L;
                else 
                    state <= state;
            end
            SEND_B_ADDR_L: begin
                if ((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd7)) 
                    state <= ACK_3;
                else 
                    state <= state;
            end
            ACK_3: begin
                if ((cnt_i2c_clk == 2'd3) && (ack == 1'b0)) begin
                    if (wr_en == 1'b1)
                        state <= WR_DATA;
                    else if (rd_en == 1'b1)
                        state <= START_2;
                    else
                        state <= state;
                end
                else 
                    state <= state;
            end
            WR_DATA: begin
                if ((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd7)) 
                    state <= ACK_4;
                else 
                    state <= state;
            end
            ACK_4: begin
                if ((cnt_i2c_clk == 2'd3) && (ack == 1'b0)) 
                    state <= STOP;
                else 
                    state <= state;
            end
            START_2: begin
                if (cnt_i2c_clk == 2'd3) 
                    state <= SEND_RD_ADDR;
                else 
                    state <= state;
            end
            SEND_RD_ADDR: begin
                if ((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd7)) 
                    state <= ACK_5;
                else 
                    state <= state;
            end
            ACK_5: begin
                if ((cnt_i2c_clk == 2'd3) && (ack == 1'b0)) 
                    state <= RD_DATA;
                else 
                    state <= state;
            end
            RD_DATA: begin
                if ((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd7)) 
                    state <= N_ACK;
                else 
                    state <= state;
            end
            N_ACK: begin
                if ((cnt_i2c_clk == 2'd3) && (ack == 1'b0)) 
                    state <= STOP;
                else 
                    state <= state;
            end
            STOP: begin
                if ((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd3)) 
                    state <= IDLE;
                else 
                    state <= state;
            end
            default: begin
                state <= IDLE;
            end
        endcase
    end
end


// I2C 时钟计数器
always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        cnt_i2c_clk <= 2'd0;
    end
    // 计数条件
    else if(cnt_i2c_clk_en == 1'b1) begin
        cnt_i2c_clk <= cnt_i2c_clk + 1'b1;
    end
end

// I2C 时钟计数器使能
always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        cnt_i2c_clk_en = 1'b0;
    end
    // 失能条件
    else if((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd3) && (state == STOP)) begin
        cnt_i2c_clk_en = 1'b0;
    end
    // 使能条件
    else if(i2c_start == 1'b1) begin
        cnt_i2c_clk_en = 1'b1;
    end
end

// 位计数器
always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        cnt_bit <= 3'd0;
    end
    // 不计数条件
    else if((state == IDLE) || (state == START_1) || (state == START_2)|| (state == ACK_1) || (state == ACK_2) || (state == ACK_3) || (state == ACK_4) || (state == ACK_5) || (state == N_ACK)) begin
        cnt_bit <= 3'd0;
    end
    // 清零条件
    else if((cnt_i2c_clk == 2'd3) && (cnt_bit == 3'd7)) begin
        cnt_bit <= 3'd0;
    end
    // 计数条件
    else if((cnt_i2c_clk == 2'd3) && (state != IDLE)) begin
        cnt_bit <= cnt_bit + 1'b1;
    end
end

// SDA 输出
always @(*) begin
    case(state)
        IDLE: begin
            sda_out <= 1'b1;
        end
        START_1: begin
            if(cnt_i2c_clk == 2'd0) 
                sda_out <= 1'b1;
            else 
                sda_out <= 1'b0;
        end
        SEND_D_ADDR: begin
            if(cnt_bit < 3'd6) 
                sda_out <= DEVICE_ADDR[6 - cnt_bit];
            else 
                sda_out <= 1'b0;
        end
        ACK_1: begin
            sda_out <= 1'b1;
        end
        SEND_B_ADDR_H: begin
            sda_out <= byte_addr[15 - cnt_bit];
        end
        ACK_2: begin
            sda_out <= 1'b1;
        end
        SEND_B_ADDR_L: begin
            sda_out <= byte_addr[7 - cnt_bit];
        end
        ACK_3: begin
            sda_out <= 1'b1;
        end
        WR_DATA: begin
            sda_out <= wr_data[7 - cnt_bit];
        end
        ACK_4: begin
            sda_out <= 1'b1;
        end
        START_2: begin
            if (cnt_i2c_clk <= 2'b1)
                sda_out <= 1'b1;
            else
                sda_out <= 1'b0;
        end
        SEND_RD_ADDR: begin
            if(cnt_bit < 3'd6) 
                sda_out <= DEVICE_ADDR[6 - cnt_bit];
            else 
                sda_out <= 1'b1;
        end
        ACK_5: begin
            sda_out <= 1'b1;
        end
        RD_DATA: begin
            sda_out <= 1'b1;
        end
        N_ACK: begin
            sda_out <= 1'b1;
        end
        STOP: begin
            if((cnt_bit == 3'd0) && (cnt_i2c_clk <= 2'd3)) 
                sda_out <= 1'b0;
            else 
                sda_out <= 1'b1;
        end
        default: sda_out <= 1'b1;

    endcase
end

// SDA 输出使能
assign sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)|| (state == ACK_3) || (state == ACK_4)|| (state == ACK_5) )? 1'b0 : 1'b1;

// ACK 信号
always @(*) begin
    case(state)
    ACK_1, ACK_2, ACK_3, ACK_4, ACK_5: begin
        if(cnt_i2c_clk == 2'd0) 
            ack = sda_in;
        else 
            ack = ack;
    end
    default: ack = 1'b0;
    endcase
end

// SDA 输入
assign sda_in = i2c_sda;

always @(*) begin
    case(state)
        IDLE: begin
            rd_data_reg <= 8'b0;
        end
        RD_DATA: begin
            rd_data_reg[7- cnt_bit] <= sda_in;
        end
        default: rd_data_reg <= rd_data_reg;

    endcase
end

// I2C_SCL 输出
always @(*) begin
    case(state)
        IDLE: begin
            i2c_scl <= 1'b1;
        end
        START_1: begin
            if(cnt_i2c_clk == 2'd3) 
                i2c_scl <= 1'b0;
            else 
                i2c_scl <= 1'b1;
        end
        SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,ACK_3,WR_DATA,ACK_4,
        START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:begin
            if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
                i2c_scl <= 1'b1;
            else 
                i2c_scl <= 1'b0;
        end
        STOP: begin
            if((cnt_bit == 3'd0) && (cnt_i2c_clk <= 2'd0)) 
                i2c_scl <= 1'b0;
            else 
                i2c_scl <= 1'b1;
        end
        default: i2c_scl <= 1'b1;
    endcase
end

// I2C_SDA 输出
assign i2c_sda = (sda_en)? sda_out : 1'b1;

// 结束信号
always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        i2c_end <= 1'b0;
    end
    else if((state == STOP) && (cnt_bit == 3'd3) && (cnt_i2c_clk == 2'd3)) begin
        i2c_end <= 1'b1;
    end
    else begin
        i2c_end <= 1'b0;
    end
end

// 数据输出
always @(posedge i2c_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin 
        rd_data <= 8'd0;
    end
    else if((state == RD_DATA) && (cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3)) begin
        rd_data <= rd_data_reg;
    end
    else begin
        rd_data <= rd_data;
    end
end
endmodule

4、IIC数据收发模块

参考链接

  1. https://www.bilibili.com/video/BV13L4y1b7So/?spm_id_from=333.788.top_right_bar_window_history.content.click&vd_source=ef5a0ab0106372751602034cdd9ab98e