本帖最后由 雾盈 于 2016-8-28 13:41 编辑
IIC总线协议FPGA应用之一
EEPROM(24LC64)字节写入(byte write)
雾盈 2016-8-4
雾盈FPGA笔记汇总目录
一、 何为IIC总线协议?它有什么特点,工作原理?
I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。
注:以下标题1)到5)的内容是从IIC总线协议中文手册中截取的。虽然文字很多,但是我觉得想要详细了解IIC协议是怎么样一个工作原理,读一读很有必要。
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总线在传送数据过程中共有三种类型信号, 它们分别是:开始信号、结束信号和应答信号。
开始信号(start):
处理器让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号。同时IIC总线上的设备检测到这个开始信号它就知道处理器要发送数据了。
停止信号(stop):
处理器让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。同时IIC总线上的设备检测到这个停止信号它就知道处理器已经结束了数据传输,我们就可以各忙各个的了,如休眠等。时序图如下:
应答信号(ACK):
接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。时序图如下:
3)数据传输
SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。时序图如下:
控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。示意图如下:
 
三、EEPROM之字节写入(byte write)
写操作分为字节写和页面写两种操作,我们今天只谈字节写,后面我会发页写和读写,毕竟我也刚学。写完这个我会用序列机写一下的。
字节写的过程如下图所示。
由图上的过程,我们是不是很容易想到这个可以用序列机(LSM)去实现,由于我们上课的时候老师要求我们用状态机(FSM)来写,所以我就说一我在写这个时候的思路,想法和遇到的问题。
我回忆一下,我在写这个程序时的思路:
首先,搞清楚我要哪些,目的是什么。
第一步:肯定先是整好工作时钟,400KHZ,我们可以写一个分频器将50M系统时钟分频为400KHZ。
- always @ (posedge clk or negedge rst_n)
- begin
- if(!rst_n)
- begin
- count <= 1'b0;
- clk_400k <= 1'b0;
- end
- else begin
- if( count == COUNT_400K )
- begin
- count <= 1'b0;
- clk_400k <= ~clk_400k;
- end
- else begin
- count <= count + 1'b1;
- end
- end
- end
复制代码
第二步:分频好400KHZ后,我们发现EEPROM 的 SLC 时钟线是200KHZ ,我们也可以如第一步那样再次分频一个200KHZ,但是我们发现200KHZ是400KHZ频率的一半,于是我们用下面简单的代码就产生了一个200KHZ的时钟。
- always @ (negedge clk_400k or negedge rst_n)
- begin
- if(!rst_n)
- begin
- scl <= 1'b1;
- end
- else if( state == 10)
- begin
- scl <= 1'b1;
- end
- else
- begin
- scl <= ~scl;
- end
- end
复制代码
 
第三步:认识 input,output,reg之外的另一种数据类型 inout (输入输出三态门)
当flag为1时输入,当flag为0 时输出
在这个程序里,既是flag=1时SDA写,flag=0时SDA读。代码可以这样写:
- assign sda = ( flag1 == 1'b1) ? data_out : 1'bz;
复制代码
 
第四步:这一步是核心,就是用状态机将数据传输过程的时序,严谨的表述出来。那搞清楚时序就是核心之核。我们来捋一下时序。
注明一下:IIC数据从最高位开始传输。
还是这张图。
我们将图中的过程视为状态机的各种状态。于是,写入一个字节,就必须有这些state:
0. 发送启动信号
1. 发送控制字
2. 接收并检测EEPROM发来的应答信号ACK
3. 发送高字节地址位
4. 接收并检测EEPROM发来的应答信号ACK
5. 发送低字节地址位
6. 接收并检测EEPROM发来的应答信号ACK
7. 发送8bit有效数据
8. 接收并检测EEPROM发来的应答信号ACK
9.发送停止信号
 
第五步,当我们把所有的思路捋清楚之后,就可以看着时序图敲代码了
源代码我会贴在后面。
由上图可以看出来,state3-8 都是在重复 state1-2 ,这样这个代码一下就在难度上缩小了一半,我们来详细说一下state0、state1、 state2和state9。
State0:发送启动信号,前面说的很详细了。让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号
State9:发送停止信号。让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。
State1:发送8位控制字。我们知道SDA线上的数据状态仅在SCL为低电平的期间才能改变后,也能写出来了,这里说一个怎么让8位数据传给单bit信号的方法,代码在下,看一下就体会出来了。
8bit传给1bit ,左移
- if( scl == 1'b0 && cnt < 8 ) //while scl is low ,change data
- begin
- data_out <= temp[7]; //cycle 0-8
- flag1 <= 1'b1;
- temp <= { temp[6:0],temp[7] };
- cnt <= cnt + 1'b1;
- state <= 1'b1;
- end
复制代码
1bit 传给 8bit ,右移
- if( scl == 1'b1 && cnt < 8 ) // scl high and flag1=0
- begin
- flag1 <= 1'b0;
- data_temp <= sda;
- temp[0] <= data_temp;
- temp <= { temp[0],temp[7:1] }; // remember !!!
- data_rd <= temp;
- cnt <= cnt + 1'b1;
- state <= 4'd10;
- end
复制代码
State2:接受ack信号。应答信号(ACK):
接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。此时,SLC保持高电平,SDA 转为只读,就表示接收到了ack 信号。
其他state就是在重复啦。
再说一点,有的状态机可能会在发送stop信号状态之前多增加一个st状态,此状态就是为了给下一状态,让SDA从低拉高的上升沿信号,这样才能够发送stop信号。代码如下:
- 9:begin // order to make sda low to high
- if( scl == 1'b0 ) // scl low and sda low ,produce "stop" signal
- begin
- data_out <= 1'b0;
- flag1 <= 1'b1;
- state <= 4'd10;
- end
- end
复制代码
所有的东西都说完了,这样一个看着貌似很难的程序,只要捋清思路,一步一步慢慢来,总能写出来的。
附源代码:
- //=====================================================================
- // module name: iic_ctrl
- // function:
- // create data: from 2016-8-3 14:01:22 to
- // editor: miao
- // Tool versions: quartus 13.0
- //=====================================================================
- module iic_ctrl(
- // system signal
- input clk, // 50MHZ
- input rst_n,
- // output signal
- output reg scl, // eeprom scl
- inout sda // eeprom sda
- );
- //=========================================================================================================
- //************************** Define parameter and internal signals *********************************
- //=========================================================================================================
- reg clk_400k;
- reg [7:0] count;
- parameter COUNT_400K = 62;
- reg flag1; //for sda
-
- reg [3:0] state;
- reg [7:0] temp; // register for sda
- reg data_out; // sad = data_out
- reg [7:0] cnt;
- //=========================================================================================================
- //********************************* 400KHZ & scl & flag1 ***********************************
- //=========================================================================================================
- // frequency division 400KHZ , count 62 times
- always @ (posedge clk or negedge rst_n)
- begin
- if(!rst_n)
- begin
- count <= 1'b0;
- clk_400k <= 1'b0;
- end
- else begin
- if( count == COUNT_400K )
- begin
- count <= 1'b0;
- clk_400k <= ~clk_400k;
- end
- else begin
- count <= count + 1'b1;
- end
-
- end
- end
- // 400KHZ could produce 200KHZ named scl of eeprom
- always @ (negedge clk_400k or negedge rst_n)
- begin
- if(!rst_n)
- begin
- scl <= 1'b1;
- end
- else if( state == 10)
- begin
- scl <= 1'b1;
- end
- else
- begin
- scl <= ~scl;
- end
-
- end
- //=========================================================================================================
- //********************************* state machine ***********************************
- //=========================================================================================================
- assign sda = ( flag1 == 1'b1) ? data_out : 1'bz;
- always @ (posedge clk_400k or negedge rst_n)
- begin
- if(!rst_n)
- begin // initial , both scl and sda high
- state <= 4'd0;
- temp <= 8'd0; // register
- data_out <= 1'b1;
- flag1 <= 1'b1;
- cnt <= 1'b0;
- end
- else begin
- case(state)
- 0:begin // send start signal
- if( scl == 1'b1 ) // scl high while sda low , produce the "start" signal
- begin
- data_out <= 1'b0;
- flag1 <= 1'b1;
- temp <= 8'b1010_0000;
- state <= 1'b1;
- end
- end
- 1:begin // send contrl_word
- if( scl == 1'b0 && cnt < 8 ) //while scl is low ,change data
- begin
- data_out <= temp[7]; //cycle 0-8
- flag1 <= 1'b1;
- temp <= { temp[6:0],temp[7] };
- cnt <= cnt + 1'b1;
- state <= 1'b1;
- end
- else if( scl == 1'b0 && cnt == 8 )
- begin
- flag1 <= 1'b0; // flag = 0 ,sda begin receive data
- state <= 4'd2;
- cnt <= 1'b0;
- end
- end
- 2:begin // receive ack
- if ( scl == 1'b1 )
- begin
- // if( sda == 1'b0 ) // scl high and sda low ,receive ACK . meanwhile switch to "state3"
- begin
- temp <= 8'd0000_0000; // put 8 bit address into the register "temp"
- state <= 4'd3;
- end
- /* else begin //if couldn't receive ACK ,switch to "state1" and transfer contrl_word again
- state <= 4'd1;
- end */
- end
- end
- 3:begin // send high address
- if( scl == 1'b0 && cnt < 8 ) // scl low could change data , similar with "state1"
- begin
- data_out <= temp[7];
- flag1 <= 1'b1;
- temp <= { temp[6:0],temp[7] };
- cnt <= cnt + 1'b1;
- state <= 4'd3;
- end
- else if( scl == 1'b0 && cnt == 8 )
- begin
- flag1 <= 1'b0; // sda = data_out
- state <= 4'd4;
- cnt <= 1'b0;
- end
- end
- 4:begin // receive ack
- if ( scl == 1'b1 )
- begin
- // if( sda = 1'b0 )
- begin
- temp <= 8'd0000_0000;
- state <= 4'd5;
- end
- /* else begin
- state <= 4'd3;
- end */
- end
- end
- 5:begin // send low address
- if( scl == 1'b0 && cnt < 8 )
- begin
- data_out <= temp[7];
- flag1 <= 1'b1;
- temp <= { temp[6:0],temp[7] };
- cnt <= cnt + 1'b1;
- state <= 4'd5;
- end
- else if( scl == 1'b0 && cnt == 8 )
- begin
- flag1 <= 1'b0;
- cnt <= 1'b0;
- state <= 4'd6;
- end
- end
- 6:begin // receive ack
- if ( scl == 1'b1 )
- begin
- // if( sda = 1'b0 )
- begin
- temp <= 8'd1111_1111;
- state <= 4'd7;
- end
- /* else begin
- state <= 4'd5;
- end */
- end
- end
- 7:begin // send data
- if( scl == 1'b0 && cnt < 8 )
- begin
- data_out <= temp[7];
- flag1 <= 1'b1;
- temp <= { temp[6:0],temp[7] };
- cnt <= cnt + 1'b1;
- state <= 4'd7;
- end
- else if( scl == 1'b0 && cnt == 8 )
- begin
- flag1 <= 1'b0;
- cnt <= 1'b0;
- state <= 4'd8;
- end
- end
- 8:begin // receive ack
- if ( scl == 1'b1 )
- begin
- // if( sda = 1'b0 )
- begin
- state <= 4'd9;
- end
- end
- /* else begin
- state <= 4'd7;
- end */
- end
- 9:begin // order to make sda low to high
- if( scl == 1'b0 ) // scl low and sda low ,produce "stop" signal
- begin
- data_out <= 1'b0;
- flag1 <= 1'b1;
- state <= 4'd10;
- end
- end
- 10:begin // "stop" flag
- if( scl == 1'b1)
- begin
- flag1 <= 1'b1;
- data_out <= 1'b1;
- state <= 4'd10;
- end
- end
- default: state <= 4'd0;
- endcase
- end
- end
- endmodule
复制代码
|