verilog实现I2C控制器 (小梅哥思路)----详细解析

这篇具有很好参考价值的文章主要介绍了verilog实现I2C控制器 (小梅哥思路)----详细解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

i2c_bit_shift

模块框图如下所示
i2c verilog,verilog实战,fpga开发
输入输出信号:
i2c verilog,verilog实战,fpga开发
整体的思路如下:
通过输入的命令组合,完成一次8字节数据的传输。定义了6种命令,

WR 写数据请求 (6’b000_001)
STA 起始位请求(6’b000_010)
RD 读数据请求(6’b000_100)
STO 停止位请求(6’b001_000)
ACK 应答位请求(6’b010_000)
NACK 无应答请求(6‘b100_000)

采用了状态机里面套用序列机的思路,在每个状态里面分为四步完成一位数据的传输,使用计数器来控制。定义的状态如下(独热码编码):

state discription
IDLE 空闲状态
GEN_STA 产生起始信号
WR_DATA 写数据状态
RD_DATA 读数据状态
CHECK_ACK 检测应答信号
GEN_ACK 产生应答信号
GNE_STO 产生停止信号

状态机转换如下所示:
i2c verilog,verilog实战,fpga开发

代码如下所示,有很清晰的注释:

module i2c_bit_shift (
    input   wire            clk         ,
    input   wire            rst_n       ,
    input   wire    [5:0]   cmd         ,       //控制总线实现各种传输的各种命令组合
    input   wire            go          ,       //整个模块的启动信号
    input   wire    [7:0]   Tx_Data     ,       //总线要发送的8位数据

    output  reg             i2c_sclk    ,       //i2c时钟总线
    inout   wire            i2c_sdat    ,       //i2c数据总线
    output  reg             Trans_Done  ,       //传输完成信号
    output  reg     [7:0]   Rx_Data     ,       //总线要接收的8位数据
    output  reg             ack_o               //从机时候应答标志
);                                          

    localparam      IDLE        =       7'b0000_001,     //空闲状态
                    GEN_STA     =       7'b0000_010,     //产生起始信号
                    WR_DATA     =       7'b0000_100,     //写数据状态
                    RD_DATA     =       7'b0001_000,     //读数据状态
                    CHECK_ACK   =       7'b0010_000,     //检测应答信号
                    GEN_ACK     =       7'b0100_000,     //产生应答信号
                    GNE_STO     =       7'b1000_000;     //产生停止信号

    localparam      WR          =       6'b000_001,      //写数据请求
                    STA         =       6'b000_010,      //起始位请求
                    RD          =       6'b000_100,      //读数据请求
                    STO         =       6'b001_000,      //停止位请求 
                    ACK         =       6'b010_000,      //应答位请求
                    NACK        =       6'b100_000;      //无应答请求
    

    parameter       SYS_CLOCK   =       50_000_000;      //系统采用50MHz时钟
    parameter       SCL_CLOCK   =       400_000   ;      //SCL总线时钟采用400khz
    localparam      SCL_CNT_M   =       SYS_CLOCK / SCL_CLOCK / 4 - 1;  //产生时钟SCL计数器最大值

    
    reg                 en_div_cnt      ;               //计数器使能信号
    reg                 i2c_sda_oe      ;               //三态门使能信号,1:输出,0:输入
    reg     [6:0]       state           ;               //状态机的状态变量
    reg     [5:0]       cnt             ;               //计数器
    reg                 i2c_sdat_o      ;               //输出的i2c_sdat_o
    reg     [19:0]      div_cnt         ;               //分频信号

    wire    sclk_plus = (div_cnt == SCL_CNT_M);         //dic_cnt计数到最大值产生一个高脉冲

    //reset为低时,若干信号的初始化
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            en_div_cnt <= 1'b0;
            i2c_sda_oe <= 1'b1;
            state <= IDLE;
            cnt <= 0;
            i2c_sdat_o <= 1'b0;
            i2c_sclk <= 1'b1;
            Rx_Data <= 8'd0;
        end
    end
    //一段式状态机
    always @(posedge clk or negedge rst_n) begin
        case(state)
            IDLE:   begin
                        Trans_Done <= 1'b0;
                        i2c_sda_oe <= 1'b1;
                        if(go) begin                            //整个模块的启动信号go
                            en_div_cnt <= 1'b1;                 //使能div_cnt计数器,跳转的状态都需要这个计数器
                            if(cmd & STA )                      //起始命令
                                state <= GEN_STA;               //跳转到起始状态
                            else if(cmd & WR)                   //写命令
                                state <= WR_DATA;               //跳转到写数据状态
                            else if(cmd & RD)                   //读命令
                                state <= RD_DATA;               //跳转到读数据状态
                            else 
                                state <= IDLE;                  //都不满足则跳转到IDLE状态
                        end
                        else begin
                            en_div_cnt <= 1'b0;                 //模块未启动
                            state <= IDLE;                      //跳转到IDLE状态,且不使能div_cnt计数器
                        end
                    end
            GEN_STA:begin                                       //i2c_sclk为高电平时,i2c_sda从高电平变成低电平产生起始位
                        if(sclk_plus) begin                     //只有在sclk_plus产生高脉冲的时候,div_cnt计数到最大值
                            if(cnt == 3)                        //cnt计数到3,归0
                                cnt <= 0;
                            else 
                                cnt <= cnt + 1'b1;              //否则计数器自增1
                            case(cnt)                                                   //分四步
                                0:begin i2c_sdat_o <= 1'b1; i2c_sda_oe <= 1'b1; end     //cnt为0时,i2c_sdat_o拉高,三态门输出
                                1:begin i2c_sclk <= 1'b1; end                           //cnt为1时,拉高i2c_sclk
                                2:begin i2c_sdat_o <= 0; i2c_sclk <= 1; end             //cnt为2时,继续拉高i2c_sclk,拉低i2c_sdat_o
                                3:begin i2c_sclk <= 0; end                              //cnt为3时,拉低i2c_sclk
                                default: begin i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end
                            endcase

                            if(cnt == 3) begin
                                if(cmd & WR)
                                    state <= WR_DATA;           //跳转到写数据状态
                                else if(cmd & RD)
                                    state <= RD_DATA;           //跳转到读数据状态
                            end
                        end
                    end
            WR_DATA:begin                                       //写数据时,只有在sclk为低电平的时候,sda才可以变化
                        if(sclk_plus) begin                     //8位数据,每位数据分4步,故cnt计数器计数到31
                            if(cnt == 31)
                                cnt <= 0;
                            else 
                                cnt <= cnt + 1'b1;
                            case(cnt)
                                0,4,8,12,16,20,24,28:                           
                                    begin           
                                        i2c_sdat_o <= Tx_Data[7-cnt[4:2]];      //在GNE_STA状态时,i2c_sclk拉低了,所以此时把Tx_data一位传给i2s_sdat_o
                                        i2c_sda_oe <= 1'b1;                     //三态门输出使能
                                    end
                                1,5,9,13,17,21,25,29:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk posedge 拉高i2c_sclk
                                    end
                                2,6,10,14,18,22,26,30:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk keep high 继续拉高i2c_sclk
                                    end
                                3,7,11,15,19,23,27,31:
                                    begin
                                        i2c_sclk <= 1'b0;                       //sclk negedge 拉低i2c_sclk,以便在下次传输Tx_Data的一位
                                    end
                                default: begin i2c_sdat_o <= 1; i2c_sclk <= 1; end
                            endcase

                            if(cnt == 31) begin
                                state <= CHECK_ACK;                             //Tx_Data的8位写完之后,跳转到检查应答位状态
                            end
                            
                        end
                    end
            CHECK_ACK:begin
                        if(sclk_plus) begin
                            if(cnt == 3)
                                cnt <= 0;
                            else
                                cnt <= cnt + 1;
                            
                            case(cnt)                                                           //应答位的检查依然分四步完成,使用计数器cnt来控制
                                0:  begin i2c_sda_oe <= 1'b0; i2c_sclk <= 1'b0; end             //拉低i2c_sclk信号,并且设置三态门为输入
                                1:  begin i2c_sclk <= 1; end                                    //拉高i2c_sclk
                                2:  begin ack_o <= i2c_sdat; i2c_sclk <= 1; end                 //拉高i2c_sclk,并且把i2c_sdat赋值给ack_o,
                                3:  begin i2c_sclk <= 0; end                                    //拉低i2c_sclk
                                default:    begin   i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end   //上面连续两次拉高i2c_sclk是为了占空比为50%的i2c_sclk时钟
                            endcase

                            if(cnt == 3) begin
                                if(cmd & STO)
                                    state <= GNE_STO;                                           //跳转到产生停止位状态
                                else begin
                                    state <= IDLE;
                                    Trans_Done <= 1'b1;                                         //一次传输完成
                                end     
                            end
                        end

                      end
            GNE_STO :   begin                                                                   //在i2c_sclk为高电平,i2c_sda从低到高则为停止信号
                            if(sclk_plus) begin                                                 //与产生起始信号类似
                                if(cnt == 3)
                                    cnt <= 0;
                                else
                                    cnt <= cnt + 1'b1;
                                
                                case(cnt)
                                    0:  begin i2c_sdat_o <= 1'b0; i2c_sda_oe <= 1'b1; end       //三态门输出, i2c_sda拉低
                                    1:  begin i2c_sclk <= 1'b1; end                             //拉高i2c_sclk
                                    2:  begin i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end         //拉高i2c_sda,i2c_sclk
                                    3:  begin i2c_sclk <= 1'b1; end
                                    default: begin i2c_sdat_o <= 1'b1; i2c_sclk <= 1'b1; end
                                endcase

                                if(cnt == 3) begin
                                    Trans_Done <= 1'b1;                                         //传输完成
                                    state <= IDLE;  
                                end
                            end
                        end
            
            RD_DATA:    begin                                                                   //取数据
                            if(sclk_plus) begin
                                if(cnt == 31)
                                    cnt <= 0;
                                else
                                    cnt <= cnt + 1'b1;                                          //8位,每位分4步完成
                                
                                case(cnt)
                                0,4,8,12,16,20,24,28:
                                    begin
                                        i2c_sda_oe <= 1'b0;
                                        i2c_sclk <= 0;                          //set data,拉低i2c_sclk,三态门设置为输入
                                    end
                                1,5,9,13,17,21,25,29:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk posedge, 拉高i2c_scclk
                                    end
                                2,6,10,14,18,22,26,30:
                                    begin
                                        i2c_sclk <= 1'b1;                       //sclk keep high 利用移位的方法来存储,技巧需掌握
                                        Rx_Data <= {Rx_Data[6:0], i2c_sdat};
                                    end
                                3,7,11,15,19,23,27,31:                          //sclk negedge
                                    begin
                                        i2c_sclk <= 1'b0;
                                    end
                                default: begin i2c_sdat_o <= 1; i2c_sclk <= 1; end
                                endcase

                                if(cnt == 31) begin
                                    state <= GEN_ACK;
                                end
                            end
                        end
            GEN_ACK:    begin                                                   //产生应答信号
                            if(sclk_plus) begin                                 //分四步完成
                                if(cnt == 3)
                                    cnt <= 0;
                                else
                                    cnt <= cnt + 1'b1;
                                
                                case(cnt)
                                    0:  begin   
                                            i2c_sda_oe <= 1'b1;                 //三态门输出
                                            i2c_sclk <= 1'b0;                   //拉低i2c_sclk
                                            if(cmd & ACK)                       //产生ACK,输出0
                                                i2c_sdat_o <= 1'b0;
                                            else if(cmd & NACK)                 //产生NOACK,输出1
                                                i2c_sdat_o <= 1'b1;
                                        end
                                    1:  begin i2c_sclk <= 1'b1; end
                                    2:  begin i2c_sclk <= 1'b1; end
                                    3:  begin i2c_sclk <= 1'b0; end
                                    default:    begin i2c_sdat_o <= 1; i2c_sclk <= 1; end
                                    
                                endcase

                                if(cnt == 3) begin
                                    if(cmd & STO)
                                        state <= GNE_STO;                       //跳转到产生停止位状态
                                    else begin
                                        state <= IDLE;
                                        Trans_Done <= 1'b1;                     //传输完成
                                    end
                                end
                            end
                        end
            default:    begin 
                            i2c_sclk <= 1'b1;
                            Trans_Done <= 1'b0;
                            i2c_sdat_o <= 1'b0;
                            i2c_sda_oe <= 1'b1;
                            cnt <= 0;
                        end
        endcase
    end 
    
    //在en_div_cnt使能,div_cnt开始奇数,计数大最大值循环计数
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            div_cnt <= 20'd0;
        else if(en_div_cnt) begin
            if(div_cnt < SCL_CNT_M)
                div_cnt <= div_cnt + 1'b1;
            else
                div_cnt <= 0;
        end
        else
            div_cnt <= 20'd0;
    end

    assign i2c_sdat = !i2c_sdat_o && i2c_sda_oe ? 1'b0 : 1'bz;              //三态门设置

endmodule

测试的tb文件如下所示,分别向0x0A,0x0B,0x0C,0x0D,0X0F,写入1,2,3,4,5。然后再把它们读出来。

`timescale 1ns/1ns
module i2c_bit_shift_tb;

	reg Clk;
	reg Rst_n;
	
	reg [5:0]Cmd;
	reg Go;
	wire [7:0]Rx_DATA;
	reg [7:0]Tx_DATA;
	wire  Trans_Done;
	wire  ack_o;
	wire i2c_sclk;
	wire i2c_sdat;
	
	pullup PUP (i2c_sdat);
	
	localparam 
		WR =  6'b000001,	// 写请求
		STA = 6'b000010,	//起始位请求
		RD =  6'b000100,	//读请求
		STO = 6'b001000,	//停止位请求
		ACK = 6'b010000,	//应答位请求
		NACK = 6'b100000;	//无应答请求

/*
	i2c_bit_shift i2c_bit_shift(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.Cmd(Cmd),
		.Go(Go),
		.Rx_DATA(Rx_DATA),
		.Tx_DATA(Tx_DATA),
		.Trans_Done(Trans_Done),
		.ack_o(ack_o),
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
*/
	i2c_bit_shift i2c_bit_shift(
		.clk(Clk),
		.rst_n(Rst_n),
		.cmd(Cmd),
		.go(Go),
		.Rx_Data(Rx_DATA),
		.Tx_Data(Tx_DATA),
		.Trans_Done(Trans_Done),
		.ack_o(ack_o),
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);

	M24LC04B M24LC04B(
		.A0(0), 
		.A1(0), 
		.A2(0), 
		.WP(0), 
		.SDA(i2c_sdat), 
		.SCL(i2c_sclk), 
		.RESET(~Rst_n)
	);
	
	initial Clk = 1;
	always #10 Clk = ~Clk;
	
	initial begin
		Rst_n = 0;
		Cmd = 0;
		Go = 0;
		Tx_DATA = 0;
		#2001;
		Rst_n = 1;
		#2000;
		
		write_one_byte(8'hA0,8'h0A,8'hd1);
		//#20000;
		write_one_byte(8'hA0,8'h0B,8'hd2);
		//#20000;
		write_one_byte(8'hA0,8'h0C,8'hd3);
		//#20000;
		write_one_byte(8'hA0,8'h0D,8'hd4);
		//#20000;
		write_one_byte(8'hA0,8'h0F,8'hd5);
		//#20000;
		
		read_one_byte(8'hA0,8'h0A);
		//#20000;
		read_one_byte(8'hA0,8'h0B);
		//#20000;
		read_one_byte(8'hA0,8'h0C);
		//#20000;
		read_one_byte(8'hA0,8'h0D);
		//#20000;
		read_one_byte(8'hA0,8'h0F);
		//#20000;
		$stop;	
	end
	
	task write_one_byte;
		input [7:0]device_id;						//器件地址
		input [7:0]mem_address; 					//寄存器地址
		input [7:0]data;							//需要写的数据
		begin
			Cmd = WR | STA;							//写入产生起始位和写数据命令
			Go = 1;									//启动整个模块
			Tx_DATA = device_id;					//把器件地址赋值给Tx_Data
			#20;		
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次传输数据完成
			#20;
			//#20000;
			
			Cmd = WR;								//传入写数据命令
			Go = 1;									//启动整个模块
			Tx_DATA = mem_address;					//把寄存器地址赋值给Tx_Data
			#20;
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次数据传输完成
			//#20000;
			#20
		
			Cmd = WR | STO;							//传入写数据和产生停止位的命令
			Go = 1;									//启动整个模块
			Tx_DATA = data;							//把需要写入的数据赋值给Tx_Data
			#20;
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次传输数据完成
			#20000;	
		end
	endtask
	
	task read_one_byte;
		input [7:0]device_id;						//传入器件地址
		input [7:0]mem_address;						//传入寄存器地址
		begin
			Cmd = WR | STA;							//传入写数据和产生开始位的命令
			Go = 1;									//启动整个模块
			Tx_DATA = device_id;					//把器件地址赋值给Tx_Data
			#20;
			Go = 0;									//延迟一个时钟周期拉低go信号
			#200;
			@(posedge Trans_Done);					//等待此次传输数据完成
			#20000;
		
			Cmd = WR | STO;
			Go = 1;
			Tx_DATA = mem_address;
			#20;
			Go = 0;
			#200;
			@(posedge Trans_Done);
			//#20000;
			#20;
			
			Cmd = WR | STA;
			Go = 1;
			Tx_DATA = device_id | 8'd1;
			#20;
			Go = 0;
			#200;
			@(posedge Trans_Done);
			//#20000;
			#20;
			
			Cmd = RD | NACK | STO;
			Go = 1;
			#20;
			Go = 0;
			#200;
			@(posedge Trans_Done);
			//#20000;
			#20;			
		end
	endtask
	
	

endmodule

仿真波形如下所示,可知成功读出了数据:
i2c verilog,verilog实战,fpga开发

I2C 控制模块

由于i2c_bit_shift模块只可以发送一个字节的数据,而一次写输入或者时读输出需要写入好几个字节。因此可以为写输入和输入分别分配若干个写字节的任务,两者均是从IDLE状态开始。但是这些任务时是顺序的,必须等待前一个任务完成,此时我们的Trans_done信号就派上用场了。故可以使用状态机来设计。
对于一次写:
i2c verilog,verilog实战,fpga开发
对于一次读:
i2c verilog,verilog实战,fpga开发
有如下的状态(采取独热码编码):

IDLE 空闲状态
WR_REG 写状态
WAIT_WR_DONE 等待写完成状态
WR_REG_DONE 写完成状态
RD_REG 读状态
WAIT_RD_DONE 等待读完成状态
RD_REG_DONE 读完成状态

状态转换图:
i2c verilog,verilog实战,fpga开发

控制模块的代码如下(代码中有很详细的注释),便不多解释


module i2c_ctrl(
    input   wire            clk         ,               //时钟信号
    input   wire            rst_n       ,               //复位信号
    input   wire            wrreg_req   ,               //写请求信号
    input   wire            rdreg_req   ,               //读请求信号
    input   wire    [15:0]  addr        ,               //16位地址输入       
    input   wire            addr_mode   ,               //输入地址的模式,0:8位的地址 1:16位的地址
    input   wire    [7:0]   wrdata      ,               //总线发送的8位数据
    output  reg     [7:0]   rddata      ,               //总线接收的8位数据
    input   wire    [7:0]   device_id   ,               //i2c器件的ID
    output  reg             RW_Done     ,               //读/写完成的标志
    output  reg             ack         ,               //从机是否应答标志
    output  wire            i2c_sclk    ,               //i2c时钟总线
    inout   wire            i2c_sdat  
);


localparam  IDLE        =       7'b0000_001,            //空闲状态
            WR_REG      =       7'b0000_010,            //写状态
            WAIT_WR_DONE=       7'b0000_100,            //等待写完成状态
            WR_REG_DONE =       7'b0001_000,            //写完成状态
            RD_REG      =       7'b0010_000,            //读状态
            WAIT_RD_DONE=       7'b0100_000,            //等待读完成状态
            RD_REG_DONE =       7'b1000_000;            //读完成状态

localparam  WR          =       6'b000_001,             //写数据请求
            STA         =       6'b000_010,             //起始位请求
            RD          =       6'b000_100,             //读数据请求
            STO         =       6'b001_000,             //停止位请求 
            ACK         =       6'b010_000,             //应答位请求
            NACK        =       6'b100_000;             //无应答请求

                                       

reg     [5:0]   cmd         ;
reg     [7:0]   Tx_Data     ;
reg             go          ;
reg     [6:0]   state       ;
reg     [7:0]   cnt         ;
//reg     [15:0]  reg_addr    ;

wire            Trans_Done  ;
wire            ack_o       ;
wire    [7:0]   Rx_Data     ;
wire    [15:0]  reg_addr    ;

i2c_bit_shift i2c_bit_shift (
    .clk        (clk) ,
    .rst_n      (rst_n) ,
    .cmd        (cmd) ,  
    .go         (go) ,  
    .Tx_Data    (Tx_Data) ,  

    .i2c_sclk   (i2c_sclk) ,  
    .i2c_sdat   (i2c_sdat) ,  
    .Trans_Done (Trans_Done) ,  
    .Rx_Data    (Rx_Data) ,  
    .ack_o      (ack_o)    
);  
assign reg_addr = (addr_mode) ? addr : {addr[7:0], addr[15:8]};
    
    always @(posedge clk or negedge rst_n)
        if(!rst_n) begin
            cmd <= 6'd0;
            Tx_Data <= 8'd0;
            go <= 1'b0;
            rddata <= 8'd0;
            state <= IDLE;
            ack <= 0;
        end
        else begin
            case(state)
                RD_REG: begin                                                       //读寄存器状态包括读数据的四个task
                            state <= WAIT_RD_DONE;                                  //跳转等待读完成状态
                            case(cnt)
                                0:  write_byte(WR | STA, device_id) ;               //产生起始位并写入器件地址
                                1:  write_byte(WR, reg_addr[15:8])  ;               //写入寄存器地址的高8位
                                2:  write_byte(WR, reg_addr[7:0]);                  //写入寄存器地址的低8位
                                3:  write_byte(WR | STA, device_id | 8'd1);         //产生起始位并写入器件地址(最后一位是1,表示读)
                                4:  read_byte(RD | NACK | STO);                     //读数据并且产生NACK,STOP信号
                                default:;
                            endcase
                        end
                WAIT_RD_DONE:begin
                            go <= 1'b0;                                             //拉低go信号
                            if(Trans_Done) begin                                    //等待此次数据传输完成
                                if(cnt <= 3)
                                    ack <= ack | ack_o;
                                case(cnt)
                                    0:  begin cnt <= 1; state <= RD_REG; end        //cnt为0时,cnt加1,跳转读寄存器状态
                                    1:  begin
                                            state <= RD_REG;
                                            if(addr_mode)                           //根据地址模式
                                                cnt <= 2;                           //addr_more为1:16位地址
                                            else
                                                cnt <= 3;                           //addr_mode位0,8位地址
                                        end
                                    2:  begin
                                            cnt <= 3;
                                            state <= RD_REG;
                                        end
                                    3:  begin
                                            cnt <= 4;
                                            state <= RD_REG;
                                        end
                                    4:  state <= RD_REG_DONE;                       //cnt为4时,跳转到读完成状态                   
                                    default:    state <= IDLE;
                                endcase
                            end
                        end
                RD_REG_DONE:begin
                            RW_Done <= 1'b1;                                        //拉高读写完成信号
                            rddata <= Rx_Data;                                      //把读出的数据Rx_Data赋值给rddata
                            state <= IDLE;                                          //跳转到IDLE状态
                        end

                WR_REG: begin
                            state <= WAIT_WR_DONE;                                  //跳转到等待写完成状态
                            case(cnt)
                                0:  write_byte(WR | STA, device_id);                //产生起始位并写入器件地址
                                1:  write_byte(WR, reg_addr[15:8]);                 //写入寄存器地址的高8位
                                2:  write_byte(WR, reg_addr[7:0]);                  //写入寄存器地址的低8位
                                3:  write_byte(WR | STO, wrdata);                   //写入数据并且产生停止位
                                default:;
                            endcase
                        end

                WAIT_WR_DONE:begin
                                go <= 1'b0;                                         //拉低go信号
                                if(Trans_Done) begin                                //等待此次传输数据完成
                                    ack <= ack | ack_o;
                                    case(cnt)
                                        0:  begin cnt <= 1; state <= WR_REG; end    //cnt为0时,跳转写寄存器状态
                                        1:  begin
                                                state <= WR_REG;                    
                                                if(addr_mode)                       //不同的地址模式
                                                    cnt <= 2;                       //决定时写入16位还是8位
                                                else
                                                    cnt <= 3;
                                            end
                                        2:  begin
                                                cnt <= 3;
                                                state <= WR_REG;
                                            end
                                        3:  state <= WR_REG_DONE;                   //cnt为3时,跳转写完成状态
                                        default: state <= IDLE;
                                    endcase
                                end
                            end

                WR_REG_DONE:begin
                                RW_Done <= 1'b1;                                    //拉高读写完成信号
                                state <= IDLE;                                      //跳转到IDLE状态
                            end
                IDLE:       begin
                                cnt <= 0;
                                ack <= 0;
                                RW_Done <= 1'b0;
                                if(wrreg_req)                           //写请求则进入写寄存器状态
                                    state <= WR_REG;
                                else if(rdreg_req)                      //读请求则进入读寄存器状态
                                    state <= RD_REG;
                                else
                                    state <= IDLE;
                            end
                default:    state <= IDLE;
            endcase
end     


//读一个字节的任务
task read_byte;
    input   [5:0]   ctrl_cmd        ;           //传入命令
    begin                       
        cmd <= ctrl_cmd             ;           //将传入的命令赋值给cmd
        go <= 1'b1                  ;           //启动整个模块
    end
endtask

//写一个字节的任务
task write_byte;
    input   [5:0]   ctrl_cmd        ;           //传入命令
    input   [7:0]   wr_byte_data    ;           //传入要写入的8位字节数据
    begin
        cmd <= ctrl_cmd             ;           //将传入的命令赋值给cmd
        Tx_Data <= wr_byte_data     ;           //将传入的8位字节数据赋值给Tx_Data
        go <= 1'b1                  ;           //启动整个模块
    end    
endtask

endmodule



仿真的tb文件:

`timescale 1ns/1ns
module i2c_control_tb;

	reg Clk;
	reg Rst_n;
	
	reg wrreg_req;
	reg rdreg_req;
	reg [15:0]addr;
	reg addr_mode;
	reg [7:0]wrdata;
	wire [7:0]rddata;
	reg [7:0]device_id;
	wire RW_Done;
	
	wire  ack;
	wire i2c_sclk;
	wire i2c_sdat;
	
	pullup PUP (i2c_sdat);
	
	i2c_ctrl i2c_control(
		.clk(Clk),
		.rst_n(Rst_n),
		.wrreg_req(wrreg_req),
		.rdreg_req(rdreg_req),
		.addr(addr),
		.addr_mode(addr_mode),
		.wrdata(wrdata),
		.rddata(rddata),
		.device_id(device_id),
		.RW_Done(RW_Done),
		.ack(ack),		
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
	/*
	i2c_control i2c_control(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.wrreg_req(wrreg_req),
		.rdreg_req(rdreg_req),
		.addr(addr),
		.addr_mode(addr_mode),
		.wrdata(wrdata),
		.rddata(rddata),
		.device_id(device_id),
		.RW_Done(RW_Done),
		.ack(ack),		
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
	*/


	M24LC64 M24LC64(
		.A0(0), 
		.A1(0), 
		.A2(0), 
		.WP(0), 
		.SDA(i2c_sdat), 
		.SCL(i2c_sclk), 
		.RESET(~Rst_n)
	);
	
	initial Clk = 1;
	always #10 Clk = ~Clk;
	
	initial begin
		Rst_n = 0;
		rdreg_req = 0;
		wrreg_req = 0;
		#2001;
		Rst_n = 1;
		#2000;
		
		write_one_byte(8'hA0,8'h0A,8'hd1);
		//#20000;
		write_one_byte(8'hA0,8'h0B,8'hd2);
		//#20000;
		write_one_byte(8'hA0,8'h0C,8'hd3);
		//#20000;
		write_one_byte(8'hA0,8'h0D,8'hd4);
		//#20000;
		write_one_byte(8'hA0,8'h0F,8'hd5);
		//#20000;
		
		read_one_byte(8'hA0,8'h0A);
		//#20000;
		read_one_byte(8'hA0,8'h0B);
		//#20000;
		read_one_byte(8'hA0,8'h0C);
		//#20000;
		read_one_byte(8'hA0,8'h0D);
		//#20000;
		read_one_byte(8'hA0,8'h0F);
		//#20000;
		$stop;	
	end
	
	task write_one_byte;
		input [7:0]id;
		input [7:0]mem_address; 
		input [7:0]data;
		begin
			addr = {8'd0,mem_address};
			device_id = id;
			addr_mode = 1;
			wrdata = data;
			wrreg_req = 1;
			#20;
			wrreg_req = 0;
			@(posedge RW_Done);
			#20000;	
		end
	endtask
	
	task read_one_byte;
		input [7:0]id;
		input [7:0]mem_address;
		begin
			addr = {8'd0,mem_address};
			device_id = id;
			addr_mode = 1;
			rdreg_req = 1;
			#20;
			rdreg_req = 0;
			@(posedge RW_Done);
			#20000;			
		end
	endtask
	
	

endmodule

从仿真波形图可以看出,成功的读写了数据。

i2c verilog,verilog实战,fpga开发文章来源地址https://www.toymoban.com/news/detail-780654.html

到了这里,关于verilog实现I2C控制器 (小梅哥思路)----详细解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【DDR】基于Verilog的DDR控制器的简单实现(一)——初始化

    在FPGA中,大规模数据的存储常常会用到DDR。为了方便用户使用,Xilinx提供了DDR MIG IP核,用户能够通过AXI接口进行DDR的读写访问,然而MIG内部自动实现了许多环节,不利于用户深入理解DDR的底层逻辑。 本文以美光(Micron)公司生产的DDR3芯片MT41J512M8RH-093为例,说明DDR芯片的操作

    2024年02月02日
    浏览(52)
  • 【STM32】入门(七):I2C硬件控制方式

    之所以叫“I2C硬件控制方式”是与“软件控制方式”相对。I2C软件控制,就是写程序直接操作两个GPIO引脚,分别作为时钟线SCL和数据线SDA,按照I2C协议的时序要求,操作GPIO输入、输出、高电平、低电平。 听着就很复杂,好在STM32中有I2C的硬件实现,即通过简单的操作寄存器

    2023年04月08日
    浏览(49)
  • 从零开始 verilog 以太网交换机(二)MAC接收控制器的设计与实现

    🔈声明: 😃博主主页:王_嘻嘻的CSDN主页 🧨 从零开始 verilog 以太网交换机系列专栏:点击这里 🔑未经作者允许,禁止转载,侵权必删 🚩关注本专题的朋友们可以收获一个经典交换机设计的全流程,包括设计与验证(FPGA);以太网MAC的基础知识。新手朋友们还将获得一个

    2024年01月19日
    浏览(57)
  • 基于AHB协议的SRAM控制器的Verilog实现(详解代码)(一)——sram_core的实现

    目录  一、sramc(sram_controller)的总框架和模块划分 Features of sramc 结构框图和信号描述 二、sram_core的Verilog实现 动手开敲前的构思 Verilog代码和代码分析 三、sram_core的仿真验证 下载链接 sram因为其高读写速度常常作为CPU和嵌入式IC的缓存,是一个数字系统中必不可少的存在,因此

    2024年01月16日
    浏览(50)
  • STM32 硬件IIC 控制OLED I2C卡死问题

    #更新通知:2023-09-06 STM32L151 固件库 使用I2C 太难了,又宕机了,建议不要在固件库版本上尝试硬件IIC 了,一般人真用不了,直接使用软件模拟的,或者不要使用固件库了,用HAL 库吧,据说HAL 库没这么多问题,不死心的我还是死心了,等有空再研究吧 3.1 I2C模式,我这里选的

    2024年02月09日
    浏览(51)
  • m基于FPGA的FOC控制器verilog实现,包括CLARK,PARK,PID及SVPWM,含testbench

    目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件 Quartus II 12.1(64-Bit) ModelSim-Altera 6.6d Starter Edition 仿真结果如下: 整个系统的结构如下所示: 1、采集到两相电流 2、经过clarke变换后得到两轴正交电流量, 3、经过旋转变换后得到正交的电流量

    2024年02月15日
    浏览(54)
  • I2C协议介绍以及HAL库实现I2C对SHT30温湿度采样

    (1)I2C协议简介 I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线,主要用于近距离、低速的芯片之间的通信。 12C Bus(IIC, Inter-Integrated Circuit Bus)是由一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步,利用上拉电阻将它们拉成高电平(表示总

    2023年04月24日
    浏览(65)
  • STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)

    ​  本次课程采用单片机型号为STM32F103C8T6。(鉴于笔者实验时身边只有STM32F103ZET6,故本次实验使基于ZET6进行的) ​  课程链接:江协科技 STM32入门教程   往期笔记链接:   STM32学习笔记(一)丨建立工程丨GPIO 通用输入输出   STM32学习笔记(二)丨STM32程序调试

    2024年01月19日
    浏览(59)
  • 微波炉控制器Verilog代码Quartus仿真

    名称:微波炉控制器Verilog代码Quartus仿真(文末获取) 软件:Quartus 语言:Verilog 代码功能: 微波炉控制器 用芯片Altera Cyclone IV FPGA作为控制芯片,实现时间设置、温度设定、火力选择、声音提示,在硬件组成上,涉及到电源供电、按键输入、数码管显示、指示灯提示等。 由按

    2024年01月20日
    浏览(65)
  • vivado技巧|如何在fpga内部实现i2c信号透传(fpga内部两组i2c inout信号互连)

    大家好,我是数字小熊饼干,一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结,并通过汇总成文章的形式进行输出,相信无论你是在职的还是已经还准备入行,看过之后都会

    2024年03月18日
    浏览(139)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包