详解STM32F030硬件I2C代码及原理解析

刚接触STM32的时候,第一个学习的就是I2C,当时去网上学习别人写得I2C代码,虽然能用,但是当时并不理解为什么要这么配置,特别希望有人把代码掰碎了讲讲看,今天突然想起来,就把以前写的I2C代码拿出来掰碎了捋捋,希望对新手有些帮助。

先说说STM32的I2C:
ST的M3系列还有M4系列的I2C基本上是一致的,但是到M0系列以后,I2C的设计是重新修改过的,所以用起来会比M3和M4系列的好用很多,前面的文章有详细描述过STM32F103的I2C的硬件缺陷,有兴趣的可以看看,接下来讲讲M0系列的硬件I2C。

从我的使用体验上来说,M0系列的I2C用起来比STM32F103的体验感强太多了,少了很多纷繁复杂的EVENT,通信的流程简化了很多,先看看初始化的代码:

void IIC_Init(void)
{
	I2C_InitTypeDef		I2C_InitStructure;
	GPIO_InitTypeDef	GPIO_InitStructure;
	
	//I2C时钟使能
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;	       //复用模式
	GPIO_InitStructure.GPIO_PuPd =  GPIO_PuPd_UP;// GPIO_PuPd_NOPULL
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	GPIO_PinAFConfig(GPIOC,GPIO_PinSource5,GPIO_AF_0);//SDA
	GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_0);//SCL
		

	
	//IIC配置
	I2C_InitStructure.I2C_Timing=0x00200002;//0x00210507
	I2C_InitStructure.I2C_AnalogFilter=I2C_AnalogFilter_Enable;
	I2C_InitStructure.I2C_DigitalFilter=4;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;		//I2C模式
	I2C_InitStructure.I2C_OwnAddress1 =0; //指定自己的地址为七位地址,和从器件不同即可
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;//启用应答	 
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//七位地址模式

	I2C_Init(I2C1, &I2C_InitStructure);
	/* 使能 I2C */
	I2C_Cmd(I2C1, ENABLE);  	

}

IO配置

IO配置为开漏输出,选择复用功能。

TIMING配置

I2C外设寄存器的配置,第一个参数I2C_Timing很多新手朋友可能不知道该怎么设置,原来的时候ST官方有个execl的小工具,选择I2C频率,上升时间和下降时间后,点击计算就可以得到一个值,复制过来填进去就行,如下图(可以在官网找到,名字:I2C_Timing_Configuration_V1.0.1)

后面我发现一个更简单的办法,直接在cubemx上面选择M0的芯片的I2C,配置好对应的设置后会自动生成配置值:

滤波器配置

timing配置完成后是滤波器的配置,模拟滤波器和数字滤波器的作用:
去除总线上的一些噪声干扰。
开启后对总线的影响:
在总线上没有挂负载时,I2C的速度可以根据时钟源选择,超过I2C协议规定的3.4M/s(如此速度下通信,前提是从机能支持),但是当开启数字滤波和模拟滤波后,速度会下降很多。
其他的配置没什么特别要注意的地方,不详细展开。

读EEPROM流程详述

I2C作为主机读写从机数据,从机是EEPROM,只做了简单的读数据操作,当时为了偷懒没有做翻页读的处理(由于当时有很多种型号的EEPROM,读写函数里做了条件编译来兼容不同系列),
EE_TYPE在头文件中的定义如下:

#define A24C01   0x01
#define A24C02   0x02
#define A24C04   0x03
#define A24C08   0x04
#define A24C16   0x05
#define A24C32   0x06
#define A24C64   0x07
#define A24C128  0x08
#define A24C256  0x09
#define A24C512  0x0a
#define A24C1024 0x0b

#define EE_TYPE A24C02
//IIC读取数据函数
void IIC_Read(uint16_t SalveAddr,uint8_t startaddr,uint8_t *buffer,uint8_t Length)
{
	uint8_t i;
	

	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));//检查总线是否繁忙
	
#if (EE_TYPE > A24C16)
	I2C_TransferHandling(I2C1,SalveAddr,2,I2C_SoftEnd_Mode,I2C_Generate_Start_Write);
	
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//检查TXDR寄存器是否为空
	
	I2C_SendData(I2C1, startaddr>>8);//向总线发送从器件地址	
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//检查发送是否完成
	 
	I2C_SendData(I2C1,startaddr);
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TC)==RESET);
	
	
#else
	I2C_TransferHandling(I2C1,SalveAddr,1,I2C_SoftEnd_Mode,I2C_Generate_Start_Write);
	
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//检查TXDR寄存器是否为空
	
	I2C_SendData(I2C1,startaddr);
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TC)==RESET);
#endif
	I2C_TransferHandling(I2C1,SalveAddr,Length,I2C_AutoEnd_Mode, I2C_Generate_Start_Read);//产生一个读的起始信号
	
	for(i=0;i<Length;i++)
	{
		while(I2C_GetFlagStatus(I2C1,I2C_FLAG_RXNE)==RESET);//等待接收完成
		
		buffer[i]=I2C_ReceiveData(I2C1);	
	}
	
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF)==RESET);//作为主机检测由外设产生的停止信号	
	I2C_ClearFlag(I2C1,I2C_FLAG_STOPF);
}

读数据前先对总线进行BUSY判断,总线电平有变化时,说明处于busy状态,暂时无法操作。
如果总线处于空闲状态,就可以接手总线的控制权,调用I2C_TransferHandling函数向总线上的器件发起寻址,参数中需要确定从器件地址,发送字节数,及达到发送字节数后的结束方式,是否生成起始条件及读写方向。
其中结束方式有三个选项:
软件结束模式:在既定传输字节完成后,TC标志将置位且SCL线低电平将被延展,此时可以执行的操作有两个,一是不放弃总线控制权重新发起start信号,二是通过软件操作手动发送停止位。
自动结束模式:在既定传输字节完成后,硬件自动发送停止位。
重加载模式:当要传输的字节数大于255时,可以选择重加载模式,完成既定传输字节数后,TCR标志将置1,SCL会被延长,向NBYTES中写入一个非0值可以继续传输数据,但是在向NBYTES中写入最后一次传输的字节数之前,必须关闭重加载模式。

由于从器件是EEPROM,在器件地址发送完成后还要发送一次读写地址,此时如果用的从机器件是24c16以下的型号,从机的读写地址就是8位的,如果是24c32以上的,就要发送16位读写地址。读写地址发送完成后判断TC标志表示发送完成,16位地址时,需要在高8位地址发送完成后判断TXIS标志,我理解的TXIS标志置起状态是TXDR寄存器中数据传输到移位寄存器中(此寄存器没有公开到用户手册),并未转化成高低电平到总线时的状态(TC标志未置位),TXIS置起后,可以继续发送低八位地址。

到这里为止,如果通信过程顺利,接下来的操作就是对eeprom具体区域读数,由于上一次调用I2C_TransferHandling选择软件结束模式,且并未发送停止信号,接下来的操作就是调用I2C_TransferHandling,再次发起起始信号,发送从器件地址,将传输方向从写改为读,且确定传输字节数,接下来就是根据既定字节数,由硬件自动发送时钟出去读取数据,根据RXNE标志来接收数据,如果想读取的字节数小于255,可以选择自动结束模式,在传输完成后,自动发出停止信号,也可以选择软件结束(需要在传输完成后配置寄存器发出停止信号),如果接收数据大于255,选择重加载模式,操作方法上文有描述。
接收完成后,需要读取状态寄存器判断主机是否已经发送停止位,此时,一次完整的I2C作为主机读取EEPROM数据的通信过程结束。

写EEPROM流程详述

void IIC_Write(uint16_t SalveAddr,uint8_t startaddr,uint8_t *buffer, uint8_t Length)
{
	uint8_t i;
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));

#if (EE_TYPE > A24C16)	
	I2C_TransferHandling(I2C1,SalveAddr,2,I2C_Reload_Mode,I2C_Generate_Start_Write);
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//检查TXDR寄存器是否为空
	
	I2C_SendData(I2C1, startaddr>>8);
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//该位在Reload=1时NBYTES个数数据发送完成后被置1,向NBYTES写入非0数值时被清0
	I2C_SendData(I2C1,startaddr);
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TCR)==RESET);

#else 
	I2C_TransferHandling(I2C1,SalveAddr,1,I2C_Reload_Mode,I2C_Generate_Start_Write);
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//检查TXDR寄存器是否为空
	
	I2C_SendData(I2C1,startaddr);
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TCR)==RESET);
#endif 
	
	I2C_TransferHandling(I2C1,SalveAddr,Length,I2C_AutoEnd_Mode,I2C_No_StartStop);
	
	for(i=0;i<Length;i++)
	{
		while(I2C_GetFlagStatus(I2C1,I2C_FLAG_TXIS)==RESET);//等待发送寄存器为空
		
		I2C_SendData(I2C1,buffer[i]);	
	}
	
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_STOPF)==RESET);//作为主机检测由外设产生的停止信号
	
	I2C_ClearFlag(I2C1,I2C_FLAG_STOPF);
}

作为主机写EEPROM的操作流程类似,但是不需要修改传输方向,所以在第一次寻址时使用重加载模式,在8位或16位读写地址发送完成后,不重新生成起始信号,直接再次写入NBYTES,开始写入数据操作,由于该函数中我既定的传输字节数小于255,所以在该次传输开始前将模式修改成自动结束模式。
然后就是开始发送数据,后续结束流程和读EEPROM操作时类似。

物联沃分享整理
物联沃-IOTWORD物联网 » 详解STM32F030硬件I2C代码及原理解析

发表评论