STM32_10(I2C)

news2024/11/25 4:32:05

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1265370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elasticsearch 线上实战问题及解决方案探讨

1、reindex相关问题 1.1 问题描述 我有 1tb 的一个大索引若干&#xff0c;要迁移到另外一个新集群去&#xff0c;有没有好办法&#xff1f;reindex好像会中断...... reindex 是不是就算设置了频率也会莫名的中断&#xff0c;而且没地方查到错误&#xff1f;1000多万的数据&…

盖茨表示GPT-5不会比GPT-4有太大改进;Intro to Large Language Models

&#x1f989; AI新闻 &#x1f680; 盖茨表示GPT-5不会比GPT-4有太大改进 摘要&#xff1a;比尔盖茨在与德国《商报》的采访中透露&#xff0c;虽然OpenAI内部有人相信GPT-5会优于GPT-4&#xff0c;但他认为目前的生成式人工智能已经达到极限。盖茨对GPT-5未来的发展并不乐观…

视频集中存储/磁盘阵列EasyCVR平台黑名单异常解决步骤是什么?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、…

七、Lua字符串

文章目录 一、字符串&#xff08;一&#xff09;单引号间的一串字符&#xff08;二&#xff09;local str "Hello, "&#xff08;三&#xff09;[[ 与 ]] 间的一串字符&#xff08;四&#xff09;例子 二、字符串长度计算&#xff08;一&#xff09;string.len&…

nginx 配置跨域(小皮面板)

本地开发的时候&#xff0c;前端请求后端&#xff0c;后端不能用域名请求&#xff0c;只能用端口模式&#xff0c;在小皮面板的话就是如下配置&#xff1a; 我的测试项目部署&#xff1a; 前端&#xff1a;http://localhost:8082 后端&#xff1a;http://localhost:8081 前端…

与珎同行录-开篇-231129

与珎同行录-开篇 珎就是对陪伴并帮助我写代码的AI的昵称 能不能读懂这个绕口令问题呢? 连续的椎体的相邻椎体质心的相邻质心的质心作为当前质心所在的椎体的质心, 该质心的方向代表该椎体的上下方向 如何代码实现呢? 还是没看懂…好吧最终的算法是:

vue建立组件无校验版

实现功能&#xff1a; 切换&#xff0c;相当于tab 1、非组件代码&#xff1a; <template><div><div class"tabStyle"><div v-for"(item,index) in tabTitle" :key"index" class"bordItemStyle" :class"c…

类指针压缩空间

一、类指针压缩介绍 压缩指针&#xff0c;指的是在 64 位的机器上&#xff0c;使用 32 位的指针来访问数据&#xff08;堆中的对象或 Metaspace 中的元数据&#xff09;的一种方式。 对象头中的 Class Pointer 默认占 8 个字节&#xff0c;开启 -XX:UseCompressedOops 后&…

git的创建以及使用

1、上传本地仓库 首先确定项目根目录中没有.git文件&#xff0c;有的话就删了&#xff0c;没有就下一步。在终端中输入git init命令。注意必须是根目录&#xff01; 将代码存到暂存区 将代码保存到本地仓库 2、创建git仓库 仓库名称和路径&#xff08;name&#xff09;随便写…

绝地求生:成长型皮肤异色定价是否有些夸张?

大家好&#xff0c;我闲游盒小盒子&#xff01; 自从26.2更新上架回归的黑市中四款成长型皮肤以后&#xff0c;能看到社区里很多玩家都分享抽中了自己心仪的成长型皮肤。 但是对于异色很少有人去实装&#xff0c;大多数玩家都是选择去分解异色换取五张图纸然后追求升级原皮等级…

leetcode:2549. 统计桌面上的不同数字(python3解法)

难度&#xff1a;简单 给你一个正整数 n &#xff0c;开始时&#xff0c;它放在桌面上。在 109 天内&#xff0c;每天都要执行下述步骤&#xff1a; 对于出现在桌面上的每个数字 x &#xff0c;找出符合 1 < i < n 且满足 x % i 1 的所有数字 i 。然后&#xff0c;将这些…

链接1:编译器驱动程序

文章目录 GNU编译器示例编译 GNU编译器 GNU编译器&#xff08;GNU Compiler&#xff09;是由自由软件基金会&#xff08;Free Software Foundation&#xff0c;FSF&#xff09;开发和维护的一套编译器集合。这些编译器主要用于编译各种编程语言的源代码&#xff0c;将其转换为…

汽车电子 -- 车载ADAS之FCW(前方碰撞预警)

相关法规文件: FCW: GB∕T 33577-2017 智能运输系统 车辆前向碰撞预警系统 性能要求和测试规程 一、前方碰撞预警 FCW&#xff08; Forward Collision Warning&#xff09; 参看&#xff1a;法规标准-GB/T 33577标准解读(2017版) 1、状态机 系统关闭 当车辆前向碰撞预警系…

08-学成在线项目中统一异常处理的规范

项目中的异常处理 规范异常类型 在Service类的业务方法中有很多的参数合法性校验,当请求参数不合法的时候会抛出异常,但此时异常信息只会在控制台输出,前端界面并不会提示用户 实际开发中前端和后端需要做一些约定: 一般将错误提示信息统一以json格式返回给前端,以HTTP状态码…

excel表格在线编辑(开源版)

文章目录 前言一、Luckysheetvue3vite 例子如有启发&#xff0c;可点赞收藏哟~ 前言 本文记录好用的开源在线表格 具体如图显示 另外记录下更名后的univer~&#xff0c;如下图&#xff08;有兴趣可自行详细了解&#xff09; univer 在线思维导图 一、Luckysheet 参考git…

【LeetCode】每日一题 2023_11_28 设计前中后队列(数组/链表/双端队列)

文章目录 刷题前唠嗑题目&#xff1a;设计前中后队列题目描述代码与解题思路偷看大佬题解 结语 刷题前唠嗑 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 这道题的难度&#xff0c;才是我想象中的中等题的难度好吧&#xff0c;昨天那玩意对我来说还是太难了…

WebUI自动化学习(Selenium+Python+Pytest框架)003

1.元素操作 在成功定位到元素之后&#xff0c;我们需要对元素进行一些操作动作。常用的元素操作动作有&#xff1a; &#xff08;1&#xff09;send_keys() 键盘动作&#xff1a;向浏览器发送一个内容&#xff0c;通常用于输入框输入内容或向浏览器发送快捷键 &#xff08;2…

C语言——有一个3*4的矩阵,要求求出其中值最大的那个元素的值,以及其所在的行号和列号

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> int main() {int i,j,row0,colum0,a[3][4]{{1,2,3,4},{9,8,7,6},{-10,10,-5,2}};int maxa[0][0];for ( i 0; i < 3; i)//行&#xff08;row&#xff09;{for ( j 0; j < 4; j)//列&#xff08;colum&#xf…

【FGPA】Verilog:JK 触发器 | D 触发器 | T 触发器 | D 触发器的实现

0x00 JK 触发器 JK 触发器是 RS 触发器和 T 触发器的组合&#xff0c;有两个输入端 J 和 K&#xff0c;如果两个输入端都等于 1&#xff0c;则将当前值反转。 行为表 状态图 Timing Diagram Circuit JK 触发器的设计目的是防止 RS 触发器在输入 S 和 R 均等于 …

Unity学习笔记11

一、视频播放功能 1.如何让视频在游戏场景中播放&#xff1f; 在Assets目录下添加一个渲染器纹理&#xff0c;步骤&#xff1a;新建→渲染器纹理 首先在创建一个平面&#xff0c;想让视频在平面上显示。在平面上添加一个组件 Video Player 然后将视频文件拖拽到视频剪辑位置上…