课程链接:[10-1] I2C通信协议_哔哩哔哩_bilibili

一、I2C总线介绍

  1.I2C总线简要介绍

        I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线,其由两根通信线:SCL(Serial Clock)、SDA(Serial Data)进行通信
        I2C总线特点:同步,半双工、带数据应答、支持总线挂载多设备(一主多从、多主多从)

2. I2C电路模型

硬件电路:

3.I2C时序基本单元

(1)起始终止条件

(2)主机向从机发送数据时序

流程:主机在拉低SCL,在其低电平时将数据放在SDA上,然后主机松开SCL,从机读取SDA的数据,注意该过程高位先行

(2)从机向主机发送数据时序

与(1)过程相似

(3)应答机制

4.读写具体时序

        (1)指定地址写:对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data),时序信号如下,第一部分为要写入的设备地址,第二部分为该设备的某个寄存器,第三部分为要写入的内容。当写入一个数据后,地址执政+1,继续写数据 

        (2)当前地址读:对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data),如下图,第一部分为设备选择并读,第二部分为读取的数据,而数据对应的寄存器地址由指针能够给出

        (3)指定地址读:对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)。过程时序由指定地址写的前两部分和当前地址写构成指定地址读。其中指定地址读的指针指向写入的地址

注意:上述时序中,从机控制SDA发送一个字节权利开始于读写标志位为1,结束于主机应答为1

二、MPU6050

1.MPU6050简介 

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

a.3轴加速度计(Accelerometer):测量XYZ轴的加速度

b.3轴陀螺仪传感器(Gyroscope):测量XYZ轴的角速度

对角速度进行积分就是角度

2.主要参数

16 ADC 采集传感器的模拟信号,量化范围: -32768~32767
加速度计满量程选择: ±2 ±4 ±8 ±16 g
陀螺仪满量程选择: ±250 ±500 ±1000 ±2000 °/sec
可配置的数字低通滤波器
可配置的时钟源
可配置的采样分频
I2C 从机地址: 1101000 AD0=0

                                    1101001(AD0=1)

 3.硬件电路及接口

引脚

功能

VCCGND

电源

SCLSDA

I2C通信引脚

XCLXDA

主机I2C通信引脚

AD0

从机地址最低位

INT

中断信号输出

 三、MPU6050软件I2C读写

软件I2C读写需要用IO口模拟I2C 的时序,分协议层、驱动层、应用层

协议:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

//起始终止时,SCL高电平必须动SDA
//发送接收数据时,SCL高电平必须不动SDA


//1.读I2C操作
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);//(BitAction)强制类型转换
	Delay_us (10);
}

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);//(BitAction)强制类型转换
	Delay_us (10);
}

//2.读I2C操作
uint8_t  MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us (10);
	
	return BitValue;
}

//3.I2C初始化
void MyI2C_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB ,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);
}

//4.起始和终止条件
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);

	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);//起始条件
}

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);

	MyI2C_W_SDA(1);
}

//5.发送接收一个字节

//(1)发送一个字节
//SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for(i=0;i<8;i++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));//高位先行与Byte移位与
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

//(2)接收一个字节
//SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位
//!!!注意:主机在接收之前,需要释放SDA
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i,Byte = 0x00;
	
	MyI2C_W_SDA(1);
	for(i=0;i<8;i++)
	{
		MyI2C_W_SCL(1);
		if(MyI2C_R_SDA() == 1)
		{
			Byte = Byte | (0x80 >> i);//若接收数据为高电平,就将Byte对应位一位置1,其余位不变
		}
		MyI2C_W_SCL(0);
	}
	
	return Byte;
	
}

//6.发送应答和接收应答

//发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

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

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	
	return AckBit;
	
}

MPU6050读写数据以及传感器加速度角速度数据获取

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS  0xD0


//指定地址写一个字节
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	//1.开始
	MyI2C_Start();
	//2.从机编号
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck(); //从机应答
	//3.发送基地址
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	//4.发送数据
	MyI2C_SendByte(Data);
	MyI2C_ReceiveAck();
	//4.发送终止
	MyI2C_Stop();
}

//指定地址读一个字节
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	//存取接收到的数据
	uint8_t Data;
	
	//开始
	MyI2C_Start();
	//从机编号
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck(); //从机应答
	//发送基地址
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	
	MyI2C_Start();
	//0xD0是写地址,0xD1是读地址
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//0xD1
	MyI2C_ReceiveAck(); 
	
	Data = MyI2C_ReceiveByte();
	//读取一个字节,不给从机应答
	MyI2C_SendAck(1);
	
	MyI2C_Stop();
	
	return Data;
	
}


void MPU6050_Init(void)
{
	MyI2C_Init();
	
	//解除睡眠
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
	//陀螺仪时钟,不待机
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
	//10分频
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
	
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);
	
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
	
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);

}

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

//读取寄存器获取陀螺仪数据
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
					 int16_t *GyroX, int16_t *GyroY ,int16_t *GyroZ)
{
	uint8_t DataH,DataL;
	
	//X轴加速度
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL; //将两个8位值构成一个16位值
	//Y轴加速度
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	//Z轴加速度
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	//X轴角速度
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	//Y轴角速度
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	//Z轴角速度
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

主函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "MyI2C.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;
int16_t AX,AY,AZ,GX,GY,GZ;

int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	OLED_ShowString(1,1,"ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1,4,ID,2);
	
/*
	计算:X/32768 = 实际值 / 16
	X为测算值
*/
	while(1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		
		OLED_ShowSignedNum(2,1,AX,5);
		OLED_ShowSignedNum(3,1,AY,5);
		OLED_ShowSignedNum(4,1,AZ,5);
		
		OLED_ShowSignedNum(2,8,GX,5);
		OLED_ShowSignedNum(3,8,GY,5);
		OLED_ShowSignedNum(4,8,GZ,5);
	}
}

 四、MPU6050硬件I2C读写

 配置结构图:

1.主机发送数据(指定地址写):

其中指定地址分为两种地址:7位地址(起始位后就是指定地址+一位读写位)和10位地址(11110+2位地址+1位读写位+8位地址位)

 7位指定地址写配置流程:

        起始条件寄存器(STM32转为主模式)---检测起始标志位SB---将从机地址写入数据寄存器DR---是否接收?标志位ADDR置位---移位/数据寄存器是否空标志位---(EV8事件)写入数据1 ......--请求设定停止位(EV8_2事件)---写入停止位

2.主机接收数据(指定地址读):

3.代码部分

删掉软件I2C的MyI2C.h文件,通过库函数进行协议配置,除了MPU6050.h文件,其余不变

MPU6050.h文件更改如下:

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS  0xD0

//定义一个等待事件函数
void MPU6050_WriteEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;
	
	while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)//EV5事件是否发生
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
	
}

//一、指定地址写一个字节
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	
	//1.开始条件时序
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5事件是否发生
	
	//2.写从机地址
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6事件是否发生
	
	//3.写入发送地址
	I2C_SendData(I2C2,RegAddress);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8事件是否发生
	
	//4.写入发送的数据
	I2C_SendData(I2C2,Data);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2事件是否发生

	//5.终止条件时序
	I2C_GenerateSTOP(I2C2,ENABLE);
}

//二、指定地址读一个字节
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	//1.开始条件时序
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5事件是否发生
	
	//2.写从机地址
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6事件是否发生
	
	//3.写入发送地址
	I2C_SendData(I2C2,RegAddress);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8事件是否发生
	
	//4.重复起始条件
	I2C_GenerateSTART(I2C2,ENABLE);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5事件是否发生

	//5.读从机地址
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6事件是否发生
	
	//6.ACK提前置0(从机不应答)、STOP置1
	I2C_AcknowledgeConfig(I2C2,DISABLE);
	I2C_GenerateSTOP(I2C2,ENABLE);
	MPU6050_WriteEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//EV7事件是否发生

	//7.接收数据
	Data = I2C_ReceiveData(I2C2);
	
	//8.恢复ACK置1(从机应答)
	I2C_AcknowledgeConfig(I2C2,ENABLE);
	
	return Data;
}

//三、MPU6050初始化
void MPU6050_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//复用开漏
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);

	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_ClockSpeed = 50000;
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;
	I2C_Init(I2C2,&I2C_InitStructure);
	
	
	
	//解除睡眠
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);
	//陀螺仪时钟,不待机
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
	//10分频
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
	
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);
	
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
	
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);

}

//四、读取寄存器获取陀螺仪数据
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
					 int16_t *GyroX, int16_t *GyroY ,int16_t *GyroZ)
{
	uint8_t DataH,DataL;
	
	//X轴加速度
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL; //将两个8位值构成一个16位值
	//Y轴加速度
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	//Z轴加速度
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	//X轴角速度
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	//Y轴角速度
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	//Z轴角速度
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

//五、获取ID号
uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

Logo

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

更多推荐