关于MPU6050学习的一些总结之二IIC协议

前言

在查阅并了解了MPU6050的芯片手册后,已经对MPU6050有了大致的了解。那么接下来如何使MPU6050工作并且读取内部寄存器的数据呢?由于MPU6050是一个IIC元件,因此要是想使MPU6050工作首先要对IIC协议有一定的了解。简单查阅之后了解了IIC目前主要由软件IIC和硬件IIC,硬件IIC就是通过硬件来模拟IIC的时序完成IIC协议,在我使用的STM32的开发板上就有一片AT24C02芯片是用来实现这个功能的,但是网上说硬件IIC经常会出现bug使传感器卡死的现象,保险起见还是应用软件编程来实现IIC。

一、IIC总线

关于IIC总线的概念,在IIC协议标准上是这么介绍的:I2C 总线支持任何IC 生产过程。两线――串行数据(SDA )和串行时钟(SCL)线在连接到总线的器件间传递信息。每个器件都有一个唯一的地址识别,无论是微控制器、LCD、驱动器、存储器或键盘接口。而且都可以作为一个发送器或接收器由器件的功能决定。很明显LCD只是一个接收器,而存储器则既可以接收又可以发送数据。除了发送器和接收器外,器件在执行数据传输时也可以被看作是主机或从机,主机是初始化总线的数据传输并产生允许传输的时钟信号的器件。此时任何被寻址的器件都被认为是从机。关于主机和从机等相关术语参考下表。
IIC术语
也就是说IIC总线简单来说就是由两根线构成的,一个是SCL负责产生时钟信号,还有一个是SDA负责传输数据。在这两根线上并联着很多器件,它们之间可以互相传递信息。但是就像打电话一样,只有你拨对了正确的电话号码(也就是主机发送的地址与从机的地址相对应,每个模块的地址是唯一的)时,两个设备之间才能开始通信。在打电话这个例子中,打电话双方都可以说话和听对方说话,因此双方都可以既是发送器又是接收器。主机也就是打电话的人,可以发出打电话的动作(也就是发送起始信号),还以放下电话结束对话(也就是发送结束信号)双方对话便结束了。从机的任务就很简单了,只负责在被主机call的时候拿起电话就行了。那么主机与从机之间是以什么样的方式进行“交流”的呢,这就需要SDA和SCL两条线相互合作了。这里附上一张IIC总线的系统结构图。
IIC总线系统结构图
如图,SDA 和SCL 都是双向线路都通过一个电流源或上拉电阻连接到正的电源电压,当总线空闲时,这两条线路都是高电平。I2C 总线上数据的传输速率在标准模式下可达100kbit/s 在快速模式下可达400kbit/s 在高速模式下可达3.4Mbit/s。
接下来介绍几个有助于理解时序图的概念。

1.数据的有效性

在IIC总线的位传输时,SDA线上的数据必须在时钟的高电平期间内保持稳定,为了保证数据的有效性,数据线的高低电平变换只能在SCL线的时钟信号是低电平期间变化,如图所示。
数据有效性

2.空闲信号

当SDA线和SCL线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处于截止状态,即释放总线。由两条信号线各自的上拉电阻把电平拉高。

3.起始和停止条件

在I2C 总线中唯一出现的是被定义为起始(S) 和停止(P) 条件的情况。
其中,起始条件(S)是在SCL 线是高电平时SDA 线从高电平向低电平切换,
停止条件(P)是指当SCL 是高电平时SDA 线由低电平向高电平切换。起始和停止条件一般由主机产生,总线在起始条件后被认为处于忙的状态,在停止条件的某段时间后总线被认为再次处于空闲状态。起始与停止条件的时序图如图所示。
起始与停止条件时序图

4.数据传输

在主机发送起始信号之后,主机与从机之间便可以传输数据了。

4.1 字节格式

发送到SDA 线上的每个字节必须为8 位,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应位(判断接收器是否接收到了数据)。首先传输的是 数据的最高位(MSB),如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,那么可以使时钟线SCL 保持低电平迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线SCL后数据传输继续。这里要注意的是由于各个器件之间的并联结构,只要有一个器件拉低电平就会使SCL总线的电平变低。因此,从机可以控制SCL为低电平从而去处理其他事情。

4.2 响应

数据传输必须带响应,相关的响应时钟脉冲由主机(主机除了可以发出起始和停止信号外,还有一个负责产生SCL时钟的功能)产生。在响应的时钟脉冲期间发送器释放SDA 线,在响应的时钟脉冲期间接收器必须将SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平,以表示接收到数据(我听到你说的话啦),下图是IIC总线在数据传输时的时序图。
数据传输时序图
IIC总线的响应时序图如下。
应答时序图

4.3 地址格式

现在已经知道了什么时候开始传输数据,什么时候停止传输数据,也知道了数据是怎样传输的以及如何判断接收器是否接收到了数据。那么顺理成章,我们有了疑问,从机怎么知道自己是从机呢?知道自己是从机之后,“我”是要发送数据还是接受数据呢?这就要从主机发出起始信号说起了,在主机发出起始信号后的第一次八位数据是由7位地址位以及1位数据方向位(读写位)构成的,前面说到了在IIC总线上每个器件都有自己唯一的地址,因而这些器件就是根据这7位地址位与自己的地址进行比较,相同的话就表示自己被翻牌子了。那么剩下的数据方向位就表示主机是写数据(对应‘0’),还是读数据(对应‘1’)。至此就完成了从机的选定以及数据传输方向的确定。对于MPU6050由于引脚AD0接的是最低地址位,使得其从机地址有两种可能,当AD0接低电平时(也是我们通常用的)地址为0x68对应的七位地址为110 1000,当AD0接高电平时,地址位0x69对应的七位地址为110 1001。完整的数据传输时序图如下。
完整数据传输时序图
最后经过查阅资料后,在编写程序之前还需要了解的对于信号的要求可以参考下图。
时间要求

二、软件IIC的实现

在学习了IIC协议的基本原理之后,我尝试着读一下之前在网上下载的一个四旋翼程序里的IIC.c文件,了解如果通过软件实现IIC。

IIC.h

首先看头文件里的一些宏定义

#define SCL_H         GPIO_I2C->BSRR = SCL_PIN /* GPIO_SetBits(GPIOB , GPIO_Pin_6)   */
#define SCL_L         GPIO_I2C->BRR  = SCL_PIN /* GPIO_ResetBits(GPIOB , GPIO_Pin_6) */

#define SDA_H         GPIO_I2C->BSRR = SDA_PIN /* GPIO_SetBits(GPIOB , GPIO_Pin_7)   */
#define SDA_L         GPIO_I2C->BRR  = SDA_PIN /* GPIO_ResetBits(GPIOB , GPIO_Pin_7) */

#define SCL_read      GPIO_I2C->IDR  & SCL_PIN /* GPIO_ReadInputDataBit(GPIOB , GPIO_Pin_6) */
#define SDA_read      GPIO_I2C->IDR  & SDA_PIN /* GPIO_ReadInputDataBit(GPIOB , GPIO_Pin_6) */

之后再查找GPIO_I2C以及SCL_PIN的定义有

#define GPIO_I2C	     GPIOB
#define SCL_PIN        GPIO_Pin_6
#define SDA_PIN        GPIO_Pin_7
#define RCC_GPIO_I2C	 RCC_APB2Periph_GPIOB

显然,头文件里用SCL_H、SCL_L、SDA_H、SCL_L分别将STM32的GPIOB.6和
GPIOB.7口置1或置0(这两个口分别是单片机的SCL和SDA引脚)。

初始化程序

void I2C_INIT(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure; 
 
	RCC_APB2PeriphClockCmd(RCC_GPIO_I2C, ENABLE);  //使能GPIOB时钟
  
	GPIO_InitStructure.GPIO_Pin =  SCL_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  
  GPIO_Init(GPIO_I2C, &GPIO_InitStructure);

  GPIO_InitStructure.GPIO_Pin =  SDA_PIN;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  GPIO_Init(GPIO_I2C, &GPIO_InitStructure);
}

该函数大致就是使能GPIOB的时钟,并设置6、7口为开漏输出。

延时程序

void I2C_delay(void)
{
	__NOP();
}

void delay5ms(void)
{
		
   int i=5000;  
   while(i) 
   { 
     i--; 
   }  
}

这里的_NOP();为空指令,其执行时间是一个机器周期(12个时钟周期),在查阅手册后了解到该程序用到的开发板的晶振是8M,那么执行一个_NOP();的时间大约是1.5us。

起始信号和停止信号

uint16_t I2C_Start(void)
{
	SDA_H;
	SCL_H;
	I2C_delay();
	if(!SDA_read)return FALSE;	//SDA线为低电平则总线忙,退出
	SDA_L;
	I2C_delay();
	if(SDA_read) return FALSE;	//SDA线为高电平则总线出错,退出
	SDA_L;
	I2C_delay();
	return TRUE;
}
void I2C_Stop(void)
{
	SCL_L;
	I2C_delay();
	SDA_L;
	I2C_delay();
	SCL_H;
	I2C_delay();
	SDA_H;
	I2C_delay();
} 

应答、非应答与等待应答

void I2C_Ack(void)
{	
	SCL_L;
	I2C_delay();
	SDA_L;
	I2C_delay();
	SCL_H;
	I2C_delay();
	SCL_L;
	I2C_delay();
}   
void I2C_NoAck(void)
{	
	SCL_L;
	I2C_delay();
	SDA_H;
	I2C_delay();
	SCL_H;
	I2C_delay();
	SCL_L;
	I2C_delay();
} 
uint16_t I2C_WaitAck(void) 	 //返回为:=1有ACK,=0无ACK
{
	SCL_L;
	I2C_delay();
	SDA_H;			
	I2C_delay();
	SCL_H;
	I2C_delay();
	if(SDA_read)
	{
      SCL_L;
	  I2C_delay();
      return FALSE;
	}
	SCL_L;
	I2C_delay();
	return TRUE;
}

数据传输

void I2C_SendByte(unsigned char SendByte) //发送一字节数据
{
    u8 i=8;
    while(i--)
    {
        SCL_L;
        I2C_delay();
      if(SendByte&0x80)//获取最高位
        SDA_H;  
      else 
        SDA_L;   
        SendByte<<=1;
        I2C_delay();
		SCL_H;
        I2C_delay();
    }
    SCL_L;
   }
unsigned char I2C_RadeByte(void)  //读取一字节数据
{ 
    u8 i=8;
    u8 ReceiveByte=0;

    SDA_H;				
    while(i--)
    {
      ReceiveByte<<=1;      
      SCL_L;
      I2C_delay();
	  SCL_H;
      I2C_delay();	
      if(SDA_read)
      {
        ReceiveByte|=0x01;
      }
    }
    SCL_L;
    return ReceiveByte;
} 

完整数据传输的实现

在完成以上的基础函数之后,加以综合便可以得到完整读写数据的函数了。

//单字节写入*******************************************
uint16_t Single_Write(unsigned char SlaveAddress,unsigned char REG_Address,unsigned char REG_data)		     //void
{
  	if(!I2C_Start())return FALSE;
    I2C_SendByte(SlaveAddress);   //这里的SlaveAddress是8位数据,即7位从机地址+1位‘0’写地址。如:MPU6050的SlaveAddress为0xD0,在后面的宏定义的文件中得到验证。
    if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
    I2C_SendByte(REG_Address );        
    I2C_WaitAck();	
    I2C_SendByte(REG_data);
    I2C_WaitAck();   
    I2C_Stop(); 
    delay5ms();
    return TRUE;
}
//单字节读取*****************************************
unsigned char Single_Read(unsigned char SlaveAddress,unsigned char REG_Address)
{  
	unsigned char REG_data;     	
	if(!I2C_Start())return FALSE;
    I2C_SendByte(SlaveAddress); //同上
    if(!I2C_WaitAck()){I2C_Stop();return FALSE;}
    I2C_SendByte((u8) REG_Address);      
    I2C_WaitAck();
    I2C_Start();
    I2C_SendByte(SlaveAddress+1);//SlaveAddress+1即把末位由‘0’变为‘1’,也就是从写数据变为读数据。
    I2C_WaitAck();
	REG_data= I2C_RadeByte();
    I2C_NoAck();
    I2C_Stop();
    //return TRUE;
	return REG_data;
}	

结语

至此,IIC协议的学习也就结束了。接下来的工作就是结合上一篇文章里了解的MPU6050寄存器以及这篇文章学习的IIC协议来解读MPU6050的程序,来看看MPU6050是怎样分析数据的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值