I2C的6个时序

一、I2C 初始化函数

在这里插入图片描述

关于 软件写I2C,主机和从机为什么配置成开漏输出和外置弱上拉电阻模式
·主机和从机的输入,无所谓,怎么都能输入。
·主机和从机的输出,如果设置为推挽输出模式,输出高电平时为强上拉,输出低电平时强下拉。但如果没有配置好主机和从机的发送时序,二者同时发送,一个发送高电平一个发送低电平会造成短路。另外从机有多个,也会冲突。 所以重点关注输出!(输出模式下也是可以输入的)

·设备的SCL和SDA均采用开漏输出模式,且各自外接上拉电阻。
避免了引脚模式的频繁切换,同时兼具输入和输出的功能。
输出低电平时下管导通是强下拉,输出高电平时由于上边没有接。
所有设备只能输出低电平而不能输出高电平,为了避免高电平造成的引脚浮空,就接上拉电阻拉到高电平(因为是电阻拉高所以是弱上拉)
在这里插入图片描述
规定所有的人不准向上拉杆子,只能向下拉杆子或者放手。
若想输出低电平,就向下拉杆子,弹簧拉不过你。
若想输出高电平,就放手,弹簧弹回高电平。
第一,完全杜绝了电路短路,无论怎么拉杆子或者放手,杆子都不会出现被同时强拉或强推的状态
第二,避免了引脚模式的频繁切换,开漏加弱上拉模式,同时兼具输入输出功能。 (开漏模式下输出高电平就相当于断开引脚,所以在输入之前可以直接输出高电平,不需要再切换成输出模式。)

//初始化 IIC 协议
void MyI2C_Init(void){
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  //开漏输出,开楼输出也可以接受输入。
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);  //开始默认都置高电平
}

二、I2C 起始条件

在这里插入图片描述
开始时要释放SCL和SDA,即写1。然后再把它们拉低。
先释放SDA,再释放SCL
原因:如果起始条件之前,二者均为高电平,那先释放哪一个都无所谓;
但是为了兼容重复起始条件sr,sr最开始的SCL是低电平,SDA电平不敢确定,为保险起见,趁SCL低电平时拉高SDA(否则SCL高电平期间读数据,读到的SDA有可能是0)
在这里插入图片描述
在这里插入图片描述
myi2c.c

//控制 SCL 时钟线,输出高电平还是低电平。
void MyI2C_W_SCL(uint8_t BitValue){
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);//BitValue非0即1
	Delay_us(10);
}
//控制 SDA 数据线,输出高电平还是低电平。
void MyI2C_W_SDA(uint8_t BitValue){
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
}
//读取 SDA 数据线,输入高电平还是低电平。
uint8_t MyI2C_R_SDA(void){
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}
// 开始信号
void MyI2C_Start(void){
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

三、I2C 停止条件

在这里插入图片描述
如果SCL和SDA都是低电平,先释放SCL再释放SDA就行。
但是这个时序单元开始时,SDA不一定是低电平,所以要 MyI2C_W_SDA(0)

//停止信号
void MyI2C_Stop(void){
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

三、I2C 发送一个字节

在这里插入图片描述在这里插入图片描述

每个时序单元的SCL都是以低电平开始低电平结束,这样这些单元拼接起来时SCL才能续得上
【SCL低电平期间,主机将数据按高位先行依次放到SDA上。SCL低电平期间,SDA可以变化】
如果发送0,就拉低SDA;如果想发送1,就释放SDA。
【然后主机释放SCL(即高电平),从机读取数据。SCL高电平期间,SDA不能变化】

void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++){
		MyI2C_W_SDA(Byte & (0x80 >> i));
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}
  1. MyI2C_W_SDA(Byte & 0x80 )即趁SCL是低电平,先把Byte的最高位放在SDA线上,写1还是0,取决于Byte的最高位。**“按位与”**就可以取出来,Byte =XXXXXXXX,0x80=1000000。
    0x80 >> i,右移i位。
  2. 释放SCL,即SCL变为高电平
  3. 拉低SCL,以备下次发送。

四、I2C 接收一个字节

在这里插入图片描述
为防止主机干扰,因为SDA为0的话,线与特性默认为0,从机此时无论发1还是0都为0,故主机这里将SDA置1放手。
从机想发1,就释放SDA;从机想发0,就拉低SDA。

//接受一个 Byte数据
uint8_t MyI2C_ReceiveByte(void){
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);  
	for (i = 0; i < 8; i ++){
		MyI2C_W_SCL(1);//SCL高电平期间,主机就能读取数据
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
		MyI2C_W_SCL(0);
	}
	return Byte;
}

if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
如果读到的为1,就把Byte第一位置为1。
读取一位之后,把SCL拉低,然后从机就可以发送下一位数据了。

五和六、I2C 发送应答和接收应答

其实是发送字节和接受字节的简化版,
发送字节发8位,而发送应答是发一位;
接收字节发8位,而发送应答是发一位;
在这里插入图片描述

//发送 ACK 应答信号
void MyI2C_SendAck(uint8_t AckBit){
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

相当于去掉了发送一个字节中的for循环,然后把发送的一位改成AckBit。

//接收 ACK 应答信号
uint8_t MyI2C_ReceiveAck(void){
	uint8_t AckBit;//不用赋值
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();//
	MyI2C_W_SCL(0);
	return AckBit;
}
  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值