【STM32】 使用软件模拟I2C与MPU6050通信

内容

使用软件模拟I2C时序写MPU6050寄存器进行相关配置,并读取六轴传感器的值在OLED显示
在这里插入图片描述
在这里插入图片描述
使用STM32F103C8的PB10、PB11引脚模拟SCL和SDA,产生以上波形。因为I2C为同步半双工通信,使用软件模拟不会有太大问题。
在这里插入图片描述

I2C总线空闲时需要保持为高电平。但是在MPU6050上已经内置上拉电阻,所以PB10、PB11设置为复用开漏输出。

低耦合高内聚,先写好底层的I2C代码,再写MPU6050的控制与通讯代码,最后在主函数中调用。

I2C底层

在初始化函数中打开GPIO时钟并初始化GPIO_Pin_10 与 GPIO_Pin_11为复用开漏输出。
根据上图依次写 通讯开始、通讯结束、发送字节、接受字节、发送应答位、接受应答位的模拟电平。
在这里插入图片描述
通讯开始时,在SDA、SCL均拉高时,先拉低SDA,再拉低SCL。
通讯结束时,在SDA、SCL均拉低时,先拉高SCL,再拉高SDA。
在这里插入图片描述
SCL拉低时主机写SDA,SCL拉高时从机读SDA。
在这里插入图片描述
SCL拉低时主机松开SDA,从机写SDA,SCL拉高时主机读SDA。
在这里插入图片描述

#include "MyI2C.h"
#include "Delay.h"

//封装度端口读写 方便移植或时序替换
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);
}

void MyI2C_Start(void)
{
	//先拉高SDA再拉高SCL 方便时序拼接
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

void MyI2C_End(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

void MyI2C_SendByte(uint8_t Byte)
{
	//先高后低按位取数据
	for(uint8_t i = 0x80; i >= 0x01; i >>= 1)
	{
		//低电平写数据
		MyI2C_W_SDA(Byte & i);
		//翻转电平驱动时钟
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

uint8_t MyI2C_ReceiveByte(void)
{
	//缓存数据
	uint8_t Byte = 0x00;
	
	//主机释放SDA
	MyI2C_W_SDA(1);
	
	for(uint8_t i = 0x80; i >= 0x01; i >>= 1)
	{
		//SCL置高电平主机读数据
		MyI2C_W_SCL(1);
		if(MyI2C_R_SDA() == 1)
		{
			Byte |= i;
		}
		//SCL置低电平从机写数据
		MyI2C_W_SCL(0);
	}
	//返回值
	return Byte;
}
	
void MyI2C_SendAck(uint8_t AckBit)
{
	//低电平写数据
	MyI2C_W_SDA(AckBit);
	//翻转电平驱动时钟
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck(void)
{
	//缓存接受位
	uint8_t AckBit = 0;
	
	//主机释放SDA
	MyI2C_W_SDA(1);
	//SCL置高电平
	MyI2C_W_SCL(1);
	//主机读数据
	AckBit = MyI2C_R_SDA();
	//SCL置低电平从机写数据
	MyI2C_W_SCL(0);
	//返回值
	return AckBit;
}

MPU6050配置

MPU6050_Reg.h

将需要操作的寄存器地址预定义在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

在MPU6050.h中预定义数据结构体用于返回数据给主函数,预定义相关函数

#ifndef __MPU6050_H
#define __MPU6050_H

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

typedef struct
{
  int16_t AccX;
	int16_t AccY;
	int16_t AccZ;
	int16_t GyroX;
	int16_t GyroY;
	int16_t GyroZ;
} MPU6050_Data;

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);

uint8_t MPU6050_GetID(void);
MPU6050_Data MPU6050_GetDATA(void);

void MPU6050_Init(void);

#endif

MPU6050.c

先根据写I2C通讯格式编写指定地址写和指定地址读的函数,在根据这两个函数编写MPU6050初始化、读取相关寄存器的函数。
在这里插入图片描述
开始信号->发送7位地址和1位读写位(写)->接收应答位->发送要操作的寄存器地址->接受应答位->发送要往寄存器内写的数据->接受应答位->结束信号
在这里插入图片描述
开始信号->发送7位地址和1位读写位(写)->接收应答位->发送要操作的寄存器地址->接受应答位->重新发送开始信号->发送7位地址和1位读写位(读)->接受应答位->接受8位数据->发送应答位->结束信号

#include "MPU6050.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_ReceiveAck();
	//指定寄存器地址
	MyI2C_SendByte(RegAddress);
	//接受应答位
	MyI2C_ReceiveAck();
	//写数据
	MyI2C_SendByte(Data);
	//接受应答位
	MyI2C_ReceiveAck();
	//通讯结束
	MyI2C_End();
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	//通讯开始
	MyI2C_Start();
	//指定设备地址
	MyI2C_SendByte(MPU6050_ADDRESS);
	//接受应答位
	MyI2C_ReceiveAck();
	//指定寄存器地址
	MyI2C_SendByte(RegAddress);
	//接受应答位
	MyI2C_ReceiveAck();
	
	//开始时序
	MyI2C_Start();
	//指定设备地址 改成读地址
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);
	//接受应答位
	MyI2C_ReceiveAck();
	//缓存数据
	uint8_t Data = MyI2C_ReceiveByte();
	//不需要其他数据 不返回应答
	MyI2C_SendAck(1);
	//通讯结束
	MyI2C_End();
	
	return Data;
}


void MPU6050_Init(void)
{
	//手册内容
	MyI2C_Init();
	//电源管理
	//解除睡眠 选择陀螺仪时钟
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
	//6轴均不待机
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
	//MPU6050配置
	//采样分频为10
	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);
}

MPU6050_Data MPU6050_GetDATA(void)
{
	MPU6050_Data Data;
	
	Data.AccX = 
	(MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H)<<8) 
	| MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	
	Data.AccY = 
	(MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H)<<8) 
	| MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	
	Data.AccZ = 
	(MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H)<<8) 
	| MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	
	Data.GyroX =
	(MPU6050_ReadReg(MPU6050_GYRO_XOUT_H)<<8) 
	| MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	
	Data.GyroY =
	(MPU6050_ReadReg(MPU6050_GYRO_YOUT_H)<<8) 
	| MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	
	Data.GyroZ =
	(MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H)<<8) 
	| MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	
	return Data;
}


主函数

在主函数循环体外进行初始化,在循环体内读取MPU6050的寄存器并显示在OLED上

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

MPU6050_Data data;

int main(void)
{
		
	OLED_Init();
	
	MPU6050_Init();
	
	OLED_ShowString(1,1,"PerhID:");
	OLED_ShowHexNum(1,9,MPU6050_GetID(),4);
	
	while(1)
	{
		data = MPU6050_GetDATA();
		OLED_ShowSignedNum(2,1,data.AccX,5);
		OLED_ShowSignedNum(3,1,data.AccY,5);
		OLED_ShowSignedNum(4,1,data.AccZ,5);
		OLED_ShowSignedNum(2,9,data.GyroX,5);
		OLED_ShowSignedNum(3,9,data.GyroY,5);
		OLED_ShowSignedNum(4,9,data.GyroZ,5);
	}
}

结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值