ATmega32单片机片内集成了TWI(Two-wire Serial Interface)总线接口。TWI总线与I 14.1
TWI模块介绍
14.1.1
TWI总线
TWI总线的结构如图14-1所示。TWI总线由数据线SDA和时钟线SCL构成。一般来说,与TWI总线连接的器件都是漏极开路或集电极开路结构,所以两根总线均需通过电阻上拉到Vcc,这样的结构可以确保总线上任意一个器件在输出低电平时会将总线拉低,从而实现“线与”的功能。TWI总线工作在7位地址模式时,总线上最多允许挂接128个器件,每个器件均有唯一的地址。在总线上,主机是指控制启动和停止数据传输、并且在SCL线上产生时钟的器件。从机则是指被主机寻址,在总线上受主机控制的器件。主机和从机都可以发送或接收数据。
14.1.2
TWI总线的数据传输格式
1.数据帧格式
TWI总线协议规定总线上在进行数据位传输时需要与时钟脉冲同步,在数据传送过程中,当时钟线为高电平时,数据线上的电平必须保持稳定。TWI总线数据位的传输格式如图14-2所示。
TWI总线上的一个数据帧由9个位构成,包括8个数据位和1个应答位,数据的高位在前,低位在后,总线上发送数据的一方称为发送器,接收数据的一方称为接收器。当8位的数据位发送完毕后,接收器在第9个时钟到来前拉低SDA线以产生应答信号(ACK),当接收器由于某种原因不能继续接收数据时,可以在第9个时钟到来时不拉低SDA线,即产生一个非应答(NACK)信号,以此通知发送器不要继续发送数据。TWI总线上数据帧格式如图14-3所示。
图14-3中所示的主从器件共同作用的SDA线状态是SDA线在发送器和接收器二者共同作用的结果,发送器控制的SDA线是发送器对SDA线作用的结果,接收器控制的SDA线是接收器对SDA线作用的结果,而主机控制的SCL线是主机产生的时钟信号。
2.起始条件和停止条件
TWI总线协议规定在时钟线SCL为高电平时,数据线SDA从高电平向低电平的跳变被定义为起始条件。当时钟线SCL为高电平时,数据线SDA从低电平向高电平的跳变被定义为停止条件。重复起始条件的定义与起始条件完全相同。
TWI总线上数据传输都是以主机产生的起始条件(START)开始,以主机产生的停止条件(STOP)终止,在起始条件之后TWI总线被认为进入了忙状态。有时为了需要,也可以在一个起始条件产生后,再次产生一个起始条件,我们称之为重复起始条件(REPEATED START)。同样,当重复起始条件产生后,总线仍然被认定为忙状态。TWI总线的起始条件、停止条件和重复起始条件定义如图14-4所示。
3.地址帧格式
TWI总线协议规定一个地址帧由9位构成,即7位的从器件地址、1位读写控制位和1位应答位。主机首先将7位的从器件地址按高位在前,低位在后的顺序依次放置在总线上,之后主机发送一个读写控制位,读写控制位为1时,表示接下来主机要对从机进行读操作,而当读写控制位为0时,表示接下来主机要对从机进行写操作。从机在检测到自身被寻址后,在第9个时钟周期开始前将SDA线拉低做出应答(ACK)。TWI总线上传送的地址帧格式如图14-5所示。
在ATmega32单片机作为从机使用时,上述7位的地址位均可由用户软件定义,但也有些问题需要注意。从机地址0000000是系统的保留地址,该地址是作为广播呼叫地址使用的,当主机需要发送相同的信息给总线上所有从器件时,即可以使用广播呼叫功能。当主机在发送0000000+0时,表示要对总线上所有从器件进行写操作,所有从器件都会在第9个时钟到来前将SDA线拉低作出应答,并且会将主机随后发送的数据写入从器件内部。主机发送0000000+1,即对所有器件进行读操作是没有意义的,所以此地址操作无效。另外,从机地址1111XXX为保留地址,暂时没有被定义,也不能使用。
4.最小数据传输格式
TWI在进行数据通信时,一个最小的数据传输格式是由起始条件、从器件地址、R/W位、至少一个数据包以及一个停止条件构成的,具体格式如图14-6所示。
在数据传送时,主机首先发送起始条件,启动本次数据传输。之后,主机发送要寻址的从器件地址+R/W位。从机被寻址后,在第9个时钟到来时将SDA线拉低,产生应答(ACK)信号,随后按照主机发送的地址包的读写要求,从机或者将自身的数据放置到总线上供主机读取,或者将主机发出的数据写入从机内部。无论是主机还是从机,只要是作为接收数据的一方,在数据接收完毕后都会将SDA线拉低产生一个应答(ACK)信号。
另外,当主机的时钟频率设定远高于从机所能接受的速度极限时,从机会利用总线“线与”的特点,在没有完成数据响应时将SCL线拉低,使时钟因低电平时间延长而降低总线上的数据传输速率。
14.1.3
TWI模块的内部结构
ATmega32单片机的TWI模块内部结构如图14-7所示。从图中我们可以看出,TWI模块的结构可以分成以下5个部分。
1.SDA和SCL引脚
SDA引脚是TWI模块的数据端,SCL引脚是TWI模块的时钟端。当TWI模块被使能时,无需指定这两个引脚的数据方向,引脚的普通I/O口功能会被TWI功能取代,其内部的上拉电阻会使能,因此总线上可以不用外接上拉电阻。
2.总线接口单元
单元内部包含有地址/数据移位寄存器TWDR、专用于存储应答位的寄存器(N)ACK寄存器、START/STOP控制器和硬件的总线仲裁判定电路。移位寄存器TWDR可以通过软件访问,但(N)ACK寄存器只能通过两线串行接口控制寄存器TWCR的TWEA位来对其进行设定。START/STOP控制器用于在总线上产生起始和停止条件,总线仲裁判定电路在TWI模块配置成主机时会持续监听总线,以确定是否可以通过仲裁来获得总线控制权。
3.比特率发生器
当TWI模块工作在主机模式时,比特率发生器控制时钟线(SCL)上的时钟频率,频率值与状态寄存器TWSR的TWPS1:TWPS0位所设定的预分频系数及比特率寄存器TWBR的值有关。
4.地址匹配单元
地址匹配单元用于检测从总线上接收到的地址是否与TWAR寄存器中保存的7位地址相匹配,一旦地址匹配成功,即可通知控制单元作出响应。当单片机处于休眠状态时,地址匹配单元仍然可以工作,一旦寻址成功,单片机即可从休眠模式中唤醒。
5.控制单元
控制单元用于监听总线,并按设置要求作出响应。当TWI总线上产生了需要应用程序干预的事件时,控制单元会将中断标志位TWINT置位,在下一个时钟周期,会将所发生事件的状态码硬件写入TWI的状态寄存器TWSR中。当TWINT标志位置1时,SCL线会被控制单元拉低,暂停总线上的数据传输,使用户有时间处理所发生的事件。
当以下事件发生时,TWINT标志位会置位:
·主机产生START或REPEATED START条件后。
·传送完SLA+R/W数据之后。
·传送完地址字节后。
·总线仲裁失败之后。
·被主机寻址之后。
·接收到一个数据字节之后。
·在作为从机工作时,TWI模块接收到STOP或REPEATED START信号之后。
·由于非法的START或STOP信号造成的总线错误。
14.2
TWI模块的应用
14.2.1
TWI模块的控制寄存器
1.TWBR寄存器
该寄存器是TWI模块的比特率寄存器,用于保存设定的分频比,以此来控制TWI模块的比特率。比特率发生器只是一个分频器,它将系统时钟分频后在SCL线上产生时钟信号。
TWBR寄存器:比特率寄存器
SCL线上的时钟频率不仅与比特率寄存器TWBR的值有关,还与状态寄存器TWSR的TWPS1:TWPS0位所设定的预分频系数有关,具体计算方式详见下式:
在上式中,TWBR是TWI比特率寄存器的值,而TWPS则是TWI状态寄存器预分频值。这里需要注意的是,在主机模式下,TWBR的值不能小于10,否则会从SDA和SCL线上输出错误的信号。
2.TWCR寄存器
该寄存器是TWI模块的控制寄存器,包含有多个控制位。
TWCR寄存器:TWI控制寄存器
bit 7TWINT:TWI的中断标志位。该位置1时表示产生了TWI相关事件的中断,当中断产生时,SCL线将保持低电平,时钟暂停。如果此时SREG的I位和TWCR寄存器的TWIE位均置1,则表示该中断得到允许,单片机会跳转到TWI中断向量地址处开始执行中断服务程序。
在中断服务程序执行后,TWINT位不能硬件自动清零,需要通过软件向该位写1才能清零此位。TWINT位也被用于启动TWI模块的运行,当TWINT位清零时,TWI模块会立即开始下一步工作,因此在清零TWINT位之前要先完成对地址寄存器TWAR、状态寄存器TWSR,以及数据寄存器TWDR的数据写入。
bit 6TWEA:应答信号使能位。该位置1时,出现如下条件时总线上会产生应答信号:
1)单片机的从机地址与主机发出的地址相符。
2)广播呼叫功能得到允许后,单片机接收到广播呼叫。
3)在主机或从机模式下接收到一个字节的数据。
bit 5TWSTA:起始信号使能位。该位置1时,TWI模块会硬件判断总线状态,一旦总线空闲,就会在总线上产生一个起始条件。如果总线忙,TWI模块就会持续监视总线,直到检测到一个停止信号后,再产生起始信号,以表明自己需要成为主机。
bit 4TWSTO:停止信号使能位。在主机模式下,该位置1时,TWI模块会在总线上产生一个停止条件。停止条件产生后,TWSTO位自动硬件清零。在从机模式下,将该位置1会使TWI接口从错误状态恢复到未被寻址的状态,但总线上不会有停止条件产生。
bit 3TWWC:写冲突标志位。当TWINT清零时,将数据写入TWDR将会使TWWC位置1,表示产生了写入冲突,只有当TWINT位置1时,TWI模块的移位寄存器为空闲状态,此时才能软件写入TWDR寄存器。
bit 2TWEN:TWI模块使能位。该位置1时激活TWI端口,并将引脚配置成SCL和SDA状态。
bit 1未定义。
bit0TWIE:TWI模块的中断允许位。该位置1时使能TWI中断,此时如果TWINT位置1且全局中断得到允许,单片机则会执行中断服务程序。
3.TWSR寄存器
该寄存器是TWI模块的状态寄存器,用于指示TWI模块的工作状态和对SCL预分频器进行设置。
TWSR寄存器:TWI状态寄存器
bit 7-3TWS7:TWS3:TWI模块的状态位,用于反映TWI逻辑和总线的状态,具体状态代码详见表14-1。
bit 2未定义。
bit 1-0TWPS1:TWPS0:TWI模块预分频比选择位,用于控制时钟线上比特率的值,具体设置详见表14-2。
4.TWDR寄存器
该寄存器是TWI模块的数据寄存器,在发送模式下,TWDR寄存器用于存储待发送的数据;在接收模式下,TWDR保存已经接收到的数据。当TWI中断标志位TWINT置1后,TWDR寄存器中的数据才是稳定的,所以对TWDR寄存器的读写需要在TWINT置1后进行。
TWDR寄存器:TWI数据寄存器
bit 7-0TWD7:TWD0:TWI数据寄存器。
5.TWAR寄存器
该寄存器是TWI模块的地址寄存器,当单片机作为从机使用时,TWAR寄存器的高7位包含了从机的地址,TWGCE位用于使能广播地址识别功能。
TWAR寄存器:TWI(从机)地址寄存器
bit 7-1TWA6:TWA0:TWI模块从机地址。
bit 0TWGCE:使能TWI广播识别。该位置1后MCU可以识别TWI总线广播地址呼叫。
14.2.2
TWI模块的编程向导
TWI接口在数据传送过程中是基于中断的,所有的总线事件发生后会产生相应的TWI中断。中断发生后,TWINT标志位会置1,表示TWI接口完成了当前的操作,等待应用程序的干预。此时TWI状态寄存器TWSR中保存了表明当前TWI总线状态的值,用户可以软件读取TWSR以判断总线当前的状态。
TWCR寄存器的TWI中断允许位TWIE和SREG寄存器的全局中断允许位共同决定了应用程序是否响应TWINT标志位产生的中断请求。在中断服务程序执行后,TWINT位不能硬件自动清零,需要通过软件向该位写1才能清零该位。TWI模块在配置成主机时,基础的数据传送是从对从机的寻扯开始的,主机向从机写入一个字节数据的过程如图14-8所示。
在图14-7所示的写入过程中,可以分成以下几个步骤:
1)发送START信号。软件向TWCR寄存器的TWEN位写1使能TWI模块;软件向TWCR寄存器的TWINT位写1以清零此标志位,否则TWI模块不会动作;软件将TWCR寄存器的TWSTA位置1,即可在总线上发送START信号;如果需要使用中断,还需将TWCR寄存器的TWIE位置1,以上步骤可以使用一条语句完成。
2)检验START信号是否正确发送。START信号成功发送后,TWCR寄存器的TWINT标志位置位,TWSR寄存器的内容更新为相应的状态码,软件读取TWSR寄存器的值,屏蔽掉预分频位,确定START信号已成功发送。如果TWSR的状态码显示为其他状态,则可启用错误处理程序进行干预。
3)发送地址包。当状态码与预期的相同时,用户可以软件将SLA+W(从器件的地址+写入位)的值写入TWI数据寄存器TWDR中,之后软件向TWINT位写1以清零此标志位,TWI模块的地址包即可开始传送。
4)检验地址包是否正确发送。地址包发送后,TWCR寄存器的TWINT标志位置1,TWSR更新为新的状态码,同时该状态码还会反映从机是否响应了主机的寻扯。
5)发送数据包。软件读取状态寄存器TWSR,以确定地址包已成功发送,且从器件已成功应答(ACK为期望值),否则将作出错处理。如果状态码与预期一致,用户可以将待发送的数据写入数据寄存器TWDR中,之后软件向TWINT标志位写1以清零此标志位,TWI模块的数据包开始在总线上传送。
6)检验数据包是否正确发送。数据包发送后,TWCR寄存器的TWINT标志位置1,TWSR更新为新的状态码,此状态码还会反映从机是否响应了主机的数据传送。
7)发送STOP信号。软件读取状态寄存器TWSR,以确定地址包已成功发送,且从器件已成功应答(ACK为期望值),否则作出错处理。如果状态码与预期一致,软件向TWCR寄存器的TWINT位写1以清零此标志位,之后软件将TWCR寄存器的TWSTO位置1,发送STOP信号。
注意:中断标志位TWINT在STOP状态发送后不会硬件置位。
14.3
DS1307实时时钟
DS1307是美国DALLAS公司推出的基于I 14.3.1
DS1307的功能
DS1307与ATmega32单片机的通信电路如图14-9所示。图中DS1307采用双电源供电,主供电电源连接至芯片的Vcc引脚,辅助供电电池连接于芯片的V DS1307具有以下功能:
1)DS1307具有产生时、分、秒、日、月、年等功能,闰年可自动调整,日历和时钟数据以BCD码的方式存放在片内的寄存器上。
2)片内集成了56字节的具有掉电后电池保持的RAM数据存储器,可以用来保存一些关键数据。
3)可编程的方波信号输出。
4)芯片具有掉电检测和自动切换电池供电功能,当DS1307靠后备电池维持工作时,拒绝CPU对其的读出和写入操作。
14.3.2
DS1307的寄存器
DS1307片内有多个时间保持寄存器,单片机就是通过读取这些寄存器得到时间和日期的相关数据的,其中有8个寄存器专门用于存储时间信息,另外,56个字节的RAM可以供用户自由使用。DS1307内部将这些寄存器进行统一编址,具体地址和寄存器数据组织格式详见表14-3。
DS1307的时间保持寄存器具有以下特点:
1)时间和日历信息在时间保持寄存器上的存放是以BCD码的形式存放的,以分钟寄存器为例,分的十位存放在寄存器的高四位,分的个位存放在寄存器的低四位,关于BCD码的详细内容请参考本书第17章。我们可以通过软件对这些时间寄存器进行初始化,从而将时间值校准。
2)日期和星期寄存器的值会在每天的午夜递增,我们需要在程序中指定星期与星期寄存器值的对应关系,也就是说如果把星期寄存器值为1时定义为星期一,那么当读取的值为7时就应当是星期日。
3)寄存器0(地址为00h)的第7位是时钟停止位CH。当这一位置1时,时钟振荡器会被禁止,该位清零时,振荡器会使能。
4)芯片在第一次加电时,时间和日期会被复位成:01月01日00年、星期01、00时00分00秒,秒寄存器的CH位会被设定为1。我们在对时间值进行设定时,需将CH位清零以使能振荡器。
5)DS1307可以设定运行在12小时模式或24小时模式。小时寄存器的bit6是定义12小时或24小时模式选择位。当该位置1时,时钟被设定为12小时模式,清零时为24小时模式。
6)在时钟设定为12小时模式下,小时寄存器的bit5是AM/PM位,其值为0时表示AM,为1时表示PM;在24小时模式下bit5是20小时位(20–23)。
7)控制寄存器(07h)用于对DS1307
SQW/OUT引脚状态的设定。
bit 7OUT:输出控制位。当方波输出禁止时(SQWE=0),OUT位置1,SQW/OUT引脚输出高电平,OUT位清零,SQW/OUT引脚输出低电平。
bit 6-5:未用位。
bit 4SQWE:方波输出使能位。该位置1时,使能方波输出,频率取决于RS1:RS0位的状态;SQWE位清零时禁止方波输出。当器件被初始化时,SQWE位被清零。
bit 3-2:未用位。
bit 1-0RS1:RS0:输出频率选择位。具体设定详见表14-4。
DS1307的时间寄存器地址编码为00h–07h,而具有掉电保护电路的RAM寄存器地址编码为08h–3Fh。在读写过程中DS1307内部维护一个地址指针,每读或写一个字节地址都会自动加一,当指针指向RAM末尾时,指针将返回到00h地址处。
14.3.3
DS1307的数据格式
DS1307在I2C总线上是从器件,其地址固定为“1101000”。主器件按如下顺序将数据写入到DS1307寄存器或内部RAM中:
1)主器件产生起始信号。
2)主器件发送寻址字节,格式为“从器件地址+W位”,即11010000,DS1307应答。
3)主器件发送1个字节的内存地址,DS1307应答。
4)主器件发送数据字节,DS1307应答。
5)主器件可以连续发送多个数据字节,DS1307每收到一个字节都会应答,而且每一个字节写入后,其内部地址计数器会自动加一。
6)主器件产生停止信号。
写DS1307的数据格式如图14-10所示。
主器件按如下顺序将数据从DS1307寄存器或内部RAM中读出:
1)主器件产生起始信号。
2)主器件发送寻址字节,格式为从器件地址+W位(11010000),DS1307应答。
3)主器件发送1个字节内存地址,DS1307应答。
4)主器件再次发送寻址字节,格式为从器件地址+R位(11010001),DS1307应答。
5)DS1307输出指定地址的一个字节的数据,主器件应答。
6)DS1307输出指定地址加1的一个字节的数据,主器件应答。
7)DS1307输出指定地址加n的一个字节的数据,主器件非应答。
8)主器件产生停止信号。
读DS1307的数据格式如图14-11所示。
14.3.4
TWI模块编程实例
TWI模块的运行需要在软件的控制下进行,为了进一步掌握TWI总线的通信过程,我们要综合本章前面所学的知识,使用DS1307芯片制作出一款实时时钟。按照本章图14-8所示的电路,制作DS1307的单元板,并用杜邦线将板上的SDA与SCL引脚与AVR系统板上的相应引脚连接起来,本书为了简化过程,使用了实时时钟模块作为目标器件,该时钟模块的外观如图14-12和图14-13所示。
硬件连接好以后,打开Atmel Studio 6.1软件,新建名为“TWI”的项目,保存在chapter14文件夹下,软件会自动将名为TWI.c的源文件添加到新建的项目中。编辑TWI.c源文件,具体代码详见代码清单14-1。
代码清单14-1
TWI方式驱动DS1307实时时钟
/*
*
TWI.c *
TWI 方式驱动DS1307
实时时钟
*
Created: 2013/10/24
19:41:16
*
Author: GAO */
#include
//
包含AVR 头文件
#include
//
包含AVR 中断控制头文件
#define F_CPU 16000000UL //
定义系统时钟
#include
//
包含延时函数头文件
#define uchar unsigned char #define uint unsigned int unsigned char table0[]={0x3f,0x06,0x5b,0x4f,0x66, 0x6d,0x7d,0x07,0x7f,0x6f }; //
共阴无点
unsigned char table1[]={0xbf,0x86,0xdb,0xcf,0xe6, 0xed,0xfd,0x87,0xff,0xef }; //
共阴有点
void display(void); //
显示函数声明
void twi_init(void); //TWI 初始化函数声明
void ERROR(void); //
总线错误函数声明
void write_onechar(uchar chip_add,uchar reg_add,uchar data ); //
写一个字节函数声明
uchar read_onechar(uchar chip_add,uchar reg_add); //
读一个字节函数声明
unsigned char TIME,SEC,SEC10,MIN,MIN10,HOUR,HOUR10; //
定义时间、秒、分、时全局变量
int main(void)
{
DDRA=0xFF; //
设数码管段驱动端为输出
DDRB=0xF0; //
设数码管位驱动端为输出
DDRC=0x00; //TWI 端口配置(TWI 端口无需指定方向)
twi_init(); //
初始化TWI write_onechar(0xD0,0x00,0x00
); //
初始化秒寄存器(00
)
_delay_ms(2); //
延时2ms write_onechar(0xD0,0x01,0x00
); //
初始化分寄存器(00
)
_delay_ms(2); //
延时2ms write_onechar(0xD0,0x02,0x12
); //
初始化时寄存器(12
)
_delay_ms(2); //
延时2ms write_onechar(0xD0,0x07,0x10
); //
初始化控制式寄存器1Hz 输出
_delay_ms(2); //
延时2ms while(1)
{
TIME=read_onechar(0xD0,0x00); //
读秒寄存器
SEC=TIME&0x0F; //
去掉高四位并赋值给SEC TIME=TIME>>4; //
把TIME 的值右移四位
SEC10=TIME; //
赋值给SEC10
display(); //
执行显示函数
TIME=read_onechar(0xD0,0x01); //
读分寄存器
MIN=TIME&0x0F; TIME=TIME>>4; MIN10=TIME; display(); TIME=read_onechar(0xD0,0x02); //
读时寄存器
HOUR=TIME&0x0F; TIME=TIME>>4; HOUR10=TIME; display(); }
}
/**********
数码管显示函数**********/
void display(void)
{
unsigned char NUM4,NUM3,NUM2,NUM1; NUM1=MIN; NUM2=MIN10; NUM3=HOUR; NUM4=HOUR10; PORTA=table0[NUM1]; PORTB|=0x10; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB&=0xEF; _delay_ms(1); //
延时1ms PORTA=table0[NUM2]; PORTB|=0x20; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB&=0xDF; _delay_ms(1); //
延时1ms if(SEC%2==1)
//
点闪烁
{
PORTA=table0[NUM3]; PORTB|=0x40; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB&=0xBF; _delay_ms(1); //
延时1ms }
else {
PORTA=table1[NUM3]; PORTB|=0x40; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB&=0xBF; _delay_ms(1); //
延时1ms }
PORTA=table0[NUM4]; PORTB|=0x80; _delay_ms(2); //
延时2ms PORTA=0x00; PORTB&=0x7F; _delay_ms(1); //
延时1ms }
/**********
出错函数**********/
void ERROR(void)
{
MIN=4; //
出错时显示-1414
MIN10=1; HOUR=4; HOUR10=1; }
/**********TWI 初始化函数**********/
void twi_init(void)
{
TWBR=0x20; //TWBR 值为32
TWSR=0; //1:1
预分频,速率200K TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT }
/**********
向DS1307
指定地址写一个字节数据函数**********/
void write_onechar(uchar chip_add,uchar reg_add,uchar data )
{
/*
发送START */
TWCR=0xA4; //TWCR=1010
0100
使能TWI ,清零标志位TWINT ,发送START while((TWCR&0x80)==0); //
等待START 发送完成
if((TWSR&0xF8)!=0x08)
//START 已发送
{
ERROR(); }
/*
发送从器件地址
*/
TWDR=chip_add|0x00; //
发送从器件地址
R/W =0
;
TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT ,并启动发送
while((TWCR&0x80)==0); //SLA+W 已发送,等待中断产生
if((TWSR&0xF8)!=0x18)
//SLA+W 已发送,并接收到ACK {
ERROR(); }
/*
发送从器件寄存器地址
*/
TWDR=reg_add; //
发送从器件寄存器地址
TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT ,并启动发送
while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x28)
//
数据已发送,并接收到ACK {
ERROR(); }
/*
发送写入从器件数据
*/
TWDR=data; //
发送写入从器件数据
TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT, 并启动发送
while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x28)
//
数据已发送,并接收到ACK {
ERROR(); }
/*
发送STOP 信号
*/
TWCR=0x94; //TWCR=1001
0100
使能TWI ,清零标志位TWINT ,发送STOP _delay_ms(10); //
延时10ms }
/**********
从DS1307
指定地址读一个字节数据函数**********/
uchar read_onechar(uchar chip_add,uchar reg_add)
{
uchar TEMP; //
定义临时变量
/*
发送START */
TWCR=0xA4; //TWCR=1010
0100
使能TWI ,清零标志位TWINT ,发送START while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x08)
//START 已发送
{
ERROR(); }
/*
发送从器件地址
*/
TWDR=chip_add|0x00; //
发送从器件地址
R/W =0
TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT, 并启动发送
while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x18)
//SLA+W 已发送,并接收到ACK {
ERROR(); }
/*
发送从器件寄存器地址
*/
TWDR=reg_add; //
发送从器件寄存器地址
TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT ,并启动发送
while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x28)
//
数据已发送,并接收到ACK {
ERROR(); }
/*
发送重复START */
TWCR=0xA4; //TWCR=1010
0100
使能TWI ,清零标志位TWINT ,发送重复START while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x10)
//
重复START 已发送
{
ERROR(); }
/*
发送从器件地址(R/W =1
)表示读
*/
TWDR=chip_add|0x01; //
发送从器件地址
R/W =1
,表示读
TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT ,并启动发送
while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x40)
//SLA+R 已发送,接收到ACK {
ERROR(); }
/*
清零标志位TWINT, 等待接收数据, 并NACK */
TWCR=0x84; //TWCR=1000
0100
使能TWI ,清零标志位TWINT, 并NACK while((TWCR&0x80)==0); //
等待中断产生
if((TWSR&0xF8)!=0x58)
//
接收到数据,已产生NOT ACK {
ERROR(); }
/*
读出接收的数据
*/
TEMP=
TWDR; //
读出接收的数据
/*
发送STOP 信号
*/
TWCR=0x94; //TWCR=1001
0100
使能TWI ,清零标志位TWINT ,发送STOP _delay_ms(5); //
延时10ms return TEMP; //
把读到的值返回
}
/**********
结束**********/
成功编译以上代码后,将其下载到AVR系统板中,程序运行后的效果如图14-14所示。
数码管显示当前时间为“12.00”,时与分之间的小数点会以秒为单位闪烁,指示时间的运行。在本驱动程序中,没有对DS1307时钟芯片的年、月、日等信息进行读取,通过对程序进行修改,你可以将以上相关信息全部读取出来,并用适当的方式显示出来,即可制成一部数字显示的万年历,怎么样,时钟芯片的功能是不是很强大?