前言:实物图片如下所示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以及代码分享如下