stm32之硬件I2C读写MPU6050陀螺仪、加速度传感器应用案例

系列文章目录

1. stm32之I2C通信协议
2. stm32之软件I2C读写MPU6050陀螺仪、加速度传感器应用案例
3. stm32之I2C通信外设


文章目录


前言

提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者

本案例实现了一个stm32使用硬件I2C外设通信读写MPU6050陀螺仪、加速度传感器的功能,最终我们将MPU6050的实时数据显示在了OLED上。OLED最上面显示的是设备ID号,左下角三个是加速度传感器的输出数据,分别为X轴、Y轴和Z轴的加速度。右边三个是陀螺仪传感器的输出数据,分别为X轴、Y轴和Z轴的角速度。当我们改变MPU6050传感器的姿态这六个数据就会相应地变化。


一、电路接线图

在这里,stm32作为主机,MPU6050作为从机,是一主一从的模型。

接线方面,MPU6050模块的VCC和GND分别接到电源的正负极进行供电,SCL接到stm32的PB10口,SDA接到stm32的PB11口。XCL和XDA用于扩展的接口这里暂时用不到就先不接。AD0引脚可用于修改从机地址的最低位,如果有需要可以接上,这里由于模块内置了下拉电阻,所以引脚悬空的话相当于接地。最后INT,中断信号输出脚,我们暂时用不到先不接。

由于本次我们使用的是I2C2外设进行硬件I2C通信,经查阅引脚定义表可知I2C2_SCL接在了PB10,I2C2_SDA接在了PB11上,这个注意不要接错了。

注意:根据I2C协议的硬件电路规定SCL和SDA都应该外挂一个上拉电阻的,但是由于MPU6050模块本身内部就已经集成了上拉电阻了,所以这里就不需要再外挂上拉电阻了。

二、应用案例代码

MPU6050_Reg.h:

c 复制代码
#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

MPU6050.h:

c 复制代码
#ifndef __MPU6050_H
#define __MPU6050_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

MPU6050.c:

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

#define MPU6050_ADDRESS		0xD0

void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;
	while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;
		}
	}
}

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	I2C_SendData(I2C2, RegAddress);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
	
	I2C_SendData(I2C2, Data);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	I2C_GenerateSTOP(I2C2, ENABLE);
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2, ENABLE);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	I2C_SendData(I2C2, RegAddress);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	I2C_GenerateSTART(I2C2, ENABLE);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	return Data;
}

void MPU6050_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	I2C_InitStructure.I2C_ClockSpeed = 50000;
	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;
	I2C_Init(I2C2, &I2C_InitStructure);
	
	I2C_Cmd(I2C2, ENABLE);
	
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

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;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}

主程序main.c:

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#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:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
	while (1)
	{
		MPU6050_GetData(&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);
	}
}

完整工程:stm32之硬件I2C读写MPU6050陀螺仪、加速度传感器

三、应用案例分析

3.1 基本思路

  • 第一步,开启I2C外设和对应GPIO口的时钟。
  • 第二步,把I2C外设对应的GPIO口初始化为复用开漏输出模式。
  • 第三步,使用结构体对整个I2C进行配置。
  • 第四步,I2C_Cmd,使能I2C。

以上就是I2C外设配置的基本思路了,接下来我们只需要在上一章软件I2C通信模块的基础上进行修改即可,把软件实现的时序用硬件I2C接口替换掉。

3.2 相关库函数介绍

在这里我们只选择部分重点需要掌握的函数进行讲解,一些讲烂了的函数就不讲了,比如初始化什么的,其实写完了就会发现套路都是差不多的,只是外设不一样而已。

1. 生成起始、终止信号

c 复制代码
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

I2C_GenerateSTART函数用于生成起始信号,I2C_GenerateSTOP函数用于生成终止信号。

2. 应答位配置

c 复制代码
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

该函数用于配置应答位,ENABLE就是应答,DISABLE就是非应答,原理就是操作控制寄存器ACK位。

3. 发送数据

c 复制代码
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);

该函数用于发送一个字节数据,其实现原理就是将一个字节的数据写入到DR数据寄存器。

4. 接收数据

c 复制代码
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

该函数用于接收一个字节数据,其实现原理就是从DR寄存器中读取一个字节数据。

5. 发送七位地址专用函数

c 复制代码
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);

该函数用于进行从机寻址时序,即从机地址(7位)+读写标志位(1位),第二个参数I2C_Direction决定读写标志位,当指定为I2C_Direction_Transmitter时表示为写,当指定为I2C_Direction_Receiver时表示为读。

6. I2C事件状态监控函数

|----------|--------------------------------------------------------------------------------------------------------------|
| 函数原型 | ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT); |
| 功能 | 该函数用于检查指定的 I2C 事件是否发生 |
| 参数 |
| 参数 | I2Cx: 指定要使用的 I2C 外设,可以是 I2C1、I2C2 或其他可用的 I2C 实例 |
| 参数 | I2C_EVENT: 指定要检查的 I2C 事件 |
| 返回值 | typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrorStatus; 成功返回SUCCESS,表示指定的 I2C 事件发生。失败返回ERROR,表示指定的 I2C 事件未发生 |

I2C_EVENT可以是预定义的一些事件常量,如:

事件 描述
I2C_EVENT_MASTER_MODE_SELECT 主机模式选择事件(EV5)
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED 主机发送模式选择事件(EV6)
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED 主机接收模式选择事件(EV6)
I2C_EVENT_MASTER_BYTE_TRANSMITTING 主机字节正在发送中(EV8)
I2C_EVENT_MASTER_BYTE_TRANSMITTED 主机字节发送完成(EV8_2)
I2C_EVENT_MASTER_BYTE_RECEIVED 主机字节接收完成(EV7)

3.3 MPU6050模块

MPU6050模块主要封装了一个模块初始化函数以及一个指定地址写函数和一个指定地址读函数。

3.1.1 模块初始化

1. 开启I2C外设和对应GPIO口的时钟

c 复制代码
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

这里I2C1和I2C2都是APB1的外设,注意不要搞错了。

2. 把I2C外设对应的GPIO口初始化为复用开漏输出模式

c 复制代码
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

3. 使用结构体对整个I2C进行配置

c 复制代码
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
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;
I2C_Init(I2C2, &I2C_InitStructure);

I2C_Mode: I2C 模式配置,这里选择I2C_Mode_I2C,即标准的 I2C 模式。STM32 的 I2C 外设还支持 I2C 模式和 SMBus 模式。

I2C_ClockSpeed: 时钟速度配置,这里选择50KHz。该参数控制 SCL 引脚的频率,进而影响 I2C 总线的通信速率。典型的 I2C 时钟速度可以是 100 kHz(标准模式),400 kHz(快速模式),或更高的速率,具体根据应用需求设置。

I2C_DutyCycle: 占空比配置,这里选择I2C_DutyCycle_2,即设置 I2C 时钟的占空比为 2(即高电平与低电平时间的比例)。该参数仅在 I2C 工作在快速模式(400 kHz 或更高)时有效,用于调整时钟信号的高低电平时间比例。

I2C_Ack: 应答配置,这里选择I2C_Ack_Enable,即启用 I2C 的应答功能(ACK)。当 I2C 接收到数据后,启用 ACK 功能可以让从设备(或主设备)自动发送一个应答信号,以表示数据接收成功。

I2C_AcknowledgedAddress: 应答地址模式配置,这里选择了I2C_AcknowledgedAddress_7bit,即将 I2C 的应答地址模式设置为 7 位地址模式。I2C 设备地址可以是 7 位或 10 位,此处选择 7 位模式。

I2C_OwnAddress1: 设备地址配置,这里选择了0x00,即设置当前 I2C 外设的主地址为 0x00。在从设备模式下,这个地址用于从设备识别自己的地址。在主设备模式下,地址参数的作用较小。

4. I2C_Cmd,使能I2C

c 复制代码
I2C_Cmd(I2C2, ENABLE);

到这里,整个I2C2外设就初始化完了,接下来就是进行MPU6050模块的初始化配置了。

5. MPU6050初始化配置

c 复制代码
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//接触睡眠,选择陀螺仪时钟
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);

3.1.2 指定地址写

1. 发送起始信号(EV5)

c 复制代码
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);

2. 发送从机地址和方向位(EV6)

c 复制代码
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

3. 发送数据(寄存器地址)(EV8)

c 复制代码
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);

4. 发送数据(写入寄存器数据)(EV8_2)

c 复制代码
I2C_SendData(I2C2, Data);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);

5. 发送终止信号

c 复制代码
I2C_GenerateSTOP(I2C2, ENABLE);

3.1.3 指定地址读

1. 发送起始信号(EV5)

c 复制代码
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);

2. 发送从机地址和方向位(EV6)

c 复制代码
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

3. 发送数据(寄存器地址)(EV8_2)

c 复制代码
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);

注意:这里由于是写时序的最后一个发送数据,所以还是使用I2C_EVENT_MASTER_BYTE_TRANSMITTED等待EV8_2稳妥点。

4. 重新发送起始信号(EV5)

c 复制代码
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);

5. 发送从机地址和方向位(EV6)

c 复制代码
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);

注意:这里有两个EV6事件,分别是主机作为发送端的I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED和主机作为接收端的I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED,不要搞错了。

6. 接收最后一个字节数据前设置

c 复制代码
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);

在接收最后一个字节之前设置ACK为非应答,设置停止位。

7. 接收数据(EV7)

c 复制代码
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);

8. 恢复默认ACK状态

c 复制代码
I2C_AcknowledgeConfig(I2C2, ENABLE);
相关推荐
北京迅为3 小时前
【北京迅为】iTOP-4412全能版使用手册- 第五章 Linux常用命令
linux·嵌入式硬件·4412开发板
LinuxST4 小时前
27、基于Firefly-rk3399中断休眠唤醒实验(按键中断)
linux·开发语言·stm32·嵌入式硬件
水饺编程5 小时前
【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-32
linux·嵌入式硬件·fpga开发·硬件架构
生活很暖很治愈6 小时前
C51数字时钟/日历---LCD1602液晶显示屏
数据库·单片机·mongodb
Shaun_青璇7 小时前
STM32 流水灯实验
stm32·单片机·嵌入式硬件·mcu·物联网
吗喽也有梦想10 小时前
STM32F4----DCA数字量转换成模拟量
stm32·单片机·嵌入式硬件
@@庆11 小时前
任务中断的两套API函数(改进FormISR的实时性)资源管理_互斥操作的本质(解决DH11经常出错的问题)
arm开发·stm32·单片机·嵌入式硬件·freertos
mftang12 小时前
STM32 Nucleo-64 boards板卡介绍
stm32·单片机·嵌入式硬件
code_snow12 小时前
STM32-- 技巧-延时
stm32·单片机·嵌入式硬件
霖霖71412 小时前
基本功能实现
单片机·嵌入式硬件