同步半双工、飞利浦公司设计。
IIC总线上的所有数据都是以字节传输的,发送端每次发送一个字节,就必须在第9个脉冲周期释放SDA,等待接收端反馈一个应答信号。应答信号为低电平,称为有效应答位(ACK)。
通信双方的回应信号,在时钟高电平期间,数据引脚为低电平。
应答信号为高电平时,称为非应答位(NACK)。
通常用于主机发送给从机的信号,在时钟高电平期间,数据引脚为高电平。
使从机内的存储单元地址指针指向我们想要读取的存储单元地址处,首先发送了一次Dummy Write也就是虚写操作,只所以称为虚写,是因为我们并不是真的要写数据,而是通过这种虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,就可以以当前地址读的方式读数据了
sda在时钟低电平期间更新数据
在时钟高电平期间采集数据
支持最大400K的频率,跟芯片的供电电压相关,电压越大,支持的时钟频率也就越大
起始位分为高电平持续时间和低电平持续时间,两者的时间长度都为时钟周期的1/4。
在不同的工作电压下,需要维持的时间也是不同的。
停止位也是对应有不同的持续时间
需要重点关注SDA引脚数据变化的位置,发生在时钟引脚SCL的低电平期间
每个IIC设备的地址有一段地址是固定的,有一段是根据外围的引脚设置,最后一个bit(R/W)设置本次控制信号是读还是写,1表示读操作,0表示写操作。
发送控制字节的时候先发送地址的高位。
固定的地址信息“1010”,是IIC接口的EEPROM固定的地址头部信息。
摄像头的地址全都是固定的,没有引脚配置选项。
每次写入一个字节(8bit)的数据。
主机在数据信号发送完成之后,发送应答信号给从机,并且立即发送停止位信号。
发送一次地址信息,一直传输数据存储数据,直到发送停止信号。
读取任意地址的数据,读取一次数据之后,主机发送无应答信号停止数据传输
写入一个地址信号之后,连续获取该地址之后的数据,直到主机发送无应答信号(在随机读的基础上),从机才会停止发送数据。
通过IIC协议完成对eeprom的读写
`include "param.v"
module eeprom_rw #(parameter WR_LEN = 3,RD_LEN = 4)(
input clk ,
input rst_n ,
input [7:0] din ,
input din_vld ,
input rd_en ,
output [7:0] dout ,//控制器输出数据
output dout_vld,
input busy ,
output req ,
output [3:0] cmd ,
output [7:0] wr_data ,
input [7:0] rd_data ,
input done //传输完成标志
);
//状态机参数
localparam IDLE = 6'b00_0001 ,
WR_REQ = 6'b00_0010 ,//写传输 发送请求、命令、数据
WAIT_WR = 6'b00_0100 ,//等待一个字节传完
RD_REQ = 6'b00_1000 ,//读传输 发送请求、命令、数据
WAIT_RD = 6'b01_0000 ,//等待一个自己传完
DONE = 6'b10_0000 ;//一次读或写完成
//信号定义
reg [5:0] state_c ;
reg [5:0] state_n ;
reg [7:0] cnt_byte ;//数据传输 字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg tx_req ;//请求
reg [3:0] tx_cmd ;
reg [7:0] tx_data ;
reg [8:0] wr_addr ;//写eeprom地址
reg [8:0] rd_addr ;//读eeprom地址
wire wfifo_rd ;
wire wfifo_wr ;
wire wfifo_empty ;
wire wfifo_full ;
wire [7:0] wfifo_qout ;
wire [5:0] wfifo_usedw ;
wire rfifo_rd ;
wire rfifo_wr ;
wire rfifo_empty ;
wire rfifo_full ;
wire [7:0] rfifo_qout ;
wire [5:0] rfifo_usedw ;
reg rd_flag ;
reg [7:0] user_data ;
reg user_data_vld ;
wire idle2wr_req ;
wire wr_req2wait_wr ;
wire wait_wr2wr_req ;
wire wait_wr2done ;
wire idle2rd_req ;
wire rd_req2wait_rd ;
wire wait_rd2rd_req ;
wire wait_rd2done ;
wire done2idle ;
//状态机设计
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end
always @(*) begin
case(state_c)
IDLE :begin
if(idle2wr_req)
state_n = WR_REQ ;
else if(idle2rd_req)
state_n = RD_REQ ;
else
state_n = state_c ;
end
WR_REQ :begin
if(wr_req2wait_wr)
state_n = WAIT_WR ;
else
state_n = state_c ;
end
WAIT_WR :begin
if(wait_wr2wr_req)
state_n = WR_REQ ;
else if(wait_wr2done)
state_n = DONE ;
else
state_n = state_c ;
end
RD_REQ :begin
if(rd_req2wait_rd)
state_n = WAIT_RD ;
else
state_n = state_c ;
end
WAIT_RD :begin
if(wait_rd2rd_req)
state_n = RD_REQ ;
else if(wait_rd2done)
state_n = DONE ;
else
state_n = state_c ;
end
DONE :begin
if(done2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end
assign idle2wr_req = state_c==IDLE && (wfifo_usedw > WR_LEN-2);
assign wr_req2wait_wr = state_c==WR_REQ && (1'b1);
assign wait_wr2wr_req = state_c==WAIT_WR && (done & cnt_byte < WR_LEN-1);
assign wait_wr2done = state_c==WAIT_WR && (end_cnt_byte);
assign idle2rd_req = state_c==IDLE && (rd_en);
assign rd_req2wait_rd = state_c==RD_REQ && (1'b1);
assign wait_rd2rd_req = state_c==WAIT_RD && (done & cnt_byte < RD_LEN-1);
assign wait_rd2done = state_c==WAIT_RD && (end_cnt_byte);
assign done2idle = state_c==DONE && (1'b1);
//cnt_byte
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_byte <= 0;
end
else if(add_cnt_byte) begin
if(end_cnt_byte)
cnt_byte <= 0;
else
cnt_byte <= cnt_byte+1 ;
end
end
assign add_cnt_byte = (state_c==WAIT_WR | state_c==WAIT_RD) & done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c==WAIT_WR)?
(WR_LEN-1):(RD_LEN-1));
//输出
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
TX(1'b0,4'd0,8'd0);
end
else if(state_c==WR_REQ)begin
case(cnt_byte)
0 :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,wr_addr[8],`WR_BIT});//发起始位、写控制字
1 :TX(1'b1,`CMD_WRITE,wr_addr[7:0]); //发 写地址
WR_LEN-1 :TX(1'b1,{`CMD_WRITE | `CMD_STOP},wfifo_qout); //最后一个字节时 发数据、停止位
default :TX(1'b1,`CMD_WRITE,wfifo_qout); //中间发数据(如果有)
endcase
end
else if(state_c==RD_REQ)begin
case(cnt_byte)
0 :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`WR_BIT});//发起始位、写控制字
1 :TX(1'b1,`CMD_WRITE,rd_addr[7:0]); //发 读地址
2 :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,rd_addr[8],`RD_BIT});//发起始位、读控制字
RD_LEN-1 :TX(1'b1,{`CMD_READ | `CMD_STOP},0); //最后一个字节时 读数据、发停止位
default :TX(1'b1,`CMD_READ,0); //中间读数据(如果有)
endcase
end
else begin
TX(1'b0,tx_cmd,tx_data);
end
end
//用task发送请求、命令、数据(地址+数据)
task TX;
input req ;
input [3:0] command ;
input [7:0] data ;
begin
tx_req = req;
tx_cmd = command;
tx_data = data;
end
endtask
//wr_addr rd_addr
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
wr_addr <= 0;
end
else if(wait_wr2done)begin
wr_addr <= wr_addr + WR_LEN-2;
end
end
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_addr <= 0;
end
else if(wait_rd2done)begin
rd_addr <= rd_addr + RD_LEN - 3;
end
end
//rd_flag
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_flag <= 1'b0;
end
else if(~rfifo_empty)begin
rd_flag <= 1'b1;
end
else begin
rd_flag <= 1'b0;
end
end
//user_data user_data_vld
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
user_data <= 0;
user_data_vld <= 0;
end
else begin
user_data <= rfifo_qout;
user_data_vld <= rfifo_rd;
end
end
//输出
assign req = tx_req ;
assign cmd = tx_cmd ;
assign wr_data = tx_data;
assign dout = user_data;//控制器输出数据
assign dout_vld= user_data_vld;
//fifo例化
wrfifo u_wrfifo (
.aclr (~rst_n ),
.clock (clk ),
.data (din ),
.rdreq (wfifo_rd ),
.wrreq (wfifo_wr ),
.empty (wfifo_empty),
.full (wfifo_full ),
.q (wfifo_qout ),
.usedw (wfifo_usedw)
);
assign wfifo_rd = state_c==WAIT_WR && done && cnt_byte > 1;
assign wfifo_wr = ~wfifo_full & din_vld;
rdfifo u_rdfifo (
.aclr (~rst_n ),
.clock (clk ),
.data (rd_data ),
.rdreq (rfifo_rd ),
.wrreq (rfifo_wr ),
.empty (rfifo_empty),
.full (rfifo_full ),
.q (rfifo_qout ),
.usedw (rfifo_usedw)
);
assign rfifo_wr = ~rfifo_full && state_c==WAIT_RD && cnt_byte > 2 && done;
assign rfifo_rd = rd_flag && ~busy;
endmodule
`include "param.v"
module i2c_master(
input clk ,
input rst_n ,
input req ,
input [3:0] cmd ,
input [7:0] din ,
output [7:0] dout ,
output done ,
output i2c_scl ,
input i2c_sda_i ,
output i2c_sda_o ,
output i2c_sda_oe
);
//状态机参数定义
localparam IDLE = 7'b000_0001,
START = 7'b000_0010,
WRITE = 7'b000_0100,
RACK = 7'b000_1000,
READ = 7'b001_0000,
SACK = 7'b010_0000,
STOP = 7'b100_0000;
//信号定义
reg [6:0] state_c ;
reg [6:0] state_n ;
reg [8:0] cnt_scl ;//产生i2c时钟
wire add_cnt_scl ;
wire end_cnt_scl ;
reg [3:0] cnt_bit ;//传输数据 bit计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_num ;
reg scl ;//输出寄存器
reg sda_out ;
reg sda_out_en ;
reg [7:0] rx_data ;
reg rx_ack ;
reg [3:0] command ;
reg [7:0] tx_data ;//发送数据
wire idle2start ;
wire idle2write ;
wire idle2read ;
wire start2write ;
wire start2read ;
wire write2rack ;
wire read2sack ;
wire rack2stop ;
wire sack2stop ;
wire rack2idle ;
wire sack2idle ;
wire stop2idle ;
//状态机
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end
always @(*) begin
case(state_c)
IDLE :begin
if(idle2start)
state_n = START ;
else if(idle2write)
state_n = WRITE ;
else if(idle2read)
state_n = READ ;
else
state_n = state_c ;
end
START :begin
if(start2write)
state_n = WRITE ;
else if(start2read)
state_n = READ ;
else
state_n = state_c ;
end
WRITE :begin
if(write2rack)
state_n = RACK ;
else
state_n = state_c ;
end
RACK :begin
if(rack2stop)
state_n = STOP ;
else if(rack2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
READ :begin
if(read2sack)
state_n = SACK ;
else
state_n = state_c ;
end
SACK :begin
if(sack2stop)
state_n = STOP ;
else if(sack2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
STOP :begin
if(stop2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end
assign idle2start = state_c==IDLE && (req && (cmd&`CMD_START));
assign idle2write = state_c==IDLE && (req && (cmd&`CMD_WRITE));
assign idle2read = state_c==IDLE && (req && (cmd&`CMD_READ ));
assign start2write = state_c==START && (end_cnt_bit && (command&`CMD_WRITE));
assign start2read = state_c==START && (end_cnt_bit && (command&`CMD_READ ));
assign write2rack = state_c==WRITE && (end_cnt_bit);
assign read2sack = state_c==READ && (end_cnt_bit);
assign rack2stop = state_c==RACK && (end_cnt_bit && ((command&`CMD_STOP ) || rx_ack));
assign sack2stop = state_c==SACK && (end_cnt_bit && (command&`CMD_STOP ));
assign rack2idle = state_c==RACK && (end_cnt_bit && ((command&`CMD_STOP ) == 0));
assign sack2idle = state_c==SACK && (end_cnt_bit && (command&`CMD_STOP ) == 0);
assign stop2idle = state_c==STOP && (end_cnt_bit);
//计数器
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_scl <= 0;
end
else if(add_cnt_scl) begin
if(end_cnt_scl)
cnt_scl <= 0;
else
cnt_scl <= cnt_scl+1 ;
end
end
assign add_cnt_scl = (state_c != IDLE);
assign end_cnt_scl = add_cnt_scl && cnt_scl == (`SCL_PERIOD)-1 ;
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
cnt_bit <= 0;
end
else if(add_cnt_bit) begin
if(end_cnt_bit)
cnt_bit <= 0;
else
cnt_bit <= cnt_bit+1 ;
end
end
assign add_cnt_bit = (end_cnt_scl);
assign end_cnt_bit = add_cnt_bit && cnt_bit == (bit_num)-1 ;
always @(*)begin
if(state_c == WRITE | state_c == READ) begin
bit_num = 8;
end
else begin
bit_num = 1;
end
end
//command
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
command <= 0;
end
else if(req)begin
command <= cmd;
end
end
//tx_data
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
tx_data <= 0;
end
else if(req)begin
tx_data <= din;
end
end
//scl
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
scl <= 1'b1;
end
else if(idle2start | idle2write | idle2read)begin//开始发送时,拉低
scl <= 1'b0;
end
else if(add_cnt_scl && cnt_scl == `SCL_HALF-1)begin//半个周期时拉低
scl <= 1'b1;
end
else if(end_cnt_scl && ~stop2idle)begin //若继续发送下一bit则继续拉低,否则保持高
scl <= 1'b0;
end
end
//sda_out
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
sda_out <= 1'b1;
end
else if(state_c == START)begin //发起始位
if(cnt_scl == `LOW_HLAF)begin //时钟低电平时拉高sda总线
sda_out <= 1'b1;
end
else if(cnt_scl == `HIGH_HALF)begin //时钟高电平时拉低sda总线
sda_out <= 1'b0; //保证从机能检测到起始位
end
end
else if(state_c == WRITE && cnt_scl == `LOW_HLAF)begin //scl低电平时发送数据 并串转换
sda_out <= tx_data[7-cnt_bit];
end
else if(state_c == SACK && cnt_scl == `LOW_HLAF)begin //发应答位
sda_out <= (command&`CMD_STOP)?1'b1:1'b0;
end
else if(state_c == STOP)begin //发停止位
if(cnt_scl == `LOW_HLAF)begin //时钟低电平时拉低sda总线
sda_out <= 1'b0;
end
else if(cnt_scl == `HIGH_HALF)begin //时钟高电平时拉高sda总线
sda_out <= 1'b1; //保证从机能检测到停止位
end
end
end
//sda_out_en 总线输出数据使能
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
sda_out_en <= 1'b0;
end
else if(idle2start | idle2write | read2sack | rack2stop)begin
sda_out_en <= 1'b1;
end
else if(idle2read | start2read | write2rack | stop2idle)begin
sda_out_en <= 1'b0;
end
end
//rx_data 接收读入的数据
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rx_data <= 0;
end
else if(state_c == READ && cnt_scl == `HIGH_HALF)begin
rx_data[7-cnt_bit] <= i2c_sda_i; //串并转换
end
end
//rx_ack
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rx_ack <= 1'b1;
end
else if(state_c == RACK && cnt_scl == `HIGH_HALF)begin
rx_ack <= i2c_sda_i;
end
end
//输出信号
assign i2c_scl = scl ;
assign i2c_sda_o = sda_out ;
assign i2c_sda_oe = sda_out_en ;
assign dout = rx_data;
assign done = rack2idle | sack2idle | stop2idle;
endmodule
按键消抖
串口
【FPGA】FPGA基于i2c的eeprom读写
https://github.com/IvanXiang/FPGA_IIC_EEPROM