简介

STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
支持多主机模型
支持7位/10位地址模式
支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
支持DMA
兼容SMBus协议

STM32F103C8T6 硬件I2C资源:I2C1、I2C2

内部电路原理

发送数据时,先把数据发送到数据寄存器DR中,当移位寄存器没有数据时,随后数据进入移位寄存器,此时置状态寄存器TEX为1(发送寄存器空)

接收数据时,数据先从引脚移入到移位寄存器里,当一个字节的数据收齐之后,数据整体移入数据寄存器。此时置状态寄存器RXNE,表示接收寄存器非空。

比较器,自身地址寄存器,双地址寄存器这三部分都是从机模式才会拥有的。因为当stm32不作为主机的时候,那他就是从机,从机需要有一个从机地址。从机地址就是由自身地址寄存器指定。我们指定一个地址写入这个寄存器。

基本结构图

使用硬件I2C时,都要把SCL和SDA引脚配置为复用开漏输出模式

复用是指GPIO引脚的状态是由片上外设的状态来设置的

主机发送

刚开始初始化时,总线默认是空闲状态,STM32是从模式。为了产生起始条件,STM32要写入控制寄存器。控制寄存器的位8为start位,置1时STM32自动产生起始条件。其实条件发出后,这一位由硬件自动清除。随后STM32由从模式变为主模式。

EV5事件就是将SR1状态寄存器的start位置1,产生起始条件

EV6事件读SR1和SR2,判断寻址是否完成。

EV8_1事件告诉你,你该写入数据了

EV8事件代表数据寄存器DR为空,置TEX = 1.只有当下一个数据装进来时,TEX标志位才会清除

EV8_2事件表示,移位寄存器完成,没有新的数据发,置TEX = 1(数据寄存器DR空),BTF = 1(请求设置停止位)

图中的EVx代表的是标志位。

主机接收

EV5事件:将SR1状态寄存器的start位置1,产生起始条件

EV6事件:主机发送的从机寄存器读,寻址成功。

EV6_1事件:没有对应的标志位,只适用于接受一个字节的情况。但是要把响应位置0,停止位置1;

EV7事件:将数据从移位寄存器转移到DR中,并且置标志位

EV7_1事件:读DR时,并且把响应位置0,停止位置1.(最后一个字节的情况)

(EV6_1的位置,数据就已经到了移位寄存器了。如果我不提前把响应位置0和停止位置1。那么等到数据从移位寄存器到数据寄存器的时候,再置这两个标志位的话,就会导致这两个标志位只会在下一个字节发生作用,那么此时就是倒数第二个字节了)

总结:如果只接收一个字节,在EV6的时候,就应该把ACK置0,STOP置1.

           如果接收多个字节,在EV7(数据存入DR)之后,EV7_1之前,就把ACK = 0,STOP = 1

常用库函数

//产生起始条件,将控制寄存器里面的start位置1
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);

//产生停止条件,将控制寄存器里面的stop位置1
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

//主机接受一个字节后,是否要给从机发送一个应答位
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

//发送数据,将Data直接写入DR寄存器,硬件自动启动数据传输
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);

//接收数据,读取DR的数据作为返回值
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

//发送7位地址的专用函数,Address也是通过DR发送的。Direction标志读写
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);

//读取标志位
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);

//清除标志位
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);

//读取中断标志位
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);

//清除中断标志位
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);


//基本状态监控。同时判断一个或多个标志位,来判断EV几和EV几发生标志
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);

初始化:

void MPU6050_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2 , ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	//将PB10和PB11设置为复用开漏输出
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;            //选择模式
	I2C_InitStructure.I2C_ClockSpeed = 50000;             //SCL的时钟频率
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;    //时钟占空比,此时给的是2:1
	/*(此参数只有SCL时钟频率大于100KHz时才有用)SCL时钟频率小于100KHz时默认是1:1*/
	
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;           //接收到数据后给应答
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	/*此参数是指定stm32作为从机可以响应的地址位数*/
	
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;             //指定stm32的自身地址
	I2C_Init(I2C2, &I2C_InitStructure);
	
	I2C_Cmd(I2C1, ENABLE);
}

I2C_InitTypeDef参数:

指定地址写

//指定地址写
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	uint32_t Timeout = 0;
	
	I2C_GenerateSTART(I2C2, ENABLE); //产生起始条件
	
	//I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT)检查EV5事件(是否产生起始条件)返回success发生/ERROR没发生
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS) //此时是ERROR,进入循环等待发生SUCCESS
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //发送设备地址,并且指定写入
	/*发送地址自带了接收应答的功能,如果应答错误,硬件会置标志位和中断来提示我们*/
	
	//I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)检查事件6是否寻址成功
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS)
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	
	/*EV8_1事件是提醒你该写入数据,我们不需要while等这个标志位,直接写入这个数据就好*/
	I2C_SendData(I2C2, RegAddress); //这里是指定寄存器
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS)
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	/*因为此时数据寄存器非空,置TEX = 1,写入DR会清除TEX标志位,随意可以直接写入下一个数据*/
	
	I2C_SendData(I2C2, Data);//数据
	/*当我们发送连续的数据时,需要等待EV8事件,当我们发送最后一个数据时,等待EV8_2事件*/
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)//等待EV8_2事件
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	I2C_GenerateSTOP(I2C2, ENABLE);//产生终止条件
	
}

指定地址读

//指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	uint32_t Timeout = 10000;
	
	I2C_GenerateSTART(I2C2, ENABLE); //产生起始条件
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)//等待EV5事件,起始位是否标志成功
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//发送指定设备地址
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS)//等待EV6事件是否寻址成功
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	
	I2C_SendData(I2C2, RegAddress);//发送指定寄存器的地址
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS)//等待EV8事件
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	
	/*另外起一个重复起始时序*/
	I2C_GenerateSTART(I2C2, ENABLE); //产生起始条件
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)//等待EV5事件,起始位是否标志成功
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	
	I2C_Send7bitAddress(I2C2, RegAddress, I2C_Direction_Receiver);//发送指定地址读
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS)//EV6
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);//主机应答位0
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	while(I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS)
	{
		Timeout --;
		if(Timeout == 0) break;
	}
	Timeout = 10000;
	/*EV7事件,将数据从移位寄存器移动到数据寄存器中,并且置标志位*/
	
	Data = I2C_ReceiveData(I2C2);//读取DR
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);//主机应答位1,默认就是1,只是最后一位不接收了改为0
	
	return Data;
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐