32I2C通信协议&I2C读写MPU6050

目录

一.I2C通信协议简介

二.硬件电路 

三.时序基本单元(六大模块)

四.I2C时序

五.代码实现

(1)软件I2C读写MPU6050(GPIO模拟I2C)

(2)硬件I2C读写MPU6050


异步时序的:非常依赖硬件外设的支持,比如串口是很难用软件来模拟的;但节省了一根时钟线的资源

同步时序可以极大地降低单片机对硬件电路的依赖,时钟线停止了,发送方和接收方都会停止

一.I2C通信协议简介

双向串行数据线(SDA)

串行时钟线(SCL)

二.硬件电路 

开漏输出模式仍然可以输入,输入时,先输出1,再直接读取输入数据寄存器就行

从机设备地址在I2C协议里分为7位地址和10位地址

不同型号的芯片地址都是不同的,相同型号的芯片地址都是一样的,如果有相同的芯片挂载在同一条总线上,需要用到地址中的可变部分(可以在电路中改变)

三.时序基本单元(六大模块)

 低电平主机放数据,高电平从机读数据

低电平从机放数据,高电平主机读数据 

四.I2C时序

 

由于地址指针自增的特性,可以实现在指定地址开始,按顺序连续写入或读出多个字节,多次执行最后一个字节的内容即可

 

 读数据结束后,主机发送非应答,从机就会释放总线,将SDA控制权交给主机

从机控制SDA发送一个字节的权利,开始于读写标志位为1,结束于主机给应答位为1.

I2C外设简介

 硬件I2C:执行效率高,可以节省软件资源,功能比较强大,可以实现完整的多主机特效模型

时序波形规整,通信速率快

 EV8_1事件表示你该写入DR发送数据,并不需要等待 EV8_1事件

void I2C_DeInit(I2C_TypeDef* I2Cx);
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);//生成起始条件
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);//生成终止条件
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);//配置在收到一个字节后是否给从机应答
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);//写数据到数据寄存器DR
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);//写读取DR接收数据
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);//发送7位地址的专用函数

基本状态监控I2C_CheckEvent() 同时判断一个或多个标志位来分析状态(Event)

高级状态监控I2C_GetLastEvent()获取SR1和SR2的数据,并拼接为16位数据

I2C_GetFlagStatus()获取标志位

void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);
ITStatus I2C_GetITStatus(I2C_TypeDef* I2Cx, uint32_t I2C_IT);
void I2C_ClearITPendingBit(I2C_TypeDef* I2Cx, uint32_t I2C_IT);

 

超过100kHz进入快速状态,占空比的设置才会有用

由于上拉是由外接电阻产生的弱上拉,而下拉直接接地,所以SCL在下降沿表现地很果断,而在上升沿较为缓慢

占空比2:1

增大低电平时间占整个周期的比例,由于低电平数据变化,高电平数据读取,而SDA需要一定时间来翻转电平,尤其是在上升沿数据变化比较慢,所以要给SCL低电平过给一些时间

五.代码实现

(1)软件I2C读写MPU6050(GPIO模拟I2C)

MyI2C.c

#include "stm32f10x.h"                  // Device header
#define I2C_PORT   GPIOB
#define I2C_PIN_SCL    GPIO_Pin_10
#define I2C_PIN_SDA    GPIO_Pin_11

void I2C_W_SCL(BitAction Value)//BitAction为枚举类型,Bit_RESET和Bit_SET
{
	GPIO_WriteBit(I2C_PORT,I2C_PIN_SCL,Value);
	
}
void I2C_W_SDA(BitAction Value)
{
	GPIO_WriteBit(I2C_PORT,I2C_PIN_SDA,Value);
	
}
uint8_t I2C_R_SDA()
{
	
	GPIO_ReadInputDataBit(I2C_PORT,I2C_PIN_SDA);
	
}
void MyI2C_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = I2C_PIN_SCL | I2C_PIN_SDA;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
 	GPIO_Init(GPIOB, &GPIO_InitStructure);

}
void MyI2C_Start(void)
{
	I2C_W_SDA(1);//这里应先确保SDA被拉高,以复合重复起始的条件
	I2C_W_SCL(1);
	
	I2C_W_SDA(0);
	I2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
	I2C_W_SDA(0);
	I2C_W_SCL(1);
	I2C_W_SDA(1);

}
void MyI2C_SendByte(uint8_t Byte)//由于该函数前的SCL一定是低电平,所以不用I2C_W_SCL(0);
{
	uint8_t i;
	for(i=0;i<8;i++)
	{	
		I2C_W_SDA(Byte&(0x80>>i));//这里是&,而不是&=;	
		I2C_W_SCL(1);
		I2C_W_SCL(0);
	}
}
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i;
	uint8_t Byte = 0x00;
	I2C_W_SDA(1);//释放总线
	
	for(i=0;i<8;i++)
	{
		I2C_W_SCL(1);//对SCL的操作都要放在循环里面
		if(I2C_R_SDA()){Byte|=(0x80>>i);}
		I2C_W_SCL(0);
	}
	return Byte;
}
void Main_ACK(uint8_t AckBit)
{
	I2C_W_SDA(AckBit);
	I2C_W_SCL(1);
	I2C_W_SCL(0);
	
}
uint8_t Part_ACK(void)
{
	uint8_t AckBit = 0x00;
	I2C_W_SDA(1);//释放总线,不是强制SDA为高电平,是可以被从机下拉的
	I2C_W_SCL(1);
	AckBit = I2C_R_SDA();
	I2C_W_SCL(0);//进入下一个时序单元
	return AckBit;
}

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#define S_Address  0xD0
void MPU_Add_W(uint8_t Reg_Address,uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(S_Address);
	Part_ACK();
	MyI2C_SendByte(Reg_Address);
	Part_ACK();
	MyI2C_SendByte(Data);
	Part_ACK();
	MyI2C_Stop();
}
uint8_t MPU_Add_R(uint8_t Reg_Address)
{
	uint8_t Data;
	MyI2C_Start();
	MyI2C_SendByte(S_Address);
	Part_ACK();
	
	MyI2C_SendByte(Reg_Address);
	Part_ACK();
	
	MyI2C_Start();
	MyI2C_SendByte(S_Address|0x01);
	Part_ACK();
	
	Data = MyI2C_ReceiveByte();
	Main_ACK(1);
	MyI2C_Stop();
	return Data;
}
void MPU6050_Init(void)
{
	MyI2C_Init();
	MPU_Add_W(MPU6050_PWR_MGMT_1, 0x01);//电源寄存器1
	MPU_Add_W(MPU6050_PWR_MGMT_2, 0x00);//电源寄存器2
	MPU_Add_W(MPU6050_SMPLRT_DIV, 0x09);//采样分频设置为10
	MPU_Add_W(MPU6050_CONFIG, 0x06);//配置低通滤波器进行滤波
	MPU_Add_W(MPU6050_GYRO_CONFIG, 0x18);//配置角速度寄存器(量程)
	MPU_Add_W(MPU6050_ACCEL_CONFIG, 0x18);//配置加速度寄存器(最大16g量程)
}
uint8_t MPU6050_GetID()
{
	return MPU_Add_R(MPU6050_WHO_AM_I);
}
void MPU6050_GetValue(int16_t* AccX,int16_t* AccY,int16_t* AccZ,
						int16_t* GyroX,int16_t* GyroY,int16_t* GyroZ)
{
	uint16_t DataH,DataL;
	DataH = MPU_Add_R(MPU6050_ACCEL_XOUT_H);
	DataL = MPU_Add_R(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH<<8) | DataL;//将8位数据转换为16位的数据,且这16位数据是用补码表示的有符号数
	
	DataH = MPU_Add_R(MPU6050_ACCEL_YOUT_H);
	DataL = MPU_Add_R(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH<<8) | DataL;	
	
	DataH = MPU_Add_R(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU_Add_R(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH<<8) | DataL;	
	
	DataH = MPU_Add_R(MPU6050_GYRO_XOUT_H);
	DataL = MPU_Add_R(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH<<8) | DataL;
	
	DataH = MPU_Add_R(MPU6050_GYRO_YOUT_H);
	DataL = MPU_Add_R(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH<<8) | DataL;
	
	DataH = MPU_Add_R(MPU6050_GYRO_ZOUT_H);
	DataL = MPU_Add_R(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH<<8) | DataL;
	
	//这里连续调用12次函数才实现数据的读取,也可以使用I2C读取多个字节的数据的方法
}
/*
	实现多返回值函数方法
	1.定义多个全局变量,并在头文件声明,即可在各个.c文件调用(不利于封装)
	2.利用地址传参的方式
	3.利用结构体打包输入参数(类似于STM32的库函数)
*/

 main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"  
#include "OLED.h" 
#include "MPU6050.h"
int16_t AX,AY,AZ,GX,GY,GZ;
int main()
{
	uint8_t Ack,ID;
	OLED_Init();
	MPU6050_Init();
//	/* 
//		可以用for将下列代码套起来
//		遍历所有地址,扫描总线上设备	
//		注:遍历的时候只遍历前7位地址,最后一位是读写位
//	*/
//	MyI2C_Start();
//	MyI2C_SendByte(0xD0);
//	Ack = Part_ACK();
//	OLED_ShowNum(1,1,Ack,3);
//	MPU_Add_W(0x6B,0x00);
//	MPU_Add_W(0x19,0x66);
//	ID = MPU_Add_R(0x19);
	
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1,1,ID,2);
	while(1)
	{
		MPU6050_GetValue(&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);
	}	
}
		

该代码实现了良好的分层设计

(2)硬件I2C读写MPU6050

MPU6050.c

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"
#define S_Address  0xD0
void Event_Wait(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint16_t Timeout;
	Timeout = 10000;
	while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)
	{
		Timeout --;
		if(Timeout == 0)
		{
			break;
		}
	}
}
void MPU_Add_W(uint8_t Reg_Address,uint8_t Data)
{
	//对于软件I2C模拟的代码(都是阻塞式的),在运行时都有一定的Delay,函数运行完成,对应的波形也肯定发送完毕了
	//而硬件I2C是操作寄存器(都是非阻塞式的),是否发送完波形,与之无关
	I2C_GenerateSTART(I2C2,ENABLE);
	Event_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//主机模式已选择事件
	I2C_Send7bitAddress(I2C2,S_Address,I2C_Direction_Transmitter);//发送数据都自带了接收应答的功能
	Event_Wait(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	I2C_SendData(I2C2,Reg_Address);
	Event_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//字节正在发送
	I2C_SendData(I2C2,Data);
	Event_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//字节发送完成
	//需要等待程序将二级缓存全部清空时才能发送终止条件
	I2C_GenerateSTOP(I2C2,ENABLE);
	
}
uint8_t MPU_Add_R(uint8_t Reg_Address)
{
	uint8_t Data;
	I2C_GenerateSTART(I2C2,ENABLE);
	Event_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//主机模式已选择事件
	I2C_Send7bitAddress(I2C2,S_Address,I2C_Direction_Transmitter);//发送数据都自带了接收应答的功能
	Event_Wait(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	I2C_SendData(I2C2,Reg_Address);
	Event_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//字节正在发送
	 
	
	I2C_GenerateSTART(I2C2,ENABLE);//调用起始条件之后如果还有字节在移位,则起始条件将会延迟
	Event_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//主机模式已选择事件

	I2C_Send7bitAddress(I2C2,S_Address,I2C_Direction_Receiver);
	Event_Wait(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
	//进入EV6_1事件,适合接收1个字节的情况,在EV6之后要清除响应和停止条件的产生
	//规定在接收最后一个字节之前就要提前将ACK置0,同时设置停止位STOP(不会截断正在发送的字节)
	I2C_AcknowledgeConfig(I2C2,DISABLE);
	I2C_GenerateSTOP(I2C2,ENABLE);
	
	Event_Wait(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);//该行代码将Ack置为1,启动应答,并方便多字节读取
	return Data;
}
void MPU6050_Init(void)
{
	I2C_InitTypeDef I2C_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;//必须要写在程序的开头位置
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_OD;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	
	I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
	I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;//配置在收到一个字节后是否给从机应答
	I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//当STM32为从机时,响应几位的地址
	I2C_InitStructure.I2C_ClockSpeed=50000;
	I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//占空比选择(2:1或16:9)
	I2C_InitStructure.I2C_OwnAddress1=0x00;
	I2C_Init(I2C2,&I2C_InitStructure);
	
	I2C_Cmd(I2C2,ENABLE);//一定要记得使能开关
	
	MPU_Add_W(MPU6050_PWR_MGMT_1, 0x01);//电源寄存器1
	MPU_Add_W(MPU6050_PWR_MGMT_2, 0x00);//电源寄存器2
	MPU_Add_W(MPU6050_SMPLRT_DIV, 0x09);//采样分频设置为10
	MPU_Add_W(MPU6050_CONFIG, 0x06);//配置低通滤波器进行滤波
	MPU_Add_W(MPU6050_GYRO_CONFIG, 0x18);//配置角速度寄存器(量程)
	MPU_Add_W(MPU6050_ACCEL_CONFIG, 0x18);//配置加速度寄存器(量程)
}
uint8_t MPU6050_GetID()
{
	return MPU_Add_R(MPU6050_WHO_AM_I);
}
void MPU6050_GetValue(int16_t* AccX,int16_t* AccY,int16_t* AccZ,
						int16_t* GyroX,int16_t* GyroY,int16_t* GyroZ)
{
	int16_t DataH,DataL;
	DataH = MPU_Add_R(MPU6050_ACCEL_XOUT_H);
	DataL = MPU_Add_R(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH<<8) | DataL;
	
	DataH = MPU_Add_R(MPU6050_ACCEL_YOUT_H);
	DataL = MPU_Add_R(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH<<8) | DataL;	
	
	DataH = MPU_Add_R(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU_Add_R(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH<<8) | DataL;	
	
	DataH = MPU_Add_R(MPU6050_GYRO_XOUT_H);
	DataL = MPU_Add_R(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH<<8) | DataL;
	
	DataH = MPU_Add_R(MPU6050_GYRO_YOUT_H);
	DataL = MPU_Add_R(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH<<8) | DataL;
	
	DataH = MPU_Add_R(MPU6050_GYRO_ZOUT_H);
	DataL = MPU_Add_R(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH<<8) | DataL;
}

计次计时,超时退出,并且可以在函数里进行打印错误日志,进行系统复位

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"  
#include "OLED.h" 
#include "MPU6050.h"
int16_t AX,AY,AZ,GX,GY,GZ;
uint8_t ID;
int main(void)
{
	
	OLED_Init();
	MPU6050_Init();
	
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1,1,ID,2);
	while(1)
	{
		MPU6050_GetValue(&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);
	}	
}
	

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值