/**************** I2C通信之EEPROM(AT24C02)(掉电不丢失) **********************/
/*
一. EEPROM及其背景知识
1.1 ROM(只读存储器——硬盘), RAM(随机访问存储器——电脑内存), PROM(可重新编程的rom), EPROM(可擦除rom), EEPROM(电可擦除rom)
1.2 为什么需要eeprom———— 单片机内部的rom只能在程序下载时进行擦除和改写,但是程序运行本身不能改写的。
单片机内部的ram中的数据程序运行时可以改,但是掉电就丢失了。
有时候我们有一些数据要存在系统中,要求掉电不丢失,而且程序还要能改。
所以内部rom和ram都不行。 这个时候系统就需要一块eeprom。
存储器分为两大类:RAM和ROM。
RAM:全称“随机存取存储器”,也叫主存,是与CPU直接交换数据的内部存储器。它可以随时读写(刷新时除外),而且速度很快,
通常作为操作系统或其他正在运行中的程序的临时数据存储介质.。RAM在计算机和数字系统中用来暂时存储程序、数据和中间结果。即内存条。
ROM:全称“只读存储器”,只能读出事先所存数据。
RAM与ROM的最大区别:在于数据的易失性,RAM一旦断电所存储的数据将随之丢失,ROM掉电数据不丢失。
总结: RAM:运行内存存储 掉电丢失,可随机访问读写,速度快
ROM: 掉电不丢失 只读存储不支持修改
EEPROM:掉电不丢失且支持读写修改
1.3 eeprom和flash的区别与联系
(1)关系:FLASH属于EEPROM,EEPROM属于ROM,即ROM>EEPROM>FLASH。
(2)EEPROM和FLASH:EEPROM存数据变量,数据存储器,可改变,flash存程序,程序存储器,不可改变(因为难度大)
(3)FLASH分类:NOR FLASH 和NAND FLASH。 nor flash存储引导代码,用一个大容量的nand flash存放文件系统和内核,即固态
EEPROM是按功能分类的一种存储设备类型;flash是存储设备生产的一种工艺。EEPROM就可能采用了flash工艺,常见的U盘也是采用flash工艺。
这里的EEPROM芯片具体型号是AT24C02,采用I2C时序进行读写。
1.4 eeprom存在系统中的2种形式:
内置在单片机内部和外部扩展
1.5 eeprom如何编程分为(1)I2C接口底层时序
(2)上层时序器件定义的寄存器读写时序
二. 原理图和数据手册
接线: SDA SCL GND 三线
物理接口:SCL + SDA
SCL(serial clock):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。
SDA(serial data):数据线,通信数据都通过SDA线传输。
通信特征:串行、同步、非差分、低速率
I2C属于串行通信,所有的数据以位为单位在SDA线上串行传输。 先传高位,与spi先传低位不同
同步通信就是通信双方工作在同一个时钟下,一般是通信的A方通过一根CLK信号线传输A自己的时钟给B,B工作在A传输的时钟下。
所以同步通信的显著特征就是:通信线中有CLK。
非差分。因为I2C通信速率不高,而且通信双方距离很近,所以使用电平信号通信。
低速率。I2C一般是用在同一个板子上的2个IC之间的通信,而且用来传输的数据量不大,
所以本身通信速率很低(一般几百Kbps,不同的I2C芯片的通 信速率可能不同,具体在编程的时候要看自己所使用的
设备允许的I2C通信最高速率,不能超过这个速率)
注意:同一时间内,I2C的总线上只能传输一对设备的通信信息,所以同一时间只能有一个从设备和主设备通信,
其他从设备处于“休眠”状态,不能出来捣乱,否则通信就乱套了(广播然后匹配的思路)。
!!!关于I2C的上拉电阻:I2C协议规定,总线空闲时两根线都必须为高。!!!
三. I2C底层时序图和程序1
eeprom的时序方式与ds1302spi时序类似,但eeprom的时序依照I2C方式进行
I2C总结
I2C总线是一种同步、半双工,带数据应答的二线制串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
工作原理:
如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;
如果主机要接收从器件的数据,首先由主器件寻址从器件,然后主机接收从器件发送的数据,最后由主机终止接收过程。
在这种情况下.主机负责产生定时时钟和终止数据传送。
注意: 寻址和数据与spi相反 要先从高位开始
(1)主CPU和其附属芯片(I2C设备)之间最常用的接口,尤其是各种传感器,因此在物联网时代非常重要。
(2)三根线:GND、SCL、SDA,串行通信(只有一根数据传输线),电平式通信(相对于差分信号)。
(3)总线式结构,可以一对多,总线上可以挂上百个器件,用从地址来区分。
(4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应。
(类似访谈节目主持人和嘉宾对话的方式,主持人具有对话发起的权力,嘉宾只能被动回复)
(5)通信速率一般(kbps级别),不适合语音、视频等信息类型
3.1 EEPROM编程思路主要包含下面两部分:
(1)I2C接口底层时序。起始信号、停止信号、发送字节、读取字节。
(2)EEPROM的寄存器读写时序。
3.2 I2C从设备的地址
(1)从设备的地址是由从设备自身定义的,不同的从器件的地址定义方式是不同的,要查具体的芯片数据手册来确定。
(2)同一个I2C总线上只有一个主设备,但是从设备可以有多个。这多个从设备的从地址不能相同(硬件设计工程师必须保证这一点。
因为从地址是不能通过软件设定的)
(3)分析原理图和AT24C02的数据手册,得出: 从设备地址是:读地址:0xA1,写地址:0xA0.
四. I2C底层时序图和程序2
4.1 起始信号和结束信号
!!!! I2C时序: (1) scl=1时, sda由高电平到低电平(下降沿)为 时序开始
(2) 只有当scl=0时, sda是低电平或者是高电平才会被允许,即数据才允许被写入
(3) scl=1时, sda由低电平到高电平(上升沿)为 时序结束
I2C通信时的基本数据单位也是以字节为单位的,每次传输的有效数据都是1个字节(8位)。
起始位及其后的8个clk都是主设备在发送(主设备掌控总线),此时从设备只能读取总线来得知主设备发给它的信息;
然后到了第9周期,按照协议规定从设备需要发送ACK给主设备,所以此时主设备必须释放总线
(也就是主设备把SDA总线置为高电平然后不要动),同时从设备试图拉低总线发出ACK。
如果从设备拉低总线失败,或者从设备根本就没有拉低总线,则主设备看到的现象就是总线在第9周期仍然一直保持高,
这对主设备来说,意味着我没收到ACK,主设备就认为刚才给从设备发送的8字节不对(接收失败)
4.2 I2C发送一个字节
4.3 应答位处理
4.4 I2C接收一个字节
概念:释放总线 SDA=1就是释放总线; 在其他更高级的单片机(譬如STM32等)这里处理还会有点不一样
// 程序设计编译
五.AT24C02的读写
1.24c02读写高层时序
1.1 从器件的地址是由器件自身定义的,不同的从器件的地址定义方式是不同的,要查具体的芯片手册来确定
1.2 同一个i2c网络中只有一个主设备,但是从设备可以有多个。这多个从设备的地址不能相同(硬件设计工程师必须保证这一点)
1.3 从器件地址1010 +a2+a1+a0 其中a2,a1,a0 表示具体器件/外设的地址. rw=1读地址0xa1 rw=0写地址0xa0
写时序:1.开始
2.从器件地址+ack
3.字节地址+ack
4.数据1+ack
5.结束
写页写(seandbyte)时序:1.开始
2.从器件地址+ack
3.字节地址+ack
4.数据1+ack
5.数据2+ack
6.数据3+ack。。。。。。。等
7.结束
立即读地址时序:1.开始
2.从器件地址
3.数据
4.结束
选择性读地址时序:1.开始
2.(readbyte)从器件地址 +ack
3.(readbyte)字节地址 +ack
4.开始
5.(readbyte)从器件地址 +ack
6.数据 no ack
4.结束
*/
/************ I2C_EEPROM ******************/
#include "reg51.h"
sbit SCL = P2^1;
sbit SDA = P2^0;
typedef unsigned char uchar;
void Delay10us(void)
{
uchar a, b;
for(b=1; b>0; b--)
{
for(a=1; a>0; a--);
}
}
// I2C起始信号函数
void I2c_start()
{
SDA = 1;
Delay10us();
SCL = 1;
Delay10us();
SDA = 0;
Delay10us();
SCL = 0;
Delay10us();
}
// I2C终止信号函数
void I2c_stop()
{
SDA = 0;
Delay10us();
SCL = 1;
Delay10us();
SDA = 1;
Delay10us();
SCL = 0;
Delay10us();
}
// 通过I2c向从设备发送一个字节
//uchar I2c_Sendbyte(uchar addr)
//{
// uchar i = 0;
// for(i=0; i<8; i++)
// {
// SDA = addr >> 7; // I2c总线高位优先
// addr <<= 1;
// Delay10us();
// SCL = 1;
// Delay10us();
// SCL = 0;
// Delay10us();
// }
// SDA = 1; // 主设备释放SDA总线 给从设备去操作
// Delay10us(); // 主设备释放SDA总线 给从设备去操作
// SCL = 1; // 主设备释放SDA总线 给从设备去操作 主设备第9个周期
// if(SDA == 0)
// {
// SCL = 0;
// Delay10us();
// return 0; //return 0 表示成功
// }
// SCL = 0;
// Delay10us();
// return 1; //return1表示失败
//}
uchar I2c_Sendbyte(uchar addr, uchar ack)
{
uchar i = 0, j = 0;
for(i=0; i<8; i++)
{
SDA = addr >> 7;
addr = addr << 1;
Delay10us();
SCL = 1;
Delay10us();
SCL = 0;
Delay10us();
}
SDA = 1; // 主设备释放SDA总线 给从设备去操作
Delay10us();
SCL = 1; // 主设备第9个周期开始
while(SDA && (ack == 1)) // 等待应答,等待从设备将SDA拉低 //逻辑与:都为真为真,有一假为假 逻辑或:有一真为真,
{
j++; // 等待200的时间看sda能否拉低
if(j > 200)
{
SCL = 0;
Delay10us();
return 1; //return1表示失败
}
}
SCL = 0;
Delay10us();
return 0; //return 0 表示成功
}
// 说明;uchar ack 是一个参数,是需要等它的响应。有应答ack=1,无应答ack=0
// ack=1时需要等从设备响应。
// ack=0时,说明不需要去等待从设备的响应
// 从 while(SDA = 0 && (ack == 1))可以看出来
// ack总结: 正常情况下一个字节(8位数据)发送完后(这里说的发送是主设备向从设备通过i2c总线发送的数据),主设备有一个等待从设备反馈的动作,动作前提是SDA需要在这之前
// 拉高,让出SDA总线,等待从设备将SDA拉低,SDA=0说明从设备ack反馈了一个值,就说明从设备接收数据完毕。
// 读取从设备在i2c中的字节
uchar I2c_readbyte()
{
uchar a = 0,dat = 0;
SDA = 1; // 主设备释放总线 (sendbyte和 readbyte都需要释放总线) //起始和发送一个字节之后I2C_SCL都是0
Delay10us();
//SCL = 0;
// 按道理这里应该有一个SCL = 0的
for(a=0; a<8; a++)//接收8个字节
{
SCL = 1; // 通知从设备我要开始读了,可以放1bit数据到SDA了
Delay10us();
dat <<= 1; // 读取的时候是高位在前的
dat |= SDA;
Delay10us();
SCL = 0; // 拉低,为下一个bit的周期做准备
Delay10us();
}
return dat;
}
/* 逻辑运算符:按位或|| 和 按位与 &&
按位或 || : 表达式左右两边有一个为真,则表达式成立都为真
按位与 %% : 表达式左右两边都为真,则表达式成立都为真
*/
/* 高层时序 */
// 以cpu为本身,向24c02写数据
void write_24C02(uchar addr, uchar dat)
{
I2c_start(); // 开始
I2c_Sendbyte(0xa0, 1); // 从器件地址,+ 需要应答
I2c_Sendbyte(addr, 1); // 字节地址, + 需要应答
I2c_Sendbyte(dat, 1); // 字节数据, + 需要应答
I2c_stop(); // 结束
}
////1. 以cpu为本身,立即 读24c02数据
//uchar read_24c02()
//{
// uchar num = 0;
// I2c_start(); // 开始
// I2c_Sendbyte(0xa0, 1); // 发送从器件地址,+ 需要应答
// num = I2c_readbyte(); // 读取地址
// return num;
//}
//2. 以cpu为本身,选择性 读24c02数据
uchar read_24c02(uchar addr)
{
uchar num = 0;
I2c_start(); // 开始
I2c_Sendbyte(0xa0, 1); // 发送从器件地址,+ 需要应答 0xa0 写 0xa1读
I2c_Sendbyte(addr, 1); // 发送字节地址, + 需要应答
I2c_start(); // 开始
I2c_Sendbyte(0xa1, 1); // 发送读从器件地址,+ 需要应答
num = I2c_readbyte(); // 读取地址
I2c_stop();
return num;
}
////3. 以cpu为本身,连续都 读24c02数据
/* 串口函数 波特率4800 */
void uart_init(void) // 串口初始化函数
{
SCON = 0x50;
PCON = 0X80; //波特率加倍 2400mhz加倍到4800,相应的TH1\TL1的计算就要改变
//波特率计算公式为:2的smod次方/32*晶振频率*10 000 000/12/(256-TH1)=4800mhz
TMOD = 0x20;
TH1 = 243;
TL1 = 243;
TR1 = 1;
// EA = 1;
// ES = 1;
}
void uart_send_byte(uchar a) // 串口发送函数
{
SBUF = a;
while (!TI);
TI = 0;
}
// 主函数主程序
void main()
{
uchar i = 0;
uchar addr = 0;
uchar dat[8] ={1,2,3,4,5,6,7,8};
uchar buf[8];
// uart_init();
// for (i=0; i<12; i++)
// { // 串口调试程序没问题
// uart_send_byte(i);
// }
// while(1);
uart_init(); // uart初始化
addr = 0;
for(i=0; i<8; i++) // 写入
{
write_24C02(addr, dat[i]);
Delay10us();
addr++;
}
addr = 0;
for(i=0; i<8; i++) // 读取
{
buf[i] = read_24c02(addr);
Delay10us();
addr++;
}
for(i=0; i<8; i++) // urat 发送
{
uart_send_byte(buf[i]);
Delay10us();
}
}
本文围绕51单片机I2C通信的EEPROM(AT24C02)展开。介绍了EEPROM背景知识,包括与RAM、ROM区别,和flash的关系等;阐述了I2C通信原理、特征及底层时序;还说明了AT24C02的读写高层时序,如写时序、页写时序、立即读和选择性读地址时序等。

2011

被折叠的 条评论
为什么被折叠?



