51单片机IIC驱动EEPROM存储方案应用------day9
1.常见存储器件: 铁电, E2PROM, FLASH。
共同特点: 掉电后数据不丢失
各自特点:
铁电: 理论上可以无限次擦写, 操作简单, 但是容量小。
E2PROM: 理论上擦写次数在30W到100W的不等,操作简单, 容量中等。 应用: 考勤机。
FLASH: 理论上擦写次数在10W到100W不等, 容量很大, 但操作较复杂, 若要改变一个字节就要改变整个扇区。 STC单片机的EEPROM是由FLASH模拟的。
2.IIC总线
I2C总线最主要的优点是其简单性和有效性。 由于接口直接在组件之上, 因此I2C总线占用的空间非常小, 减少了电路板的空间和芯片管脚的数量, 降低了互联成本。 I2C总线的另一个优点是, 它支持多主机, 其中任何能够进行发送和接收的设备都可以成为主机。 一个主控能够控制信号的传输和时钟频率。当然, 在任何时间点上只能有一个主机。
2、 I2C总线是由数据线SDA和时钟SCL构成的串行总线, 可发送和接收数据。 各种I2C均并联在这条总线上, 但就像电话机一样只有拨通各自的号码才能工作, 所以每个电路和模块都有唯一的地址。
3.硬件原理图:
WP为高电平时只能读不能写,防止误写。
WR为低电平时可读可写。
4.I2C的起始和停止
起始信号:SCL线为高电平期间, SDA线由高电平向低电平的变化表示起始信号;
停止条件:SCL线为高电平期间, SDA线由低电平向高电平的变化表示终止信号。
SCL是主裁判, SDA是副裁判。
//SCL为高电平时SDA由高电平变为低电平表示IIC开始信号
void I2C_Start_Fun(void)
{
I2C_SDA_DA = 1; //SDA置高
Delay_I2C(); // 延时子程序
I2C_SCL_CK = 1; //SCL置高
Delay_I2C(); // 延时子程序
I2C_SDA_DA = 0; //SDA置低
Delay_I2C(); // 延时子程序
I2C_SCL_CK = 0;
}
//SCL为高电平期间SDA由低电平变为高电平为停止信号
void I2C_Stop_Fu(void)
{
I2C_SCL_CK = 0;
Delay_I2C();
I2C_SDA_DA = 0;
Delay_I2C();
I2C_SCL_CK = 1;
Delay_I2C();
I2C_SDA_DA = 1;
Delay_I2C();
}
IIC数据传输:
SCL为高电平期间, 数据线上的数据必须保持稳定, 只有SCL信号为低电平期间, SDA状态才允许变化。
I2C与UART不同的地方首先在于先传高位, 后传送低位。 UART先传低位后传高位
UART发送时序如下图:低位在前
I2C先传高位后传低位时序如下图所示:
主机写数据时, 每发送一个字节, 接收机需要回复一个应答位“0”, 通过应答位来判断从机是否接收成功。
主机读数据时, 接收一个字节结束后, 主机也需要发送一应答位“0”, 但是当接收最后一个字节结束后, 则需发送一个非应答位(应答位取反)“1”, 发完了1后, 再发一个停止信号, 最终结束通信。
写数据流程:
首先I2C起始信号, 然后发送首字节, 即器件( EEPROM)地址。并且在读写方向位上选择“ 写操作” 方向。 (0xa0) <哪一个芯片>
第二个字节, 发送数据的存储地址, 就是要读取的数据所存
储在EEPROM中的位置。 <存在芯片的哪个位置>
第三个字节, 发送要存储的数据第一个字节, 第二个字
节……
在写数据的过程中, 都要等待EEPROM返回一个“ 应答位” 。
EEPROM的地址根据电路的A2A1A0决定,接地为0.高电平为1
最后一位R/W为读写位,读为1,写为0
故:写地址为0xa0 1010 0000
读地址为0xa1 1010 0001
/*****************************************************************************
** 函数名称:write_eeprom
** 功能描述:读取EEPROM数据函数(可多片共存)
** 输 入:E2中目的地址addr
** 输 出:读取的数据
******************************************************************************/
void write_eeprom(uchar addr, uchar databyte)
{
I2C_Start_Fun();
I2C_Write_Data(0xa0); //<哪一个芯片>
I2C_Write_Data(addr); // <存在芯片的哪个位置>
I2C_Write_Data(databyte);
I2C_Stop_Fu(); //停止位
}
/*****************************************************************************
** 函数名称:I2C_Write_Data
** 功能描述:向I2C总线发送一个字节数据,并检测应答
** 输 入:待发送字节byte
** 输 出:无
** 全局变量:无
** 调用模块:Delay_I2C()
** 可移植性:直接移植
******************************************************************************/
void I2C_Write_Data(uchar Byte)
{
uchar mask;
uchar i;
uchar j;
mask = 0x80;
for(i = 0; i < 8; i++) //发送8位数据
{
I2C_SCL_CK = 0; //SCL为低电平时才允许SDA数据变化
Delay_I2C(); //延时,保证时序稳定
if((mask & Byte) == 0) //1000 0000 &Byte 设Byte=95H 1000 0000& 1001 0101 如果最高位为0则SDA发送0 如果最高位为1则发送SDA为高
{
I2C_SDA_DA = 0;
}
else
{
I2C_SDA_DA = 1;
}
mask >>= 1; //0x40 0100 0000&1001 0101
Delay_I2C();
I2C_SCL_CK = 1; //拉高保持数据
Delay_I2C();
}
I2C_SCL_CK = 0;
I2C_SDA_DA = 1; //释放总线
Delay_I2C();
I2C_SCL_CK = 1;
j = I2C_SDA_DA; //读取应答位
Delay_I2C();
I2C_SCL_CK = 0; //方便下次使用
}
I2C总线通过上拉电阻接正电源。 当总线空闲时, 两根线均为高电平。 连到总线上的任一器件输出的低电平, 都将使总线的信号变低, 即各器件的SDA及SCL都是线“ 与” 关系(只要有一个器件是低电平总线都是低电平)。
IIC读数据流程:<根据手册的时序图来>
首先I2C起始信号, 然后发送首字节, 即器件( EEPROM)地址。 并且在读写方向位上选择“ 写操作” 方向。
第二个字节, 发送数据的存储地址, 就是要读取的数据所
存储在EEPROM中的位置。
第三个字节, 重新发送I2C起始信号和器件地址, 并且在方向位上选择**“ 读操作”** 方向。
在前三个字节操作过程, 都要等待器件给与回应一个“ 应答位0”
第四个字节, 接收从器件发回的首字节后, 单片机要主动返回一个“ 非应答位1”……
注意事项: 读数据前, 必须将SDA口置为1。
/*****************************************************************************
** 函数名称:read_eeprom
** 功能描述:读取EEPROM数据函数(可多片共存)
** 输 入:E2中目的地址addr
** 输 出:读取的数据
** 全局变量:无
** 调用模块:I2CStart(),I2CSend(),I2CStop()
******************************************************************************/
uchar read_eeprom(uchar addr)
{
uchar databyte;
I2C_Start_Fun(); //开始
I2C_Write_Data(0xa0); //<哪一个芯片>
I2C_Write_Data(addr);//将读取的地址告诉EEPROM
I2C_Start_Fun();//开始
I2C_Write_Data(0xa1); //发送读取命令
databyte = I2C_Read_LData(); //读取数据
I2C_Stop_Fu(); //停止
return databyte;
}
读取子函数:
/*****************************************************************************
** 函数名称:I2C_Read_Ack
** 功能描述:从I2C总线读取一个字节数据,并发送应答
** 输 入:无
** 输 出:接收到的字节byte
** 全局变量:无
** 调用模块:Delay_us()
** 可移植性:直接移植
******************************************************************************/
uchar I2C_Read_Ack(u8 ack)
{
uchar i;
uchar Byte;
Byte = 0;
for(i=0;i<8;i++)
{
I2C_SCL_CK = 0;
I2C_SDA_DA = 1;//先释放总线线与关系
Delay_I2C();
I2C_SCL_CK = 1;//高电平时才会把数据读取出来
Delay_I2C();
Byte <<= 1; //0x00<<1=0x00
if(I2C_SDA_DA==1) {Byte |= 0x01;}//0x00|0x01=0x01
Delay_I2C();
}
I2C_SCL_CK = 0;
Delay_I2C();
I2C_SDA_DA = 0; //发送应答位
Delay_I2C();
I2C_SCL_CK = 1;//保持数据
Delay_I2C();
I2C_SCL_CK = 0;//方便下次使用
return Byte;
}
/*****************************************************************************
** 函数名称:I2C_Read_LData
** 功能描述:从I2C总线读取最后一个字节数据,并发送非应答位
** 输 入:无
** 输 出:接收到的字节Byte
** 全局变量:无
** 调用模块:Delay_I2C()
** 可移植性:直接移植
******************************************************************************/
uchar I2C_Read_LData(void)
{
uchar Byte;
uchar i;
Byte = 0;
for(i = 0; i < 8; i++)
{
I2C_SCL_CK = 0;
I2C_SDA_DA = 1;
Delay_I2C();
I2C_SCL_CK = 1;
Delay_I2C();
Byte <<= 1;
if(I2C_SDA_DA == 1)
{
Byte |= 0x01;
}
Delay_I2C();
}
I2C_SCL_CK = 0;
I2C_SDA_DA = 1;
Delay_I2C();
I2C_SCL_CK = 1;
Delay_I2C();
I2C_SCL_CK = 0;
return Byte;
}
在第9个时钟时,数据发送端会释放SDA的控制权,由数据接收端控制SDA,若SDA为高电平,表示非应答信号(NACK)低电平表示应答信号ACK
程序源码:
IIC文件:
#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL_IO = P0^6;
sbit I2C_SDA_IO = P0^7;
/* 产生总线起始信号 */
void I2C_Start_Fun()
{
I2C_SDA_IO = 1; //首先确保SDA、SCL都是高电平
I2C_SCL_IO = 1;
I2CDelay();
I2C_SDA_IO = 0; //先拉低SDA
I2CDelay();
I2C_SCL_IO = 0; //再拉低SCL
}
/* 产生总线停止信号 */
void I2C_Stop_Fun()
{
I2C_SCL_IO = 0; //首先确保SDA、SCL都是低电平
I2C_SDA_IO = 0;
I2CDelay();
I2C_SCL_IO = 1; //先拉高SCL
I2CDelay();
I2C_SDA_IO = 1; //再拉高SDA
I2CDelay();
}
/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
bit I2C_Write_Fuc(unsigned char dat)
{
bit ack; //用于暂存应答位的值
unsigned char mask; //用于探测字节内某一位值的掩码变量
for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
{
if ((mask&dat) == 0) //该位的值输出到SDA上
I2C_SDA_IO = 0;
else
I2C_SDA_IO = 1;
I2CDelay();
I2C_SCL_IO = 1; //拉高SCL
I2CDelay();
I2C_SCL_IO = 0; //再拉低SCL,完成一个位周期
}
I2C_SDA_IO = 1; //8位数据发送完后,主机释放SDA,以检测从机应答
I2CDelay();
I2C_SCL_IO = 1; //拉高SCL
ack = I2C_SDA_IO; //读取此时的SDA值,即为从机的应答值
I2CDelay();
I2C_SCL_IO = 0; //再拉低SCL完成应答位,并保持住总线
return (~ack); //应答值取反以符合通常的逻辑:
//0=不存在或忙或写入失败,1=存在且空闲或写入成功
}
unsigned char I2C_Read_NAK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA_IO = 1;
for(mask=0x80; mask!=0; mask>>=1)
{
I2CDelay();
I2C_SCL_IO = 1;
if(I2C_SDA_IO == 0)
dat &= ~mask;
else
dat |= mask;
I2CDelay();
I2C_SCL_IO = 0;
}
I2C_SDA_IO = 1;
I2CDelay();
I2C_SCL_IO = 1;
I2CDelay();
I2C_SCL_IO = 0;
return dat;
}
unsigned char I2C_Read_ACK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA_IO = 1;
for(mask=0x80; mask!=0; mask>>=1)
{
I2CDelay();
I2C_SCL_IO = 1;
if(I2C_SDA_IO == 0)
dat &= ~mask;
else
dat |= mask;
I2CDelay();
I2C_SCL_IO = 0;
}
I2C_SDA_IO = 0;
I2CDelay();
I2C_SCL_IO = 1;
I2CDelay();
I2C_SCL_IO = 0;
return dat;
}
main.c文件
#include <reg52.h>
#include "inc/hc595.h"
#include "inc/delay.h"
code unsigned char ucDataOneTab[10] = {0x3f,0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f,0x6f};
//uchar code LEDSEG1[]= {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0xc6,0X89};//0~9,C,H,正
//uchar code LEDSEG2[]= {0xC0,0xCF,0xA4,0x86,0x8B,0x92,0x90,0xC7,0x80,0x82,0xF0,0X89};//0~9,C,H,倒
code unsigned char ucDataTwoTab[8] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F};
unsigned char disbuf[8] = {0x3f,0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07};
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
extern void I2C_Start_Fun();
extern void I2C_Stop_Fun();
extern unsigned char I2C_Read_NAK();
extern bit I2C_Write_Fuc(unsigned char dat);
unsigned char E2ReadByte(unsigned char addr);
void E2WriteByte(unsigned char addr, unsigned char dat);
void main()
{
unsigned char dat,i;
unsigned char str[10];
//E2WriteByte(0x02, 0); //再写回到对应的地址上
//InitLcd1602(); //初始化液晶
dat = E2ReadByte(0x02); //读取指定地址上的一个字节
str[0] = (dat/100); //转换为十进制字符串格式
str[1] = (dat/10%10) ;
str[2] = (dat%10) ;
//str[3] = '\0';
//LcdShowStr(0, 0, str); //显示在液晶上
dat++; //将其数值+1
E2WriteByte(0x02, dat); //再写回到对应的地址上
//disbuf[2] = ucDataOneTab[str[2]];
//disbuf[1] = ucDataOneTab[str[1]];
//disbuf[0] = ucDataOneTab[str[0]];
disbuf[2] = ucDataOneTab[str[2]];
disbuf[1] = ucDataOneTab[str[1]];
disbuf[0] = ucDataOneTab[str[0]];
while (1)
{
for (i = 0; i < 8; i++ )
{
SendData(disbuf[i], ucDataTwoTab[i]);
Delay1ms(1);
}
}
}
unsigned char E2ReadByte(unsigned char addr)
{
unsigned char dat;
I2C_Start_Fun();
I2C_Write_Fuc(0x50<<1);
I2C_Write_Fuc(addr);
I2C_Start_Fun();
I2C_Write_Fuc((0x50<<1) |0x01);
dat = I2C_Read_NAK();
I2C_Stop_Fun();
return dat;
}
void E2WriteByte(unsigned char addr, unsigned char dat)
{
I2C_Start_Fun();
I2C_Write_Fuc(0x50<<1);
I2C_Write_Fuc(addr);
I2C_Write_Fuc(dat);
I2C_Stop_Fun();
}