I2C 总线操纵 EEPROM

现如今对 FPGA 的基础已经有了一定的了解,并且进入到了总线操纵的学习中来。近一段时间打算更几篇关于总线操作的博文,首先从简单的 I2C 接口对 EEPROM 的操作开始。

何为总线?

总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束,按照计算机所传输的信息种类,计算机的总线可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。

I2C 总线

I2C 总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。

个人认为总线的控制,主要为主从设备之间的数据交互。依靠握手机制,主控设备(Master)在需要读时,向从属设备(Slave)发出读请求。Slave 接收到读请求后,将会回复一个 ACK 代表确认收到请求,写请求与之相类似。

I2C 总线操纵 EEPROM_第1张图片
板子上的电路图如图所示,可以看到两根线上拉的有电阻,即在空闲状态时,总线上始终为高电平。SCL 代表设备时钟,SDA 代表数据线,A0、A1、A2始终为低,24LC04 的设备地址为 A0。

设备的时序操作如下图所示。
I2C 总线操纵 EEPROM_第2张图片
信号状态与时序分析如下:

  • 总线空闲状态:
    I2c总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

  • 启动信号:
    在时钟线sCL保持高电平期间,数据线SDA上的电平被拉低(即负跳变),定义为12C总线总线的启动信号,它标志着一次数据传输的开始。启动信号是由主控器主动建立的,在建立该信号之前12C总线必须处于空闲状态,如下图所示。
    I2C 总线操纵 EEPROM_第3张图片

  • 停止信号:
    在时钟线SCL保持高电平期间,数据线SDA被释放,使得SDA返回高电平(即正跳变),称为12C总线的停止信号,它标志着一次数据传输的终止。停止信号也是由主控器主动建立的,建立该信号之后,12C总线将返回空闲状态。

  • 数据位传送
    在12C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL呈现高电平期 间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1.只有在SCL为低电平期间, 才允许SDA上的电平改变状态。

  • 应答信号
    I2C总线上的所有数据都是以8位字节传送的,发送每发送一个字节,就在时钟脉冲期间释放数据线,由接收器反馈一个应笞信号。应答信号为低电平时,规定为有效应答位(ACK简 称应答位),表示接收噩已经成功地接收了该字节。
    应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低, 并且确保在该时钟的高电平期间为稳定的低电平。
    如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号。
    I2C 总线操纵 EEPROM_第4张图片

程序设计

i2ceeprom_test模块完成 EEPROM的读写, EEPROM设备地址是A0,程序中将地址00的数据读出,然后通过LED显示,在按键按下时,数字加一并再次写入 EEPROM并显示出来。在 12C控制器中,代码的大部分功能在备注中也有很多批注。

module i2c_eeprom_test(
    input            sys_clk,       //system clock 50Mhz on board
    input            rst_n,         //reset ,low active
    input            key,			//data will add 1 when push key
    inout            i2c_sda,		
    inout            i2c_scl,
    output [3:0]     led

);
localparam S_IDLE       = 0;
localparam S_READ       = 1;
localparam S_WAIT       = 2;
localparam S_WRITE      = 3;
reg[3:0] state;

wire button_negedge;
reg[7:0] read_data;
reg[31:0] timer;

wire scl_pad_i;
wire scl_pad_o;
wire scl_padoen_o;

wire sda_pad_i;
wire sda_pad_o;
wire sda_padoen_o;

reg[ 7:0] 	i2c_slave_dev_addr;
reg[15:0] 	i2c_slave_reg_addr;

reg 		i2c_write_req;
wire 		i2c_write_req_ack;
reg[ 7:0] 	i2c_write_data;

reg 		i2c_read_req;
wire 		i2c_read_req_ack;
wire[7:0] 	i2c_read_data;

assign led = ~read_data[3:0];

ax_debounce ax_debounce_m0
(
    .clk             (sys_clk),
    .rst             (~rst_n),
    .button_in       (key),
    .button_posedge  (),
    .button_negedge  (button_negedge),
    .button_out      ()
);
 
always@(posedge sys_clk or negedge rst_n)
begin
    if(rst_n == 1'b0)
    begin
        state 				<= S_IDLE;
        i2c_write_req 		<= 1'b0;
        read_data 			<= 8'h00;
        timer 				<= 32'd0;
        i2c_write_data 		<= 8'd0;
        i2c_slave_reg_addr 	<= 16'd0;
        i2c_slave_dev_addr 	<= 8'ha0;//1010 000 0, device address
        i2c_read_req 		<= 1'b0;
    end
    else
        case(state)
            S_IDLE:
            begin
                if(timer >= 32'd499_999)//wait 10ms
                    state <= S_READ;
                else
                    timer <= timer + 32'd1;
            end
            S_READ:
            begin
                if(i2c_read_req_ack)			//if read request ack, then i2c read data valid
                begin
                    i2c_read_req <= 1'b0;
                    read_data	 <= i2c_read_data;
                    state 		 <= S_WAIT;
                end
                else
                begin
                    i2c_read_req 		<= 1'b1;
                    i2c_slave_dev_addr 	<= 8'ha0;
                    i2c_slave_reg_addr 	<= 16'd0;
                end
            end
            S_WAIT:
            begin
                if(button_negedge)			//when push button, then data add 1, and switch to write state
                begin
                    state 		<= S_WRITE;
                    read_data 	<= read_data + 8'd1;
                end
            end
            S_WRITE:
            begin
                if(i2c_write_req_ack)		//if write request ack, then switch to read state
                begin
                    i2c_write_req 	<= 1'b0;
                    state 			<= S_READ;
                end
                else
                begin
                    i2c_write_req 	<= 1'b1;
                    i2c_write_data 	<= read_data;
                end
            end
            
            default:
                state <= S_IDLE;
        endcase
end
//i2c bidirection control
assign sda_pad_i = i2c_sda;
assign i2c_sda = ~sda_padoen_o ? sda_pad_o : 1'bz;

assign scl_pad_i = i2c_scl;
assign i2c_scl = ~scl_padoen_o ? scl_pad_o : 1'bz;

i2c_master_top i2c_master_top_m0
(
    .rst					(~rst_n),
    .clk					(sys_clk),
    .clk_div_cnt			(16'd99),       	//Standard mode:100Khz; prescale = 50MHz/(5*100Khz) - 1
    
    // I2C signals 
    // i2c clock line
    .scl_pad_i				(scl_pad_i),        // SCL-line input
    .scl_pad_o				(scl_pad_o),        // SCL-line output (always 1'b0)
    .scl_padoen_o			(scl_padoen_o),     // SCL-line output enable (active low)

    // i2c data line
    .sda_pad_i				(sda_pad_i),        // SDA-line input
    .sda_pad_o				(sda_pad_o),        // SDA-line output (always 1'b0)
    .sda_padoen_o			(sda_padoen_o),     // SDA-line output enable (active low)
    
    
    .i2c_addr_2byte			(1'b0),   			//register address is 1 byte
    .i2c_read_req			(i2c_read_req),
    .i2c_read_req_ack		(i2c_read_req_ack),
    .i2c_write_req			(i2c_write_req),
    .i2c_write_req_ack		(i2c_write_req_ack),
    .i2c_slave_dev_addr		(i2c_slave_dev_addr),
    .i2c_slave_reg_addr		(i2c_slave_reg_addr),
    .i2c_write_data			(i2c_write_data),
    .i2c_read_data			(i2c_read_data),
    .error					()
);
endmodule 

完整源代码下载链接:
CSDN下载

你可能感兴趣的:(FPGA硬件设计)