





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

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


一主多从模型下,如何发出指令来确定要访问的是哪一个设备呢?这就需要首先把每一个设备都确定一个唯一的设备地址,从机设备地址就相当于每个设备的名字,主机在起始条件之后,要先发送一个字节选择从机名字,所以从机都会收到这个字节(名字),然后和自己的名字做比较,如果不一样,则该设备将不会再接收如何数据,如果一样,就说明主机选择了该设备,则响应接下来主机的读写操作。(这就要求在同一条I2C总线中挂载的每个设备的地址都必须得不一样)
从机设备地址在I2C协议标准中分为7位地址和10位地址,7位地址最常用(可在相关芯片手册中找到),如果有相同的芯片挂载在同一条总线中该如何解决呢?一般器件地址的最后几位都是可以在电路中改变的。一般高位都是由厂商确定的,低位可以依靠接入的引脚进行灵活切换。(比如MPU6050的地址的最后一位就可以由其引脚AD0确定,接低电平,地址为1101 000,接高电平,地址为1101 001)






1101001(AD0=1)(0x69)
我们在I2C时序中输入I2C从机的地址时,输入的第一个字节前七位位从机地址,最后一位是决定读或者写。当我们输入时就需要将我们的从机的地址左移一位,然后或上读1写0.
当然我们可以直接把左移一位的地址看做I2C从机地址,把读写位也融入I2C从机地址中,即1101 0000(AD0=0)(0xD0)和1101 0010(AD0=1)(0xD2) ,然后再按照读1写0或上对应数字。

SDA和SCL是I2C通信脚,可以看到已经挂载了两个上拉电阻,可以不用外接电阻再接到GPIO口上了。 XCL、XDA:用于拓展芯片功能,MPU6050是6轴姿态传感器,融合出来的姿态角是有缺陷的,可以外接磁力计和气压计,拓展为十轴姿态传感器,MPU6050的主机接口就可以直接访问这些拓展芯片的数据,把这些拓展芯片的数据读取到MPU6050中,通过DMP单元进行数据融合和姿态解算,融合出更准确的姿态角。
INT:可以配置芯片内部的一些事件来触发中断引脚的输出,例如数据准备好了、I2C主机错误等,芯片中还内置了一些实用的小功能,比如自由落体检测、运动检测、零运动检测等这些信号都可以触发INT引脚产生电平跳变。

XYZ....:都是各个方向的有关传感器
Temp Sensor:温度传感器;
各个传感器传出来的数据都会由芯片自动进行AD数模转换发送到数据寄存器中,且不会相互覆盖。
自测系统:我们先使能自测系统,得到传感器的数据,再失能自测系统,得到另一组传感器的数据,两组数据之差在芯片数据手册中有标明一个正常的范围,如果超了这个范围,则代表传感器失灵。
Interrupt Status Register:中断状态寄存器,控制内部哪些事件到中断引脚的输出;
FIFO:先入先出寄存器,可以对数据流进行缓存
Config Register:配置寄存器,可以对内部的各个电路进行配置
Sensor Register:传感器数据寄存器
数组运动处理器(DMP):芯片内部自带的姿态解算的硬件算法,配合官方的DMP库,可以进行姿态解算
采样分频寄存器:配置采样分频率的分频系数,简单来说,分频越小,内部的AD转换就越快,数据寄存器刷新越快

配置寄存器
分为两部分:外部同步设置和低通滤波器配置

外部我们不用,先不看
低通滤波器配置——让输出数据更加平滑,配置滤波器参数越大,输出数据抖动越小

陀螺仪配置寄存器
高三位:XYZ轴的自测使能位,中间两位:满量程选择位
自测响应的计算公式
自测响应的范围
满量程选择:量程越大,范围越广,量程越小,分辨率越高
加速度计配置寄存器
高三位:XYZ轴的自测使能位,中间两位:满量程选择位,第三位:配置高通滤波器

加速度计的数据寄存器

得到的数据时16位的有符号数,以二进制补码的方式存储
其他数据寄存器也是相同操作
电源管理寄存器1

第一位:设备复位,写1,所有寄存器恢复默认值;
2:睡眠模式,写1芯片睡眠,不工作,进入低功耗;
3:循环模式,设备进入低功耗,过一段时间启动一次
4:温度传感器失能,写1,禁用内部的温度传感器
最后三位:选择系统的时钟来源(建议选择陀螺仪的晶振,比较准确)

电源管理寄存器2:

前两位:控制电源管理寄存器1中的循环模式的频率;
后面6位:可以分别控制6个轴进入待机模式
ID号:只读(即这个芯片的I2C地址)

除了ID号和电源管理寄存器1,其他寄存器默认值为0x00

首先建立I2C通信层的.c和.h模块,在通信层里写好I2C底层的GPIO初始化和6个时序基本单元(起始终止,发送一个字节,接收一个字节,发送应答和接收应答)。
|
再建立MPU6050的.c和.h模块,再这一层基于I2C通信的模块来实现指定地址读、指定地址写,再实现寄存器对芯片进行配置,读寄存器得到传感器数据。
|
最后再在主函数中调用MPU6050模块,初始化,拿到数据,显示数据。
因为是用软件实现I2C,所以我们可以不用库中的I2C外设的函数,而是自己通过配置GPIO口的函数来实现就行了。
- void MyI2C_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);
-
- //再把SCL和SDA置高电平,释放总线,此时I2C总线处于空闲状态
- GPIO_WriteBit(GPIOA, GPIO_Pin_10 | GPIO_Pin_11, Bit_SET);
-
- }
配置起始条件等时序基本单元时需要多次反转SCL和SDA的电平,如果我们仅仅使用Reset和Set来反转电平,并不是不可以,只是这样会导致代码意义不明确,而且在修改时需要更改太多,况且也不好移植到其他开发板上应用,所以我们就需要对反转电平的操作进行简化,目标是使其易更改,易移植且意义明确。
我们就可以使用宏定义来完成
想法1是把SET和RESET语句宏定义(有参宏)(OLED模块中有示例)
- #define MyI2C_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
- //BitAction是枚举类型
但是这种方法移植到其他单片机时,不好修改,而且如果把这种代码移植到一个主频很高的单片机中,需要对软件时序进行延时操作时,不方便进一步修改
所以不如直接用函数封装起来
- /*用于翻转电平的函数*/
- //释放或拉低SCL
- void MyI2C_W_SCL(uint8_t BitValue)
- {
- GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
- //以防单片机主频太快
- Delay_us(10);
- }
-
- void MyI2C_W_SDA(uint8_t BitValue)
- {
- GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
- Delay_us(10);
- }
-
- //读取SDA的值
- uint8_t MyI2C_R_SDA(void)
- {
- uint8_t BitValue;
- BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
- Delay_us(10);
- return BitValue;
- }
这样我们在移植代码时就可以只修改这部分和初始化部分的代码了
起始条件:SCL高电平期间,SDA从高电平切换到低电平
- //起始条件:SCL高电平期间,SDA从高电平切换到低电平
- void MyI2C_Start(void)
- {
- MyI2C_W_SDA(1);
- MyI2C_W_SCL(1);
-
- MyI2C_W_SDA(0);
- MyI2C_W_SCL(0);
- }
终止条件:SCL高电平期间,SDA从低电平切换到高电平
- //终止条件:SCL高电平期间,SDA从低电平切换到高电平
- void MyI2C_Stop(void)
- {
- //因为在终止之前SDA可能是高电平,则无法实现从低切换到高
- //所以需要创造能从低到高的条件,就必须先拉低SDA
- MyI2C_W_SDA(0);
-
- MyI2C_W_SCL(1);
- MyI2C_W_SDA(1);
- }
除了终止条件,我们都会保证SCL以低电平结束,方便各个单元的拼接

- //发送一个字节
- void MyI2C_SendByte(uint8_t Byte)
- {
- //下面函数的参数非0即1
- //通过与上一个值把每一位数字都取出来
- MyI2C_W_SDA(Byte & 0x80);
-
- //高电平读取SCL的数据
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
-
- MyI2C_W_SDA(Byte & 0x40);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
-
- MyI2C_W_SDA(Byte & 0x20);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
-
- MyI2C_W_SDA(Byte & 0x10);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
-
- MyI2C_W_SDA(Byte & 0x08);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
-
- MyI2C_W_SDA(Byte & 0x04);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
-
- MyI2C_W_SDA(Byte & 0x02);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
-
- MyI2C_W_SDA(Byte & 0x01);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
- }
用for循环简化
- //发送一个字节
- void MyI2C_SendByte(uint8_t Byte)
- {
- uint8_t i;
- for (i = 0; i < 8; i ++)
- {
- MyI2C_W_SDA(Byte & (0x80 >> i));
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
- }
- }
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)(虚线是从机控制)
在接收字节前,主机必须放手SDA(SDA置高电平),交由从机控制SDA,这样就可以实现在SCL高电平期间读取从机SDA上的电平而实现读取从机的数据。要读取从机的数据,主机就不可以“乱动”SDA,如果这期间主机SDA变化,那么就是起始位和终止位的条件了。

- //接收一个字节
- uint8_t MyI2C_ReceiveByte(void)
- {
- uint8_t i, Byte = 0x00;
- //主机先放手SDA
- MyI2C_W_SDA(1);
- for (i = 0; i < 8; i ++)
- {
- //在SCL为1时接收数据(从高位开始)
- MyI2C_W_SCL(1);
- if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
- MyI2C_W_SCL(0);
- }
- return Byte;
- }
其实就是发送一个字节和接收一个字节的简化版,只接收一位数据版
- //发送应答
- void MyI2C_SendAck(uint8_t AckBit)
- {
- MyI2C_W_SDA(AckBit);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
- }
-
- //接收应答
- uint8_t MyI2C_ReceiveAck(void)
- {
- uint8_t AckBit;
- MyI2C_W_SDA(1);
- MyI2C_W_SCL(1);
- AckBit = MyI2C_R_SDA();
- MyI2C_W_SCL(0);
- return AckBit;
- }
这样就把六个时序基本单元配置好了
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
-
- /*用于翻转电平的函数*/
- //释放或拉低SCL
- void MyI2C_W_SCL(uint8_t BitValue)
- {
- GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
- //以防单片机主频太快
- Delay_us(10);
- }
-
- void MyI2C_W_SDA(uint8_t BitValue)
- {
- GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
- Delay_us(10);
- }
-
- //读取SDA的值
- uint8_t MyI2C_R_SDA(void)
- {
- uint8_t BitValue;
- BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
- Delay_us(10);
- return BitValue;
- }
-
- void MyI2C_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);
-
- //再把SCL和SDA置高电平,释放总线,此时I2C总线处于空闲状态
- GPIO_WriteBit(GPIOA, GPIO_Pin_10 | GPIO_Pin_11, Bit_SET);
-
- }
-
- //时序基本单元
- //起始条件:SCL高电平期间,SDA从高电平切换到低电平
- void MyI2C_Start(void)
- {
- MyI2C_W_SDA(1);
- MyI2C_W_SCL(1);
-
- MyI2C_W_SDA(0);
- MyI2C_W_SCL(0);
- }
-
- //终止条件:SCL高电平期间,SDA从低电平切换到高电平
- void MyI2C_Stop(void)
- {
- //因为在终止之前SDA可能是高电平,则无法实现从低切换到高
- //所以需要创造能从低到高的条件,就必须先拉低SDA
- MyI2C_W_SDA(0);
-
- MyI2C_W_SCL(1);
- MyI2C_W_SDA(1);
- }
-
- //发送一个字节
- void MyI2C_SendByte(uint8_t Byte)
- {
- uint8_t i;
- for (i = 0; i < 8; i ++)
- {
- MyI2C_W_SDA(Byte & (0x80 >> i));
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
- }
- // //下面函数的参数非0即1
- // //通过与上一个值把每一位数字都取出来
- // MyI2C_W_SDA(Byte & 0x80);
- // //高电平读取SCL的数据
- // MyI2C_W_SCL(1);
- // MyI2C_W_SCL(0);
- }
-
- //接收一个字节
- uint8_t MyI2C_ReceiveByte(void)
- {
- uint8_t i, Byte = 0x00;
- //主机先放手SDA
- //I2C的引脚都是开漏输出+弱上拉的配置
- //主机输出1,并不是强制SDA为高电平,而是释放SDA
- MyI2C_W_SDA(1);
- for (i = 0; i < 8; i ++)
- {
- //在SCL为1时接收数据(从高位开始)
- MyI2C_W_SCL(1);
- if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
- MyI2C_W_SCL(0);
- }
- return Byte;
- }
-
- //发送应答
- void MyI2C_SendAck(uint8_t AckBit)
- {
- MyI2C_W_SDA(AckBit);
- MyI2C_W_SCL(1);
- MyI2C_W_SCL(0);
- }
-
- //接收应答
- uint8_t MyI2C_ReceiveAck(void)
- {
- uint8_t AckBit;
- MyI2C_W_SDA(1);
- MyI2C_W_SCL(1);
- AckBit = MyI2C_R_SDA();
- MyI2C_W_SCL(0);
- return AckBit;
- }
目前还未写好MPU的代码,可以先验证起始条件、终止条件、发送一个字节和接收应答
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "MyI2C.h"
-
- int main(void)
- {
- OLED_Init();
-
- MyI2C_Init();
-
- MyI2C_Start();
- MyI2C_SendByte(0xD0); //1101 000 0
- uint8_t Ack = MyI2C_ReceiveAck();
- MyI2C_Stop();
-
- OLED_ShowNum(1, 1, Ack, 3);
- while(1)
- {
-
- }
- }
如果OLED上显示0,则表示接收到了数据,我们再修改发送的值为其他,如果OLED上显示1,则代表没接收到数据,也就代表代码编写正确。
我们还可以通过连接MPU的AD0引脚到高电平修改从机的地址来验证代码,此时输入的字节必须为0xD2(1101 010 0)
即读写MPU6050的寄存器
- #include "stm32f10x.h" // Device header
- #include "MyI2C.h"
-
- #define MPU6050_ADDRESS 0xD0
- //指定地址写
- void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
- {
- MyI2C_Start();
- MyI2C_SendByte(MPU6050_ADDRESS);
- //可以验证是否收到数据,具体怎么处理就不加上了
- MyI2C_ReceiveAck();
- //发送寄存器的地址
- MyI2C_SendByte(RegAddress);
- MyI2C_ReceiveAck();
- //接收一个字节
- MyI2C_SendByte(Data);
- MyI2C_ReceiveAck();
- MyI2C_Stop();
- }
-
- //指定地址读
- uint8_t MPU6050_ReadtheReg(uint8_t RegAddress)
- {
- uint8_t Data;
- MyI2C_Start();
- MyI2C_SendByte(MPU6050_ADDRESS);
- MyI2C_ReceiveAck();
- MyI2C_SendByte(RegAddress);
- MyI2C_ReceiveAck();
-
- MyI2C_Start();
- MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
- MyI2C_ReceiveAck();
- Data = MyI2C_ReceiveByte();
- //发送主机应答,已经接收到数据了
- MyI2C_SendAck(1);
- MyI2C_Stop();
-
- return Data;
- }
-
- void MPU6050_Init(void)
- {
- MyI2C_Init();
- }
然后再在主函数中验证
先读MPU的ID号
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "MPU6050.h"
-
-
- int main(void)
- {
- OLED_Init();
-
- MPU6050_Init();
-
- uint8_t Data = MPU6050_ReadtheReg(0x75);
- OLED_ShowHexNum(1, 1, Data, 2);
- while(1)
- {
-
- }
- }
结果是OLED上显示68,即ID号为0x68,则代表读寄存器的代码编写正确
那我们再验证一下写寄存器的代码
首先需要关闭芯片的睡眠模式,下图Bit7即为我们需要更改的寄存器

先关闭睡眠模式,然后再在地址为0x19的寄存器上写入0x66
- #include "stm32f10x.h" // Device header
- #include "Delay.h"
- #include "OLED.h"
- #include "MPU6050.h"
-
-
- int main(void)
- {
- OLED_Init();
-
- MPU6050_Init();
-
- MPU6050_WriteReg(0x6B, 0x00);
-
- MPU6050_WriteReg(0x19, 0x66);
-
- uint8_t Data = MPU6050_ReadtheReg(0x19);
- OLED_ShowHexNum(1, 1, Data, 2);
- while(1)
- {
-
- }
- }
结果是OLED上显示为66,即0x66,代表没问题
我们这里的验证思路是把MPU6050当做一个存储器来验证的,MPU6050里的每个寄存器都对应着芯片的一种状态,寄存器和外设的硬件电路是可以进行互动的,意思是我们可以通过配置寄存器来和MPU6050的硬件电路进行互动。
课后任务:实现发送一个数组到多个地址,读取多个地址的数据
我们初始化MPU6050芯片还需要做到配置其电源管理寄存器、采样分频寄存器等等,所以我们可以在Init函数中调用写入寄存器函数来配置这些寄存器
为了方便移植,我们可以用宏定义来定义各个寄存器,因为寄存器数量多,我们决定使用头文件模块化这部分宏定义
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
然后配置各个寄存器
- void MPU6050_Init(void)
- {
- MyI2C_Init();
- //电源管理寄存器1
- //设备复位 睡眠模式 循环模式 无关位 温度传感器失能 选择时钟(后三位)
- //0(不复位) 0(解除睡眠) 0(不需要) 0 0(不失能) 000(内部时钟)(或者001-陀螺仪时钟)
- MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
-
- //电源管理寄存器2
- //循环模式唤醒频率(不需要-00)
- //后6位每个轴的待机位-(000000-不待机)
- MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
-
- //采样率分频(值越小,数据输出越快)(采样分频为10)
- MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
-
- //配置寄存器
- //外部同步-000000 数字低通滤波-110
- MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
-
- //陀螺仪配置寄存器
- //自测使能-000 满量程选择-11 无关位-000
- MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
-
- //加速度计配置寄存器
- //自测使能-000 满量程选择-11 高通滤波器-000
- MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
- }
根据任务需求,我们这个函数需要返回6个数据,但是c语言函数不可以同时返回这么多值,所以我们就会用到以下方法
1、定义全局变量,调用函数时修改这些变量的值——不推荐在大项目中使用
2、使用指针
3、使用结构体,将这些数据打包起来
这里我们使用指针来完成
- //读取寄存器
- 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_ReadtheReg(MPU6050_ACCEL_XOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_ACCEL_XOUT_L);
- *AccX = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_ACCEL_YOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_ACCEL_YOUT_L);
- *AccY = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_ACCEL_ZOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_ACCEL_ZOUT_L);
- *AccZ = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_GYRO_XOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_GYRO_XOUT_L);
- *GyroX = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_GYRO_YOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_GYRO_YOUT_L);
- *GyroY = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_GYRO_ZOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_GYRO_ZOUT_L);
- *GyroZ = (DataH << 8) | DataL;
- }
-
- //读取寄存器
- 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_ReadtheReg(MPU6050_ACCEL_XOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_ACCEL_XOUT_L);
- *AccX = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_ACCEL_YOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_ACCEL_YOUT_L);
- *AccY = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_ACCEL_ZOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_ACCEL_ZOUT_L);
- *AccZ = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_GYRO_XOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_GYRO_XOUT_L);
- *GyroX = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_GYRO_YOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_GYRO_YOUT_L);
- *GyroY = (DataH << 8) | DataL;
-
- DataH = MPU6050_ReadtheReg(MPU6050_GYRO_ZOUT_H);
- DataL = MPU6050_ReadtheReg(MPU6050_GYRO_ZOUT_L);
- *GyroZ = (DataH << 8) | DataL;
- }
-
- uint8_t MPU6050_GetID(void)
- {
- return MPU6050_ReadtheReg(MPU6050_WHO_AM_I);
- }
-
这样是连续调用多次读取指定寄存器的函数,再把两个8位的数据拼接成16位的数据的方法
但其实有更方便的方法,就是使用课后作业中实现读取连续多个字节的代码,从一个基地址开始,连续读取一片的寄存器——因为这些寄存器的地址是连续在一起的,这样在时序上,读取效率就会大大提升。
在主函数中调用
- #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);
- }
- }
求出各项具体值的方法:显示值/32768=x/满量程,x即为所求