9.1 用I/O口模拟I2C总线实现AT24C16的读写 9.1.1、实例功能 I2C总线(Inter Integrate Circuit BUS)全称为芯片间总线,是Philips公司推出的一种双向二进制总线。它在芯片间以两根连线实现全双工同步数据传送,一条数据线(SDA)和一条串行时钟线(SCL),可以很方便的构成外围器件扩展系统。 I2C总线协议允许总线介入多个期间,总线上的器件既可以作为主控制器也可以作为被控制器,既可以是发送器,也可以是接收器。I2C总线在进行数据交换时,作为主控制器的器件需要通过总线竞争获得主控权,然后才可以启动数据传输。系统中每个器件都具有唯一的芯片地址,数据传输时通过寻址可以确定数据接收方。 I2C总线自从出现以后,得到了广泛应用。I2C总线结构简单,可靠性和抗干扰性好,可构成各种通用的硬件和软件模块。方便重复利用,大大简化了系统的设计过程。 I2C总线的实现有两种方法:一、软件模拟I2C通信协议实现数据传输,二、利用硬件I2C接口实现数据传输。 ATmega16单片机集成了硬件I2C模块,称为TWI接口,TWI电路结构简单,只占用两个I/O口,可以实现多个器件共享一条总线,使用比较方便,系统也很简洁。AVR单片机用硬件实现了这种总线的时序,省去了很多编程工作。只要控制相关的寄存器,就可以实现通过TWI总线传输数据。 但是使用硬件I2C接口的缺点是接口固定,在特定的系统里面,会增加硬件和软件设计的复杂程度。在本例中我们采用模拟I2C总线时序的方法实现I2C通信。软件模拟I2C时序的方法增加了软件的复杂程度,但是方便了硬件设计,模拟I2C接口可以使用单片机的如何普通I/O口。 本节首先介绍I2C总线的的一些基本知识:特点、结构、原理、控制时序、与单片机的接口方法等。最后通过一个实例实现模拟I2C接口。 本实例分为三个功能模块,分别描述如下: ● 单片机系统:利用ATmega16单片机与AT24C16实现数据传输,利用模拟I2C总线接口的方法读写AT24C16。 ● 外围电路:外围电路分两部分:LED显示部分(用于指示从AT24C16中读取的数值正确与否)、AT24C16接口电路电路(实现模拟I2C总线功能)。 ● 软件程序:编写软件,实现对AT24C16的数据读写。 通过本实例的学习,掌握以下内容: ● 理解AT24C16的特点、结构和原理和接口设计方法。 ● 掌握AT24C16的控制时序和控制方法流程。 ● 掌握模拟I2C总线的设计方法。 9.1.2 器件和原理 1、I2C总线介绍 AT24C16的外形级封装和引脚说明如图9.1.1。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg 图9.1.1 AT24C16的外形封装和引脚说明 I2C总线协议规定,任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器。主器件控制串行时钟和起始、停止信号的发生。主器件何从期间都可以发送或接收数据,但是主器件控制数据传送模式(发送或者接收)。 通过器件地址输入端A0、A1、A2可以实现讲最多8个at24c01器件和a424c02器件、4个at24c04器件、2个at24c08器件、1个at24c16器件连接到总线上。当总线上只有一个器件时,A0、A1、A2可以连接到地或者悬空。 WP写保护引脚:当该引脚连接到VCC,I2C器件内的内容被写保护(只能读)。如果允许对器件进行正常的读写,那么WP引脚需连接到地或者悬空。 2、I2C总线接口 I2C总线的信号线有两种: ●时钟线SCL。 ●数据线SDA。 SCL和SDA都是双向总线,I2C总线为同步传输串行总线结构,及总线上的数据信号完全与时钟同步。数据传输采用主从方式:主器件寻址从器件,启动总线数据传输,并产生时钟脉冲。总线传输中的所有状态及操作都有相应的编码,主器件依照这些协议编码自动地进行总线控制与管理。从器件接收主器件的请求并应答。数据传输结束后,主器件将总线释放。 当总线空闲时,SCL和SDA均为高电平。连接到总线上的器件的输出端口必须是漏极开路,任一器件输出低电平时,总线信号变低。即总线SCL和SDA上的信号都是线“与”的关系。 由于SDA和SCL的端口输出都是漏极开路,因此总线上必须连接上拉电阻。上拉电阻的大小与电源电压、传输速率等有关系。当传输速率为100KHz时,上拉电阻一般采用10K,对于400KHz的传输速率,上拉电阻可以采用2K欧姆。 9.1.3、I2C总线的寻址方式 I2C总线上的器件都是共用总线的,因此,主器件在进行数据传输前必须选择需要通信的从器件。即进行总线寻址。 I2C总线上所有外围器件都有唯一的地址,这个地址由器件地址和引脚地址两部分组成。共7位。器件地址是I2C器件固有的地址编码,器件出厂时已经给定,不可更改。引脚地址由I2C总线外围器件的地址引脚A0、A1、A2决定,根据其在电路中接电源正极、接地或者悬空的不同,形成不同的地址代码。引脚地址数也决定了同一器件可接入总线的最大数目。 地址位与一个方向位共同构成I2C总线器件寻址字节。寻址字节的格式如图所示;方向位(R/W)规定了总线上主器件与外围器件(从器件)的数据传输方向。当方向位R/W=1时,表示主器件读取从器件的数据;R/W=0时,表示主器件向从器件发送数据。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg 9.1.4、I2C总线的数据传输协议 I2C总线的数据传输遵循严格的时序格式,下面分别介绍数据传输过程中的格式。 1、起始信号、终止信号 在时钟线SCL为高电平期间,数据线SDA上出现高电平向低电平变化的下降沿时,被认为是起始信号。起始信号出现以后,后面才可以进行寻址或数据传输等。 在时钟信号SCL高电平期间,数据线SDA上出现由低电平到高电平变化的上升沿时,被认为是终止信号。终止信号一出现,所有总线操作都结束,主器件释放总线控制权。 起始信号、终止信号的时序如下图所示。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg 2、数据读写 当SCL为高电平期间,SDA上的数据必须保持不变,如果此时SDA上的电平发生变化,则会被认为是起始或者终止信号。只有在SCL为低电平期间,SDA上的数据才能发生变化。所以在进行读取SDA上的数据时,必须使SCL处于低电平。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image008.jpg 3、总线数据位 每次发送到I2C总线SDA上的数据必须是一个字节,传输的数据字节按照由高位到低位的顺序发送。在I2C总先启动后或应答信号后的第1-8个时钟脉冲,对应于要传送字节的8位数据。 I2C总线上的数据是伴随着时钟脉冲,一位一位的传送的,每位数据占一个时钟脉冲。在时钟线SCL高电平期间,数据线SDA的状态就表示要传送的数据,高电平为数据1,低电平为数据0.在数据传送时,数据线上数据的变化在时钟线为低电平时完成;而时钟线为高电平时,数据线必须保持稳定,否则数据线上的任何变化都被当成起始或者终止信号,从而导致数据传输停止。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image010.jpg 4、应答信号、非应答信号 I2C总线数据传送时,每传送一个字节数据后都必须有应答信号。应答信号由主器件产生。主器件在第9个时钟位上释放数据总线,使其处于高电平状态,此时从器件输出低电平拉低数据线产生应答信号。 在传送完一个字节数据后,在第9个时钟位上,从器件输出高电平为非应答信号。非应答信号的产生有两种情况: ● 当从器件正在进行其他处理而无法接收总线上的数据时,从器件不产生应答,此时从器件释放总线,将数据线置为高电平。这样,主器件可产生一个停止信号来终止总线传输数据。 ● 当主器件接收来自从器件的数据时,接收到最后一个字节数据后,必须给从器件发送一个非应答信号,使从器件释放数据总线。这样,主器件才可以发送终止信号,从而终止数据传送。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image012.jpg 5、数据传送格式 I2C总线协议规定了完整的数据传送格式。按照协议规定,数据的传输以主器件发送起始信号开始,然后发送寻址从器件的寻址字节。寻址字节共8位,前7位为被寻址的从器件(对于AT24C16来说,由于总线上只能连接1个AT24C16,所以对AT24C16的寻址字节,高4位固定为1010,1-3位则是其内部页地址的高3位),第0位是方向位。在寻址字节后面跟着操作地址,操作地址指明了对被寻址器件内部的某一位或某几位进行操作。操作地址之后跟的就是要传输的数据了,数据传输结束后,主器件发送一个终止信号以释放总线控制权。 数据可以单字节传输,也可以多字节传输,如果主器件希望继续占用总线,则可以不发送停止信号,马上再次发送起始信号,便可以进行新的操作。 9.1.5 AT24C16的内部数据存储结构 AT24C16内部有2048*8位的存储容量,即可以存储2K字节的数据。这2K字节被放在128个页内,每页存放16个字节。所以对AT24C16内部的访问需要11位地址(0-7ff)。对AT24C16访问时,按照页地址和页偏移量的方式进行访问。比如要访问第100页的第3个字节,则在发送寻址的时候,就要发送0X0643,其中页地址的高三位放在器件地址中。所以在编写程序对AT24C16第100页的第3个字节进行写数据的时候,步骤如下:1)发送起始信号;2)发送器件地址0XA6(1010 0110,1010是固定地址,011是页地址的高三位,0表示写操作);3)发送操作地址0X43(0100 0011,0100是页地址的低四位,0011是页地址偏移量,即第100页内的第三个字节,4)发送要写的数据,5)发送终止信号。 9.1.6、电路和连接 LED发光二极管电路已经在第一个实例中介绍过,本例中不再重复。本例中AT24C16与单片机的连接如图9.1.2所示,由于AT24C16的数据线、时钟线要求空闲状态为高电平,所以我在AT24C16的数据线和时钟线上分别加了4.7K的上拉电阻,如果不想接上拉电阻的话,可以使能PA2口的内部上拉功能,但是程序设计会有些不同。AT24C16的数据线SDA、时钟线SCL分别连到单片机的PC1、PC0口。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image014.jpg 图9.1.2 AT24C16电路 7.1.4、程序设计 1、程序功能 程序的功能是使用单片机的PC1、PC0口的模拟I2C总线时序实现对AT24C16的数据读写操作,然后用LED的亮灭指示读取数据的正确性。 2函数说明 本程序多个功能函数,分别是: ● AT24C16操作相关函数: void I2C_Init(void); //I2C端口初始化 unsigned char I2C_Start(void); //发送起始信号 void I2C_Stop(void); //发送结束信号 unsigned char I2C_WriteByte(unsigned char dat); //写一个字节 unsigned char I2C_ReadByte(unsigned char ack); //读一个字节 unsigned char EEPROM_ReadByte(unsigned intadd); //从固定地址读一字节 void EEPROM_WriteByte(unsigned int add,unsigned char data); //向固定地址写一字节● 延时相关函数: void Delayus(unsigned intlus); //us延时函数 void Delayms(unsigned intlms); //ms延时函数 由于WINAVR自带函数库中的延时函数使用起来很不方便,并且晶振频率不同,延时时间也有区别,所以本实例中自己写了两个延时函数。 3、使用WINAVR开发环境,使用的是外部12M的晶振,所以需要将makefile文件中的时钟频率修改为12M。另外在程序烧录到单片机的时候,熔丝位也要选择为外部12M晶振(注意是晶振,不是外部振荡器,一定不要选择错了,否则会导致单片机不能再烧写程序)。 4、程序说明。在本实例中我们首先了解了I2C总线的原理和特点,用模拟I2C总线接口的方式实现了对AT24C16的读写操作。 5、程序代码 #include <avr/io.h> //io端口寄存器配置文件,必须包含 #include <util/delay.h> //端口声明 /*注: AVR单片机I/O口模拟I2C总线时建议在外部连接上拉电阻 ,这样可通过改变I/O口输入输出方向的方式 来设置高低电平, 输出口保持不变(0) ,此时如DDRX寄存器为1则变成输出0,若DDRX为0,则I/O口 呈现高阻状态,但因外部的上拉电阻,总线相当于设置高电平,即通过设置DDRX的方式控制总线的高低 */ #define SCL_INPUT (DDRC &=~(1 << PC0)) //SCL设置为输入口 #define SCL_OUTPUT (DDRC |= (1<< PC0)) //SCL设置为输出口 #define SCL_LOW (PORTC &= ~(1<< PC0)) //SCL设置为输出低电平 #define SCL_HIGH (PORTC |= (1<< PC0)) //SCL设置为输出高电平 #define SCL_INDATA (PINC & (1<< PC0)) //读取SCL的端口状态 #define SDA_INPUT (DDRC &=~(1 << PC1)) //SDA设置为输入口 #define SDA_OUTPUT (DDRC |= (1<< PC1)) //SDA设置为输出口 #define SDA_LOW (PORTC &= ~(1<< PC1)) //SDA设置为输出低电平 #define SDA_HIGH (PORTC |= (1<< PC1)) //SDA设置为输出高电平 #define SDA_INDATA (PINC & (1<< PC1)) //读取SDA的端口状态 //变量声明 #define EEPROM_BUS_ADDRESS0xa0 //器件地址 //函数声明 void Delayus(unsigned intlus); //us延时函数 void Delayms(unsigned intlms); //ms延时函数 void I2C_Init(void); //I2C端口初始化 unsigned char I2C_Start(void); //发送起始信号 void I2C_Stop(void); //发送结束信号 unsigned char I2C_WriteByte(unsigned char dat); //写一个字节 unsigned char I2C_ReadByte(unsigned char ack); //读一个字节 unsigned charEEPROM_ReadByte(unsigned int add); //从固定地址读一字节 void EEPROM_WriteByte(unsignedint add,unsigned char data); //向固定地址写一字节 int main(void) //GCC中main文件必须为返回整形值的函数,没有参数 { unsignedchar i; PORTB= 0x00; DDRB= 0xFF; //端口PortB设为输出口,通过相应位LED的变化指示程序运行结果 I2C_Init(); //I2C端口初始化 EEPROM_WriteByte(0x01aa,0x5a); //向固定地址写一字节,向第26页的第十个字节写入数据0x5a i= EEPROM_ReadByte(0x01aa); //从固定地址读一字节 if(i== 0x5a) { PORTB|= 0x01; //读出的数据正确,则LED0点亮 } else { PORTB|= 0x02; //读出的数据不正确,则LED1点亮 } while(1) { } } //I2C初始化函数 void I2C_Init(void) { SCL_LOW; //SCL的PORT状态锁定为0,以后不再改变 SCL_INPUT; //SCL设置为输入口 SDA_LOW; //SDA的PORT状态锁定为0,以后不再改变 SDA_INPUT; //SDA设置为输入口 Delayus(10); } //I2C起始条件 unsigned char I2C_Start(void) { Delayus(10); SDA_INPUT; //SDA高电平 Delayus(10); //延时一段时间,使单片机时钟频率符合I2C时钟 SCL_INPUT; //SCL高电平 Delayus(10); SDA_OUTPUT; //SDA变低,产生由高到低的变化 Delayus(10); SCL_OUTPUT; //SCL变低,占用总线 Delayus(10); return1; } //I2C结束条件 void I2C_Stop(void) { Delayus(10); SDA_OUTPUT; //SDA低电平 Delayus(10); SCL_INPUT; //SCL高电平 Delayus(10); SDA_INPUT; //SDA变为高电平,产生由低到高的变化 Delayus(10); } //向I2C写一个字节 unsigned char I2C_WriteByte(unsigned char dat) { unsignedchar i,ack; //ack为应答信号 for(i= 0;i < 8; i++) //写8位(1个字节)数据 { if(dat& 0x80) //写入数据,左移,从最高位写入 { SDA_INPUT; //如果该位为1,SDA拉高电平 } else { SDA_OUTPUT; // 如果该位为0,SDA拉低电平 } SCL_INPUT; //SCL高电平,保持数据 Delayus(10); SCL_OUTPUT; //SCL低电平,数据被送入I2C dat<<= 1; //需要写入的数据左移一位,送最高位 Delayus(10); // } Delayus(10); SDA_INPUT; //SDA拉高,同时变为输入口 Delayus(10); SCL_INPUT; //SCL拉高,准备读取应答信号 Delayus(10); if(SDA_INDATA) { ack= 0; //如果此时SDA为高,说明没有应答信号 PORTB|= 0x04; //没有应答信号,点亮LED2 } else { ack= 1; //如果此时SDA为低,说明有应答信号 PORTB|= 0x08; //有应答信号,点亮LED3 } SCL_OUTPUT; //SCL拉低 Delayus(10); returnack; //返回应答信号 } //从I2C读一个字节 unsigned char I2C_ReadByte(unsigned char ack) { unsignedchar i,dat = 0; //dat为读出的数据 SDA_INPUT; //SDA变为输入口 for(i= 0;i < 8;i++) //读出8位(1个字节)数据 { Delayus(10); SCL_OUTPUT; //SCL低,这时允许从I2C发送数据到SDA上 Delayus(10); SCL_INPUT; //SCL高,准备读取SDA上的数据 Delayus(10); dat<<= 1; //读取的数据左移一位,从最高位读起 if(SDA_INDATA) { dat++; //如果DSA为高,则读取的数据加1 } Delayus(10); } SCL_OUTPUT; //SCL拉低,准备下一个数据变化 Delayus(10); if(!ack) // { SDA_INPUT; //发送NACK } else { SDA_OUTPUT; //发送ACK } Delayus(10); SCL_INPUT; //SCL高 Delayus(10); SCL_OUTPUT; //SCL低 Delayus(10); return(dat); //返回读到的数据 } //从固定地址读一字节 unsigned charEEPROM_ReadByte(unsigned int add) { unsignedchar data; I2C_Start(); //发送起始信号 I2C_WriteByte(EEPROM_BUS_ADDRESS | ((add>> 8) << 1)); //写器件地址和页地址的高3位 I2C_WriteByte(add); //写页地址的低4位和页地址内部偏移量 I2C_Start(); //发送起始信号 I2C_WriteByte(EEPROM_BUS_ADDRESS | 0x01); //发送读命令 data= I2C_ReadByte(0); //读一个字节 I2C_Stop(); //发送结束信号 returndata; } //向固定地址写一字节 void EEPROM_WriteByte(unsignedint add,unsigned char data) { I2C_Start(); //发送起始信号 I2C_WriteByte(EEPROM_BUS_ADDRESS | ((add>> 8) << 1)); //写器件地址和页地址的高3位 I2C_WriteByte(add); //写页地址的低4位和页地址内部偏移量 I2C_WriteByte(data); //写一个字节数据 I2C_Stop(); //发送结束信号 Delayms(10); } //us级别的延时函数 void Delayus(unsigned int lus) { while(lus--) { _delay_loop_2(3); //_delay_loop_2(1)是延时4个时钟周期,参数为3则延时12 //个时钟周期,本实验用12M晶体,则12个时钟周期为12/12=1us } } //ms级别的延时函数 void Delayms(unsigned int lms) { while(lms--) { Delayus(1000); //延时1ms } } 9.2 基于硬件接口的AT24C02的读写实验 9.2.1、实例功能 AVR单片机提供了实现标准2先串行总线通信TWI(兼容I2C总线)硬件接口。其主要性能和特点有: ● 简单,但是强大而灵活的串行通信接口,只需要两根线; ● 支持主机和从机操作; ● 器件可以作为发送器,也可以作为接收器; ● 7位地址空间,最大允许有128个从机; ● 支持多主机模式; ● 最高可达400K的数据传输率; ● 全可编程的从机地址 ● 地址监听中断可以是AVR单片机从休眠状态唤醒。 AVR的TWI是支持I2C通信的硬件接口,使用硬件接口的优点是可以直接使用硬件接口完成I2C通信,而不必使用I/O口模拟I2C的时序。比软件模拟要简单,代码短,效率高。 本实例包括三个功能模块,分别介绍如下: ● 单片机系统:利用ATmega16单片机与AT24C16通信,实现数据的读写,并使用LED指示读写数据是否正确。 ● 外围电路:外围电路分两部分:LED显示电路(实现指示读写数据是否正确)、AT24C16通信电路(实现对AT24C16的读写操作)。 ● 软件程序:编写软件,实现利用AVR的硬件I2C接口对AT24C16进行数据读写数据功能。 通过本实例的学习,掌握以下内容: ● 理解AVR单片机硬件I2C接口的特点、原理。 ● 掌握AVR单片机硬件I2C接口的控制时序和控制方法流程。 ● 掌握使用查询方法对AT24C16进行操作的软件编程。 9.2.2 器件和原理 上一示例中我们已经对I2C总线的特点、原理进行了了解,并且通过利用I/O口模拟I2C总线时序的方法实现对AT24C16的读写。 在本例中我们学习使用AVR提供的硬件I2C接口。 1、TWI模块描述 AVR的TWI模块由总线接口单元、比特率发生器、地址匹配单元和控制单元等模块构成。 ● SDA和SCL引脚 SDA和SCL是AVR单片机TWI接口的引脚。引脚的输出驱动器包含一个波形斜率限制器,以满足TWI规范;引脚的输入部分包含尖峰抑制但愿,以去除小于50ns的毛刺。 ●波特率发生器 TWI工作在主控器模式时,又该控制单元产生TWI时钟信号,并驱动时钟线SCL, ●总线接口单元 这个单元包括:数据和地址移位寄存器、起始、停止信号控制和总线仲裁判定的硬件电路。 ● 地址匹配单元 地址匹配单元将检测总线上接收到的地址是否与TWAR寄存器中的7位地址相匹配。如果匹配成功,将通知控制单元转入适当的操作状态。TWI可以响应,也可以不响应主控器对其的寻址访问。 ● 控制单元 控制单元监听TWI总线,并根据TWI控制寄存器的设置作出相应的响应。 当在TWI总线上产生需要应用程序干预处理的事件时,先对TWI的中断标志位TWINT进行相应设置,在下一个时钟周期时,将表示这个事件的状态字写入TWI状态寄存器TWSR中。在其他情况下,TWSR中的内容为一个表示无事件发生的状态字。一旦TWINT标志位置1,就会将时钟线SCL拉低,暂停TWI总线上的传送,让用户程序处理事件。 在下列事件出现时,TWINT标志位设为“1”: ● 在TWI传送完一个起始或再次起始(START/RESTART)信号后; ● 在TWI传送完一个主控器寻址读/写(SLA+R/W)数据后; ● 在TWI传送完一个地址字节后; ● 在TWI丢失总线控制权后; ● 在TWI被主控器寻址(地址匹配成功)后; ● 在TWI接收到一个数据字节后; ● 在作为被控器时,TWI接收到停止或再次起始信号后; ● 由于非法的起始或停止信号造成总线上的冲突出错时。 2、TWI寄存器 1)、波特率寄存器 TWBR file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image016.jpg TWBR用于设置波特率发生器的分频因子,波特率发生器是一个频率分频器,当工作在主控器模式下,它产生和提供SCL引脚上的时钟信号。 在主机模式下,TWBR的值应大于10,否则可能会产生不正确的输出。尤其在中断模式下,TWBR的值应大于10。 2)、控制寄存器 TWCR file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image018.jpg TWCR用来控制TWI操作。例如使能TWI接口;在总线上加起始和终止信号;产生ACK应答等。 ● 位7–TWINT:TWI中断标志位。当TWI接口完成当前工作并期待应用程序响应时,该位被置位。如果全局中断控制位和TWCR寄存器中的TWIE位都置位,则MCU将跳到TWI中断向量处。一旦TWINT标志位被置位,,时钟线SCL将被拉位低。在执行中断服务程序时,TWINT标志位不会由硬件自动清零,必须通过由软件将该位写为“1”来清零,清零TWINT标志位将开始TWI接口的操作,因此对TWI寄存器TWAR、TWI状态寄存器TWSR、TWI数据寄存器TWDR的访问,必须在清零TWINT标志位前完成。 ● 位6—TWEA:TWI应答(ACK)允许位。TWEA位控制应答ACK信号的发生。如果TWEA位置1,则在器件作为主控器接收器时,接收到一个数据字节时, ACK脉冲将在TWI总线上发生。如果清零TWEA位,将使器件暂时虚拟地脱离TWI总线。 ● 位5—TWSTA:TWI起始(START)信号状态位。当要将器件设置为串行总线上的主控器时,须设置TWSTA位为1.TWI借口硬件将检查总线是否空闲。如果总线空闲,将在总线上发送一个起始信号;如果总线不空闲,则TWI将等待总线上一个停止信号被检测后,再发出一个新的起始信号,以获得总线的控制权而成为主控器。当起始信号发出后,硬件将自动清零TWSTA位。 ● 位4—TWSTO:TWI停止(STOP——信号状态位。当芯片工作在主控模式时,设置TWSTO位为1,将在总线上产生一个终止信号。当终止信号发出后,TWSTO位将被自动清零。 ● 位3—TWWC:TWI写冲突标志位。当TWINT为0时,试图向TWI写数据,TWWC位将被置1;当TWINT位为1时,写数据时,TWWC由硬件自动清零。 ● 位2—TWEN:TWI允许位。TWEN用于使能TWI接口操作和激活TWI接口。该位置1,则TWI接口模块将I/O引脚PC0、PC1转换为SCL和SDA引脚。如果该位清零,则TWI模块将被关闭,所有TWI传输将被终止,PC0、PC1转换为普通I/O引脚。 ● 位1—保留位。读出总为0; ● 位0—TWIE:TWI中断使能位。当该位为1,并且全局终端也使能时,只要TWINT标志为1,TWI中断请求就使能。 4)状态寄存器 TWSR file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image020.jpg ● 位【7:3】--TWS:TWI状态位。这5位反映了TWI逻辑状态和TWI总线的状态。不同的状态码表示不同的操作状态,具体可查询数据手册获得。注意,从TWSR寄存器中读取的值包括了5位状态值和2位预分频值。因此,在检查状态位时,应该将预分频器位屏蔽,使状态检验与预分频器无关。 ● 位2—保留。读出始终为0; ● 位【1:0】--TWPS:TWI预分频器位。这些位能读能写,用于设置波特率的预分频率。如下表所示。 file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image022.jpg 5)数据寄存器 TWDR file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image024.jpg 在发送模式下,TWDR寄存器的内容为下一个要传送的字节;在接受模式下,TWDR寄存器的内容为最后接受的字节。当TWI不处在字节移位操作过程时,该寄存器可以被读写即当TWI中断标志位置位时,该寄存器可以被写入。在第一次TWI中断发生前,数据寄存器不能由用户初始化。 6)(被控器)地址寄存器TWAR file:///C:/Users/m/AppData/Local/Temp/msohtmlclip1/01/clip_image026.jpg TWAR寄存器高7位的内容为被控器的7位地址字。当TWI设置为被控接收器或被控发送器时,在TWAR中应设置被控器寻址地址。而在主控器模式下,不需要设置TWAR。 9.2.3、电路连接 本例中的电路连接与上一实例完全相同,在此不再说明。 需要提醒的是,在AT24C16的接口电路中,我们在电路板上增加了两个上拉电阻,这两个上拉电阻分别接在SCL和SDA引脚上。 9.2.4 利用硬件TWI接口实现I2C总线的读写 1、程序功能 程序的功能是使用单片机的硬件TWI接口实现I2C总线的读写操作,然后将读出数据通过LED指示正确与否。硬件TWI接口可以使用查询和中断两种方式实现I2C总线的读写,在本实例中我们使用查询法实现,下一实例中我们再讨论使用中断方式的操作。 2函数说明 本程序多个功能函数,分别是: ● TWI硬件i2c总线操作相关函数: void I2C_Init(void); //I2C端口初始化 unsigned char I2C_Start(void); //发送起始信号 void I2C_Stop(void); //发送结束信号 unsigned char I2C_WriteByte(unsigned char dat); //写一个字节 unsigned char I2C_ReadByte(unsigned char ack); //读一个字节 unsigned char EEPROM_ReadByte(unsigned intadd); //从固定地址读一字节 void EEPROM_WriteByte(unsigned intadd,unsigned char data); //向固定地址写一字节 void EEPROM_ReadPage(unsigned intadd,unsigned char n,unsigned char *data); //多字节读操作 void EEPROM_WritePage(unsigned intadd,unsigned char n,unsigned char *data); //多字节写操作 ● 延时相关函数: void Delayus(unsigned intlus); //us延时函数 void Delayms(unsigned int lms); //ms延时函数 由于WINAVR自带函数库中的延时函数使用起来很不方便,并且晶振频率不同,延时时间也有区别,所以本实例中自己写了两个延时函数。 3、使用WINAVR开发环境,使用的是外部12M的晶振,所以需要将makefile文件中的时钟频率修改为12M。另外在程序烧录到单片机的时候,熔丝位也要选择为外部12M晶振(注意是晶振,不是外部振荡器,一定不要选择错了,否则会导致单片机不能再烧写程序)。 4、程序说明 在本实例中我们不但实现了使用AVR单片机的硬件TWI接口,通过查询方法实现了对AT24C16的读写,并且通过控制LED的亮灭来指示读出的数据是否正确。 在程序中我们使用查询法实现对AT24C16的读写,写操作的具体步骤是: 1)TWI寄存器配置; 2)发送START信号; 3)轮询等待TWINT置位,TWINT置位表示START信号已发出; 4)发送写器件地址,到TWDR寄存器,清零TWINT标志位,等待TWINT再次置位,再次置位表示数据已发送完成; 5)读取总线状态是否是器件地址发送完成并收到ACK; 6)发送数据地址,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成; 7)读取总线状态是否是器件地址发送完成并收到ACK; 8)发送数据字节,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成; 9)读取总线状态是否是器件地址发送完成并收到ACK; 10)数据写操作结束,发送终止信号。 读操作的具体步骤是: 1)TWI寄存器配置; 2)发送START信号; 3)轮询等待TWINT置位,TWINT置位表示START信号已发出; 4)发送写器件地址,到TWDR寄存器,清零TWINT标志位,等待TWINT再次置位,再次置位表示数据已发送完成; 5)读取总线状态是否是器件地址发送完成并收到ACK; 6)发送数据地址,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示数据已发送完成; 7)读取总线状态是否是器件地址发送完成并收到ACK; 8)发送重复开始(RESTART)信号,并清零TWINT标志位,然后等待TWINT再次置位,再次置位表示发送重复开始信号完成; 9)发送读数据信号,并等待发送完成ACK,判断总线状态是否正确; 10)读取TWDR的值,并根据是否读取完最后一个字节,发送ACK或NACK; 11)数据读取完毕,发送终止信号。 5、程序代码 由于本实例的程序量比较大,这里就不再列出了,直接放在附件里面。 9.3 使用中断方法实现TWI总线的读写 9.3.1、实例功能 在前面的实例中我们分别学习了使用模拟I2C总线和硬件TWI接口实现对AT24C16的读写,这两种方法都存在编程比较复杂,代码执行效率低等缺点。在本实例中我们将学习使用中断方法实现对AT24C16的读写;中断方法的优点是执行效率高,不占用CPU过多时间,从而使CPU能有更多的时间处理别的任务,非常适合实时多任务系统中使用。 本实例包括三个功能模块,分别介绍如下: ● 单片机系统:利用ATmega16单片机的硬件TWI接口实现对AT24C16的读写,并将读取出来的数据通过串口发送到计算机,同时利用LED指示读出的数据是否正确。 ● 外围电路:外围电路分三部分:串口电路部分(实现将读取的数值送到计算机的功能)、LED显示电路(指示读取的数据是否正确)、AT24C16接口电路(实现对AT24C16的读写)。 ● 软件程序:编写软件,实现使用中断方法对AT24C16进行数据的读写。 通过本实例的学习,掌握以下内容: ● 理解使用中断方法实现TWI总线的接口设计方法和程序设计。 9.3.2、器件和原理 前面的实例中我们已经了解了TWI总线的使用方法以及I2C总线的通信协议和操作时序,在此我们不再重复。 9.3.3、电路连接 本实例的电路由三部分:串口转换电路、LED显示电路、AT24C16接口电路。这些电路我们在前面已经介绍过,在此不再说明。 需要提醒的是,在AT24C16的接口电路中,我们在电路板上增加了两个上拉电阻,这两个上拉电阻分别接在SCL和SDA引脚上。 9.3.4 程序设计 1、程序功能 程序的功能是使用单片机的TWI硬件接口,实现在中断方式下对AT24C16的读写,然后将读出数据通过串口发送到计算机,并且在电路板上通过LED的亮灭指示读取的数据是否正确。 2函数说明 本程序多个功能函数,分别是: ● 端口初始化函数,设置各端口的初始工作状态。 ● 串口通信相关函数: void Usart_Init(void); //USART寄存器设置 void Usart_PutChar(unsigned charcTXData); //字节发送函数 这些函数已经在前面的实例中做过介绍,在此不再重复。 ● AT24C16操作相关函数: void I2C_Init(void); //I2C端口初始化 unsigned char EEPROM_ReadWrite(unsigned charI2C_Add,unsigned int add, unsignedchar n,unsigned char *data); //读写数据操作,参数分别是:器件地址,数据地址,数据长度,读写的数据 ● 延时相关函数: void Delayus(unsigned intlus); //us延时函数 void Delayms(unsigned intlms); //ms延时函数 由于WINAVR自带函数库中的延时函数使用起来很不方便,并且晶振频率不同,延时时间也有区别,所以本实例中自己写了两个延时函数。 3、使用WINAVR开发环境,使用的是外部12M的晶振,所以需要将makefile文件中的时钟频率修改为12M。另外在程序烧录到单片机的时候,熔丝位也要选择为外部12M晶振(注意是晶振,不是外部振荡器,一定不要选择错了,否则会导致单片机不能再烧写程序)。 4、程序说明 在本实例中我们不但实现了使用AVR单片机的硬件TWI接口,通过中断方法实现了对AT24C16的读写,并且将读出的数据通过串口发送到计算机进行显示,同时通过控制LED的亮灭来指示读出的数据是否正确。 使用中断方法的处理步骤和查询法的步骤基本一致,不同之处在于在中断方式下不用使程序在等待中断标志位置位时一直处于等待状态,而是在中断标志置位后自动进入中断处理函数。
|