I2C-集成电路总线

一、概述

  1. IIC即Inter-Intergrated Circuit(集成电路总线),I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。
  2. 时钟线必须由主机(通常为微控制器)控制,主机产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。I2C总线上有主机(MCU)和从机(片外外设,如AT24C02)之分,可以有多个主机和多个从机。从机永远不会主动给主机发送数据。
  3. 每个接到I2C总线的设备都有一个唯一的地址,以便于主机寻访。
  4. IIC的通信速度分为三种:标准模式传输速率:100KHZ,快速模式:400KHZ,高速模式:3.4MHZ

二、信号概念

I2C通信,存在几种信号

  1. 起始信号(条件):通知从机做好通信的准备。
  2. 应答信号:有应答和无应答。有应答是低电平,无应答是高电平。
  3. 停止信号(条件):告诉从机通信已经结束。

三、关键时序图

数据位的传输:主机在时钟的上升沿时接收数据,下降沿时发送数据,一次数据的传输就是8位,高位先发

数据的有效性:
在这里插入图片描述
由图可知:时钟线为高电平时,数据线上的数据才稳定;时钟线为低电平时,数据线上的数据改变

起始和停止条件
在这里插入图片描述
1. 起始条件:时钟线处于高电平期间,数据线产生了一个下降沿
2. 停止条件:时钟线处于高电平期间,数据线产生了一个上升沿

应答信号:判断刚刚接收到的数据有没有正常响应,应答位一定是接收方发出来的,发送和接收的时序和普通的一个位传输没有区别。发生在第9个时钟周期。
在这里插入图片描述
3. 主机发送一个应答位:在第9个时钟周期,时钟低电平时,数据引脚输出低电平时是应答,高电平时是无应答。

  1. 主机接收一个应答位:在第9个时钟周期,时钟从低电平到高电平变化时,读取SDA引脚的电平,数据引脚位低电平是时应答,高电平时是无应答。

一个字节的发送和接收
在这里插入图片描述
发送一个字节:和发送一个应答位差不多,for循环8次即可,先发送高位``。
接收一个字节:和接收一个应答位差不多
时钟和数据转换:SDA引脚通常使用外部设备拉高。SDA引脚上的数据可能仅在SCL低时段发生变化。SCL高期间的数据变化将指示定义的起始或停止条件。

I2C的通信步骤

  1. 起始条件
  2. 发送8位的数据(发送器件地址+写/读方向)
  3. 等待应答位
  4. 继续发送相应的字节数据
  5. 停止条件

如何区分是和哪个从机进行通信?
通过IIC器件自带的地址:一般是7bit的硬件地址,加上1bit的读写位
如果是主机发送:器件地址+写方向(0)
如果是主机接收:器件地址+读方向(1)

四、代码实现

主要看时序图编写

模拟I2C初始化,
使用的引脚:PB8–SCL , PB9–SDA
主机通过时钟引脚输出高低电平,故配置为推挽输出
主机有时通过数据引脚输出数据,有时需要从数据引脚中读取数据,故先配置为推挽输出

/*
***************************
函数功能:I2C初始化
函数参数:None
函数返回值:None
函数说明:SCL_PIN--SCL 推挽输出
		SDA_PIN--SDA 推挽输出
***************************
*/
void i2c_init(void)
{
	 GPIO_InitTypeDef  	 GPIO_InitStructure;
	 
	//PB硬件时钟使能
	 RCC_AHB1PeriphClockCmd(GPIO_CLK, ENABLE);
	
	//配置PB8 pB9输出模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin = SCL_PIN|SDA_PIN;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	
	GPIO_Init(GPIO_PORT, &GPIO_InitStructure);
	
	//看时序图,PB8 PB9会有一个初始电平
	SCL_W = 1;
	SDA_W = 1;
}

数据线输入/输出配置函数
当需要读取SDA引脚上的电平时,设置为输入模式:GPIO_Mode_IN
当需要从SDA引脚上输出数据时,设置为输出模式:GPIO_Mode_OUT

//配置数据线的输入/输出
void sda_pin_mode(GPIOMode_TypeDef pin_mode)
{
	GPIO_InitTypeDef  	 GPIO_InitStructure;
	//配置 pB9输出模式
	GPIO_InitStructure.GPIO_Mode = pin_mode;    //输出或者输入模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin = SDA_PIN;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_Init(GPIO_PORT, &GPIO_InitStructure);
}
/*
**********************************************************************
函数功能:iic起始条件函数
函数形参:u8 delay
函数返回值:None
备注:时钟线高电平期间,数据线产生下降沿
**********************************************************************
*/
void i2c_start(u8 delay)
{
	//保证SDA引脚为输出模式
	sda_pin_mode(GPIO_Mode_OUT);
	
	SCL_W = 1;
	SDA_W = 1;
	delay_us(delay); //起始条件的建立时间
	
	SDA_W = 0;
	delay_us(delay);//起始条件的维持时间
	
	SCL_W = 0;
	delay_us(delay);  //钳住I2C总线,准备发送或接收数据 
}

起始条件和结束条件的时序图
在这里插入图片描述

/*
**********************************************************************
函数功能:iic停止条件函数
函数形参:u8 delay
函数返回值:None
备注:时钟线高电平期间,数据线产生上升沿
**********************************************************************
*/
void i2c_stop(u8 delay)
{
	//保证SDA引脚为输出模式
	sda_pin_mode(GPIO_Mode_OUT);
	
	SCL_W = 1;
	SDA_W = 0;
	delay_us(delay); 
	
	SDA_W = 1;   //发送I2C总线结束信号
	delay_us(delay);
}
/*
**********************************************************************
函数功能:主机发送应答信号
函数形参:u8 ack,u8 delay
函数返回值:None
**********************************************************************
*/
void i2c_send_ack(u8 ack,u8 delay)
{
	//保证SDA引脚为输出模式
	sda_pin_mode(GPIO_Mode_OUT);	
	SCL_W=0;

	//先更改SDA引脚的电平
	if(ack)
	{
		SDA_W = 1;  //无应答
	}
	else
	{
		SDA_W = 0;  //应答
	}
	delay_us(delay);//发送数据需要的时间
	
	SCL_W=1;
	delay_us(delay);//接收数据需要的时间
	
	SCL_W=0;
	delay_us(delay);//保证一个完整的周期
}

发送应答位和接收应答位的时序图
在这里插入图片描述

/*
**********************************************************************
函数功能:主机接收应答信号
函数形参:u8 delay
函数返回值:接收到的应答信号
**********************************************************************
*/
u8 i2c_recv_ack(u8 delay)
{
	u8	ack = 0;
	//保证SDA引脚为输入模式
	sda_pin_mode(GPIO_Mode_IN);
	
	SCL_W = 1;     //时钟线为高电平时,才能读取SDA引脚稳定的电平
	delay_us(delay); 
	//读取SDA引脚的电平
	if(SDA_R)   ack = 1;  //无应答信号
	else 		ack = 0;  //有应答信号
	
	SCL_W = 0;   //总线在忙的状态
	delay_us(delay);
	
	return ack;
}

发送一个字节:和发送一个应答位差不多,for循环8次即可,先发送高位``。

/*
**********************************************************************
函数功能:主机发送一个字节数据,再接收一个应答信号函数
函数形参:待发送的数据,u8 delay
函数返回值:接收到的应答信号
备注:0:应答   1:非应答     8bit   先高后低
**********************************************************************
*/
u8 i2c_send_byte(u8 byte,u8 delay)
{
	u8 ack;
	int i = 0;
	//保证SDA引脚为输出模式
	sda_pin_mode(GPIO_Mode_OUT);
	
	//最高位优先传输,1字节=8位
	for(i=7;i>=0;i--)
	{
		SCL_W = 0;
		//先更改SDA引脚的电平
		if(byte & (1<<i))  //判断bit位为1
		{
			SDA_W = 1;
		}
		else
		{
			SDA_W = 0; 
		}
		delay_us(5);
		
		SCL_W = 1;
		delay_us(5); //保持高电平
		
		SCL_W = 0;  //时钟线为低电平时,才能变更数据,传输下一位数据
		delay_us(5); 		
		
	}
	//SCL_W = 0;
	ack = i2c_recv_ack(delay);
	return ack;	
}
/*
**********************************************************************
函数功能:主机接收一个字节数据,再发送一个应答信号函数
函数形参:待发送的应答,u8 delay
函数返回值:接收到的数据
备注:0:应答   1:非应答     8bit   先高后低
**********************************************************************
*/
u8 i2c_recv_byte(u8 ack,u8 delay)
{
	u8 d = 0;
	int i;
	
	//保证SDA引脚为输入模式
	sda_pin_mode(GPIO_Mode_IN);	
	
	for(i=7; i>=0; i--)
	{
		SCL_W = 1;
		delay_us(delay);
		
		if(SDA_R)
			d|=1<<i;	
		
		SCL_W = 0;	
		delay_us(delay);	
	}
//	SCL_W = 1;//ERR
	i2c_send_ack(ack,delay);
	
	return d;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

⁽⁽ଘ晴空万里ଓ⁾⁾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值