自学单片机-14

第14章 I2C总线与E2PROM

	前几章学习了一种通信协议叫作UART异步串行通信,本章要学习第二种常用的通信协议I2C。I2C总线是由PHILIPS公司开发的两线式串行总线,多用于连接微处理器及其外围芯片。I2C总线的主要特点是接口方式简单,两条线可以挂多个参与通信的器件,即多机模式,而且任何一个器件都可以作为主机,当然同一时刻只能有一个主机。
	从原理上来讲,UART属于异步通信,比如计算机发送给单片机,计算机只负责把数据通过TXD发送出来即可,接收数据是单片机自己的事情。而I2C属于同步通信,SCL时钟线负责收发双方的时钟节拍,SDA数据线负责传输数据。I2C的发送方和接收方都以SCL这个时钟节拍为基准进行数据的发送和接收。
	从应用上来讲,UART通信多用于板间通信,比如单片机和计算机,这个设备和另外一个设备之间的通信。而I2C多用于板间通信,比如单片机和计算机,这个设备和另外一个设备之间的通信。而I2C多用于板内通信,比如单片机和本章要学的E^2^PROM之间的通信。

14.1 I2C时序初步认识

	在硬件上,I2C总线是由时钟总线SCL和数据总线SDA两条线构成,连接到总线上的所有器件的SCL都连到一起,所有SDA都连到一起。I2C总线是开漏引脚并联的结构,因此外部要添加上拉电阻。对于开漏电路外部加上拉电阻,就组成了线“与”的关系。总线上线“与”的关系就是说,所有接入的器件操持高电平,这条线才是高电平,而任何一个器件输出一个低电平,那这条线就会保持低电平,因此可以做到任何一个器件都可以拉低电平,也就是任何一个器件都可以作为主机,如图14-1所示添加了R63和R64两个上拉电阻。
	![在这里插入图片描述](https://img-blog.csdnimg.cn/df70e09f3cd340fd931ac2623fb657c3.png#pic_center)

在这里插入图片描述

	虽然说任何一个设备都可以作为主机,但绝大多数情况下都是用单片机来做主机,而总线上挂的多个器件,每一个都像电话机一样有自己唯一的地址,在信息传输的过程中,通过唯一的地址就可以正常识别到属于自己的信息,在KST-51开发板上,就挂接了2个I2C设备,一个是24C02,一个是PCF8591。
	在学习UART串行通信的时候,知道了通信流程分为起始位、数据位、停止位这三部分,同理在I2C中也有起始信号、数据传输和停止信号,如图14-2所示。

在这里插入图片描述 从图上可以看出来,I2C和UART时序流程有相似性,也有一定的区别。UART每个字节中都有1个起始位、8个数据位、1个停止位。而I2C分为起始信号、数据传输部分和停止信号。其中数据传输部分可以一次通信过程传输很多个字节,字节数是不受限制的,而每个字节的数据最后也跟了一位,这一位叫作应答位,通常用ACK表示,有点类似于UART的停止位。
下面逐步地把I2C通信时序进行剖析。之前已经学过了UART,所以学习I2C的过程尽量拿UART来作为对比,这样有助于更好地理解。但是有一点要理解清楚,就是UART通信虽然用了TXD和RXD两根线,但是实际一次通信中,1条线就可以完成,2条线是把发送和接收分开而已,而I2C每次通信,不管是发送还是接收,必须2条线都参与工作才能完成,为了更方便地看出每一位的传输流程,把图14-2改进成图14-3。
在这里插入图片描述 (1)起始信号:UART通信是从一直持续的高电平出现一个低电平标志起始位;而I2C通信的起始信号的定义是SCL为高电平期间,SDA由高电平向低电平变化产生一个下降沿,表示起始信号,如图14-3中的Start部分所示。
(2)数据传输:首先,UART是低位在前、高位在后;而I2C通信是高位在前、低位在后。其次,UART通信数据位是固定长度,波特率分之一,一位一位固定时间发送完毕就可以了。而I2C没有固定波特率,但是有时序的要求,要求当SCL在低电平的时候,SDA允许变化,也就是说,发送方必须先保持SCL是低电平,才可以改变数据线SDA,输出要发送的当前数据的一位;而当SCL在高电平的时候,SDA绝对不可以变化,因为这时,接收方要来读取当前SDA的电平信号是0还是1,因此要保证SDA的稳定,如图14-3中的每一位数据的变化,都是在SCL的低电平位置。8位数据位后边跟着的是一位应答位,关于应答位在后面还要具体介绍。
(3)停止信号:UART通信的停止位是一位固定的高电平信号;而I2C通信停止信号的定义是SCL为高电平期间,SDA由低电平向高电平变化产生一个上升沿,表示结束信号,如图14-3中的Stop部分所示。

14.2 I2C寻址模式

	14.1节介绍的是I2C每一位信号的时序流程,而I2C通信在字节级的传输中也有固定的时序要求。I2C通信的起始信号(Start)后,首先要发送一个从机的地址, 这个地址一共有7位,紧跟着的第8位是数据方向(R/W),"0"表示接下来要发送数据(写), “1"表示接下来是请求数据(读)。
	人们知道,打电话的时候,当拔通电话,接听方捡起电话肯定要回一个”喂“,这就是告诉拔电话的人,这边有人了。同理, 这个第9位ACK实际上起到的就是这样一个作用。当发送完了这7位地址和1位方向后,如果发送的这个地址确实存在,那么这个地址 的器件应该回应一个ACK(拉低SDA即输出”0“),如果不存在,就没”人“回应ACK(SDA将保持高电平即”1“)。
	写一个简单的程序,访问一下开发板上的E^2^PROM的地址,另外再写一个不存在的地址,看看它们是否能回一个ACK,来了解和确认一下这个问题。
	开发板上的E^2^PROM器件型号是24C02,在24C02的数据手册3.6节中可查到,24C02的7位地址中,其中高4位是固定的0b1010,而低3位的地址取决于具体电路的设计,由芯片上的A2、A1、A0这3个引脚的实际电平决定,来看一下24C02的电路图,它和24C01的原理图完全一样,如图14-4所示。
	![在这里插入图片描述](https://img-blog.csdnimg.cn/79aac165a62b4accb06458c3dca6ec74.png#pic_center)

在这里插入图片描述

	从图14-4可以看出,A2、A1、A0都接GND,也就是说都是0,因此24C02的7位地址实际上是二进制的0b1010000,也就是0x50。用I2C的协议来寻址0x50,另外再寻址一个不存在的地址0x62,寻址完毕,把返回的ACK显示到我们的1602液晶上,大家对比一下。

/Lcd1602.c文件程序源代码*/
在这里插入图片描述在这里插入图片描述
/***********main.c文件程序源代码/

在这里插入图片描述在这里插入图片描述
把这个程序在KST-51开发板上运行完毕,会在液晶上边显示出来预想的结果,主机发送一个存在的从机地址 ,从机会回复一个应答位,即应答位为0;主机如果发送一个不存在的从机地址,就没有从机应答,即应答位为1。
前面的章节中已经提到利用库函数_nop_()可以进行精确延时,一个_nop_()的时间就是一个机器周期,这个库函数包含在intrins.h这个文件中,如果要使用这个库函数,只需要在程序最开始,和包含reg52.h一样,include<intrins.h>之后,程序中就可以使用这个库函数了。
还有一点要提一下,I2C通信分为低速模式100kb/s、快速模式400kb/s和高速模式3.4MB/s。因为所有的I2C器件都支持低速,但未必支持另外两种速度,所以作为通用的I2C程序我们选择100K这个速率来实现,也就是说实际程序产生的时序必须小于或等于函数中通过插入I2CDelay()这个总线延时函数(它实际上就是4个NOP指令,用define在文件开头做了定义),加上改变SCL值语句本身占用的至少一个周期,来达到这个速度限制。如果以后需要提高速度,那么只需减小这里的总线延时时间即可。
此外要学习一个发送数据的技巧,就是I2C通信时如何将一个字节的数据发送出去。大家注意函数I2CWrite()中,用的那个for循环的技巧。for(mask = 0x80; mask != 0; mask>=1),由于I2C通信是从高位开始发送数据,所以先从最高位开始,0x80和dat进行按位与运算,从而得知dat第7位是0还是1,然后右移一位,也就是变成了用0x40和dat按位与运算,得到第6位是0还是1,一直到第0位结束,最终通过if语句,把dat的8位数据依次发送出去。其他的逻辑对照前边讲到的理论知识,认真研究明白就可以了。

14.3 E2PROM的学习

	在实际的应用中,保存在单片机RAM中的数据掉电后就丢失了,保存在单片机的FLASH中的数据又不能随意改变,也就是不能用它来记录变化的数值。但是在某些场合又确实需要记录下某些数据,而它们还时常需要改变或更新,掉电之后数据还不能丢失,比如家用电表度数,电视机里边的频道记忆,一般都是使用E^2^PROM来保存数据,特点就是掉电后不丢失。开发板上使用的这个器件是24C02,一个容量大小是2Kbits,也就是256个字节的E^2^PROM。一般情况下,E^2^PROM拥有30~100万次的寿命,也就是它可以反复写入30万~100万次,而读取次数是无限的。
	24C02是一个基于I2C通信协议的器件,因此从现在开始,I2C和E^2^PROM就是合体了。但是要分清楚,I2C是一个通信协议,它拥有严密的通信时序逻辑要求,而E^2^PROM是一个器件,只是这个器件采用了I2C协议的接口与单片机相连而已,二者并没有必然的联系,E^2^PROM可以用其他接口,I2C也可以用在其他很多器件上。

14.3.1 E2PROM单字节读写操作时序

1.E2PROM写数据流程

(1)首先是I2C的起始信号,接着跟上首字节,也就是I2C的器件地址,并且在读写方向上选择”写“操作。
(2)发送数据的存储地址。24C02一共256个字节的存储空间,地址从0x00~0xFF,想把数据存储在哪个位置,此刻写的就是哪个地址。
(3)发送要存储的数据第一个字节、第二个字节·······注意在写数据的过程中,E^2^PROM每个字节都会回应一个”应答位0“,来告诉我们写E^2^PROM数据成功,如果没有回应答位,说明写入不成功。
在写数据的过程中,每成功写入一个字节,E^2^PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址会溢出又变成了0x00。
2.E^2^PROM读数据流程
(1)首先是I2C的起始信号,接着跟上首字节,也就是I2C的器件地址,并且在读写方向上选择”写“操作。这个地方可能有同学会诧异,明明是读数据为何方向也要选”写“呢?刚才说过了,24C02一共有256个地址,选择写操作是为了把所要读的数据的存储地址先写进去,告诉E^2^PROM要读取哪个地址的数据。这就是如同打电话,先拔总机号码*(E^2^PROM器件地址),而后还要继续拔分机号码(数据地址),而拔分机号码这个动作,主机仍然是发送方,方向依然是”写“。
(2)发送要读取的数据的地址,注意是地址而非存在E^2^PROM中的数据,通知E^2^PROM要哪个分机的信息。
(3)重新发送I2C起始信号和器件地址,并且在方向位选择”读“操作。
在这三步当中,每一个字节实际上都是在”写“,所以每一个字节E^2^PROM都会回应一个”应答位0“。
(4)读取从器件发回的数据,读一个字节,如果还想继续读下一个字节,就发送一个”应答位ACK(0)",如果不想读了,告诉E^2^PROM不想要数据了,别再发数据了,那就发送一个“非应答位NAK(1)。”
和写操作规则一样,每读一个字节,地址会自动加1,如果想继续往下读,给E^2^PROM一个ACK(0)低电平,那再继续给SCL完整的时序,E^2^PROM会继续往外送数据。如果不想读了,要告诉E^2^PROM不要数据了,直接给一个NAK(1)高电平即可。这个地方大家要从逻辑上理解透彻,不能简单地靠死记硬背了,一定要理解明白。梳理一下几个要点:1:在本例中单片机是主机,24C02是从机;2:无论是读还是写,SCL始终都是由主机控制的;3:写的时候应答信号由从机给出,表示从机是否正确接收了数据;4:读的时候应答信号侧由主机给出,表示是否继续读下去。
下面写一个程序,读取E^2^PROM的0x02这个地址上的一个数据,不管这个数据之前是多少都将读出来的数据加1,再写到E^2^PROM的0x02这个地址上。此外将I2C的程序建立一个文件,写一个I2C.c程序文件,形成又一个程序模块。大家也可以看出来,连续的这几个程序,Lcd1602.c文件里的程序都是一样的,今后大家写1602显示程序也可以直接拿过去用,这大大提高了程序的移植。
/***************************************************************I2C.c文件程序源代码*************************************/

在这里插入图片描述在这里插入图片描述在这里插入图片描述
I2C.c文件提供了I2C总线所有的底层操作函数,包括起始、停止、字节写、字节读+应答,字节读+非应答。
/***********Lcd1602.c文件程序源代码/
在这里插入图片描述
在这里插入图片描述
/******main.c文件程序源代码/

在这里插入图片描述
以同学们现在的基础,独立分析这个程序应该不困难了,遇到哪个语句不懂可以及时问问别人或者搜索一下,把该解决的问题理解明白。大家把这个程序复制过去后,编译一下会发现,Keil软件提示了一个警告:*****WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESS,这个警告的意思是在代码中存在没有被调用过的变量或者函数,即I2C.c文件中的I2CReadACK()这个函数在本例中没有用到。
仔细观察一下这个程序,读取E2PROM的时候,只读了一个字节就要告诉E2PROM不需要再读数据了,读完后直接发送一个NAK,因此只调用了I2CReadNAK()这个函数,而并没有调用I2CReadACK()这个函数。今后很可能在读数据的时候要连续读几个字节,因此这个函数写在了I2C.c文件中,作为I2C功能模块的一部分是必要的,方便这个文件以后移植到其他程序中使用,因此这个警告在这里就不必管它了。

14.3.2 E2PROM多字节读写操作时序

	读取E^2^PROM的时候很简单,E^2^PROM根据所送的时序,直接就把数据送出来了,但是写E^2^PROM却没有这么简单了。给E^2^PROM发送数据后,先保存在E^2^PROM的缓存中,E^2^PROM必须要把缓存中的数据搬移到“非易失”的区域,才能达到掉电不丢失的效果。而往非易失区域写需要一定的时间,每种器件不完全一样,Atmel公司的24C02的这个写入时间最高不超过5ms。在往非易失区域写的过程,E^2^PROM是不会再响应我们的访问的,不仅接收不到数据,即使用I2C标准的寻址模式去寻址,E^2^PROM都不会应答,就如同这个总线上没有这个器件一样。数据写入非易失区域完后,E^2^PROM再次恢复正常,可以正常读写了。
	细心的同学在看14.2节程序的时候会发现,写数据的那段代码实际上有去读应答位ACK,但是读到了应答位也没有做任何处理。这是因为一次只写一个字节的数据进去,等到下次重新上电再写的时候,时间肯定远远超过了5ms,但如果是连续写入几个字节,就必须考虑应答位的问题了。写入一个字节后,再写入下一个字节之前,必须等待E^2^PROM再次响应才可以,大家注意程序的写法,可以学习一下。
	之前知道编写多.c文件移植的方便性了,本节程序和上一节的Lcd1602.c文件与I2C.c文件完全是一样的,因此这次只把main.c文件给大家发出来,帮大家分析明白。而读者却不能这样,读者是初学,很多知识和技巧需要多练才能巩固下来,因此每个程序还是建议大家在Keil软件上一个代码一个代码地敲出来。
	/**********************************************I2C.c文件程序源代码****************************************************/

在这里插入图片描述在这里插入图片描述![在这里插入图片描述](https://img-blog.csdnimg.cn/7d60c01cdec74cf39f24e42a01152731.png#pic_center
/******Lcd1602.c文件程序源代码/

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/c494071c5c3a4c21820a8ab99c6be077.png#pic_center

/*******main.c文件程序源代码/

在这里插入图片描述

(1)函数MemToStr:可以把一段内存数据转换成十六进制字符串的形式。由于从E2PROM读出来的是正常的数据,而1602液晶接收的是ASCII码字符,因此要通过液晶把数据显示出来必须先通过一步转换。算法倒是很简单,就是把每一个字节的数据高4位和低4位分开,和9进行比较,如果小于或等于9,则直接加’0‘转为0~9的ASCII码;如果大于9,则先减掉10再加’A’即可转为A-F的ASCII码。
(2)函数E2Read: 在读之前,要查询一下当前是否可以进行读写操作,E2PROM正常响应才可以进行。进行后,读最后一个字节之前的,全部给出ACK,而读完了最后一个字节,要给出一个NAK。
(3)函数E2Write: 每次写操作之前都要进行查询判断当前E2PROM是否响应,正常响应后才可以写数据。

14.3.3 E2PROM的页写入

在向E^2^PROM连续写入多个字节的数据时,如果每写一个字节都要等待几ms的话,整体上的写入效率就太低了。因此E^2^PROM的厂商就想了一个办法,把E^2^PROM分页管理。24C01、24C02这两个型号是8个字节一个页,而24C04、24C08、24C16是16个字节一页。我们开发板上用的型号是24C02,一共是256个字节,8个字节一页,那么就一共有32页。
分配好页之后,如果在同一个页内连续写入几个字节后,最后再发送停止位的时序。E^2^PROM检测到这个停止位后,就会一次性把这一页的数据写到非易失区域,就不需要像上节那样写一个字节检测 一次了,并且页写入的时间也不会超过5ms。如果写入的数据跨页了,那么写完了一页之后,要发送一个停止位,然后等等并且检测E^2^PROM的空闲模式,一直等到把上一页数据完全写到非易失区域后,再进行下一页的写入,这样就可以在很大程序上提高数据的写入效率。
/*************************************************I2C.c文件程序源代码***************************************************/

在这里插入图片描述在这里插入图片描述在这里插入图片描述

/Lcd1602.c*******/
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

/**********eeprom.c文件程序源代码/
在这里插入图片描述在这里插入图片描述

	遵循模块化的原则,把E^2^PROM的读写函数也单独写成一个E^2^PROM.c文件。其中 E^2^Read函数和上一节是一样的,因为读操作与分页无关。重点是E2Write函数,在写入数据的时候,要计算下一个要写的数据的地址是否是一个页的起始地址,如果是,则必须跳出循环,等待E^2^PROM把当前这一页写入非易失区域后,再进行后续页的写入。

/********************************main.c文件程序源代码/
在这里插入图片描述
在这里插入图片描述
多字节写入和页写入程序都编写出来了 ,而且页写入的程序还特地跨页写数据,它们的写入时间到底差别多大呢?用一些工具可以测量一下,比如示波器,逻辑分析仪等工具。现在把两次写入时间逻辑分析仪给抓了出来,并且用时间标签A1和A2标注了开始位置和结束位置,如图14-5和图14-6所示,右侧显示的|A1-A2|就是最终写入5个字节所耗费的时间。多字节一个一个写入,每次写入后都需要再次通信检测E2PROM是否在“忙”,因此耗费了大量的时间,同样的写入5个字节的数据,一个一个写入用了8.4ms左右的时间,而使用页写入,只用了3.5ms左右的时间。
在这里插入图片描述

14.4 I2C和E2PROM的综合实验学习

		电视频道记忆功能、交通灯倒计时时间的设定、户外LED广告的记忆功能都有可能用到E^2^PROM这类存储器件。这类器件的优势是存储的数据不仅可以改变,而且掉电后数据保存不丢失,因此大量应用在各种电子产品上。
		本节课的例子有点类似广告屏。上电后,1602的第一行显示E^2^PROM从0x20地址开始的16个字符,第二行显示E^2^PROM从0x40开始的16个字符。可以通过UART串口通信来改变E^2^PROM内部的这个数据,并且同时也改变了1602显示的内容,下次上电的时候,直接会显示更新过的内容。
		这个程序所有的相关内容前面都已经讲过了。但是这个程序体现在了一个综合应用能力上。这个程序用到了1602液晶、UART串口通信、E^2^PROM读写操作等多个功能的综合应用。写个点亮小灯好简单,但是想真正学好单片机,必须学会这种综合程序的应用,实现多个模块同时参与工作。因此要认认真真地把工程建立起业,一行一行地把程序编写起来,最终巩固下来。
/************************************************I2C.c文件程序源代码*****************************************************/

在这里插入图片描述在这里插入图片描述在这里插入图片描述

/************Lcd1602.c文件程序源代码/
在这里插入图片描述在这里插入图片描述
/*********eeprom.c文件程序源代码/
在这里插入图片描述在这里插入图片描述

/****Uart.c文件程序源代码/

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

/main.c文件程序源代码*/
在这里插入图片描述
在这里插入图片描述

在学习UART通信的时候,刚开始也是用的I/O口去模拟UART通信过程,最终实现和计算机的通信,而后因为STC89C52内部具备UART硬件通信模块,所以直接可以通过配置寄存器就可以很轻松地实现单片机的UART通信。同样的道理,这个I2C通信,如果单片机内部有硬件模块,单片机可以直接自动实现I2C通信了,就不需要再进行I/O口模拟起始、模拟发送、模拟结束,配置好寄存器,单片机就会把这些工作全部做了。
不过STC89C52单片机内部不具备I2C的硬件模块,所以使用STC89C52进行I2C通信必须用I/O口来模拟。使用I/O口模拟I2C,实际上更有利于读者彻底理解透彻I2C通信的实质。当然,通过学习I/O口模拟通信,今后如果遇到内部带I2C模块的单片机,也应该很轻松地搞定,使用内部的硬件模块,可以提高程序的执行效率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

manyoftenvictory

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值