目录

STM32-I2C通信外设

STM32-I2C通信外设


一:I2C外设简介

STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担。也就是由硬件电路来自动翻转引脚电平,软件只需要写入控制寄存器CR和数据寄存器DR,还需要用SR读取状态。(高位先行)

https://i-blog.csdnimg.cn/direct/c2ed3c119da84d6bbb31fa0bbbbd9c95.jpg

二:I2C外设数据收发

1.当我们需要发送数据时,可以把一个数据写到数据寄存器DR,当移位寄存器没有数据移位时,这个数据寄存器(DATA REGISTER)的值就会进一步转到移位寄存器里,在移位的过程中,就可以把下一个数据放到数据寄存器(DATA REGISTER)里等着,一旦前一个数据完成,下一个数据就可以无缝衔接,继续发送。当数据由数据寄存器(DATA REGISTER)转到移位寄存器时,就会置状态寄存器的TXE位为1,表述发送寄存器为空。

2.接收数据时,输入的数据,一位一位地从引脚移动到移位寄存器里,当一个字节收齐之后,数据就整体从移位寄存器转到数据寄存器,同时置标志位RXNE,表示接受寄存器非空,这时就可以从数据由数据寄存器(DATA REGISTER)里把数据读出来。

https://i-blog.csdnimg.cn/direct/bba6cba3978f472abb6f5febfe291235.jpg

三:I2C的复用端口

https://i-blog.csdnimg.cn/direct/db3efb88ad634a138020d68728a13bdc.png

https://i-blog.csdnimg.cn/direct/e0f436d12f3f4759bc3824969a436a63.png

四:主机发送和接收

1.主机发送:

首先,初始化之后,总线默认空闲状态STM32默认是从模式,为了产生一个起始条件,STM32需要写入控制寄存器,只要在控制寄存器START位,写1就可以产生起始条件——STM32由从模式变为主模式。

https://i-blog.csdnimg.cn/direct/2d9210116b1b44b2a83a0a0ee3ce2941.png

https://i-blog.csdnimg.cn/direct/b03948c0ee724b62b0523d8d371fc392.jpg

https://i-blog.csdnimg.cn/direct/e1d9d3dca0ee40e6a7d6b12966cfabc6.jpg

五:硬件I2C读写MPU6050

相关函数:

1.I2C_ GenerateSTART

功能描述: 产生

I2Cx

传输

START

条件

参数1: I2Cx

x

可以是

1

或者

2

,来选择

I2C

外设

参数2:NewState: I2Cx START 条件的新状态,这个参数可以取:ENABLE 或者 DISABLE

https://i-blog.csdnimg.cn/direct/1e93677be99744eaad808a65b58e774d.png

2. I2C_ GenerateSTOP

功能描述: 产生

I2Cx

传输

STOP

条件

参数1:I2Cx

x

可以是

1

或者

2

,来选择

I2C

外设

参数2: NewState: I2Cx STOP 条件的新状态 ,这个参数可以取:ENABLE

或者

DISABLE

https://i-blog.csdnimg.cn/direct/597f82659bc548b3a0a9b6197f2a53ba.png

3. I2C_ AcknowledgeConfig

功能描述: 使能或者失能指定

I2C

的应答功能

参数1: I2Cx

x

可以是

1

或者

2

,来选择

I2C

外设

参数2: NewState: I2Cx 应答的新状态 ,这个参数可以取:ENABLE

或者

DISABLE

https://i-blog.csdnimg.cn/direct/5777198f487840428351fcb27eba4762.png

4. I2C_ SendData

功能描述: 通过外设

I2Cx

发送一个数据

参数1:I2Cx

x

可以是

1

或者

2

,来选择

I2C

外设

参数2: Data:

待发送的数据

5. I2C_ ReceiveData

功能描述: 返回通过

I2Cx

最近接收的数据

输入参数:I2Cx

x

可以是

1

或者

2

,来选择

I2C

外设

https://i-blog.csdnimg.cn/direct/4281348e1f7d4977a1bdd6b96c28ceb0.png

第一步:配置I2C外设,对I2C外设进行初始化

  • 开启I2C和GPIO时钟
  • 需要把PB10和PB11都初始化为复用开漏模式(原因:开漏是I2C协议设计要求,复用是GPIO的控制权要交给硬件外设,如果是软件I2C的话就是通用开漏模式)
  • 初始I2C2外设
  • 使能I2C255
/**
  * 函    数:MPU6050初始化
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);		//开启I2C2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	
	/*GPIO初始化*/
	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);					//将PB10和PB11引脚初始化为复用开漏输出
	
	/*I2C初始化*/
	I2C_InitTypeDef I2C_InitStructure;						//定义结构体变量
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;				//模式,选择为I2C模式
	I2C_InitStructure.I2C_ClockSpeed = 50000;				//时钟速度,选择为50KHz
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;		//时钟占空比,选择Tlow/Thigh = 2
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;				//应答,选择使能
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	//应答地址,选择7位,从机模式下才有效
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;				//自身地址,从机模式下才有效
	I2C_Init(I2C2, &I2C_InitStructure);						//将结构体变量交给I2C_Init,配置I2C2
	
	/*I2C使能*/
	I2C_Cmd(I2C2, ENABLE);									//使能I2C2,开始运行
	
	/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);				//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);				//电源管理寄存器2,保持默认值0,所有轴均不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);				//采样率分频寄存器,配置采样率
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);					//配置寄存器,配置DLPF
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);			//陀螺仪配置寄存器,选择满量程为±2000°/s
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);			//加速度计配置寄存器,选择满量程为±16g
}

第二步:控制外设电路,实现指定地址写的时序

  • 生成起始条件(对于非阻塞式的程序,在函数结束之后,我们都要等待相应的标志位来确保这个函数的操作执行到位了,所以在程序中我们要等待EV5事件的到来),检测EV5事件: I2C_ CheckEvent——检查最近一次

    I2C

    事件是否是输入的事件

  • 起始条件发送之后,就要发送从机地址、接收应答

  • 按这个流程写代码: https://i-blog.csdnimg.cn/direct/931d084a81124bc7ba2cd8b5b12f46ff.png

/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);			//等待EV8
	
	I2C_SendData(I2C2, Data);												//硬件I2C发送数据
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2
	
	I2C_GenerateSTOP(I2C2, ENABLE);											//硬件I2C生成终止条件
}

第三步:控制外设电路,实现指定地址读的时序

/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//硬件I2C发送从机地址,方向为发送
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);	//等待EV6
	
	I2C_SendData(I2C2, RegAddress);											//硬件I2C发送寄存器地址
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);				//等待EV8_2
	
	I2C_GenerateSTART(I2C2, ENABLE);										//硬件I2C生成重复起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);					//等待EV5
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);		//硬件I2C发送从机地址,方向为接收
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);		//等待EV6
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);									//在接收最后一个字节之前提前将应答失能
	I2C_GenerateSTOP(I2C2, ENABLE);											//在接收最后一个字节之前提前申请停止条件
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);				//等待EV7
	Data = I2C_ReceiveData(I2C2);											//接收数据寄存器
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);									//将应答恢复为使能,为了不影响后续可能产生的读取多字节操作
	
	return Data;
}

六:防止死循环卡机

/**
  * 函    数:MPU6050等待事件
  * 参    数:同I2C_CheckEvent
  * 返 回 值:无
  */
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;									//给定超时计数时间
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)	//循环等待指定事件
	{
		Timeout --;										//等待时,计数值自减
		if (Timeout == 0)								//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;										//跳出等待,不等了
		}
	}
}