- 嵌入式入门教学汇总:
- 嵌入式入门教学——C51(上)
- 嵌入式入门教学——C51(中)
- 嵌入式入门教学——C51(下)
十三、AT24C02(I2C总线)
1、存储器
- RAM、ROM各有优势,所以需要结合使用。
1.1、AT24C02简介
- AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。
- 存储介质:E2PROM
- 通讯接口:I2C总线
- 容量:256字节
2、AT24C02原理图
- VDD、VSS:电源(1.8V~5.5V)
- WE:写使能(低电平有效)
- SCL、SDA:I2C接口
- E0、E1、E2:I2C地址
3、I2C总线
3.1、I2C简介
- 12C总线 (InterIC BUS) 是由Philips公司开发的一种通用数据总线。
- 两根通信线:SCL (Serial Clock)、SDA(Serial Data )
- 特点:同步,半双工,带数据应答
- 通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用着来说,使用通用的通信协议可以避免学习各种各样的自定义协议降低了学习和应用的难度。
3.2、I2C电路规范
- 所有I2C设备的SCL连在一起,SDA连在一起。
- 设备的SCL和SDA均要配置成开漏输出模式。
- 【注】开漏输出模式:输出电平只能被拉低,而不能被拉高。开关断开时,处于一种浮空的状态;开关连接时,输出低电平。
- SCL和SDA各添加一个上拉电阻(把一个信号通过一个电阻接到电源),阻值一般为4.7KQ左右。
- 【注】为什么开漏输出又要加上拉电阻?
- 接上拉电阻是因为I2C通信需要输出高电平的能力。一般开漏输出无法输出高电平,如果在漏极接上拉电阻,则可以进行电平转换。
- 【注】为什么开漏输出又要加上拉电阻?
- 开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题。
3.3、I2C时序结构
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平。
- 终止条件:SCL高电平期间,SDA从低电平切换到高电平。
- 发送一个字节(主机->从机):SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
- 接收一个字节(从机->主机):SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,让从机使用)。
- 发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
- 接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。
3.4、I2C数据帧
- 发送一帧数据:在起始条件开始后,第一个字节一定是发送从机地址+写标志0。从机地址前四位是固定的,后三位是可配置的,即E0,E1,E2。每发送一个字节的数据,主机需要接收从机的应答。
- 接受一帧数据:第一个字节一定是发送从机地址+读标志1。第一次发送从机地址后,主机需要接收从机的应答。之后每接收一个字节的数据,主机需要向从机发送应答。
- 先发送再接收数据帧:
4、AT24C02数据帧
- 字节写:在WORD ADDRESS(字地址)处写入数据DATA。
- 随机读:读出在WORD ADDRESS处的数据DATA。
- AT24C02的固定地址为1010,可配置地址本开发板上为000,所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS(从机地址)+R为0xA1。
- 【注】每次写入需要延时5ms,因为AT24C02的写周期为5ms。连续读不用。
5、AT24C02数据存储(LCD1602显示)
- 内容:按下按键1,Num加1;按下按键2,Num减1;按下按键3,将十六位的Num拆开写入AT24C02存储器中;按下按键4,AT24C02存储器中读出Num。(数据是写入芯片中的,掉电后仍会保留)
- 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数、独立按键和LCD1602模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c、Key.c和LCD1602.c到工程中,并设置其引入路径。
- 代码结构:
5.1、I2C模块
- I2C.c
-
#include <REGX52.H> sbit I2C_SCL=P2^1; sbit I2C_SDA=P2^0; /** * @brief I2C开始 * @param 无 * @retval 无 */ void I2C_Start(void){ I2C_SDA=1; I2C_SCL=1; I2C_SDA=0; I2C_SCL=0; } /** * @brief I2C停止 * @param 无 * @retval 无 */ void I2C_Stop(void){ I2C_SDA=0; I2C_SCL=1; I2C_SDA=1; } /** * @brief I2C发送一个字节 * @param Byte 要发送的字节 * @retval 无 */ void I2C_SendByte(unsigned char Byte){ unsigned char i; for(i=0;i<8;i++){ I2C_SDA=Byte&(0x80>>i); I2C_SCL=1; // 读取 I2C_SCL=0; } } /** * @brief I2C接收一个字节 * @param 无 * @retval 接收到的一个字节数据 */ unsigned char I2C_ReceiveByte(void){ unsigned char i,Byte=0x00; I2C_SDA=1; // 主机释放总线,让从机使用 for(i=0;i<8;i++){ I2C_SCL=1; if(I2C_SDA) Byte|=(0x80>>i); I2C_SCL=0; } return Byte; } /** * @brief I2C发送应答 * @param AckBit 应答位,0为应答,1为非应答 * @retval 无 */ void I2C_SendAck(unsigned char AckBit){ I2C_SDA=AckBit; I2C_SCL=1; I2C_SCL=0; } /** * @brief I2C接收应答 * @param 无 * @retval 接收到的应答位,0为应答,1为非应答 */ unsigned char I2C_ReceiveAck(void){ unsigned char AckBit; I2C_SDA=1; // 主机释放总线,让从机使用 I2C_SCL=1; AckBit=I2C_SDA; I2C_SCL=0; return AckBit; }
- I2C.h
-
#ifndef __I2C_H__ #define __I2C_H__ void I2C_Start(void); void I2C_Stop(void); void I2C_SendByte(unsigned char Byte); unsigned char I2C_ReceiveByte(void); void I2C_SendAck(unsigned char AckBit); unsigned char I2C_ReceiveAck(void); #endif
5.2、AT24C02模块
- AT24C02.c
-
#include <REGX52.H> #include "I2C.h" #define AT24C02_ADDRESS 0xA0 /** * @brief AT24C02写入一个字节 * @param WordAddress 要写入字节的地址 * @param Data 要写入的数据 * @retval 无 */ void AT24C02_WriteByte(unsigned char WordAddress,Data){ I2C_Start(); I2C_SendByte(AT24C02_ADDRESS); // 发送从机地址,写 I2C_ReceiveAck(); // 接收应答 I2C_SendByte(WordAddress); // 发送字地址 I2C_ReceiveAck(); I2C_SendByte(Data); // 发送数据 I2C_ReceiveAck(); I2C_Stop(); } /** * @brief AT24C02读取一个字节 * @param WordAddress 要读出字节的地址 * @retval 读出的数据 */ unsigned char AT24C02_ReadByte(unsigned char WordAddress){ unsigned char Data; I2C_Start(); I2C_SendByte(AT24C02_ADDRESS); // 发送从机地址 I2C_ReceiveAck(); // 接收应答 I2C_SendByte(WordAddress); // 发送字地址 I2C_ReceiveAck(); I2C_Start(); I2C_SendByte(AT24C02_ADDRESS|0x01); // 发送从机地址,读 I2C_ReceiveAck(); // 接收应答 Data=I2C_ReceiveByte(); I2C_SendAck(0); // 发送应答 return Data; I2C_Stop(); }
- AT24C02.h
-
#ifndef __AT24C02_H__ #define __AT24C02_H__ void AT24C02_WriteByte(unsigned char WordAddress,Data); unsigned char AT24C02_ReadByte(unsigned char WordAddress); #endif
5.3、编写main.c文件
-
#include <REGX52.H> #include "Delay.h" #include "Key.h" #include "LCD1602.h" #include "AT24C02.h" unsigned char KeyNum; unsigned int Num; // Num为十六位数据,所以需要拆开存放 void main(){ LCD_Init(); LCD_ShowNum(1,1,Num,5); while(1){ KeyNum=Key(); if(KeyNum==1){ Num++; LCD_ShowNum(1,1,Num,5); } if(KeyNum==2){ Num--; LCD_ShowNum(1,1,Num,5); } if(KeyNum==3){ AT24C02_WriteByte(0,Num%256); // 低八位 Delay(5); // 写周期 AT24C02_WriteByte(1,Num/256); // 高低位 Delay(5); LCD_ShowString(2,1,"Write OK"); Delay(1000); LCD_ShowString(2,1," "); } if(KeyNum==4){ Num=AT24C02_ReadByte(0); Num|=AT24C02_ReadByte(1)<<8; LCD_ShowNum(1,1,Num,5); LCD_ShowString(2,1,"Read OK"); Delay(1000); LCD_ShowString(2,1," "); } } }