文章目录
一、IIC总线
1.1.IIC总线介绍
IIC(Inter-Integrated Circuit)总线是由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。IIC 总线只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。由于其管脚少,硬件实现简单,可扩展性强等特点,因此被广泛的使用在各大集成芯片内。下面我们就从 IIC 的物理层与协议层来了解 IIC。
1.2.IIC总线物理层
I2C 通信设备常用的连接方式如下图所示:
- 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 IIC 通讯总线中,可连接多个 IIC 通讯设备,支持多个通讯主机及多个通讯从机。
- 一个 IIC 总线只使用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
- 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
- 总线通过上拉电阻接到电源。当 IIC 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
- 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
- 具有三种传输模式:标准模式传输速率为 100kbit/s,快速模式为400kbit/s,高速模式下可达 3.4Mbit/s,但目前大多 IIC 设备尚不支持高速模式。
- 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
1.3.IIC总线协议层
I2C 总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
1.3.1.起始与停止的信号
起始信号
和终止信号
,数据线都在时钟线为1
时变化的,其他时候时钟线只有为0,数据线才能变化。
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平。
- 终止条件:SCL高电平期间,SDA从低电平切换到高电平。
如下图:
1.3.2.发送一个字节
SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
1.3.3.接收一个字节
SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
1.3.4.发送应答和接收应答
- 发送应答:在接收完一个字节之后,
主机
在下一个时钟发送
一位数据,数据0表示应答,数据1表示非应答。 - 接收应答:在发送完一个字节之后,
主机
在下一个时钟接收
一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。
1.4.发送一帧数据
一帧数据指的是:一次完整的数据传输单元,由特定格式的信号或数据段组成,包含起始、数据、校验、结束等部分,确保数据传输的有序性和可靠性。发送一帧数据是由主机发送给从机,显示主机发送一个起始信号,告诉各个IIC协议上的设备,主机要发送消息了。接着发送从机的地址码加写操作,意思是主机要写数据给从机,如果主机读到从机发送的应答信号,则继续下面操作,否则就没有该从机设备。继续下面操作之后,循环发送再接收应答,最后主机发起停止信号才停止。下图是发送一帧数据:
1.5.接收一帧数据
接收一帧数据是主机接收从机发来的一帧数据。首先由主机发起起始信号,再发出从机地址码加读操作,接着主机读到从机发出的应答信号,接下来SDA线就由从机操作,从机发出一字节数据,主机发出应答信号,循环以上操作,主机发出非应答信号,再发出停止信号,整个数据帧就停止了。下图是接收一帧数据:
1.6.字节写
例如:在WORD ADDRESS处写入数据DATA。主机第一次收到从机的应答信号后,主机就可以发出从机里寄存器的地址码,主机再收到一次从机应答信号,最后主机就可以进行写数据了。
1.7.随机读
例如:读出WORD ADDRESS处的数据,首先主机发起起始信号和从机地址码加写操作,从机发出应答信号,主机再发出WORD ADDRESS,从机再发出应答信号,接着主机重新发起起始信号,再发送从机地址码加读操作,从机发送应答信号,主机读出从机发出来的数据,主机最后发送非应答信号和停止信号。下图是随机读操作:
1.8.IIC软件设计
利用P2_1和P2_0来模拟IIC的SCL和SDA两根线。下面代码是IIC.c文件:
#include <REGX52.H>
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
/**
*@brief I2C开始
*@param 无
*@retval 无
*/
void I2C_Start()
{
I2C_SDA=1; //先把两根先都拉高
I2C_SCL=1;
I2C_SDA=0; //趁着时钟线为1的时候,数据线拉低
I2C_SCL=0; //接着时钟线再拉低
}
/**
*@brief I2C停止
*@param 无
*@retval 无
*/
void I2C_Stop()
{
I2C_SDA=0; //为了确保数据线由0变1,先把数据线拉低
I2C_SCL=1; //再把时钟线拉高
I2C_SDA=1; //在时钟线为1时,操作数据线为1
}
/**
*@brief I2C发送
*@param Byte 要发送的字节
*@retval 无
*/
void I2C_SentByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
I2C_SDA=Byte&(0x80>>i); //按位字节与,1和1等于1,1和0等于0,先把数据线的数据固定好
I2C_SCL=1; //接着时钟线为1,数据线的数据被发送
I2C_SCL=0; //时钟线再为0,数据线可以变化
}
}
/**
*@brief I2C接受一个字节
*@param 无
*@retval 接收到的一个字节数据
*/
unsigned char I2C_ReceiveByte()
{
unsigned char Byte=0x00,i; //定义一个保存读到字节的变量,初始化成 0
I2C_SDA=1; //主机释放数据线,让从机操作数据线
for(i=0;i<8;i++)
{
I2C_SCL=1; //高电平读取
if(I2C_SDA){Byte|=(0x80>>i);} //读取数据线发来的数据并存在Byte变量当中,如果I2C_SDA为1,就进入if语句,把当前位置1
I2C_SCL=0;
}
return Byte; //返回读出来的数据
}
/**
*@brief I2C发送应答位
*@param Ackbit 应答位,0为应答,1为非应答
*@retval 无
*/
void I2C_SentAckbit(unsigned char Ackbit)
{
I2C_SDA=Ackbit; //发送完一个字节后,主机发一个应答
I2C_SCL=1;
I2C_SCL=0;
}
/**
*@brief I2C接受应答位
*@param 无
*@retval 接收到的应答位,应答位,0为应答,1为非应答
*/
unsigned char I2C_ReceiveAckbit()
{
unsigned char Ackbit;
I2C_SDA=1; //主机先把数据线释放,让从机来操作
I2C_SCL=1; //时钟线为1,就可以读取从机发来的应答信号
Ackbit=I2C_SDA;
I2C_SCL=0;
return Ackbit; //返回从机发来的应答信号
}
下面是IIC.h文件:
#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start();
void I2C_Stop();
void I2C_SentByte(unsigned char Byte);
unsigned char I2C_ReceiveByte();
void I2C_SentAckbit(unsigned char Ackbit);
unsigned char I2C_ReceiveAckbit();
#endif
二、AT24C02芯片
2.1.存储器
2.2.AT24C02 芯片介绍
AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。
- 存储介质:E2PROM(电可擦除可编程ROM)
- 通讯接口:I2C总线
- 容量:256字节
AT24C02 芯片管脚及外观图如下图所示:
2.3.引脚及应用电路
引脚 | 功能 |
---|---|
VCC、GND | 电源(1.8V~5.5V) |
WP | 写保护(高电平有效) |
SCL、SDA | IIC接口 |
A0、A1、A2 | IIC地址 |
下图是应用电路:
2.4.AT24C02芯片软件设计
该器件通过 I2C 总线接口进行操作,下面代码是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_SentByte(AT24C02_Address); //主机发送从机的地址码加写操作,呼叫该从机
I2C_ReceiveAckbit(); //从机收到,从机发送应答
I2C_SentByte(WordAddress); //主机再发送特定位置写入
I2C_ReceiveAckbit(); //从机收到,从机发送应答
I2C_SentByte(Data); //主机发送数据
I2C_ReceiveAckbit(); //从机收到,从机发送应答
I2C_Stop(); //主机发起停止信号
}
/**
*@brief AT24C02读取一个字节
*@param WordAddress 要读出的地址
*@retval 读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data; //定义一个变量Data来保存主机接收到从机发来的数据
I2C_Start();
I2C_SentByte(AT24C02_Address);
I2C_ReceiveAckbit();
I2C_SentByte(WordAddress); //要读出WordAddress的数据,所以发送WordAddress的地址码
I2C_ReceiveAckbit();
I2C_Start(); //主机重新发起起始信号
I2C_SentByte(AT24C02_Address|0x01); //主机发送该设备地址码并将最后一位置1,意味着读操作
I2C_ReceiveAckbit();
Data=I2C_ReceiveByte(); //用变量Data来承接I2C_ReceiveByte()返出来的数据,也就是主机读出从机发送的数据
I2C_SentAckbit(1); //主机发送非应答信号,如果发送0,从机继续发送数据
I2C_Stop();
return Data; //把读出的数据返出去
}
下面是AT24C02.h头文件:
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
由AT24C02芯片实现储存数据,下面是main函数:
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "Key.h"
#include "AT24C02.h"
unsigned char KeyNum;
unsigned int 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)
//为什么要区分高8位,低8位,因为定义Num位int类型,int为16位,
//定义char类型就不需要区分了,char类型只有8位
{
AT24C02_WriteByte(0,Num%256); //这里是低8位
Delay(5);
AT24C02_WriteByte(1,Num/256); //这里是高8位
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," ");
}
}
}