AVR学习笔记九基于AT24C16的数据存储实验.docx
《AVR学习笔记九基于AT24C16的数据存储实验.docx》由会员分享,可在线阅读,更多相关《AVR学习笔记九基于AT24C16的数据存储实验.docx(29页珍藏版)》请在冰豆网上搜索。
AVR学习笔记九基于AT24C16的数据存储实验
Ema{@AVR学习笔记九、基于AT24C16的数据存储实验
-------基于LT_Mini_M16
9.1用I/O口模拟I2C总线实现AT24C16的读写
9.1.1、实例功能
I2C总线(InterIntegrateCircuitBUS)全称为芯片间总线,是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。
图9.1.1AT24C16的外形封装和引脚说明
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时,表示主器件向从器件发送数据。
9.1.4、I2C总线的数据传输协议
I2C总线的数据传输遵循严格的时序格式,下面分别介绍数据传输过程中的格式。
1、起始信号、终止信号
在时钟线SCL为高电平期间,数据线SDA上出现高电平向低电平变化的下降沿时,被认为是起始信号。
起始信号出现以后,后面才可以进行寻址或数据传输等。
在时钟信号SCL高电平期间,数据线SDA上出现由低电平到高电平变化的上升沿时,被认为是终止信号。
终止信号一出现,所有总线操作都结束,主器件释放总线控制权。
起始信号、终止信号的时序如下图所示。
2、数据读写
当SCL为高电平期间,SDA上的数据必须保持不变,如果此时SDA上的电平发生变化,则会被认为是起始或者终止信号。
只有在SCL为低电平期间,SDA上的数据才能发生变化。
所以在进行读取SDA上的数据时,必须使SCL处于低电平。
3、总线数据位
每次发送到I2C总线SDA上的数据必须是一个字节,传输的数据字节按照由高位到低位的顺序发送。
在I2C总先启动后或应答信号后的第1-8个时钟脉冲,对应于要传送字节的8位数据。
I2C总线上的数据是伴随着时钟脉冲,一位一位的传送的,每位数据占一个时钟脉冲。
在时钟线SCL高电平期间,数据线SDA的状态就表示要传送的数据,高电平为数据1,低电平为数据0.在数据传送时,数据线上数据的变化在时钟线为低电平时完成;而时钟线为高电平时,数据线必须保持稳定,否则数据线上的任何变化都被当成起始或者终止信号,从而导致数据传输停止。
4、应答信号、非应答信号
I2C总线数据传送时,每传送一个字节数据后都必须有应答信号。
应答信号由主器件产生。
主器件在第9个时钟位上释放数据总线,使其处于高电平状态,此时从器件输出低电平拉低数据线产生应答信号。
在传送完一个字节数据后,在第9个时钟位上,从器件输出高电平为非应答信号。
非应答信号的产生有两种情况:
●当从器件正在进行其他处理而无法接收总线上的数据时,从器件不产生应答,此时从器件释放总线,将数据线置为高电平。
这样,主器件可产生一个停止信号来终止总线传输数据。
●当主器件接收来自从器件的数据时,接收到最后一个字节数据后,必须给从器件发送一个非应答信号,使从器件释放数据总线。
这样,主器件才可以发送终止信号,从而终止数据传送。
5、数据传送格式
I2C总线协议规定了完整的数据传送格式。
按照协议规定,数据的传输以主器件发送起始信号开始,然后发送寻址从器件的寻址字节。
寻址字节共8位,前7位为被寻址的从器件(对于AT24C16来说,由于总线上只能连接1个AT24C16,所以对AT24C16的寻址字节,高4位固定为1010,1-3位则是其内部页地址的高3位),第0位是方向位。
在寻址字节后面跟着操作地址,操作地址指明了对被寻址器件内部的某一位或某几位进行操作。
操作地址之后跟的就是要传输的数据了,数据传输结束后,主器件发送一个终止信号以释放总线控制权。
数据可以单字节传输,也可以多字节传输,如果主器件希望继续占用总线,则可以不发送停止信号,马上再次发送起始信号,便可以进行新的操作。
9.1.5AT24C16的内部数据存储结构
AT24C16内部有2048*8位的存储容量,即可以存储2K字节的数据。
这2K字节被放在128个页内,每页存放16个字节。
所以对AT24C16内部的访问需要11位地址(0-7ff)。
对AT24C16访问时,按照页地址和页偏移量的方式进行访问。
比如要访问第100页的第3个字节,则在发送寻址的时候,就要发送0X0643,其中页地址的高三位放在器件地址中。
所以在编写程序对AT24C16第100页的第3个字节进行写数据的时候,步骤如下:
1)发送起始信号;2)发送器件地址0XA6(10100110,1010是固定地址,011是页地址的高三位,0表示写操作);3)发送操作地址0X43(01000011,0100是页地址的低四位,0011是页地址偏移量,即第100页内的第三个字节,4)发送要写的数据,5)发送终止信号。
9.1.6、电路和连接
LED发光二极管电路已经在第一个实例中介绍过,本例中不再重复。
本例中AT24C16与单片机的连接如图9.1.2所示,由于AT24C16的数据线、时钟线要求空闲状态为高电平,所以我在AT24C16的数据线和时钟线上分别加了4.7K的上拉电阻,如果不想接上拉电阻的话,可以使能PA2口的内部上拉功能,但是程序设计会有些不同。
AT24C16的数据线SDA、时钟线SCL分别连到单片机的PC1、PC0口。
图9.1.2AT24C16电路
7.1.4、程序设计
1、程序功能
程序的功能是使用单片机的PC1、PC0口的模拟I2C总线时序实现对AT24C16的数据读写操作,然后用LED的亮灭指示读取数据的正确性。
2函数说明
本程序多个功能函数,分别是:
●AT24C16操作相关函数:
voidI2C_Init(void);//I2C端口初始化
unsignedcharI2C_Start(void);//发送起始信号
voidI2C_Stop(void);//发送结束信号
unsignedcharI2C_WriteByte(unsignedchardat);//写一个字节
unsignedcharI2C_ReadByte(unsignedcharack);//读一个字节
unsignedcharEEPROM_ReadByte(unsignedintadd);//从固定地址读一字节
voidEEPROM_WriteByte(unsignedintadd,unsignedchardata);//向固定地址写一字节●延时相关函数:
voidDelayus(unsignedintlus);//us延时函数
voidDelayms(unsignedintlms);//ms延时函数
由于WINAVR自带函数库中的延时函数使用起来很不方便,并且晶振频率不同,延时时间也有区别,所以本实例中自己写了两个延时函数。
3、使用WINAVR开发环境,使用的是外部12M的晶振,所以需要将makefile文件中的时钟频率修改为12M。
另外在程序烧录到单片机的时候,熔丝位也要选择为外部12M晶振(注意是晶振,不是外部振荡器,一定不要选择错了,否则会导致单片机不能再烧写程序)。
4、程序说明。
在本实例中我们首先了解了I2C总线的原理和特点,用模拟I2C总线接口的方式实现了对AT24C16的读写操作。
5、程序代码
#include//io端口寄存器配置文件,必须包含
#include
//端口声明
/*注:
AVR单片机I/O口模拟I2C总线时建议在外部连接上拉电阻,这样可通过改变I/O口输入输出方向的方式
来设置高低电平,输出口保持不变(0),此时如DDRX寄存器为1则变成输出0,若DDRX为0,则I/O口
呈现高阻状态,但因外部的上拉电阻,总线相当于设置高电平,即通过设置DDRX的方式控制总线的高低
*/
#defineSCL_INPUT(DDRC&=~(1<#defineSCL_OUTPUT(DDRC|=(1<#defineSCL_LOW(PORTC&=~(1<#defineSCL_HIGH(PORTC|=(1<#defineSCL_INDATA(PINC&(1<#defineSDA_INPUT(DDRC&=~(1<#defineSDA_OUTPUT(DDRC|=(1<#defineSDA_LOW(PORTC&=~(1<#defineSDA_HIGH(PORTC|=(1<#defineSDA_INDATA(PINC&(1<//变量声明
#defineEEPROM_BUS_ADDRESS0xa0//器件地址
//函数声明
voidDelayus(unsignedintlus);//us延时函数
voidDelayms(unsignedintlms);//ms延时函数
voidI2C_Init(void);//I2C端口初始化
unsignedcharI2C_Start(void);//发送起始信号
voidI2C_Stop(void);//发送结束信号
unsignedcharI2C_WriteByte(unsignedchardat);//写一个字节
unsignedcharI2C_ReadByte(unsignedcharack);//读一个字节
unsignedcharEEPROM_ReadByte(unsignedintadd);//从固定地址读一字节
voidEEPROM_WriteByte(unsignedintadd,unsignedchardata);//向固定地址写一字节
intmain(void)//GCC中main文件必须为返回整形值的函数,没有参数
{
unsignedchari;
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初始化函数
voidI2C_Init(void)
{
SCL_LOW;//SCL的PORT状态锁定为0,以后不再改变
SCL_INPUT;//SCL设置为输入口
SDA_LOW;//SDA的PORT状态锁定为0,以后不再改变
SDA_INPUT;//SDA设置为输入口
Delayus(10);
}
//I2C起始条件
unsignedcharI2C_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结束条件
voidI2C_Stop(void)
{
Delayus(10);
SDA_OUTPUT;//SDA低电平
Delayus(10);
SCL_INPUT;//SCL高电平
Delayus(10);
SDA_INPUT;//SDA变为高电平,产生由低到高的变化
Delayus(10);
}
//向I2C写一个字节
unsignedcharI2C_WriteByte(unsignedchardat)
{
unsignedchari,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读一个字节
unsignedcharI2C_ReadByte(unsignedcharack)
{
unsignedchari,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);//返回读到的数据
}
//从固定地址读一字节
unsignedcharEEPROM_ReadByte(unsignedintadd)
{
unsignedchardata;
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;
}
//向固定地址写一字节
voidEEPROM_WriteByte(unsignedintadd,unsignedchardata)
{
I2C_Start();//发送起始信号
I2C_WriteByte(EEPROM_BUS_ADDRESS|((add>>8)<<1));
//写器件地址和页地址的高3位
I2C_WriteByte(add);//写页地址的低4位和页地址内部偏移量
I2C_WriteByte(data);//写一个字节数据
I2C_Stop();//发送结束信号
Delayms(10);
}
//us级别的延时函数
voidDelayus(unsignedintlus)
{
while(lus--)
{
_delay_loop_2(3);
//_delay_loop_2
(1)是延时4个时钟周期,参数为3则延时12
//个时钟周期,本实验用12M晶体,则12个时钟周期为12/12=1us
}
}
//ms级别的延时函数
voidDelayms(unsignedintlms)
{
while(lms--)
{
Delayus(1000);//延时1ms
}
}
9.2基于硬件接口的AT24C02的读写实验
9.2.1、实例功能
AVR单片机提供了实现标准2先串行总线通信TWI(兼容I2C总线)硬件接口。
其主要性能和特点有:
●简单,但是强大而灵活的串行通信接口,只需要两根线;
●支持主机和从机操作;
●器件可以作为发送器,也可以作为接收器;
●7位地址空间,最大允许有128个从机;
●支持多主机模式;
●最高可达400K的数据传输率;
●全可编程的从机地址
●地址监听中断可以是A