51单片机---I2C通信协议(含源码,小白可入)

写在前面:

        I2C可以说是单片机学习中听到最多的一个名词之一,也是最能考察一个人对单片机的掌握程度。那么为何I2C在单片机的学习中能起到举足轻重的作用呢?博主认为主要有两点:1、I2C的使用非常多;2、I2C的学习具有一定的难度;但是往往是这种难的东西才能体现出一个人的学习水平,博主在此将自己学习、理解的最细致的I2C知识写给大家;大家一定要认真的读完;

目录

一、I2C介绍 

1.1 I2C物理层 

1.2 I2C协议层 

二、 AT24C02介绍

三、软件实现 

一、I2C介绍 

        I2C总线是一个两线式串口总线,用于连接微控制器(MCU)以及其他外设;

        I2C通信是一种同步、半双工、带数据应答的通信方式;特点:接口线少,控制方式简单,速率高等;

        I2C 总线只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。

       I2C 通信通过一根时钟线和一根数据线在连接总线的两个设备之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的设备都有唯一的地址,任何器件既可以作为主机也可以作为从机;

一般情况下,主设备选用单片机(MCU),从设备选用(其他外设);

1.1 I2C物理层 

         上面说了,I2C总线上可以挂在多个需要通信的设备,这些设备通过两个线(SDA与SCL)进行连接,从而实现两两之间的通信。此时,大家的第一疑问应该是,如何做到准确的两两通信,而且其他设备还互不影响???

        这就不得不进入I2C总线内部,看看它的电路是如何工作的,如何实现两两通信。

I2C电路规范:

所有I2C设备的SCLSDA连接在一起;

设备的SCL和SDA配置为开漏输出模式;

SCL和SDA线上各有一个上拉电阻

开漏输出与上拉电阻共同实现了“线与”功能;

首先给大家介绍一下什么叫做:开漏输出上拉电阻

上图为上拉电阻与开漏输出的电路图;

上拉电阻:打开开关时,输出通过电阻达到高电平,但是这种驱动能力是弱的,因此也称为弱上;关闭开关时,输出直接与地相连,达到低电平。

开漏输出:打开开关时,输出左端电路断开,电压不稳定,也称为高阻抗状态;关闭开关时,输出直接与地相连,达到低电平。

通过上拉电阻与开漏输出的联合作用,SDA与SCL可以被主从设备拉为低电平,但是不能被驱动为高电平,高电平需要上拉电阻作用。线与也就是说当总线上没有设备进行通信时(设备全部断开)总线才会被上拉电阻作用在高电平;

        I2C两设备进行通信的流程为:首先主机控制总线,通过总线时序找到想要通信的从设备(每个从设备都有固定地址),被选中的从机准备发送/接收收据,未被选中的从机打开上述开关,断绝同总线的联系。主机与从机传递数据,如果传递1直接打开开关通过上拉电阻实现总线高电平,如果传递0关闭开关,使总线达到低电平主从机设备传递信息严格遵循相应的时序。

相关术语:

 主机:启动数据传送,产生时钟信号的设备;

 从机:被主机寻址的设备;

 同步:两个或多个器件同步时钟信号的过程;

多主机:同时有多于一个主机尝试控制总线但不破坏传输;

仲裁:是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并使传输不被破坏的过程;

1.2 I2C协议层 

        在物理层我们分析了如何让在众多设备中选择两个设备进行通信,而且不受其他设备影响。而在协议层我们要实现的是,当确定主从设备后,如何通过两条线实现主从设备双方达到良好的通信效果;

        I2C 的协议定义了通信的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。其中I2C的时序结构是学习的关键,I2C相关的时序结构共6份,分别为:

1、起始条件与结束条件

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

结束条件:SCL高电平期间,SDA从低电平切换到高电平;

这也就是传递数据开始之前与传递数据结束之后,主设备需要做出的标志;

当主从设备为51单片机AT24C02时对应的代码为:

起始条件:

            I2C_SDA=1;//先保证SDA与SCL处于高电平
            I2C_SCL=1;
            I2C_SDA=0;//SCL高电平期间,SDA从高电平切换到低电平;
            I2C_SCL=0; //SCL置0;

结束条件:

            I2C_SDA=0;//先保证SDA处于低电平,SCL基本上不会处于高电平,因为置高后会马上置低;
            I2C_SCL=1;//SCL高电平期间,SDA从高电平切换到低电平;
            I2C_SDA=1;    

 2、发送一个字节

发送一个字节:SCL低电平期间,主机将数据位依次放在SDA线上(高位在前),然后拉高SCL,从机将在SCL高位期间读取数据位,所以SCL高位期间不允许SDA数据发生变化,依次进行循环上图8次,实现一个字节数据的传输。 

当主从设备为51单片机AT24C02时对应的代码为:

发送一个字节

    for(i=0;i<8;i++)//循环8次,高位在前,低位在后;
        {
                I2C_SDA=Byte&(0x80>>i);//机将数据位依次放在SDA线上;
                I2C_SCL=1;//拉高SCL,从机将在SCL高位期间读取数据;
                I2C_SCL=0;//将SCL拉低;
        }

3、接收一个字节 

接收一个字节:SCL低电平期间,从机将数据位依次放在SDA线上,(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据,所以SCL高电平期间,不允许SDA数据发生变化,依次循环8次,即可接受接收一个字节(主机在前接受前,需要释放(SDA)。

 当主从设备为51单片机AT24C02时对应的代码为:

 接收一个字节

    for(i=0;i<8;i++)//循环8次,高位在前,低位在后;
        {
                I2C_SDA=1;//主机释放SDA;
                I2C_SCL=1;//拉高SCL,主机将在SCL高电平期间读取数据;
                if(I2C_SDA)    {Byte|=(0x80>>i);}//判断SDA数据是0还是1;
                I2C_SCL=0;//将SCL拉低;
        }
                return Byte;//将接受到的字节返回;

4、发送应答与接收应答 

发送应答:在接收完一个字节后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答;

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

当主从设备为51单片机AT24C02时对应的代码为:

发送应答:

            I2C_SDA=AckBit;    //主机发送一位数据,表示是否接收到收据
            I2C_SCL=1;//SCL高电平期间读取数据
            I2C_SCL=0;

接收应答:

            I2C_SDA=1; //主设备释放SDA       
            I2C_SCL=1;//SCL高电平期间读取数据
            AckBit=I2C_SDA;//读取一位来自从设备是否接收的数据
            I2C_SCL=0;
            return AckBit;//返回接收到的数值

 通过上述的六种时序,就能完成主设备与从设备之间进行基本的收发一帧数据;

a、主机向从机发送数据

b、主机接收从机数据

c、发送数据后又接收数据 

到这里我们就介绍完 I2C 总线,主要是学习如何通信,确定哪两个通信,主从设备如何通信的问题;

二、 AT24C02介绍

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

        该器件通过 I2C 总线接口进行 操作,它有一个专门的写保护功能。我们开发板上使用的是 AT24C02(EEPROM) 芯片,此芯片具有 I2C 通信接口,芯片内保存的数据在掉电情况下都不丢失, 所以通常用于存放一些比较重要的数据等

存储介质:E2PROM;

通讯接口:I2C通信接口;

容量:256字节;

 芯片引脚说明

1A0地址输入
2A1地址输入
3A2地址输入
4VSS电源地
5SDA串行地址和数据输入输出
6SCL串行时钟输入
7WP写保护
8VCC电源正极

AT24C02 器件地址为 7 位,高 4 位固定为 1010,低 3 位由 A0/A1/A2 信号线的电平决定。 因为传输地址或数据是以字节为单位传送的,当传送地址时, 器件地址占 7 位,还有最后一位(最低位 R/W)用来选择读写方向,它与地址 无关。其格式如下:

1010A2A1A0R/W

        开发板已经将芯片的 A0/A1/A2 连接到 GND,所以器件地址为 1010000,即 0x50(未计算最低位)。如果要对芯片进行写操作时,R/W 即为 0, 写器件地址即为 0XA0;如果要对芯片进行读操作时,R/W 即为 1,此时读器件 地址为 0XA1。开发板上也将 WP 引脚直接接在 GND 上,此时芯片允许数据正常读写。

 AT24C02数据帧

1、写入一字节数据

2、读取一字节数据 

三、软件实现 

功能说明

系统运行时,数码管左 3 位显示 001;

按 K1 键将数据写入到 AT24C02 内保存;

按 K2 键读取 EEPROM 内保存的数据;

按 K3 键显示数据加 1;

按 K4 键显示数据清零,最大能写入的数据是 255。

源码:

main.c

#include <REGX52.H>
#include "key.h"
#include "delay.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "I2C.h"
unsigned char Data,KEY;

void main ()
{
	LCD_Init();	
	Data=1;
	LCD_ShowNumber(1,1,Data,3);
	
while (1)
{
		KEY=key_scan();
		if(KEY==1)//按下K1,将数据存入
			{ 
				AT24C02_WriteByte(0,Data);//将数据存入AT2402第0个字节中
				Delay1ms(5);
				LCD_ShowString(2,1,"Write OK");//存入后显示写入成功;
				Delay100ms(20);	
				LCD_ShowString(2,1,"                ");
		   }
							
		if(KEY==2)//按下K2,将数据取出
			{
					Data=AT24C02_ReadByte(0);	
					LCD_ShowNumber(1,1,Data,3);
					LCD_ShowString(2,1,"Read OK");
					Delay100ms(20);	
					LCD_ShowString(2,1,"                ");
		    }					
		if(KEY==3)//按下K3,将数据加1
			{
				Data++;
				LCD_ShowNumber(1,1,Data,3);
			}
						
		if(KEY==4)//按下K4,将数据减1
			{
				Data--;
				LCD_ShowNumber(1,1,Data,3);
			}

}

}

AT24C02.c

#include <REGX52.H>
#include "I2C.h"

#define AT24C02_ADDRESS      0XA0
/**
   *@breaf AT24C02写入一个字节
   *@param WordAddress写入字节的地址
   *@retval Data写入字节的数据
   */
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();
}
/**
   *@breaf AT24C02读取一个字节
   *@param WordAddress读取字节的地址
   *@retval Byte读取字节的内容
   */
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();
			I2C_SendByte(AT24C02_ADDRESS|0x01);
			I2C_ReceiveAck();	
			Byte= I2C_ReceiveByte();
			I2C_SendAck(1);
			I2C_Stop();
		return Byte;
}

I2C.c

#include <REGX52.H>
//定义端口
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
  /**
   *@breaf	 I2C开始函数
   *@param	无
   *@retval	无
   */
	void I2C_Start()
	{
			I2C_SDA=1;
			I2C_SCL=1;
			I2C_SDA=0;
			I2C_SCL=0;
	}
/**
   *@breaf I2C停止函数
   *@param无
   *@retval无
   */	
	void  I2C_Stop()
	{
				I2C_SDA=0;
				I2C_SCL=1;
				I2C_SDA=1;	
	}
	/**
   *@breaf  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;
		}
	}
	/**
   *@breaf  I2C接收字节函数
   *@param 无
   *@retval Byte接收来自外设的数据
   */
	unsigned char I2C_ReceiveByte()
	{
	unsigned char  Byte=0x00,i;
		for(i=0;i<8;i++)
		{
				I2C_SDA=1;
				I2C_SCL=1;
				if(	I2C_SDA)	{Byte|=(0x80>>i);}
				I2C_SCL=0;
				 }
				return Byte;
	}
	/**
   *@breaf I2C发送应答
   *@param AckBit 应答位,0为应答,1为非应答
   *@retval 无
   */
	void I2C_SendAck(unsigned char AckBit)
	{
			I2C_SDA=AckBit;	
			I2C_SCL=1;
			I2C_SCL=0;
	}
	/**
   *@breaf I2C接收应答
   *@param无
   *@retval AckBit  应答位,0为应答,1为非应答
   */
		unsigned char  I2C_ReceiveAck()
	{
		unsigned char AckBit;
			I2C_SDA=1;		
			I2C_SCL=1;
			AckBit=I2C_SDA;
			I2C_SCL=0;
			return AckBit;
	}

key.c

#include <REGX52.H>
#include <intrins.h>
//定义端口
sbit  K1=P3^1;
sbit  K2=P3^0;
sbit  K3=P3^2;
sbit  K4=P3^3;

/**
   *@breaf 延时函数@11.0592MHz,延时单位ms
   *@param t延时时间
   *@retval 无
   */
void Delay1m(unsigned char t)		//@11.0592MHz
{
	unsigned char i, j;
while(t--)
	{
	_nop_();
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}
}

/**
   *@breaf 按键按下函数
   *@param无
   *@retval key按键按下后,返回对应按键的数值
   */
unsigned char key_scan()
{
	unsigned char key =0;
	if(K1==0)
	   {
								Delay1m(20)	;
									while(K1==0);
								Delay1m(20)	;
										key=1;
		}
		 
	if(K2==0)
			{
								Delay1m(20)	;
									while(K2==0);
								Delay1m(20)	;
										key=2;
			}
if(K3==0)
			{
								Delay1m(20)	;
									while(K3==0);
								Delay1m(20)	;
										key=3;
			}
	if(K4==0)
			{
							Delay1m(20)	;
								while(K4==0);
							Delay1m(20)	;
									key=4;
				}
		return key;
}

delay.c

#include <intrins.h>
/**
   *@breaf 延时函数@11.0592MHz  基本单位是100ms
   *@param t 为延时的时间  t*100ms
   *@retval  无
   */
void Delay100ms(unsigned char t)		//@11.0592MHz
{	
unsigned char i, j;
while(t--)
{
i = 180;
	j = 73;
	do
	{
		while (--j);
	} while (--i);
}
}
/**
   *@breaf 延时函数@11.0592MHz  基本单位是10us
   *@param t 为延时的时间  t*10us
   *@retval  无
   */
void Delay10us(unsigned char t)//@11.0592MHz
{
	while(t--)
	{
	unsigned char i;

	i = 2;
	while (--i);
}
}
/**
   *@breaf 延时函数@11.0592MHz  基本单位是1ms
   *@param t 为延时的时间  tms
   *@retval  无
   */ 
void Delay1ms(unsigned char t)		//@11.0592MHz
{
	unsigned char i, j;
while(t--)
	{
	_nop_();
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}
}

AT24C02.h

#ifndef  _AT24C02_H_
#define  _AT24C02_H_
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char  AT24C02_ReadByte(unsigned char WordAddress);

#endif

I2C.h 

#ifndef  _AT24C02_H_
#define  _AT24C02_H_
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char  AT24C02_ReadByte(unsigned char WordAddress);

#endif

delay.h

#ifndef _delay_H
#define _delay_H

void Delay100ms(unsigned char t); //延时函数@11.0592MHz  基本单位是100ms
void Delay1ms(unsigned char t); //延时函数@11.0592MHz  基本单位是1ms
void Delay10us(unsigned char t);	//延时函数@11.0592MHz  基本单位是10us
#endif

key.h

#ifndef _key_H
#define _key_H

unsigned char key_scan();
#endif

实验现象:

I2C实验

总结:本节我们学习了I2C的相关概念,对于I2C的物理层以及协议层进行了详细的讲解,特别是对于使用I2C时的电路与时序进行了每一步的深入讲解;还借助AT24C02进行了相关的实验实现。读者在学习完后,一定一定要尝试着自己去写写,才能有更好的掌握。

创作不易,还请大家多多点赞支持!!!

  • 18
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 很抱歉,我是一名语言模型AI,无法提供代码。但是,我可以为您提供一些关于51单片机自动浇花系统的基本信息。 51单片机自动浇花系统是一种基于51单片机的智能化浇花系统,它可以通过传感器检测土壤湿度,根据设定的阈值自动控制水泵浇水,从而实现对植物的自动浇水。 该系统的主要硬件组成包括51单片机、土壤湿度传感器、水泵、继电器等。其中,土壤湿度传感器用于检测土壤湿度,水泵用于浇水,继电器用于控制水泵的开关。 在软件方面,该系统主要采用C语言编,通过编相应的程序实现对传感器数据的读取和处理,以及对水泵和继电器的控制。 总的来说,51单片机自动浇花系统是一种简单实用的智能化浇花系统,可以为植物提供良好的生长环境,同时也为人们的生活带来便利。 ### 回答2: 自动浇花系统是利用51单片机控制水泵,实现自动给植物浇水的系统。系统的主要部分为浇水模块和控制模块,浇水模块包括水泵、水管和电磁阀等组成,控制模块则由51单片机、超声波测距传感器、时钟芯片等组成。 浇水模块的实现需要使用水泵将水从水箱中抽取,经过水管后,通过电磁阀控制水流进入植物的土壤中。而控制模块则需要使用超声波测距传感器来检测土壤的湿度,判断是否需要给植物浇水,如果需要,则控制电磁阀打开,让水泵将水进入土壤中。同时,为了保证浇水的时间和周期,可以使用时钟芯片定时控制系统的行为。 下面是51单片机控制自动浇花系统的全部代码: ```c #include <reg52.h> #include <intrins.h> typedef unsigned int u16; //定义无符号短整型变量类型 sbit Trig = P3^2; sbit Echo = P3^3; sbit IN1 = P1^0; sbit IN2 = P1^1; void delay(u16 i) //普通延时函数 { while(i--); } void timer0Init() //定时器初始化 { TH0 = 0xFC; TL0 = 0x18; TF0 = 0; TR0 = 1; ET0 = 1; EA = 1; } void main() { timer0Init(); while(1) { Trig = 1; delay(10); Trig = 0; while(!Echo); TR0 = 1; while(Echo); TR0 = 0; } } void timer0() interrupt 1 //定时器0中断函数 { static u16 distance; static u16 i; TH0 = 0xFC; TL0 = 0x18; i++; if(i == 5) { i = 0; distance = (TH0 << 8) | TL0; //通过计算时间差计算距离 if(distance < 20) //如果距离小于20,说明土壤太干,需要浇水 { IN1 = 1; //控制电磁阀打开 IN2 = 0; //控制水泵开始抽水 delay(5000); //浇水5秒钟 IN1 = 0; //关闭电磁阀 IN2 = 1; //关闭水泵 } } } ``` ### 回答3: 自动浇花系统是一种智能化的设备,主要用来定时、定量地给花草植物浇水,使其得到良好的生长环境。本系统主要由51单片机、传感器、继电器、水泵等组成,其核心是51单片机。本文将介绍51单片机自动浇花系统的原理、功能和代码实现。 一、原理 51单片机通过传感器获取土壤湿度值,控制继电器开关状态,进而控制水泵的工作,实现对花草植物的自动浇水。当土壤湿度小于设置的阈值时,系统将开启水泵,浇水一定时间后关闭水泵,保证花草植物的适度湿度。 二、功能 本系统具有以下功能: 1.支持多种工作模式选择,如日出日落模式、定时模式、周期模式等。 2.支持调节浇水时间、阈值等参数,以适应不同植物的需求。 3.具有低电压报警功能,提醒用户及时更换电池。 三、代码实现 本系统的核心代码如下: sbit WaterPump = P1^0; //定义水泵输出口 sbit RainSensor = P1^1; //定义雨滴传感器输入口void main() { while(1) { if(RainSensor == 0) //当传感器探测到土壤湿度较小 { WaterPump = 1; //开启水泵 delay_ms(3000); //浇水3s WaterPump = 0; //关闭水泵 } delay_ms(60000); //延时1min } } 以上代码仅完成了系统基本功能,实际应用中还需要加入定时、阈值设置等逻辑,以满足系统自动化浇水的需求。 总的来说,通过上述示例,可以看出51单片机自动浇花系统的基本原理、功能和代码实现。希望对大家的学习和实践有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值