单片机 IIC 总线协议 和 详细例程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31389903/article/details/51726068

先简单的说明以下I2C总线, I2C 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线SDA,另一根是时钟线SCL

处理器和芯片间的通信可以形象的比喻成两个人讲话:1、你说的别人得能听懂:双方约定信号的协议。2、你的语速别人得能接受:双方满足时序要求。

看IIC协议先:两条线可以挂多个设备。IIC设备(稍微有点智能的)里有个固化的地址。只有在两条线上传输的值等于我(IIC设备)的地址时,我才作出响应。


开始信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号。同时IIC总线上的设备检测到这个开始信号它就知道处理器要发送数据了。

停止信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。同时IIC总线上的设备检测到这个停止信号它就知道处理器已经结束了数据传输,我们就可以各忙各个的了,如休眠等。

看数据怎么传:SDA上传输的数据必须在SCL为高电平期间保持稳定:因为外接IIC设备在SCL为高电平的期间采集数据方知SDA是高或低电平。SDA上的数据只能在SCL为低电平期间翻转变化。

响应信号(ACK):处理器把数据发给外接IIC设备,如何知道IIC设备数据已经收到呢?就需要外接IIC设备回应一个信号给处理器。处理器发完8bit数据后就不再驱动总线了(SDA引脚变输入),而SDA和SDL硬件设计时都有上拉电阻,所以这时候SDA变成高电平。那么在第8个数据位,如果外接IIC设备能收到信号的话接着在第9个周期把SDA拉低,那么处理器检测到SDA拉低就能知道外接IIC设备数据已经收到。

IIC数据从最高位开始传输。


再进一步说:IIC总线是允许挂载多个设备的,如何访问其中一个设备而不影响其他设备呢?

7bit表示从地址,那么可以挂载的从设备数是2的7次方128个。处理器想写的话:先发送起始位,再发一个8bit数据:前7bit表示从地址,第8bit表示读或者写。0write是处理器往IIC从设备发,1read是IIC从设备往处理器发。第9个时钟周期回复响应信号。

下面就以AT24Cxx为例详细说明一下:

首先发出一个start信号,从设备地址,R/W(0,写),回应ACK表示有这个从设备存在。这时候是处理器从指定的从设备读数据的从设备里8bit存储地址的指定。所以这里R/W是0为写。ACK回应有这个设备的话,处理器把要访问的从设备里的8bit存储地址写好。ACK对方回应。继续一个start信号+从设备地址,最低位是高电平表示读数据,回应ACK表示有这个从设备存在。在读数据的时候,每发出一个时钟,处理器会SDA上的数据存起来。那么发出8个时钟后处理器就能得到8位的数据。这时候若想连续读就不断回应ACK信号否则就发出停止信号。

读的过程:start信号,从设备地址,写,待读取存储地址,再一个start信号,从设备地址,读,8个时钟,从设备就把对应的数据反馈给处理器。

start信号,哪一个设备地址,写,紧跟连续两个字节的数据:要写的地址,对方收到8bit地址后回应ACK,再8bit数据发给从设备,对方收到8bit数据后回应ACK,处理器写完后发送停止信号。


例程:

实现功能:0-99秒的自动计时器,随机关断电源,在通电以后计时器接着断电时的状态继续计时

实现工具:51单片机
电路图如图所示:

所用芯片是:AT24C02芯片,51单片机基本搭载这块芯片。 AT24C系列E2PROM的型号地址皆为1010,器件地址中的低3位为引脚地址A2A1A0,对应器件寻址字节中的D3D2D1 位,在硬件设计时由连接的引脚电平给定。对 AT24C 系列 E2PROM的读写操作完全遵守 I2C总线的主收从发和主发从收的规则。

#include<reg51.h>
#include<intrins.h>
#define uint unsigned int
#define uchar unsigned char
unsigned char sec; // 定义计数值,每过1s,sec加1
unsigned int tcnt; // 定时中断次数
bit write = 0; // 写2408的标志
sbit sda = P2^0; // I2C接口SDA定义
sbit scl = P2^1; // I2C接口SCL定义
sbit dula = P2^6;
sbit wela = P2^7;
unsigned char j,k;

void delay(unsigned char i) // 延时程序
{
	for( j=i ; j>0 ; j-- )
	for( k=125; k>0; k-- );
}

uchar code table[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; // 数码管编码

void display(uchar bai_c,uchar sh_c) // 显示程序
{
	dula = 0;
	P0 = table[bai_c];// 显示百位
	dula = 1;
	dula = 0;
	wela = 0;
	P0 = 0x7e;
	wela = 1;
	wela = 0;
	delay(5);
	dula = 0;
	P0 = table[sh_c];
	dula = 1;
	dula = 0;
	wela = 0;
	P0 = 0x7d;
	wela = 1;
	wela = 0;
	delay(5);	
}
	
// 24c02读写驱动程序
void delay1(unsigned char x)
{
	unsigned int i ;
	for( i=0; i<x; i++);
}	
	
void flash() // 延时子函数
{
	; ; ;
}
	
void x24c08_init()// 2402初始化子程序
{
	scl = 1;
	flash();
	sda = 1;
	flash();
}
	
void start()// 启动I2C总线
{
	sda = 1;
	flash();
	scl = 1;
	flash();
	sda = 0;
	flash();
	scl = 0;
	flash();
}
	
void stop()// 停止I2C总线
{ 
	sda = 0;
	flash();
	scl = 1;
	flash();
	sda = 1;
	flash();
}

void writex(unsigned char j)// 写一个字节
{
	unsigned char i,temp;
	temp = j;
  for(i=0; i<8; i++)
  {
  	temp = temp<<1;
  	scl = 0;
  	flash();
  	sda = CY;
  	flash();
  	scl = 1;
  	flash();
  }
   	scl = 0;
  	flash();
  	sda = 1;
  	flash();	
}

unsigned char readx()// 读一个字节
{
	unsigned char i,j,k=0;
	scl = 0;
  flash();
  sda = 1;
  for(i=0; i<8; i++)
  {
  	 flash();
  	 scl = 1;
  	 flash();
  	 if(sda==1) j = 1;
  	 	else j = 0;
  	 k = (k<<1)|j;
  	 scl = 0;
  	}
  flash();
  return(k);
}
	
void clock()// I2C总线应答子函数
{
	unsigned char i = 0;
	scl = 1;
	flash();
	while((sda==1)&&(i<255)) i++;
	scl = 0;
	flash();
}
	

unsigned char x24c08_read(unsigned char address)  // 从24c02的地址address中读取一个字节数据
{
	unsigned char i;
	start();
	writex(0xa0);
	clock();
	writex(address);
	clock();
	start();
	writex(0xa1);
	clock();
	i = readx();
	stop();
	delay(10);
	return(i);
}

void x24c08_write(unsigned char address,unsigned char info)// 向2402的address地址中写入一个字节数据
{
	EA = 0;
	start();
	writex(0xa0);
	clock();
	writex(address);
	clock();
	writex(info);
	clock();
	stop();
	EA = 1;
	delay1(50); 
}

void t0(void) interrupt 1 using 0 // 定时中断服务函数
{
	TH0 = (65536-50000)/256; // 对TH0,TL0赋值
	TL0 = (65536-50000)%256;
	tcnt++; // 每过250us tcnt加1
	if(tcnt == 20)
		{
			tcnt = 0; // 重新再计
			sec++;
			write = 1; // 1s写一次24c02
			if(sec == 100)
				{ 
					sec = 0; // 定时100s,从0开始计时
				}
		}	
}	

void main() // 主函数
{
	unsigned char i;
	TMOD = 0x01; // 定时器工作在方式1
	ET0 = 1;
	EA = 1; // 开中断
	x24c08_init();// 初始化24c08
	sec = x24c08_read(2); // 读出保存的数据赋予sec
	TH0 = (65536-50000)/256;
	TL0 = (65534-50000)%256 ; // 使定时器0.05s中断一次
	TR0 = 1;// 启动定时器
	while(1)
	{
		i = 10;
		while(i--)
		{ 
			display(sec/10,sec%10);
			}
			
		if(write = 1) // 判断计时器是否计时1s
			{
				write = 0; // 清0
				x24c08_write(2,sec); // 在24c02的地址中写入数据sec
				}
		}
	}
	

经试验,得到实验结果。

6

先简单的说明以下I2C总线, I2C 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线SDA,另一根是时钟线SCL

处理器和芯片间的通信可以形象的比喻成两个人讲话:1、你说的别人得能听懂:双方约定信号的协议。2、你的语速别人得能接受:双方满足时序要求。

看IIC协议先:两条线可以挂多个设备。IIC设备(稍微有点智能的)里有个固化的地址。只有在两条线上传输的值等于我(IIC设备)的地址时,我才作出响应。


开始信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号。同时IIC总线上的设备检测到这个开始信号它就知道处理器要发送数据了。

停止信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。同时IIC总线上的设备检测到这个停止信号它就知道处理器已经结束了数据传输,我们就可以各忙各个的了,如休眠等。

看数据怎么传:SDA上传输的数据必须在SCL为高电平期间保持稳定:因为外接IIC设备在SCL为高电平的期间采集数据方知SDA是高或低电平。SDA上的数据只能在SCL为低电平期间翻转变化。

响应信号(ACK):处理器把数据发给外接IIC设备,如何知道IIC设备数据已经收到呢?就需要外接IIC设备回应一个信号给处理器。处理器发完8bit数据后就不再驱动总线了(SDA引脚变输入),而SDA和SDL硬件设计时都有上拉电阻,所以这时候SDA变成高电平。那么在第8个数据位,如果外接IIC设备能收到信号的话接着在第9个周期把SDA拉低,那么处理器检测到SDA拉低就能知道外接IIC设备数据已经收到。

IIC数据从最高位开始传输。


再进一步说:IIC总线是允许挂载多个设备的,如何访问其中一个设备而不影响其他设备呢?

7bit表示从地址,那么可以挂载的从设备数是2的7次方128个。处理器想写的话:先发送起始位,再发一个8bit数据:前7bit表示从地址,第8bit表示读或者写。0write是处理器往IIC从设备发,1read是IIC从设备往处理器发。第9个时钟周期回复响应信号。

下面就以AT24Cxx为例详细说明一下:

首先发出一个start信号,从设备地址,R/W(0,写),回应ACK表示有这个从设备存在。这时候是处理器从指定的从设备读数据的从设备里8bit存储地址的指定。所以这里R/W是0为写。ACK回应有这个设备的话,处理器把要访问的从设备里的8bit存储地址写好。ACK对方回应。继续一个start信号+从设备地址,最低位是高电平表示读数据,回应ACK表示有这个从设备存在。在读数据的时候,每发出一个时钟,处理器会SDA上的数据存起来。那么发出8个时钟后处理器就能得到8位的数据。这时候若想连续读就不断回应ACK信号否则就发出停止信号。

读的过程:start信号,从设备地址,写,待读取存储地址,再一个start信号,从设备地址,读,8个时钟,从设备就把对应的数据反馈给处理器。

start信号,哪一个设备地址,写,紧跟连续两个字节的数据:要写的地址,对方收到8bit地址后回应ACK,再8bit数据发给从设备,对方收到8bit数据后回应ACK,处理器写完后发送停止信号。


例程:

实现功能:0-99秒的自动计时器,随机关断电源,在通电以后计时器接着断电时的状态继续计时

实现工具:51单片机
电路图如图所示:

所用芯片是:AT24C02芯片,51单片机基本搭载这块芯片。 AT24C系列E2PROM的型号地址皆为1010,器件地址中的低3位为引脚地址A2A1A0,对应器件寻址字节中的D3D2D1 位,在硬件设计时由连接的引脚电平给定。对 AT24C 系列 E2PROM的读写操作完全遵守 I2C总线的主收从发和主发从收的规则。

#include<reg51.h>
#include<intrins.h>
#define uint unsigned int
#define uchar unsigned char
unsigned char sec; // 定义计数值,每过1s,sec加1
unsigned int tcnt; // 定时中断次数
bit write = 0; // 写2408的标志
sbit sda = P2^0; // I2C接口SDA定义
sbit scl = P2^1; // I2C接口SCL定义
sbit dula = P2^6;
sbit wela = P2^7;
unsigned char j,k;

void delay(unsigned char i) // 延时程序
{
	for( j=i ; j>0 ; j-- )
	for( k=125; k>0; k-- );
}

uchar code table[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; // 数码管编码

void display(uchar bai_c,uchar sh_c) // 显示程序
{
	dula = 0;
	P0 = table[bai_c];// 显示百位
	dula = 1;
	dula = 0;
	wela = 0;
	P0 = 0x7e;
	wela = 1;
	wela = 0;
	delay(5);
	dula = 0;
	P0 = table[sh_c];
	dula = 1;
	dula = 0;
	wela = 0;
	P0 = 0x7d;
	wela = 1;
	wela = 0;
	delay(5);	
}
	
// 24c02读写驱动程序
void delay1(unsigned char x)
{
	unsigned int i ;
	for( i=0; i<x; i++);
}	
	
void flash() // 延时子函数
{
	; ; ;
}
	
void x24c08_init()// 2402初始化子程序
{
	scl = 1;
	flash();
	sda = 1;
	flash();
}
	
void start()// 启动I2C总线
{
	sda = 1;
	flash();
	scl = 1;
	flash();
	sda = 0;
	flash();
	scl = 0;
	flash();
}
	
void stop()// 停止I2C总线
{ 
	sda = 0;
	flash();
	scl = 1;
	flash();
	sda = 1;
	flash();
}

void writex(unsigned char j)// 写一个字节
{
	unsigned char i,temp;
	temp = j;
  for(i=0; i<8; i++)
  {
  	temp = temp<<1;
  	scl = 0;
  	flash();
  	sda = CY;
  	flash();
  	scl = 1;
  	flash();
  }
   	scl = 0;
  	flash();
  	sda = 1;
  	flash();	
}

unsigned char readx()// 读一个字节
{
	unsigned char i,j,k=0;
	scl = 0;
  flash();
  sda = 1;
  for(i=0; i<8; i++)
  {
  	 flash();
  	 scl = 1;
  	 flash();
  	 if(sda==1) j = 1;
  	 	else j = 0;
  	 k = (k<<1)|j;
  	 scl = 0;
  	}
  flash();
  return(k);
}
	
void clock()// I2C总线应答子函数
{
	unsigned char i = 0;
	scl = 1;
	flash();
	while((sda==1)&&(i<255)) i++;
	scl = 0;
	flash();
}
	

unsigned char x24c08_read(unsigned char address)  // 从24c02的地址address中读取一个字节数据
{
	unsigned char i;
	start();
	writex(0xa0);
	clock();
	writex(address);
	clock();
	start();
	writex(0xa1);
	clock();
	i = readx();
	stop();
	delay(10);
	return(i);
}

void x24c08_write(unsigned char address,unsigned char info)// 向2402的address地址中写入一个字节数据
{
	EA = 0;
	start();
	writex(0xa0);
	clock();
	writex(address);
	clock();
	writex(info);
	clock();
	stop();
	EA = 1;
	delay1(50); 
}

void t0(void) interrupt 1 using 0 // 定时中断服务函数
{
	TH0 = (65536-50000)/256; // 对TH0,TL0赋值
	TL0 = (65536-50000)%256;
	tcnt++; // 每过250us tcnt加1
	if(tcnt == 20)
		{
			tcnt = 0; // 重新再计
			sec++;
			write = 1; // 1s写一次24c02
			if(sec == 100)
				{ 
					sec = 0; // 定时100s,从0开始计时
				}
		}	
}	

void main() // 主函数
{
	unsigned char i;
	TMOD = 0x01; // 定时器工作在方式1
	ET0 = 1;
	EA = 1; // 开中断
	x24c08_init();// 初始化24c08
	sec = x24c08_read(2); // 读出保存的数据赋予sec
	TH0 = (65536-50000)/256;
	TL0 = (65534-50000)%256 ; // 使定时器0.05s中断一次
	TR0 = 1;// 启动定时器
	while(1)
	{
		i = 10;
		while(i--)
		{ 
			display(sec/10,sec%10);
			}
			
		if(write = 1) // 判断计时器是否计时1s
			{
				write = 0; // 清0
				x24c08_write(2,sec); // 在24c02的地址中写入数据sec
				}
		}
	}
	

经试验,得到实验结果。

猜你喜欢

转载自blog.csdn.net/qq_31389903/article/details/51726068
今日推荐