經過兩天兩夜的查閱文獻資料、整理學習,成功的把江科大的軟件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);elseDL_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);elseDL_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高電平期間讀取SDASDA_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);elseDL_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);elseDL_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高電平期間讀取SDASDA_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_ADDRESS 0xD0 //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#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
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
完整ccs theia工程