初识IIC之EEPROM的读与写
3赞一、IIC设计介绍
1.1IIC总线协议
IIC协议与接口在实际工程中得到广泛应用,例如数据采集领域的串行AD,图像处理领域的摄像头配置,工业控制领域的X射线管配置等。此外,由于IIC协议占用的IO资源少,连接方便,所以工程中也常常选用IIC接口作为不同芯片间的通信协议。
下面来介绍一下IIC协议的具体时序是什么?IIC的读写分为随机读写和页面读写,也就是我们常说的byte write/read和page write/read。
我们先来看一看byte write/read。
1、Byte write时序图
从图中我们可以看到,IIC协议完成一次字节写操作,大致需要这样几步:
1. 发送启动信号,IIC主要有两条线,SCL时钟线,SDA数据线,数据的读写都是在这两条线的控制下完成的。IIC总线空闲状态下,SCL应为高电平。当SCL为高电平时,主控芯片将SDA由高拉低,IIC协议则认为主控芯片发出的启动信号。
2. 发送控制字节,上图中的control_byte的高4位1010,代表的是EEPROM,A2A1A0相当于器件的代号,由于一条总线上可以挂载多个器件,所以需要不同的代号加以区分。最后一位0代表写操作,如果是读操作,则应该为1。主控芯片在发送数据时,只能在SCL为0时改变数据,在SCL为高电平时,必须保持数据的稳定
3. 接收并检测ACK应答信号,主控芯片发送完control_byte,如果EEPROM成功接收,会给主控芯片回复一个ACK应答信号。需要注意的是接收ACK时SDA是作为输入线,所以这里的SDA是一个inout的双向端口。主控芯片在SCL为高电平时,如果检测到SDA为低电平,则表示接收到ACK。(记住SCL为低电平时发送数据,SCL为高电平时接收数据)
4. 发送高字节地址,这里地址总共是13位,由于EEPROM的存储空间只有8KB,1024x8 = 8192 = 2^13;但是由于IIC是以字节为基本单位进行读写,所以不足8bit的,要补齐8bit
5. 接收并检测ACK。
6. 发送低字节地址;
7. 接收ACK;
8. 发送需要写入的数据
9. 接收ACK,
10. 发送STOP信号。STOP是在SCL为高电平时,主控芯片令SDA从低变高。注意:主控芯片在SCL为高电平时发送的信号,不是启动信号就是停止信号,在传送数据的时候,都是在低电平发送数据的。
2、page_write时序图
Page_write和byte_write在发送第一个直接之前都是一模一样的,page_write一次可最多写入32字节的数据。主控芯片在发送地址时,只用传递起始地址,每写入一个数据地址会自动加一。
3、Byte_read时序图
读操作和写操作前半部分是一样的,只不过在发送完低字节地址的时候,需要重新发送启动信号,再发送读控制信号(也就是最低位为1,其他不变),然后接受ACK,再接收数据,接收完数据之后,主控芯片需要回复EEPROM一个NO_ACK信号(NO_ACK:SCL为低电平时,SDA输出高电平),告诉它,自己信号收到了。然后主控芯片发出停止信号。
4、Page_read时序图
连续读操作和字节读的启动过程是一样的,只是在EEPROM发送完第一个数据之后,主控芯片发出ACK信号(注意和前面有点不一样:1,是主控芯片发出,不是主控芯片接收。注意是ACK低电平,不是NO_ACK高电平)。ACK信号指示EEPROM发送下一个连续地址的字节数据。接收完最后一个字节的数据后,主控芯片会发送NO_ACK,告诉EEPROM数据接收完,最后发送停止信号。
5、总结
Start信号:SCL为高时,SDA由高到低
Stop信号:SCL为高时,SDA由低变高
ACK信号:主控芯片发出:SCL为低,SDA输出低电平
主控芯片接收:SCL为高,SDA采集到低电平
NO_ACK :SCL为低,SDA输出高电平
1.2 EEPROM介绍
这是一个EEPROM的引脚封装图,
A0,A1,A2是一个片选信号,当一条总线上挂载多个器件时,需要A0,A1,A2加以区分。
Vcc与Vss分别是电源线和地线,
WP为读写使能信号。悬空或接地时,器件可读可写。拉高时,只能读不能写。
SCL是串行时钟线,保证数据收发同步。
SDA是双向数据线
这是开发板上EEPROM的原理图,从图中我们可以看到,由于开发板上只有一个EEPROM,所以A0A1A2都是接地,WP也是接地,表示器件可读可写。剩下SCL和SDA是要连接到主器件的。
二、系统框图
基本上每个读写操作都做了一个实验来验证,在第一节介绍IIC总线协议的时序的时候,我们就可以发现,每一个操作的时序基本类似,甚至读和写的前几部都是一模一样的,所以我直接就来介绍我最后一个实验的基本框架吧
这便是整个系统的一个框架,时钟分频模块用来产生所有其他模块需要的时钟,系统有两个按键,一个读一个写,烧录系统后,按下写按键,系统将程序中设定的数写入EEPROM,再按下读按键,系统将刚写入的数据读出来,并用数码管显示。由于输入IIC的按键信号需要是一个脉冲信号,为了保证按键信号的脉冲宽度和IIC的脉冲宽度一致,我们得使用与IIC一样的脉冲来进行按键的消抖。这里我不得不吐槽几句,我用我原来的按键消抖模块得到一个无抖动的平滑按键信号,然后再用500k的时钟对这个平滑的按键信号进行一个边缘检测,也可以输出一个尖峰脉冲,当时仿真也通过了,我把这个模块拿出来单独建一个工程,用singaltap抓到的波形也是我想要的波形,但是我把它接到这个系统里面就是出不来结果,用singaltap也抓不到波形。但是换一种消抖方案就可以正常运行了,问老师老师也是一脸懵逼。很无奈,我只能把它归咎为玄学问题了,希望哪位大师能为我解惑一下。
最后说一下IIC模块,我们先来根据前面介绍的时序画一下IIC的流程图。(博客中的框图原理图都是用VISIO绘制,这是一个非常好用的画图软件,包括波形图都能画,墙裂推荐!)
一开始,系统会判断有没有键按下,如果有,那么系统就开始他的读写操作了,根据时序图分析,我们发现读和写的前几部操作都是一样的,所以我们不管按下的是读还是写,前几部操作都要执行。当执行到分水岭的时候,我们再来判断按下的是读还是写,然后执行相应的操作。
这里我们顺便解释一下为什么消抖模块要输出一个和IIC驱动时钟一样宽度的脉冲。假如我们输出的是一个持续的低电平,那么情况会怎么样,系统执行完一轮操作后,又检测到按键了,又执行一轮,这显然是不合理的。那么如果我们输出的脉冲宽度小于IIC的驱动时钟宽度又会怎么样?假如你的脉冲正好在上一个时钟沿到下一个时钟沿之间,是不是就不会被采集到了。所以,我们消抖模块输出的按键脉冲,最好和IIC的时钟周期宽度一致。
看了上面的框图,我们可以看到,IIC模块有两个输入时钟,一个500k,一个250k。这两个时钟有什么用呢?其他频率的时钟可以吗?答案是可以的。EEPROM的官方数据手册上规定:SCL的时钟必须在100k-400k之间。所以这个250k的时钟就是给SCL的。我在前面说过,SCL是数据收发的时钟,他的作用是控制主从芯片之间数据传输的同步性,类似于串口的波特率。现在我们知道SCL是250(SCL:你才250,你全家都250….),那500又是干嘛的?(伍佰是喝酒的,喝完这杯还有3杯,哈哈,开个玩笑,回归正题),500K可以用其他的频率代替吗?答案是不可以的。前面说过,SDA作为输出的时候,SCL低电平的时候可以改变数据,在高电平的时候必须保持数据,SDA作为输入的时候,SCL高电平的时候采集数据,不然就是启停信号了对吧。那么我们要怎么来保证他这个时序的要求呢?
我们让SCL在500K的上升沿到来时翻转,那么我们每一个500k的下降沿是不是正好就可以采集到SCL的高电平和低电平的中间状态,讲道理,这个时候的信号是最稳定的,不管你是采集信号,还是要发送信号都是最好时机了,如果你用其他频率的时钟来驱动,不仅会错过最佳时机,而且到后面还会出现错误。所以说IIC的驱动时钟和SCL必须是2倍关系,如果你是250,那么clk必须是500。当然,你也可以在下降沿翻转,上升沿采集,没毛病。
三、代码解析
其他三个模块我就不讲了,之前的博客里都有讲过,不懂得小伙伴可以去翻一翻,或者跟我评论私聊。这里主要讲一下IIC的模块。
大家看完我前面的介绍之后,对应着前面的流程图和时序图,这段代码应该还是比较容易理解的吧。我从头到尾把没有介绍的地方再讲一下吧。
这里的SDA是一个双向的输入输出端口,对于双向的端口我们在Verilog中是怎么定义的呢?
assign sda = flag ? data : 1'bz;
这里flag相当于是一个开关,当flag为1的时候开关打开,SDA作为输出,输出data的值,所以我们还需要定义一个中间寄存器data。当flag为0的时候,SDA作为输入,可以接收从器件传进来的值,输出是一个高阻态。
这里的串行控制时钟SCL,当系统处于空闲状态时,他为高电平,当系统启动时,他为250k的时钟。同样我们也给他定义一个标志not_busy。当not_busy为高电平,SCL = 1,not_busy为低电平,SCL = 250K 。
assign scl = not_busy? 1'b1 : clk_250k;
在0状态,我们检测是否有按键按下,如果有,我们就把两个按键的脉冲值(只有一个为高)传递给两个按键寄存器,以供后面判断是按下的是读还是写。
后面的状态就是依次按照上面的时序图和流程图来写了,由于状态实在太多,我就不一一解释了。需要注意的是在接受ACK的时候一定要保证接受一整拍(是SCL的一整拍,不是500k的),我们在SCL = 0的时候拉低flag来接收ACK,那么我们在拉高flag的时候也一定要等到SCL的下一个低电平。因为如果在SCL = 1的时候你就将flag拉高了,那么FPGA很有可能采集不到这个ACK,最关键的是,整个时序还乱了,所以,一定要严格按照时序图来设计,半拍都不能错。
四、波形分析
第一个图是一个读加写的整体的波形图,但是由于太密了,我估计各位看官看不清,所以将波形放大分别把读和写截下来了。从上到下的信号分别是复位、clk_500k、clk_250k、写脉冲、读脉冲、数据输出、SCL、SDA。图中蓝线SDA为输入状态,仿真时时高阻,虽然我们看不到他接收到的数据是多少,但是作为主控芯片,如果你传出去的数据没错,那么你接收到的数据一般不会出错。
现在我们简单分析一下这个波形,先来看第二张写的。在SCL为高电平的时候SDA拉低,说明发出启动信号。然后发出控制字1010_0000,可以仔细看一下,他是不是在SCL为低电平的时候才改变数据,SCL为高电平的时候,数据保持不变。然后是SDA作为输入接收ACK,从SCL上一个低电平到下一个低电平,是不是一整拍。然后依次是高字节地址和低字节地址,然后写入3个数据(BCD码)21、22、23。我是设置的每写一个数据让下一个数据自动加一,当然你也可以设置成其他的。最后发送一个停止信号。
然后再来看读操作,从启动到输出低字节地址都和写一样,输完低地址后,发送一个读控制字1010_0001(写控制字是1010_0000),然后是接收三个数据,注意接收第一个数据前还有一个ACK要接收,你们可以数一下,第一个是不是比后面两个要长一拍。接收完数据之后,需要向EEPROM发送一个NO_ACK信号,告诉对方数据收到了。然后发送停止信号。
这个只是端口信号的波形,如果你们需要看所有内部寄存器信号的波形,可以先CTRL A+DELETE,把当前信号都删掉,然后去modelsim的sim窗口,选中你要观察的模块,然后Ctrl + w,然后再命令窗口输入restart 回车再回车,再输入run –all回车。再去看你的波形窗口,你会发现你想看的波形都在里面了。记住F键可以全局缩小哦。
今天就写这么多了,好累啊,感觉贫道的洪荒之力都用完了,还没网,先保存在open liver writer吧,明天再上传了