一本正经的学习stm32 IIC

stm32 IIC
本文是在编译软件上编写的,文章格式稍有不是,请见谅。
一、IIC的简介
IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围
设备。它是半双工通信方式(同一时刻只能是接或者是收)。
IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间
和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
IIC总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主
控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。
一般情况下,数据线SDA和时钟线SCL都是处于上拉电阻状态。因为:在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
具有三种传输模式:标准模式传输速率为100kbit/s ,快速模式为400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多I2C
设备尚不支持高速模式。
连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。

二、I2C的协议层
IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号。这些信号中,起始信号是必需的结束信号和应答信号,都可以不要。还有其空闲状态、数据的有效性、数据传输。
1.空闲状态
当IIC总线的数据线SDA和时钟线SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2.起始信号与停止信号
起始信号:当时钟线SCL为高期间,数据线SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号;
停止信号:当时钟线SCL为高期间,数据线SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。
3.应答信号
发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
对于反馈有效应答位ACK的要求是:接收器在第9个时钟脉冲之前的低电平期间将数据线SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放数据线SDA,以便主控接收器发送一个停止信号P。
I2C的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。
传输时主机产生时钟,在第9个时钟时,数据发送端会释放SDA的控制权,由数据接收端控制SDA,若SDA为高电平,表
示非应答信号(NACK),低电平表示应答信号(ACK)。
4.数据有效性
IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定;只有在时钟线上的信号为低电平期间,
数据线上的高电平或低电平状态才允许变化。即:数据在时钟线SCL的上升沿到来之前就需准备好。并在在下降沿到来之
前必须稳定。
SCL为高电平的时候SDA表示的数据有效,即此时的SDA为高电平时表示数据“1”,为低电平时表示数据“0”。
当SCL为低电平时,SDA的数据无效,一般在这个时候SDA进行电平切换,为下一次表示数据做好准备。
5.数据的到达
在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行
传送每一位数据。数据位的传输是边沿触发。
6.IIC总线的数据传送
IIC总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。

三、可执行代码和驱动

现拟采用PB6、PB7来模拟IIC时序,使用IIC读写EEPROM,型号是AT24C02.其中:PB6为时钟线,PB7为数据线。
//要写入到24c02的字符串数组
const u8 TEXT_Buffer[]={"Elite STM32 IIC TEST"};
#define SIZE sizeof(TEXT_Buffer)	

//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号
void SDA_OUT(void);					//SDA线输出
void SDA_IN(void);					//SDA线输入

//初始化IIC
void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOB时钟   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_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 输出高,空闲状态
}
//SDA线输出
void SDA_OUT(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
}
//SDA线输入
void SDA_IN(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
}

//产生IIC起始信号
void IIC_Start(void)
{
	SDA_OUT();     //sda线输出
	GPIO_SetBits(GPIOB,GPIO_Pin_6);	  	  
	GPIO_SetBits(GPIOB,GPIO_Pin_7);	
	delay_us(4);
	GPIO_ResetBits(GPIOB,GPIO_Pin_7);    //START:when CLK is high,DATA change form high to low 
	delay_us(4);
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);    //钳住I2C总线,准备发送或接收数据 
}	  
//产生IIC停止信号
void IIC_Stop(void)
{
	SDA_OUT();    //sda线输出
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);
	GPIO_ResetBits(GPIOB,GPIO_Pin_7);    //STOP:when CLK is high DATA change form low to high
	GPIO_SetBits(GPIOB,GPIO_Pin_6);
	delay_us(4);
	GPIO_SetBits(GPIOB,GPIO_Pin_7);	    //发送I2C总线结束信号
	delay_us(4);							   	
}
//这里用了24XX的最后一个地址(255)来存储标志字
//如果用其他24C系列,这个地址要修改  返回1:检测失败 返回0:检测成功
u8 AT24CXX_Check(void)
{
	u8 temp;
	temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX			   
	if(temp==0X55)return 0;		   
	else//排除第一次初始化的情况
	{
		AT24CXX_WriteOneByte(255,0X55);
		temp=AT24CXX_ReadOneByte(255);	  
		if(temp==0X55)return 0;
	}
	return 1;											  
}	
//在AT24CXX指定地址读出一个数据 ReadAddr:开始读数的地址  返回值  :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{				  
	u8 temp=0;		  	    																 
	IIC_Start();  
	if(AT24C02>AT24C16)
	{
		IIC_Send_Byte(0XA0);	   //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(ReadAddr>>8);//发送高地址
		IIC_Wait_Ack();		 
	}else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据 	 

	IIC_Wait_Ack(); 
	IIC_Send_Byte(ReadAddr%256);   //发送低地址
	IIC_Wait_Ack();	    
	IIC_Start();  	 	   
	IIC_Send_Byte(0XA1);           //进入接收模式			   
	IIC_Wait_Ack();	 
	temp=IIC_Read_Byte(0);		   
	IIC_Stop();//产生一个停止条件	    
	return temp;
}
//IIC发送一个字节   返回从机有无应答 1,有应答0,无应答			  
void IIC_Send_Byte(u8 txd)//255
{                        
	u8 t;   
	SDA_OUT(); 	    
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);//拉低时钟开始数据传输
	for(t=0;t<8;t++)
	{              
		if((txd&0x80)>0) //0x80  1000 0000
			GPIO_SetBits(GPIOB,GPIO_Pin_7);
		else
			GPIO_ResetBits(GPIOB,GPIO_Pin_7);	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		GPIO_SetBits(GPIOB,GPIO_Pin_6);delay_us(2); 
		GPIO_ResetBits(GPIOB,GPIO_Pin_6);	delay_us(2);
	}	 
} 
//等待应答信号到来   返回值:1,接收应答失败      0,接收应答成功
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN();      //SDA设置为输入  
	GPIO_SetBits(GPIOB,GPIO_Pin_7);delay_us(1);	   
	GPIO_SetBits(GPIOB,GPIO_Pin_6);delay_us(1);	 
	while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7))
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);    //时钟输出0 	   
	return 0;  
} 
//产生ACK应答
void IIC_Ack(void)
{
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);
	SDA_OUT();
	GPIO_ResetBits(GPIOB,GPIO_Pin_7);delay_us(2);
	GPIO_SetBits(GPIOB,GPIO_Pin_6);delay_us(2);
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);
}
//不产生ACK应答		    
void IIC_NAck(void)
{
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);
	SDA_OUT();
	GPIO_SetBits(GPIOB,GPIO_Pin_7);delay_us(2);
	GPIO_SetBits(GPIOB,GPIO_Pin_6);delay_us(2);
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);
}			
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();//SDA设置为输入
	for(i=0;i<8;i++ )
	{
		GPIO_ResetBits(GPIOB,GPIO_Pin_6);delay_us(2);
		GPIO_SetBits(GPIOB,GPIO_Pin_6);
		receive<<=1;
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7))receive++;   
		delay_us(1); 
	}					 
	if (!ack)
		IIC_NAck();//发送nACK
	else
		IIC_Ack(); //发送ACK   
	return receive;
}
//在AT24CXX里面的指定地址开始读出指定个数的数据 ReadAddr :开始读出的地址 对24c02为0~255  pBuffer  :数据数组首地址  NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
	while(NumToRead)
	{
		*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);	
		NumToRead--;
	}
}  
//在AT24CXX里面的指定地址开始写入指定个数的数据   WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer :数据数组首地址    NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
	while(NumToWrite--)
	{
		AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
		WriteAddr++;
		pBuffer++;
	}
}
	int main(void)
{	 
	u8 key;
	u16 i=0;
	u8 datatemp[SIZE];			
	IIC_Init();			//IIC初始化 	
	while(AT24CXX_Check())//检测24c02,可以分检测到和检测不到情况来看
}
while(1)
{
	AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);//这个是写
	AT24CXX_Read(0,datatemp,SIZE);//这个是读
	接下来自己发挥,,,
}

猜你喜欢

转载自blog.csdn.net/weixin_45075787/article/details/113397200
今日推荐