FPGA实现IIC接口(2)-EEPROM芯片写数据

这篇具有很好参考价值的文章主要介绍了FPGA实现IIC接口(2)-EEPROM芯片写数据。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1.单次随机写数据

1.1简介

1.2代码

1.3Modelsim仿真

1.4逻辑分析仪上板验证

2.页写

2.1简介

2.1代码

2.3Modelsim仿真

2.4逻辑分析仪上板验证


1.单次随机写数据

1.1简介

在黑金ax301开发板上使用IIC在EEPROM 24LC04芯片上写数据。

fpga型号:EP4CE6F17C8

开发工具:Quartus ll 13.0 + Modelsim 10.1c

系统时钟:50MHZ

IIC时钟:250KHZ

两个模块:IIC驱动模块和IIC顶层模块

使用的ip核:pll

单次随机写数据时序图如下:

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

过程如下:

(1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;

(2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);

(3) 先向从机写入高 8 位地址,且高位在前低位在后;

(4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位在后,若为 2 字节地址,跳转到步骤(6);

(5) 按高位在前低位在后的顺序写入单字节存储地址;

(6) 地址写入完成,主机接收到从机回传的应答信号后,开始单字节数据的写入;

(7) 单字节数据写入完成,主机接收到应答信号后,向从机发送停止信号,单字节数据 写入完成。 

1.2代码

i2c_driver模块:该模块的主要功能是通过IIC向EEPROM写入数据。器件地址为A0H,字节地址为12H,数据为ABH。在这里iic的时钟周期为250khz,对它进行4分频来作为i2c_scl。

module i2c_driver (
    //系统接口
    input 				rst_n,      //复位信号,低电平有效
    input 				i2c_clk,    //i2c系统时钟,250khz
    //i2c物理接口
    output 	reg 		i2c_scl,    //串行时钟信号
    inout 				i2c_sda,    //串行数据信号
    //用户接口
    input 	[7:0] 	pi_data,    //写入i2c的数据
    input 				i2c_start,  //i2c开始信号
    input 	[15:0] 	word_addr,	//字节地址
    input 				i2c_num,    //1表示字节地址为16位,0表示字节地址为8位
    output 	reg 		i2c_end     //i2c结束信号
);
    parameter DEVICE_ADDR = 7'b1_010_000;//器件地址
    localparam IDLE = 0,
               START = 1,
               SEND_ADDR_1 = 2,//发送器件地址
               ACK_1 = 3,
               SEND_ADDR_H = 4,//发送高八位字节地址
               ACK_2 = 5,
               SEND_ADDR_L = 6,//发送低八位字节地址
               ACK_3 = 7,
               SEND_DATA = 8,  //发送数据
               ACK_4 = 9,
               STOP = 10;   

    reg [1:0] i2c_clk_cnt;//分频计数器  
    reg i2c_clk_cnt_en;   //i2c系统时钟分频允许位
    reg [2:0] cnt_data;   //数据位计数
    reg sda_en;           //三态门开关
    reg sda_out;          //sda输出
    wire sda_in;          //sda输入
    reg ack_flag;         //响应标志信号 
	//状态机
    reg [3:0] cur_state;
    reg [3:0] next_state;

    wire [7:0] addr_w = {DEVICE_ADDR,1'b0}; //7位设备地址+1位读写标志位

    //i2c_clk_cnt
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            i2c_clk_cnt <= 0;
        else if(i2c_clk_cnt_en)  //当i2c系统时钟分频允许位为高电平时,i2c_clk_cnt自加1
            i2c_clk_cnt <= i2c_clk_cnt + 1'b1;   
        else
            i2c_clk_cnt <= 0; 
    end 
    //i2c_clk_cnt_en
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            i2c_clk_cnt_en <= 0;
		  else if(i2c_start)//i2c开始信号来后拉高
            i2c_clk_cnt_en <= 1;	
        else if((cur_state == STOP && cnt_data == 3'd3 && i2c_clk_cnt == 2'd3) || (cur_state == IDLE && !i2c_start))//当STOP状态结束或者空闲状态没有出现i2c开始信号时拉低i2c_clk_cnt_en信号
            i2c_clk_cnt_en <= 0;
        else
            i2c_clk_cnt_en <= i2c_clk_cnt_en;
    end
    //i2c_sda
    assign sda_in = i2c_sda;//i2c_sda作为输入
    assign i2c_sda = sda_en ? (sda_out ? 1'bz : 1'b0) : 1'bz;//i2c_sda作为输出
    
    //三段式状态机第一段同步时序描述状态转移
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            cur_state <= IDLE;
        else
            cur_state <= next_state;   
    end
    
    //三段式状态机第二段组合逻辑判断状态转移条件,描述状态转移规律
    always@(*)begin
        case(cur_state)
            IDLE:
                if(i2c_start)
                    next_state = START;
                else
                    next_state = IDLE;
            START:
                if(i2c_clk_cnt == 2'd3)
                    next_state = SEND_ADDR_1;
                else
                    next_state = START;
            SEND_ADDR_1: 
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_1;
                else
                    next_state = SEND_ADDR_1;    
            ACK_1:
                if(ack_flag && i2c_clk_cnt == 2'd3)begin
                    if(i2c_num)                    //判断字节地址位16位还是8位
                        next_state = SEND_ADDR_H;  //字节地位为16位,先发高8位字节地址
                    else
                        next_state = SEND_ADDR_L;  //字节地址位8位,跳转到发低8位状态
                end
                else if(i2c_clk_cnt == 2'd3)       //从机未响应返回空闲状态
                    next_state <= IDLE;  
                else
                    next_state = ACK_1; 
            SEND_ADDR_H:
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_2;
                else
                    next_state = SEND_ADDR_H;  
            ACK_2:
                if(ack_flag && i2c_clk_cnt == 2'd3)
                    next_state = SEND_ADDR_L;      //从机响应,转移到发送低8位字节地址状态
                else if(i2c_clk_cnt == 2'd3)
                    next_state = IDLE;             //从机未响应返回空闲状态
                else
                    next_state = ACK_2;
            SEND_ADDR_L:
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_3;
                else
                    next_state = SEND_ADDR_L;
            ACK_3:
                if(ack_flag && i2c_clk_cnt == 2'd3)
                    next_state = SEND_DATA;         //从机响应,转移到发送数据状态
                else if(i2c_clk_cnt == 2'd3)   
                    next_state = IDLE;              //从机未响应返回空闲状态        
                else
                    next_state = ACK_3;
            SEND_DATA:
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_4;
                else
                    next_state = SEND_DATA;     
            ACK_4:
                if(ack_flag && i2c_clk_cnt == 2'd3)
                    next_state = STOP;              //从机响应,转移停止状态
                else if(i2c_clk_cnt == 2'd3)
                    next_state = IDLE;              //从机未响应返回空闲状态
                else
                    next_state = ACK_4;   
            STOP:
					 if(i2c_clk_cnt == 2'd3 && cnt_data == 3'd3)
						  next_state <= IDLE;
					 else
					     next_state = STOP; 
            default:next_state <= IDLE;    
        endcase
    end  
    
    //三段式状态机第三段
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)begin    //初始状态,i2c_sda为输出态,sda_en为高电平,sda_out为高电平
            sda_en <= 1'b1;  
            sda_out <= 1'b1; 
            cnt_data <= 3'd0;
            i2c_end <= 0;
        end
        else begin
            i2c_end <= 0;
            case(cur_state)
                IDLE:begin                             //空闲状态
                        sda_en <= 1'b1;                //sda位输出状态
                        sda_out <= 1'b1;               //总线拉高
                    end
                START: begin   
                    if(i2c_clk_cnt == 2'd3)begin
                        sda_en <= 1'b1;
                        sda_out <= addr_w[7];          //此时sda_scl为下降沿,改变sda
                    end
                    else begin
                        sda_en <= 1'b1;
                        sda_out <= 1'b0;              //sda在scl高电平是出现下降沿,i2c开始
                    end
                end
                SEND_ADDR_1:begin                      //发送器件地址
                    if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd7)begin      //8位数据发送完毕
                            cnt_data <= 3'd0;
                            sda_en <= 1'b0;            //8位数据发送完毕,等待从机响应
                        end
                        else begin                     //发送完一位数据
                            cnt_data <= cnt_data + 1'b1;
                            sda_out <= addr_w[6 - cnt_data];
                            sda_en <= 1'b1;
                        end
                    end					  
                end
                ACK_1:begin
                    if(i2c_clk_cnt == 2'd3)begin
                        if(i2c_num == 1)begin
								sda_en <= 1;               //从机响应完成,拉高sda_en 
								sda_out <= word_addr[15]; 	//释放总线 
						end
						else begin
							sda_en <= 1;
							sda_out <= word_addr[7];
						end	
					end		
                end
                SEND_ADDR_H:begin                  //发送高8位字节地址
                    if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd7)begin  //8位数据发送完毕
                            cnt_data <= 3'd0;
                            sda_en <= 1'b0;        //8位数据发送完毕,等待从机响应
                        end
                        else begin                 //发送完一位数据
                            cnt_data <= cnt_data +1'b1;
                            sda_out <= word_addr[14 - cnt_data];
                            sda_en <= 1'b1;
                        end
                    end
                end
                ACK_2:
                    if(i2c_clk_cnt == 2'd3)begin
                        sda_en <= 1;          //从机响应完成,拉高sda_en 
                        sda_out <= word_addr[7];         //释放总线 
                end
                SEND_ADDR_L:                  //发送低8位字节地址
                    if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd7)begin  //低8位字节地址发送完毕
                            cnt_data <= 1'b0;
                            sda_en <= 1'b0;     //8位数据发送完毕,等待从机响应
                        end
                        else begin
                            cnt_data <= cnt_data + 1'b1;
                            sda_out <= word_addr[6 - cnt_data];
                            sda_en <= 1'b1;
                        end
                    end
                ACK_3:
                    if(i2c_clk_cnt == 2'd3)begin
                        sda_en <= 1;
                        sda_out <= pi_data[7];
                    end
                SEND_DATA:begin  //写入数据
                    if(i2c_clk_cnt == 2'd3)
                        if(cnt_data == 3'd7)begin   //高8位数据写完
                            sda_en <= 0;            //高8位数据发送完毕,等待从机响应
                            cnt_data <= 0; 
                        end
                        else begin
                            cnt_data <= cnt_data + 1'b1;
                            sda_out <= pi_data[6 - cnt_data];
                            sda_en <= 1'b1;
                        end
                end
                ACK_4:begin
                    if(i2c_clk_cnt == 2'd3)
						  
                    sda_en <= 1;      //从机响应完成,拉高sda_en 
                    sda_out <= 0;    //释放总线
                end
                STOP:begin
                    if(i2c_clk_cnt == 2'd2 && cnt_data == 3'd0)begin//拉高信号作为终止信号
                        sda_en <= 1;
                        sda_out <= 1;
                    end
                    else if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd3)begin    //送完了终止信号且延时一段时间发送I2C结束信号
                            i2c_end <= 1'b1;  
                            cnt_data <= 0;
                        end
                        else
                            cnt_data <= cnt_data + 1'b1;
                    end
                end
                default:;
            endcase
        end
    end

    //生成i2c_scl
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            i2c_scl <= 1;   //空闲状态scl为高电平
        else if(cur_state != STOP)begin
            if(i2c_clk_cnt == 2'd2)
                i2c_scl <= 0;
            else if(i2c_clk_cnt == 2'd0)
                i2c_scl <= 1;
        end
        else
            i2c_scl <= 1;
    end
    //生成从机响应信号
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            ack_flag <= 0;
        else
            case(cur_state)
                ACK_1,ACK_2,ACK_3,ACK_4:
                    //if(i2c_clk_cnt == 2'd1 && !sda_in)
						  if(i2c_clk_cnt == 2'd1)   //仿真时默认从机响应
                        ack_flag <= 1'b1;
                    else if(i2c_clk_cnt == 2'd3)
                        ack_flag <= 1'b0;
                default:ack_flag <= 1'b0;
            endcase
    end

endmodule

iic顶层模块:该模块为iic驱动模块提供了iic开始信号、字节地址和数据。

module i2c_write(
	input             clk,
	input             rst_n,
	output            i2c_scl,    //串行时钟信号
    inout             i2c_sda     //串行数据信号
);

	wire i2c_clk;
	wire i2c_end;
	pll_250k	pll_250k_inst (
		.inclk0 ( clk ),
		.c0 ( i2c_clk )
		);
		
	reg [15:0] cnt;
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			cnt <= 0;
		else if (cnt < 1000)
			cnt <= cnt + 1'b1;
		else
			cnt <= cnt;
	wire i2c_start = (cnt > 50 && cnt < 300) ? 1'b1 : 1'b0;

	i2c_driver i2c_driver(
		 //系统接口
		  .rst_n(rst_n),          //复位信号,低电平有效
		  .i2c_clk(i2c_clk),        //i2c系统时钟,250khz
		 //i2c物理接口
		  .i2c_scl(i2c_scl),   //串行时钟信号
		  .i2c_sda(i2c_sda),        //串行数据信号
		 //用户接口
		  .pi_data(8'hab), //写入i2c的数据
		  .i2c_start(i2c_start),     //i2c开始信号
		  .word_addr(16'h0012),
		  .i2c_num(0),        //1表示字节地址为16位,0表示字节地址为8位
		  .i2c_end(i2c_end)   //i2c结束信号
	);
	
endmodule

1.3Modelsim仿真

tb代码:由于没有EPPROM的仿真模型,所以仿真时我们默认从机响应。

`timescale 1ns/1ns
module i2c_tb;
	reg clk;
	reg rst_n;
	wire i2c_scl;
	wire i2c_sda;
i2c_write i2c_write(
	 .clk,
	 .rst_n,
	 .i2c_scl,   //串行时钟信号
    .i2c_sda        //串行数据信号
);
	initial clk = 0;
	always #10 clk = !clk;
	
	initial begin
		rst_n = 0;
		#65;
		rst_n = 1;
		#1_000_000;
		$stop;
	end
endmodule

波形:

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

1.4逻辑分析仪上板验证

我们以i2c_clk作为抓取时钟,深度设为512,当i2c_start出现上升沿时开始抓取。

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

2.页写

2.1简介

首先使用串口调试助手通过uart232向开发板发送数据,开发板接收到数据后存到fifo中,然后从fifo中读取数据再通过iic向EEPROM中写入数据。

fpga型号:EP4CE6F17C8

开发工具:Quartus ll 13.0 + Modelsim 10.1c

系统时钟:50MHZ

IIC时钟:250KHZ

四个模块:uart接收模块,i2c控制模块,i2c驱动模块,顶层模块

使用的ip核:pll,fifo

UART波特率:115200

时序图:

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

过程如下:

(1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;

(2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写 入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);

(3) 先向从机写入高 8 位地址,且高位在前低位在后;

(4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位在后,若为 2 字节地址,跳转到步骤(6);

(5) 按高位在前低位在后的顺序写入单字节存储地址;

(6) 地址写入完成,主机接收到从机回传的应答信号后,开始第一个单字节数据的写入;

(7) 数据写入完成,主机接收到应答信号后,开始下一个单字节数据的写入;

(8) 数据写入完成,主机接收到应答信号。若所有数据均写入完成,顺序执行操作 程;若数据尚未完成写入,跳回到步骤(7);

(9) 主机向从机发送停止信号,页写操作完成。

2.1代码

uart_rx模块:该模块的功能是接收串口调试助手传来的数据

module uart_rx(
    input                 rst_n,          //复位
    input                 clk,            //时钟,50mhz
    input                 rx,             //接收信号
    input [2:0]           bot_set,        //波特率设置位
    output reg            rx_done,        //接收完成位
    output reg [7:0]      data            //fpga接收位
);
    reg en;               //使能位
    reg[31:0]time1;        //接收十六分之一数据所用时间
    reg[1:0]d;            //2个D触发器
    reg[7:0]counter;      //160个状态
    reg[8:0]q;            //计数器
    //wire pdge;            //上升沿标准标志
    wire ndge;            //下降沿标志
    reg[3:0]s_begin;      //开始位检测
    reg[3:0]s_end;        //结束位检测
    reg[3:0]s[7:0];       //0到7位数据位检测

    //波特率
    always@(*)
        case(bot_set)
            0:time1=1000000000/9600/16/20;
            1:time1=1000000000/19200/20/16;
            2:time1=1000000000/38400/20/16;
            3:time1=1000000000/57600/20/16;
            4:time1=1000000000/115200/20/16; 
            default:time1=1000000000/9600/20/16;
        endcase
	//d触发器,用来检测rx出现上升沿还是下降沿	
    always@(posedge clk)begin
        d[0]<=rx;
        d[1]<=d[0];
        end
  
    //assign pdge=(d==2'b01); //rx出现上升沿
    assign ndge=(d==2'b10); //rx出现下降沿

    //en
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            en<=0;  
        else if(ndge)                  //当rx出现下降沿时en拉高
            en<=1;
        else if(rx_done||(s_begin>=4)) //传输结束en拉低
            en<=0;
    end

		
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            q<=0;
        else if(en)begin      //当en为高电平时,q开始计数
                if(q==time1-1)
                    q<=0;
                else 
                    q<=q+1'b1;
        end
        else
            q<=0;
    end

    wire flag=(q==time1/2);   //中点为采样点,中点到来产生一个时钟周期脉冲

    always@(posedge clk or negedge rst_n)
        if(!rst_n)
            counter<=0;
        else if(en)begin
            if(flag)begin  //中点到来,counter自加1
                if(counter==160)
                    counter<=0;
                else 
                    counter<=counter+1'b1;
            end
            else
                counter<=counter;
        end
        else   
            counter<=0;  //en低电平时,counter保持0

    always@(posedge clk or negedge rst_n)
        if(!rst_n)begin  //复位,检测位清0
            s_begin<=0;
            s_end<=0;
            s[0]<=0;
            s[1]<=0;
            s[2]<=0;
            s[3]<=0;
            s[4]<=0;
            s[5]<=0;
            s[6]<=0;
            s[7]<=0;
            end
        else if(flag)begin
            case(counter)         
                0:begin	s_begin<=0;
                    s_end<=0;
                    s[0]<=0;
                    s[1]<=0;
                    s[2]<=0;
                    s[3]<=0;
                    s[4]<=0;
                    s[5]<=0;
                    s[6]<=0;
                    s[7]<=0;
                    end
                5,6,7,8,9,10,11,12:s_begin<=s_begin+rx;
                21,22,23,24,25,26,27:s[0]<=s[0]+rx;
                37,38,39,40,41,42,43:s[1]<=s[1]+rx;
                53,54,55,56,57,58,59:s[2]<=s[2]+rx;
                69,70,71,72,73,74,75:s[3]<=s[3]+rx;
                85,86,87,88,89,90,91:s[4]<=s[4]+rx;
                101,102,103,104,105,106,107:s[5]<=s[5]+rx;
                117,118,119,120,121,122,123:s[6]<=s[6]+rx;
                133,134,135,136,137,138,139:s[7]<=s[7]+rx;
                149,150,151,152,153,154,155:s_end<=s_end+rx;
                default: begin s_begin<=s_begin;
                            s_end<=s_end;
                            s[0]<=s[0];
                            s[1]<=s[1];
                            s[2]<=s[2];
                            s[3]<=s[3];
                            s[4]<=s[4];
                            s[5]<=s[5];
                            s[6]<=s[6];
                            s[7]<=s[7];
                            end
            endcase
        end
	
	
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			data<=0;
		else if(counter==160&&flag)begin
			data[0]<=((s[0]>=4)?1'b1:1'b0);
			data[1]<=((s[1]>=4)?1'b1:1'b0);
			data[2]<=((s[2]>=4)?1'b1:1'b0);
			data[3]<=((s[3]>=4)?1'b1:1'b0);
			data[4]<=((s[4]>=4)?1'b1:1'b0);
			data[5]<=((s[5]>=4)?1'b1:1'b0);
			data[6]<=((s[6]>=4)?1'b1:1'b0);
			data[7]<=((s[7]>=4)?1'b1:1'b0);
		end
		
		
	always@(posedge clk or negedge rst_n)
		if(!rst_n)
			rx_done<=0;
		else if(counter==160&&(q==(time1-1)/2))
			rx_done<=1;
		else 
			rx_done<=0;
	endmodule

i2c控制模块:该模块的功能是把232串口接收到的数据存到fifo中,并产生i2c的开始信号和数据。

module i2c_ctrl(
    input 				clk,             //50m时钟信号
    input 				rst_n,           //复位信号
    input [7:0] 		rx_data,         //232串口接收的数据
    input 				rx_done,         //232串口接收数据标志位
    input [7:0] 		rx_num,          //接收的字节数
    input 				data_flag,       //fifo开始读取标志位
    output 				i2c_start,       //i2c开始信号
    output [7:0] 		i2c_data         //写入i2c的数据
);
    reg [7:0] cnt_data;    //计数接收的字节数  
    reg [7:0] q;           //232串口结束到i2c开始信号计数
    reg rd_en;             //fifo读信号
    reg [7:0] rd_en_flag;  //fifo读信号标志,由于data_flag高电平持续一个i2c时钟周期(200系统时钟周期),因此它为1时rd_en拉高
    //fifo IP核例化  
    fifo fifo_inst (
        .clock ( clk ),
        .data ( rx_data ),
        .rdreq ( rd_en),
        .wrreq ( rx_done ),
        .q ( i2c_data )
    );
    //计数接收的字节数 
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            cnt_data <= 0;
        else if(rx_done)begin     //当rx_done来的时候,cnt_data自加1
            if(cnt_data == rx_num)
                cnt_data <= 0;
            else
                cnt_data <= cnt_data + 1'b1;
        end
    end

    //q,由于1个i2c时钟周期等于200个系统时钟周期,因此需计数200
    always@(posedge clk or negedge rst_n)begin
        if(!rst_n)
            q <= 0;
        else if(cnt_data == rx_num)begin  //当数据全部接收完后q开始计数
            if(q == 202)
                q <= 202;
            else
                q <= q + 1'b1;
        end
    end
    assign i2c_start = (q > 0 && q <202) ? 1'b1 : 1'b0;  //由于i2c的时钟位250khz,因此得让i2c_start保持一个i2c时钟周期的高电平
    //data_flag高电平持续200个系统时钟周期
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            rd_en_flag <= 0;
        else if(data_flag)
            rd_en_flag <= rd_en_flag + 1'b1;
        else
            rd_en_flag <= 0;
    end
    //rd_en
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            rd_en <= 0;
        else if(rd_en_flag == 8'd1)
            rd_en <= 1;
        else
            rd_en <= 0;
    end

endmodule

i2c驱动模块:该模块的主要功能是通过IIC向EEPROM写入数据。器件地址为A0H,字节地址为01H,数据为fifo中读取的数据。在这里iic的时钟周期为250khz,对它进行4分频来作为i2c_scl。

module i2c_driver (
    //系统接口
    input 				rst_n,            //复位信号,低电平有效
    input 				i2c_clk,          //i2c系统时钟,250khz
    //i2c物理接口
    output reg 		    i2c_scl,     		//串行时钟信号
    inout 				i2c_sda,          //串行数据信号
    //用户接口
    input [7:0] 		pi_data,   			//写入i2c的数据
    input 				i2c_start,        //i2c开始信号
    input [15:0] 		word_addr,			//字节地址
    input 				i2c_num,          //1表示字节地址为16位,0表示字节地址为8位
    input [7:0] 		data_num,         //写入的字节数
    output reg 		    i2c_end,    		//i2c结束信号
    output reg 		    data_flag         //从fifo中开始读取标志位
);
    parameter DEVICE_ADDR = 7'b1_010_000;//器件地址
    localparam IDLE = 0,
               START = 1,
               SEND_ADDR_1 = 2,//发送器件地址
               ACK_1 = 3,
               SEND_ADDR_H = 4,//发送高八位字节地址
               ACK_2 = 5,
               SEND_ADDR_L = 6,//发送低八位字节地址
               ACK_3 = 7,
               SEND_DATA = 8,  //发送数据
               ACK_4 = 9,
               STOP = 10;   

    reg [1:0] i2c_clk_cnt;//分频计数器  
    reg i2c_clk_cnt_en;   //i2c系统时钟分频允许位
    reg [2:0] cnt_data;   //数据位计数
    reg sda_en;           //三态门开关
    reg sda_out;          //sda输出
    wire sda_in;          //sda输入
    reg ack_flag;         //响应标志信号 
    reg [7:0] write_num;  //计数发送的字节数
	//状态机
    reg [3:0] cur_state;
    reg [3:0] next_state;

    wire [7:0] addr_w = {DEVICE_ADDR,1'b0}; //7位设备地址+1位读写标志位

    //i2c_clk_cnt
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            i2c_clk_cnt <= 0;
        else if(i2c_clk_cnt_en)  //当i2c系统时钟分频允许位为高电平时,i2c_clk_cnt自加1
            i2c_clk_cnt <= i2c_clk_cnt + 1'b1;   
        else
            i2c_clk_cnt <= 0; 
    end 
    //i2c_clk_cnt_en
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            i2c_clk_cnt_en <= 0;
		  else if(i2c_start)//i2c开始信号来后拉高
            i2c_clk_cnt_en <= 1;	
        else if((cur_state == STOP && cnt_data == 3'd3 && i2c_clk_cnt == 2'd3) || (cur_state == IDLE && !i2c_start))//当STOP状态结束或者空闲状态没有出现i2c开始信号时拉低i2c_clk_cnt_en信号
            i2c_clk_cnt_en <= 0;
        else
            i2c_clk_cnt_en <= i2c_clk_cnt_en;
    end
    //i2c_sda
    assign sda_in = i2c_sda;//i2c_sda作为输入
    assign i2c_sda = sda_en ? (sda_out ? 1'bz : 1'b0) : 1'bz;//i2c_sda作为输出
    
    //三段式状态机第一段同步时序描述状态转移
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            cur_state <= IDLE;
        else
            cur_state <= next_state;   
    end
    
    //三段式状态机第二段组合逻辑判断状态转移条件,描述状态转移规律
    always@(*)begin
        case(cur_state)
            IDLE:
                if(i2c_start)
                    next_state = START;
                else
                    next_state = IDLE;
            START:
                if(i2c_clk_cnt == 2'd3)
                    next_state = SEND_ADDR_1;
                else
                    next_state = START;
            SEND_ADDR_1: 
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_1;
                else
                    next_state = SEND_ADDR_1;    
            ACK_1:
                if(ack_flag && i2c_clk_cnt == 2'd3)begin
                    if(i2c_num)                    //判断字节地址位16位还是8位
                        next_state = SEND_ADDR_H;  //字节地位为16位,先发高8位字节地址
                    else
                        next_state = SEND_ADDR_L;  //字节地址位8位,跳转到发低8位状态
                end
                else if(i2c_clk_cnt == 2'd3)       //从机未响应返回空闲状态
                    next_state <= IDLE;  
                else
                    next_state = ACK_1; 
            SEND_ADDR_H:
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_2;
                else
                    next_state = SEND_ADDR_H;  
            ACK_2:
                if(ack_flag && i2c_clk_cnt == 2'd3)
                    next_state = SEND_ADDR_L;      //从机响应,转移到发送低8位字节地址状态
                else if(i2c_clk_cnt == 2'd3)
                    next_state = IDLE;             //从机未响应返回空闲状态
                else
                    next_state = ACK_2;
            SEND_ADDR_L:
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_3;
                else
                    next_state = SEND_ADDR_L;
            ACK_3:
                if(ack_flag && i2c_clk_cnt == 2'd3)
                    next_state = SEND_DATA;         //从机响应,转移到发送数据状态
                else if(i2c_clk_cnt == 2'd3)   
                    next_state = IDLE;              //从机未响应返回空闲状态        
                else
                    next_state = ACK_3;
            SEND_DATA:
                if(cnt_data == 3'd7 && i2c_clk_cnt == 2'd3)
                    next_state = ACK_4;
                else
                    next_state = SEND_DATA;     
            ACK_4:begin
                if(ack_flag && i2c_clk_cnt == 2'd3)begin  //从机响应
                    if(write_num == data_num)
                        next_state = STOP;                //数据全部发送完,跳转至停止状态
                    else 
                        next_state = SEND_DATA;           //数据并未发完,跳转至发数据状态   
                end
                else if(i2c_clk_cnt == 2'd3)
                    next_state = IDLE;                   //从机未响应返回空闲状态
                else
                    next_state = ACK_4;   
            end
            STOP:
				if(i2c_clk_cnt == 2'd3 && cnt_data == 3'd3)
					next_state <= IDLE;
				else
					next_state = STOP; 
            default:next_state <= IDLE;    
        endcase
    end  
    
    //三段式状态机第三段
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)begin    //初始状态,i2c_sda为输出态,sda_en为高电平,sda_out为高电平
            sda_en <= 1'b1;  
            sda_out <= 1'b1; 
            cnt_data <= 3'd0;
            i2c_end <= 0;
            write_num <= 8'b0;
            data_flag <= 0;
        end
        else begin
            i2c_end <= 0;
            case(cur_state)
                IDLE:begin                             //空闲状态
                        sda_en <= 1'b1;                //sda位输出状态
                        sda_out <= 1'b1;               //总线拉高
                    end
                START: begin   
                    if(i2c_clk_cnt == 2'd3)begin
                        sda_en <= 1'b1;
                        sda_out <= addr_w[7];          //此时sda_scl为下降沿,改变sda
                    end
                    else begin
                        sda_en <= 1'b1;
                        sda_out <= 1'b0;              //sda在scl高电平是出现下降沿,i2c开始
                    end
                end
                SEND_ADDR_1:begin                      //发送器件地址
                    if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd7)begin      //8位数据发送完毕
                            cnt_data <= 3'd0;
                            sda_en <= 1'b0;            //8位数据发送完毕,等待从机响应
                        end
                        else begin                     //发送完一位数据
                            cnt_data <= cnt_data + 1'b1;
                            sda_out <= addr_w[6 - cnt_data];
                            sda_en <= 1'b1;
                        end
                    end					  
                end
                ACK_1:begin
                    if(i2c_clk_cnt == 2'd3)begin
                        if(i2c_num == 1)begin
								sda_en <= 1;               //从机响应完成,拉高sda_en 
								sda_out <= word_addr[15]; 	//释放总线 
						end
						else begin
							sda_en <= 1;
							sda_out <= word_addr[7];
						end	
					end		
                end
                SEND_ADDR_H:begin                  //发送高8位字节地址
                    if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd7)begin  //8位数据发送完毕
                            cnt_data <= 3'd0;
                            sda_en <= 1'b0;        //8位数据发送完毕,等待从机响应
                        end
                        else begin                 //发送完一位数据
                            cnt_data <= cnt_data +1'b1;
                            sda_out <= word_addr[14 - cnt_data];
                            sda_en <= 1'b1;
                        end
                    end
                end
                ACK_2:
                    if(i2c_clk_cnt == 2'd3)begin
                        sda_en <= 1;          //从机响应完成,拉高sda_en 
                        sda_out <= word_addr[7];         //释放总线 
                end
                SEND_ADDR_L:                  //发送低8位字节地址
                    if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd7)begin  //低8位字节地址发送完毕
                            cnt_data <= 1'b0;
                            sda_en <= 1'b0;     //8位数据发送完毕,等待从机响应
                        end
                        else begin
                            cnt_data <= cnt_data + 1'b1;
                            sda_out <= word_addr[6 - cnt_data];
                            sda_en <= 1'b1;
                        end
                    end
                ACK_3:
                    if(i2c_clk_cnt == 2'd3)begin
                        sda_en <= 1;
                        sda_out <= pi_data[7];
                        data_flag <= 0;
								write_num <= write_num + 1'b1;
                    end
                    else if(i2c_clk_cnt == 2'd2)  //提前一个i2c时钟周期开始从fifo中读取数据
                        data_flag <= 1;         
                SEND_DATA:begin  //写入数据
                    if(i2c_clk_cnt == 2'd3)
                        if(cnt_data == 3'd7)begin   //高8位数据写完
                            sda_en <= 0;            //高8位数据发送完毕,等待从机响应
                            cnt_data <= 0; 
                        end
                        else begin
                            cnt_data <= cnt_data + 1'b1;
                            sda_out <= pi_data[6 - cnt_data];
                            sda_en <= 1'b1;
                        end
                end
                ACK_4:begin
                    if(i2c_clk_cnt == 2'd3)begin	  
                        data_flag <= 0;
                        if(write_num == data_num)begin  //数据全部写完
                            write_num <= 8'b0;
                            sda_en <= 1;                //从机响应完成,拉高sda_en 
                            sda_out <= 0;               //拉低sda_out,方便在scl高电平时,sda产生上升沿
                        end 
                        else begin                      //数据并未全部写完,因此sda_out得为数据的最高位
                            sda_en <= 1;
                            sda_out <= pi_data[7];
									 write_num <= write_num + 1'b1;
                        end
                    end
                    else if(i2c_clk_cnt == 2'd2 && write_num < data_num)
                        data_flag <= 1;              //提前一个i2c时钟周期改变数据                                  
                end
                STOP:begin
                    if(i2c_clk_cnt == 2'd2 && cnt_data == 3'd0)begin//拉高信号作为终止信号
                        sda_en <= 1;
                        sda_out <= 1;
                    end
                    else if(i2c_clk_cnt == 2'd3)begin
                        if(cnt_data == 3'd3)begin    //送完了终止信号且延时一段时间发送I2C结束信号
                            i2c_end <= 1'b1;  
                            cnt_data <= 0;
                        end
                        else
                            cnt_data <= cnt_data + 1'b1;
                    end
                end
                default:;
            endcase
        end
    end

    //生成i2c_scl
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            i2c_scl <= 1;   //空闲状态scl为高电平
        else if(cur_state != STOP)begin
            if(i2c_clk_cnt == 2'd2)
                i2c_scl <= 0;
            else if(i2c_clk_cnt == 2'd0)
                i2c_scl <= 1;
        end
        else
            i2c_scl <= 1;
    end
    //生成从机响应信号
    always @(posedge i2c_clk or negedge rst_n) begin
        if(!rst_n)
            ack_flag <= 0;
        else
            case(cur_state)
                ACK_1,ACK_2,ACK_3,ACK_4:
                    //if(i2c_clk_cnt == 2'd1 && !sda_in)
						  if(i2c_clk_cnt == 2'd1)   //仿真时默认从机响应
                        ack_flag <= 1'b1;
                    else if(i2c_clk_cnt == 2'd3)
                        ack_flag <= 1'b0;
                default:ack_flag <= 1'b0;
            endcase
    end

endmodule

顶层模块:

module i2c_write(
    //系统接口
    input      sys_clk,    
    input      sys_n,
    //uart_rx
    input      rx,
    //i2c
    inout      i2c_sda,
    output     i2c_scl
);
    wire clk;       //50MHZ
    wire i2c_clk;    //250KHZ
    wire locked;
    wire rst_n = locked && sys_n;
    wire rx_done;
    wire [7:0] rx_data;
    wire i2c_end;
    wire data_flag;
    wire i2c_start;
    wire [7:0] i2c_data;
	 parameter num = 8'd10;//接收的字节数
	 wire [7:0] rx_num = num;
    //pll IP核例化
    pll_250k pll_250k_inst (
        .inclk0 ( sys_clk ),
        .c0 ( clk ),
        .c1 ( i2c_clk ),
        .locked ( locked )
        );
    //232接收模块例化
    uart_rx uart_rx(
        .rst_n(rst_n),          //复位
        .clk(clk),              //时钟,50mhz
        .rx(rx),                //接收信号
        .bot_set(4),            //波特率设置位
        .rx_done(rx_done),      //接收完成位
        .data(rx_data)          //fpga接收位
    );
	 //i2c控制模块例化
    i2c_ctrl i2c_ctrl(
        .clk(clk),              //50m时钟信号
        .rst_n(rst_n),          //复位信号
        .rx_data(rx_data),      //232串口接收的数据
        .rx_done(rx_done),      //232串口接收数据标志位
        .rx_num(rx_num),        //接收的字节数
        .data_flag(data_flag),  //fifo开始读取标志位
        .i2c_start(i2c_start),  //i2c开始信号
        .i2c_data(i2c_data)     //写入i2c的数据
    );
    //iic驱动模块例化
    i2c_driver i2c_driver(
        //系统接口
        .rst_n(rst_n),            //复位信号,低电平有效
        .i2c_clk(i2c_clk),        //i2c系统时钟,250khz
        //i2c物理接口
        .i2c_scl(i2c_scl),        //串行时钟信号
        .i2c_sda(i2c_sda),        //串行数据信号
        //用户接口
        .pi_data(i2c_data),       //写入i2c的数据
        .i2c_start(i2c_start),    //i2c开始信号
        .word_addr(16'h0001),     //字节地址
        .i2c_num(1'b0),           //1表示字节地址为16位,0表示字节地址为8位
        .data_num(rx_num),        //写入的字节数
        .i2c_end(i2c_end),        //i2c结束信号
        .data_flag(data_flag)     //fifo开始读取标志位
    );

endmodule

2.3Modelsim仿真

tb代码:仿真时模拟向开发板发送3给数据:5FH、F5H和12H。

`timescale 1ns/1ns
module i2c_tb;
    reg sys_clk;
    reg sys_n;
    reg rx;
    wire i2c_sda;
    wire i2c_scl;

    i2c_write i2c_write(
        //系统接口
        .sys_clk(sys_clk),    
        .sys_n(sys_n),
        //uart_rx
        .rx(rx),
        //i2c
        .i2c_sda(i2c_sda),
        .i2c_scl(i2c_scl)
    );


    task f1;
	input[7:0]tx_data;
	begin
        rx=1;
        #20
        rx=0;
        #8680;
        rx=tx_data[0];
        #8680;
        rx=tx_data[1];
        #8680;
        rx=tx_data[2];
        #8680;
        rx=tx_data[3];
        #8680;
        rx=tx_data[4];
        #8680;
        rx=tx_data[5];
        #8680;
        rx=tx_data[6];
        #8680;
        rx=tx_data[7];
        #8680;
        rx=1;
        #8680;
    end
    endtask
    initial sys_clk = 0;
    always#10 sys_clk =! sys_clk;
    initial begin
        sys_n=0;
        rx=1;
        #201
        sys_n=1;
        #200;
        f1(8'h5f);
        #10000;
        f1(8'hf5);
        #10000;
        f1(8'h12);
        #2_000_000;	
        $stop;
    end
endmodule

uart接收模块和iic控制模块仿真图:

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

i2c驱动模块:

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

2.4逻辑分析仪上板验证

首先我们通过串口调试助手向开发板发送10个数据

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

然后以i2c_clk作为抓取时钟,深度设为512,当i2c_start出现上升沿时开始抓取。

FPGA实现IIC接口(2)-EEPROM芯片写数据,fpga开发

这样就成功向EEPROM中写入了数据(初始字节地址为(01H),通过前面的文章FPGA实现IIC接口(1)-EEPROM芯片读取数据-CSDN博客我们也可以顺利读出这些数据。

————————————————
版权声明:本文为CSDN博主「守雲开见月明」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45742083/article/details/135307355文章来源地址https://www.toymoban.com/news/detail-815992.html

到了这里,关于FPGA实现IIC接口(2)-EEPROM芯片写数据的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • FPGA开发(2)——IIC通信

    IIC物理层框图如下图所示。 (1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。 (2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数

    2024年02月02日
    浏览(33)
  • FPGA IIC的状态机实现

    注:一个IIC总线可以挂载多个设备,一个IIC总线有两条线,一个是数据线,一个是时钟线。主机通过访问不同的从机地址来进行不同设备之间的通信。细节请自己百度,这里不做过多介绍。 2.1整体时序图 注:图片纯手画,有些丑,不喜勿喷。 由图中可以看出,整体的时序图

    2024年02月02日
    浏览(31)
  • FPGA实现IIC驱动环境光、距离传感器

    本次实验平台为野火征途mini开发板,用到的外设有按键、LED灯数码管、环境光(ALS)+距离(PS)传感器芯片。 AP3216C是一款环境光、距离传感器芯片,其接口为IIC接口,FPGA通过IIC接口可以配置工作模式、读取环境光、距离数据。 系统模块连接如下: key_filter 模块实现按键消

    2024年02月16日
    浏览(38)
  • IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

    该文章结合PCF8591 8-bit AD/DA 模数/数模转换器来详细介绍IIC通信协议,尽量做到条理清晰,通俗易懂。该文图片均从PCF8591手册中截取,一定程度上引导读者学习阅读data sheet。 之后可能会更新 如何将IIC的Verilog实现变为一个IP核,并在pynq-Z2板子上使用 。 2.1 地址位 在I2C总线系统

    2024年02月04日
    浏览(51)
  • Fpga开发笔记(一):高云FPGA芯片介绍,入手开发板套件、核心板和底板介绍

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/135551179 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中… 上一篇:没有了 下一篇:《Fpga开

    2024年02月02日
    浏览(30)
  • 【【IIC模块Verilog实现---用IIC协议从FPGA端读取E2PROM】】

    下面是 design 设计 下面是testbench 下面是注意事项 因为时钟的不同 我们先设计出本次时钟所需要的dri_clk 在配置完dri_clk 之后 我们需要做的是对整个I2C结构 进行状态机的 书写 建议 写成经典的三段状态机的形式 同步时序描述状态转移 组合逻辑判断状态转移条件 时序电路描述

    2024年02月03日
    浏览(45)
  • 基于FPGA的高速数据采集ATA接口Verilog开发与Matlab

    基于FPGA的高速数据采集ATA接口Verilog开发与Matlab 摘要: 本文介绍了基于FPGA的高速数据采集ATA接口的Verilog开发与Matlab的应用。通过使用Verilog语言进行FPGA的硬件设计,实现了ATA接口的数据采集功能。同时,结合Matlab进行数据处理和分析,实现了对采集的数据进行实时处理和显

    2024年02月07日
    浏览(42)
  • Vivado | FPGA开发工具(Xilinx系列芯片)

    官网下载地址 最详细的Vivado安装教程 Vivado的安装以及使用_入门

    2024年02月12日
    浏览(43)
  • 基于USB总线技术的数据采集系统接口——FPGA实现Matlab

    基于USB总线技术的数据采集系统接口——FPGA实现Matlab USB总线技术是一种常用的数据传输接口,广泛应用于各种设备和系统中。在数据采集系统中,USB接口可以用于连接外部传感器、测量设备等,将采集到的数据传输到计算机或其他处理设备上进行处理和分析。本文将介绍如

    2024年02月08日
    浏览(38)
  • fpga xvc 调试实现,支持多端口同时调试多颗FPGA芯片

    xilinx 推荐的实现结构方式如下: 通过一个ZYNQ运行xvc服务器,然后通过zynq去配置其他的FPGA,具体参考设计可以参考手册xapp1251,由于XVC运行的协议是标准的TCP协议,这种方式需要ZYNQ运行TCP协议,也就需要运行操作系统,可移植性差; 本方案考虑到XVC协议本身是非常简单的协

    2024年01月20日
    浏览(37)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包