【自学嵌入式: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号
浙公网安备 33010602011771号