Bootstrap

【STM32】软件I2C读写MPU6050

软件I2C读写MPU6050

要实现软件I2C读写MPU6050分为两个部分:

  1. 完成软件I2C协议时序
  2. 基于I2C协议读写寄存器操控MPU6050

接线图

在这里插入图片描述

代码整体框架

首先建立 I2C 通信层的 .c 和 .h 模块,在通信层中写好 I2C 底层的 GPIO 初始化和 6 个时序基本单元(起始、终止、发送一个字节、接收一个字节、发送应答、接收应答)

写好 I2C 通信层之后再建立 MPU6050 的 .c 和 .h 模块,在这一层将基于 I2C 通信的模块来实现指定地址读、指定地址写,再实现写寄存器对芯片进行配置,读寄存器获取传感器数据

最终在 main.c 中调用 MPU6050 的模块

在这里插入图片描述

MyI2C模块

MyI2C.c

I2C 底层 GPIO 初始化

软件 I2C 初始化 GPIO 有两个任务:

  1. 把 SCL 和 SDA 都初始化为开漏输出模式
  2. 把 SCL 和 SDA 置高电平
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/*
因为在后面的程序中有很多地方需要指定 GPIO 端口号
如果一直使用 GPIO_Pin_x 这样的形式很不方便且语义不明显
一旦切换端口需要改动的地方会特别多
可以使用宏定义,将端口号统一替换一个名字
#define SCL_PORT GPIOB
#define SCL_PIN GPIO_Pin_10
但是替换之后可能不太好移植,所以直接定义函数解决
*/
void MyI2C_W_SCL(uint8_t BitValue){
    GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
    Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue){
    GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
    Delay_us(10);
}
uint8_t MyI2C_R_SDA(void){
    uint8_t BitValue;
    BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
    Delay_us(10);
    return BitValue;
}

void MyI2C_Init(void){
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
}

6 个时序基本单元

  1. 起始

    void MyI2C_Start(void){
        MyI2C_W_SDA(1);
        MyI2C_W_SCL(1);
        MyI2C_W_SDA(0);
        MyI2C_W_SCL(0);
    }
    
  2. 终止

    void MyI2C_Stop(void){
        MyI2C_W_SDA(0);
        MyI2C_W_SCL(1);
        MyI2C_W_SDA(1);
    }
    
  3. 发送一个字节

    void MyI2C_SendByte(uint8_t Byte){
        uint8_t i;
        for(i = 0; i < 8; i++){
            MyI2C_W_SDA(Byte & (0x80) >> i); 
            	//使用按位与的方式取出对应位
        	MyI2C_W_SCL(1);	//释放SCL之后从机会立刻读取SDA中的数据
        	MyI2C_W_SCL(0);	//拉低SCL,继续存放下一位数据
        }    
    }
    
  4. 接收一个字节

    uint8_t MyI2C_RecieveByte(void){
        uint8_t Byte = 0x00;
        MyI2C_W_SDA(1);	
        //主机发送完请求释放SDA,因为SDA是开漏模式,所以从机回应就会拉低SDA,如果没有拉低,说明没有回应    
        uint8_t i;
        for(i = 0; i < 8; i++){
            MyI2C_W_SCL(1);	//主机释放SCL,主机读取SDA数据
            if(MyI2C_R_SDA() == 1){
            	Byte = Byte | (0x80) >> i;
        	}
            MyI2C_W_SCL(0);	//拉低SCL,从机将数据放到SDA
        }
        return Byte;
    }
    
  5. 发送应答

    void MyI2C_SendAck(uint8_t AckBit){
        MyI2C_W_SDA(AckBit);       
        MyI2C_W_SCL(1);
        MyI2C_W_SCL(0);
    }
    
  6. 接收应答

    uint8_t MyI2C_RecieveAck(void){
        uint8_t AckBit = 0x00;
        MyI2C_W_SDA(1);
        MyI2C_W_SCL(1);	
        AckBit = MyI2C_R_SDA();
        MyI2C_W_SCL(0);	    
        return AckBit;
    }
    

MyI2C.h

#ifndef __MYI2C_H
#define __MYI2C_H

void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_RecieveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_RecieveAck(void);

#endif

MPU6050模块

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"

#define MPU6050_ADDRESS 0xD0

指定地址写寄存器

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data){
    MyI2C_Start();
    MyI2C_SendByte(MPU6050_ADDRESS);
    MyI2C_RecieveAck();
    MyI2C_SendByte(RegAddress);
    MyI2C_RecieveAck();
    MyI2C_SendByte(Data);
    MyI2C_RecieveAck();
    MyI2C_Stop();
}

指定地址读寄存器

uint8_t MPU6050_ReadReg(uint8_t RegAddress){
    uint8_t Data;
    
    MyI2C_Start();
    MyI2C_SendByte(MPU6050_ADDRESS);
    MyI2C_RecieveAck();
    MyI2C_SendByte(RegAddress);
    MyI2C_RecieveAck();
    
    MyI2C_Start();
    MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
    MyI2C_RecieveAck();
    Data = MyI2C_RecieveByte();
    MyI2C_SendAck(1);	//主机给从机应答
    //如果想继续读多个字节,就给应答 MyI2C_SendAck(0),从机收到应答后会继续发送数据,如果不想继续读,就不能给从机应答 MyI2C_SendAck(1)
    MyI2C_Stop();
    
    return Data;
}

其他


使用单独的头文件 MPU6050_Reg.h 存放寄存器地址的宏定义,方便后续使用(因为寄存器数量较多所以单独建一个头文件,数量不多的话直接在 MPU6050.c 文件中写就行)

MPU6050_Reg.h
#ifndef __MPUMPU6050_REG_H
#define __MPUMPU6050_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

void MPU6050_Init(void){
	MyI2C_Init();
    
    //还需要写入一些寄存器对MPU6050硬件电路进行初始化配置
    /*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);		//电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);		//电源管理寄存器2,保持默认值0,所有轴均不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);		//采样率分频寄存器,配置采样率
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);			//配置寄存器,配置DLPF
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);	//陀螺仪配置寄存器,选择满量程为±2000°/s
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);	//加速度计配置寄存器,选择满量程为±16g
}

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);		//返回WHO_AM_I寄存器的值
}

/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
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;								//定义数据高8位和低8位的变量
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据
	*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据
	*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据
	*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据
	*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据
	*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据
	*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
}

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

main.c 源程序

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;								//定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;			//定义用于存放各个数据的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	MPU6050_Init();		//MPU6050初始化
	
	/*显示ID号*/
	OLED_ShowString(1, 1, "ID:");		//显示静态字符串
	ID = MPU6050_GetID();				//获取MPU6050的ID号
	OLED_ShowHexNum(1, 4, ID, 2);		//OLED显示ID号
	
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);		//获取MPU6050的数据
		OLED_ShowSignedNum(2, 1, AX, 5);					//OLED显示数据
		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 专栏文章均参考 《STM32入门教程-2023版 细致讲解 中文字幕》教程视频

;