单片机学习(十一)I2C总线和AT24C02的使用
[toc]
一、 存储器介绍
存储器分类图
1. RAM
这类存储器中的数据都是掉电即失的,例如计算机中的内存就是DRAM,但它们数据读写速度都是要比ROM要快得多的。
- SRAM:本质是电路,使用电路构成的触发器来存储数据(如JK触发器),因此这种存储器读写数据是最快的,而它们的成本也比较高,一般用作计算机的高速存储器,寄存器等
- DRAM:使用电容来存储数据,因为电容存在漏电现象,因此需要每隔一段时间进行扫描重新充电。它们一般用来构成计算机的内存,手机的闪存等。
2. ROM
这类存储器中的数据有着掉电不丢失的特性,但它们读写数据的速度远小于RAM
- Mask ROM:仅由电路构成,只读的ROM,就是只可读不可写
- PROM:可以写入数据,但只能写入一次数据
- EPROM:可读可写
- ...
二、AT24C02简介
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
- 存储介质:E2PROM
- 通讯接口:I2C总线
- 容量:256字节
电路连接
三、I2C总线和AT24C02数据帧
I2C总线(Inter IC BUS)是由
Philips公司开发的一种通用数据总线(通信协议)
- 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
- 同步、半双工,带数据应答
- 通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度
1. 电路规范
- 所有I2C设备的SCL连在一起,SDA连在一起
- 设备的SCL和SDA均要配置成开漏输出模式
- SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
- 开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
2. I2C的时序结构
我们可以将通过I2C协议实现主机与从机通信的过程分为以下六个部分,这六个部分可以像拼图一样拼凑出所有的通信过程:
① 发送起始信息:
起始条件:
SCL高电平期间,
SDA从高电平切换到低电平,如下图所示:
同时为了使这块拼图可以和其他的部分连接上,我们在发送起始信息(
start)之后,也将SCL拉低。
② 发送终止信息:
终止条件:
SCL高电平期间,
SDA从低电平切换到高电平
同理,为了和其他拼图拼接上,我们得到的SCL原来是低电平的(这是因为后面的4块拼图结束时SCL都是低电平,因此来到终止信息时也是低电平),我们先拉高SCL,然后拉高SDA即可发送终止信号了。
③ 发送一个byte的信息:
发送一个
byte(字节)信息可以分解为循环发送8个bit 的信息,因此我们只需要知道如何发送一个bit的信息即可。
发送一个
bit信息的操作:(1)SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),(2)然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化
即如果我们希望发送0,则:
- 在SCL在低电平时,将SDA的电平拉低(置零)
- 再将SCL的电平拉高,提醒从机读取信息
- 过一段时间(等待从机把信息读取完成)后再次将SCL拉低
具体的过程如图所示:
例如在B7时间内,如果SDA为低电平,则发送的数据为0,如果为高电平则发送的数据为1
④ 接收一个byte的信息:
和发送信息类似,我们只需要知道如何接收一个bit的信息,然后只需要循环进行8次即可接收一个字节的信息了。
接收一个
bit信息的操作:(1)SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),(2)然后拉高SCL(相当于通知从机主机正在读取这个bit的数据),主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化。(主机在接收之前,需要释放SDA)
具体的过程如图所示:
这个过程我个人的理解是:
- 释放SDA(拉高电平),将写入数据的主动权交给从机
- 从机写入下一位bit到SDA中
- 主机拉高SCL,提示从机我正在读取这个bit的数据,你先不要变化
- 主机读取完数据,将SCL拉低,实际上这个过程是在告诉从机我已经读完这个bit了,你给我下一个bit的数据吧,然后如果还未满8位则转到第2步继续接受数据,如果满了一个字节则接收结束
对比发送数据和接收数据可以发现,这两个过程非常的相似,只不过(1)发送信息时写入信息的一方是主机,而接收信息时写入数据的是从机,(2)发送信息时拉高和拉低SCL电平是通知从机读取信息,而在接收信息时则是通知从机主机当前正在读取信息。
可以发现单从开始时的
SCL和
SDA的状态是无法区分主机是想发送还是接收信息的,其实接收数据还是发送数据是由后面的时序过程(数据帧)所决定的,不需要起始状态进行区分。
⑤ 发送应答:
在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
其实发送应答的操作就是发送一个bit的操作而已:
⑥ 接收应答:
在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
和发送应答类似,接收应答的操作也就是接收一个bit数据的操作。
3. I2C数据帧
① 发送一帧数据:
其过程是(过程中的接收应答省略不写了):
- 发送一个开始信号
- 发送从机地址(加上写入标记,最后一位为0)
- 循环发送字节数据
- 发送完后发送结束信号
② 接收一帧数据:
过程和发送一帧数据的过程非常类似,只是中间的发送字节数据变成了接收字节数据,此外还有地址部分的最后一位为1,代表读取数据。
③ 先发送后接收数据:
4. AT24C02数据帧
AT24C02是使用I2C通讯协议进行通讯的,I2C数据帧相当于是一辆卡车,而AT24C02数据帧则是在原来卡车的基础上装上了特定货物的卡车。
① 字节写:
在
WORD ADDRESS处写入数据
DATA
② 随机读:
读出在
WORD ADDRESS处的数据
DATA
AT24C02的固定地址为1010,可配置地址本开发板上为000,所以
SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1
四、代码实现
1. I2C模块
实现I2C模块,即实现上面介绍的6块拼图。
首先定义出
SCL和
SDA连接的引脚:
sbit I2C_SCL = P2 ^ 1; sbit I2C_SDA = P2 ^ 0;
① 发送起始信息和发送终止信息:
void I2C_Start() { I2C_SDA = 1; I2C_SCL = 1; // 在SCL为高电平拉低SDA I2C_SDA = 0; I2C_SCL = 0; } void I2C_Stop() { I2C_SDA = 0; // 在SCL为高电平时拉高SDA I2C_SCL = 1; I2C_SDA = 1; }
② 发送字节信息和接收字节信息:
void I2C_SendByte(unsigned char byte) { unsigned char i = 0; for (i = 0; i < 8; i++) { // 先将一个bit的数据写入到SDA中 I2C_SDA = byte & (0x80 >> i); // SCL先拉高,后拉低,通知从机接收数据 I2C_SCL = 1; I2C_SCL = 0; } } unsigned char I2C_ReceiveByte() { unsigned char byte = 0x00; unsigned char i; // 先释放SDA(将主动权交给从机) I2C_SDA = 1; for (i = 0; i < 8; i++) { // 通知从机主机正在读取数据 I2C_SCL = 1; // 如果是1则置一,否则默认为0 if (I2C_SDA) { byte |= (0x80 >> i); } // 通知从机这个bit已经读取完毕,可以发送下一个bit I2C_SCL = 0; } return byte; }
③ 发送应答和接收应答:
void I2C_SendAck(unsigned char AckBit) { I2C_SDA = AckBit; // SCL先拉高,后拉低,通知从机接收数据 I2C_SCL = 1; I2C_SCL = 0; } unsigned char I2C_ReceiveAck() { unsigned char AckBit; // 先释放SDA(将主动权交给从机) I2C_SDA = 1; // 通知从机主机正在读取数据 I2C_SCL = 1; AckBit = I2C_SDA; // 通知从机这个bit已经读取完毕 I2C_SCL = 0; return AckBit; }
2. AT24C02模块
这个模块依赖于I2C模块,即利用I2C发送和接收数据。
首先定义从机地址:
// 最后一位为0代表写,即发送数据,为1代表读,即接受数据 #define AT24C02_ADDRESS 0xA0
① 字节写:
前面已经提到,AT24C02可以存储256个字节的数据,因此我们的数据可以任意0~255号地址的空间进行存储:
void AT24C02_WriteByte(unsigned char WordAddress, unsigned char Byte) { I2C_Start(); // 从机地址 I2C_SendByte(AT24C02_ADDRESS); I2C_ReceiveAck(); // 字地址 I2C_SendByte(WordAddress); I2C_ReceiveAck(); // 发送真正的数据 I2C_SendByte(Byte); I2C_ReceiveAck(); I2C_Stop(); }
② 随机读:
读取
WordAddress地址中存储的字节信息:
unsigned char AT24C02_ReadByte(unsigned char WordAddress) { unsigned char Byte; I2C_Start(); // 从机地址 I2C_SendByte(AT24C02_ADDRESS); I2C_ReceiveAck(); // 字地址 I2C_SendByte(WordAddress); I2C_ReceiveAck(); I2C_Start(); // 从机地址,read模式 I2C_SendByte(AT24C02_ADDRESS|0x01); I2C_ReceiveAck(); // 读取信息 Byte = I2C_ReceiveByte(); I2C_SendAck(1); I2C_Stop(); return Byte; }
3. 使用AT24C02进行数据存储
我们使用
LCD_1602进行显示,第二行显示num数字,当我们单击按钮时:
- 点击k1,
num--
- 点击k2,
num++
- 点击k3,将
num
的数据存储到AT24C02
中地址为1的空间中
void main() { unsigned char key, num; unsigned char storageData; LCD_Init(); LCD_ShowString(1, 1, "Hello world"); LCD_ShowString(2, 1, "num:"); storageData = AT24C02_ReadByte(1); num = storageData; LCD_ShowNum(2, 5, storageData, 3); Timer0_Init(); while (1) { key = Key(); if (key) { switch (key) { case 1: num--; break; case 2: num++; break; case 3: AT24C02_WriteByte(1, num); break; } } LCD_ShowNum(2, 5, num, 3); } } void Timer0_Routine() interrupt 1 { static unsigned int T0Count..; TL0 = 0x18;//设置定时初值 TH0 = 0xFC;//设置定时初值 T0Count++; if (T0Count >= 20) { T0Count = 0; Key_Loop();//每20ms调用一次按键驱动函数 } }
这样我们每次重启时就可以看到上次存储的数字了。
- [每日学习笔记][2012.08.03]使用Java理解程序逻辑(十一)
- 单片机c语言XBYTE的使用 分类: c51 单片机学习 2013-03-23 15:32 1546人阅读 评论(2) 收藏
- MVC学习十一:浅谈在ASP.NET MVC3中使用IClientValidatable接口实现客户端和服务器端同时验证
- 【Python3.6爬虫学习记录】(十一)使用代理IP及用多线程测试IP可用性--刷访问量
- OpenCV学习笔记(十一)——谈谈像素的类型和对Templates的限制使用
- JAVA学习笔记(十一):enum的使用
- 单片机入门学习十一 STM32单片机学习八 外部中断
- IOS开发学习笔记(十一)——ObjectC中集合类型的使用
- 奶爸业余单片机学习之:定时器中断的使用方法——进入中断TF0(TF1)值硬件自动重置
- Java学习--(十一)包装类,Date,SimpleDateFormat,Calendar类的使用
- android学习(十一) 使用分享菜单
- 单片机入门学习十四 STM32单片机学习十一 通用定时器二
- gradle学习之旅(十一) 使用和配置仓库
- 嵌入式学习笔记(17)——AVR单片机之头文件简介和使用
- Unity学习(十一): Unity中的NetWork使用
- OpenGL入门学习之十一——纹理的使用入门
- Oracle学习(十一)之使用RMAN对数据库做在线完全备份
- flume学习(十一):如何使用Spooling Directory Source
- zynq-7000学习笔记(十一)——Linux下VDMA的使用
- Spring学习之旅(十一) Spring Web Flow的配置及简单使用