51单片机——模拟I2C总线与AT24C02通信

目录

一、写在前面

二、功能描述

三、主要模块介绍

3.1 I2C总线介绍

3.2 I2C总线协议

3.2.1数据有效规定

3.2.2起始信号和停止信号 

3.2.3 发送应答和接收应答

3.2.4 主机发送一个字节和接收一个字节

3.3 AT24C02介绍

3.3 字节写和随机读

四、测试文件test.c

五、现象描述


一、写在前面

  • AT24C02芯片有I2C接口,但是51单片机是没有I2C接口的,是用软件程序模拟I2C总线。
  • 51单片机是主机,AT24C02是从机。在这个实验中最重要的就是,弄清是主机还是从机去发送或接收数据。
  • 默认是主机控制SCL、SDA线。从机想控制SDA线,发送数据就要主机释放总线(SDA=1)。

二、功能描述

        用单片机模拟I2C与AT24C02通信,把数据写入AT24C02中,利用AT24C02存储数据掉电不丢失的特点,在单片机重新启动后,把对应地址的数据读取出来。

三、主要模块介绍

3.1 I2C总线介绍

        I2C 总线(Inter IC Bus)只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。有时钟线而且是由主机发送,从机接收或者由从机发送,主机接收,所以I2C是同步、半双工的通讯方式。

3.2 I2C总线协议

3.2.1数据有效规定

        I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

3.2.2起始信号和停止信号 

  • 起始信号:当SCL为高电平时,SDA由高电平向低电平变化。
  • 停止信号:当SCL为高电平时,SDA由低电平向高电平变化。

        起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态。

起始和终止信号由程序模拟如下:

/*
     *函数名:	I2C_star()
     *函数功能:I2C的起始信号
     *输入:	无
     *输出:	无
*/
void I2C_star()
{
	I2C_SDA=1;
	I2C_SCL=1;
	I2C_SDA=0;
	I2C_SCL=0;	
}
/*
     *函数名:	I2C_stop()
     *函数功能:I2C的停止信号
     *输入:	无
     *输出:	无
*/
void I2C_stop()
{
	 I2C_SDA=0;
	 I2C_SCL=1;
	 I2C_SDA=1;
}

3.2.3 发送应答和接收应答

        发送应答和接收应答都是相对于主机而言的。应答是低电平脉冲,非应答是高电平脉冲。

  • 发送应答:当主机在接收从机发送的一个数据后,主机发送应答信号给从机,还要不要继续发送数据。
  • 接收应答:主机发送一个数据给从机后,从机通过控制SDA(此时主机要释放总线)发送应答信号,主机根据应答信号决定还要不要发送数据。

 程序模拟如下:

/*
     *函数名:	 I2C_SendAck(bit ack)
     *函数功能:  主机发送应答
     *输入:	     ack:发送的一个应答
     *输出:	     无
*/
void I2C_SendAck(bit ack)//主机发送应答
{
	I2C_SDA=ack;
	I2C_SCL=1;
	I2C_SCL=0;
}
/*
     *函数名:	  I2C_ReadAck()
     *函数功能:   主机接收应答
     *输入:	      无
     *输出:	      ack:接收的应答
*/
bit I2C_ReadAck()//主机接收应答
{
	bit ack=0;
	I2C_SDA=1;//释放总线
	I2C_SCL=1;
	ack=I2C_SDA;
	I2C_SCL=0;
	return ack;
}

3.2.4 主机发送一个字节和接收一个字节

  1. 主机发送一个字节数据:在SCL低电平时,主机把数据依次写到SDA线上(高位在前),再拉高SCL,从机将在SCL高电平期间读取数据位,循环8次,即可发送一个字节数据。
  2. 主机接收一个字节数据:首先主机释放总线(SDA=1),在SCL低电平时,从机把数据依次写到SDA线上(高位在前),再拉高SCL,主机将在SCL高电平期间读取数据位,循环8次,即可接收一个字节数据。

程序如下:

/*
     *函数名:	 I2C_Writebyte(unsigned char byte)
     *函数功能: I2C总线主机发送一个字节数据
     *输入:	 byte:要发送的字节数据
     *输出:	 无
*/
void I2C_Writebyte(unsigned char byte)//主机发送一个字节的数据
{
	unsigned char i=0;
	for(i=0;i<8;i++)
	{
		I2C_SDA=byte&(0x80>>i);
		I2C_SCL=1;
		I2C_SCL=0;
	}
}
/*
     *函数名:	 I2C_Readbyte()
     *函数功能: I2C总线主机接收一个字节数据
     *输入:	 无
     *输出:	 byte:读取的数据
*/
unsigned char I2C_Readbyte()//主机接收一个字节数据
{
	unsigned char i,byte=0;
	I2C_SDA=1;//释放总线
	for(i=0;i<8;i++)
	{
		I2C_SCL=1;
		if(I2C_SDA==1){byte|=(0x80>>i);}//I2C_SDA上的数据已经是从机发送的数据
		I2C_SCL=0;
	}
	return byte;
}

3.3 AT24C02介绍

        我们开发板上使用的是 AT24C02(EEPROM)芯片,此芯片具有 I2C 通信接口,芯片内保存的数据在掉电情况下都不丢失, 所以通常用于存放一些比较重要的数据等。

3.3 字节写和随机读

        I2C总线寻址按照从机地址位数可分为两种,一种是 7 位,另一种是 10 位。采用 7 位的寻址字节的位定义如 下:

        一个从机 的 7 位寻址位有 4 位是固定位,3 位是可编程位。

  •  AT24C02的固定为是1010,可编程位本开发板上为000。从机地址加读/写位(0/1),就是起始信号后的第一个字节。
  1.  字节写的顺序:起始信号+从机地址带读/写位(0/1)+接收应答+指定写入数据的地址+接收应答+写入的数据+接收应答+停止信号。
  2.  随机读的顺序:起始信号+从机地址带读/写位(0/1)+接收应答+指定读取数据的地址+接收应答+   起始信号+从机地址带读/写位(0/1)+接收应答+主机接收数据+发送应答+停止信号。

 程序如下:

#define AT24C02_address 0xA0  //AT24C02的地址
/*
     *函数名:	AT24C02_Writebyte(unsigned char word_address,byte)
     *函数功能:向AT24C02中的某个地址写入一个字节数据
     *输入:	word_address:字节地址	 byte:写入的字节数据
     *输出:	无
*/
void AT24C02_Writebyte(unsigned char word_address,byte)
{
	bit ack=0;
	I2C_star();
	I2C_Writebyte(AT24C02_address);
	ack=I2C_ReadAck();
	I2C_Writebyte(word_address);
	ack=I2C_ReadAck();
	I2C_Writebyte(byte);
	ack=I2C_ReadAck();
	I2C_stop();
}
/*
     *函数名:	  AT24C02_Readbyte(unsigned char word_address)
     *函数功能:  主机读取AT24C02的指定地址的数据
     *输入:	  word_address:数据的地址
     *输出:	  byte:被读取的字节数据
*/
unsigned char AT24C02_Readbyte(unsigned char word_address)
{
	unsigned char byte=0;
	bit ack=0;
	I2C_star();
	I2C_Writebyte(AT24C02_address);
	ack=I2C_ReadAck();
	I2C_Writebyte(word_address);
	ack=I2C_ReadAck();
	I2C_star();
	I2C_Writebyte(AT24C02_address|0x01);
	ack=I2C_ReadAck();
	byte=I2C_Readbyte();
	I2C_SendAck(1);
	I2C_stop();
	return byte;
}

四、测试文件test.c

#include <REGX52.H>
#include"I2C.h"
#include"LCD1602.h"
#include"AT24C02.h"
#include"Independentkey.h"
#include"Delay.h"
int main()
{
	unsigned char keynum,num=0;
	LCD_Init();
	LCD_ShowNum(1,1,0,3);
	AT24C02_Writebyte(0x01,1);
	Delay(5);
	AT24C02_Writebyte(0x02,2);
	Delay(5);
	AT24C02_Writebyte(0x03,3);
	Delay(5);
	AT24C02_Writebyte(0x04,4);
	Delay(5);
	while(1)
	{
		keynum=Independentkey();
		if(keynum!=0)
		{
			 if(keynum==1)
			 {
			 	num=AT24C02_Readbyte(0x01);
				LCD_ShowNum(1,1,num,3);
			 }
			 if(keynum==2)
			 {
			 	num=AT24C02_Readbyte(0x02);
				LCD_ShowNum(1,1,num,3);
			 }
			 if(keynum==3)
			 {
			 	num=AT24C02_Readbyte(0x03);
				LCD_ShowNum(1,1,num,3);
			 }
			 if(keynum==4)
			 {
			 	num=AT24C02_Readbyte(0x04);
				LCD_ShowNum(1,1,num,3);
			 }
		}
	}
}

五、现象描述

        在AT24C02的0x01,0x02,0x03,0x04地址处,写入1,2,3,4,然后按下独立按键,分别读取出来(可掉电不丢失数据),并显示在LCD1602上。

  • 写在后面:当遇到听不懂的或者程序出现什么问题,首先不应该去抱怨什么什么这么难,先是静下心来,一遍听不懂我就听两遍,两边不懂就三遍,直到听懂为止。也可以出去走走,把思路捋清,再回去看视频,有些东西别人以为你是知道的就没说,但实际上你是不知道的,导致思路很混乱,觉得为什么是这样的啊,从而卡在那里很久。写在前面的内容就是我在看视频过程中不知道的点和值得注意的点。程序运行不出来就仔细找问题的原因,以前一直以为Warning没什么用,就只是报出个警告,程序照样可以运行,这次找问题找半天,原来是有个if()语句判断相等的时候只有一个“=”号,编译器报警告但是没有报错,我就以为是我程序哪个地方写的不对,改改改的,浪费很多时间。最后,遇到问题最重要的就是静下心来,仔细寻找问题原因,然后去解决问题。

猜你喜欢

转载自blog.csdn.net/ssssshhbh/article/details/129220526