STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)

本篇文章包含的内容

  • 一、I2C原理简介
    • 1.1 I2C通信协议
    • 1.2 STM32的I2C外设
  • 二、MPU6050简介
  • 三、代码实现
    • 3.1 软件模拟的I2C通信
      • 3.1.1 I2C软件模拟通信(协议)层
      • 3.1.2 MPU6050设备操作层
      • 3.1.3 主函数逻辑层
    • 3.2 使用STM32的I2C外设实现I2C通信
      • 3.2.1 常用库函数
      • 3.2.2 代码实现

​  本次课程采用单片机型号为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多通道转换)


一、I2C原理简介

1.1 I2C通信协议

1.2 STM32的I2C外设

  STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担。

  • 支持多主机模型(STM32的I2C是基于多主机模型设计的,如果在使用时不加改变,默认上电时STM32的I2C处于从模式)
  • 支持7位/10位地址模式
  • 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)(由于是同步通信方式,I2C通信对时序的要求并不像串口通信那样严格)
  • 支持DMA
  • 兼容SMBus协议

  STM32F103C8T6 硬件I2C资源:I2C1、I2C2。(本次实验使用STM32F103ZET6)

STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)_第1张图片
STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)_第2张图片
STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)_第3张图片
STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)_第4张图片

二、MPU6050简介

STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)_第5张图片

  MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,结合PID算法,常应用于小车走直线,平衡车、飞行器等需要检测自身姿态的场景。

  • 3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度。测量出的加速度具有静态稳定性,不具有动态稳定性。
  • 3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度。测量出的角速度具有动态稳定性,不具有静态稳定性。

  加速度和角速度都无法单独得到当前仪器的姿态,如果要得到仪器的姿态可以将所得数据进行数据解算。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)

STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)_第6张图片
  套件中使用的模块电路图如下所示:
STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)_第7张图片

三、代码实现

3.1 软件模拟的I2C通信

3.1.1 I2C软件模拟通信(协议)层

  • 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();

3.1.2 MPU6050设备操作层

  • 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);
}

3.1.3 主函数逻辑层

  • 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外设实现I2C通信

3.2.1 常用库函数

// 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状态监测方法,根据应用需求和限制而定:
  1. 基本状态监测(本次实验采用此方案)
      使用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()以清除错误标志和源,并恢复正确的通信状态。
  1. 高级状态监测:
      使用函数I2C_GetLastEvent(),它以一个单独的字(uint32_t)返回两个状态寄存器的图像(状态寄存器2的值左移16位并连接到状态寄存器1)。
  • 使用时机:
      对于上述相同的应用程序,该函数也是合适的,但它允许克服I2C_GetFlagStatus()函数的限制(见下文)。返回的值可以与库(stm32f10x_i2c.h)中已定义的事件进行比较,或与用户定义的自定义值进行比较。
      当同时监测多个标志时,该函数是合适的。
  • 限制:
      用户可能需要定义自己的事件。
      如果用户决定仅检查常规通信标志(并忽略错误标志),则与该函数相关的错误管理的相同备注适用。
  1. 基于标志的状态监测:
      使用函数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);

3.2.2 代码实现

  软件和硬件I2C通信在本次实验中实验数据和现象完全相同,仅在通信层有区别。在算法实现时,MPU6050.c模块不在需要继承软件通信协议MyI2C.h,所以在工程文件中可以直接删除MyI2C.cMyI2C.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入门教程,欢迎大家一起交流学习。
  持续更新完善中……


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~

你可能感兴趣的:(STM32,学习笔记,stm32,学习,笔记)