STM32F1之I2C通信·软件I2C代码编写

目录

1.  软件I2C代码编写 

1.1  I2C起始

方法一

方法二

方法三

方法四

1.2  I2C终止

1.3  发送一个字节 

1.4  接收一个字节

1.5  发送应答

1.6  接收应答


1.  软件I2C代码编写 

        由于软件I2C不受引脚限制,随便找两个普通的GPIO口就可以使用,首先我们随机找两个引脚对其进行初始化:

void MyI2C_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	//开启GPIOB的时钟
	
	/*GPIO初始化*/
	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);					//将PB10和PB11引脚初始化为开漏输出
	
	/*设置默认电平*/
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);			//设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

        然后根据I2C通讯的时序进行配置软件I2C,了解I2C通讯:

STM32F1之I2C通信-CSDN博客

1.1  I2C起始

        首先配置起始和终止条件,根据I2C通讯的时基单元我们可以了解到,起始条件下,SCL高电平期间,SDA从高电平切换到低电平;终止条件下,SCL高电平期间,SDA从低电平切换到高电平。

方法一

        我们可以通过GPIO_ResetBits()和GPIO_SetBits()的方式来控制引脚的电平,假如我们要将,PB10配置成SCL引脚,PB11配置成SDA引脚,则我们可以编写代码:

void MyI2C_Start(void)
{
	GPIO_SetBits(GPIOB, GPIO_Pin_11);			//释放SDA,确保SDA为高电平
	GPIO_SetBits(GPIOB, GPIO_Pin_10);			//释放SCL,确保SCL为高电平
	GPIO_ResetBits(GPIOB, GPIO_Pin_11);			//在SCL高电平期间,拉低SDA,产生起始信号
	GPIO_ResetBits(GPIOB, GPIO_Pin_10);			//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
方法二

        或者我们可以采用宏定义的方法,先对PB10和PB11的高低电平状态进行宏定义:

#define SDA_H()	GPIO_SetBits(GPIOB, GPIO_Pin_11);			
#define SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_10);			
#define SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_11);			
#define SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_10);			

        然后使用定义:

void MyI2C_Start(void)
{
	SDA_H();			//释放SDA,确保SDA为高电平
	SCL_H();			//释放SCL,确保SCL为高电平
	SDA_L();			//在SCL高电平期间,拉低SDA,产生起始信号
	SCL_L();			//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
		
方法三

        另一种有参宏定义:

#define	MyI2C_W_SDA(x);	 GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)(x));						
#define	MyI2C_W_SCL(x);	 GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)(x));						
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
方法四

        对于方法三的有参宏,要是移植到别的库或者往别的单片机移植不容易修改,并且要是将其换到一些主频很高的单片机中需要进行延时操作,不方便进行修改,我们可以基于以上方法进行函数封装:

I2C写SCL引脚电平:

        其中,BitValue 协议层传入的当前需要写入SCL的电平,范围0~1,此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平:

void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);		//根据BitValue,设置SCL引脚的电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

 I2C写SDA引脚电平:

        其中,BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF,此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平:

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);		//根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
	Delay_us(10);												//延时10us,防止时序频率超过要求
}

        调用以上封装函数:

void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);							//释放SDA,确保SDA为高电平
	MyI2C_W_SCL(1);							//释放SCL,确保SCL为高电平
	MyI2C_W_SDA(0);							//在SCL高电平期间,拉低SDA,产生起始信号
	MyI2C_W_SCL(0);							//起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

1.2  I2C终止

        这里我们可以直接调用方法四中的封装:

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);							//拉低SDA,确保SDA为低电平
	MyI2C_W_SCL(1);							//释放SCL,使SCL呈现高电平
	MyI2C_W_SDA(1);							//在SCL高电平期间,释放SDA,产生终止信号
}

1.3  发送一个字节 

        发送一个字节: SCL低电平期间,主机将数据位依次放到SDA线上(高位先行) ,然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。

        Byte 要发送的一个字节数据,范围:0x00~0xFF,通过与0x80按位与的方式取出最高位数据,由于我们在方法四的封装函数void MyI2C_W_SDA(uint8_t BitValue);其中BitValue的取值为非0即1的特性,因此我们可以直接使用MyI2C_W_SDA(Byte & 0x80);通过与上不同的位得到一个字节数据,代码如下:

void MyI2C_SendByte(uint8_t Byte)
{
		MyI2C_W_SDA(Byte & 0x80);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x40);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x20);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x10);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x08);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x04);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x02);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);	

		MyI2C_W_SDA(Byte & 0x01);
		MyI2C_W_SCL(1);						
		MyI2C_W_SCL(0);						

}

        我们可以通过for循环简化上述操作,通过对0x80右移实现上述操作:

void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)				//循环8次,主机依次发送数据的每一位
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));	//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
		MyI2C_W_SCL(1);						//释放SCL,从机在SCL高电平期间读取SDA
		MyI2C_W_SCL(0);						//拉低SCL,主机开始发送下一位数据
	}
}

1.4  接收一个字节

        SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。

I2C读SDA引脚电平:

        我们需要先创建一个读SDA电平的函数封装:

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);		//读取SDA电平
	Delay_us(10);												//延时10us,防止时序频率超过要求
	return BitValue;											//返回SDA电平
}

        此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1。

uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	for (i = 0; i < 8; i ++)				//循环8次,主机依次接收数据的每一位
	{
		MyI2C_W_SCL(1);						//释放SCL,主机机在SCL高电平期间读取SDA
		if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}	//读取SDA数据,并存储到Byte变量
														//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
		MyI2C_W_SCL(0);						//拉低SCL,从机在SCL低电平期间写入SDA
	}
	return Byte;							//返回接收到的一个字节数据
}

1.5  发送应答

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

void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);					//主机把应答位数据放到SDA线
	MyI2C_W_SCL(1);							//释放SCL,从机在SCL高电平期间,读取应答位
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
}

1.6  接收应答

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

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;							//定义应答位变量
	MyI2C_W_SDA(1);							//接收前,主机先确保释放SDA,避免干扰从机的数据发送
	MyI2C_W_SCL(1);							//释放SCL,主机机在SCL高电平期间读取SDA
	AckBit = MyI2C_R_SDA();					//将应答位存储到变量里
	MyI2C_W_SCL(0);							//拉低SCL,开始下一个时序模块
	return AckBit;							//返回定义应答位变量
}

STM32F1之I2C通信-CSDN博客

STM32F1之RS485通讯协议·MODBUS-RTU超详细解析-CSDN博客

STM32F1之FLASH闪存-CSDN博客

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
STM32F1系列是意法半导体(STMicroelectronics)推出的一款32位ARM Cortex-M3内核的微控制器系列。其中,STM32F1I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在微控制器和其他外设之间传输数据。 I2C总线由两根线组成:SDA(串行数据线)和SCL(串行时钟线)。STM32F1I2C接口支持标准模式和快速模式两种传输速率。标准模式下的最大传输速率为100kbps,快速模式下的最大传输速率为400kbps。I2C总线上可以连接多个从设备,每个从设备都有一个唯一的7位地址。 要使用STM32F1I2C,首先需要配置相关的寄存器。主要包括以下几个方面: 1. I2C时钟配置:设置I2C时钟频率和时钟分频因子。 2. GPIO配置:设置SDA和SCL引脚的工作模式和速度。 3. I2C外设配置:设置I2C的工作模式、传输速率、地址模式等。 4. 中断配置(可选):如果需要使用中断方式来处理I2C事件,还需要配置中断相关的寄存器。 配置完成后,可以通过读写相关寄存器来进行数据传输。常用的操作包括: 1. 发送数据:将要发送的数据写入到I2C数据寄存器中。 2. 接收数据:读取I2C数据寄存器中的数据。 3. 发送起始位和停止位:通过控制相关寄存器,发送起始位和停止位来启动和结束数据传输。 4. 等待传输完成:通过查询相关的状态位,等待数据传输完成。 需要注意的是,在使用I2C进行数据传输时,应该根据具体的从设备协议来编写相应的通信代码。不同的从设备可能有不同的寄存器映射和数据格式。 以上是对STM32F1I2C的简单讲解,如果你有更具体的问题或需求,可以继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

时光の尘

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

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

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

打赏作者

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

抵扣说明:

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

余额充值