STM32——通过GPIO来模拟I2C

I2C总线是一种同步、双向、半双工的串行总线,通信双方只需要两条线就可以实现通信,非常方便。

1. 硬件I2C和软件I2C

I2C有硬件I2C和软件I2C之分,简单介绍如下:

  • 硬件I2C:即对应芯片上的I2C外设,速度比软件I2C快,并且可以使用DMA,但有些单片机的I2C
    外设不太稳定,就例如STM32上的I2C就有bug。
  • 软件I2C:用程序来控制IO口输出高低电平,模拟I2C协议的时序,从而实现I2C协议。相对于硬件I2C,软件I2C一般较稳定,但速度较慢,不过软件I2C可以模拟在任何管脚上,不像硬件I2C只有固定管脚。

2. I2C协议

在模拟I2C之前,得了解I2C总线的通讯过程,不然只会无从下手。

I2C总线的传输一个字节的过程概括如下:
在总线空闲(即SDA线和SCL线处于高电平,在I2C中要通过上拉电阻才能实现高电平)时,发送端产生起始信号,表示本次传输的开始,之后发送一个字节的数据(该数据总是高位在前的),然后等待接收端的应答信号或非应答信号,应答信号和非应答信号分别表示接受端接受到数据和没接收到数据,最后产生停止信号,表示本次传输的结束。

有几个要注意的点:

  1. 除了起始信号和停止信号,其他信号的电平状态切换只能在SCL线处于低电平时进行;而且在SCL线处于高电平时,SDA线必须保持稳定的电平状态。
  2. I2C协议支持的通讯速度在100kHz到400kHz之间,如果是模拟I2C,要保证所写的延时函数延时的时间尽量在频率对应的范围之内。

接下来,来看看各信号的定义:

  • 起始信号和停止信号
    STM32——通过GPIO来模拟I2C_第1张图片
    起始信号:在SCL线高电平时,SDA线从高电平向低电平切换
    停止信号:在SCL线高电平时,SDA线从低电平向高电平切换

  • 应答信号和非应答信号
    STM32——通过GPIO来模拟I2C_第2张图片
    在传输一个byte数据后,在第9个bit对的脉冲高电平期间,SDA线若保持高电平,即为应答信号;SDA线若保持低电平,即为非应答信号。

3. 通过IO口模拟I2C

那么下来就是如何通过IO口来模拟I2C了,话就不多说,直接开冲!

既然模拟I2C是通过IO来模拟的,那首先肯定是要对相关GPIO进行初始化的,代码如下:

void I2C_GPIO_Init()
{
	GPIO_InitTypeDef I2C_GPIO_InitStruct;

	//打开数据信号线SDA和时钟信号线SCL的时钟
	RCC_APB2PeriphClockCmd(I2C_SDA_CLK|I2C_SCL_CLK, ENABLE);
	
	//SDA线对应IO口的配置
	I2C_GPIO_InitStruct.GPIO_Pin = I2C_SDA_PIN;
	I2C_GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	I2C_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_Init(I2C_SDA_PORT, &I2C_GPIO_InitStruct);
	
	//SCL线对应IO口的配置
	I2C_GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN;
	I2C_GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	I2C_GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_Init(I2C_SCL_PORT, &I2C_GPIO_InitStruct);
}

//下面是对应头文件的一下宏定义
//修改下面宏定义的参数可以更改IO口
#include "sys.h"

//SDA线
#define I2C_SDA_PORT	GPIOB
#define I2C_SDA_PIN		GPIO_Pin_9
#define I2C_SDA_CLK		RCC_APB2Periph_GPIOB

//SCL线
#define I2C_SCL_PORT	GPIOB
#define I2C_SCL_PIN		GPIO_Pin_8
#define I2C_SCL_CLK		RCC_APB2Periph_GPIOB

//SDA线 and SCL线的输入输出
#define I2C_SDA_IN	PBin(9)
#define I2C_SDA_OUT PBout(9)
#define I2C_SCL_OUT PBout(8)

为了方便对照,下面把时序图跟代码一起贴出来。

  • 起始信号
    STM32——通过GPIO来模拟I2C_第3张图片
void I2C_Start(void)
{
	I2C_SCL_OUT = 1;
	I2C_SDA_OUT = 1;
	I2C_Delay();
	I2C_SDA_OUT = 0;
	I2C_Delay();
	I2C_SCL_OUT = 0;
	I2C_Delay();	
}
  • 停止信号
    STM32——通过GPIO来模拟I2C_第4张图片
void I2C_Stop(void)
{
	I2C_SCL_OUT = 0;
	I2C_SDA_OUT = 0;
	I2C_Delay();
	I2C_SCL_OUT = 1;
	I2C_Delay();
	I2C_SDA_OUT = 1;
	I2C_Delay();
}
  • 产生应答信号和产非应答信号
    STM32——通过GPIO来模拟I2C_第5张图片
//产生应答信号
void I2C_Ack(void)
{
	I2C_SCL_OUT = 0;
	I2C_Delay();
	I2C_SDA_OUT = 0;
	I2C_Delay();
	I2C_SCL_OUT = 1;
	I2C_Delay();
	I2C_SCL_OUT = 0;
	I2C_Delay();
}

//产生非应答信号
void I2C_Nack(void)
{
	I2C_SCL_OUT = 0;
	I2C_Delay();
	I2C_SDA_OUT = 1;
	I2C_Delay();
	I2C_SCL_OUT = 1;
	I2C_Delay();
	I2C_SCL_OUT = 0;
	I2C_Delay();
}
  • 发送一个字节数据
void I2C_Send_Byte(u8 send_byte)
{
	u8 i;
	
	for(i=0;i<8;i++)
	{
		I2C_SDA_OUT = (send_byte&0x80)>>7;
		I2C_Delay();
		I2C_SCL_OUT = 1;
		I2C_Delay();
		I2C_SCL_OUT = 0;
		if(i==7)
		{
			I2C_SDA_OUT = 1;	//发送最后一位之后,释放SDA总线,方便接收来自接收端的应答信号
		}
		I2C_Delay();
		send_byte <<= 1;
	}
}
  • 接收一个字节数据
u8 I2C_Read_Byte()
{
	u8 i;
	u8 receive=0;

	for(i=0;i<8;i++)
	{
		receive <<= 1;
		I2C_SCL_OUT = 1;
		I2C_Delay();
		if(I2C_SDA_IN==1)
		{
			receive++;
		}
		I2C_SCL_OUT = 0;
		I2C_Delay();
	}
	return receive;
}
  • 等待应答信号
u8 I2C_Wait_Ack(void)
{
	u8 ack_flag = 0;
	
	I2C_SDA_OUT = 1;	//释放SDA总线
	I2C_Delay();
	I2C_SCL_OUT = 1;
	I2C_Delay();
	if(I2C_SDA_IN == 1)
	{	
		ack_flag = 1;	//非应答
	}
	else
	{
		ack_flag = 0;	//应答
	}
	I2C_SCL_OUT = 0;
	I2C_Delay();
	return ack_flag;
}

至此,通过IO口来模拟I2C基本上完成了。如果想进行通信的话,得根据通信双方的芯片手册的要求来写相对应的代码即可。具体情况具体分析,这里就不一一细说了。

我清楚自己表达能力不强,有什么表达不够清楚的地方,请多多包涵。
希望你看完这篇小小的文章后能有所收获。

你可能感兴趣的:(stm32)