I2C通信协议

一、I2C通信

1、I2C(Inter IC Bus)是由Philips(飞利浦)公司开发的一种通用数据总线

2、两根通信线:SCL(Serial Clock)、SDA(Serial Data)

3、同步半双工

4、带数据应答

5、支持总线挂载多设备一主多从、多主多从

6、硬件电路

(1)所有I2C设备SCL连在一起SDA连在一起

(2)设备的SCL和SDA均要配置成开漏输出模式

(3)SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

7、I2C时序基本单元

(1)起始条件:SCL高电平期间,SDA电平切换到电平

(2)终止条件:SCL高电平期间,SDA电平切换到电平

(3)发送一个字节SCL低电平期间,主机数据位依次放到SDA线上高位先行),然后释放SCL从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

(4)接收一个字节SCL低电平期间,从机数据位依次放到SDA线上高位先行),然后释放SCL主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节主机在接收之前,需要释放SDA

(5)发送应答主机接收完一个字节之后,在下一个时钟发送一位数据数据0表示应答数据1表示非应答

(6)接收应答主机发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答数据0表示应答数据1表示非应答主机在接收之前,需要释放SDA

8、I2C时序

(1)指定地址写

        对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

(2)当前地址读

        对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

(3)指定地址读

        对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

二、MPU6050陀螺仪加速度计

1、MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车飞行器等需要检测自身姿态的场景

        (1)3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度

        (2)3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度

2、MPU6050参数

(1)16位ADC采集传感器的模拟信号,量化范围:-32768~32767

(2)加速度计满量程选择:±2、±4、±8、±16(g)

(3)陀螺仪满量程选择: ±250、±500、±1000、±2000(°/sec)

(4)可配置的数字低通滤波器

(5)可配置的时钟源

(6)可配置的采样分频

(7)I2C从机地址:1101000(AD0=0)             

                                 1101001(AD0=1)

3、硬件电路

4、MPU6050框图

三、软件I2C读写MPU6050

1、按照以下接线方式连接,并将STLINK插到电脑上

2、多层模块架构

(1)最底层:I2C协议层

        MyI2C.c

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

#define SCL_PORT GPIOB
#define SCL_PIN	 GPIO_Pin_10	

/*
	写SCL
*/
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
	Delay_us(10);
}

/*
	写SDA
*/
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
	Delay_us(10);
}

/*
	读SDA
*/
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

/*
	初始化I2C
*/
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)
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);

}

/*
	停止条件
*/
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

/*
	发送一个字节
*/
void MyI2C_Sendbyte(uint8_t Byte)
{
	uint8_t i;
	for(i=0;i<8;i++)
	{
		MyI2C_W_SDA(Byte & (0x80>>i));//右移i位
		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);
		if(MyI2C_R_SDA() == 1){Byte |= (0x80>>i);}
		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;
	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_Receivebyte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);


#endif

(2)协议层之上: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_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);
	MyI2C_ReceiveAck();
	Data = MyI2C_Receivebyte();
	MyI2C_SendAck(1);
	MyI2C_Stop();
	
	return Data;
}

void MPU6050_Init(void)
{
	MyI2C_Init();
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//配置电源管理寄存器1,解除睡眠,选择陀螺仪时钟
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//配置电源管理寄存器2,6个轴均不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//配置采样率分频,10分频
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);//配置寄存器,滤波参数给最大
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪配置寄存器,选择最大量程
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度计配置寄存器,选择最大量程
}

/*
	获取芯片ID号
*/
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);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH<<8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH<<8) | DataL;
	
	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);
void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
						int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ);
uint8_t MPU6050_GetID(void);
#endif

(3)应用层:主函数

mian.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.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);
	}
}

3、实现效果

软件I2C读写MPU6050

四、I2C外设简介

1、I2C外设简介

(1)STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成起始终止条件生成应答位收发数据收发等功能,减轻CPU的负担

(2)支持多主机模型

(3)支持7位/10位地址模式

(4)支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)

(5)支持DMA

(6)兼容SMBus(系统管理总线)协议

(7)STM32F103C8T6 硬件I2C资源:I2C1、I2C2

2、I2C外设框图

3、I2C基本结构

4、主机发送

5、主机接收

6、软件/硬件波形对比

五、硬件I2C读写MPU6050

1、I2C引脚

2、把MyI2C模块移出工程

(1)在选项卡对应的文件这里右键,关闭.c和.h文件

(2)在工程树对应的文件这里右键,把.c和.h文件移除

(3)把工程目录对应的两个文件也删掉,保证工程树和工程目录里的文件一致,有利于工程管理

3、I2C函数驱动模块

(1)I2C库函数的功能

(2)状态监控函数

(3)MPU6050.c

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

#define MPU6050_ADDRESS 0XD0

/*
	将CheckEvent封装成带有超时退出机制的WaitEvent函数
*/
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
	uint32_t Timeout;
	Timeout = 10000;
	while(I2C_CheckEvent(I2Cx,I2C_EVENT) != SUCCESS)//EV5:主机模式选择
		{						//计次计时超时退出机制
			Timeout--;
			if(Timeout ==0)
			{
				break;
			}
		}
}

/*
	指定地址写
*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2,ENABLE);//起始条件
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5:主机模式选择
	
	
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送:地址最低位清0,接收:地址最低位置1,发送数据自带接收应答过程
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6:主机发送模式已选择
	
	I2C_SendData(I2C2,RegAddress);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8: 主机字节正在发送
	
	I2C_SendData(I2C2,Data);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) ;//EV8_2: 主机字节已经发送完毕
	
	I2C_GenerateSTOP(I2C2,ENABLE);//终止条件
}

/*
	指定地址读
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2,ENABLE);//起始条件
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5:主机模式选择
	
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);//发送:地址最低位清0,接收:地址最低位置1,发送数据自带接收应答过程
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6:主机发送模式已选择
	
	I2C_SendData(I2C2,RegAddress);
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2: 主机字节已经发送完毕,等待字节发送完毕再重新开始
	
	I2C_GenerateSTART(I2C2,ENABLE);//重新起始条件
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5:主机模式选择
	
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);//接收:地址最低位置1,读
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6:主机接收模式已选择
	
	I2C_AcknowledgeConfig(I2C2,DISABLE);//不给应答
	I2C_GenerateSTOP(I2C2,ENABLE);//申请产生终止条件
	
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//EV7:主机字节已经接收完毕
	Data = I2C_ReceiveData(I2C2);//读取DR的值放在Data里
	
	I2C_AcknowledgeConfig(I2C2,ENABLE);//ACK置1,默认状态下ACK就是1,给丛机应答
	
	return Data;
}

void MPU6050_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);		//开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	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_InitTypeDef I2C_InitStructure;						//I2C初始化
	I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
	I2C_InitStructure.I2C_ClockSpeed=50000;//50MHz 标准速度
	I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;//快速模式下的占空比,标准速度下占空比没有用
	I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_OwnAddress1=0x00;
	I2C_Init(I2C2,&I2C_InitStructure);
	
	I2C_Cmd(I2C2,ENABLE);	//使能I2C2
	
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//配置电源管理寄存器1,解除睡眠,选择陀螺仪时钟
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//配置电源管理寄存器2,6个轴均不待机
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//配置采样率分频,10分频
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);//配置寄存器,滤波参数给最大
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪配置寄存器,选择最大量程
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度计配置寄存器,选择最大量程
}

/*
	获取芯片ID号
*/
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);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH<<8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH<<8) | DataL;
	
	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;
}

(4)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);
void MPU6050_GetData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
						int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ);
uint8_t MPU6050_GetID(void);
#endif

4、编写main.c代码和实现效果

        硬件I2C读写MPU6050和软件I2C读写MPU6050的main.c代码和实现效果是相同的,不再重复展示,软件的I2C初始化是用MyI2C协议层进行编写,硬件不需要这一步,可以直接用库函数的结构体进行初始化,但是在读写数据时要注意while的死循环情况,要设置超时退出机制。

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
I2C通信协议是一种串行总线协议,它允许多个设备在同一条总线上进行通信。在I2C总线上,每个设备都有一个唯一的地址,可以通过这个地址与其他设备进行通信。下面是I2C通信协议详细讲解: 1. 总线结构 I2C总线结构包括两根信号线:SCL和SDA。SCL是时钟线,由主设备负责产生;SDA是数据线,用于传输数据。 2. 通信方式 I2C通信协议主要分为两种方式:传输数据和发送命令。在传输数据时,从设备首先向主设备发送一个应答信号(ACK),主设备接收到应答信号后,才会继续发送数据。在发送命令时,主设备向从设备发送命令并等待从设备的应答信号。 3. 传输数据 在I2C总线上传输数据时,每个字节都由8位二进制数字组成。在传输一个字节之前,主设备必须向从设备发送一个起始信号(Start Bit),表示一个新的传输过程开始了。然后主设备会先发送从设备的地址(包括读/写位),然后等待从设备的应答信号。如果从设备存在,并且它的地址与主设备发送的地址匹配,那么它会发送一个应答信号。接下来主设备会开始发送数据,每发送一个字节就等待从设备的应答信号。当主设备发送完最后一个字节后,它会发送一个停止信号(Stop Bit),表示这次传输已经结束了。 4. 发送命令 在I2C总线上发送命令时,主设备首先向从设备发送命令,并等待从设备的应答信号。如果从设备存在,并且它的地址与主设备发送的地址匹配,那么它会发送一个应答信号。接下来主设备可以向从设备发送一个或多个字节的数据,然后等待从设备的应答信号。当主设备完成数据传输后,它会向从设备发送停止信号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值