当前位置: 首页 > news >正文

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

系列文章目录

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


文章目录

  • 系列文章目录
  • 前言
  • 一、电路接线图
  • 二、应用案例代码
  • 三、应用案例分析
    • 3.1 基本思路
    • 3.2 相关库函数介绍
    • 3.3 MPU6050模块
      • 3.1.1 模块初始化
      • 3.1.2 指定地址写
      • 3.1.3 指定地址读


前言

提示:本文主要用作在学习江科大自化协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:

#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:

#ifndef __MPU6050_H
#define __MPU6050_Hvoid 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:

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS		0xD0void 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:

#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. 生成起始、终止信号

void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);

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

2. 应答位配置

void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

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

3. 发送数据

void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);

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

4. 接收数据

uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

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

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

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口的时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

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

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

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进行配置

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

I2C_Cmd(I2C2, ENABLE);

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

5. MPU6050初始化配置

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)

I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);

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

I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

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

I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);

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

I2C_SendData(I2C2, Data);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);

5. 发送终止信号

I2C_GenerateSTOP(I2C2, ENABLE);

3.1.3 指定地址读

1. 发送起始信号(EV5)

I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);

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

I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

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

I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);

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

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

I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);

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

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. 接收最后一个字节数据前设置

I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);

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

7. 接收数据(EV7)

MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);

8. 恢复默认ACK状态

I2C_AcknowledgeConfig(I2C2, ENABLE);

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 2024杭电6
  • Spring之整合Mybatis底层源码解析
  • 万字文档带你走进Python的世界
  • 一图打尽C++内存分区(分段)
  • 力扣9.2
  • World of Warcraft [CLASSIC][80][Grandel]Sapphire Hive Drone
  • Nvidia股价前景引投资者情绪波动:杠杆ETF数据透视市场风向
  • k8s集群搭建
  • 工业软件架构4:(QT和C++实现)
  • 二十五、go语言的通道
  • 代码随想录算法训练营第32天|509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
  • 8.28路虎女事件
  • 掌握 JavaScript 解构赋值的指南
  • 蜜罐网络MHN安装过程中的坑
  • Pytorch实现多层LSTM模型,并增加emdedding、Dropout、权重共享等优化
  • [LeetCode] Wiggle Sort
  • canvas绘制圆角头像
  • ES6核心特性
  • Facebook AccountKit 接入的坑点
  • iOS 颜色设置看我就够了
  • php中curl和soap方式请求服务超时问题
  • windows-nginx-https-本地配置
  • 从零搭建Koa2 Server
  • 浏览器缓存机制分析
  • 什么是Javascript函数节流?
  • 线上 python http server profile 实践
  • 想写好前端,先练好内功
  • ​MySQL主从复制一致性检测
  • ​Spring Boot 分片上传文件
  • #includecmath
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • (2)Java 简介
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (javascript)再说document.body.scrollTop的使用问题
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (二)c52学习之旅-简单了解单片机
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (三)elasticsearch 源码之启动流程分析
  • (转) ns2/nam与nam实现相关的文件
  • (转)大型网站的系统架构
  • (转)为C# Windows服务添加安装程序
  • .gitattributes 文件
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
  • .NET 使用配置文件
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .NET/C# 阻止屏幕关闭,阻止系统进入睡眠状态
  • :如何用SQL脚本保存存储过程返回的结果集
  • @Builder注释导致@RequestBody的前端json反序列化失败,HTTP400
  • @Data注解的作用
  • [ JavaScript ] JSON方法
  • [] 与 [[]], -gt 与 > 的比较
  • [Android 数据通信] android cmwap接入点