现如今对 FPGA 的基础已经有了一定的了解,并且进入到了总线操纵的学习中来。近一段时间打算更几篇关于总线操作的博文,首先从简单的 I2C 接口对 EEPROM 的操作开始。
总线(Bus)是计算机各种功能部件之间传送信息的公共通信干线,它是由导线组成的传输线束,按照计算机所传输的信息种类,计算机的总线可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。总线是一种内部结构,它是cpu、内存、输入、输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。在计算机系统中,各个部件之间传送信息的公共通路叫总线,微型计算机是以总线结构来连接各个功能部件的。
I2C 总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。
个人认为总线的控制,主要为主从设备之间的数据交互。依靠握手机制,主控设备(Master)在需要读时,向从属设备(Slave)发出读请求。Slave 接收到读请求后,将会回复一个 ACK 代表确认收到请求,写请求与之相类似。
板子上的电路图如图所示,可以看到两根线上拉的有电阻,即在空闲状态时,总线上始终为高电平。SCL 代表设备时钟,SDA 代表数据线,A0、A1、A2始终为低,24LC04 的设备地址为 A0。
总线空闲状态:
I2c总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
启动信号:
在时钟线sCL保持高电平期间,数据线SDA上的电平被拉低(即负跳变),定义为12C总线总线的启动信号,它标志着一次数据传输的开始。启动信号是由主控器主动建立的,在建立该信号之前12C总线必须处于空闲状态,如下图所示。
停止信号:
在时钟线SCL保持高电平期间,数据线SDA被释放,使得SDA返回高电平(即正跳变),称为12C总线的停止信号,它标志着一次数据传输的终止。停止信号也是由主控器主动建立的,建立该信号之后,12C总线将返回空闲状态。
数据位传送
在12C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL呈现高电平期 间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1.只有在SCL为低电平期间, 才允许SDA上的电平改变状态。
应答信号
I2C总线上的所有数据都是以8位字节传送的,发送每发送一个字节,就在时钟脉冲期间释放数据线,由接收器反馈一个应笞信号。应答信号为低电平时,规定为有效应答位(ACK简 称应答位),表示接收噩已经成功地接收了该字节。
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低, 并且确保在该时钟的高电平期间为稳定的低电平。
如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号。
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下载