最近遇到一个BUG,跟IIC通信有关,所以借这个机会总结一下IIC总线协议
1.引脚接口介绍
1.A0,A1,A2为24LC64的片选信号,IIC总线最多可以挂载8个IIC接口器件,通过对A0,A1,A2寻址,可以实现对不同的EEPROM操作
2.WP为读写使能信号,当WP悬空或者接地,EEPROM可读可写,当WP接电源,EEPROM只能读不能写。因为我们要对EEPROM写,所以这里WP信号悬空
3.SCL为时钟信号线,最高频率400Khz
4.SDA为数据线,双向线(inout),当为in时,数据通过SDA写到EEPROM。为out时,eeprom读出来的数据通过SDA传到外面
2.接口时序
IIC读写时序分为随机读写和页读写,这里只研究随机读写
2.1 写时序
写操作步骤
1.发送启动信号
2.发送控制字写(1010_A0A1A2_0 )
3.EEPROM发送应答信号ACK
4.发送高字节写地址
5.EEPROM发送应答信号ACK
6.发送低字节写地址
7.EEPROM发送应答信号ACK
8.发送8bit写数据
9.EEPROM发送应答信号ACK
10.发送停止信号
2.2 读时序
读操作信号
1.发送启动信号
2.发送控制字写(1010_A0A1A2_0)
3.EEPROM发送应答信号ACK
4.发送高字节读地址
5.EEPROM发送应答信号ACK
6.发送低字节读地址
7.EEPROM发送应答信号ACK
8.发送启动信号
9.发送控制字读(1010_A0A1A2_1)
10.EEPROM发送应答信号ACK
11.读取一个8bit数据
12..EEPROM发送NO ACK信号
13.发送停止信号
3.操作步骤解析
3.1启动信号
SCL 保持高电平期间 ,如果 SDA 出现由高到低的跳变,代表启动信号
3.2控制字
1010_A0A1A2X,
1.1010为EEPROM信号标识,为一组固定的序列
2.A0A1A2为片选信号,由于只有一个flash,所以A0A1A2在这里全为0
3.最后一个bit X,为0时代表写,为1时代表读。
3.3地址
24LC64表示有64Kbit的存储空间,需要13位地址线寻址。但是IIC是以字节的实行操作的,所以需要13位地址线扩展成16位,高3位随意填0或者1,习惯填0
3.4应答信号与非应答信号
应答信号和非应答信号都是由数据接收方(EEPROM)发出的,当SCL为高电平时候,如果检测到SDA为低电平,说明有应答信号。如果检测到SDA为高电平,说明有非应答信号。所以在应答时钟周期的时候,我们要释放SDA信号线,让EEPROM通过SDA发送一个低电平或者高电平过来。
3.5停止信号
SCL 保持高电平期间 ,如果 SDA 出现由低到高的跳变,代表停止信号
3.6 数据传输
由于IIC总线协议的启动和停止信号都是在SCL高电平期间发生跳变,这就决定了其数据只能在SCL低电平期间发生改变,不然会被当做启动或者停止信号处理。在SCL为高电平期间,数据必须保持稳定。即在SCL低电平的时候改变数据,高电平的时候采集数据
4关键代码解析
4.1状态机设置
4.2 sda信号线控制
由于sda是inout型,读写都是有这根线控制。所以我们要有一个信号,来指示sda信号线什么时候写,什么时候是读。
当link_sda信号为1的时候,指示sda信号写。这时候我们把需要写的数据一个bit一个bit的赋给中间变量sda_buf信号,该信号经过sda信号线把数据写进flash
当link_sda信号为0的时候,指示sda信号读。
完整代码如下
module iic_control( input wire sclk, input wire reset, input wire key_wr, input wire key_rd, output reg scl, inout wire sda, output wire[7:0] dataout, output reg led ); parameter IDLE = 14'b00_0000_0000_0000, start1 = 14'b00_0000_0000_0001, control_byte1 = 14'b00_0000_0000_0010, ack1 = 14'b00_0000_0000_0100, high_addr_byte = 14'b00_0000_0000_1000, ack2 = 14'b00_0000_0001_0000, low_addr_byte = 14'b00_0000_0010_0000, ack3 = 14'b00_0000_0100_0000, start2 = 14'b00_0000_1000_0000, control_byte2 = 14'b00_0001_0000_0000, ack4 = 14'b00_0010_0000_0000, transfer_data = 14'b00_0100_0000_0000, ack5 = 14'b00_1000_0000_0000, no_ack = 14'b01_0000_0000_0000, stop = 14'b10_0000_0000_0000; reg[13:0] state; reg[6:0] cnt; //分频计数 reg link_sda; //总线开关 reg sda_buf; //总线数据缓存器 reg wr; //写使能 reg rd; //读使能 reg[7:0] data; reg[3:0] cnt_num; reg[7:0] result; assign sda=(link_sda)?sda_buf:1'hz; assign dataout = result; always@(posedge sclk or negedge reset) if(!reset) cnt <= 7'd0; else if(cnt==7'd124) cnt <= 7'd0; else cnt <= cnt + 1'b1; always@(posedge sclk or negedge reset) if(!reset) scl <= 1'b0; else if(cnt==7'd30) scl <= 1'b1; else if(cnt==8'd93) scl <= 1'b0; always@(posedge sclk or negedge reset) if(!reset==1) begin state <= IDLE; link_sda <= 0; sda_buf <= 0; data <= 0; cnt_num <= 4'd0; result <= 8'd0; led <= 1; end else case(state) IDLE: begin if(!key_wr) wr <= 1; if(!key_rd) rd <= 1; if((wr==1)||(rd==1)&&(!scl==1)) begin state <= start1; link_sda <= 1; sda_buf <= 1; data <= 8'b10100000; //写控制字准备 end end start1: begin if((scl==1)&&(cnt==7'd61)) begin state <= control_byte1; link_sda <= 1; sda_buf <= 0; end end control_byte1: begin if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack1; link_sda <= 0; cnt_num <= 0; end end ack1: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= high_addr_byte; // data <= 8'b00000000; //高字节地址准备 // link_sda <= 1; // end if((scl==1)&&(cnt==7'd61)) begin state <= high_addr_byte; data <= 8'b00000000; //高字节地址准备 // link_sda <= 1; end end high_addr_byte: begin if(cnt==7'd124) begin link_sda <= 1; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack2; link_sda <= 0; cnt_num <= 0; end end ack2: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= low_addr_byte; // data <= 8'b00000000; //低字节地址准备 // link_sda <= 1; // end if((scl==1)&&(cnt==7'd61)) begin state <= low_addr_byte; data <= 8'b00000000; //低字节地址准备 // link_sda <= 1; end end low_addr_byte: begin if(cnt==7'd124) begin link_sda <= 1; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack3; link_sda <= 0; cnt_num <= 0; end end ack3: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // link_sda <= 1; // if(wr==1) // begin // state <= transfer_data; // data <= 8'b10101010;//准备想要写入的数据 // end // if(rd==1) // begin // state <= start2; // sda_buf <= 1;//准备再次发启动信号 // end // end if((scl==1)&&(cnt==7'd61)) begin // link_sda <= 1; if(wr==1) begin state <= transfer_data; data <= 8'b10101010;//准备想要写入的数据 end if(rd==1) begin state <= start2; sda_buf <= 1;//准备再次发启动信号 end end end start2: begin if(cnt==7'd124) begin link_sda <= 1; end if((scl==1)&&(cnt==7'd61)) begin state <= control_byte2; sda_buf <= 0; data <= 8'b10100001; //读控制字准备 end end control_byte2: begin if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack4; link_sda <= 0; cnt_num <= 0; end end ack4: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= transfer_data; // data <= 8'b00000000; //低字节地址准备 // link_sda <= 0; // end if((scl==1)&&(cnt==7'd61)) begin state <= transfer_data; data <= 8'b00000000; //低字节地址准备 // link_sda <= 0; end end transfer_data: begin if(wr==1) begin if(cnt==7'd124) begin link_sda <= 1; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack5; link_sda <= 0; cnt_num <= 0; wr <= 0; led <= 0; end end if(rd==1) begin if(cnt==7'd124) begin link_sda <= 0; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; result <= {result[6:0],sda}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= no_ack; link_sda <= 1; cnt_num <= 0; sda_buf <= 1; rd <= 0; end end end ack5: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= stop; // link_sda <= 1; // end if((scl==1)&&(cnt==7'd61)) begin state <= stop; // link_sda <= 1; end end no_ack: begin if(cnt==7'd124) begin state <= stop; link_sda <= 1; sda_buf <= 0; end end stop: begin if(cnt==7'd124) begin link_sda <= 1; end if((scl==1)&&(cnt==7'd61)) begin sda_buf <= 1; state <= IDLE; end end default:state<=0; endcase endmodule
说明:由于仿真中没有嵌入EEPROM仿真模型,因此,无法给出ACK应答信号,没有应答信号,状态机就没办法继续向下跳转。所以为了完成仿真,就在代码中屏蔽了所有的ACK检测。在仿真中,只要看本该出现ACK信号的时候,sda信号是不是蓝色高组态。如果是高组态就表示仿真没有问题。在实际工程中,只需要把代码中屏蔽掉的if语句放开,把对应的if语句(仿真用的)屏蔽掉就可以直接用了