【自学嵌入式:stm32单片机】软件I2C读写MPU6050
软件I2C读写MPU6050
接线图

MPU6050的模块内部自带上拉电阻,所以外部不需要接上拉电阻,目前STM32是主机,MPU6050是从机,是一主一从的模式,AD0引脚,修改从机地址的最低位,这里由于模块内置了下拉电阻,所以引脚悬空的话,就相当于接地,INT中断信号输出脚,用不到,可以不接,这是江科大的接线图,接下来说说我的接线图,因为我之前实现OLED屏幕的时候做了一个I2C通信模块,不想再实现一遍,只想改一改对应的代码,所以我买了一个I2C扩展模块,相当于OLED和MPU6050都是从机,STM32是主机

我让MPU6050和OLED都接在这个扩展模块上面,扩展模块连着GPIO的PB8和PB9的口,相当于一主多从,而且MPU6050支持最快的波特率是400K,我那个代码大概是100K波特率,问题也不大,顺便修复一下I2C通信中读一个字节的函数没返回值的问题(之前的代码懒得改了):
//从I2C总线上读一个字节的数据
uint8_t MYI2C_ReadByte(void)
{
uint8_t i = 0;
uint8_t Byte = 0x00;
for(i = 0; i<8 ;i++)
{
//拉高SDA,开漏模式,电平交给总线(总线有个电阻上拉)
MYI2C_SDA(GPIO_PIN_SET);
MYI2C_SCL(GPIO_PIN_SET); //拉高时钟SCL,准备接收SDA
//HAL_Delay(5); //延迟5us电平翻转
if(MYI2C_GET_SDA() == GPIO_PIN_SET) //为1,则写1,为0则还保持0,用或等于写进去
{
Byte |= (0x80>>i);
}
MYI2C_SCL(GPIO_PIN_RESET); //拉低SCL,完成读取一个字节
}
return Byte;
}
代码实现
标准库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32
MPU6050.h
#ifndef __MPU6050_H
#define __MPU6050_H
#include "stm32f10x.h"
//结构体存储MPU6050的数据
typedef struct MPU6050Data
{
int16_t AccX;
int16_t AccY;
int16_t AccZ;
int16_t GyroX;
int16_t GyroY;
int16_t GyroZ;
}MPU6050Data;
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
void MPU6050_GetData(MPU6050Data* pMPU6050Data);
uint8_t MPU6050_GetID(void);
#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
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MYI2C.h"
#include "MPU6050_Reg.h"
#include "MPU6050.h"
#define MPU6050_ADDRESS 0xD0
// 指定地址写寄存器
// RegAddress - 8位寄存器地址
// Data - 8位数据
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MYI2C_Start();
MYI2C_WriteByte(MPU6050_ADDRESS); // 从机地址+读写位
MYI2C_ReadAck(); //接收应答,不进行处理,测试用
MYI2C_WriteByte(RegAddress); //指定寄存器地址
MYI2C_ReadAck(); //接收应答,不进行处理,测试用
MYI2C_WriteByte(Data); //指定寄存器的数据
MYI2C_ReadAck(); //接收应答,不进行处理,测试用
MYI2C_Close();
}
//指定地址读数据
//RegAddress - 指定读数据的8位寄存器地址
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data = 0; //接收的数据
MYI2C_Start();
MYI2C_WriteByte(MPU6050_ADDRESS); // 从机地址+读写位
MYI2C_ReadAck(); //接收应答,不进行处理,测试用
MYI2C_WriteByte(RegAddress); //指定寄存器地址
MYI2C_ReadAck(); //接收应答,不进行处理,测试用
//重新指定读写位,就要重新开始
MYI2C_Start();
MYI2C_WriteByte(MPU6050_ADDRESS | 0x01); // 从机地址+读写位,读写位为1
MYI2C_ReadAck(); //接收应答,不进行处理,测试用
//接收应答之后,总线控制权正式交给从机
Data = MYI2C_ReadByte();
//发送ACK
MYI2C_WriteAck(1); //不想继续读,就写1,想连续读多个字节,就写0
return Data;
}
void MPU6050_Init(void)
{
MYI2C_Init();
//设置电源管理寄存器1
//0不复位0解除睡眠模式0不需要循环0无关位0温度传感器不失能001选择x轴陀螺仪时钟
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
//设置电源管理寄存器2
//00不需要循环模式唤醒频率,000000,每个轴待机位,不需要待机,都给0
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
//设置采样率分频寄存器
//10分频
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
//设置配置寄存器
//bit7-bit3全都给0,不需要外部同步
//bit2-bit0,数字低通滤波器,给110,最平滑的滤波
MPU6050_WriteReg(MPU6050_CONFIG, 0X06);
//设置陀螺仪配置寄存器
//bit7-bit5是自测使能,不自测,都给0
//bit4-bit3满量程选择,给11,选择最大量程
//bit2-bit0无关位,都给0
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0X18);
//设置加速度计配置寄存器
//自测000
//满量程,最大给11
//高通滤波器,用不到,给000
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
//获取数据寄存器
void MPU6050_GetData(MPU6050Data* pMPU6050Data)
{
//因为这个16位数据是补码表示的有符号数
//直接赋值给int16_t也没问题
pMPU6050Data->AccX = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //先读高8位
pMPU6050Data->AccX = (pMPU6050Data->AccX)<<8; //左移8位,准备接收低8位
pMPU6050Data->AccX = (pMPU6050Data->AccX) | MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读低8位
pMPU6050Data->AccY = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //先读高8位
pMPU6050Data->AccY = (pMPU6050Data->AccY)<<8; //左移8位,准备接收低8位
pMPU6050Data->AccY = (pMPU6050Data->AccY) | MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读低8位
pMPU6050Data->AccZ = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //先读高8位
pMPU6050Data->AccZ = (pMPU6050Data->AccZ)<<8; //左移8位,准备接收低8位
pMPU6050Data->AccZ = (pMPU6050Data->AccZ) | MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读低8位
pMPU6050Data->GyroX = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //先读高8位
pMPU6050Data->GyroX = (pMPU6050Data->GyroX)<<8; //左移8位,准备接收低8位
pMPU6050Data->GyroX = (pMPU6050Data->GyroX) | MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读低8位
pMPU6050Data->GyroY = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //先读高8位
pMPU6050Data->GyroY = (pMPU6050Data->GyroY)<<8; //左移8位,准备接收低8位
pMPU6050Data->GyroY = (pMPU6050Data->GyroY) | MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读低8位
pMPU6050Data->GyroZ = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //先读高8位
pMPU6050Data->GyroZ = (pMPU6050Data->GyroZ)<<8; //左移8位,准备接收低8位
pMPU6050Data->GyroZ = (pMPU6050Data->GyroZ) | MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读低8位
}
//获取MPU6050的ID
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(0X75);
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "MYOLED.h"
#include "MYI2C.h"
#include "MPU6050.h"
MPU6050Data mpu6050data;
uint8_t ID; //设备ID
// 扫描总线上的I2C设备
// 显示的是地址
void scanI2CBusDevices(void)
{
uint8_t address = 0; //从机地址
uint8_t ack = 1; //应答位
uint8_t y = 0; //OLED显示行数,每显示一个,行数要加1
uint8_t devicesNum = 0; //设备总数
//扫描所有可能的7位I2C地址(0x00到0x7F),目前是高位为读写位,下面左移一位即可
for (address = 0; address < 128; address++)
{
// 发送起始信号
MYI2C_Start();
// 写入地址,最低位为0表示写操作
MYI2C_WriteByte((address << 1) | 0x00);
// 读取应答
ack = MYI2C_ReadAck();
// 发送停止信号
MYI2C_Close();
//如果收到应答,显示对应地址
if(ack == 0)
{
MYOLED_ShowHexNum(0, y, (address << 1), 7);
y++;
devicesNum++;
}
}
//最后在OLED屏幕右上角显示设备总数
MYOLED_ShowNum(13, 0, devicesNum, 3);
}
int main(void)
{
//MYI2C_Init();
MYOLED_Init();
MPU6050_Init();
// // 开始I2C通信
// MYI2C_Start();
// MYI2C_WriteByte(0xD0); // 1101 000 0,前7位从机地址,最后一位,0代表即将进行写入操作
// // OLED从机地址0111 100,最后一位置0代表写
// // 不冲突
// uint8_t Ack = MYI2C_ReadAck();
// // MPU6050默认睡眠,没法写寄存器,直接停止
// MYI2C_Close();
// MYOLED_ShowNum(0, 0, Ack, 3);
//scanI2CBusDevices();
//读取芯片ID号看看对不对
// uint8_t ID = MPU6050_ReadReg(0X75);
// MYOLED_ShowHexNum(0,0,ID,2);
// MPU6050_WriteReg(0x6B, 0x00);
// MPU6050_WriteReg(0x19, 0x66);
// uint8_t a = MPU6050_ReadReg(0x19);
// MYOLED_ShowHexNum(0,1,a,2);
MYOLED_ShowString(0,0,"ID:");
ID = MPU6050_GetID();
MYOLED_ShowHexNum(3,0,ID,2);
while (1) {
MPU6050_GetData(&mpu6050data);
MYOLED_ShowSignedNum(0,1,mpu6050data.AccX, 5);
MYOLED_ShowSignedNum(0,2,mpu6050data.AccY, 5);
MYOLED_ShowSignedNum(0,3,mpu6050data.AccZ, 5);
MYOLED_ShowSignedNum(7,1,mpu6050data.GyroX, 5);
MYOLED_ShowSignedNum(7,2,mpu6050data.GyroY, 5);
MYOLED_ShowSignedNum(7,3,mpu6050data.GyroZ, 5);
}
}
HAL库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32-hal
HAL库移植几乎是无痛移植,因为基于之前封装好的MYI2C,要改的地方很少,由于代码几乎一样,就给个main.c,其余内容可在开源仓库中看到
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 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 "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "MYOLED.h"
#include "MPU6050.h"
#include "MYI2C.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 */
MPU6050Data mpu6050data;
uint8_t ID; //设备ID号
/* 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 */
// 扫描总线上的I2C设备
// 显示的是地址
void scanI2CBusDevices(void)
{
uint8_t address = 0; //从机地址
uint8_t ack = 1; //应答位
uint8_t y = 0; //OLED显示行数,每显示一个,行数要加1
uint8_t devicesNum = 0; //设备总数
//扫描所有可能的7位I2C地址(0x00到0x7F),目前是高位为读写位,下面左移一位即可
for (address = 0; address < 128; address++)
{
// 发送起始信号
MYI2C_Start();
// 写入地址,最低位为0表示写操作
MYI2C_WriteByte((address << 1) | 0x00);
// 读取应答
ack = MYI2C_ReadAck();
// 发送停止信号
MYI2C_Close();
//如果收到应答,显示对应地址
if(ack == 0)
{
MYOLED_ShowHexNum(0, y, (address << 1), 7);
y++;
devicesNum++;
}
}
//最后在OLED屏幕右上角显示设备总数
MYOLED_ShowNum(13, 0, devicesNum, 3);
}
/* 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();
/* USER CODE BEGIN 2 */
MYOLED_Init();
MPU6050_Init();
//scanI2CBusDevices();
MYOLED_ShowString(0,0,"ID:");
ID = MPU6050_GetID();
MYOLED_ShowHexNum(3,0,ID,2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
MPU6050_GetData(&mpu6050data);
MYOLED_ShowSignedNum(0,1,mpu6050data.AccX, 5);
MYOLED_ShowSignedNum(0,2,mpu6050data.AccY, 5);
MYOLED_ShowSignedNum(0,3,mpu6050data.AccZ, 5);
MYOLED_ShowSignedNum(7,1,mpu6050data.GyroX, 5);
MYOLED_ShowSignedNum(7,2,mpu6050data.GyroY, 5);
MYOLED_ShowSignedNum(7,3,mpu6050data.GyroZ, 5);
/* USER CODE END WHILE */
/* 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_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
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_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != 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 */
实现效果

那个读取I2C总线通信设备地址和设备总数量的函数也是好用的,可以自己尝试,我懒得拍照了
浙公网安备 33010602011771号