本次课程采用单片机型号为STM32F103C8T6。(鉴于笔者实验时身边只有STM32F103ZET6,故本次实验使基于ZET6进行的)
课程链接:江协科技 STM32入门教程
往期笔记链接:
STM32学习笔记(一)丨建立工程丨GPIO 通用输入输出
STM32学习笔记(二)丨STM32程序调试丨OLED的使用
STM32学习笔记(三)丨中断系统丨EXTI外部中断
STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)
STM32学习笔记(五)丨TIM定时器及其应用(输出比较丨PWM驱动呼吸灯、舵机、直流电机)
STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)
STM32学习笔记(七)丨TIM定时器及其应用(编码器接口丨用定时器实现编码器测速)
STM32学习笔记(八)丨ADC模数转换器(ADC单、双通道转换)
STM32学习笔记(九)丨DMA直接存储器存取(DMA数据转运、DMA+AD多通道转换)
STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担。
STM32F103C8T6 硬件I2C资源:I2C1、I2C2。(本次实验使用STM32F103ZET6)
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,结合PID算法,常应用于小车走直线,平衡车、飞行器等需要检测自身姿态的场景。
加速度和角速度都无法单独得到当前仪器的姿态,如果要得到仪器的姿态可以将所得数据进行数据解算。MPU6050拥有内部的数字运动处理器DMP(Digital Motion Processor),它是MPU6050自带的硬件姿态解算算法。可以使用官方的DMP库方便地实现姿态解算得到角度。
MPU6050可以外扩更多的测量计,例如三轴磁力计来矫正方向,气压计来测量高度。添加更多的测量计可以使其成为9轴/10轴的姿态传感器。
16位ADC采集传感器的模拟信号,量化范围:-32768~32767
加速度计满量程选择:±2、±4、±8、±16(g)
陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)
可配置的数字低通滤波器
可配置的时钟源
可配置的采样分频
I2C从机地址:1101000(AD0=0),1101001(AD0=1)
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和引脚时,只需要更改以下的宏定义即可
// 需要注意:请检查使用的GPIO是否是APB2总线的外设,如果不是,则需要更改MyI2C_Init函数中的RCC_APB2PeriphClockCmd函数名称
#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端口初始化函数
* @param 无
* @retval 无
*/
void MyI2C_Init(void)
{
// 开启SCL和SDA对应GPIO的时钟
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信号兼容重复起始信号RS
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
/**
* @brief 软件I2C的结束信号
* @param 无
* @retval 无
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
/**
* @brief 发送一个字节
* @param Byte 发送的字节数据
* @retval 无
*/
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
// SCL产生一个正脉冲,让从机读取数据
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
/**
* @brief 接收一个字节
* @param 无
* @retval 接收的字节数据
*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1); // 主机释放SDA,交出SDA控制权
for (i = 0; i < 8; i ++)
{
// 在SCL高电平期间读取SDA,如果SDA为1,则将Byte对应位置1(高位先行)
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1)
{
Byte |= (0x80 >> i);
}
MyI2C_W_SCL(0);
}
return Byte;
}
/**
* @brief 发送应答,以通知从机数据是是否发送结束
* @param AckBit 0为发送未结束,1为发送已结束
* @retval 无
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
// SCL产生一个正脉冲,让从机读取数据
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
/**
* @brief 接受应答,主机读取该信号以确认从机是否接受到数据
* @param 无
* @retval 应答信号,0为已收到(从机受到后下拉SDA),1为未收到
*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1); // 主机释放SDA,交出SDA控制权
// 在SCL高电平期间读取SDA,如果SDA为1,则将AckBit置1
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
// I2C地址应答测试程序,在main函数中可以循环以下过程来遍历I2C地址,以检查从机是否能正常应答
uint8_t ACK;
MyI2C_Start();
MyI2C_SendByte(0xD0); // 0xD0为MPU6050的I2C地址和读写操作复合而成的地址
ACK = MyI2C_ReceiveAck();
MyI2C_Stop();
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寄存器(默认数值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 向芯片内部的指定地址写
* @param RegAddress 芯片内部的寄存器地址
* @param Data 要写入的数据
* @retval 无
*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS_W);
MyI2C_ReceiveAck(); // 这里可以对获取到的回应信号进行处理
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
/**
* @brief MPU6050 向芯片内部的指定地址读
* @param RegAddress 要读取的寄存器在芯片内部的地址
* @retval 读取的数据
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
// 向MPU6050发送将要读的地址
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS_W);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start(); // 重复启动信号
MyI2C_SendByte(MPU6050_ADDRESS_R); // 发送读地址,让出SDA控制权
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1); // 向从机发送应答信号(不响应),从机终止数据发送
MyI2C_Stop();
return Data;
}
/**
* @brief MPU6050初始化函数
* @param 无
* @retval 无
*/
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 不复位,关闭睡眠模式,不循环,使能温度传感器,选择X轴陀螺仪的内部震荡电路作为系统时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 不需要设置循环模式的唤醒频率, 六个轴都不需要待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 设置采样分频, 这里选择10分频
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 不需要外部同步, 数字低通滤波设置为最高(最平滑)
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 角速度计配置:不自测(高三位为自测使能, 手册有遗漏), 设计为最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度计配置:不自测,选择为最大量程,不使用高通滤波器
}
/**
* @brief 获取MPU6050的ID值,可根据ID值检查STM32和MPU6050之间是否正常通信
* @param 无
* @retval ID值
*/
uint8_t MPU6050_ReadID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
/**
* @brief 获取并返回MPU6050六轴传感器的返回值
* @param 无
* @retval 通过参数指针操作(返回)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);
}
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);
}
}
// I2C外设缺省配置
void I2C_DeInit(I2C_TypeDef* I2Cx);
// I2C外设初始化函数
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
// 初始化结构体的缺省初始化
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
// I2C外设的开关控制函数
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 生成起始信号
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 生成结束信号
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 在主机接收数据后是否相应从机配置
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
// 主机发送数据
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
// 主机接收数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
// 7位地址模式发送函数(该函数功能也可由数据发送函数实现)
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
- 关于I2C状态监测方案(来自文件
stm32f10x_i2c.h
)
该I2C驱动程序提供了三种不同的I2C状态监测方法,根据应用需求和限制而定:
- 基本状态监测(本次实验采用此方案):
使用I2C_CheckEvent()函数:它将状态寄存器(SR1和SR2)的内容与给定的事件进行比较(可以是一个或多个标志的组合)。如果当前状态包含给定的标志,则返回SUCCESS,如果当前状态中缺少一个或多个标志,则返回ERROR。
- 使用时机:
对于大多数应用程序以及启动活动,此函数是合适的,因为在产品参考手册(RM0008)中对事件进行了详细描述。
对于需要定义自己的事件的用户也是合适的。- 限制:
如果发生错误(即除了被监测的标志之外,设置了错误标志),I2C_CheckEvent()函数可能会返回SUCCESS,尽管通信暂停或实际状态已损坏。在这种情况下,建议使用错误中断来监测错误事件,并在中断IRQ处理程序中处理它们。对于错误管理,建议使用以下函数:
I2C_ITConfig()用于配置和使能错误中断(I2C_IT_ERR)。
I2Cx_ER_IRQHandler(),在发生错误中断时调用该函数。其中x是外设实例(I2C1、I2C2等)。
在I2Cx_ER_IRQHandler()中调用I2C_GetFlagStatus()或I2C_GetITStatus(),以确定发生了哪个错误。
调用I2C_ClearFlag()或I2C_ClearITPendingBit()和/或I2C_SoftwareResetCmd(),和/或I2C_GenerateStop()以清除错误标志和源,并恢复正确的通信状态。
- 高级状态监测:
使用函数I2C_GetLastEvent(),它以一个单独的字(uint32_t)返回两个状态寄存器的图像(状态寄存器2的值左移16位并连接到状态寄存器1)。
- 使用时机:
对于上述相同的应用程序,该函数也是合适的,但它允许克服I2C_GetFlagStatus()函数的限制(见下文)。返回的值可以与库(stm32f10x_i2c.h)中已定义的事件进行比较,或与用户定义的自定义值进行比较。
当同时监测多个标志时,该函数是合适的。- 限制:
用户可能需要定义自己的事件。
如果用户决定仅检查常规通信标志(并忽略错误标志),则与该函数相关的错误管理的相同备注适用。
- 基于标志的状态监测:
使用函数I2C_GetFlagStatus(),它简单地返回一个单独标志的状态(如I2C_FLAG_RXNE …)。
- 使用时机:
该函数可用于特定应用程序或调试阶段。
当只需要检查一个标志时,它是合适的(大多数I2C事件通过多个标志进行监测)。- 限制:
调用该函数时,将访问状态寄存器。访问状态寄存器时,某些标志会被清除。因此,检查一个标志的状态可能会清除其他标志。
为了监测一个单一事件,可能需要调用该函数两次或更多次。
下面是和状态检测相关的库函数:
// 获取当前事件是否发生
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);
// 获取当前状态寄存器的值(两个16位寄存器拼接而成的数据)
uint32_t I2C_GetLastEvent(I2C_TypeDef* I2Cx);
// 获取当前的状态标志位
FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
// 清除当前的状态标志位
void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
// 获取中断标志位
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
// 清除中断标志位
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
软件和硬件I2C通信在本次实验中实验数据和现象完全相同,仅在通信层有区别。在算法实现时,MPU6050.c
模块不在需要继承软件通信协议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有些许区别
// 用宏定义在一定程度上实现解耦
// 重定义GPIO端口,需要注意使用的GPIO如果不是APB2的外设需要更改MPU6050_Init函数
#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外设端口,这里同样需要检查使用的I2C外设是否是APB1的外设
#define I2C_Periph_CLK RCC_APB1Periph_I2C1
#define I2C_Periph I2C1
/**
* @brief I2C硬件读写的事件等待发生函数,即等待某事件发生
* @param I2Cx 操作的I2Cx外设
* @param I2C_EVENT 要等待的事件,该参数的可取值在stm32f10x_i2c.c文件中
* @retval 无
*/
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t TimeOut = 10000; // 等待的时间值,可以由多次实验调试确定
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
TimeOut --;
if (TimeOut == 0)
{
/* 可在此进行错误和故障处理 */
break;
}
}
}
/**
* @brief MPU6050 向芯片内部的指定地址写
* @param RegAddress 芯片内部的寄存器地址
* @param Data 要写入的数据
* @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, 等待起始条件已发送,事件发生(主模式已选择)
I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Transmitter);
/* 在库函数中,发送函数都自带接收应答的过程,接收函数都自带发送应答的过程 */
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // EV6, I2C地址和写命令已发送
I2C_SendData(I2C_Periph, RegAddress); // 发送寄存器地址
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTING); // EV8, 数据正在发送(DR非空)
I2C_SendData(I2C_Periph, Data); // 发送寄存器数据
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // EV8_2, 数据发送已完成
I2C_GenerateSTOP(I2C_Periph, ENABLE);
}
/**
* @brief MPU6050 向芯片内部的指定地址读
* @param RegAddress 要读取的寄存器在芯片内部的地址
* @retval 读取的数据
*/
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, 起始条件已发送(主模式已选择)
I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // EV6, I2C地址和写命令已发送
I2C_SendData(I2C_Periph, RegAddress);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // EV8_2, 等待数据(寄存器地址)发送已完成
I2C_GenerateSTART(I2C_Periph, ENABLE); // 重复起始条件
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_MODE_SELECT); // EV5, 起始条件已发送(主模式已选择)
I2C_Send7bitAddress(I2C_Periph, MPU6050_ADDRESS, I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); // EV6, I2C地址和读命令已发送
// 如果要读取的数据使最后一个数据, 则在执行读命令之前, 就将ACK置0(不响应), STOP置1(结束条件)
I2C_AcknowledgeConfig(I2C_Periph, DISABLE);
I2C_GenerateSTOP(I2C_Periph, ENABLE);
MPU6050_WaitEvent(I2C_Periph, I2C_EVENT_MASTER_BYTE_RECEIVED); // EV7, RxNE = 1, 已收到一个字节
Data = I2C_ReceiveData(I2C_Periph); // 取走DR的值, 并存放在Data变量中
return Data;
}
/**
* @brief MPU6050初始化函数
* @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; // 复用开漏模式, 将GPIO端口的控制权交给片上外设
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; // 标准模式,时钟频率为100kHz(该参数最大不能超过400kHz)
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 快速模式下的时钟占空比, 在标准模式下该参数没有作用
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 主机接收一个数据后是否响应从机, 该参数也可以由独立的函数进行配置
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 地址的位数
I2C_InitStructure.I2C_OwnAddress1 = 0x00; // STM32从模式下的自身地址, 主模式下没有作用
I2C_Init(I2C_Periph, &I2C_InitStructure);
I2C_Cmd(I2C_Periph, ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); // 不复位,关闭睡眠模式,不循环,使能温度传感器,选择X轴陀螺仪的内部震荡电路作为系统时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); // 不需要设置循环模式的唤醒频率, 六个轴都不需要待机
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 设置采样分频, 这里选择10分频
MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 不需要外部同步, 数字低通滤波设置为最高(最平滑)
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 角速度计配置:不自测(高三位为自测使能, 手册有遗漏), 设计为最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); // 加速度计配置:不自测,选择为最大量程,不使用高通滤波器
}
/**
* @brief 获取MPU6050的ID值,可根据ID值检查STM32和MPU6050之间是否正常通信
* @param 无
* @retval ID值
*/
uint8_t MPU6050_ReadID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
/**
* @brief 获取并返回MPU6050六轴传感器的返回值
* @param 无
* @retval 通过参数指针操作(返回)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);
}
课程链接:江协科技 STM32入门教程,欢迎大家一起交流学习。
持续更新完善中……
原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~