零基础玩转 MPU6050:I2C 协议白话解析 + STM32 初始化代码,直接套用

1.硬件介绍

本期介绍一下智能家居项目中用到的角度传感器MPU6050。此模块在项目中用于获取姿态角度,方便后续添加无按键控制功能以及步数获取功能。

1.1.产品参数

产品名称:MPU-6050模块(三轴陀螺仪+三轴加速度)

使用芯片:MPU-6050

供电电源:3-5V(内部低压差稳压)通信方式:标准IIC通信协议

芯片内置:16bit AD转换器,16位数据输出

陀螺仪范围: ±250 500 1000 2000°/s

加速度范围: ±2±4±8±16g

引脚间距:2.54mm

1.2.产品介绍详解

MPU-6050 这模块挺实用的,里面集成了 3 轴陀螺仪和 3 轴加速器,相当于一下子能测六个方向的运动状态。

更方便的是,它自带一个叫 DMP 的硬件加速引擎,专门处理运动数据。这个引擎能直接算出 9 轴融合的结果(虽然本身是 6 轴,但通过算法能实现类似 9 轴的效果),不用我们自己写复杂的融合代码。

它有两个 I2C 接口,主接口用来传输数据,第二个可以接其他传感器。最关键的是InvenSense 已经做好了现成的运动处理数据库,能帮我们处理那些复杂的运动数据计算,这样一来,单片机或者处理器就不用费劲儿去算这些,能减轻不少负担。

而且它还提供了结构化的 API,咱们做开发的时候,直接调用接口就行,不用从底层一点点写,用起来挺顺手的。

此外,MPU-6050 的角速度测量范围挺灵活的,能调到 ±250、±500、±1000 还有 ±2000 度每秒,不管是快动作还是慢动作都能精准捕捉到。加速度的测量范围也能自己设置,有 ±2g、±4g、±8g 和 ±16g 这几个档,按需调就行。

传数据的话,它支持两种方式:I2C 最快能到 400kHz,SPI 更快,能到 20MHz,看你项目需求选哪种都行。

供电方面也挺方便,主电源 VDD 可以用 2.5V、3.0V 或者 3.3V,误差在 ±5% 以内都没问题;不过逻辑接口的电压得是 1.8V±5%,这点要注意。

另外它的尺寸是真小,4x4x0.9 毫米的 QFN 封装,在业内算是很小巧的了,省空间。还有些实用功能,比如自带温度传感器,里面的振荡器在工作时误差也很小,只有 ±1%,稳定性不错。

2.I2C协议

2.1.I2C协议简介

I2C 协议说白了,就是电子设备之间 “聊天” 的一套规矩,专门用来解决多个小器件(比如传感器、显示屏、单片机)怎么用最少的线互相传数据的问题。

咱们具体说,它就靠两根线干活:一根叫 SDA(数据线),专门用来传实际的数据(比如传感器测到的温度、湿度);另一根叫 SCL(时钟线),负责 “打拍子”—— 所有设备都跟着这根线的节奏走,保证传数据时不抢、不错位,就像大家一起听着同一个节拍唱歌,不会乱。

这协议厉害的地方在于 “一根线能接一串设备”。比如一个单片机,想连温湿度传感器、光照传感器、OLED 屏幕,不用给每个设备单独拉一堆线,只要把它们的 SDA 和 SCL 分别并到一起,再加上电源和地线,就能让单片机挨个跟它们 “聊天”。

每个设备都有自己独一无二的 “地址”,就像每家有个门牌号,单片机喊 “地址 0x48 的设备,把数据发过来”,对应的传感器就会回应,其他设备则乖乖等着,不会瞎掺和。

传数据的时候也有固定流程:比如单片机想从传感器拿数据,先通过 SDA 线发个 “开始信号”(告诉大家要说话了),然后发传感器的地址,再发 “我要读数据” 的指令,传感器收到后就会把数据通过 SDA 线传回来,最后单片机发个 “结束信号”,这次聊天就结束了。整个过程都跟着 SCL 线的节奏走,一步一步来,不容易出错。

平时咱们玩的开发板、智能家居里的小模块(比如 DHT11 温湿度传感器有时候也会用 I2C 版本),很多都靠它来传数据,主要就是因为省线、简单,一堆设备挤在一起也能有条不紊地通信。

 

2.2.I2C通讯过程

I2C 的通信过程其实就像两个人打电话,有一套固定的 “通话流程”:

首先,主机(比如单片机)得先 “拿起电话”—— 发一个起始信号,告诉总线上的设备 “准备开始通信了”。

接着,主机得说清 “打给谁、要干嘛”—— 发一个字节的数据,里面包含了目标从机(比如传感器)的地址,以及接下来是要 “听数据” 还是 “发数据”(也就是传送方向)。

被点名的从机收到后,得 “喂一声” 回应 —— 发一个应答信号,告诉主机 “我收到了,你说吧”。

然后就进入正题了:发送数据的一方(可能是主机也可能是从机)发一个字节的数据,接收的一方收到后,再发一个应答信号表示 “收到了,继续”。

就这样,发数据、回应答,一步一步循环着来,直到所有要传的内容都发完。

最后,主机 “挂电话”—— 发一个停止信号,告诉大家 “这次通信结束了”,总线就释放出来,其他设备可以用了。

整个过程就像按剧本走,每个步骤都有回应,保证数据传得又准又顺~

2.3.I2C起始与停止信号

2.3.1.起始信号

I2C发送一个起始信号:SDA和SCL线都设置为高电平,然后在SCL为高电平的期间,让SDA线让从高电平到低电平进行一个下降沿。

//产生IIC起始信号
void MPU_IIC_Start(void)
{
    MPU_SDA_OUT();     //sda线输出
    MPU_IIC_SDA=1;	  	  
    MPU_IIC_SCL=1;
    MPU_IIC_Delay();
    MPU_IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
    MPU_IIC_Delay();
    MPU_IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}	  

2.3.2.停止信号

I2C发送一个停止信号:SCL和SDA线拉低,SCL线先拉高,再将SDA拉高,让SDA线在SCL高电平期间,经历一个上升沿。

//产生IIC停止信号
void MPU_IIC_Stop(void)
{
    MPU_SDA_OUT();//sda线输出
    MPU_IIC_SCL=0;
    MPU_IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
    MPU_IIC_Delay();
    MPU_IIC_SCL=1;  
    MPU_IIC_SDA=1;//发送I2C总线结束信号
    MPU_IIC_Delay();							   	
}

2.4.I2C应答信号

这是 I²C 总线里 “数据格式和应答机制” 的说明,用大白话拆解下:

数据是 8 位一组:不管传啥数据,都拆成一个一个 8 位的字节(比如温度值、设备地址 ),想传多少字节都行,没限制。

每传 1 字节必须跟个 “应答(ACK)”:就像你给朋友发消息,发一条得等对方回个 “收到”,保证数据没丢。

2.4.1.应答信号

发送完一字节数据后,拉低SCL线,SDA线,在SDA为低电平过程中,将SCL线拉高,再拉低为一个应答信号。

//产生ACK应答
void MPU_IIC_Ack(void)
{
    MPU_IIC_SCL=0;
    MPU_SDA_OUT();
    MPU_IIC_SDA=0;
    MPU_IIC_Delay();
    MPU_IIC_SCL=1;
    MPU_IIC_Delay();
    MPU_IIC_SCL=0;
}

2.4.1.不产生应答信号

发送完一字节数据后,拉低SCL线,SDA线为高电平,在SDA为低电平过程中,将SCL线拉高,再拉低为代表一个非应答信号。

//不产生ACK应答		
void MPU_IIC_NAck(void)
{
    MPU_IIC_SCL=0;
    MPU_SDA_OUT();
    MPU_IIC_SDA=1;
    MPU_IIC_Delay();
    MPU_IIC_SCL=1;
    MPU_IIC_Delay();
    MPU_IIC_SCL=0;
}

2.5.I2C写数据函数

SCL线拉低开始数据传输,SDA线每传输一位,SCL从低到高,再由高到低同步时钟一次。循环8次,发送一个字节数据。

//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void MPU_IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
    MPU_SDA_OUT(); 	    
    MPU_IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        MPU_IIC_SDA=(txd&0x80)>>7;
        txd<<=1; 	  
		MPU_IIC_SCL=1;
		MPU_IIC_Delay(); 
		MPU_IIC_SCL=0;	
		MPU_IIC_Delay();
    }	 
} 	    

2.6.I2C读数据函数

这是 “读 8 位数据” 的核心循环,逐位读取 SDA 上的数据:拉低SCL,告诉发送方 “准备发下一位数据”;Delay(); → 延时等待,让数据稳定;拉高SCL,此时 SDA 上的数据有效,receive<<=1; → 把 receive 里的二进制位 左移 1 位,给新数据腾位置;if(MPU_READ_SDA)receive++; → 读 SDA 引脚状态:如果 SDA 是高电平(MPU_READ_SDA 为真),receive 最低位加 1;如果是低电平,不加(保持 0)

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 MPU_IIC_Read_Byte(unsigned char ack)
{
    unsigned char i,receive=0;
    MPU_SDA_IN();//SDA设置为输入
    for(i=0;i<8;i++ )
    {
        MPU_IIC_SCL=0; 
        MPU_IIC_Delay();
        MPU_IIC_SCL=1;
        receive<<=1;
        if(MPU_READ_SDA)receive++;   
        MPU_IIC_Delay(); 
    }					 
    if (!ack)
        MPU_IIC_NAck();//发送nACK
    else
        MPU_IIC_Ack(); //发送ACK   
    return receive;
}

2.7.I2C通讯全过程

这段文字和波形图讲的是 I²C 通信的完整流程,用大白话拆解每一步:

  1. 主机(例如单片机)先给一个起始信号(S)

  2. 接着发 7 位从机地址(比如传感器的地址,总线上设备靠这个区分)

  3. 再跟 1 位 读写位(R/W):读写位是 0 → 主机要 “写数据” 给从机(比如给显示屏发内容);读写位是 1 → 主机要 “读数据” 从从机(比如从温湿度传感器拿数据)。

  4. 发完地址 + 读写位后,主机松开 SDA 线,等从机应答:

  5. 从机收到地址后,如果是自己的地址,就把 SDA 拉低,并在 SCL 高电平期间保持低电平 → 这就是 应答(ACK)

  6. 接下来进入 “传数据阶段”:主机 / 从机发 8 位数据(比如传感器的温度值),每发 1 字节,必须等对方应答(ACK);应答逻辑和之前一样:接收方拉低 SDA,在 SCL 高电平时保持低 → 确认 “收到这字节了”。

  7. 最后主机想结束通信时,发 “停止信号”:SCL 高电平时,SDA 从低变高 ,相当于说 “不传啦,总线释放,大家可以用了”。

3.MPU6050代码

3.1.I2C通讯应用

以下代码就是对上面I2C通讯全过程的实现,包括读和写的过程。

//IIC写一个字节 
//reg:寄存器地址
//data:数据
//返回值:0,正常
//    其他,错误代码
u8 MPU_Write_Byte(u8 reg,u8 data) 				 
{ 
    MPU_IIC_Start(); 
    MPU_IIC_Send_Byte((MPU_ADDR<<1)|0);//发送器件地址+写命令	
    if(MPU_IIC_Wait_Ack())	//等待应答
    {
        MPU_IIC_Stop();		 
        return 1;		
    }
    MPU_IIC_Send_Byte(reg);	//写寄存器地址
    MPU_IIC_Wait_Ack();		//等待应答 
    MPU_IIC_Send_Byte(data);//发送数据
    if(MPU_IIC_Wait_Ack())	//等待ACK
    {
        MPU_IIC_Stop();	 
        return 1;		 
    }		 
    MPU_IIC_Stop();	 
    return 0;
}

//IIC读一个字节 
//reg:寄存器地址 
//返回值:读到的数据
u8 MPU_Read_Byte(u8 reg)
{
    u8 res;
    MPU_IIC_Start(); 
    MPU_IIC_Send_Byte((MPU_ADDR<<1)|0);//发送器件地址+写命令	
    MPU_IIC_Wait_Ack();		//等待应答 
    MPU_IIC_Send_Byte(reg);	//写寄存器地址
    MPU_IIC_Wait_Ack();		//等待应答
    MPU_IIC_Start();
    MPU_IIC_Send_Byte((MPU_ADDR<<1)|1);//发送器件地址+读命令	
    MPU_IIC_Wait_Ack();		//等待应答 
    res=MPU_IIC_Read_Byte(0);//读取数据,发送nACK 
    MPU_IIC_Stop();			//产生一个停止条件 
    return res;		
}

3.2.初始化配置

以下是对MPU6050寄存器的一系列配置,详细可见其数据手册。

//初始化MPU6050
//返回值:0,成功
//其他,错误代码
u8 MPU_Init(void)
{ 
    u8 res; 
    MPU_IIC_Init();//初始化IIC总线
    MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X80);	//复位MPU6050
    delay_ms(100);
    MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X00);	//唤醒MPU6050 
    MPU_Set_Gyro_Fsr(3);					//陀螺仪传感器,±2000dps
    MPU_Set_Accel_Fsr(0);					//加速度传感器,±2g
    MPU_Set_Rate(200);						//设置采样率50Hz
    MPU_Write_Byte(MPU_INT_EN_REG,0X00);	//关闭所有中断
    MPU_Write_Byte(MPU_USER_CTRL_REG,0X00);	//I2C主模式关闭
    MPU_Write_Byte(MPU_FIFO_EN_REG,0X00);	//关闭FIFO
    res=MPU_Read_Byte(MPU_DEVICE_ID_REG); 
    printf("ID: %d\r\n", res);

    if(res==MPU_ADDR)//器件ID正确
    {
        MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X01);	//设置CLKSEL,PLL X轴为参考
        MPU_Write_Byte(MPU_PWR_MGMT2_REG,0X00);	//加速度与陀螺仪都工作
        MPU_Set_Rate(100);						//设置采样率为100Hz,周期为10ms
    }else return 1;
    return 0;
}

3.2.main.c

调用初始化函数,初始化MPU6050配置和启动专门处理运动数据的DMP硬件加速引擎。

int main()
{	
    MPU_Init();
    mpu_dmp_init();
    
    while(1)
    {
        MPU_Get_Gyroscope(&g_ngyrox,&g_ngyroy,&g_ngyroz);  // 读取角速度
        MPU_Get_Accelerometer(&g_naacx,&g_naacy,&g_naacz); // 读取加速度
    }
}

4.完整代码

我用夸克网盘给你分享了「SmartHom...iyun」,点击链接或复制整段内容,打开「夸克APP」即可获取。
/~1d17370YmM~:/
链接: 夸克网盘分享
提取码:1KdH
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值