IIC为Inter-Integrated Circuit,其实应该为IICbus的简称,所以中文名为集成电路总线,是一种串行通信总线,使用的是多主从架构,一般用于低速通信,是由飞利浦公司在1980年提出,I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。IIC可以在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上.
SCL:serial clock 串行时钟
SDA:serial data 串行数据
一般情况下IIC的SCL和SDA都会加一个上拉电阻,电阻的值由许多因素决定的,这里之后有空我再研究更新。那为什么要加这么一个上拉电阻呢?
因为IIC内部使用漏级开路进行输出,漏级开路便如下图所示,IC内部只能输出低电压,因此必须要一个上拉电阻将电压拉高,使输出有高电压。
IIC设备的一般流程:闲置——开始信号——发送地址/应答——发送数据/应答——停止信号
开始信号:当时钟线(SCL)为高时,数据线(SDA)由高到低进行跳变
停止信号:当时钟线(SCL)为高时,数据线(SDA)由低到高进行跳变
发送数据:数据是在时钟线(SCL)为高时进行读取的
应答:接收数据的IC在接收完八位数据后会发送一个特定低电平脉冲的信号,告诉发送方我已经接收完了
接收地址:IIC接收地址与接收数据一样,只是在主机发送完开始信号后发送的第一个八位便是地址,其中高七位是从机的地址位,所以从机最多可以有128个,最低位为0代表主设备将向从设备发送数据,为1代表主设备接收从设备数据。
众所周知stm32的IIC接口是有一些问题的,所以我们使用GPIO模拟IIC进行通信,这次模拟通信的从设备是AT24C02,一个EEPROM存储器,有一点需要注意的是,主设备与从设备的时钟都是以主设备的时钟线为准的,但不能超过从设备的最大传输速率,从设备的最大传输速率一般都会在芯片手册上进行标注,同样以AT24C02为例,如图:
芯片手册上明确说明SCL最大传输速率为400khz,换算成时间的话即表示一个时钟周期最小为2.5μs,并且芯片手册规定了低电平时间和高电平时间最短分别为1.2与0.6μs。
首先就是IIC的初始化:
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //GPIO使能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;//初始化GPIO6&7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //设置为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7输出高
}
当需要模拟的时钟线初始化后,就需要写IIC的开始,结束,应答,等待应答,非应答、发送、接收等信号了,这里是用GPIO模拟IIC总线协议的,但从器件是用硬件控制信号的,所以写时序的时候要根据从器件所给出的IIC时序进行编写,这样的话编写代码也就比较简单了。AT24C02的IIC时序如下图:
开始信号:当时钟线(SCL)为高时,数据线(SDA)由高到低进行跳变
void IIC_Start(void)
{
SDA_OUT(); //SDA线设置为输出
IIC_SDA=1; //SDA设置为高
IIC_SCL=1;//SCL为高
delay_us(4);
IIC_SDA=0;//START:数据线(SDA)由高到低进行跳变
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
这里的SDA_OUT函数就很有意思了,实际上调用的是.h文件里的一个宏定义,但却使用寄存器版本的使用方法,代码如下:
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
这里就以SDA_OUT为例,因为SDA线使用的是pin7,所以将第八个字节进行一个与操作,全部清零,然后进行一个或操作,写入3,由下表可知,3表示的就是通用推挽输出模式,最大速度50MHz。
停止信号:当时钟线(SCL)为高时,数据线(SDA)由低到高进行跳变
void IIC_Stop(void)
{
SDA_OUT();
IIC_SCL=0;
IIC_SDA=0;
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;
delay_us(4);
}
发送数据:数据是在时钟线(SCL)为高时进行读取的,先将数据线设置为输出模式,再将时钟线拉低进行发送数据,发送数据为一位一位进行发送的,当要发送的位为1时数据线变为高电平。然后时钟线变高,使从设备进行读取,再次变低进入下一个循环。
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
接收数据:同理,在时钟高电平时读取数据,读取到的数据每位左移一位,当数据线为高时,接收数据加一,即最低位变为1,进行下一个循环,根据传入函数是否有ack来进行应答即不应答。
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();
else
IIC_Ack();
return receive;
}
应答:接收数据的IC在接收完八位数据后会发送一个特定低电平脉冲的信号,告诉发送方我已经接收完了
应答包括等待应答与应答,首先来看等待应答:等待应答就是先把数据线设置为输入,然后将数据线与时钟线都拉高,等待其他设备将总线的电平拉低,当超过250us时,判定为无应答。
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN();
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;
return 0;
}
产生应答信号:时钟线拉低,将数据线设置为输出,等待后将时钟线拉高,表示读取数据线状态,之后再将时钟线拉低。
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
不产生应答信号:与应答信号类似,但数据线不会拉低。
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
小白的一点个人想法,若有不对还请指出。