STM32开发之模拟I2C与MPU6050通信

本文详细介绍了STM32通过模拟I2C与MPU6050传感器通信的过程,包括I2C通信协议的基础知识、起始和终止条件、发送与接收字节的时序,以及在实际代码中的应用。在实践中遇到了MPU6050寄存器读写问题,通过分析和调试,提供了完整的模拟I2C通信代码和MPU6050的C语言实现。

相信大家在I2C与MPU6050通信时总会碰到一些问题,作者的问题是可以读ID号,但是无法对MPU6050寄存器进行写入,读取寄存器的数据总是0

问题

 我们可以看到,数据一直是0,作者在网上看到部分帖子,但是按照上述解决办法没有解决,作者会将文章的链接给出,希望对大家有帮助:MPU6050可以读取ID值,温度值和原始数据值为零问题解决 - Darren_pty - 博客园 (cnblogs.com)

 嵌入式C语言应用

do-while

do-while循环是一个后测试循环,它在执行循环内部的代码之后检查循环条件。这意味着循环内部的代码至少会被执行一次,即使循环条件一开始也达不到满足。

do {
    // 循环体内的代码
    // 这部分代码会至少被执行一次

} while (循环条件);

 C语言代码换行

do {
    // 循环体内的代码
    // 这部分代码会至少被执行一次} \(\表示换行,并且注意在'\'后仅能跟回车,不能加其他字符,否则编译报错)
while (循环条件);

通信协议前置知识

串行通信与并行通信

1.并行通信的特点为同时传输多bit位数据,优点:传输效率高;缺点:数据线多,设计复杂

 2.串行通信特点是只能一位一位传输

 异步串行通信与同步串行通信

视频:你知道同步通信和异步通信有什么区别吗?_哔哩哔哩_bilibili

同步:发送方发出数据后,等接收方发回响应才能发送下一个数据包的通信方式

异步:发送方发送数据后,不等接收方发回响应接着发送下个数据包的通信方式

1.同步串行通信

 2.异步串行通信

 单工、半双工、全双工

I2C前置理论知识

支持总线挂载多设备(一主多从、多主多从),在多主多从模式下会进行总线仲裁,时钟线也是由主机控制的,多主机模式下还需要进行时钟的同步

硬件电路

1.SCL线:对于SCL时钟线,在任何时刻都只能被动的读取,从机不允许控制SCL

2.因为主机拥有SCL的绝对控制权,所以主机的SCL可以设置为推挽输出模式,所有的从机的SCL都配置成浮空输入或者上拉输入

3.SDA线:主机在发送的时候是输出,在接收的时候是输入,同样从机的SDA也会在输入和输出之间反复切换(如果输入输出的时序遇到问题,就会出现麻烦,极有可能发生两个引脚同时处于输出状态,如果这时应该输出高电平,一个输出低电平就是电源短路)

4.避免短路设计:I2C的设计是禁止所有设备输出强上拉的高电平,采用外置弱上拉的电阻+开漏输出的电路结构

 线与逻辑:什么叫线与逻辑? - NewLook的回答 - 知乎 https://www.zhihu.com/question/534999506/answer/3081799468

I2C时序分析

本文借鉴了以下文章:STM32软件I2C驱动MPU6050 - Sightseer观光客 - 博客园 (cnblogs.com)

起始条件和终止条件

起始条件:SCL高电平期间,拉低SDA;终止条件:SCL高电平期间,拉高SDA

 起始条件与终止条件的程序

黄线部分是我们的重复起始条件:在SCL高电平期间,拉低SDA

 我们为什么需要在重复开始条件前,先释放SDA再释放SCL(准确来说,只有先拉高了SDA,就可以拒绝一切的影响,拉高SCL是为了创造起始条件)

终止条件是:在SCL高电平期间,释放SDA

 下面的文字说错了:一个是把SDA拉低,就可以解决无效的终止条件

 我们为什么要在终止条件前先把SDA拉低,是为了解决重复的终止条件,再拉高是为了创造终止条件的环境;最后一步拉低SDA才是正确的终止条件

//起始条件
void MyI2C_Start(){
    //为保证兼容重复开始条件,先释放SDA再释放SCL
    SDA_SET(1);
    SCL_SET(1);
/*
	起始条件是:先拉低SDA,再拉低SCL
*/
    SDA_SET(0);
    SCL_SET(0);
}
//终止条件
void MyI2C_Stop(){
//为了避免在释放前SDA是高电平,我们需要先拉低SDA,再释放SCL,再释放SDA
    SDA_SET(0);
    SCL_SET(1);
    SDA_SET(1);
}

 发送一个字节

主机发送,从机读取;一般是在SCL高电平期间,从机才能读取数据(但是为了尽快读取,一般从机在SCL上升沿期间就读取了SDA的数据,因为时钟是主机控制的,从机并不知道什么时候SCL就是下降沿了,所以上升沿就赶快进行读取)

发送一个字节的代码

void MyI2C_SendByte(uint8_t byte){
//除了终止条件,SCL以高电平结束,所有的单元我们都会保证SCL以低电平结束,这样方便各个单元的拼接
	/*这里我们是高位先行,我们就需要按位与取出来最高位
	MyI2C_W_SDA(Byte & 0x80);//(x x x x x x x x)&(1 0 0 0 0 0 0 0)=x 0 0 0 0 0 0 0*/
//为了避免重复书写,我们使用for循环,然后每次右移1为,再与
    for(uint8_t i = 0;i < 8;i++){
        SDA_SET(byte & (0x80 >> i));    //SDA写数据,I2C是高位先行
        SCL_SET(1);   //给SCL高电平,让从机把SDA的数据读走
        SCL_SET(0);      //再拉低,发送下一位      
    }
}

接收一个字节

从机发送,主机接收:在SCL低电平期间,从机把数据放到SDA上,高电平期间主机读取数据(主机在接收之前,需要先释放SDA(因为线与的特点,只要有一个是低电平,就是都是低电平,释放SDA相当于切换为输入模式)

接收一个字节的代码

uint8_t MyI2C_ReceiveByte(){
    uint8_t byte = 0x00;
    SDA_SET(1);            //先释放SDA
    for(uint8_t i = 0; i < 8;i++){
        SCL_SET(1);        //设置SCL为高,此时从机把数据放在SDA上,主机就可以读取数据了
        if(READ_SDA() == 1){
					/*第一次读SDA为1,把byte最高位置1,如果读SDA为0,if不成立,byte=0x00
					就相当于写入0了,因为我们是一个for循环,每次都是会刷新byte的值,所以
					第一次为1:(0 0 0 0 0 0 0 0)&(1 0 0 0 0 0 0 0)=1 0 0 0 0 0 0 0
					第二次为0:byte=(1 0 0 0 0 0 0 0)
					第二次为1:byte=(1 0 0 0 0 0 0 0)&(0 0 1 0 0 0 0 0)=1010 0 0 0 0
					*/
				byte |= (0x80 >> i);
				
				}    //由高到低位读SDA
        SCL_SET(0);        //设置SCL为低,一个时钟结束
    }
    return byte;
}

接收应答与发送应答

接收应答与发送应答的代码

//发送应答
void MyI2C_SendACK(uint8_t ackbit){
    SDA_SET(ackbit);        //把应答位放在SDA上
    SCL_SET(1);  //SCL高电平,从机读取应答
    SCL_SET(0);    //SCL低电平,进入下一个时序单元,完毕
}
//接收应答其实就是接收一个字节的一位
uint8_t MyI2C_ReceiveACK(){
	/*函数进来时,SCL低电平,主机释放SDA,防止干扰从机
	同时,从机把应答位放到SDA上,SCL高电平,主机读取应答位
  */
    uint8_t ackbit;
    SDA_SET(1);        //释放SDA
    SCL_SET(1);        //给SCL一个脉冲,让从机把应答位写到SDA上
    ackbit = READ_SDA();
    SCL_SET(0);      //设置SCL为低,一个时钟结束
    return ackbit;
}

 I2C时序之指定地址写

指定地址写 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

  I2C时序之当前地址读

当前地址读 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

读写位:0表示写,1表示读

 当前地址指针:在RA接收应答之后,SDA的控制权就要交给从机了,I2C协议的规定是,主机进行寻址时,一旦读写标志位给1了,下一个字节就要立马转换为读的时序,所以主机还来不及指定我想要读那个寄存器,就得开始接收了,所以这里就没有指定地址这个环节,(主机并没有指定地址,那么从机到底一个发那个寄存器的数据呢,这就需要当前地址指针了)。

在从机中,所有的寄存器被分配到了一个线性区域中,并且会有一个单独的指针变量,指示着这个寄存器,这个指针上电默认,一般指向0地址(并且每写入一个字节和读出一个字节后,这个指针就会自增一次,移动到下一个位置,那么在调用当前地址读的时序时,主机没有指定地址,从机就会返回当前指针指向的寄存器的值);

假如我刚刚调用了在指定地址写的时序,在0x19的位置写入了0XAA,那么指针就会+1,移动到0X1A的位置,我再调用当前地址读的时序,返回的就是0X1A地址下的值,如果再调用一次就是0x1B下的值

I2C时序之指定地址读

 

 就是指定地址读的时序了

 最开始依旧是启动条件,然后发送一个字节进行寻址,指定从机地址是1101000,读写标志位是0,代表写的操作,进过从机应答之后,再发送一个字节;第二个字节用来指定地址(从机的寄存器的地址),然后ox19这个地址就写入到了从机的地址指针中去了,从机接收到0x19之后,它的寄存器指针就指向了0x19;之后我们直接重新起始条件,指定好从机地址为读,这时指针已经指向了0x19,后面就直接读就行了

模拟I2C的全部代码

1.MyI2C.c

#include "stm32f10x.h"                  // Device header
#include "MYI2C.h"
#include "Delay.h"  
//设置I2C引脚端口,注意如端口号修改,时钟使能也要修改
#define SCL_PORT    GPIOB
#define SCL_LINE    GPIO_Pin_10
#define SDA_PORT    GPIOB
#define SDA_LINE    GPIO_Pin_11
//设置I2C操作延迟(速度)
#define I2C_DELAY    do{Delay_us(10);}while(0);
//I2C引脚电平写
#define SCL_SET(x)    do{GPIO_WriteBit(SCL_PORT,SCL_LINE,(BitAction)(x)); I2C_DELAY;} \
                      while(0);
#define SDA_SET(x)    do{GPIO_WriteBit(SDA_PORT,SDA_LINE,(BitAction)(x)); I2C_DELAY;} \
                      while(0);
//I2C引脚电平读
uint8_t READ_SDA(void){
    uint8_t val;
    val = GPIO_ReadInputDataBit(SDA_PORT,SDA_LINE);
    I2C_DELAY;
    return val;
}
void MyI2C_Init(){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStructure.GPIO_Pin  = SCL_LINE | SDA_LINE;
    GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
}
void MyI2C_Start(){
    //为保证兼容重复开始条件,先释放SDA再释放SCL
    SDA_SET(1);
    SCL_SET(1);
    SDA_SET(0);
    SCL_SET(0);
}
void MyI2C_Stop(){
    SDA_SET(0);
    SCL_SET(1);
    SDA_SET(1);
}
void MyI2C_SendByte(uint8_t byte){
    for(uint8_t i = 0;i < 8;i++){
        SDA_SET(byte & (0x80 >> i));    //SDA写数据,I2C是高位先行
        SCL_SET(1);    SCL_SET(0);            //给SCL一个脉冲,让从机把SDA的数据读走
    }
}
 
uint8_t MyI2C_ReceiveByte(){
    uint8_t byte = 0x00;
    SDA_SET(1);            //先释放SDA
    for(uint8_t i = 0; i < 8;i++){
        SCL_SET(1);        //设置SCL为高,此时从机把数据放在SDA上,主机就可以读取数据了
        if(READ_SDA() == 1){
					/*第一次读SDA为1,把byte最高位置1,如果读SDA为0,if不成立,byte=0x00
					就相当于写入0了,因为我们是一个for循环,每次都是会刷新byte的值,所以
					第一次为1:(0 0 0 0 0 0 0 0)&(1 0 0 0 0 0 0 0)=1 0 0 0 0 0 0 0
					第二次为0:byte=(1 0 0 0 0 0 0 0)
					第二次为1:byte=(1 0 0 0 0 0 0 0)&(0 0 1 0 0 0 0 0)=1010 0 0 0 0
					*/
				byte |= (0x80 >> i);
				
				}    //由高到低位读SDA
        SCL_SET(0);        //设置SCL为低,一个时钟结束
    }
    return byte;
}
//发送应答
void MyI2C_SendACK(uint8_t ackbit){
    SDA_SET(ackbit);        //把应答位放在SDA上
    SCL_SET(1);  //SCL高电平,从机读取应答
    SCL_SET(0);    //SCL低电平,进入下一个时序单元,完毕
}
//接收应答其实就是接收一个字节的一位
uint8_t MyI2C_ReceiveACK(){
	/*函数进来时,SCL低电平,主机释放SDA,防止干扰从机
	同时,从机把应答位放到SDA上,SCL高电平,主机读取应答位
  */
    uint8_t ackbit;
    SDA_SET(1);        //释放SDA
    SCL_SET(1);        //给SCL一个脉冲,让从机把应答位写到SDA上
    ackbit = READ_SDA();
    SCL_SET(0);      //设置SCL为低,一个时钟结束
    return ackbit;
}

2.MyI2C.H

#ifndef _MYI2C_H
#define _MYI2C_H
											
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendACK(uint8_t ackbit);
uint8_t MyI2C_ReceiveACK(void);

#endif

MPU6050代码分析

指定地址读+指定地址写

//指定地址读寄存器
uint8_t MPU6050_ReadReg(uint8_t reg_addr){
    uint8_t data;
    MyI2C_Start();  //起始位
    MyI2C_SendByte(MPU6050_WRITE_ADDR);  //从机地址+读
    MyI2C_ReceiveACK();  //接收应答,这里有应答位可以判断从机有没有接收到数据
	
		//寻址找到从机之后就可以发送下一个字节了
    MyI2C_SendByte(reg_addr);  //用来指定我写的地址,这个地址就写到从机的地址指针中了
	//从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
    MyI2C_ReceiveACK();  //接收应答
    
    MyI2C_Start();  //再来一个起始条件
    MyI2C_SendByte(MPU6050_READ_ADDR);  //再次指定设备的ID号,这次我们读
    MyI2C_ReceiveACK();  //主机接收应答
    data = MyI2C_ReceiveByte();  //指针指向的寄存器的数据
	MyI2C_SendACK(1);        //主机接收后,发送应答
    MyI2C_Stop();  //结束
    
    return data;
}
//指定地址写寄存器
 void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
    MyI2C_Start();  ////起始位
    MyI2C_SendByte(MPU6050_WRITE_ADDR);  //从机地址+写
    MyI2C_ReceiveACK();  ////接收应答,这里有应答位可以判断从机有没有接收到数据
	
    MyI2C_SendByte(reg_addr);  // //用来指定我写的寄存器地址
    MyI2C_ReceiveACK();  //接收应答
    MyI2C_SendByte(data);  //写入的数据
    MyI2C_ReceiveACK();  //接收应答
    MyI2C_Stop();  //结束
}

MPU6050代码

1.MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h" 
#include "MYI2C.h" 
#include "stdio.h"
#include "stdlib.h"
#define MPU6050_I2C_ADDR    (0x68)
#define MPU6050_WRITE_ADDR    (((MPU6050_I2C_ADDR) << 1) | 0x00)  //写
#define MPU6050_READ_ADDR    (((MPU6050_I2C_ADDR) << 1) | 0x01)   //读
//指定地址读寄存器
uint8_t MPU6050_ReadReg(uint8_t reg_addr){
    uint8_t data;
    MyI2C_Start();  //起始位
    MyI2C_SendByte(MPU6050_WRITE_ADDR);  //从机地址+读
    MyI2C_ReceiveACK();  //接收应答,这里有应答位可以判断从机有没有接收到数据
	
		//寻址找到从机之后就可以发送下一个字节了
    MyI2C_SendByte(reg_addr);  //用来指定我写的地址,这个地址就写到从机的地址指针中了
	//从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
    MyI2C_ReceiveACK();  //接收应答
    
    MyI2C_Start();  //再来一个起始条件
    MyI2C_SendByte(MPU6050_READ_ADDR);  //再次指定设备的ID号,这次我们读
    MyI2C_ReceiveACK();  //主机接收应答
    data = MyI2C_ReceiveByte();  //指针指向的寄存器的数据
	MyI2C_SendACK(1);        //主机接收后,发送应答
    MyI2C_Stop();  //结束
    
    return data;
}
//指定地址写寄存器
 void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
    MyI2C_Start();  ////起始位
    MyI2C_SendByte(MPU6050_WRITE_ADDR);  //从机地址+写
    MyI2C_ReceiveACK();  ////接收应答,这里有应答位可以判断从机有没有接收到数据
	
    MyI2C_SendByte(reg_addr);  // //用来指定我写的寄存器地址
    MyI2C_ReceiveACK();  //接收应答
    MyI2C_SendByte(data);  //写入的数据
    MyI2C_ReceiveACK();  //接收应答
    MyI2C_Stop();  //结束
}
 void MPU6050_Init(){
    MyI2C_Init();
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);        //关闭睡眠模式
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
    MPU6050_WriteReg(MPU6050_CONFIG,0x06);
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x00);
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x00);
    

}
 void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
	int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
){
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴的高8位
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴的高低位
	*AccX = (DataH << 8) | DataL;//高8位左移动8位,再或上低8位,因为最终赋值的变量是16位的,所以八位数据左移之后会进行类型转换

		DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

 2.MPU6050.H

#ifndef _MPU6050_H
#define _MPU6050_H
uint8_t MPU6050_ReadReg(uint8_t reg_addr);
 void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data);
 void MPU6050_Init();
 void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
	int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
);

#endif

MPU6050寄存器文件

1.MPU6050_Reg.h

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H
 
#define    MPU6050_SMPLRT_DIV        0x19
#define    MPU6050_CONFIG            0x1A
#define    MPU6050_GYRO_CONFIG        0x1B
#define    MPU6050_ACCEL_CONFIG    0x1C
 
#define    MPU6050_ACCEL_XOUT_H    0x3B
#define    MPU6050_ACCEL_XOUT_L    0x3C
#define    MPU6050_ACCEL_YOUT_H    0x3D
#define    MPU6050_ACCEL_YOUT_L    0x3E
#define    MPU6050_ACCEL_ZOUT_H    0x3F
#define    MPU6050_ACCEL_ZOUT_L    0x40
#define    MPU6050_TEMP_OUT_H        0x41
#define    MPU6050_TEMP_OUT_L        0x42
#define    MPU6050_GYRO_XOUT_H        0x43
#define    MPU6050_GYRO_XOUT_L        0x44
#define    MPU6050_GYRO_YOUT_H        0x45
#define    MPU6050_GYRO_YOUT_L        0x46
#define    MPU6050_GYRO_ZOUT_H        0x47
#define    MPU6050_GYRO_ZOUT_L        0x48
 
#define    MPU6050_PWR_MGMT_1        0x6B
#define    MPU6050_PWR_MGMT_2        0x6C
#define    MPU6050_WHO_AM_I        0x75
 
#endif
 

最后

我们想使用不同的方式来完成对加速度计和陀螺仪六个数据的读取

/*在C语言中,函数的返回值只有一个,我们想要返回多个数据,就是多返回值的设计
1.使用全局变量
2.使用指针,进行变量的地址传递,来实现多返回值
3.用结构体对多个变量进行打包,然后再统一进行传递
*/

使用指针的方式

MUC6050.c

void MPU6050_GetDate(int16_t *AccX,int16_t *AccZ,int16_t *AccY,
	int16_t *GyroX,int16_t *GyroZ,int16_t *GyroY
){
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取加速度寄存器X轴的高8位
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取加速度寄存器X轴的高低位
	*AccX = (DataH << 8) | DataL;//高8位左移动8位,再或上低8位,因为最终赋值的变量是16位的,所以八位数据左移之后会进行类型转换

		DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"  
#include "OLED.h"  
#include "USART.h" 
#include "MYI2C.h" 
#include "MPU6050.h" 
#include "LED.h" 
int16_t AX, AY, AZ, GX, GY, GZ;
uint8_t ID;
int main(void)
{
   Usart_Init();
   MyI2C_Init();
	MPU6050_Init();
 while(1)
 {
		MPU6050_GetDate(&AX, &AY, &AZ, &GX, &GY, &GZ);
	 printf("AX=%d,AY=%d,AZ=%d,GX=%d,GY=%d,GZ=%d\n",AX,AY,AZ,GX,GY,GZ);
	Delay_ms(200);

	
 }
}

使用结构体方式

1.MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h" 
#include "MYI2C.h" 
#include "stdlib.h"
#include "MPU6050.h"
#define MPU6050_I2C_ADDR    (0x68)
#define MPU6050_WRITE_ADDR    (((MPU6050_I2C_ADDR) << 1) | 0x00)  //写
#define MPU6050_READ_ADDR    (((MPU6050_I2C_ADDR) << 1) | 0x01)   //读
//指定地址读寄存器
extern MPU6050_DATA;
MPU6050_DATA pDATA;
uint8_t MPU6050_ReadReg(uint8_t reg_addr){
    uint8_t data;
    MyI2C_Start();  //起始位
    MyI2C_SendByte(MPU6050_WRITE_ADDR);  //从机地址+读
    MyI2C_ReceiveACK();  //接收应答,这里有应答位可以判断从机有没有接收到数据
	
		//寻址找到从机之后就可以发送下一个字节了
    MyI2C_SendByte(reg_addr);  //用来指定我写的地址,这个地址就写到从机的地址指针中了
	//从机接收到reg_addr之后,它的寄存器指针就指向了0x19这个位置
    MyI2C_ReceiveACK();  //接收应答
    
    MyI2C_Start();  //再来一个起始条件
    MyI2C_SendByte(MPU6050_READ_ADDR);  //再次指定设备的ID号,这次我们读
    MyI2C_ReceiveACK();  //主机接收应答
    data = MyI2C_ReceiveByte();  //指针指向的寄存器的数据
	MyI2C_SendACK(1);        //主机接收后,发送应答
    MyI2C_Stop();  //结束
    
    return data;
}
//指定地址写寄存器
 void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data){
    MyI2C_Start();  ////起始位
    MyI2C_SendByte(MPU6050_WRITE_ADDR);  //从机地址+写
    MyI2C_ReceiveACK();  ////接收应答,这里有应答位可以判断从机有没有接收到数据
	
    MyI2C_SendByte(reg_addr);  // //用来指定我写的寄存器地址
    MyI2C_ReceiveACK();  //接收应答
    MyI2C_SendByte(data);  //写入的数据
    MyI2C_ReceiveACK();  //接收应答
    MyI2C_Stop();  //结束
}
 void MPU6050_Init(){
    MyI2C_Init();
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);        //关闭睡眠模式
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
    MPU6050_WriteReg(MPU6050_CONFIG,0x06);
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x00);
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x00);
	  
}
 
// 读取MPU6050数据并存储在指定结构体中
void MPU6050_GetData(MPU6050_DATA* pDATA) {
    uint16_t dataH, dataL;
    // AccX
    dataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
    dataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
    pDATA->AccX = (dataH << 8) | dataL;
    // AccY
    dataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
    dataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
    pDATA->AccY = (dataH << 8) | dataL;
    // AccZ
    dataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
    dataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
    pDATA->AccZ = (dataH << 8) | dataL;
    // GyroX
    dataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
    dataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
    pDATA->GyroX = (dataH << 8) | dataL;
    // GyroY
    dataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
    dataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
    pDATA->GyroY = (dataH << 8) | dataL;
    // GyroZ
    dataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
    dataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
    pDATA->GyroZ = (dataH << 8) | dataL;
    // TEMP
    dataH = MPU6050_ReadReg(MPU6050_TEMP_OUT_H);
    dataL = MPU6050_ReadReg(MPU6050_TEMP_OUT_L);
    pDATA->Temp = (dataH << 8) | dataL;
}

2.MPU6050.h

#ifndef _MPU6050_H
#define _MPU6050_H
typedef struct{
    int16_t AccX,AccY,AccZ;
    int16_t GyroX,GyroY,GyroZ;
    int16_t Temp;
}MPU6050_DATA;
 void MPU6050_Init();
void MPU6050_WriteReg(uint8_t reg_addr,uint8_t data);
uint8_t MPU6050_ReadReg(uint8_t reg_addr);
void MPU6050_GetData(MPU6050_DATA *pDATA);
#endif

3.main.c

#include "Delay.h"  
#include "OLED.h"  
#include "USART.h" 
#include "MPU6050.h"
#include "LED.h" 
#include "stdio.h"
#include <stdio.h>
#include <math.h>
float ConvertTemperature(int16_t rawTemp) {
    return (rawTemp / 340.0f) + 36.53f;
}

// 函数用于将原始陀螺仪数据转换为角速度(度/秒)
float ConvertGyro(int16_t rawGyro) {
    return rawGyro / 131.0f;
}

// 函数用于将原始加速度数据转换为加速度(g)
float ConvertAcc(int16_t rawAcc) {
    return rawAcc / 16384.0f;
}
int main(void){
    MPU6050_Init();
	  Usart_Init();
    MPU6050_DATA mpuData;
    while (1){
			  MPU6050_GetData(&mpuData);
        // 转换并打印温度
        float temperature = ConvertTemperature(mpuData.Temp);
        printf("温度是:%.2f°C\n", temperature);

        // 转换并打印陀螺仪数据
        float gyroX = ConvertGyro(mpuData.GyroX);
        float gyroY = ConvertGyro(mpuData.GyroY);
        float gyroZ = ConvertGyro(mpuData.GyroZ);
        printf("GyroX=%.2f°/s, GyroY=%.2f°/s, GyroZ=%.2f°/s\n", gyroX, gyroY, gyroZ);

        // 转换并打印加速度数据
        float accX = ConvertAcc(mpuData.AccX);
        float accY = ConvertAcc(mpuData.AccY);
        float accZ = ConvertAcc(mpuData.AccZ);
        printf("AccX=%.2fg, AccY=%.2fg, AccZ=%.2fg\n", accX, accY, accZ);


        Delay_ms(100);
    }
}

数据显示结果

 测试代码

	/*指定地址写*/
	MyI2C_Start();//开始传输
	//起始之后,主机必须首先发送一个字节,内容是从机地址+读写位,进行寻址
	MyI2C_SendByte(0xD0);  //1101 000 0(mpu6050地址+读写位)
	//发送一个字节后,我们要接收一个应答位,看看从机有没有收到刚才的数据
	uint8_t Ack=MyI2C_ReceiveAck();//我们判断Ack的值就知道从机有没有给我们应答了
	
	//接收应答之后,我们要继续再发送一个字节,写入寄存器地址
	MyI2C_Stop();
	printf("Ack=%d\n",Ack);
	//测试读寄存器
	uint8_t ID;
	Usart_Init();
	MPU6050_Init();
//	who I am I寄存器,只读,地址0x75,默认是ID号,默认值就是0x60
	ID=MPU6050_ReadReg(0x75);//返回值是ID号的内容
	printf("ID=%x\n",ID);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程者也

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值