stm32入门-----软件I2C读写MPU6050

目录

前言

一、MPU6050 

1.简介

2.相关参数

3.硬件电路

 4.MPU6050框图

二、C编程实现I2C读写MPU6050步骤

1.MyI2C.c文件

(1)引脚的宏定义

(2)对SCL和SDA的操作以及初始化

(3)起始信号标志

(4)终止信号标志

(5)发送一个字节

(6)接收一个字节

(7)发送应答

 (8)接收应答

2.MPU6050.c文件

MPU6050_reg.h

(1)指定地址写

(2)指定地址读

(3)获取转换后的数据

(4)初始化

三、项目实践


前言

        上一期我发布了关于I2C通讯的原理(链接:stm32入门-----I2C通讯-CSDN博客),本期我们就来学习通过软件编程来去实现stm32与MPU6050之间的I2C读写操作(视频:[10-3] 软件I2C读写MPU6050_哔哩哔哩_bilibili

本期项目工程代码我已上传至百度网盘,可自行下载

链接: https://pan.baidu.com/s/1-HVrt5ROsxLqMWCnUgxXCw?pwd=0721

提取码: 0721 

一、MPU6050 

(MPU6050介绍视频:[10-2] MPU6050简介_哔哩哔哩_bilibili

1.简介

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

         下面第一张图是MPU6050元器件的芯片,上面讲到MPU6050是一种6轴姿态传感器,是可以去测量芯片自身X、Y、Z轴的加速度、角速度参数,那么我们对于加速度可以想象成第二张图这种形式,也就是中间放一个滑块,如果前后上下左右这六个面都接上一个弹簧电压传感器,然后去通过这个滑块的姿势不同的受力情况来去感应出电压的大小进而求出受力大小,其实这个就是我们高中学过的,然后通过F=ma,测出加速度a的值就行了。同样的对于角速度我们可以想象成下面第三张图的情况,就是中间有一个平衡环,当感应到旋转的时候也会感应出不同的电压进而得到角速度的值。但是实际上MPU6050中间并没有图二和图三这种东西,这只不过是一种用来理解的模型,其内部是通过自身电路去实现这些模型的。

2.相关参数

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

  • I2C从机地址:1101000AD0=0
  •   1101001AD0=1

3.硬件电路

引脚

功能

VCCGND

电源

SCLSDA

I2C通信引脚

XCLXDA

主机I2C通信引脚

AD0

从机地址最低位

INT

中断信号输出

 4.MPU6050框图

下面从这个框图就可以看出,MPU6050这个芯片去测量加速度和角速度都是去通过ADC模数转换器来去实现的。

二、C编程实现I2C读写MPU6050步骤

如果相关原理不熟悉的可以去看看上一期的内容:stm32入门-----I2C通讯-CSDN博客

工程主要文件如下,其中MyI2C.c 和MyI2C.h 文件是用来编写I2C接口读取功能的,MPU6050的文件是用来封装前面写好的I2C模块来去读写MPU6050寄存器的操作。

下面我就对这些模块进行一一讲解。

1.MyI2C.c文件

对于这个文件我们需要实现6个功能,实际上就是我们上一期讲到的I2C通讯的六大功能,分别是开始、停止、发送、接受、发送应答、接收应答。

(1)引脚的宏定义

这里我们可以去根据接线图来去宏定义SDA与SCL的引脚,通过宏定义可以去提高代码的修改复用效率,到时候如果想修改为其他引脚,我们只需要去改这个宏定义对应的引脚就行了。下面我是定义到pin6 和pin7的引脚,跟接线图的是不一样的,注意一下。

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

#define SDA_PORT         GPIOA
#define SCL_PORT         GPIOA
#define SCL_PIN         GPIO_Pin_6
#define SDA_PIN         GPIO_Pin_7
(2)对SCL和SDA的操作以及初始化

下面我就去通过函数的形式去对GPIO口读写SCL和SDA的操作进行封装,到时候直接去拿出来使用就行了,就不需要用GPIO口的函数读写。

//对SCL和SDA进行操作
void I2C_W_SCL(uint8_t val) { //写入SCL
    GPIO_WriteBit(SCL_PORT, SCL_PIN, (BitAction)val);
    Delay_us(10);
}
void I2C_W_SDA(uint8_t val) { //写SDA
    GPIO_WriteBit(SDA_PORT, SDA_PIN, (BitAction)val);
    Delay_us(10);
}
uint8_t I2C_R_SDA() { //读取SDA
    uint8_t value;  
    value = GPIO_ReadInputDataBit(SDA_PORT, SDA_PIN);
    Delay_us(10);
    return value;
}

//初始化
void I2C_init() {
    //配置SCL和SDA 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_Out_OD; //开漏输出
	GPIO_initstruct.GPIO_Pin=SCL_PIN | SDA_PIN;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
	
    GPIO_Init(GPIOA, &GPIO_initstruct);
    GPIO_SetBits(GPIOA, SCL_PIN | SDA_PIN);

}
(3)起始信号标志
起始条件: SCL 高电平期间, SDA 从高电平切换到低电平

// 1.开始
void I2C_Start() {
    //这里需要先SDA再放手SCL,因为避免SDA在SCL高电平的时候变化,这是结束的意思
    I2C_W_SDA(1);
    I2C_W_SCL(1);
    //先拉低SDA
    I2C_W_SDA(0);
    I2C_W_SCL(0);
}
(4)终止信号标志
        终止条件:SCL高电平期间,SDA从低电平切换到高电平

//2.终止
void I2C_Stop() {
    I2C_W_SDA(0);
    I2C_W_SCL(1);
    I2C_W_SDA(1);
}
(5)发送一个字节
发送一个字节: SCL 低电平期间,主机将数据位依次放到 SDA 线上(高位先行),然后释放 SCL ,从机将在 SCL 高电平期间读取数据位,所以 SCL 高电平期间 SDA 不允许有数据变化,依次循环上述过程 8 次,即可发送一个字节

// 发送一个字节
void I2C_sendbyte(uint8_t byte) {
    uint8_t i;
    for (i = 0;i < 8;i++) {
         //在SCL低电平期间给SDA放入这个字节的一位,高位先放
        I2C_W_SDA(byte & (0x80 >> i));//按位与1000 0000
        //下面这里走一个SCL时钟,从机就会把上面放入的数据读取走
        I2C_W_SCL(1);
        I2C_W_SCL(0);
    }
}
(6)接收一个字节
接收一个字节: SCL 低电平期间,从机将数据位依次放到 SDA 线上(高位先行),然后释放 SCL ,主机将在 SCL 高电平期间读取数据位,所以 SCL 高电平期间 SDA 不允许有数据变化,依次循环上述过程 8 次,即可接收一个字节(主机在接收之前,需要释放 SDA

//接受一个字节
uint8_t I2C_recivebyte() {
    uint8_t result = 0x00;//存储读取到的结果
    uint8_t i;
    I2C_W_SDA(1);//主机释放SDA,这时候从机就会拿到SDA放入数据
    for (i = 0;i < 8;i++) {
        I2C_W_SCL(1);//SCL为高电平,下面主机就可以开始读取操作了
        if (I2C_R_SDA() == 1) {//如果读取到最高位为1
            result |= (0x80 >> i);//此时获取到的最高位置1
        }
        I2C_W_SCL(0);//读取结束
    }
    return result;
}
(7)发送应答
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据 0 表示应答,数据1表示非应答

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

//接收应答
uint8_t I2C_reciveack() {
    uint8_t ackbit;
    I2C_W_SDA(1);//主机释放SDA,这时候从机就会拿到SDA放入数据
    I2C_W_SCL(1);//SCL为高电平,下面主机就可以开始读取操作了
    ackbit = I2C_R_SDA(); //读取到了0就表示接受到了应答OK
    I2C_W_SCL(0);//读取结束
    return ackbit;
}

2.MPU6050.c文件

MPU6050_reg.h

这里还有一个MPU6050_reg.h的文件,这个文件是用来定义好MPU6050芯片相关寄存器地址的头文件。如下所示:

#ifndef __MPU6050_REG_h
#define __MPU6050_REG_h

//这里存放的是MPU6050的寄存器地址
#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 // !__MPU6050_REG_h
(1)指定地址写
指定地址写
对于指定设备( Slave Address ),在指定地址( Reg Address )下,写入指定数据( Data

这里我们就对应着上面这个时序来去编程。 

//指定地址写
void MPU6050_writereg(uint8_t regaddress, uint8_t data) {
    I2C_Start();
    I2C_sendbyte(MPU6050_ADDRESS); 
    I2C_reciveack(); //返回值这边就不做处理了,这里就直接去进行数据的收发
    I2C_sendbyte(regaddress);
    I2C_reciveack();
    I2C_sendbyte(data);
    I2C_reciveack();
    I2C_Stop();
}
(2)指定地址读
指定地址读
对于指定设备( Slave Address ),在指定地址( Reg Address )下,读取从机数据( Data

//指定地址读
uint8_t MPU6050_readreg(uint8_t regaddress) {
    uint8_t result;
    //当前指针指向这个地址
    I2C_Start();
    I2C_sendbyte(MPU6050_ADDRESS); 
    I2C_reciveack();
    I2C_sendbyte(regaddress);
    I2C_reciveack();
    
    //转入读的时序
    I2C_Start();
    I2C_sendbyte(MPU6050_ADDRESS | 0x01); //MPU6050_ADDRESS默认是写,这里要或上0x01表示读取操作
    I2C_reciveack();
    result = I2C_recivebyte(); //获取到接收的数据
    I2C_sendack(1); //读取完成之后要给从机回复,如果回复0那么就表示从机继续发送数据,如果回复1就表示读取结束,主机拿回SDA控制权
    I2C_Stop();
    return result;
}
(3)获取转换后的数据

这里是通过传入数据的地址来去存储数据,这种操作学过C语音的都很常见的了。

//获取MPU6050转换出加速度和角速度的值
void MPU6050_getdata(int16_t* accX, int16_t* accY, int16_t* accZ,
                    int16_t* gyroX, int16_t* gyroY, int16_t* gyroZ)
{
    uint16_t dataH, dataL;
    //加速度值数据
    dataH = MPU6050_readreg(MPU6050_ACCEL_XOUT_H);
    dataL = MPU6050_readreg(MPU6050_ACCEL_XOUT_L);
    *accX = (dataH << 8) | dataL;

    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;
}
(4)初始化
//初始化
void MPU6050_init() {
    I2C_init();
    MPU6050_writereg(MPU6050_PWR_MGMT_1, 0x01); //电源管理器1
    MPU6050_writereg(MPU6050_PWR_MGMT_2, 0x00); //电源管理器2
    MPU6050_writereg(MPU6050_SMPLRT_DIV, 0x09); //分频器
    MPU6050_writereg(MPU6050_CONFIG, 0x06); //配置寄存器
    MPU6050_writereg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器
    MPU6050_writereg(MPU6050_ACCEL_CONFIG, 0x18);//加速度计配置寄存器
}

//获取当前的ID号,也就是MPU6050的I2C地址
uint8_t MPU6050_getID() {
    return MPU6050_readreg(MPU6050_WHO_AM_I);
}

三、项目实践

上面讲解了相关模块的功能,那么这里我们就可以去写一个主函数来去进行对MPU6050读取操作了。

 接线图:

main.c文件: 

#include "stm32f10x.h"                  // Device header
#include "Delay.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);

	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);
	}
}

实验效果:

 

以上就是本期的全部内容了,我们下次见!

今日壁纸:

  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: STM32F407是一款强大的微控制器,可以通过软件模拟I2C总线来读写MPU6050传感器。首先,我们需要设置相关的GPIO引脚来模拟I2C的时钟(SCL)和数据线(SDA)。然后,我们可以通过相应的软件算法来模拟I2C的时序,并使用GPIO引脚来模拟信号的传输。 在进行软件模拟I2C读写MPU6050之前,我们需要先了解MPU6050的寄存器结构和通讯协议。MPU6050内部有多个寄存器,它们存储着各种传感器的原始数据和配置信息。通讯协议使用I2C,我们需要根据MPU6050的地址和寄存器地址来发送读写命令。 首先,发送开始信号。通过GPIO引脚模拟SCL和SDA,在SCL为高电平,SDA从高电平转为低电平时即可认为发送了开始信号。然后,发送MPU6050的设备地址,该地址为7位,由3位固定的值和一个可选择的读写位组成。接下来,发送要访问的寄存器地址,该地址也为7位。再发送读写命令位,0表示写入,1表示读取。 接下来,我们可以进行读写操作。对于写操作,我们发送要写入的数据到寄存器中。对于读操作,我们需要发送重复启动信号,然后再发送MPU6050的设备地址和寄存器地址,并设置读取命令位。此时,我们将把数据读取到SDA引脚上。 最后,发送停止信号。通过GPIO引脚模拟SCL和SDA,在SCL为高电平时将SDA从低电平转为高电平即可认为发送了停止信号。 以上是对于STM32F407软件模拟I2C读写MPU6050传感器的简要步骤介绍。具体实现过程中,需要根据实际需求进行相应的编码和调试。 ### 回答2: stm32f407是一款高性能的ARM Cortex-M4微控制器,可以通过软件模拟I2C总线来读写MPU6050传感器。 首先,我们需要了解MPU6050传感器的通信协议和寄存器地址。MPU6050使用I2C总线进行通信,需要通过开始信号、设备地址、寄存器地址、数据等步骤完成数据的读写。 在stm32f407上,我们可以使用GPIO引脚来模拟I2C总线的SCL(时钟线)和SDA(数据线)。我们需要配置SCL和SDA引脚为GPIO模式,并在代码中编写相应的功能实现软件模拟I2C总线的读写操作。 对于I2C总线的读操作,首先我们需要发送开始信号,然后发送设备地址加上写命令,接着发送寄存器地址,再发送一个重新开始信号,然后发送设备地址加上读命令,最后读取接收到的数据。 对于I2C总线的写操作,首先也是发送开始信号,然后发送设备地址加上写命令,接着发送寄存器地址,最后发送要写入的数据。 在代码中,我们可以按上述过程编写相应的函数来进行软件模拟的I2C读写操作,并将读取到的数据存储到相应的变量中。 需要注意的是,软件模拟I2C通信可能会导致一定的时序不准确。为了确保通信的稳定性和可靠性,我们可以使用延时函数来控制时序,并根据MPU6050传感器的参数手册调整延时时间。 总之,通过在stm32f407上进行软件模拟的I2C读写操作,我们可以实现对MPU6050传感器的数据读取和写入,从而实现与该传感器的通信和控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Fitz&

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

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

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

打赏作者

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

抵扣说明:

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

余额充值