系列文章目录
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:
#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_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:
#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:
#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);