???本次課程采用單片機(jī)型號為STM32F103C8T6。(鑒于筆者實(shí)驗(yàn)時(shí)身邊只有STM32F103ZET6,故本次實(shí)驗(yàn)使基于ZET6進(jìn)行的)
???課程鏈接:江協(xié)科技 STM32入門教程
??往期筆記鏈接:
??STM32學(xué)習(xí)筆記(一)丨建立工程丨GPIO 通用輸入輸出
??STM32學(xué)習(xí)筆記(二)丨STM32程序調(diào)試丨OLED的使用
??STM32學(xué)習(xí)筆記(三)丨中斷系統(tǒng)丨EXTI外部中斷
??STM32學(xué)習(xí)筆記(四)丨TIM定時(shí)器及其應(yīng)用(定時(shí)中斷、內(nèi)外時(shí)鐘源選擇)
??STM32學(xué)習(xí)筆記(五)丨TIM定時(shí)器及其應(yīng)用(輸出比較丨PWM驅(qū)動呼吸燈、舵機(jī)、直流電機(jī))
??STM32學(xué)習(xí)筆記(六)丨TIM定時(shí)器及其應(yīng)用(輸入捕獲丨測量PWM波形的頻率和占空比)
??STM32學(xué)習(xí)筆記(七)丨TIM定時(shí)器及其應(yīng)用(編碼器接口丨用定時(shí)器實(shí)現(xiàn)編碼器測速)
??STM32學(xué)習(xí)筆記(八)丨ADC模數(shù)轉(zhuǎn)換器(ADC單、雙通道轉(zhuǎn)換)
??STM32學(xué)習(xí)筆記(九)丨DMA直接存儲器存取(DMA數(shù)據(jù)轉(zhuǎn)運(yùn)、DMA+AD多通道轉(zhuǎn)換)
一、I2C原理簡介
1.1 I2C通信協(xié)議
1.2 STM32的I2C外設(shè)
??STM32內(nèi)部集成了硬件I2C收發(fā)電路,可以由硬件自動執(zhí)行時(shí)鐘生成、起始終止條件生成、應(yīng)答位收發(fā)、數(shù)據(jù)收發(fā)等功能,減輕CPU的負(fù)擔(dān)。
- 支持多主機(jī)模型(STM32的I2C是基于多主機(jī)模型設(shè)計(jì)的,如果在使用時(shí)不加改變,默認(rèn)上電時(shí)STM32的I2C處于從模式)
- 支持7位/10位地址模式
- 支持不同的通訊速度,標(biāo)準(zhǔn)速度(高達(dá)100 kHz),快速(高達(dá)400 kHz)(由于是同步通信方式,I2C通信對時(shí)序的要求并不像串口通信那樣嚴(yán)格)
- 支持DMA
- 兼容SMBus協(xié)議
??STM32F103C8T6 硬件I2C資源:I2C1、I2C2。(本次實(shí)驗(yàn)使用STM32F103ZET6)
二、MPU6050簡介
??MPU6050是一個6軸姿態(tài)傳感器,可以測量芯片自身X、Y、Z軸的加速度、角速度參數(shù),通過數(shù)據(jù)融合,可進(jìn)一步得到姿態(tài)角,結(jié)合PID算法,常應(yīng)用于小車走直線,平衡車、飛行器等需要檢測自身姿態(tài)的場景。
- 3軸加速度計(jì)(Accelerometer):測量X、Y、Z軸的加速度。測量出的加速度具有靜態(tài)穩(wěn)定性,不具有動態(tài)穩(wěn)定性。
- 3軸陀螺儀傳感器(Gyroscope):測量X、Y、Z軸的角速度。測量出的角速度具有動態(tài)穩(wěn)定性,不具有靜態(tài)穩(wěn)定性。
??加速度和角速度都無法單獨(dú)得到當(dāng)前儀器的姿態(tài),如果要得到儀器的姿態(tài)可以將所得數(shù)據(jù)進(jìn)行數(shù)據(jù)解算。MPU6050擁有內(nèi)部的數(shù)字運(yùn)動處理器DMP(Digital Motion Processor),它是MPU6050自帶的硬件姿態(tài)解算算法。可以使用官方的DMP庫方便地實(shí)現(xiàn)姿態(tài)解算得到角度。
??MPU6050可以外擴(kuò)更多的測量計(jì),例如三軸磁力計(jì)來矯正方向,氣壓計(jì)來測量高度。添加更多的測量計(jì)可以使其成為9軸/10軸的姿態(tài)傳感器。
-
16位ADC采集傳感器的模擬信號,量化范圍:-32768~32767
-
加速度計(jì)滿量程選擇:±2、±4、±8、±16(g)
-
陀螺儀滿量程選擇: ±250、±500、±1000、±2000(°/sec)
-
可配置的數(shù)字低通濾波器
-
可配置的時(shí)鐘源
-
可配置的采樣分頻
-
I2C從機(jī)地址:1101000(AD0=0),1101001(AD0=1)
??套件中使用的模塊電路圖如下所示:
三、代碼實(shí)現(xiàn)
3.1 軟件模擬的I2C通信
3.1.1 I2C軟件模擬通信(協(xié)議)層
MyI2C.h
#ifndef __MYI2C_H_
#define __MYI2C_H_
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif
MyI2C.c
#include "stm32f10x.h" // Device header
// 更改GPIO和引腳時(shí),只需要更改以下的宏定義即可
// 需要注意:請檢查使用的GPIO是否是APB2總線的外設(shè),如果不是,則需要更改MyI2C_Init函數(shù)中的RCC_APB2PeriphClockCmd函數(shù)名稱
#define SCL_GPIO_Port_CLK RCC_APB2Periph_GPIOA
#define SDA_GPIO_Port_CLK RCC_APB2Periph_GPIOA
#define SCL_GPIO_Port GPIOA
#define SDA_GPIO_Port GPIOA
#define SCL_Pin GPIO_Pin_6
#define SDA_Pin GPIO_Pin_7
/**
* @brief 軟件I2C的GPIO端口初始化函數(shù)
* @param 無
* @retval 無
*/
void MyI2C_Init(void)
{
// 開啟SCL和SDA對應(yīng)GPIO的時(shí)鐘
RCC_APB2PeriphClockCmd(SCL_GPIO_Port_CLK, ENABLE);
RCC_APB2PeriphClockCmd(SDA_GPIO_Port_CLK, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = SCL_Pin;
GPIO_Init(SCL_GPIO_Port, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = SDA_Pin;
GPIO_Init(SDA_GPIO_Port, &GPIO_InitStructure);
GPIO_SetBits(SCL_GPIO_Port, SCL_Pin);
GPIO_SetBits(SDA_GPIO_Port, SDA_Pin);
}
/**
* @brief 控制SCL線的下拉與釋放
* @param BitValue 其值可以是0或1,0為下拉,1為釋放
* @retval
*/
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(SCL_GPIO_Port, SCL_Pin, (BitAction)BitValue);
// Delay_us(10);
}
/**
* @brief 控制SDA線的下拉與釋放
* @param BitValue 其值可以是0或1,0為下拉,1為釋放
* @retval 無
*/
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(SDA_GPIO_Port, SDA_Pin, (BitAction)BitValue);
// Delay_us(10);
}
/**
* @brief 讀取SDA
* @param 無
* @retval 讀取到SDA的高低電平值
*/
uint8_t MyI2C_R_SDA(void)
{
return GPIO_ReadInputDataBit(SDA_GPIO_Port, SDA_Pin);
// Delay_us(10);
}
/**
* @brief 軟件I2C的起始信號
* @param 無
* @retval 無
*/
void MyI2C_Start(void)
{
// 這里先釋放SDA,再釋放SCL的原因是為了使Start信號兼容重復(fù)起始信號RS
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
/**
* @brief 軟件I2C的結(jié)束信號
* @param 無
* @retval 無
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
/**
* @brief 發(fā)送一個字節(jié)
* @param Byte 發(fā)送的字節(jié)數(shù)據(jù)
* @retval 無
*/
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
// SCL產(chǎn)生一個正脈沖,讓從機(jī)讀取數(shù)據(jù)
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
/**
* @brief 接收一個字節(jié)
* @param 無
* @retval 接收的字節(jié)數(shù)據(jù)
*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1); // 主機(jī)釋放SDA,交出SDA控制權(quán)
for (i = 0; i < 8; i ++)
{
// 在SCL高電平期間讀取SDA,如果SDA為1,則將Byte對應(yīng)位置1(高位先行)
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1)
{
Byte |= (0x80 >> i);
}
MyI2C_W_SCL(0);
}
return Byte;
}
/**
* @brief 發(fā)送應(yīng)答,以通知從機(jī)數(shù)據(jù)是是否發(fā)送結(jié)束
* @param AckBit 0為發(fā)送未結(jié)束,1為發(fā)送已結(jié)束
* @retval 無
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
// SCL產(chǎn)生一個正脈沖,讓從機(jī)讀取數(shù)據(jù)
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
/**
* @brief 接受應(yīng)答,主機(jī)讀取該信號以確認(rèn)從機(jī)是否接受到數(shù)據(jù)
* @param 無
* @retval 應(yīng)答信號,0為已收到(從機(jī)受到后下拉SDA),1為未收到
*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1); // 主機(jī)釋放SDA,交出SDA控制權(quán)
// 在SCL高電平期間讀取SDA,如果SDA為1,則將AckBit置1
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
// I2C地址應(yīng)答測試程序,在main函數(shù)中可以循環(huán)以下過程來遍歷I2C地址,以檢查從機(jī)是否能正常應(yīng)答
uint8_t ACK;
MyI2C_Start();
MyI2C_SendByte(0xD0); // 0xD0為MPU6050的I2C地址和讀寫操作復(fù)合而成的地址
ACK = MyI2C_ReceiveAck();
MyI2C_Stop();
3.1.2 MPU6050設(shè)備操作層
MPU6050.h
#ifndef __MPU6050_H_
#define __MPU6050_H_
void MPU6050_Init(void);
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
uint8_t MPU6050_ReadID(void);
void MPU6050_ReadData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
#endif
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 //電源管理1
#define MPU6050_PWR_MGMT_2 0x6C //電源管理2
#define MPU6050_WHO_AM_I 0x75 //ID寄存器(默認(rèn)數(shù)值0x68,只讀)
#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS_W 0xD0
#define MPU6050_ADDRESS_R 0xD1
/**
* @brief MPU6050 向芯片內(nèi)部的指定地址寫
* @param RegAddress 芯片內(nèi)部的寄存器地址
* @param Data 要寫入的數(shù)據(jù)
* @retval 無
*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS_W);
MyI2C_ReceiveAck(); // 這里可以對獲取到的回應(yīng)信號進(jìn)行處理
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
/**
* @brief MPU6050 向芯片內(nèi)部的指定地址讀
* @param RegAddress 要讀取的寄存器在芯片內(nèi)部的地址
* @retval 讀取的數(shù)據(jù)
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
// 向MPU6050發(fā)送將要讀的地址
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS_W);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start(); // 重復(fù)啟動信號
MyI2C_SendByte(MPU6050_ADDRESS_R); // 發(fā)送讀地址,讓出SDA控制權(quán)
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1); // 向從機(jī)發(fā)送應(yīng)答信號(不響應(yīng)),從機(jī)終止數(shù)據(jù)發(fā)送
MyI2C_Stop();
return Data;
}
/**
* @brief MPU6050初始化函數(shù)
* @param 無
* @retval 無
*/
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 不復(fù)位,關(guān)閉睡眠模式,不循環(huán),使能溫度傳感器,選擇X軸陀螺儀的內(nèi)部震蕩電路作為系統(tǒng)時(shí)鐘
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 不需要設(shè)置循環(huán)模式的喚醒頻率, 六個軸都不需要待機(jī)
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 設(shè)置采樣分頻, 這里選擇10分頻
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 不需要外部同步, 數(shù)字低通濾波設(shè)置為最高(最平滑)
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 角速度計(jì)配置:不自測(高三位為自測使能, 手冊有遺漏), 設(shè)計(jì)為最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度計(jì)配置:不自測,選擇為最大量程,不使用高通濾波器
}
/**
* @brief 獲取MPU6050的ID值,可根據(jù)ID值檢查STM32和MPU6050之間是否正常通信
* @param 無
* @retval ID值
*/
uint8_t MPU6050_ReadID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
/**
* @brief 獲取并返回MPU6050六軸傳感器的返回值
* @param 無
* @retval 通過參數(shù)指針操作(返回)6個返回值
*/
void MPU6050_ReadData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
*AccX = (MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccY = (MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccZ = (MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*GyroX = (MPU6050_ReadReg(MPU6050_GYRO_XOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroY = (MPU6050_ReadReg(MPU6050_GYRO_YOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroZ = (MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
}
3.1.3 主函數(shù)邏輯層
main.c
#include "stm32f10x.h" // Device header
#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:0x");
ID = MPU6050_ReadID();
OLED_ShowHexNum(1, 6, ID, 2);
while (1)
{
MPU6050_ReadData(&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);
}
}
3.2 使用STM32的I2C外設(shè)實(shí)現(xiàn)I2C通信
3.2.1 常用庫函數(shù)
// I2C外設(shè)缺省配置
void I2C_DeInit(I2C_TypeDef* I2Cx);
// I2C外設(shè)初始化函數(shù)
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
// 初始化結(jié)構(gòu)體的缺省初始化
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
// I2C外設(shè)的開關(guān)控制函數(shù)
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 生成起始信號
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 生成結(jié)束信號
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 在主機(jī)接收數(shù)據(jù)后是否相應(yīng)從機(jī)配置
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 主機(jī)發(fā)送數(shù)據(jù)
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
// 主機(jī)接收數(shù)據(jù)
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
// 7位地址模式發(fā)送函數(shù)(該函數(shù)功能也可由數(shù)據(jù)發(fā)送函數(shù)實(shí)現(xiàn))
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
- 關(guān)于I2C狀態(tài)監(jiān)測方案(來自文件
stm32f10x_i2c.h
)
??該I2C驅(qū)動程序提供了三種不同的I2C狀態(tài)監(jiān)測方法,根據(jù)應(yīng)用需求和限制而定:
- 基本狀態(tài)監(jiān)測(本次實(shí)驗(yàn)采用此方案):
??使用I2C_CheckEvent()函數(shù):它將狀態(tài)寄存器(SR1和SR2)的內(nèi)容與給定的事件進(jìn)行比較(可以是一個或多個標(biāo)志的組合)。如果當(dāng)前狀態(tài)包含給定的標(biāo)志,則返回SUCCESS,如果當(dāng)前狀態(tài)中缺少一個或多個標(biāo)志,則返回ERROR。
- 使用時(shí)機(jī):
??對于大多數(shù)應(yīng)用程序以及啟動活動,此函數(shù)是合適的,因?yàn)樵诋a(chǎn)品參考手冊(RM0008)中對事件進(jìn)行了詳細(xì)描述。
??對于需要定義自己的事件的用戶也是合適的。- 限制:
??如果發(fā)生錯誤(即除了被監(jiān)測的標(biāo)志之外,設(shè)置了錯誤標(biāo)志),I2C_CheckEvent()函數(shù)可能會返回SUCCESS,盡管通信暫?;?qū)嶋H狀態(tài)已損壞。在這種情況下,建議使用錯誤中斷來監(jiān)測錯誤事件,并在中斷IRQ處理程序中處理它們。對于錯誤管理,建議使用以下函數(shù):
??I2C_ITConfig()用于配置和使能錯誤中斷(I2C_IT_ERR)。
??I2Cx_ER_IRQHandler(),在發(fā)生錯誤中斷時(shí)調(diào)用該函數(shù)。其中x是外設(shè)實(shí)例(I2C1、I2C2等)。
??在I2Cx_ER_IRQHandler()中調(diào)用I2C_GetFlagStatus()或I2C_GetITStatus(),以確定發(fā)生了哪個錯誤。
??調(diào)用I2C_ClearFlag()或I2C_ClearITPendingBit()和/或I2C_SoftwareResetCmd(),和/或I2C_GenerateStop()以清除錯誤標(biāo)志和源,并恢復(fù)正確的通信狀態(tài)。
- 高級狀態(tài)監(jiān)測:
??使用函數(shù)I2C_GetLastEvent(),它以一個單獨(dú)的字(uint32_t)返回兩個狀態(tài)寄存器的圖像(狀態(tài)寄存器2的值左移16位并連接到狀態(tài)寄存器1)。
- 使用時(shí)機(jī):
??對于上述相同的應(yīng)用程序,該函數(shù)也是合適的,但它允許克服I2C_GetFlagStatus()函數(shù)的限制(見下文)。返回的值可以與庫(stm32f10x_i2c.h)中已定義的事件進(jìn)行比較,或與用戶定義的自定義值進(jìn)行比較。
??當(dāng)同時(shí)監(jiān)測多個標(biāo)志時(shí),該函數(shù)是合適的。- 限制:
??用戶可能需要定義自己的事件。
??如果用戶決定僅檢查常規(guī)通信標(biāo)志(并忽略錯誤標(biāo)志),則與該函數(shù)相關(guān)的錯誤管理的相同備注適用。
- 基于標(biāo)志的狀態(tài)監(jiān)測:
??使用函數(shù)I2C_GetFlagStatus(),它簡單地返回一個單獨(dú)標(biāo)志的狀態(tài)(如I2C_FLAG_RXNE …)。
- 使用時(shí)機(jī):
??該函數(shù)可用于特定應(yīng)用程序或調(diào)試階段。
??當(dāng)只需要檢查一個標(biāo)志時(shí),它是合適的(大多數(shù)I2C事件通過多個標(biāo)志進(jìn)行監(jiān)測)。- 限制:
??調(diào)用該函數(shù)時(shí),將訪問狀態(tài)寄存器。訪問狀態(tài)寄存器時(shí),某些標(biāo)志會被清除。因此,檢查一個標(biāo)志的狀態(tài)可能會清除其他標(biāo)志。
??為了監(jiān)測一個單一事件,可能需要調(diào)用該函數(shù)兩次或更多次。
??下面是和狀態(tài)檢測相關(guān)的庫函數(shù):
// 獲取當(dāng)前事件是否發(fā)生
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);
// 獲取當(dāng)前狀態(tài)寄存器的值(兩個16位寄存器拼接而成的數(shù)據(jù))
uint32_t I2C_GetLastEvent(I2C_TypeDef* I2Cx);
// 獲取當(dāng)前的狀態(tài)標(biāo)志位
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
// 清除當(dāng)前的狀態(tài)標(biāo)志位
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
// 獲取中斷標(biāo)志位
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
// 清除中斷標(biāo)志位
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
3.2.2 代碼實(shí)現(xiàn)
??軟件和硬件I2C通信在本次實(shí)驗(yàn)中實(shí)驗(yàn)數(shù)據(jù)和現(xiàn)象完全相同,僅在通信層有區(qū)別。在算法實(shí)現(xiàn)時(shí),MPU6050.c
模塊不在需要繼承軟件通信協(xié)議MyI2C.h
,所以在工程文件中可以直接刪除MyI2C.c
和MyI2C.h
文件。
新的MPU6050.c
的代碼如下所示:
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 // 0x68 + 讀寫位,注意這里的定義和軟件模擬I2C有些許區(qū)別
// 用宏定義在一定程度上實(shí)現(xiàn)解耦
// 重定義GPIO端口,需要注意使用的GPIO如果不是APB2的外設(shè)需要更改MPU6050_Init函數(shù)
#define GPIO_Periph_CLK RCC_APB2Periph_GPIOB
#define GPIO_Periph GPIOB
#define GPIO_Pin_SCL GPIO_Pin_6
#define GPIO_Pin_SDA GPIO_Pin_7
// 重定義I2C外設(shè)端口,這里同樣需要檢查使用的I2C外設(shè)是否是APB1的外設(shè)
#define I2C_Periph_CLK RCC_APB1Periph_I2C1
#define I2C_Periph I2C1
/**
* @brief I2C硬件讀寫的事件等待發(fā)生函數(shù),即等待某事件發(fā)生
* @param I2Cx 操作的I2Cx外設(shè)
* @param I2C_EVENT 要等待的事件,該參數(shù)的可取值在stm32f10x_i2c.c文件中
* @retval 無
*/
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t TimeOut = 10000; // 等待的時(shí)間值,可以由多次實(shí)驗(yàn)調(diào)試確定
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
TimeOut --;
if (TimeOut == 0)
{
/* 可在此進(jìn)行錯誤和故障處理 */
break;
}
}
}
/**
* @brief MPU6050 向芯片內(nèi)部的指定地址寫
* @param RegAddress 芯片內(nèi)部的寄存器地址
* @param Data 要寫入的數(shù)據(jù)
* @retval 無
*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C_Periph, ENABLE);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_MODE_SELECT); // EV5, 等待起始條件已發(fā)送,事件發(fā)生(主模式已選擇)
I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Transmitter);
/* 在庫函數(shù)中,發(fā)送函數(shù)都自帶接收應(yīng)答的過程,接收函數(shù)都自帶發(fā)送應(yīng)答的過程 */
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // EV6, I2C地址和寫命令已發(fā)送
I2C_SendData(I2C_Periph, RegAddress); // 發(fā)送寄存器地址
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTING); // EV8, 數(shù)據(jù)正在發(fā)送(DR非空)
I2C_SendData(I2C_Periph, Data); // 發(fā)送寄存器數(shù)據(jù)
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // EV8_2, 數(shù)據(jù)發(fā)送已完成
I2C_GenerateSTOP(I2C_Periph, ENABLE);
}
/**
* @brief MPU6050 向芯片內(nèi)部的指定地址讀
* @param RegAddress 要讀取的寄存器在芯片內(nèi)部的地址
* @retval 讀取的數(shù)據(jù)
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C_Periph, ENABLE);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_MODE_SELECT); // EV5, 起始條件已發(fā)送(主模式已選擇)
I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // EV6, I2C地址和寫命令已發(fā)送
I2C_SendData(I2C_Periph, RegAddress);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // EV8_2, 等待數(shù)據(jù)(寄存器地址)發(fā)送已完成
I2C_GenerateSTART(I2C_Periph, ENABLE); // 重復(fù)起始條件
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_MODE_SELECT); // EV5, 起始條件已發(fā)送(主模式已選擇)
I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); // EV6, I2C地址和讀命令已發(fā)送
// 如果要讀取的數(shù)據(jù)使最后一個數(shù)據(jù), 則在執(zhí)行讀命令之前, 就將ACK置0(不響應(yīng)), STOP置1(結(jié)束條件)
I2C_AcknowledgeConfig(I2C_Periph, DISABLE);
I2C_GenerateSTOP(I2C_Periph, ENABLE);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_RECEIVED); // EV7, RxNE = 1, 已收到一個字節(jié)
Data = I2C_ReceiveData(I2C_Periph); // 取走DR的值, 并存放在Data變量中
return Data;
}
/**
* @brief MPU6050初始化函數(shù)
* @param 無
* @retval 無
*/
void MPU6050_Init(void)
{
RCC_APB1PeriphClockCmd(I2C_Periph_CLK, ENABLE);
RCC_APB2PeriphClockCmd(GPIO_Periph_CLK, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 復(fù)用開漏模式, 將GPIO端口的控制權(quán)交給片上外設(shè)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_SCL | GPIO_Pin_SDA;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO_Periph, &GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 100000; // 標(biāo)準(zhǔn)模式,時(shí)鐘頻率為100kHz(該參數(shù)最大不能超過400kHz)
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 快速模式下的時(shí)鐘占空比, 在標(biāo)準(zhǔn)模式下該參數(shù)沒有作用
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 主機(jī)接收一個數(shù)據(jù)后是否響應(yīng)從機(jī), 該參數(shù)也可以由獨(dú)立的函數(shù)進(jìn)行配置
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 地址的位數(shù)
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // STM32從模式下的自身地址, 主模式下沒有作用
I2C_Init(I2C_Periph, &I2C_InitStructure);
I2C_Cmd(I2C_Periph, ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 不復(fù)位,關(guān)閉睡眠模式,不循環(huán),使能溫度傳感器,選擇X軸陀螺儀的內(nèi)部震蕩電路作為系統(tǒng)時(shí)鐘
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 不需要設(shè)置循環(huán)模式的喚醒頻率, 六個軸都不需要待機(jī)
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 設(shè)置采樣分頻, 這里選擇10分頻
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 不需要外部同步, 數(shù)字低通濾波設(shè)置為最高(最平滑)
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 角速度計(jì)配置:不自測(高三位為自測使能, 手冊有遺漏), 設(shè)計(jì)為最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度計(jì)配置:不自測,選擇為最大量程,不使用高通濾波器
}
/**
* @brief 獲取MPU6050的ID值,可根據(jù)ID值檢查STM32和MPU6050之間是否正常通信
* @param 無
* @retval ID值
*/
uint8_t MPU6050_ReadID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
/**
* @brief 獲取并返回MPU6050六軸傳感器的返回值
* @param 無
* @retval 通過參數(shù)指針操作(返回)6個返回值
*/
void MPU6050_ReadData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
*AccX = (MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccY = (MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccZ = (MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*GyroX = (MPU6050_ReadReg(MPU6050_GYRO_XOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroY = (MPU6050_ReadReg(MPU6050_GYRO_YOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroZ = (MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H) << 8)
| MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
}
???課程鏈接:江協(xié)科技 STM32入門教程,歡迎大家一起交流學(xué)習(xí)。
??持續(xù)更新完善中……文章來源:http://www.zghlxwxcb.cn/news/detail-803082.html
??原創(chuàng)筆記,碼字不易,歡迎點(diǎn)贊,收藏~ 如有謬誤敬請?jiān)谠u論區(qū)不吝告知,感激不盡!博主將持續(xù)更新有關(guān)嵌入式開發(fā)、機(jī)器學(xué)習(xí)方面的學(xué)習(xí)筆記~文章來源地址http://www.zghlxwxcb.cn/news/detail-803082.html
到了這里,關(guān)于STM32學(xué)習(xí)筆記(十)丨I2C通信(使用I2C實(shí)現(xiàn)MPU6050和STM32之間通信)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!