一:基础知识
1-1:I2C通信
1-1-1:I2C介绍
•
I2C
(
Inter IC Bus
)是由
Philips
公司开发的一种通用数据总线
•
两根通信线:
SCL
(
Serial Clock
)、
SDA
(
Serial Data
)
•
同步,半双工
•
带数据应答
•
支持总线挂载多设备(一主多从、多主多从)
1-1-2:硬件电路
•
所有
I2C
设备的
SCL
连在一起,
SDA
连在一起
•
设备的
SCL
和
SDA
均要配置成开漏输出模式
•
SCL
和
SDA
各添加一个上拉电阻,阻值一般为
4.7KΩ
左右

1-1-3:I2C时序基本单元I2C时序基本单元I2C时序基本单元
•
起始条件:
SCL
高电平期间,
SDA
从高电平切换到低电平
终止条件:
SCL
高电平期间,
SDA
从低电平切换到高电平

•
发送一个字节:
SCL
低电平期间,主机将数据位依次放到
SDA
线上(高位先行),然后释放
SCL
,从机将在
SCL
高电平期间读取数据位,所以
SCL
高电平期间
SDA
不允许有数据变化,依次循环上述过程
8
次,即可发送一个字节

•
接收一个字节:
SCL
低电平期间,从机将数据位依次放到
SDA
线上(高位先行),然后释放
SCL
,主机将在
SCL
高电平期间读取数据位,所以
SCL
高电平期间
SDA
不允许有数据变化,依次循环上述过程
8
次,即可接收一个字节(主机在接收之前,需要释放
SDA
)

•
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据
0
表示应答,数据
1
表示非应答
•
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据
0
表示应答,数据
1
表示非应答(主机在接收之前,需要释放
SDA
)

1-1-4:I2C时序
•
指定地址写
•
对于指定设备(
Slave Address
),在指定地址(
Reg Address
)下,写入指定数据(
Data
)

•
当前地址读
•
对于指定设备(
Slave Address
),在当前地址指针指示的地址下,读取从机数据(
Data
)

•
指定地址读
•
对于指定设备(
Slave Address
),在指定地址(
Reg Address
)下,读取从机数据(
Data
)

1-2:MPU6050介绍
1-2-1:MPU6050简介
•
MPU6050
是一个
6
轴姿态传感器,可以测量芯片自身
X
、
Y
、
Z
轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
•
3
轴加速度计(
Accelerometer
):测量
X
、
Y
、
Z
轴的加速度
•
3
轴陀螺仪传感器(
Gyroscope
):测量
X
、
Y
、
Z
轴的角速度


1-2-2:MPU6050参数
•
16
位
ADC
采集传感器的模拟信号,量化范围:
-32768~32767
•
加速度计满量程选择:
±2
、
±4
、
±8
、
±16
(
g
)
•
陀螺仪满量程选择:
±250
、
±500
、
±1000
、
±2000
(
°/sec
)
•
可配置的数字低通滤波器
•
可配置的时钟源
•
可配置的采样分频
1-2-3:硬件电路
引脚定义
XDA XCL 用于外接磁力计将6轴传感器扩展到9轴
1-2-4:结构框图
二:程序代码编写
2-1:Cube MX配置

默认开启I2C1和USART1即可,USART用于后期通过串口进行调试和查看数据
2-2:代码编写
2-2-1:初始化I2C接口
根据手册的第9章接口部分描述:

MPU6050作为从机与系统通信的最大速率为400KHz,且从机地址为b110100(x由ADO引脚决定,接地时为0,接VCC为1)
2-2-2:操作寄存器
以下截至寄存器组手册:



定义MPU6050内部地址为(传感器采样率,加速度和陀螺仪的量程我们取典型值):
#define MPU6050_SMPLRT_DIV 0x19 // 陀螺仪采样率,典型值:0x07(125Hz)
#define MPU6050_CONFIG 0x1A // 低通滤波频率,典型值:0x06(5Hz)
#define MPU6050_GYRO_CONFIG 0x1B // 陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define MPU6050_ACCEL_CONFIG 0x1C // 加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#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 //电源管理,典型值:0x00(正常启用)
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
2-2-2-1:写寄存器
代码如下:
/**********************************************************************
* 函数名称: MPU6050_WriteRegister
* 功能描述: 写MPU6050寄存器
* 输入参数: reg-寄存器地址, data-要写入的数据
* 输出参数: 无
* 返 回 值: 0 - 成功, 其他值 - 失败
***********************************************************************/
static int MPU6050_WriteRegister(uint8_t reg, uint8_t data)
{
uint8_t tmpbuf[2];
tmpbuf[0] = reg;
tmpbuf[1] = data;
return HAL_I2C_Master_Transmit(g_pHI2C_MPU6050, MPU6050_I2C_ADDR, tmpbuf, 2, MPU6050_TIMEOUT);
}
2-2-2-2:读寄存器

代码如下:
/**********************************************************************
* 函数名称: MPU6050_ReadRegister
* 功能描述: 读MPU6050寄存器
* 输入参数: reg-寄存器地址
* 输出参数: pdata-用来保存读出的数据
* 返 回 值: 0 - 成功, 其他值 - 失败
***********************************************************************/
int MPU6050_ReadRegister(uint8_t reg, uint8_t *pdata)
{
return HAL_I2C_Mem_Read(g_pHI2C_MPU6050, MPU6050_I2C_ADDR, reg, 1, pdata, 1, MPU6050_TIMEOUT);
}
2-2-2-3:设备唤醒&复位

综上,初始化代码如下:
/**********************************************************************
* 函数名称: MPU6050_Init
* 功能描述: MPU6050初始化函数,
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 0 - 成功, 其他值 - 失败
***********************************************************************/
int MPU6050_Init(void)
{
MPU6050_WriteRegister(MPU6050_PWR_MGMT_1, 0x00); //解除休眠状态
MPU6050_WriteRegister(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteRegister(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteRegister(MPU6050_CONFIG, 0x06);
MPU6050_WriteRegister(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteRegister(MPU6050_ACCEL_CONFIG, 0x18);
return 0;
}
2-2-3:从寄存器中读取原始数据
其中:
只需要通过I2C读取这些连续的寄存器,进行简单的换算就可以得到原始的数据值
代码如下:
/**********************************************************************
* 函数名称: MPU6050_ReadData
* 功能描述: 读取MPU6050数据
* 输入参数: 无
* 输出参数: pAccX/pAccY/pAccZ - 用来保存X/Y/Z轴的加速度
* pGyroX/pGyroY/pGyroZ - 用来保存X/Y/Z轴的角速度
* 返 回 值: 0 - 成功, 其他值 - 失败
***********************************************************************/
int MPU6050_ReadData(int16_t *pAccX, int16_t *pAccY, int16_t *pAccZ, int16_t *pGyroX, int16_t *pGyroY, int16_t *pGyroZ)
{
uint8_t datal, datah;
int err = 0;
err |= MPU6050_ReadRegister(MPU6050_ACCEL_XOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_ACCEL_XOUT_L, &datal);
if(pAccX)
*pAccX = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_ACCEL_YOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_ACCEL_YOUT_L, &datal);
if(pAccY)
*pAccY = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_ACCEL_ZOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_ACCEL_ZOUT_L, &datal);
if(pAccZ)
*pAccZ = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_GYRO_XOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_GYRO_XOUT_L, &datal);
if(pGyroX)
*pGyroX = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_GYRO_YOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_GYRO_YOUT_L, &datal);
if(pGyroY)
*pGyroY = (datah << 8) | datal;
err |= MPU6050_ReadRegister(MPU6050_GYRO_ZOUT_H, &datah);
err |= MPU6050_ReadRegister(MPU6050_GYRO_ZOUT_L, &datal);
if(pGyroZ)
*pGyroZ = (datah << 8) | datal;
return err;
}
至此,一个简单的MPU6050程序就写好了
2-2-4:.h文件的编写
#ifndef MPU6050_H
#define MPU6050_H
#include <stdint.h>
int MPU6050_Init(void);
int MPU6050_GetID(void);
int MPU6050_ReadData(int16_t *pAccX, int16_t *pAccY, int16_t *pAccZ, int16_t *pGyroX, int16_t *pGyroY, int16_t *pGyroZ);
#endif
2-2-5:示例程序
一下是一个简单的示例程序,通过晃动MPU6050,串口以1s的时间间隔打印对应得到的数据
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include <stdarg.h>
#include "MPU6050.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static void UART_Printf(const char *format, ...)
{
char tmp[128];
va_list argptr;
va_start(argptr, format);
vsprintf((char* )tmp, format, argptr);
va_end(argptr);
HAL_UART_Transmit(&huart1, (const uint8_t *)&tmp, strlen(tmp), HAL_MAX_DELAY);
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
UART_Printf("start!!!");
int id;
int16_t AccX, AccY, AccZ, GyroX, GyroY, GyroZ;
MPU6050_Init();
id = MPU6050_GetID();
HAL_Delay(1000);
while (1)
{
MPU6050_ReadData(&AccX, &AccY, &AccZ, &GyroX, &GyroY, &GyroZ);
/* USER CODE END WHILE */
UART_Printf("ID:%d \r\n",id);
UART_Printf("X:%d ",AccX);
UART_Printf("Y:%d ",AccY);
UART_Printf("Z:%d \r\n",AccZ);
UART_Printf("Gx:%d ",GyroX);
UART_Printf("Gy:%d ",GyroY);
UART_Printf("Gz:%d \r\n",GyroZ);
UART_Printf(" \r\n");
HAL_Delay(2000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
三:程序开源
文件夹内附文章keil代码和Cube MX工程