简介
硬件电路
I2C时序基本单元
•发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
•接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)(虚线是从机控制)
I2C从机地址
一主多从模型下,如何发出指令来确定要访问的是哪一个设备呢?这就需要首先把每一个设备都确定一个唯一的设备地址,从机设备地址就相当于每个设备的名字,主机在起始条件之后,要先发送一个字节选择从机名字,所以从机都会收到这个字节(名字),然后和自己的名字做比较,如果不一样,则该设备将不会再接收如何数据,如果一样,就说明主机选择了该设备,则响应接下来主机的读写操作。(这就要求在同一条I2C总线中挂载的每个设备的地址都必须得不一样)
从机设备地址在I2C协议标准中分为7位地址和10位地址,7位地址最常用(可在相关芯片手册中找到),如果有相同的芯片挂载在同一条总线中该如何解决呢?一般器件地址的最后几位都是可以在电路中改变的。一般高位都是由厂商确定的,低位可以依靠接入的引脚进行灵活切换。(比如MPU6050的地址的最后一位就可以由其引脚AD0确定,接低电平,地址为1101 000,接高电平,地址为1101 001)
I2C时序
MPU6050
简介
参数
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通信
首先建立I2C通信层的.c和.h模块,在通信层里写好I2C底层的GPIO初始化和6个时序基本单元(起始终止,发送一个字节,接收一个字节,发送应答和接收应答)。
|
再建立MPU6050的.c和.h模块,再这一层基于I2C通信的模块来实现指定地址读、指定地址写,再实现寄存器对芯片进行配置,读寄存器得到传感器数据。
|
最后再在主函数中调用MPU6050模块,初始化,拿到数据,显示数据。
因为是用软件实现I2C,所以我们可以不用库中的I2C外设的函数,而是自己通过配置GPIO口的函数来实现就行了。
软件I2C初始化
1、把SCL和SDA都初始化为开漏输出模式,把SCL和SDA置高电平
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);
}
2、时序基本单元的配置
配置起始条件等时序基本单元时需要多次反转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;
}
这样我们在移植代码时就可以只修改这部分和初始化部分的代码了
a、起始条件
起始条件:SCL高电平期间,SDA从高电平切换到低电平
//起始条件:SCL高电平期间,SDA从高电平切换到低电平
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
b、终止条件
终止条件:SCL高电平期间,SDA从低电平切换到高电平
//终止条件:SCL高电平期间,SDA从低电平切换到高电平
void MyI2C_Stop(void)
{
//因为在终止之前SDA可能是高电平,则无法实现从低切换到高
//所以需要创造能从低到高的条件,就必须先拉低SDA
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
c、发送一个字节
除了终止条件,我们都会保证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);
}
}
d、接收一个字节
接收一个字节: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;
}
e、发送应答和接收应答
其实就是发送一个字节和接收一个字节的简化版,只接收一位数据版
//发送应答
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模块
即读写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即为所求