集成电路技术分享

 找回密码
 我要注册

QQ登录

只需一步,快速开始

搜索
查看: 9298|回复: 8

简单的I2C协议理解及通用的程序

[复制链接]
ddr 发表于 2010-4-12 11:59:16 | 显示全部楼层 |阅读模式
本帖最后由 fpgaw 于 2011-1-18 04:42 编辑

一. 技术性能:

    工作速率有100K和400K两种;

    支持多机通讯;

    支持多主控模块,但同一时刻只允许有一个主控;      

    由数据线SDA和时钟SCL构成的串行总线;

    每个电路和模块都有唯一的地址;                    

    每个器件可以使用独立电源

二. 基本工作原理:

    以启动信号START来掌管总线,以停止信号STOP来释放总线;

    每次通讯以START开始,以STOP结束;

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

    当被控器件检测到收到的地址与自己的地址相同时,在第9个时钟期间反馈应答信号;

    每个数据字节在传送时都是高位(MSB)在前;

写通讯过程:

    1. 主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;

    2. 发送一个地址字节(包括7位地址码和一位R/W);

    3. 当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);

    4. 主控收到ACK后开始发送第一个数据字节;

    5. 被控器收到数据字节后发送一个ACK表示继续传送数据,发送NACK表示传送数据结束;

    6. 主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

读通讯过程:

    1. 主控在检测到总线空闲的状况下,首先发送一个START信号掌管总线;

    2. 发送一个地址字节(包括7位地址码和一位R/W);

    3. 当被控器件检测到主控发送的地址与自己的地址相同时发送一个应答信号(ACK);

    4. 主控收到ACK后释放数据总线,开始接收第一个数据字节;

    5. 主控收到数据后发送ACK表示继续传送数据,发送NACK表示传送数据结束;

    6. 主控发送完全部数据后,发送一个停止位STOP,结束整个通讯并且释放总线;

四. 总线信号时序分析

    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。

五. 寻址约定

    地址的分配方法有两种:

    1. 含CPU的智能器件,地址由软件初始化时定义,但不能与其它的器件有冲突;

    2. 不含CPU的非智能器件,由厂家在器件内部固化,不可改变。

    高7位为地址码,其分为两部分:

    1. 高4位属于固定地址不可改变,由厂家固化的统一地址;

    2. 低三位为引脚设定地址,可以由外部引脚来设定(并非所有器件都可以设定);

#include

#include

#define   uchar unsigned char   /*宏定义*/

#define   uint   unsigned int

extern void Delay1us(unsigned char );

sbit SDA=P1^6;              /*模拟I2C数据传送位*/

sbit SCL=P1^7;              /*模拟I2C时钟控制位*/

bit ack;            /*应答标志位*/

/*******************************************************************

                      起动总线函数               

函数原型: void   Start_I2c();  

功能:      启动I2C总线,即发送I2C起始条件.  

********************************************************************/

void Start_I2c()

{

   SDA=1;     /*发送起始条件的数据信号*/

   Delay1us(1);

   SCL=1;

   Delay1us(5);      /*起始条件建立时间大于4.7us,延时*/

   

   SDA=0;     /*发送起始信号*/

   Delay1us(5);      /* 起始条件锁定时间大于4μs*/

        

   SCL=0;     /*钳住I2C总线,准备发送或接收数据 */

   Delay1us(2);

}

/*******************************************************************

                       结束总线函数               

函数原型: void   Stop_I2c();  

功能:      结束I2C总线,即发送I2C结束条件.  

********************************************************************/

void Stop_I2c()

{

   SDA=0;    /*发送结束条件的数据信号*/

   Delay1us(1);     /*发送结束条件的时钟信号*/

   SCL=1;    /*结束条件建立时间大于4us*/

   Delay1us(5);

  

   SDA=1;    /*发送I2C总线结束信号*/

   Delay1us(4);

}

/*******************************************************************

                  字节数据发送函数               

函数原型: void   SendByte(uchar c);

功能:      将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对

           此状态位进行操作.(不应答或非应答都使ack=0)     

         发送数据正常,ack=1; ack=0表示被控器无应答或损坏。

********************************************************************/

void   SendByte(uchar c)

{

uchar BitCnt;

for(BitCnt=0;BitCnt<8;BitCnt++)   /*要传送的数据长度为8位*/

     {

      if((c<< *判断发送位*>

        else   SDA=0;               

      Delay1us(1);

      SCL=1;                /*置时钟线为高,通知被控器开始接收数据位*/

      

      Delay1us(5);              /*保证时钟高电平周期大于4μs*/

            

      SCL=0;

     }

   

     Delay1us(2);

     SDA=1;                 /*8位发送完后释放数据线,准备接收应答位*/

     Delay1us(2);   

     SCL=1;

     Delay1us(3);

     if(SDA==1)ack=0;     

        else ack=1;         /*判断是否接收到应答信号,由从机返回*/

     SCL=0;

     Delay1us(2);

}

/*******************************************************************

                  字节数据接收函数               

函数原型: uchar   RcvByte();

功能:      用来接收从器件传来的数据,并判断总线错误(不发应答信号),

           发完后请用应答函数应答从机。  

********************************************************************/

uchar   RcvByte()

{

   uchar retc;

   uchar BitCnt;

  

   retc=0;

   SDA=1;                /*置数据线为输入方式*/

   for(BitCnt=0;BitCnt<8;BitCnt++)

       {

         Delay1us(1);   

         SCL=0;                   /*置时钟线为低,准备接收数据位*/

        

         Delay1us(5);                  /*时钟低电平周期大于4.7μs*/

      

         SCL=1;                   /*置时钟线为高使数据线上数据有效*/

         Delay1us(3);

         retc=retc<<1;

         if(SDA==1)retc=retc+1;   /*读数据位,接收的数据位放入retc中 */

         Delay1us(2);

       }

   SCL=0;   

   Delay1us(2);

   return(retc);

}

/********************************************************************

                      应答子函数 (用于主机读取数据后,应答从机)

函数原型:   void Ack_I2c(bit a);

功能:       主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)

********************************************************************/

void Ack_I2c(bit a)

{

  

   if(a==0)SDA=0;            /*在此发出应答或非应答信号 */

         else SDA=1;

   Delay1us(3);      

   SCL=1;

  

   Delay1us(5);                     /*时钟低电平周期大于4μs*/

   

   SCL=0;                      /*清时钟线,钳住I2C总线以便继续接收*/

   Delay1us(2);   

}

/*******************************************************************

                     向有子地址器件发送多字节数据函数               

函数原型: bit   ISendStr(uchar sla,uchar suba,ucahr *s,uchar no);  

功能:      从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件

           地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。

           如果返回1表示操作成功,否则操作有误。

注意:     使用前必须已结束总线。

********************************************************************/

bit ISendStr(uchar sla,uchar suba,uchar *s,uchar no)

{

    uchar i;

    Start_I2c();                /*启动总线*/

    SendByte(sla);              /*发送器件地址*/

    if(ack==0)return(0);

    SendByte(suba);             /*发送器件子地址*/

    if(ack==0)return(0);

    for(i=0;i< FONT>

    {   

      SendByte(*s);             /*发送数据*/

      if(ack==0)return(0);       // 检测从机发送过来的应答位,ack由sendbyte函数返回

      s++;

    }

    Stop_I2c();                 /*结束总线*/

    return(1);

}

/*******************************************************************

                     向有子地址器件读取多字节数据函数               

函数原型: bit   ISendStr(uchar sla,uchar suba,ucahr *s,uchar no);  

功能:      从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件

           地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。

            如果返回1表示操作成功,否则操作有误。

注意:     使用前必须已结束总线。

********************************************************************/

bit IRcvStr(uchar sla,uchar suba,uchar *s,uchar no)

{

    uchar i;

    Start_I2c();                   /*启动总线*/

    SendByte(sla);                 /*发送器件地址*/

    if(ack==0)return(0);

    SendByte(suba);                /*发送器件子地址*/

    if(ack==0)return(0);

    Start_I2c();     /*重新启动总线*/

    SendByte(sla+1);

    if(ack==0)return(0);

    for(i=0;i< FONT>

    {   

      *s=RcvByte();                /*接收数据*/

       Ack_I2c(0);                 /*读完一个数据,就发送一个应答位*/  

      s++;

    }

    *s=RcvByte();

    Ack_I2c(1);                    /*全部读完,发送非应位*/

    Stop_I2c();                    /*结束总线*/

    return(1);

}
heilaoshi 发表于 2011-3-9 22:22:32 | 显示全部楼层
看看,虽然看不懂。。。。。。
670687308 发表于 2011-3-10 10:42:59 | 显示全部楼层
回复一下,挣回点银子
wangxia6112 发表于 2011-3-14 17:16:18 | 显示全部楼层
前一阵刚做过I2C的程序,先是软件实现I2C的读写功能,然后是用verilog实现I2C的读写功能,花费了我好长时间啊,虽然期间充满了波折,不过,在我的努力下,最终还是成功地实现了。个人感觉,要写好一个程序,一定要对芯片的datasheet仔细地阅读、推敲。
wolfson 发表于 2011-5-7 12:40:31 | 显示全部楼层
新手,学习一下,好好研究
sophy688 发表于 2011-5-30 18:31:13 | 显示全部楼层
不错不错,支持一下
zjj81515 发表于 2011-6-20 14:00:35 | 显示全部楼层
看下,以前没弄过这个,学习下
ningkuiwang 发表于 2011-12-7 09:14:02 | 显示全部楼层
谢谢楼主分享
tsssnow 发表于 2011-12-7 09:32:34 | 显示全部楼层
您需要登录后才可以回帖 登录 | 我要注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|小黑屋|手机版|Archiver|集成电路技术分享 ( 京ICP备20003123号-1 )

GMT+8, 2024-5-18 19:21 , Processed in 0.131922 second(s), 24 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表