I2C总线是一种同步、双向、半双工的串行总线,通信双方只需要两条线就可以实现通信,非常方便。
I2C有硬件I2C和软件I2C之分,简单介绍如下:
在模拟I2C之前,得了解I2C总线的通讯过程,不然只会无从下手。
I2C总线的传输一个字节的过程概括如下:
在总线空闲(即SDA线和SCL线处于高电平,在I2C中要通过上拉电阻才能实现高电平)时,发送端产生起始信号,表示本次传输的开始,之后发送一个字节的数据(该数据总是高位在前的),然后等待接收端的应答信号或非应答信号,应答信号和非应答信号分别表示接受端接受到数据和没接收到数据,最后产生停止信号,表示本次传输的结束。
有几个要注意的点:
接下来,来看看各信号的定义:
起始信号和停止信号
起始信号:在SCL线高电平时,SDA线从高电平向低电平切换
停止信号:在SCL线高电平时,SDA线从低电平向高电平切换
应答信号和非应答信号
在传输一个byte数据后,在第9个bit对的脉冲高电平期间,SDA线若保持高电平,即为应答信号;SDA线若保持低电平,即为非应答信号。
那么下来就是如何通过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)
为了方便对照,下面把时序图跟代码一起贴出来。
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();
}
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();
}
//产生应答信号
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基本上完成了。如果想进行通信的话,得根据通信双方的芯片手册的要求来写相对应的代码即可。具体情况具体分析,这里就不一一细说了。
我清楚自己表达能力不强,有什么表达不够清楚的地方,请多多包涵。
希望你看完这篇小小的文章后能有所收获。