一、IIC总线协议特点及其工作原理
I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。
1)I2C总线特点
I2C总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
I2C总线的另一个优点是,它支持多主控(multimastering),
其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
2)I2C总线工作原理
I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率 100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程
中,I2C总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。CPU发出的控制信号分为地址码和控制
量两部分,地址码用来选址,即接通需要控制的电路,确定控制的种类;控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。这样,各控制电路虽然挂
在同一条总线上,却彼此独立,互不相关。
3)总线的构成及信号类型
I2C总线在传送数据过程中共有三种类型信号,
它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单
元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
4)I2C总线操作
I2C规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。
总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。
SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。
写操作
写操作分为字节写和页面写两种操作,对于页面写根据芯片的一次装载的字节不同有所不同。
读操作
读操作有三种基本操作:当前地址读、随机读和顺序读。
5)I2C总线应用
目前有很多半导体集成电路上都集成了I2C接口。带有I2C接口的单片机有:CYGNAL的 C8051F0XX系列,三星的S3C24XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外围器
件如存储器、监控芯片等也提供I2C接口。
二、IIC之EEPROM
1)下面是EEPROM(24LC64)不同封装情况。
数据手册中关于这八个引脚的介绍:
A0, A1,A2 :片选地址输入
SDA : 单bit数据线
SLC : 时钟线(200KHZ)
WP : 接地或者悬空时,可读可写,接电源VCC时,只读不可写。
注意:EEPROM(24LC64)工作的最大时钟为400KHZ,所以我们用系统50M时钟来分频一个400KHZ。
2)I2C总线时序图
-
总线非忙状态(A段)数据线SDA 和时钟线SCL都保持高电平 -
启动数据传输(B段)当时钟线SCL为高电平时,数据线由高变低,此时认为是启动信号,有启动信号之后,后面的命令才有用。 -
停止数据传输(C段): 当时钟SCL为高电平时,数据线SDA由低到高的上升沿认为是停止信号。 -
数据有效(D段): 出现启动信号后,在时钟线SCL为高电平时,数据线是稳定的,这时候数据线的状态是稳定的,而在时钟线SCL为低电平的时候,允许数据线发生改变,每位数据占用一个时钟脉冲。 -
应答信号: 每个正在接收数据的从机EEPROM在接到一个字节后,通常需要发出一个应答信号ACK,用来告诉FPGA数据已经传输过来。此时EEPROM读写控制器必须产生一个与这个应答位相对应和联系的额外时钟脉冲。在EEPROM的读操作中,其读控制器对eeprom完成的最后一个字节产生一个高的应答为,这个信号叫做NOack(非应答位信号),随后给EEPROM一个结束信号。
控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。示意图如下:
读控制字节: 1010_0001
写控制字节: 1010_0000
.
(3) IIC总线读写时序
图 随机写
图 页面写
图 随机读
图 页面读
三 EEPROM 之随机写操作
写操作的时候就要考虑几个问题:
- 工作时钟的问题,晶振是50MHZ时钟,怎么确定时钟,在数据手册里我们发现最大时钟为400Khz,同时又要考虑到如何去保证scl为高电平的时候,sda从高到低的问题所以考虑用到两个时钟,先用晶振产生一个400Khz时钟,然后在用这个400khz时钟产生SCL时钟(200kHZ),在400Khz的上升沿的作用下,产生200Khz时钟SCL,在400Khz时钟的下降沿的作用下,记性状态的转换即数据的传输写入。
- 认识input和output之外的另一种端口三态门inout,因为要考虑到后面的应答信号的传输问题,应答信号是eeprom从机给FPGA的传输,而数据的传输是FPGA到EEPROM,所以这个sda端口应该是双向的,双向的就要考虑三态门。
当ENB==1时,数据从FPGA到EEPROM,
当ENB==0时,数据从EEPROM到FPGA。
实现语句: assign sda = (flag==1) ? data :1’bz;
3.写时序的问题,要对照数据手册看到整个过程都有哪些,并捋清高低电平的问题,这里对前面几个状态进行详细阐述。
State0 启动信号,此时要搞清楚高低电平,在前边我介绍数据手册的时候,就说过了,当SCL为高电平的时候,sda此时也为高电平,此时应将flag三态开关打开,迎接数据的到来,其次sda线要拉低进入启动信号,同时给temp(数据缓存总线)赋控制字节
State1 控制字节的输入读取,我们通过阅读数据手册知道当SCL为高电平的时候为数据有效,所以只有在SCL为低电平的时候,对其进行赋值,数据发生改变,因为有8个字节,所以定义一个8位的cnt,进行判断是否数据已经输入完毕,数据的输入采用循环移位拼接的方法来实现,当移位完毕的时候,flag关闭,以迎接ack信号的到来,计数清零。
State2 应答信号(ack)的检测 接受完8bit数据后,会向IIC发送低电平脉冲,表示接收到了数据,此时时钟SCL为高电平的时候,SDA转为只读,表示接收到了ack信号。
State9 发送停止信号 scl为高时,让SDA由低到高表示一个停止信号。
在stop状态之前又加了一状态是为了给下一个状态让sda从低到高的上升沿信号,这样才能发送STOP信号。
其余的状态就是状态1和2的重复
module IIC_rw ( input wire sclk, input wire rst_n, output reg scl, inout wire sda ); reg [23:0] count; reg clk_400k; reg [7:0] state; reg flag; reg data; reg [7:0] temp; reg [7:0] cnt; always @(posedge sclk or negedge rst_n) begin if(!rst_n) begin count <=0; clk_400k <=0; end else begin if(count==62) begin count <=0; clk_400k <=~clk_400k; end else count <= count +1; end end always @(posedge clk_400k or negedge rst_n) begin if(!rst_n) scl <=1; else scl <=~scl; end assign sda = flag ==1 ? data :1‘bz; always @(negedge clk_400k or negedge rst_n) begin if(!rst_n) begin state <= 0; flag <= 1; data <= 1; temp <= 0; cnt <= 0; end else case (state) 0: if(scl) //启动信号 begin flag <=1; state <=1; data <=0; temp <=8‘b1010_0000; end 1: if(scl==0 && cnt<8) //控制信号 begin flag <=1; data <=temp[7]; cnt <= cnt +1; temp <= {temp[6:0],temp[7]};//左移位像最低位移动 end else if(scl==0&&cnt==8) begin flag <=0; state <=2; cnt <=0; end 2: if(scl==1) //ack begin // if(sda==0) begin state<=3; temp<=8‘b0000_0000; //高字节 end // else // state <=0; end 3: if(scl==0&&cnt<8)//高字节 begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=4; end 4: if(scl==1) //ack begin // if(sda==0) begin state<=5; temp<=8‘b0000_0000; //低字节 end // else // state <=0; end 5: if(scl==0&&cnt<8)//低字节输入 begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=6; end 6: if(scl==1) //ACK begin // if(sda==0) begin state<=7; temp<=8‘b0010_0101; //要输入的数据 end // else // state <=0; end 7:if(scl==0&&cnt<8) begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=8; end 8: if(scl==1) //ack begin // if(sda==0) begin state<=9; end // else // state <=0; end 9: if(scl==0)//high to low begin flag <=1; data <=0; state <=10; end 10: if(scl==1)//STOP begin flag <=1; data <=1; state <=10; end default :state<=0; endcase end endmodule
仿真波形:
四 EEPROM 之随机读操作
共13个状态,所以可以通过状态机来书写读操作
涉及重点和难点:
NO ACK(非应答信号):
由上面随机读出的过程示意图就可以看到,NO ACK 信号是个高电平,只能由FPGA通过SDA 数据线向EEPROM 发送。
上一状态当SCL为低电平时,且数据读完后,SDA 开关打开(flag=1)data为高电平,在NOack状态下,SCL为低电平的时候,数据可读写,此时将data拉低,以备后来的STOP信号产生上升沿而用。
我们把NO ACK 和ACK 拉到一块
,就能联想出来,ACK 信号是EEPROM 收到八位数据后反馈给FPGA的信号,那么 NO ACK 信号就是 FPGA 读到八位数据后向EEPROM 发送的反馈信号。
module IIC_read ( input wire sclk, input wire rst_n, output reg scl, inout wire sda, output reg [7:0] result); reg [23:0] count;reg clk_400k;reg [7:0] state;reg flag;reg data;reg [7:0] temp;reg [7:0] cnt; always @(posedge sclk or negedge rst_n) begin if(!rst_n) begin count <=0; clk_400k <=0; end else begin if(count==62) begin count <=0; clk_400k <=~clk_400k; end else count <= count +1; end end always @(posedge clk_400k or negedge rst_n) begin if(!rst_n) scl <=1; else scl <=~scl; end assign sda = flag ==1 ? data :1‘bz; always @(negedge clk_400k or negedge rst_n) begin if(!rst_n) begin state <= 0; flag <= 1; data <= 1; temp <= 0; cnt <= 0; result <= 0; end else case (state) 0: if(scl) //启动信号 begin flag <=1; state <=1; data <=0; temp <=8‘b1010_0000; result<=0; end 1: if(scl==0 && cnt<8) //控制信号 begin flag <=1; data <=temp[7]; cnt <= cnt +1; temp <= {temp[6:0],temp[7]}; end else if(scl==0&&cnt==8) begin flag <=0; state <=2; cnt <=0; end 2: if(scl==1) //ack begin if(sda==0) begin state<=3; temp<=8‘b0000_0000; //高字节 end else state <=0; end 3: if(scl==0&&cnt<8)//高字节 begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=4; end 4: if(scl==1) //ack begin if(sda==0) begin state<=5; temp<=8‘b0000_0000; //低字节 end else state <=0; end 5: if(scl==0&&cnt<8)//低字节输入 begin flag <=1; data <=temp[7]; cnt <= cnt+1; temp <={temp[6:0],temp[7]}; end else if(scl ==0&&cnt==8) begin flag <=0; cnt <=0; state<=6; end 6: if(scl==1) //ACK begin if(sda==0) begin state<=7; end else state <=0; end 7: if(scl==0) // 拉高sda 备启动信号使用 启动信号是在scl为高电平的同时, sda由高电平到低电平 begin flag <=1; state <=8; data <=1; temp <=8‘b1010_0001; result<=0; end 8: if(scl)//启动信号 高到低 begin data<=0; state<=9; end 9: if(scl==0 && cnt<8) //控制信号 begin flag <=1; data <=temp[7]; cnt <= cnt +1; temp <= {temp[6:0],temp[7]}; end else if(scl==0&&cnt==8) begin flag <=0; state <=10; cnt <=0; end 10: if(scl==1) //ACK begin if(sda==0) begin state<=11; end else state <=0; end 11: if(scl==1&&cnt<8)//读 读的时候为高电平 begin flag <=0; result <={result[6:0],sda}; cnt <=cnt+1; end else if(scl==0&&cnt==8) begin flag <=1; cnt <=0; state <=12; data <=1; end 12: if(scl==0) //NOack begin if(sda==1) begin state <=13; data <=0; end else state <=0; end 13: if(scl==1)//STOP begin flag <=1; data <=1; state <=13; end // 14: if(scl==1)//进入非忙态// begin// flag <=1;// data <=1;// state <=14;// end default :state<=0; endcase end endmodule
读信号时的波形图: