STM32_10(I2C)

I2C通信

  • I2C(Inter IC Bus)是由Philips公司开发的一种通用数据总线
  • 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
  • 同步,半双工
  • 带数据应答
  • 支持总线挂载多设备(一主多从、多主多从)
  • 使用同步时序可以极大降低单片机对硬件电路的依赖

 

I2C硬件电路

  • 所有I2C设备的SCL连在一起,SDA连在一起
  • 设备的SCL和SDA均要配置成开漏输出模式
  • SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

任何时候都是主机完全掌握SCL,在空闲状态下,主机可以主动发起对SDA的控制。只有从机在发送数据和从机应答的时候,主机才会把SDA的控制权交给从机。

把SDA和SCL想象成一根杆子,并且让所有人都不能往上拉,只能往下拉或松手,之后我们外置一根弹簧到这根杆子上,输出低电平就往下拽,输出高电平就放手,这是一个弱上拉的高电平,完全不影响数据传输。

 

I2C时序基本单元

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

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

 

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

 

起始条件后,第一个字节必须为主机发送。如果主机想发送0,则让拉低SDA到低电平。如果发送1,则放手让SDA回弹到高低电平。在SCL低电平期间,允许改变SDA电平,在SCL高电平期间,不允许改变SDA的电平,并且是从机读取SDA的时候,从机必须尽快读取SDA,一般在上升沿时刻就读取完数据。主机放手SCL一段时间后,继续拉低SCL传输下一位,主机也需要在SCL下降沿之后尽快把数据放在SDA上,但主机掌握着时钟的主导权,所以在低电平的任意时刻把数据放在SDA上即可。

 

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

实线是主机控制部分,虚线是从机控制部分。

 

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

主机释放SDA的时候,从机就应该把SDA拉下来,在SCL高电平期间,主机读取应答位,应答位为0,说明从机确实收到了。

发送应答位目的是告诉从机,是不是要继续发,如果从机发送一个数据后,得到主机的应答,说明要继续发送,如果主机没有应答则从机停止发送,交出SDA的控制权。

 

I2C时序

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

这个数据帧的目的式在指定从机地址1101000的设备,在其内部0x19地址的寄存器下,写入0xAA数据。

在起始条件后,紧跟着的时序必须是发送一个字节的时序,字节的内容必须是从机地址+读写位。从机地址为7位,读写位为1位,加起来正好8位。发送从机地址就是确定通信的对象,发送读写位就是确定接下来是写入还是读出。紧跟着的单元式接受从机的应答位。

 

指定地址读;对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)。

在Sr前面就是指定地址写,后面就是当前地址读。

先起始写入地址,停止,因为写入的地址会存在地址指针里,所以这个地址不会因为时序停止而消失,我们可以再起始,读当前位置,停止。

 

I2C外设

  • STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
  • 支持多主机模型
  • 支持7位/10位地址模式
  • 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
  • 支持DMA
  • 兼容SMBus协议
  • STM32F103C8T6 硬件I2C资源:I2C1、I2C2

 

I2C框图

SDA的核心部分就是数据寄存器和数据移位寄存器。

发送流程:当需要发送数据时,可以把一个字节数据写到数据寄存器DR中,当移位寄存器没有数据移位时,数据寄存器的值就会进一步转到移位寄存器里。在移位的过程中,可以把下一个数据放在数据寄存器等着,一旦前一个数据移位完成,下个数据就可以无缝衔接,继续发送。当数据寄存器转到移位寄存器时,就会置状态寄存器TXE位为1(表示发送寄存器为空)。

接收流程:输入的数据移一位一位传输到移位寄存器,当一个字节数据接收完毕,数据整体从移位寄存器转到数据寄存器,同时置标志位RXNE(表示接收寄存器为非空)。

 

I2C外设基本结构

首先移位寄存器和数据寄存器DR的配合是通信的核心部分,因为I2C是高位先行,所以移位寄存器是向左移位,在发送的时候,最高位先移出去,然后是次高位等等。一个SCL时钟移位一次,移位8次就可以把一个字节,由高位到低位一次放到SDA线上。

发送的时候:数据先写入数据寄存器,如果移位寄存器没有数据,再转到移位寄存器进行发送。

接收的时候:数据通过GPIO口从右边依次移进来,最终移8次,一个字节就接收完成了。

使用硬件I2C的时候,需要把GPIO口配置成复用开漏输出模式(复用:GPIO状态是交由片上外设来控制)。

 

主机发送

7位主发送:起始条件后的一个字节是寻址。

10位主发送:起始条件后的两个字节是寻址。

EVX:组合多个标志位的大标志位。

7位主发送流程:当检测(EV5)起始条件已发送后,就可以发送一个字节的从机地址,从机地址需要写到数据寄存器DR中,写入DR之后,硬件电路会自动把这个字节转到移位寄存器里,再把这个字节发送到I2C总线上,之后硬件会自动接收应答并判断,如果没有应答就会置失败的标志位(这个标志位可以利用中断来提醒)。当寻址完成后会发生EV6事件,接下来发生EV8_1事件,一旦写入DR后,DR会立刻转移到移位寄存器进行发送,这时就是EV8事件,这时就是移位寄存器正在发送数据的状态。EV8结束时,数据2写入到数据寄存器等着,接收应答位之后,数据2就转入移位寄存器进行发送。一旦检测到EV8事件,就可以写入下一个数据。最后,当我们想发的数据写完之后,就没有新数据写入数据寄存器中。当移位寄存器当前数据移位完成时,此时是移位寄存器空,数据寄存器也空的状态,这就是EV8_2事件。

 

主机接收

首先写入控制寄存器的START位,产生起始条件,等待EV5事件,之后是寻址,接收应答(A),结束后产生EV6事件,数据1代表正在通过移位寄存器进行输入。当这个时序单元结束后,说明移位寄存器已经成功移入一个字节的数据1,移入的一个字节整体转移到数据寄存器。同时置RxNE标志位,表示数据寄存器非空,也就是收到一个字节的数据,这就是EV7事件。当不需要接收时,需要在最后一个时序单元发生时,提前把应答位控制的寄存器Ack置0,并且设置终止条件请求,这就是EV7_1事件。在时序完成之后,由于之前设置了Ack=0,所以这里会给出非应答。由于设置了STOP位,最后产生终止条件。

 

MPU6050

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

 

MUP6050参数

  • 16位ADC采集传感器的模拟信号,量化范围:-32768~32767
  • 加速度计满量程选择:±2、±4、±8、±16(g)(如果测量的物体运动非常剧烈,可以把满量程选择大一些。反之同理)
  • 陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)(满量程选得越小,测量分辨率越高)
  • 可配置的数字低通滤波器
  • 可配置的时钟源
  • 可配置的采样分频
  • I2C从机地址:1101000(AD0=0) 1101001(AD0=1)

如果认为0x68是从地址,在发送第一个字节需要把0x68左移一位,再按位或上读写位。  

把0x68左移一位后的数据当作从机地址,左移一位是0xD0。在实际发送第一个字节的时候,如果需要写就直接把0xD0当作第一个字节,如果需要读则把0xD0或0x01,即0xD1当作第一个字节。这种操作方式是读写位融入到从机地址中。0xD0是写地址,0xD1是读地址。

 

MPU6050硬件电路

   

XDA和XCL通常用于外接磁力计或气压计,当接上之后,MPU6050主机接口可以直接访问扩展芯片的数据。

 

MPU6050框图

Self test(自测模块):先使能Self test测得X Accel数据,再失能Self test测X Acce数据,测出来的数据两个进行相减,再根据手册里面数据进行对比,看是否在这个区间内,在能正常使用。

Interrupt Status Register(中断状态寄存器):控制内部事件到中断引脚的输出。

FIFO(先入先出寄存器):对数据流进行缓存。

Config Registers(配置寄存器):对内部的各个电路进行配置。

Sensor Registers(传感器寄存器或数据寄存器):存储各个传感器的数据。

Serial Interface Bypass Mux(接口旁路选择器):如果拨到上面,辅助的I2C引脚和正常的I2C引脚接在一起,两路总线结合在一起,STM32能控制所有设备。如果拨到下面辅助的I2C引脚就由MPU6050控制,这时STM32是MPU6050大哥,MPU6050是扩展芯片的大哥。

 

代码部分

I2C软件配置代码

#include "Bsp_I2C.h"   
#include "Delay.h" 

/* SCL写功能 */
void Bsp_I2C_W_SCL(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
    Delay_us(10);
}

/* SDA写功能 */
void Bsp_I2C_W_SDA(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
    Delay_us(10);
}

/* SDA读功能 */
uint8_t Bsp_I2C_R_SDA(void)
{
    uint8_t BitValue;
    BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
    Delay_us(10);
    return BitValue;
}

/* I2C初始化*/
void Bsp_I2C_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);
}

/* I2C起始条件和重复起始条件(先让SDA置低电平,之后让SCL置低电平。为什么要先让他们置高电平,是因为防止之后起始的时候SCL先置低电平) */
void Bsp_I2C_Strat(void)
{
    Bsp_I2C_W_SDA(1);
    Bsp_I2C_W_SCL(1);
    Bsp_I2C_W_SDA(0);
    Bsp_I2C_W_SCL(0);
}     

/* I2C终止条件 (为什么要先让SDA置低电平,因为SDA不一定是低电平))*/
void Bsp_I2C_Stop(void)
{
    Bsp_I2C_W_SDA(0);
    Bsp_I2C_W_SCL(1);
    Bsp_I2C_W_SDA(1);
}

/* I2C发送数据 */
void Bsp_I2C_SendByte(uint8_t Byte)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_I2C_W_SDA(Byte & (0x80 >> i));             // 只与最高位,最终结果为0x80、0x40...,为什么最后写进去的是0或1呢,因为这个强整型(BitAction)
        Bsp_I2C_W_SCL(1);
        Bsp_I2C_W_SCL(0);
    }
}

/* I2C接收数据 */
uint8_t Bsp_I2C_ReceiveByte(void)
{
    uint8_t Byte = 0x00;

    Bsp_I2C_W_SDA(1);
    for (uint8_t i = 0; i < 8; i++)
    {
        Bsp_I2C_W_SCL(1); 
        if (Bsp_I2C_R_SDA() == 1)
        {
            Byte |= (0x80 >> i);
        }
        Bsp_I2C_W_SCL(0); 
    }
    return Byte;
}

/* I2C发送应答 */
void Bsp_I2C_WaitSendAck(uint8_t AckBit)
{
    Bsp_I2C_W_SDA(AckBit);
    Bsp_I2C_W_SCL(1);
    Bsp_I2C_W_SCL(0);
}

/* I2C接收应答 */
uint8_t Bsp_I2C_WaitReceiveAck(void)
{
    uint8_t AckBit;

    Bsp_I2C_W_SDA(1);
    Bsp_I2C_W_SCL(1);
    AckBit = Bsp_I2C_R_SDA();
    Bsp_I2C_W_SCL(0);
    
    return AckBit;
}

 

MPU6050寄存器配置代码

#ifndef __BSP_MPU6050_REG_H
#define __BSP_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配置代码

Bsp_MPU6050.h代码

#ifndef __BSP_MPU6050_H
#define __BSP_MPU6050_H

#include "stm32f10x.h"
#include "Bsp_MPU6050_Reg.h"
#include "Bsp_I2C.h"

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_GetData(void);
uint8_t MPU6050_IDGet(void);
void MPU6050_Init(void);

typedef struct
{
    int16_t AccX;
    int16_t AccY;
    int16_t AccZ;
    int16_t GyroX;
    int16_t GyroY;
    int16_t GyroZ;
}MPU6050_Data;

#endif

 

Bsp_MPU6050.c代码

#include "Bsp_MPU6050.h"

#define MPU6050_Address 0xD0                                // 定义MPU6050地址

MPU6050_Data Data;

/* MPU6050的I2C写数据 */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
    Bsp_I2C_Strat();
    Bsp_I2C_SendByte(MPU6050_Address);                      // 发送从机地址
    Bsp_I2C_WaitReceiveAck();                               // 等待接收应答
    Bsp_I2C_SendByte(RegAddress);                           // 发送寄存器地址
    Bsp_I2C_WaitReceiveAck();
    Bsp_I2C_SendByte(Data);                                 // 发送数据(这里还可以写for循环写入多个数据)
    Bsp_I2C_WaitReceiveAck();
    Bsp_I2C_Stop();
}

/* MPU6050的I2C读数据 */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
    uint8_t Data;

    Bsp_I2C_Strat();
    Bsp_I2C_SendByte(MPU6050_Address);
    Bsp_I2C_WaitReceiveAck(); 
    Bsp_I2C_SendByte(RegAddress);
    Bsp_I2C_WaitReceiveAck();

    Bsp_I2C_Strat();
    Bsp_I2C_SendByte(MPU6050_Address | 0x01);               // 改为读
    Bsp_I2C_WaitReceiveAck();
    Data = Bsp_I2C_ReceiveByte();                           // 这里可以写for循环读多个数据,但完成前的所有从机给 应答 都给0,最后一个从机给 非应答 给1
    Bsp_I2C_WaitSendAck(1);                                 // 0:给从机应答         1:不给从机应答
    Bsp_I2C_Stop();

    return Data;
}

/* MPU6050初始化, 配置完成后,陀螺仪内部就在不断进行数据转换,输出的数据就在数据寄存器里,如果想获取数据,读取相应的寄存器就即可 */
void MPU6050_Init(void)
{
    Bsp_I2C_Init();
    
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);             // 解除睡眠,选择陀螺仪时钟
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);             // 6个轴均不待机
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);             // 10分频
    MPU6050_WriteReg(MPU6050_CONFIG, 0x06);                 // 滤波参数选择(这里选择最大)
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);            // 陀螺仪量程选择(这里选择最大量程)
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);           // 加速度量程选择(这里选择最大量程)
}

/* 获取MPU6050数据 */
void MPU6050_GetData(void)
{
    uint8_t DataH, DataL;                                   // 定义数据高位和低位
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);          // 读MPU6050_ACCEL_XOUT_H寄存器的高八位
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);          // 读MPU6050_ACCEL_XOUT_L寄存器的低八位
    Data.AccX = (DataH << 8) | DataL;                       // 高八位左移八位再与上第八位,就可以得到16位数据

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
    Data.AccY = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
    Data.AccZ = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
    Data.GyroX = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
    Data.GyroY = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
    Data.GyroZ = (DataH << 8) | DataL;
}

/* 获取ID */
uint8_t MPU6050_IDGet(void)
{
    return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

 

I2C硬件配置代码

#include "Bsp_MPU6050.h"

#define MPU6050_Address 0xD0                                // 定义MPU6050地址

MPU6050_Data Data;

/* 对I2C_CheakEvent进行封装 加入了超时退出机制 */
void MPU6050_WaitCheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
    uint32_t TimeOut;
    while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
    {
        TimeOut --;
        if (TimeOut == 0)
        {
            break;
        } 
    }
}

/* MPU6050的I2C写数据 */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
    I2C_GenerateSTART(I2C2, ENABLE);                                                       // 起始
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);                            // 等待EV5事件

    I2C_Send7bitAddress(I2C2, MPU6050_Address, I2C_Direction_Transmitter);                 // 发送地址
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);              // 等待EV6事件                

    I2C_SendData(I2C2, RegAddress);
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);                      // 等待EV8事件
    
    I2C_SendData(I2C2, Data);           
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);                       // 等待EV8_2事件,因为到这里就结束了

    I2C_GenerateSTOP(I2C2, ENABLE);                                                        // 终止
}
/* 在程序中,大量的死循环等待非常危险,一旦一个环节没有产生,则会产生死循环。所以对这种情况可以加入超时退出机制 */
/* MPU6050的I2C读数据 */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
    uint8_t Data;

    I2C_GenerateSTART(I2C2, ENABLE);
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);                            // 等待EV5事件

    I2C_Send7bitAddress(I2C2, MPU6050_Address, I2C_Direction_Transmitter);                 // 从机地址:发送模式
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);              // 等待EV6事件

    I2C_SendData(I2C2, RegAddress);
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);                       // 等待EV8事件

    // 重新启动
    I2C_GenerateSTART(I2C2, ENABLE);
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);                            // 等待EV5事件

    I2C_Send7bitAddress(I2C2, MPU6050_Address, I2C_Direction_Receiver);                    // 从机地址:接收模式
    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);                 // 等待EV6事件

    I2C_AcknowledgeConfig(I2C2, DISABLE);                                                  // 在接收最后一个字节之前,需要临时把ACK置0。
    I2C_GenerateSTOP(I2C2, ENABLE);

    MPU6050_WaitCheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);                          // 等待EV7_1事件     因为这里只读取了一个字节,所以就要立刻把Ack置0,STOP置1
    Data = I2C_ReceiveData(I2C2);                                                          // 如果需要指定多个地址,那么需要在47-51加for循环,并在最后一个字节的时候,利用if加入47和48行代码。
    
    I2C_AcknowledgeConfig(I2C2, ENABLE);
    
    return Data;
}

/* MPU6050初始化, 配置完成后,陀螺仪内部就在不断进行数据转换,输出的数据就在数据寄存器里,如果想获取数据,读取相应的寄存器就即可 */
void MPU6050_Init(void)
{
    // Bsp_I2C_Init();
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitTypeDef GPIO_InitStrcuture;
    GPIO_InitStrcuture.GPIO_Mode = GPIO_Mode_AF_OD;                             // 引脚模式为复用开漏输出
    GPIO_InitStrcuture.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_10;
    GPIO_InitStrcuture.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStrcuture);

    I2C_InitTypeDef I2C_InitStructure;
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;                                 // 开启应答位
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;   // 选择7位地址还是确认10位地址,这里选择7位地址
    I2C_InitStructure.I2C_ClockSpeed = 100000;                                  // 通讯速度,这里选择标准哦通信速度100KHZ
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;                          // I2C快速模式占空比
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;                                  // 选择I2C模式
    I2C_InitStructure.I2C_OwnAddress1 = 0x00;                                   // 设备自身地址(如果选择7位地址就要写自身的7位地址,10位同理)
    I2C_Init(I2C2, &I2C_InitStructure);

    I2C_Cmd(I2C2, ENABLE);
    
    MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);             // 解除睡眠,选择陀螺仪时钟
    MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);             // 6个轴均不待机
    MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);             // 10分频
    MPU6050_WriteReg(MPU6050_CONFIG, 0x06);                 // 滤波参数选择(这里选择最大)
    MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);            // 陀螺仪量程选择(这里选择最大量程)
    MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);           // 加速度量程选择(这里选择最大量程)

}

/* 获取MPU6050数据 */
void MPU6050_GetData(void)
{
    uint8_t DataH, DataL;                                   // 定义数据高位和低位
    
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);          // 读MPU6050_ACCEL_XOUT_H寄存器的高八位
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);          // 读MPU6050_ACCEL_XOUT_L寄存器的低八位
    Data.AccX = (DataH << 8) | DataL;                       // 高八位左移八位再与上第八位,就可以得到16位数据

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
    Data.AccY = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
    Data.AccZ = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
    Data.GyroX = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
    Data.GyroY = (DataH << 8) | DataL;

    DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
    DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
    Data.GyroZ = (DataH << 8) | DataL;
}

/* 获取ID */
uint8_t MPU6050_IDGet(void)
{
    return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

本文作者:烟儿公主

本文链接:https://www.cnblogs.com/toutiegongzhu/p/17403919.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   烟儿公主  阅读(175)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 夏日大冒险 暴躁的兔子
夏日大冒险 - 暴躁的兔子
00:00 / 00:00
An audio error has occurred.

作词 : 暴躁的兔子

作曲 : 暴躁的兔子

编曲 : IOF

混音:Gfanfan

出品:网易飓风

夏天 不要再浪费时间

实现 你承诺过的改变

别再 找一堆借口拖延

现在就和我一起飞向海边

人生苦短 你应该学会如何作乐

低着头还怎么应对挫折

人应该为自己活着

不用去迎合

要去寻欢作乐

撮合我的浪漫和悲欢

把这荒诞人生都塞满

生活难免磕磕绊绊

对抗生活的平庸就是浪漫

学会取悦自己逆风翻盘

去反抗变态的三观

把条条框框都砸烂

建立新的规则推翻谈判

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

去冒险

我真的不care你是否会喜欢我

不跟风被定义的美 全都是灾祸

我才不讨好大多数绝不与示弱

过好你的生活

你管我应该怎么快活

没有人能有资格审判

别人的生活和牵绊

快闭上你的高谈阔论

乘风破浪吧 理想的风帆

我就是肆意张扬又如何

我就是锋芒毕露又如何

我就是离经叛道又如何

我就是要出格 你管我要如何

我就是与众不同又如何

我就是特立独行又如何

我就是不知好歹又如何

你管我怎样出格 你管我如何

无可救药的人呐

和我一起去海边

看那日出和晚霞 海天一线

看阳光穿越地平线

现实交织的明天

就在这个夏天

为自己改变

别怕山高路远

不知进退的人呐

和我一起去海边

聊聊曾经的理想 一起想当年

那曾想改变世界的人

是否还满腔热忱

不羁的我们放肆着

反抗那命运的指针

解放灵魂

推广:网易飓风

企划:贾焱祺

监制:徐思灵

出品人:谢奇笛