1.介绍
I2C总线是Philips半导体公司(现在的NXP半导体公司)开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。SDA(串行数据线)和SCL(串行时钟线)都是双向I/O线,接口电路为开漏输出.需通过上拉电阻接电源VCC.当总线空闲时.两根线都是高电平。
在I2C器件开始通信(传输数据)之前,串行时钟线SCL和串行数据线SDA线由于上拉的原因处于高电平状态,此时I2C总线处于空闲状态。如果主机(此处指FPGA)想开始传输数据,只需在SCL为高电平时将SDA线拉低,产生一个起始信号,从机检测到起始信号后,准备接收数据,当数据传输完成,主机只需产生一个停止信号,告诉从机数据传输结束,停止信号的产生是在SCL为高电平时, SDA从低电平跳变到高电平,从机检测到停止信号后,停止接收数据。
在串行时钟线SCL为低电平状态时, SDA允许改变传输的数据位( 1为高电平, 0为低电平),在SCL为高电平状态时, SDA要求保持稳定,相当于一个时钟周期传输1bit数据,经过8个时钟周期后,传输了8bit数据,即一个字节。第8个时钟周期末,主机释
放SDA以使从机应答,在第9个时钟周期,从机将SDA拉低以应答;如果第9个时钟周期, SCL为高电平时, SDA未被检测到为低电平,视为非应答,表明此次数据传输失败。第9个时钟周期末,从机释放SDA以使主机继续传输数据,如果主机发送停止信号,此次传输结束。数据以8bit即一个字节为单位串行发出,其最先发送的是字节的最高位。
eeprom选用at24c02,at24c64
器件地址
写数据
读数据
2.硬件
FPGA管脚配置
信号名 | 方向 | 管教 | 端口说明 |
sys_clk | input | E1 | 系统时钟50M |
sys_rst_n | input | M1 | 系统复位,低电平有效 |
led0 | output | F3 | |
led1 | output | F5 | |
key0 | input | G5 | |
key1 | input | L7 | |
key2 | input | J6 | |
i2c_scl | output | D5 | I2C时钟 |
i2c_sdk | input | C3 | I2C数据 |
3.程序设计
always @(posedge i2c_clk or negedge rst) begin
if(!rst)begin
state <= s0_idle_start;
r_rd_sel <= SDA_WRITE;
sda_buffer <= 1'd1;
buffer <= 8'd0;
bit_cnt <= 4'd0;
rd_data <= 8'd0;
I2C_WorR <= I2C_READ;
end
else begin
case(state)
s0_idle_start:begin
if ((key_flag == 1'd1) && (key_value == 3'b110))begin // read
r_rd_sel <= SDA_WRITE; // system control the iic sda write
sda_buffer <= 1'd0; // start signal
state <= s1_device_addr; // state change
buffer <= 8'b1010_0000;
I2C_WorR <= I2C_READ;
rd_data <= 8'd0;
end
else if((key_flag == 1'd1) && (key_value == 3'b101))begin // write
r_rd_sel <= SDA_WRITE; // system control the iic sda write
sda_buffer <= 1'd0; // start signal
state <= s1_device_addr; // state change
buffer <= 8'b1010_0000;
I2C_WorR <= I2C_WRITE;
end
else begin
state <= s0_idle_start;
end
end
s1_device_addr :begin
if(!scl)begin // if scl is low level
if(bit_cnt < 4'd8)begin
r_rd_sel <= SDA_WRITE;
sda_buffer <= buffer[7];
buffer <= {buffer[6:0],buffer[7]};
bit_cnt <= bit_cnt + 1'b1;
end
else begin
state <= s2_device_addr_ack;
bit_cnt <= 4'd0;
r_rd_sel <= SDA_READ; // sda receive data
end
end
end
s2_device_addr_ack :begin
if(scl)begin // if scl is high level
r_rd_sel <= SDA_WRITE;
if(sda == 1'd0)begin
if(DATA_ADDR_WIDTH)begin // 16bit data
state <= s3_word_addr_high;
buffer <= 8'h00;
end
else begin
state <= s5_word_addr_low;
buffer <= 8'h01;
end
end
else begin
//state <= s5_word_addr_low;
state <= s0_idle_start;
end
end
end
s3_word_addr_high :begin
if(!scl)begin // if scl is low level
if(bit_cnt < 4'd8)begin
sda_buffer <= buffer[7];
buffer <= {buffer[6:0],buffer[7]};
bit_cnt <= bit_cnt + 1'd1;
end
else begin
state <= s4_word_addr_high_ack;
bit_cnt <= 4'd0;
r_rd_sel <= SDA_READ; // sda receive data
end
end
end
s4_word_addr_high_ack:begin
if(scl)begin
r_rd_sel <= SDA_WRITE;
if(sda == 1'd0)begin
state <= s5_word_addr_low;
buffer <= 8'h01;
end
else begin
state <= s0_idle_start;
//state <= s5_word_addr_low;
end
end
end
s5_word_addr_low :begin
if(!scl)begin // if scl is low level
if(bit_cnt < 4'd8)begin
sda_buffer <= buffer[7];
buffer <= {buffer[6:0],buffer[7]};
bit_cnt <= bit_cnt + 1'd1;
end
else begin
state <= s6_word_addr_low_ack;
bit_cnt <= 4'd0;
sda_buffer <= 1'd1;
r_rd_sel <= SDA_READ; // sda receive data
end
end
end
s6_word_addr_low_ack :begin
if(scl)begin
r_rd_sel <= SDA_WRITE;
if(sda == 1'd0)begin
buffer <= 8'h53; // character "S"
if(I2C_WorR == I2C_WRITE)begin // write iic
state <= s7_data_write;
sda_buffer <= 1'd0; // put down sda in order to prevent the generation of stop signal
end
else begin // iic read
state <= s9_read_start;
sda_buffer <= 1'd1; // put down sda in order to generate a start signal
end
end
else begin
state <= s0_idle_start;
// if(rw_en == 2'b01)begin
// state <= s7_data_write;
// end
// else begin
// state <= s9_read_start;
// end
end
end
end
s7_data_write :begin
if(!scl)begin // scl low level
if(bit_cnt < 4'd8)begin
bit_cnt <= bit_cnt +1'd1;
sda_buffer <= buffer[7];
buffer <= {buffer[6:0],buffer[7]};
end
else begin
state <= s8_data_write_ack;
bit_cnt <= 4'd0;
r_rd_sel <= SDA_READ;
sda_buffer <= 1'd0;
end
end
end
s8_data_write_ack :begin
if(scl)begin
r_rd_sel <= SDA_WRITE;
sda_buffer <= 1'd0;
if(sda == 1'd0)begin
state <= s13_stop;
end
else begin
// state <= s13_stop;
state <= s0_idle_start;
end
end
end
s9_read_start :begin
if(scl)begin // scl high level
sda_buffer <= 1'd0;
state <= s10_read_device_addr;
buffer <= 8'b1010_0001;
end
end
s10_read_device_addr :begin
if(!scl)begin // if scl is low level
if(bit_cnt < 4'd8)begin
r_rd_sel <= SDA_WRITE;
sda_buffer <= buffer[7];
buffer <= {buffer[6:0],buffer[7]};
bit_cnt <= bit_cnt + 1'b1;
end
else begin
state <= s11_read_decice_ack;
bit_cnt <= 4'd0;
r_rd_sel <= SDA_READ; // sda receive data
end
end
end
s11_read_decice_ack :begin
if(scl)begin
r_rd_sel <= SDA_READ;
if(sda == 1'd0)begin
state <= s12_data_read;
end
else begin
// state <= s12_data_read;
state <= s0_idle_start;
end
end
end
s12_data_read :begin
if(scl)begin
if(bit_cnt < 4'd8)begin
bit_cnt <= bit_cnt + 1'd1;
rd_data[7-bit_cnt] <= sda;
end
else begin
bit_cnt <= 4'd0;
state <= s13_stop;
r_rd_sel <= SDA_WRITE;
sda_buffer <= 1'd0;
end
end
end
s13_stop :begin
if(scl)begin
state <= s0_idle_start;
sda_buffer <= 1'd1;
end
end
default:;
endcase
end
end
用sigaltap观察波形