前言:实物图片如下所示MPU 6050模块,在其他32的单片机里面研究过一次,现在将软件代码工程移植到CW32F003开发板里面,也可以正常使用,这里使用了软件模拟出来的IIC与模块进行通讯。
一:MPU6050模块的基本介绍
1. MPU6050是一种常用的六轴姿态传感器模块,结合了三轴陀螺仪和三轴加速度计,以及一个可扩展的数字运动处理器DMP(Digital Motion Processor),可用I2C接口连接一个第三方的数字传感器,比如磁力计。MPU6050 对陀螺仪和加速度计分别用了三个16 位的ADC(0~65535),将其测量的模拟量转化为可输出的数字量。为了精确跟踪快速和慢速的运动,传感器的测量范围都是用户可控的,
MPU-6000(6050)的角速度全格感测范围为±250、±500、±1000与±2000°/sec (dps),可准确追踪快速与慢速动作,并且,用户可程式控制的加速器全格感测范围为±2g、±4g±8g与±16g。产品传输可透过最高至400kHz的IIC或最高达20MHz的SPI(MPU-6050没有SPI)。MPU-6000可在不同电压下工作,VDD供电电压介为2.5V±5%、3.0V±5%或3.3V±5%,逻辑接口VDDIO供电为1.8V± 5%(MPU6000仅用VDD)。MPU-6000的包装尺寸4x4x0.9mm(QFN),在业界是革命性的尺寸。其他的特征包含内建的温度感测器、包含在运作环境中仅有±1%变动的振荡器。:
医用场景如下:
运动感测游戏 现实增强 电子稳像 (EIS: Electronic Image Stabilization) 光学稳像(OIS: Optical Image Stabilization) 行人导航器 “零触控”手势用户接口 姿势快捷方式 认证 车轮力传感器
二:硬件资源以及引脚配置
CW32F003开发板,0.96的OLED模块,MPU6050模块,调试器使用的WCH-link;
【GY-521模块与CW32F003开发板连接方式】: VCC<-->+DVCC
GND<-->DVSS
SCL<-->PB5
SDA<-->PB4
【OLED显示屏与CW32F003开发板连接方式】: VCC<-->+DVCC
GND<-->DVSS
SCL<-->PA0
SDA<-->PA1
注:SCL和SDA是连接MCU的IIC接口,MCU通过这个IIC接口来控制MPU6050,
另外还有一个IIC接口:AXCL和 XDA,这个接口可用来连接外部从设备,比如磁传感 器,这样就可以组成一个九轴传感器。VLOGIC是IO口电压,该引脚最低可以到1.8V,我们 一般直接接VDD即可。AD0是从IIC接口(接MCU)的地址控制引脚,该引脚控制IIC地址 的最低位。如果接GND,则MPU6050的IIC地址是:0X68,如果接VDD,则是0X69,注意: 这里的地址是不包含数据传输的最低位的(最低位用来表示读写)。
MPU6050的默认IIC地址是:0X68,如果AD0接VDD,则是0X69。需要注意的是:这里的地址0x68(110 1000)和0x69(110 1001)是不包含最低位的7位数据,通常最低位用于表示IIC主机的读取数据/写数据模式。如默认情况下对MPU6050进行写操作,则发送地址0xD0(1101 0000),读操作则发送地址0xD1(1101 0001)。
寄存器说明:
该寄存器是配置陀螺仪输出速率的分频器,用于为MPU-6050生成采样速率。这里有个公式:采样频率=陀螺仪输出频率/(1+采样分频数)。当 DLPF(数字低通滤波器,见寄存器Configuration)禁用时,陀螺仪输出频率为8kHz;当 DLPF 使能,陀螺仪输出频率=1KHz。
该寄存器为陀螺仪和加速度计配置外部帧同步(FSYNC) 管脚的采样和数字低通滤波(DLPF)设置。其中,数字低通滤波器DLPF由DLPF_CFG配置。根据下表所示的DLPF_CFG值对加速度计和陀螺仪进行滤波。
FS为陀螺仪输出频率。SMPLRT_DIV由预设定的采样频率根据上述的公式计算得出。一般情况下,DPLF滤波频率为采样频率的一半,如设定采样频率为50Hz,由表可知当FS为1kHz,SMPLRT_DIV的值为1000/50-1=19。
该寄存器是用来触发陀螺仪自检和配置陀螺仪的满量程范围。其中,XG_ST、YG_ST、ZG_ST分别用来设置陀螺仪X轴、Y轴、Z轴自检,置0则不触发自检。FS_SEL[1:0]用于设置陀螺仪的满量程,如下表:
我们一般设置为3,即满量程为±2000°/s
该寄存器是用来触发加速度计自检和配置加速度计的满量程范围。同时这个寄存器也可以用于配置数字高通滤波器(DHPF)。其中,XA_ST、YA_ST、ZA_ST分别用来设置加速度计X轴、Y轴、Z轴自检,置0则不触发自检。AFS_SEL[1:0]用于选择加速度计的满量程范围,如下表:
我们一般设置为0,即满量程为±2g
ACCEL_XOUT :由 2部分组成的 16位数值存储最近X 轴加速度计的测量值。ACCEL_YOUT :由 2部分组成的 16位数值存储最近Y 轴加速度计的测量值。ACCEL_ZOUT :由 2部分组成的 16位数值存储最近Z 轴加速度计的测量值。
以ACCEL_XOUT为例,若倍率设定为2g,则意味着ACC_X取最小值-32768时,当前加速度为沿X轴正方向2倍的重力加速度;若设定为4g,取-32768时表示沿X轴正方向4倍的重力加速度,以此类推。显然,倍率越低精度越好,倍率越高表示的范围越大,这要根据具体的应用来设定。以ACC_X为例,若当前设定的加速度倍率为4g,那么将ACC_X读数换算为加速度的公式为:
g可取当地重力加速度。
该寄存器存储最近加陀螺仪的测量值,构成与加速度计测量值寄存器相同,不做赘述。
以GYR_X为例,若倍率设定为250度/秒,则意味着GYR取正最大值32768时,当前角速度为顺时针250度/秒;若设定为500度/秒,取32768时表示当前角速度为顺时针500度/秒。显然,倍率越低精度越好,倍率越高表示的范围越大。以GYR_X为例,若当前设定的角速度倍率为1000度/秒,那么将GRY_X读数换算为角速度(顺时针)的公式为:
该寄存器允许用户配置电源模式和时钟源,还提供了复位整个设备和禁用温度传感器的位。当置SLEEP位为1时,MPU-60X0 可以进入低功耗睡眠模式。该寄存器的最低三位用于设置系统的时钟源选择,默认值是0(内部8M RC振荡),不过一般设置为1,即选择x轴陀螺仪PLL作为时钟源,以获得更高精度的时钟。DEVICE_RESET该位置 1,重启内部寄存器到默认值。复位完成后该位自动清0。TEMP_DIS该位置 1,禁用温度传感器。
主要代码分享如下:
使用模拟IIC方式 驱动OLED代码如下所示:
/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WritePin(CW_GPIOA, GPIO_PIN_1, (GPIO_PinState)(x))
#define OLED_W_SDA(x) GPIO_WritePin(CW_GPIOA, GPIO_PIN_0, (GPIO_PinState)(x))
/*引脚初始化*/
void OLED_I2C_Init(void)
{
__RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.IT=GPIO_IT_NONE;
GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pins=GPIO_PIN_1 | GPIO_PIN_0;
GPIO_InitStruct.Speed=GPIO_SPEED_LOW;
GPIO_Init(CW_GPIOA, &GPIO_InitStruct);
GPIO_WritePin(CW_GPIOA,GPIO_PIN_1 | GPIO_PIN_0,GPIO_Pin_SET);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
if(Byte & (0x80 >> i)) OLED_W_SDA(1);
else OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置低4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置高4位
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED显示一个中文
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~15,注意两个汉字的列位置间隔两个位置
比如:
OLED_ShowChinese(1,1,0);第一个汉字
OLED_ShowChinese(1,3,1);第二个汉字
* @param Select 从汉字库选择汉字索引
* @retval 无
*/
void OLED_ShowChinese(uint8_t Line, uint8_t Column, uint8_t Select)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 16; i++)
{
OLED_WriteData(OLED_F16x16[Select*2][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2+1, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 16; i++)
{
OLED_WriteData(OLED_F16x16[Select*2][i+16]); //显示上半部分内容
}
}
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '�'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}MPU6050驱动代码如下:
模拟IIC驱动模块的底层驱动部分:
void I2C_Delay() //I2C延时函数
{
Delay_us(time);
}
uint8_t I2C_Start(void) //发送起始信号
{
WriteSDA(1);
WriteSCL(1);
I2C_Delay();
if(ReadSDA==0) return 0;
WriteSDA(0);
I2C_Delay();
if(ReadSDA==1) return 0;
WriteSCL(0);
I2C_Delay();
return 1;
}
void I2C_Stop(void) //发送停止信号
{
WriteSDA(0);
WriteSCL(0);
I2C_Delay();
WriteSCL(1);
I2C_Delay();
WriteSDA(1);
}
void I2C_SendACK(uint8_t ackbit) //发送应答
{
WriteSDA(ackbit);
WriteSCL(1);
I2C_Delay();
WriteSCL(0);
I2C_Delay();
}
void I2C_SendByte(uint8_t Byte) //发送1字节(8-bit)的数据
{
uint8_t i;
WriteSCL(0);
for (i = 0; i < 8; i++)
{
if(Byte&0x80) WriteSDA(1);
else WriteSDA(0);
WriteSCL(1);
I2C_Delay();
WriteSCL(0);
Byte<<=1;
I2C_Delay();
}
}
uint8_t I2C_ReceiveByte(void) //接收1字节(8-bit)的数据
{
uint8_t data,i;
WriteSDA(1);
Delay_us(1);
for(i=0;i<8;i++)
{
WriteSCL(1);
data<<=1;
if(ReadSDA==1) data|=0x01;
I2C_Delay();
WriteSCL(0);
I2C_Delay();
}
return data;
}
uint8_t I2C_WaitAck(void) //等待应答
{
uint16_t i;
WriteSDA(1);
WriteSCL(1);
while(ReadSDA==1)
{
if(++i==500)
break;
}
if(ReadSDA==1)
{
WriteSCL(0);
return 0;
}
I2C_Delay();
WriteSCL(0);
I2C_Delay();
return 1;
}
uint8_t WriteData(uint8_t Slave_Addr,uint8_t REG_Addr,uint8_t data) //写操作
{
if(I2C_Start()==0) RETURN
I2C_SendByte(Slave_Addr);
if(I2C_WaitAck()==0) RETURN
I2C_SendByte(REG_Addr);
if(I2C_WaitAck()==0) RETURN
I2C_SendByte(data);
if(I2C_WaitAck()==0) RETURN
I2C_Stop(); //发送停止信号
return 1;
}
uint8_t ReadData(uint8_t Slave_Addr,uint8_t REG_Addr,uint8_t *data,uint8_t length) //读操作
{
if(I2C_Start()==0) RETURN
I2C_SendByte(Slave_Addr);
if(I2C_WaitAck()==0) RETURN
I2C_SendByte(REG_Addr);
if(I2C_WaitAck()==0) RETURN
if(I2C_Start()==0) RETURN
I2C_SendByte(Slave_Addr+1);
if(I2C_WaitAck()==0) RETURN
while(--length)
{
*data++=I2C_ReceiveByte();
I2C_SendACK(0);
}
*data=I2C_ReceiveByte();
I2C_SendACK(1);
I2C_Stop(); //发送停止信号
return 1;
}应用层代码如下:
typedef struct{
int16_t X;
int16_t Y;
int16_t Z;
}MPU6050_Data; //MPU6050加速度计/陀螺仪X、Y、Z轴数据
MPU6050_Data Adata,Gdata; //结构体变量加速度计数据Adata,陀螺仪数据Gdata
struct MPL3115A2 mpl3115a2date; //转换结果值
void GY_521_Init(void) //GY-521初始化
{
GY521_GPIO_Init(); //GPIO初始化
//解除睡眠,失能温度传感器,选择X轴的陀螺仪时钟
WriteData(GY521_ADDR, MPU6050_PWR_MGMT_1, 0x09);
WriteData(GY521_ADDR, MPU6050_CONFIG, 0x06); //低通滤波
WriteData(GY521_ADDR, MPU6050_SMPRT_DIV, 0x09); //1KHz十分频为100Hz
WriteData(GY521_ADDR, MPU6050_GYRO_CONFIG, 0x18);//陀螺仪最大量程:正负2000°/秒
WriteData(GY521_ADDR, MPU6050_ACCEL_CONFIG, 0x18);//加速度计最大量程:正负16g
}
void MPU6050_GetData() //获取MPU6050六轴数据
{
uint8_t MPU6050_Raw_Data[14]={0};
//以MPU6050_ACCEL_XOUT_H为起始地址,连续读取14字节的数据
ReadData(GY521_ADDR,MPU6050_ACCEL_XOUT_H,MPU6050_Raw_Data,14);
//数据处理
Adata.X=(MPU6050_Raw_Data[0]<<8)|MPU6050_Raw_Data[1];
Adata.Y=(MPU6050_Raw_Data[2]<<8)|MPU6050_Raw_Data[3];
Adata.Z=(MPU6050_Raw_Data[4]<<8)|MPU6050_Raw_Data[5];
Gdata.X=(MPU6050_Raw_Data[8]<<8)|MPU6050_Raw_Data[9];
Gdata.Y=(MPU6050_Raw_Data[10]<<8)|MPU6050_Raw_Data[11];
Gdata.Z=(MPU6050_Raw_Data[12]<<8)|MPU6050_Raw_Data[13];
}主函数显示部分如下:
MPU6050_GetData(); //获取六轴数据 OLED_ShowSignedNum(2,1,Adata.X,5); OLED_ShowSignedNum(3,1,Adata.Y,5); OLED_ShowSignedNum(4,1,Adata.Z,5); OLED_ShowSignedNum(2,9,12345,5); OLED_ShowSignedNum(3,9,Gdata.Y,5); OLED_ShowSignedNum(4,9,Gdata.Z,5);
实物测试图片如下所示:

MPU6050以及代码分享如下
我要赚赏金
