小白笔记——IIC详解

关于stm32上IIC

  • 定义
  • 硬件连接
  • IIC的时序
  • 详细代码

定义

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详解_第1张图片

IIC的时序

IIC设备的一般流程:闲置——开始信号——发送地址/应答——发送数据/应答——停止信号
开始信号:当时钟线(SCL)为高时,数据线(SDA)由高到低进行跳变
停止信号:当时钟线(SCL)为高时,数据线(SDA)由低到高进行跳变
小白笔记——IIC详解_第2张图片
发送数据:数据是在时钟线(SCL)为高时进行读取的
小白笔记——IIC详解_第3张图片
应答:接收数据的IC在接收完八位数据后会发送一个特定低电平脉冲的信号,告诉发送方我已经接收完了
接收地址:IIC接收地址与接收数据一样,只是在主机发送完开始信号后发送的第一个八位便是地址,其中高七位是从机的地址位,所以从机最多可以有128个,最低位为0代表主设备将向从设备发送数据,为1代表主设备接收从设备数据。

详细代码

众所周知stm32的IIC接口是有一些问题的,所以我们使用GPIO模拟IIC进行通信,这次模拟通信的从设备是AT24C02,一个EEPROM存储器,有一点需要注意的是,主设备与从设备的时钟都是以主设备的时钟线为准的,但不能超过从设备的最大传输速率,从设备的最大传输速率一般都会在芯片手册上进行标注,同样以AT24C02为例,如图:

小白笔记——IIC详解_第4张图片
芯片手册上明确说明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时序如下图:
小白笔记——IIC详解_第5张图片
开始信号:当时钟线(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。
小白笔记——IIC详解_第6张图片
停止信号:当时钟线(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;
}	

小白的一点个人想法,若有不对还请指出。

你可能感兴趣的:(stm32,嵌入式,单片机,串口通信)