I2C简介

两根通信线:SCL(时钟线:高电平读取数据,低电平变换数据)、SDA(数据线)

同步、半双工

带数据应答

支持总线挂载多设备(一主多从、多住多从)


相关外设简介-MPU6050

MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常用于平衡车、飞行器等需要检测自身姿态的场景。

MPU6050参数

16位ADC采集传感器的模拟信号,量化范围:-32768~32767

加速度计满量程选择:±2、±4、±8、±16(g)

陀螺仪计满量程选择:±250、±500、±1000、±2000(°/sec)

I2C从机地址:11011000(AD0=0)

                        11011001(AD0=1)


I2C时序基本单元

-起始条件:SCL高电平期间,SDA从高电平切换到低电平

-终止条件:SCL高电平期间,SDA从低电平切换到高电平

-发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

-接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

-发送应答:主机在接收一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

-接收应答:主机发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

接收应答则是判断从机有没有接收到数据,并且我们要将SDA的控制权转交给从机,即释放SDA(拉高)


I2C基本结构


由于无法获取一个字节的数据是否发送完毕等等,STM32设置了许多代表不同事件的标志位

主机发送:

主机接收:


初始化函数


void MPU6050_Init(void)
{	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);//I2C1和2都是APB1的设备
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	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
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;//选择I2C模式
	I2C_InitStructure.I2C_ClockSpeed=50000;//时钟速度 可配置SCL时钟频率
	I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//配置占空比
	I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;//应答位配置
	I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//作为从机时能响应多少位的地址
	I2C_InitStructure.I2C_OwnAddress1=0x00;//STM32作为从机时的地址,长度和上面参数对应
	I2C_Init(I2C2,&I2C_InitStructure);
	
	I2C_Cmd(I2C2,ENABLE);
}

GPIO口在此处选择复用输出开漏,虽然是输出,但仍然可以输入,应为这里默认高电平是一种弱上拉,从机要发送数据只需要进行拉低电平或释放这一种操作,“拉”或“不拉”,主机读取电平就能接收从机发送的数据。PB10和PB11是I2C2的引脚。


指定地址写和指定地址读函数

//指定地址写
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//参数为寄存器地址和要写入的数据
{
//	MyI2C_Start();
//	MyI2C_SendByte(0xD0);从机地址
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(Data);
//	MyI2C_ReceiveAck();
//	MyI2C_Stop(); 
	
	I2C_GenerateSTART(I2C2,ENABLE);//起始条件
	//判断EV5事件
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);
	
	I2C_Send7bitAddress(I2C2,0xD0,I2C_Direction_Transmitter);//发送从机地址,选择“写”
	//判断EV6事件,这里的EV6选择“发送事件已选择”
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS);
	
	I2C_SendData(I2C2,RegAddress);//发送寄存器地址
	//判断EV8事件,“字节正在发送”
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS);
	
	I2C_SendData(I2C2,Data);//发送数据
	//判断EV8_2事件,即移位寄存器空,且没有新数据要发
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS);
	
	I2C_GenerateSTOP(I2C2,ENABLE);//终止条件
}

I2C_CheckEvent()函数用于判断各个标志位,完成各个标志位后会返回SUCCESS,当判断等于SUCCESS后跳出循坏,避免数据没有发送完全等。

硬件I2C和软件I2C不同的是:软件I2C因为是手动模拟,发送字节数据都是完整的,是通过发送和接收应答位来判断双方交流是否正常;而硬件I2C则是判断标志位,并不需要管应答位,只需要使能ACK应答位即可。

//指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)//参数为寄存器地址
{
	uint8_t Data;
	
//	MyI2C_Start();
//	MyI2C_SendByte(0xD0);从机地址
//	MyI2C_ReceiveAck();
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	
//	MyI2C_Start();
//	MyI2C_SendByte(0xD0|0x01);
//	MyI2C_ReceiveAck();
//	Data=MyI2C_ReceiveByte();
//	MyI2C_SendAck(1);
//	MyI2C_Stop();
	
	//这里和指定地址写的前三步一样,即指定寄存器地址
	I2C_GenerateSTART(I2C2,ENABLE);//起始条件
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);
	I2C_Send7bitAddress(I2C2,0xD0,I2C_Direction_Transmitter);//发送从机地址,选择“写”
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS);
	I2C_SendData(I2C2,RegAddress);//发送寄存器地址
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS);
 
	I2C_GenerateSTART(I2C2,ENABLE);//重复起始条件
    //判断EV5
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS);
	
	I2C_Send7bitAddress(I2C2,0xD0,I2C_Direction_Receiver);//发送从机地址,选择“读”
	//等待EV6事件,选择“接收事件已选择”
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS);
	
	//在接收最后一个字节时,要提前将ACK置0,并且产生终止条件
	I2C_AcknowledgeConfig(I2C2,DISABLE);//ACK=0,不给应答
	I2C_GenerateSTOP(I2C2,ENABLE);//STOP=1,申请产生终止条件
	
	//判断EV7事件,意味着数据移到DR寄存器,可以读走了
	while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS);
	Data=I2C_ReceiveData(I2C2);//读取数据
	
	//最后将应答位置1
	I2C_AcknowledgeConfig(I2C2,ENABLE);
	
	return Data;
}

 指定地址读的前三步和指定地址写一样,起始条件后发送从机地址,写寄存器地址,然后重复起始条件,发送从机地址选择“读”,这里判断EV6事件就不同于指定地址写。

因为我们写和读都是只有一个字节的数据,所以指定地址读后就是接收最后一个字节,如果接收多个字节则是判断EV7事件,这里接收一个字节也是最后一个字节,要提前将ACK置0(失能),同时申请终止条件。最后终止条件产生后判断EV7事件,读走DR寄存器的数据。最后将ACK置1(使能)(我的理解是因为要在主循环里不断调用读的操作,在初始化函数里通过结构体参数I2C_Ack使能ACK,意味着正常读写ACK都是处于使能的状态,而函数I2C_AcknowledgeConfig可以单独配置ACK的状态,为了后续正常运行,所以最后还要将ACK使能)


常用函数

I2C_SendData  发送数据

I2C_ReceiveData  接收数据

I2C_GeneratesSTART  生成起始条件

I2C_GenerateSTOP  生成终止条件

I2C_AcknowedgeConfig  使能或失能ACK,给ENABLE就是给从机应答

Logo

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

更多推荐