STC89C52单片机I2C通信以及AT24C02介绍使用代码演示

目录

AT24C02引脚介绍与使用

AT24C02介绍

I2C通信介绍

        I2C通信时序

起始条件与终止条件

发送一个字节(主机发送到从机)

接受一个字节(从机发送到主机)

发送应答与接受应答

I2C数据帧

发送一帧数据(向谁发送什么)

接受一帧数据(向谁接受什么)

先发送后接受数据帧,复合格式书写

应用:字节写与随机读

 代码演示:

I2C代码(AT24C02底层代码)

AT24C02引脚介绍与使用

首先AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息

在早起时候,我们的储存器的是出场的时候就储存好内容的,这种储存介质也叫Mask ROM,看下面的图片也可以知道,二极管(具有单向导通性)的方向是确定的。

后来,就发明了一种叫做PROM的储存器,看下面的图片可以知道,他一条线上有2个二极管,在制造出来后,我们进行写入程序,利用高电压击穿,使其烧毁,使得其一条线上只具有一个二极管,但是这种写入程序的方式是不可逆的,所以写入的时候又叫烧写。

再到后来就有了EPROM(在外线的照射下,擦除之前编写的程序)

和E2PROM也就是我们现在常用的写入储存器,其通电后就可以擦去不要的内容,进行重新编写

AT24C02介绍

存储介质:E2PROM

通讯接口:I2C总线

容量:256字节

AT24C02内部结构图

 引脚介绍:

在写入AT24C02的代码中要做适当延时,确保数据写入!!!

I2C通信介绍

I2C总线介绍

I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线

两根通信线:SCL(Serial Clock)、SDA(Serial Data)

同步、半双工,带数据应答

通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度

I2C电路规范

所有I2C设备的SCL连在一起,SDA连在一起(在主机和规定的从机进行交换数据的时候,主机要先发送从机的地址)

设备的SCL和SDA均要配置成开漏输出模式

SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题

 什么是开漏输出?

      开漏输出就是不输出电压,控制输出低电平时引脚接地,控制输出高电平时引脚既不输出高电平,也不输出低电平,为高阻态。如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻的电源电压。这种方式适合在连接的外设电压比单片机电压低的时候。

I2C通信时序

手册上一段完整的时序是这样的:

但是问题是他这种时序图的可读性不强,即对于每一个部分作用区分的不是很明显,而且高低电平表示的1,0数据不是很清晰

所以下面的介绍类似于心电图的方式,使其更加清晰

起始条件与终止条件

起始条件:SCL高电平期间,SDA从高电平切换到低电平

终止条件:SCL高电平期间,SDA从低电平切换到高电平

发送一个字节(主机发送到从机)

SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化(也就是说我们只有在SCL低电平的时候才能变化SDA的数据),依次循环上述过程8次,即可发送一个字节

一般是用8次循环的“ & ”运算,依次将数据赋给SDA

 

接受一个字节(从机发送到主机)

SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,即将SDA置1,因为单片机上电的时候默认所有的接口都为高电平1)

一般是用8次循环的“ | ”运算,依次将数据读给中间变量

    

发送应答与接受应答

发送应答(主机发送到从机)在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

接收应答(从机发送到主机)在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

总而言之,写完以后接受应答,读完之后发送应答。

S:--->RA     R:--->SA

I2C数据帧

发送一帧数据(向谁发送什么)

 AT24C02的固定地址为1010(SLAVE ADDRESS),可配置地址本开发板上为000(A0,A1,A2)

所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1

接受一帧数据(向谁接受什么)

先发送后接受数据帧,复合格式书写

应用:字节写与随机读

随机读包括两个步骤,首先是确认读的位置(发送对AT24C02进行写入的指令+发送读入地址)

其次是读出数据(发送对AT24C02进行读出的指令)之前已经确认数据的地址

对应的手册上面的时序图是这样的:

 代码演示:

main主函数

#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"

unsigned char KeyNum;
unsigned int Num;

void main()
{
	LCD_Init();
	LCD_ShowNum(1,1,Num,5);
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)	//K1按键,Num自增
		{
			Num++;
			LCD_ShowNum(1,1,Num,5);
		}
		if(KeyNum==2)	//K2按键,Num自减
		{
			Num--;
			LCD_ShowNum(1,1,Num,5);
		}
		if(KeyNum==3)	//K3按键,向AT24C02写入数据
		{
			AT24C02_WriteByte(0,Num%256);//一页中最多存储8个字节,2的8次方等于256,所以分高8位和低8位存储
			Delay(5);//须要5ms写入AT24C02中
			AT24C02_WriteByte(1,Num/256);//高位用除的方式存入一页,低位用取余的方式存入一页
			Delay(5);
			LCD_ShowString(2,1,"Write OK");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
		if(KeyNum==4)	//K4按键,从AT24C02读取数据
		{
			Num=AT24C02_ReadByte(0);
			Num|=AT24C02_ReadByte(1)<<8;//将高位先向左移8位后,“或”上Num即可
			LCD_ShowNum(1,1,Num,5);
			LCD_ShowString(2,1,"Read OK ");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
	}
}

I2C代码(AT24C02底层代码

#include <REGX52.H>
sbit I2C_SCL=P2^1;//相当于晶振,高电平时写入/读出,低电平时SDA进行改变
sbit I2C_SDA=P2^0;//高低电平传输2进制数据

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void I2C_Start(void)
{
	I2C_SDA=1;//SDA可能为1也可能为0,又因为我们在SCL低电平的时候变换数据(SCL在传输结束后都为0),所以先将SDA置1
	I2C_SCL=1;
	I2C_SDA=0;//按照拼图开始时SDA先置0后SDL置0
	I2C_SCL=0;
}

/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void I2C_Stop(void)
{
	I2C_SDA=0;//在停止前可能是RA:0(收到数据应答)也可能是SA:1(发送数据不应答),所以将SDA置1
	I2C_SCL=1;
	I2C_SDA=1;//按照拼图开始时SCL先置1后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);//在SCL为0的时候变换数据
		I2C_SCL=1;//查芯片手册可知,最大延时小于单片机执行一条语句的时间,所以中间不用加延时
		I2C_SCL=0;
	}
}

/**
  * @brief  I2C接收一个字节
  * @param  无
  * @retval 接收到的一个字节数据
  */
unsigned char I2C_ReceiveByte(void)
{
	unsigned char i,Byte=0x00;//局部变量,手动定义
	I2C_SDA=1;//接收时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;//接收时SDA首先要释放,即置1
	I2C_SCL=1;
	AckBit=I2C_SDA;
	I2C_SCL=0;
	return AckBit;
}

AT24C02储存与读出

#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(1);
	I2C_Stop();
	return Data;
}

LCD1602显示

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;
	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

Delay延时

void Delay(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

版权声明:本文为CSDN博主「爱吃炸鸡的小猪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_60521516/article/details/122776459

生成海报
点赞 0

爱吃炸鸡的小猪

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

趣聊51之串口通信(概念篇)

对于刚刚接触单片机的同学们来说,串口通信似乎是一个神秘感十足的东西,笔者在刚刚开始学习51单片机时,读的是郭天祥先生的那本著名的《新概念51单片机教程》,贼厚的一本书,但是等