经过两天两夜的查阅文献资料、整理学习,成功的把江科大的软件IIC读写MPU6050移植到MSPM0G3507,亲测有效!!包的,为了让大家直观地感受下,先上图。记得点个赞哦!
学过江科大的STM32的小伙伴是不是觉得这个画面非常熟悉,在这里我选的是满量程为16g,且陀螺仪水平放置,根据Z轴的读数可以计算出当地的重力加速度值,计算公式为读数(X/2^15)*16,即1963/32768*16=0.96。
思路讲解
1.软硬件型号
选择CCS theia进行M0G3507的开发,显示屏为0.96寸4引脚OLED显示屏,陀螺仪选择常见的MPU6050,GY-521模块。
2.软件IIC时序模拟
//IIC写SDA引脚void MyI2C_W_SDA(uint8_t BitValue){ SDA_OUT(); if(BitValue) DL_GPIO_setPins(GPIO_sda_PORT, GPIO_sda_PIN_0_PIN); else DL_GPIO_clearPins(GPIO_sda_PORT, GPIO_sda_PIN_0_PIN);Delay_us(8);//延时8us,防止时序频率超过要求}//IIC写SCL引脚void MyI2C_W_SCL(uint8_t BitValue){ if(BitValue) DL_GPIO_setPins(GPIO_scl_PORT, GPIO_scl_PIN_1_PIN); else DL_GPIO_clearPins(GPIO_scl_PORT, GPIO_scl_PIN_1_PIN);Delay_us(8);//延时8us,防止时序频率超过要求}//IIC开始void MyI2C_Start(void){ SDA_OUT();MyI2C_W_SDA(1);//释放SDA,确保SDA为高电平MyI2C_W_SCL(1);//释放SCL,确保SCL为高电平MyI2C_W_SDA(0);//在SCL高电平期间,拉低SDA,产生起始信号MyI2C_W_SCL(0);//起始后拉低SCL,为了占用总线,方便总线时序的拼接}
3.IIC发送一个字节数据
void MyI2C_SendByte(uint8_t Byte){ SDA_OUT();uint8_t i;for (i = 0; i < 8; i ++)//循环8次,主机依次发送数据的每一位{MyI2C_W_SDA(Byte & (0x80 >> i));//使用掩码的方式取出Byte的指定一位数据并写入到SDA线MyI2C_W_SCL(1);//释放SCL,从机在SCL高电平期间读取SDAMyI2C_W_SCL(0);//拉低SCL,主机开始发送下一位数据}}
4.IIC接收一个字节的数据
uint8_t MyI2C_ReceiveByte(void){ SDA_OUT();uint8_t i, Byte = 0x00;//定义接收的数据,并赋初值0x00MyI2C_W_SDA(1);//接收前,主机先确保释放SDA,避免干扰从机的数据发送for (i = 0; i < 8; i ++)//循环8次,主机依次接收数据的每一位{ SDA_IN();MyI2C_W_SCL(1);//释放SCL,主机机在SCL高电平期间读取SDAif (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}//读取SDA数据,并存储到Byte变量//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0MyI2C_W_SCL(0);//拉低SCL,从机在SCL低电平期间写入SDA}return Byte;//返回接收到的一个字节数据}
5.IIC发送应答位
void MyI2C_SendAck(uint8_t AckBit){ SDA_OUT();MyI2C_W_SDA(AckBit);//主机把应答位数据放到SDA线MyI2C_W_SCL(1);//释放SCL,从机在SCL高电平期间,读取应答位MyI2C_W_SCL(0);//拉低SCL,开始下一个时序模块}
6.IIC接收应答位
uint8_t MyI2C_ReceiveAck(void){ SDA_OUT();uint8_t AckBit;MyI2C_W_SDA(1);//接收前,主机先确保释放SDA,避免干扰从机的数据发送MyI2C_W_SCL(1);//释放SCL,主机机在SCL高电平期间读取SDA SDA_IN();AckBit = MyI2C_R_SDA();MyI2C_W_SCL(0);//拉低SCL,开始下一个时序模块return AckBit;//返回定义应答位变量}
7.MPU6050写数据
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data){MyI2C_Start();//I2C起始MyI2C_SendByte(MPU6050_ADDRESS);//发送从机地址(0xD0),读写位为0,表示即将写入MyI2C_ReceiveAck();//接收应答MyI2C_SendByte(RegAddress);//发送寄存器地址MyI2C_ReceiveAck();//接收应答MyI2C_SendByte(Data);//发送要写入寄存器的数据MyI2C_ReceiveAck();//接收应答MyI2C_Stop();//I2C终止}
8.MPU6050读数据
uint8_t MPU6050_ReadReg(uint8_t RegAddress){uint8_t Data; MyI2C_Start();//I2C起始MyI2C_SendByte(MPU6050_ADDRESS);//发送从机地址,读写位为0,表示即将写入MyI2C_ReceiveAck();//接收应答MyI2C_SendByte(RegAddress);//发送寄存器地址MyI2C_ReceiveAck();//接收应答MyI2C_Start();//I2C重复起始MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//发送从机地址,读写位为1,表示即将读取MyI2C_ReceiveAck();//接收应答Data = MyI2C_ReceiveByte();//接收指定寄存器的数据MyI2C_SendAck(1);//发送应答,给从机非应答,终止从机的数据输出MyI2C_Stop();//I2C终止return Data;}
9.myi2c.c
#include "ti_msp_dl_config.h"#include "ti/driverlib/dl_gpio.h"//打开SDA引脚(输出)void SDA_OUT(void) { DL_GPIO_initDigitalOutput(GPIO_sda_PIN_0_IOMUX); DL_GPIO_setPins(GPIO_sda_PORT, GPIO_sda_PIN_0_PIN); DL_GPIO_enableOutput(GPIO_sda_PORT, GPIO_sda_PIN_0_PIN); }//关闭SDA引脚(输入)void SDA_IN(void){ DL_GPIO_initDigitalInputFeatures(GPIO_sda_PIN_0_IOMUX, DL_GPIO_INVERSION_DISABLE, DL_GPIO_RESISTOR_PULL_UP, DL_GPIO_HYSTERESIS_DISABLE, DL_GPIO_WAKEUP_DISABLE);}void Delay_us(uint16_t us){ while(us--) delay_cycles(CPUCLK_FREQ/1000000);}//CPUCLK_FREQ为时钟频率,可以根据配置的改变而改变/*引脚配置层*//** * 函 数:I2C写SCL引脚电平 * 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平 */void MyI2C_W_SCL(uint8_t BitValue){ if(BitValue) DL_GPIO_setPins(GPIO_scl_PORT, GPIO_scl_PIN_1_PIN); else DL_GPIO_clearPins(GPIO_scl_PORT, GPIO_scl_PIN_1_PIN);Delay_us(8);//延时8us,防止时序频率超过要求}/** * 函 数:I2C写SDA引脚电平 * 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF * 返 回 值:无 * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平 */void MyI2C_W_SDA(uint8_t BitValue){ SDA_OUT(); if(BitValue) DL_GPIO_setPins(GPIO_sda_PORT, GPIO_sda_PIN_0_PIN); else DL_GPIO_clearPins(GPIO_sda_PORT, GPIO_sda_PIN_0_PIN);Delay_us(8);//延时8us,防止时序频率超过要求}/** * 函 数:I2C读SDA引脚电平 * 参 数:无 * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1 * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1 */uint8_t MyI2C_R_SDA(void){uint8_t b; uint32_t BitValue; SDA_IN();BitValue = DL_GPIO_readPins(GPIO_sda_PORT, GPIO_sda_PIN_0_PIN);//读取SDA电平 { if(BitValue) b=1; else b=0; }Delay_us(8);//延时8us,防止时序频率超过要求return b; //返回SDA电平}/** * 函 数:I2C初始化 * 参 数:无 * 返 回 值:无 * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化 */void MyI2C_Init(void){ SYSCFG_DL_GPIO_init();/*设置默认电平*/DL_GPIO_setPins(GPIOA, GPIO_sda_PIN_0_PIN |GPIO_scl_PIN_1_PIN);//设置PA8和PA9引脚初始化后默认为高电平(释放总线状态)}/*协议层*//** * 函 数:I2C起始 * 参 数:无 * 返 回 值:无 */void MyI2C_Start(void){ SDA_OUT();MyI2C_W_SDA(1);//释放SDA,确保SDA为高电平MyI2C_W_SCL(1);//释放SCL,确保SCL为高电平MyI2C_W_SDA(0);//在SCL高电平期间,拉低SDA,产生起始信号MyI2C_W_SCL(0);//起始后拉低SCL,为了占用总线,方便总线时序的拼接}/** * 函 数:I2C终止 * 参 数:无 * 返 回 值:无 */void MyI2C_Stop(void){ SDA_OUT();MyI2C_W_SDA(0);//拉低SDA,确保SDA为低电平MyI2C_W_SCL(1);//释放SCL,使SCL呈现高电平MyI2C_W_SDA(1);//在SCL高电平期间,释放SDA,产生终止信号}/** * 函 数:I2C发送一个字节 * 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF * 返 回 值:无 */void MyI2C_SendByte(uint8_t Byte){ SDA_OUT();uint8_t i;for (i = 0; i < 8; i ++)//循环8次,主机依次发送数据的每一位{MyI2C_W_SDA(Byte & (0x80 >> i));//使用掩码的方式取出Byte的指定一位数据并写入到SDA线MyI2C_W_SCL(1);//释放SCL,从机在SCL高电平期间读取SDAMyI2C_W_SCL(0);//拉低SCL,主机开始发送下一位数据}}/** * 函 数:I2C接收一个字节 * 参 数:无 * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF */uint8_t MyI2C_ReceiveByte(void){ SDA_OUT();uint8_t i, Byte = 0x00;//定义接收的数据,并赋初值0x00MyI2C_W_SDA(1);//接收前,主机先确保释放SDA,避免干扰从机的数据发送for (i = 0; i < 8; i ++)//循环8次,主机依次接收数据的每一位{ SDA_IN();MyI2C_W_SCL(1);//释放SCL,主机机在SCL高电平期间读取SDAif (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}//读取SDA数据,并存储到Byte变量//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0MyI2C_W_SCL(0);//拉低SCL,从机在SCL低电平期间写入SDA}return Byte;//返回接收到的一个字节数据}/** * 函 数:I2C发送应答位 * 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答 * 返 回 值:无 */void MyI2C_SendAck(uint8_t AckBit){ SDA_OUT();MyI2C_W_SDA(AckBit);//主机把应答位数据放到SDA线MyI2C_W_SCL(1);//释放SCL,从机在SCL高电平期间,读取应答位MyI2C_W_SCL(0);//拉低SCL,开始下一个时序模块}/** * 函 数:I2C接收应答位 * 参 数:无 * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答 */uint8_t MyI2C_ReceiveAck(void){ SDA_OUT();uint8_t AckBit;//定义应答位变量MyI2C_W_SDA(1);//接收前,主机先确保释放SDA,避免干扰从机的数据发送MyI2C_W_SCL(1);//释放SCL,主机机在SCL高电平期间读取SDA SDA_IN();AckBit = MyI2C_R_SDA();//将应答位存储到变量里MyI2C_W_SCL(0);//拉低SCL,开始下一个时序模块return AckBit;//返回定义应答位变量}
10.myi2c.h
#ifndef __MYI2C_H#define __MYI2C_H#include "stdint.h"void MyI2C_Init(void);void MyI2C_Start(void);void MyI2C_Stop(void);void MyI2C_SendByte(uint8_t Byte);uint8_t MyI2C_R_SDA(void);uint8_t MyI2C_ReceiveByte(void);void MyI2C_SendAck(uint8_t AckBit);uint8_t MyI2C_ReceiveAck(void);void MyI2C_W_SCL(uint8_t BitValue);void MyI2C_W_SDA(uint8_t BitValue);void Delay_us(uint16_t us);#endif
11.mpu6050.c
#include "ti_msp_dl_config.h"#include "myi2C.h"#include "MPU6050_Reg.h"#define MPU6050_ADDRESS0xD0//MPU6050的I2C从机地址/** * 函 数:MPU6050写寄存器 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF * 返 回 值:无 */void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data){MyI2C_Start();//I2C起始MyI2C_SendByte(MPU6050_ADDRESS);//发送从机地址,读写位为0,表示即将写入MyI2C_ReceiveAck();//接收应答MyI2C_SendByte(RegAddress);//发送寄存器地址MyI2C_ReceiveAck();//接收应答MyI2C_SendByte(Data);//发送要写入寄存器的数据MyI2C_ReceiveAck();//接收应答MyI2C_Stop();//I2C终止}/** * 函 数:MPU6050读寄存器 * 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述 * 返 回 值:读取寄存器的数据,范围:0x00~0xFF */uint8_t MPU6050_ReadReg(uint8_t RegAddress){uint8_t Data; MyI2C_Start();//I2C起始MyI2C_SendByte(MPU6050_ADDRESS);//发送从机地址,读写位为0,表示即将写入MyI2C_ReceiveAck();//接收应答MyI2C_SendByte(RegAddress);//发送寄存器地址MyI2C_ReceiveAck();//接收应答MyI2C_Start();//I2C重复起始MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//发送从机地址,读写位为1,表示即将读取MyI2C_ReceiveAck();//接收应答Data = MyI2C_ReceiveByte();//接收指定寄存器的数据MyI2C_SendAck(1);//发送应答,给从机非应答,终止从机的数据输出MyI2C_Stop();//I2C终止return Data;}/** * 函 数:MPU6050初始化 * 参 数:无 * 返 回 值:无 */void MPU6050_Init(void){MyI2C_Init();//先初始化底层的I2C/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2,保持默认值0,所有轴均不待机MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样率分频寄存器,配置采样率MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器,配置DLPFMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪配置寄存器,选择满量程为±2000°/sMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度计配置寄存器,选择满量程为±16g}/** * 函 数:MPU6050获取ID号 * 参 数:无 * 返 回 值:MPU6050的ID号 */uint8_t MPU6050_GetID(void){return MPU6050_ReadReg(MPU6050_WHO_AM_I);//返回WHO_AM_I寄存器的值}/** * 函 数:MPU6050获取数据 * 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 * 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767 * 返 回 值:无 */void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ){uint8_t DataH, DataL;//定义数据高8位和低8位的变量DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取加速度计X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取加速度计X轴的低8位数据*AccX = (DataH << 8) | DataL;//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);//读取加速度计Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);//读取加速度计Y轴的低8位数据*AccY = (DataH << 8) | DataL;//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);//读取加速度计Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);//读取加速度计Z轴的低8位数据*AccZ = (DataH << 8) | DataL;//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);//读取陀螺仪X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);//读取陀螺仪X轴的低8位数据*GyroX = (DataH << 8) | DataL;//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);//读取陀螺仪Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);//读取陀螺仪Y轴的低8位数据*GyroY = (DataH << 8) | DataL;//数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);//读取陀螺仪Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);//读取陀螺仪Z轴的低8位数据*GyroZ = (DataH << 8) | DataL;//数据拼接,通过输出参数返回}
12.mpu6050.h
#ifndef __MPU6050_H#define __MPU6050_H#include "myi2C.h"void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);uint8_t MPU6050_ReadReg(uint8_t RegAddress);void MPU6050_Init(void);uint8_t MPU6050_GetID(void);void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);#endif
13.mpu6050_Reg.h
#ifndef __MPU6050_REG_H#define __MPU6050_REG_H#defineMPU6050_SMPLRT_DIV0x19#defineMPU6050_CONFIG0x1A#defineMPU6050_GYRO_CONFIG0x1B#defineMPU6050_ACCEL_CONFIG0x1C#defineMPU6050_ACCEL_XOUT_H0x3B#defineMPU6050_ACCEL_XOUT_L0x3C#defineMPU6050_ACCEL_YOUT_H0x3D#defineMPU6050_ACCEL_YOUT_L0x3E#defineMPU6050_ACCEL_ZOUT_H0x3F#defineMPU6050_ACCEL_ZOUT_L0x40#defineMPU6050_TEMP_OUT_H0x41#defineMPU6050_TEMP_OUT_L0x42#defineMPU6050_GYRO_XOUT_H0x43#defineMPU6050_GYRO_XOUT_L0x44#defineMPU6050_GYRO_YOUT_H0x45#defineMPU6050_GYRO_YOUT_L0x46#defineMPU6050_GYRO_ZOUT_H0x47#defineMPU6050_GYRO_ZOUT_L0x48#defineMPU6050_PWR_MGMT_10x6B#defineMPU6050_PWR_MGMT_20x6C#defineMPU6050_WHO_AM_I0x75#endif
14.main.c
#include "ti_msp_dl_config.h"#include "oled.h"#include "mpu6050.h"#include "ti/driverlib/dl_gpio.h"int16_t AX,AY,AZ,GX,GY,GZ;int main(void){ SYSCFG_DL_init(); OLED_Init(); OLED_CLS(); MPU6050_Init(); //MPU6050_WriteReg(0x19,0x00); while (1) { MPU6050_GetData(&AX,&AY,&AZ,&GX,&GY,&GZ); OLED_ShowString(2,1,"AX:");OLED_ShowSignedNum(2,4,AX,4); OLED_ShowString(3,1,"AY:");OLED_ShowSignedNum(3,4,AY,4); OLED_ShowString(4,1,"AZ:");OLED_ShowSignedNum(4,4,AZ,4); OLED_ShowString(2,9,"GX:");OLED_ShowSignedNum(2,12,AX,4); OLED_ShowString(3,9,"GY:");OLED_ShowSignedNum(3,12,GY,4); OLED_ShowString(4,9,"GZ:");OLED_ShowSignedNum(4,12,GZ,4); }}
15.总结
在syscfg中配置好MPU6050的SDA和SCL引脚,通过软件模拟IIC时序,对MPU6050进行读写操作。作为嵌入式的小伙伴们,都知道MPU6050的重要性,它可以用于飞行控制和姿态稳定,提供的角速度和加速度数据对于无人机的稳定飞行至关重要,能够帮助无人机实现精确的姿态调整和位置控制,在智能小车中,MPU6050可用于检测机器人的姿态和运动状态,帮助机器人实现自主导航和避障。
现在比较成功的案例是嘉立创的1306系列的开发板,但是讲解资料少,羞涩难懂,小编这个成功地将江科大讲解的MPU6050移植到M0G3507,视频讲解细致,函数形式和参数通俗易懂,包会的!!需要完整的MPU6050代码可以留言哦,然后可以搭配自己的OLED进行结果的展示,这是小编花了两天多的时间才成功移植,成功地读取到了陀螺仪的六轴数据,接下来就可以进行姿态解算继续完成M0G3507的项目啦!
MPU6050引脚 | M0G3507引脚 |
GND | GND |
SCL | PA9 |
SDA | PA8 |
VCC | VCC |
参考资料:STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili
百度网盘,无需密码,配合自己的OLED