使用IIC的经验

软件IIC

我自己做四旋翼也有一年了,我开始整理这一年里,通过做四旋翼学习到的单片机知识。四旋翼得用到加速度计,陀螺仪,磁力计,气压计这些模块。芯片用到stm32。首先就要从这些模块获取我们需要的数据,怎么跟这些模块通信呢,我们可以使用IIC,或者SPI。今天我总结一下IIC。

IIC总线

IIC(Inter-Integrated Circuit)协议是由Philips公司开发的,由于它具备引脚少,硬件实现简单,可拓展性强,不需要加USART,CAN的外部收发设备等特点,现在被广泛地使用在系统内多个间的通信。常见的I2C通信系统模型如图所示。
使用IIC的经验_第1张图片

  1. 物理层
    (1)它只使用两条总线线路:一条双向串行数据线(SDA),一条串行时钟线(SCL)
    (2)每个连接到总线的设备都有一个独立地址,主机可以利用这个地址进行不同设备之间的访问。设备地址是7位的,所以2的7次方为128,最多可以有128个设备。连接到相同总线上的IC数量受到总线的最大电容400pF的限制。
    (3)多主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
    (4)具有三种传输模式:标准的传输速率为100kb/s,快速模式为400kb/s,高速模式下可达3.4Mb/s,但目前大多IIC设备尚不支持高速模式。
    (5)片上的滤波器可以滤去总线数据线上的毛刺波以保证数据完整

  2. 协议层
    I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地 址广播等环节
    (1)
    先看看 I2C 通讯过程的基本结构,它的通讯过程见图 24-2、图 24-3及图 24-4。
    使用IIC的经验_第2张图片

这些图表示的是主机和从机通讯时,SDA线的数据包序列。 其中 S 表示由主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有 从机都会接收到这个信号。 起始信号产生后,所有从机就开始等待主机紧接下来广播 的从机地址信号 (SLAVE_ADDRESS)。在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与 某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。 根据 I2C协议,这个从机地址可以是 7位或 10 位。 在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主 机传输至从机,即主机向从机写数据。该位为 1时,则相反,即主机由从机读数据。 从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号, 只有接收到应答信号后,主机才能继续发送或接收数据。
写数据
若配置的方向传输位为“写数据”方向,即第一幅图的情况,广播完地址,接收到应 答信号后,主机开始正式向从机传输数据(DATA),数据包的大小为 8 位,主机每发送完一 个字节数据,都要等待从机的应答信号(ACK),重复这个过程,可以向从机传输 N 个数据, 这个 N 没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号§,表示不 再传输数据。 读数据
若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址,接收到应 答信号后,从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数 据,都会等待主机的应答信号(ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有 大小限制。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自 动停止数据传输。 读和写数据
除了基本的读写,I2C 通讯更常用的是复合格式,即第三幅图的情况,该传输过程有 两次起始信号(S)。一般在第一次传输中,主机通过 SLAVE_ADDRESS 寻找到从设备后, 发送一段“数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它 与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是 说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。 以上通讯流程中包含的各个信号分解如下:
(2) 通讯的起始和停止信号
前文中提到的起始(S)和停止§信号是两种特殊的状态,见图 24-5。当 SCL 线是高电 平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。
使用IIC的经验_第3张图片
(3)数据有效性
I2C 使用 SDA 信号线来传输数据,使用 SCL 信号线进行数据同步。见图 24-6。SDA 数据线在 SCL 的每个时钟周期传输一位数据。传输时,SCL 为高电平的时候 SDA 表示的 数据有效,即此时的 SDA为高电平时表示数据“1”,为低电平时表示数据“0”。当 SCL 为低电平时,SDA 的数据无效,一般在这个时候 SDA 进行电平切换,为下一次表示数据 做好准备。
使用IIC的经验_第4张图片
(4)地址及数据方向
I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送 设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实 际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W ),第 8 位或第 11 位。数据方向位为“1”时表示主机由从机读数据,该
位为“0”时表示主机向从机写数据。见图 24-7

(5)响应
I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种 信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后, 若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下 一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接 收到该信号后会产生一个停止信号,结束信号传输。见图 24-8。
使用IIC的经验_第5张图片
传输时主机产生时钟,在第 9个时钟时,数据发送端会释放 SDA的控制权,由数据接 收端控制 SDA,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
3. 代码
跟其它外设一样,STM32 标准库提供了 I2C 初始化结构体及初始化函数来配置 I2C 外 设。初始化结构体及函数定义在库文件“stm32f10x_i2c.h”及“stm32f10x_i2c.c”中,编程 时我们可以结合这两个文件内的注释使用或参考库帮助文档。
但是,stm32的硬件IIC在使用时存在bug,具体不细说,所以我用gpio模拟IIC的时序。
(1)初始化GPIO
使用IIC的经验_第6张图片
使用IIC的经验_第7张图片
(2)起始信号,停止信号,应答信号。可以对应协议层的图的时序理解
使用IIC的经验_第8张图片
使用IIC的经验_第9张图片
(3)以上函数就可以组合成收发1byte的函数
使用IIC的经验_第10张图片
使用IIC的经验_第11张图片
(4)接下来也是以上函数各种组合

// An highlighted block
void Single_WriteI2C(unsigned char SlaveAddress,unsigned char REG_Address,unsigned char REG_data)
{
  I2C_Start();
  I2C_SendByte(SlaveAddress);
  I2C_SendByte(REG_Address);
  I2C_SendByte(REG_data);
  I2C_Stop();
}

//**************************************
unsigned char Single_ReadI2C(unsigned char SlaveAddress,unsigned char REG_Address)
{
  unsigned char REG_data;
  I2C_Start();
  I2C_SendByte(SlaveAddress);
  I2C_SendByte(REG_Address);
  I2C_Start();
  I2C_SendByte(SlaveAddress+1);
  REG_data=I2C_RecvByte();
  I2C_SlaveAck();
  I2C_Stop();
  return REG_data;
}

unsigned short int Double_ReadI2C(unsigned char SlaveAddress,unsigned char REG_Address)
{
  unsigned char msb , lsb ;
  msb = Single_ReadI2C(SlaveAddress,REG_Address);
  lsb = Single_ReadI2C(SlaveAddress,REG_Address+1);
  return ( ((unsigned short int)msb) << 8 | lsb) ;
}

unsigned long int Three_ReadI2C(unsigned char SlaveAddress,unsigned char REG_Address)
{
  unsigned char msb , lsb,xlsb;
  msb = Single_ReadI2C(SlaveAddress,REG_Address);
  lsb = Single_ReadI2C(SlaveAddress,REG_Address+1);
  xlsb= Single_ReadI2C(SlaveAddress,REG_Address+2);
  return (((unsigned long int)msb<< 16)|((unsigned long int)lsb<<8)|xlsb) ;
}



static void I2C_Delay_Adjust(void)
{
  volatile int i = 1;	//7
  while (i)
    i--;
}


//**************************************
void I2C_Start_Adjust(void)
{
  SDA_IOOUT();
  SCL_H;
  SDA_H;
  I2C_Delay_Adjust();
  SDA_L;
  I2C_Delay_Adjust();
}
//**************************************
void I2C_Stop_Adjust(void)
{
  SDA_IOOUT();
  SCL_L;
  I2C_Delay_Adjust();
  SDA_L;
  I2C_Delay_Adjust();
  SCL_H;
  I2C_Delay_Adjust();
  SDA_H;
  I2C_Delay_Adjust();
}

//**************************************
unsigned char I2C_SlaveAck_Adjust(void)
{
  SDA_IOOUT();
  SCL_L;
  I2C_Delay_Adjust();
  SDA_H;
  
  SDA_IOIN();
  I2C_Delay_Adjust();
  SCL_H;
  
  I2C_Delay_Adjust();
  
  if(SDA_read)
  {
    SCL_L;
    return 0;
  }
  SCL_L;
  I2C_Delay_Adjust();
  return 1;
}

//**************************************
void I2C_SendByte_Adjust(unsigned char byte)
{
  unsigned char i = 8;
  SDA_IOOUT();
  while (i--)
  {
    SCL_L;
    I2C_Delay_Adjust();
    if (byte & 0x80)
      SDA_H;
    else
      SDA_L;
    byte <<= 1;
    I2C_Delay_Adjust();
    SCL_H;
    I2C_Delay_Adjust();
  }
  SCL_L;
  if(I2C_SlaveAck_Adjust()==0)
  {
    return ;
  }
}

//**************************************
unsigned char I2C_RecvByte_Adjust(void)
{
  unsigned char i;
  unsigned char dat = 0;
  SDA_IOIN();
  for (i=0; i<8; i++)
  {
    dat <<= 1;
    SCL_H;
    I2C_Delay_Adjust();
    dat |= SDA_read;
    SCL_L;
    I2C_Delay_Adjust();
  }
  return dat;
}

你可能感兴趣的:(使用IIC的经验)