软件I2C协议的时序&基于I2C协议读写寄存器操控MPU6050

目录

接线图部分

​编辑I2C程序的整体架构

实现功能解释部分

完整代码


接线图部分

I2C程序的整体架构

1:用OLED框架,在Hardware文件夹中建立MyI2C.C和MyI2C.H的文件

2:在通讯层写好I2CGPIO的初始化

2:6个基本时序单元:起始条件,终止条件,发送一个一个字节,接收一个字节,发送应答,输出应答

3: 建立MPU6050模块,基于I2C模块,建立指定地址读,指定地址写

4:实现写寄存器对芯片的配置,读寄存器得到传感器配置

5:在main函数里得到MPU6050模块,初始化,读寄存器得到数据

实现功能解释部分

任务一:把SCL和SDA的GPIO口转换为开漏输出模式

任务二:把SCL和SDA置为高电平

根据引脚接线图,I2C的GPIO置为B口

GPIO的初始化,把Pin_10和Pin_11口置高电平,使总线处于空闲状态,(注意输出方式要改成复用开漏输出,否则会使后续的接收应答变成001)

对SCL和SDA的高低电平进行封装

BitAction:是一个用于位操作的枚举类型和数据类型,也可以理解为,指定GPIO引脚的具体电平值,

对SDA的读寄存器封装

起始条件和终止条件

Sr部分为重复起始条件,必须先把SDA置为高点平,才可以让起始条件兼容两个

在I2C时序图最后的P部分是终止条件,根据终止条件,设置SCL和SDA的高低电平

发送一个字节

用包装函数,用for循环来发送数据位,总共发8位,用0x80来从最高位开始发送,把SCL拉低,再释放,循环8次。

接收一个字节

先包装函数,定义Byte的初始值为16进制的0x00,主机刚刚进来时,SCL为低位,把SDA置1,防止干扰SCL。

接着把SCL置1,让主机可以开始读取数据,如果SDA读为1,字节位右移,开始读取,读取字节1,否则SCL写0,从机不再读取数据,这个过程循环8次。

发送和接收应答

 发送和接收应答就是发送和接送字节的简化版

发送一个字节,收8位,接收应答,收1位就行

发送应答就是ACKBit直接读取应答位。

接收应答就是在SCL为低电平的时候把SDA置1,再把SCL拉高,让SDA读到的值赋给ACKBit,最后把SCL拉低,进入下一个时序单元。

把GPIO的初始化和6个时序单元放到头文件中,没啥操作难度

地址名为1101 0000

000代表模块会进行响应

地址名位11010010

模块响应

写入寄存器

在主函数中,初始化MyI2C_Init();让整个系统开始,发送字节地址,十六进制字节的最后一个0代表即将写入,用来测试MPU6050的功能,随后定义一个8字节数Ack来接收应答,再用OLED函数显示出来

根据I2C时序,写入指定设备,指定地址,指定数据,记得设置应答位。

首先先用宏定义,定义一个MPU6050的地址,为0xD0,换算成二进制为1101 0000,最后一位为0,指定为写地址,随后设置应答位,然后再发送指定地址RegAddress的字节地址指针位,设置应答位,最后写入8为字节的指定数据Data。

指定读的地址,指定设备和指定地址都不变,要开启第二个接收字节地址,将宏定义的0xD0的值和0x01相与,变成读地址,设置应答位,接收一个字节之后,咋讲接收到的数据赋值给Data,给一个非应答MyI2C_SendAck(1),非应答,引用停止位,代表一个字节的接收完成

读出寄存器

在main函数里,#include"MPU6050.h",初始化,然后定义一个8字节的16进制数ID,就可以利用MPU6050的ReadReg函数(这个函数在前面有定义为I2C的通讯,功能为读出一个字节)来将16进制数0x75传递在OLED屏幕上

OLED_ShowHex(1,1,ID,3);表示在显示屏的1行1列显示ID,I2C传递过来的0x75,值为68

写寄存器

利用WriteReg();在电源管理寄存器写入0x00,地址为0X6B解除睡眠模式,再利用手册里的采样频率,0x19,指定寄存器的地址,然后再定义要写入的数据,MPU6050_WriteReg(指定寄存器地址,要写入的数据可以是任何值),利用ID函数可以在OLED屏幕显示出来

把手册上的寄存器地址全部新建一个文件,名称叫MPU6050.h定义下来

江科大并没有给出这些MPU6050寄存器的注释,我自己加上去了,请用

接着,设置封装函数,这部分函数是寄存器中读取的值,用指针来定义加速度计和陀螺仪的指针值,字节位为16,先定义个8字节的高8位和高8位的值,高8位左移8位,与低8位相与,然后再把他放入16位的指针*AccX当中.

陀螺仪的值同理。

其实还有一种用I2C通信的方法用循环函数表示出来,会更加高级,但我还不会,有会的大佬可以指导一下

定义整形16字节的6个变量,把6个变量的地址传进去,再在OLED屏幕上显示出来

左边为MPU6050所受的重力加速度值,单位为g,右边表示角速度值

读取的数据/32768 = x/满量程

这里的z轴的加速度值是1966

计算出来的值是0.96g左右,目前在福建厦门

最后,用一个8字节的函数来读取ID,识别寄存器的ID,确认寄存器的设备类型

在主函数里面定义一个ID字节,然后把读到的设备标识寄存器赋值给ID,最终在OLED屏幕上显示出来。

最终效果

ID号:68,左边:加速度值。右边:角速度值

完整代码

main.c 

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

uint8_t ID;
int16_t AX, AY ,AZ, GX, GY, GZ;

int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	OLED_ShowString(1,1,"ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1,4,ID,2);
	while(1)
	{
		MPU6050_GetData(&AX, &AY ,&AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);//输出加速度
		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);
	}
}

MyI2C.c 

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

void MyI2C_W_SCL(uint8_t BitValue)
{
	//BitAction要写入GPIO引脚的具体电平值
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);	//用于位操作的枚举类型和数据类型
	Delay_us(10);
}	

void MyI2C_W_SDA(uint8_t BitValue)
{
	//要写入GPIO引脚的具体电平值
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);	//用于位操作的枚举类型和数据类型
	Delay_us(10);
}

uint8_t MyI2C_R_SDA(void)	//SDA读寄存器的封装操作
{
	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;//置输出端,可以为输入端,输1,再读取寄存器值
	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)
{
	//起始条件
	MyI2C_W_SCL(1);//注意要把SDA放前面,为了兼容起始条件和重复起始条件
	MyI2C_W_SDA(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);

}

void MyI2C_Stop(void)
{
	//终止条件
	MyI2C_W_SDA(0);//注意要把SDA放前面,为了兼容起始条件和重复起始条件
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

void MyI2C_SendByte(uint8_t Byte)//发送一个字节
{
	uint8_t i;
	for(i=0;i<8;i++)//发8位为一个字节
	{
		MyI2C_W_SDA(!!(Byte & (0x80 >> i)));	//取Byte的最高位
		MyI2C_W_SCL(1);		//释放
		MyI2C_W_SCL(0);		//拉低
	}
}

uint8_t MyI2C_ReceiveByte(void)//接收一个字节
{
	uint8_t i, Byte=0x00;
	MyI2C_W_SDA(1);		//高电平为读,低电平为写	
	
	for(i=0;i<8;i++)
	{
		MyI2C_W_SCL(1);		//SCL写入置高,主机才能读取数据
		if(MyI2C_R_SDA()){Byte |= (0x80 >> i);}	//字节位右移一位一位地读取数据1}
		MyI2C_W_SCL(0);
	}
	return Byte;	//返回接收Byte
	
}
//发送应答
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);	
	MyI2C_W_SCL(1);		//从机读取应答
	MyI2C_W_SCL(0);		//拉低,进入下一个时序单元
}
//接收应答
uint8_t MyI2C_ReceiveAck(void)//接收一个字节
{
	//函数刚刚进来,SCL为低电平
	uint8_t AckBit;
	MyI2C_W_SDA(1);		//主机释放SDA,防止从机干扰,并不是把SDA置高电平,从机仍然在继续工作
	MyI2C_W_SCL(1);		//主机读取应答位
	AckBit = MyI2C_R_SDA();		//读到的值,直接赋给ACKBit
	MyI2C_W_SCL(0);		//拉低,进入下一个时序单元
	return AckBit;	 
}

MyI2C.h 

#ifndef __MYI2C_H
#define __MYI2C_H


uint8_t MyI2C_R_SDA(void);
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);



#endif

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_ReceiveAck();		//设置应答位,判断从机有没有收到应答位
	MyI2C_SendByte(RegAddress);	//指定地址发送指定地址指针,写入指定寄存器地址指针下的数据
	MyI2C_ReceiveAck();
	MyI2C_SendByte(Data);	//写入指定数据
	MyI2C_ReceiveAck();
	MyI2C_Stop();
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress) //指定读的地址
{
	uint8_t Data;
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);	//指定设备,写地址
	MyI2C_ReceiveAck();		//发送一个字节之后要接受一个应答
	MyI2C_SendByte(RegAddress);	//指定地址发送指定地址指针
	MyI2C_ReceiveAck();
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);		//1101 0001末尾变1变成读地址
	MyI2C_ReceiveAck();			//总线控制权交给从机
	Data = MyI2C_ReceiveByte();		//接收一个字节
	MyI2C_SendAck(1);//给非应答		//接收一个字节以后,要给从机发送一个应答,收到应答之后就会继续发送数据
	//发送应答了才会继续发送数据,给1是为了不给从机应答
	MyI2C_Stop();
	
	return Data;
}

void MPU6050_Init(void)
{
	MyI2C_Init();
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//选择一个x轴的陀螺仪时钟,其他都为0
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//6个轴均不待机
	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);
}

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;
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);	//读取高8位的值,放在DataH中
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);	//读取低8位的值,
	*AccX = (DataH << 8)| DataL;//用补码表示的有符号数,返回赋值给int6_t
	//原理:高8位左移8位,或低8位,就变成指针中的16字节数
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8)| DataL;//加速计变成16位数据
	
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8)| DataL;
	
	//陀螺仪
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8)| DataL;
	
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8)| DataL;
	
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*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

MPU6050_Reg.h

#ifndef MPU6050_H
#define MPU6050_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	//加速度计X轴输出高字节寄存器
#define	MPU6050_ACCEL_XOUT_L	0x3C	//加速度计X轴输出低字节寄存器
#define	MPU6050_ACCEL_YOUT_H	0x3D	//加速度计Y轴输出高字节寄存器
#define	MPU6050_ACCEL_YOUT_L	0x3E	//加速度计Y轴输出低字节寄存器
#define	MPU6050_ACCEL_ZOUT_H	0x3F	//加速度计Z轴输出高字节寄存器
#define	MPU6050_ACCEL_ZOUT_L	0x40	//加速度计Z轴输出低字节寄存器
#define	MPU6050_TEMP_OUT_H		0x41	//温度传感器输出高字节寄存器
#define	MPU6050_TEMP_OUT_L		0x42	//温度传感器输出低字节寄存器
#define	MPU6050_GYRO_XOUT_H		0x43	//陀螺仪X轴输出高字节寄存器
#define	MPU6050_GYRO_XOUT_L		0x44	//陀螺仪X轴输出低字节寄存器
#define	MPU6050_GYRO_YOUT_H		0x45	//陀螺仪Y轴输出高字节寄存器
#define	MPU6050_GYRO_YOUT_L		0x46	// 陀螺仪Y轴输出低字节寄存器
#define	MPU6050_GYRO_ZOUT_H		0x47	//陀螺仪Z轴输出高字节寄存器
#define	MPU6050_GYRO_ZOUT_L		0x48	//陀螺仪Z轴输出低字节寄存器

#define	MPU6050_PWR_MGMT_1		0x6B	//电源管理寄存器1,用于控制设备的电源模式
#define	MPU6050_PWR_MGMT_2		0x6C	//电源管理寄存器2,用于进一步配置电源管理
#define	MPU6050_WHO_AM_I		0x75	//设备标识寄存器,用于确认设备类型


#endif

OLED和DELAY的引用部分的代码大家就自行找江科大开源软件了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值