I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL
这里以一个byte的读写为例
时序图:
通过时序图可知,IIC读的时候需要先完成写的控制字和地址的命令,因为读的部分和写的部分有重复,所以这里的IIC控制模块我使用状态机来完成的,划分状态时,写时序的start—ack_low_addr这段为复用状态
控制命令和状态的定义
//instru
parameter
WR_CTRL_DATA= 8'hA0,
HIGH_DATA = 8'h00,
LOW_DATA = 8'h05,
DATA_WR = 8'hAf,
RD_CTRL_DATA= 8'hA1;
//state
parameter
WR_START = 15'b000_0000_0000_0001,
WR_CTRL_BYTE= 15'b000_0000_0000_0010,
ACK_WR_CTRL = 15'b000_0000_0000_0100,
HIGH_ADDR = 15'b000_0000_0000_1000,
ACK_HIGH = 15'b000_0000_0001_0000,
LOW_ADDR = 15'b000_0000_0010_0000,
ACK_LOW = 15'b000_0000_0100_0000,
WR_DATA = 15'b000_0000_1000_0000,
ACK_WR = 15'b000_0001_0000_0000,
RD_START = 15'b000_0010_0000_0000,
RD_CTRL_BYTE= 15'b000_0100_0000_0000,
ACK_RD_CTRL = 15'b000_1000_0000_0000,
RD_DATA = 15'b001_0000_0000_0000,
NO_ACK = 15'b010_0000_0000_0000,
STOP = 15'b100_0000_0000_0000;
用两个按键控制写和读
//----------key_flag--------------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
key_wr_state <= 1'b0;
else if(w_valid)
key_wr_state <= 1'b1;
else if(key_clr)
key_wr_state <= 1'b0;
else
key_wr_state <= key_wr_state;
always @(posedge clk or negedge rst_n)
if(!rst_n)
key_rd_state <= 1'b0;
else if(r_valid)
key_rd_state <= 1'b1;
else if(key_clr)
key_rd_state <= 1'b0;
else
key_rd_state <= key_rd_state;
iic的scl这里用的是200K
//------scl 200K------------------------------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_clk <= 'd0;
else if(key_wr_state || key_rd_state)begin
if(cnt_clk == 'd124)
cnt_clk <= 'd0;
else
cnt_clk <= cnt_clk + 1'b1;
end
else
cnt_clk <= 'd0;
always @(posedge clk or negedge rst_n)
if(!rst_n)
eeprom_scl <= 1'b1;
else if((key_wr_state || key_rd_state) && cnt_clk == 'd124)
eeprom_scl <= ~eeprom_scl;
else
eeprom_scl <= eeprom_scl;
状态开始信号由按键标志产生
//-------------start-------------------
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
wr_start <= 1'b0;
else if(w_valid || r_valid)
wr_start <= 1'b1;
else if(eeprom_scl == 1 && eeprom_sda == 1'b0 && cnt_clk == 'd124)
wr_start <= 1'b0;
else
wr_start <= wr_start;
控制字命令的
//此处建议使用case语句
//参考
alw @(posedge clk or negedge rst_n)
instru <= 'd0;
else begin
case(state)
WR_CTRL_BYTE :
HIGH_ADDR :
LOW_ADDR :
WR_DATA :
RD_CTRL_BYTE :
default :
endcase
end
//-----------------instru---------------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
instru <= 'd0;
else if(state == WR_CTRL_BYTE)
instru <= WR_CTRL_DATA;
else if(state == HIGH_ADDR)
instru <= HIGH_DATA;
else if(state == LOW_ADDR)
instru <= LOW_DATA;
else if(state == WR_DATA)
instru <= DATA_WR;
else if(state == RD_CTRL_BYTE)
instru <= RD_CTRL_DATA;
else
instru <= instru;
移位计数器:因为iic需要从机ack应答
所以这里移位计数器设置9位,1到8位为串行命令的移位输入,第9位为从机应答
//--------------shift_byte-----------------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
shift_cnt <= 'd0;
else if(shift_flag && state != RD_START && state != STOP)begin
if(shift_cnt== 'd8 &&eeprom_scl == 'd0 && cnt_clk == 'd63)
shift_cnt <= 'd0;
else if(state != WR_START && eeprom_scl == 'd0 && cnt_clk == 'd63)
shift_cnt <= shift_cnt + 'b1;
end
else
shift_cnt <=shift_cnt;
always @(posedge clk or negedge rst_n)
if(!rst_n)
shift_byte <= 'd0;
else if(shift_cnt == 'd0)
shift_byte <= instru;
else if(shift_cnt != 'd0 && cnt_clk == 'd64 && eeprom_scl == 'd0)
shift_byte <= {shift_byte[6:0],shift_byte[7]};
else
shift_byte <= shift_byte;
当写控制完成后产生stop信号,然后需要读的时候这里设置了读数据的开始信号
//-------------------rd_start_flag--------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
rd_start_flag <= 'd0;
else if(key_rd_state && state == ACK_LOW && eeprom_scl == 'd0 && cnt_clk == 'd63)
rd_start_flag <= 'd1;
else
rd_start_flag <= 'd0;
写状态完成和读状态完成后都会产生一个stop标志回到程序复位状态等待下次读写
//------------------stop_wr-----------------------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
stop_wr <= 'd0;
else if((state == ACK_WR||state == NO_ACK) && eeprom_scl =='d0&& cnt_clk == 'd63)
stop_wr <= 'd1;
else
stop_wr <= 'd0;
因为iic需要从机应答,所以仿真时应答信号可以设置为高阻态
//---------eeprom_sda----------------------------------------
always @(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
oe <= 1'b1;
else if((shift_cnt =='d8 && state != NO_ACK) || state ==RD_DATA)//
oe <= 1'b0;
else
oe <= 1'b1;
assign eeprom_sda = (oe == 1'b1)? sda_r: 1'bz;
状态机
wire key_clr;
assign key_clr = (state == STOP && eeprom_scl == 'd1 && cnt_clk == 'd123)? 1'b1:1'b0;
wire state_jump_flag;
assign state_jump_flag = (eeprom_scl == 0 && cnt_clk == 'd63 && shift_cnt=='d7)? 1'b1:1'b0;
wire ack_jump;
assign ack_jump = (shift_cnt=='d8 && eeprom_scl == 0 && cnt_clk == 'd63)? 1'b1:1'b0;
//----------------state_ctrl------------------------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
state <= WR_START;
else begin
case(state)
//1-------------------------wr_state----------------------
WR_START : if(shift_flag && eeprom_scl == 0 && cnt_clk == 'd63)
state <= WR_CTRL_BYTE;
else
state <= WR_START;
//2
WR_CTRL_BYTE : if(state_jump_flag)
state <= ACK_WR_CTRL;
else
state <= WR_CTRL_BYTE;
//4
ACK_WR_CTRL : if(ack_jump)
state <= HIGH_ADDR;
else
state <= ACK_WR_CTRL;
//8
HIGH_ADDR : if(state_jump_flag)
state <= ACK_HIGH;
else
state <= HIGH_ADDR;
//10
ACK_HIGH : if(ack_jump)
state <= LOW_ADDR;
else
state <= ACK_HIGH;
//20
LOW_ADDR : if(state_jump_flag)
state <= ACK_LOW;
else
state <= LOW_ADDR;
//40
ACK_LOW : if(ack_jump)begin
if(key_wr_state)
state <= WR_DATA;
else if(key_rd_state)
state <= RD_START;
end
else
state <= ACK_LOW;
//
WR_DATA : if(state_jump_flag)
state <= ACK_WR;
else
state <= WR_DATA;
//
ACK_WR : if(ack_jump)
state <= STOP;
else
state <= ACK_WR;
//---------------------rd_state--------------------------
RD_START : if(shift_flag && eeprom_scl == 0 && cnt_clk == 'd63)
state <= RD_CTRL_BYTE;
else
state <= RD_START;
RD_CTRL_BYTE : if(state_jump_flag)
state <= ACK_RD_CTRL;
else
state <= RD_CTRL_BYTE;
ACK_RD_CTRL : if(ack_jump)
state <= RD_DATA;
else
state <= ACK_RD_CTRL;
RD_DATA : if(state_jump_flag)
state <= NO_ACK;
else
state <= RD_DATA;
NO_ACK : if(ack_jump)
state <= STOP;
else
state <= NO_ACK;
//-------------------wr_rd_stop------------------------------
STOP : if(eeprom_scl && cnt_clk == 'd124)
state <= WR_START;
else
state <= STOP;
default : state <= WR_START;
endcase
end
读数据串行数据转换
//-----------------RD_DATA--------------------------
always @(posedge clk or negedge rst_n)
if(!rst_n)
DATA_r <= 8'd0;
else if(state == RD_DATA)begin
case(shift_cnt)
4'd0 : DATA_r[7] <= eeprom_sda;
4'd1 : DATA_r[6] <= eeprom_sda;
4'd2 : DATA_r[5] <= eeprom_sda;
4'd3 : DATA_r[4] <= eeprom_sda;
4'd4 : DATA_r[3] <= eeprom_sda;
4'd5 : DATA_r[2] <= eeprom_sda;
4'd6 : DATA_r[1] <= eeprom_sda;
4'd7 : DATA_r[0] <= eeprom_sda;
default : DATA_r <= 8'd0;
endcase
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
data <= 8'd0;
else if(shift_cnt == 'd7 && eeprom_scl == 1'b0 && cnt_clk == 'd63)
data <= DATA_r;
else
data <= data;