EEPROM
Electrically Erasable Programmable Read-Only Memory is a commonly used non-volatile memory (data will not be lost after power failure).
Structure diagram:
SCL: the clock pin of iic
SDA: bidirectional data pin
WP: write protection pin, low level - can be written, high level - can be read
A0, A1, A2 address pins
iic
The following is quoted from Brother Atomic:
IIC ( I2C , Inter - Integrated Circuit) is an integrated circuit bus, which is a two-wire serial bus developed by PHILIPS to connect microcontrollers and their peripherals. It is mostly used for master-slave communication between the master and the slave in the case of small data volume and short transmission distance.
The I2C bus consists of a data line SDA and a clock line SCL to form a communication line, which can be used to send data or receive data
Standard mode: 100Kbit /s
Fast Mode: 400kbit /s
High speed mode: 3.4Mbit /s
IIC is a half-duplex communication method
① idle state②Start signal③Stop signal④ Validity of data⑤ Response signal⑥Data transmission
1. Idle state
SCL: 1
SDA: 1
2. Idle state
SCL: 1
SDA: 1 — — > 0
3. Valid data status
SCL: 0— — — — — >1 — — — — — > 0
SDA: Stable Ready [Data] Stable
adopt adopt
Data needs to be ready before the rising edge of SCL arrives. and must be stable before the falling edge of the
4. Answer signal:
Every time the transmitter sends a byte, it releases the data line during the clock pulse 9 , and the receiver feeds back a response signal. When the response signal is low, it is defined as an effective response bit ( ACK for short), indicating that the receiver has successfully received the byte; when the response signal is high, it is specified as a non-acknowledgment bit ( NACK ), which generally indicates that the receiver has not successfully received the byte.
The requirement for feeding back the valid acknowledgment bit ACK is that the receiver pulls the SDA line low during the low level period before the ninth clock pulse , and ensures that it is a stable low level during the high level period of the clock. If the receiver is the master, after it receives the last byte, it sends a NACK signal to inform the controlled transmitter to end data transmission, and releases the SDA line so that the master receiver sends a stop signal P.
5. Stop state
SCL: 1
SDA: 0 — — > 1
The following EEPROM size is: internally divided into 256 pages, 32 bytes per page
So the storage size is: 256*32*8 / 1024 = 64 Kbit
The write timing is:
About four parts:
1. Device address + response bit
2. Internal high 5 address of EEPROM + response bit
3. Low 8-bit address + response bit inside EEPROM
4. 8-bit data + response bit
Why is the internal address 13 bits?
Answer: 256*32 bytes = 8192. 8191 is 13 bits
Note: AT24C64 supports page write mode
read timing
The complete timing table is as follows:
SCL | SDA | SCL | SDA | SCL | SDA | SCL | SDA | SCL | SDA | SCL | SDA | ||||||||||
1 | 1 | initial state | 0 | 1 | 1 | Register high 8-bit address | 0 | 0 | add【15】 | Register the lower 8-bit address | 0 | 0 | add【7】 | read data | 0 | 0 | write data | 0 | 0 | data【7】 | |
1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | data【7】 | 1 | 1 | ||||||||
Write/Read Device Address | 2 | 1 | 0 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | ||||||||||
3 | 0 | 0 | 3 | 0 | 3 | 0 | 3 | 0 | 3 | 0 | |||||||||||
4 | 0 | add【6】 | 4 | 0 | add【14】 | 4 | 0 | add【6】 | 4 | 0 | 4 | 0 | data【6】 | ||||||||
5 | 1 | 5 | 1 | 5 | 1 | 5 | 1 | data【6】 | 5 | 1 | |||||||||||
6 | 1 | 6 | 1 | 6 | 1 | 6 | 1 | 6 | 1 | ||||||||||||
7 | 0 | 7 | 0 | 7 | 0 | 7 | 0 | 7 | 0 | ||||||||||||
8 | 0 | add【5】 | 8 | 0 | add【13】 | 8 | 0 | add【5】 | 8 | 0 | 8 | 0 | data【5】 | ||||||||
9 | 1 | 9 | 1 | 9 | 1 | 9 | 1 | data【5】 | 9 | 1 | |||||||||||
10 | 1 | 10 | 1 | 10 | 1 | 10 | 1 | 10 | 1 | ||||||||||||
11 | 0 | 11 | 0 | 11 | 0 | 11 | 0 | 11 | 0 | ||||||||||||
12 | 0 | add【4】 | 12 | 0 | add【12】 | 12 | 0 | add【4】 | 12 | 0 | 12 | 0 | data【4】 | ||||||||
13 | 1 | 13 | 1 | 13 | 1 | 13 | 1 | data【4】 | 13 | 1 | |||||||||||
14 | 1 | 14 | 1 | 14 | 1 | 14 | 1 | 14 | 1 | ||||||||||||
15 | 0 | 15 | 0 | 15 | 0 | 15 | 0 | 15 | 0 | ||||||||||||
16 | 0 | add【3】 | 16 | 0 | add【11】 | 16 | 0 | add【3】 | 16 | 0 | 16 | 0 | data【3】 | ||||||||
17 | 1 | 17 | 1 | 17 | 1 | 17 | 1 | data【3】 | 17 | 1 | |||||||||||
18 | 1 | 18 | 1 | 18 | 1 | 18 | 1 | 18 | 1 | ||||||||||||
19 | 0 | 19 | 0 | 19 | 0 | 19 | 0 | 19 | 0 | ||||||||||||
20 | 0 | add【2】 | 20 | 0 | add【10】 | 20 | 0 | add【2】 | 20 | 0 | 20 | 0 | data【2】 | ||||||||
21 | 1 | 21 | 1 | 21 | 1 | 21 | 1 | data【2】 | 21 | 1 | |||||||||||
22 | 1 | 22 | 1 | 22 | 1 | 22 | 1 | 22 | 1 | ||||||||||||
23 | 0 | 23 | 0 | 23 | 0 | 23 | 0 | 23 | 0 | ||||||||||||
24 | 0 | add【1】 | 24 | 0 | add【9】 | 24 | 0 | add【1】 | 24 | 0 | 24 | 0 | data【1】 | ||||||||
25 | 1 | 25 | 1 | 25 | 1 | 25 | 1 | data【1】 | 25 | 1 | |||||||||||
26 | 1 | 26 | 1 | 26 | 1 | 26 | 1 | 26 | 1 | ||||||||||||
27 | 0 | 27 | 0 | 27 | 0 | 27 | 0 | 27 | 0 | ||||||||||||
28 | 0 | add【1】 | 28 | 0 | add【8】 | 28 | 0 | add【0】 | 28 | 0 | 28 | 0 | data【0】 | ||||||||
29 | 1 | 29 | 1 | 29 | 1 | 29 | 1 | data【0】 | 29 | 1 | |||||||||||
30 | 1 | 30 | 1 | 30 | 1 | 30 | 1 | 30 | 1 | ||||||||||||
31 | 0 | 31 | 0 | 31 | 0 | 31 | 0 | 31 | 0 | ||||||||||||
32 | 0 | 0/1 | 32 | 0 | Answer from the machine | 32 | 0 | Answer from the machine | 32 | 0 | answer | 32 | 0 | answer | |||||||
33 | 1 | 33 | 1 | 33 | 1 | 33 | 1 | 33 | 1 | ||||||||||||
34 | 1 | 34 | 1 | 34 | 1 | 34 | 1 | 34 | 1 | ||||||||||||
35 | 0 | 35 | 0 | 35 | 0 | 35 | 0 | 35 | 0 | ||||||||||||
36 | 0 | Answer from the machine | |||||||||||||||||||
37 | 1 | ||||||||||||||||||||
38 | 1 | ||||||||||||||||||||
39 | 0 | Finish | |||||||||||||||||||
We can draw a conclusion:
Write something while SCL is 0, 0
Read data when SCL is 0, 1
code:
module i2c_dri
#(// slave address(器件地址),放此处方便参数传递
parameter SLAVE_ADDR = 7'b1010000 ,
parameter CLK_FREQ = 26'd50_000_000, // i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 // I2C的SCL时钟频率
)(
//global clock
input clk , // i2c_dri模块的驱动时钟(CLK_FREQ)
input rst_n , // 复位信号
//i2c interface
input i2c_exec , // I2C触发执行信号
input bit_ctrl , // 字地址位控制(16b/8b)
input i2c_rh_wl , // I2C读写控制信号
input [15:0] i2c_addr , // I2C器件内地址
input [ 7:0] i2c_data_w , // I2C要写的数据
output reg [ 7:0] i2c_data_r , // I2C读出的数据
output reg i2c_done , // I2C一次操作完成
output reg scl , // I2C的SCL时钟信号
inout sda , // I2C的SDA信号
//user interface
output reg dri_clk // 驱动I2C操作的驱动时钟,1us
);
//localparam define
localparam st_idle = 8'b0000_0001; // 空闲状态
localparam st_sladdr = 8'b0000_0010; // 发送器件地址(slave address)
localparam st_addr16 = 8'b0000_0100; // 发送16位字地址
localparam st_addr8 = 8'b0000_1000; // 发送8位字地址
localparam st_data_wr = 8'b0001_0000; // 写数据(8 bit)
localparam st_addr_rd = 8'b0010_0000; // 发送器件地址读
localparam st_data_rd = 8'b0100_0000; // 读数据(8 bit)
localparam st_stop = 8'b1000_0000; // 结束I2C操作
//reg define
reg sda_dir ; // I2C数据(SDA)方向控制
reg sda_out ; // SDA输出信号
reg st_done ; // 状态结束
reg wr_flag ; // 写标志
reg [ 6:0] cnt ; // 计数
reg [ 7:0] cur_state ; // 状态机当前状态
reg [ 7:0] next_state ; // 状态机下一状态
reg [15:0] addr_t ; // 地址
reg [ 7:0] data_r ; // 读取的数据
reg [ 7:0] data_wr_t ; // I2C需写的数据的临时寄存
reg [ 9:0] clk_cnt ; // 分频时钟计数
//wire define
wire sda_in ; // SDA输入信号
wire [8:0] clk_divide ; // 模块驱动时钟的分频系数
//*****************************************************
//** main code
//*****************************************************
//SDA控制
assign sda = sda_dir ? sda_out : 1'bz; // SDA数据输出或高阻
assign sda_in = sda ; // SDA数据输入
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd3;// 模块驱动时钟的分频系数,25
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0) begin
dri_clk <= 1'b1;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
if(rst_n == 1'b0)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//组合逻辑判断状态转移条件
always @( * ) begin
// next_state = st_idle;
case(cur_state)
st_idle: begin // 空闲状态
if(i2c_exec) begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done) begin
if(bit_ctrl) // 判断是16位还是8位字地址
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin // 写16位字地址
if(st_done) begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8: begin // 8位字地址
if(st_done) begin
if(wr_flag==1'b0) // 读写判断
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr: begin // 写数据(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd: begin // 写地址以进行读数据
if(st_done) begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd: begin // 读取数据(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin // 结束I2C操作
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default: next_state= st_idle;
endcase
end
//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
//复位初始化
if(rst_n == 1'b0) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r <= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin // 空闲状态
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin
wr_flag <= i2c_rh_wl ;
addr_t <= i2c_addr ;
data_wr_t <= i2c_data_w;
end
end
st_sladdr: begin // 写地址(器件地址和字地址)
case(cnt)
7'd1 : sda_out <= 1'b0; // 开始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; // 传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; // 0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0; // 从机应答
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15]; // 传送字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; // 从机应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7]; // 字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; // 从机应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin // 写数据(8 bit)
case(cnt)
7'd0: begin
sda_out <= data_wr_t[7]; // I2C写8位数据
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; // 从机应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr_rd: begin // 写地址以进行读数据
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; // 重新开始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; // 传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; // 1:读
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0; // 从机应答
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin // 读取数据(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1; // 非应答
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
st_stop: begin // 结束I2C操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1; // 结束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; // 向上层模块传递I2C结束信号
end
default : ;
endcase
end
endcase
end
end
endmodule
Here are some tutorials for using signal ii: