广告

原创 I2C

2011-3-9 15:04 4013 0 分类: EDA/ IP/ 设计与制造

一. I2C协议技术性能:
    工作速率有100K和400K两种;
    支持多机通讯;
    支持多主控模块,但同一时刻只允许有一个主控;     
    由数据线SDA和时钟SCL构成的串行总线;
    每个电路和模块都有唯一的地址;                   
    每个器件可以使用独立电源

二. I2C协议基本工作原理:
    以启动信号START来掌管总线,以停止信号STOP来释放总线;
    每次通讯以START开始,以STOP结束;

void I2C_Start(void)        //在SCL高电平期间,SDA发生从高到低的电平跳变
{
    SDA_H;
    I2C_delay();
    SCL_H;
    I2C_delay();
    SDA_L;                  //发送起始信号
    I2C_delay();
    SCL_L;                  //钳住I2C总线准备发送或接收数据
    I2C_delay();            //I2C总线在空闲状态下都是被上拉为高电平的,所以当它们处于低电平时就表示忙的状态
}

void I2C_Stop(void)         //结束条件的格式是在SCL高电平期间,SDA由低电平向高电平跳变.
{
    SDA_L;                  //发送结束条件的数据信号
    I2C_delay();
    SCL_H;                  //发送结束条件的时钟信号
    I2C_delay();
    SDA_H;
    I2C_delay();
}

    启动信号START后紧接着发送一个地址字节,其中7位为被控器件的地址码,一位为读/写控制位R/W,R. /W位为0表示由主控向被控器件写数据,R/W为1表示由主控向被控器件读数据;

//字节数据传送函数,将数据 c 发送出去,可以是地址,也可以是数据,发完后等待应答,并对此状态位进行操作
//在传送数据时,数据(SDA)的改变只能发生在SCL的低电平期间,在SCL的高电平期间保持不变

void I2C_SendByte(u8 SendByte)  //数据从高位到低位//
{   uint8_t i;
    for(i=0;i<8;i++)            //要传送的数据长度为8
    { 
      if((SendByte<<i)&0x80)    //判断发送位(从高位起发送)
        SDA_H;
      else
        SDA_L;
     
      I2C_delay();
     
      SCL_H;                    //置时钟线为高通知被控器开始接收数据位
      I2C_delay();
      SCL_L;
      I2C_delay();
    }   
    I2C_delay();   
}

//接收从器件传来的数据,并判断总线错误(不发应答信号),收完后需要调用应答函数。

unsigned char I2C_ReceiveByte(void)  //数据从高位到低位//

    uint8_t i;
    uint8_t ReceiveByte=0;

    SDA_H;                //置数据总线为输入方式,作为接收方要释放SDA.
    for(i=0;i<8;i++)
    {
      I2C_delay();
      SCL_L;              //置时钟线为低准备接收数据位
      I2C_delay();
      SCL_H;              //设时钟为高使数据有效
      ReceiveByte<<=1;
      if(SDA_read)
      {
        ReceiveByte|=0x01;
      }
      I2C_delay();
    }
     SCL_L;
     I2C_delay();
     return ReceiveByte;  
}
bool I2C_OneByte_Write(uint8_t pBuffer, uint8_t WriteAddr)
{
    I2C_Start();//启动总线
    I2C_SendByte(0xa0); //发送器件地址
    if(!I2C_WaitAck())
      return FALSE;
   
    I2C_SendByte(WriteAddr); //发送器件子地址
    if(!I2C_WaitAck())
      return FALSE; 
   
    I2C_SendByte(pBuffer);
    if(!I2C_WaitAck())
      return FALSE; 
   
    I2C_Stop();
                //注意:因为这里要等待EEPROM写完,可以采用查询或延时方式(3ms)
    delay_ms(3);
    return TRUE;
}

unsigned char I2C_OneByte_Read(uint8_t WriteAddr)
{
    unsigned char data;
    I2C_Start();//启动总线
    I2C_SendByte(0xa0);
    if(!I2C_WaitAck())
      return FALSE;
   
    I2C_SendByte(WriteAddr);
    if(!I2C_WaitAck())
      return FALSE;

    I2C_Start();//重复起始条件
    I2C_SendByte(0xa1);
    if(!I2C_WaitAck())
      return FALSE;
   
    data = I2C_ReceiveByte();
    I2C_Ackn(FALSE);
    I2C_Stop();
    delay_ms(3);
    return data;
}

    当被控器件检测到收到的地址与自己的地址相同时,在第9个时钟期间反馈应答信号;
    每个数据字节在传送时都是高位(MSB)在前;

写通讯过程:
    1. 主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
    2. 发送一个地址字节(包括7位地址码和一位R/W,0:表示写。1:表示读);
    3. 当被控制器件检测到主机发送的地址与自己的地址相同时发送一个应答信号(ACK)(主机在等待应答信号时,先把DAT拉高,从机应答是把DAT拉低,主机检测到这个拉低,表示有应答);
    4. 主控收到ACK后开始发送第一个数据字节;
    5. 被控器收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束;
    6. 主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

I2C协议读通讯过程:
    1. 主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;
    2. 发送一个地址字节(包括7位地址码和一位R/W);
    3. 当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);
    4. 主控收到ACK后释放数据总线,开始接收第一个数据字节;
    5. 主控收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;
    6. 主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

四. I2C协议总线信号时序分析
    1. 总线空闲状态
    SDA和SCL两条信号线都处于高电平,即总线上所有的器件都释放总线,两条信号线各自的上拉电阻把电平拉高;
    2. 启动信号START
    时钟信号SCL保持高电平,数据信号SDA的电平被拉低(即负跳变)。启动信号必须是跳变信号,而且在建立该信号前必修保证总线处于空闲状态;
    3. 停止信号STOP
    时钟信号SCL保持高电平,数据线被释放,使得SDA返回高电平(即正跳变),停止信号也必须是跳变信号。
    4. 数据传送
    SCL线呈现高电平期间,SDA线上的电平必须保持稳定,低电平表示0(此时的线电压为地电压),高电平表示1(此时的电压由元器件的VDD决定)。只有在SCL线为低电平期间,SDA上的电平允许变化。
    5. 应答信号ACK
    I2C总线的数据都是以字节(8位)的方式传送的,发送器件每发送一个字节之后,在时钟的第9个脉冲期间释放数据总线,由接收器发送一个ACK(把数据总线的电平拉低)来表示数据成功接收。
    6. 无应答信号NACK
    在时钟的第9个脉冲期间发送器释放数据总线,接收器不拉低数据总线表示一个NACK,NACK有两种用途:
    a. 一般表示接收器未成功接收数据字节;
    b. 当接收器是主控器时,它收到最后一个字节后,应发送一个NACK信号,以通知被控发送器结束数据发送,并释放总线,以便主控接收器发送一个停止信号STOP。

五. I2C协议寻址约定
    地址的分配方法有两种:
    1. 含CPU的智能器件,地址由软件初始化时定义,但不能与其它的器件有冲突;
    2. 不含CPU的非智能器件,由厂家在器件内部固化,不可改变。

    高7位为地址码,其分为两部分:
    1. 高4位属于固定地址不可改变,由厂家固化的统一地址;
    2. 低三位为引脚设定地址,可以由外部引脚来设定(并非所有器件都可以设定);

//I2C总线协议中规定传输的每个字节之后必须跟一个应答位,
//所以从器件在接收到每个字节之后必须反馈一个应答信号给主控制器,
//而主控制器就需要检测从器件回传的应答信号
//在由发送器产生的时钟响应周期里,发送器先释放SDA(置高),
//然后由接受器将SDA拉低,并在这个时钟脉冲周期的高电平期间保持稳定的低电平.
//即表示从器件做出了应答

bool I2C_WaitAck(void)      //等待应答信号,返回true为有应答,flase为无应答
{
    uint8_t ErrTime = 255;  // 因故障接收方无 Ack,超时值为255
    SDA_H;                  //发送器先释放SDA
    SCL_H;
    I2C_delay();
    while(SDA_read)
    {
      I2C_delay();
      ErrTime--;
      if(ErrTime==0)
      {
        I2C_Stop();
        return FALSE;
      }
    }
    SCL_L;
    I2C_delay();
    return TRUE;
}


//主控制器进行应答信号(可以是应答或非应答信号)
//作为接收方的时候,必须根据当前自己的状态向发送器反馈应答信号

void I2C_Ackn(bool a)       //a=0为非应答信号
{
    if(a==FALSE)                //在此发送应答或非应答信号
      SDA_H;//----
    else
      SDA_L; //----
    I2C_delay();
    SCL_H;
    I2C_delay();
    SCL_L;                  //清时钟线钳住I2C总线以便继续接收
    I2C_delay();
}

void I2C_delay(void)
{
    unsigned short i=15;          //这里可以优化速度 ,经测试最低到5还能写入
    while(i--) 
    { 
    } 
}
/*=============================================================================
    delay Time function for general purpose 
                begin(have calibrated @ 24MHz CPU CLK)
==============================================================================*/
void delay_us(unsigned short dd)
{
    unsigned short i;
    for(i=0;i<dd;i++)
    {
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop"); 
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
      asm("nop");
    }
}

void delay_ms(unsigned short dd)
{
    unsigned short i;
    for(i=0;i<dd;i++)
    {
      delay_us(992);
    }
}

 因为示波器在周期上很难同步,所以在用示波器看波形时,要设一个定时器,每隔10MS写I2C,这样才可以看到波形。示波器的周期时间设为25US,示波器显示窗口里左上角有一个小三角,是同步标志,尽量把这个移入显示窗口。

 

 

广告

文章评论 0条评论)

登录后参与讨论
相关推荐阅读
d_a_b_521494469 2012-02-03 15:20
评论:@jjldc(九九)的电子博客 博客中提到的“转一篇比较详细介绍FatFs文件系统移植的文章”
11...
d_a_b_521494469 2011-05-04 10:11
STM32 IAP
引导加载程序是存储在内部引导ROM存储器(系统内存),其主要任务是通过下载应用程序到内部FLASH通过USART1的通信接口. 从系统内存启动bootloader然后通过USART1接口外设下载应...
d_a_b_521494469 2011-04-11 09:31
基于AVR单片机队列的UART通信模块
对于堆栈来说,插入、删除操作是固定在一端进行的,这一端称为“栈顶”,另一端称为“栈底”。 堆栈指针(Stack  Pointer)用于指示栈顶位置(地址),在有些单片机中,堆栈指针可以通过程序去设置。...
d_a_b_521494469 2011-04-08 11:03
assert_param STM32的固件库 使用须知
在STM32的固件库和提供的例程中,到处都可以见到assert_param()的使用。如果打开任何一个例程中的stm32f10x_conf.h文件,就可以看到实际上assert_param是一个宏定义...
d_a_b_521494469 2011-03-30 16:50
单片机的非OS的事件驱动思考1
很多单片机项目恐怕都是没有操作系统的前后台结构,就是main函数里用while无限循环各种任务,中断处理紧急任务。这种结构最简单,上手很容易,可是当项目比较大时,这种结构就不那么适合了,编写代码前你...
d_a_b_521494469 2011-03-24 14:41
C
要从逻辑上删除一段C代码,更好的办法是用#if指令。 #if  0   statements#endif int *a;*a = 12;  //我们声明了这个指针变量,但从未对它进行过初始化,所以我们...
我要评论
0
0
广告
关闭 热点推荐上一条 /2 下一条