iic 订阅
IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”("I-squared-C"),而“I二C”("I-two-C")则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。 展开全文
IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”("I-squared-C"),而“I二C”("I-two-C")则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。
信息
应    用
外围器件接口
外文名
Inter-Integrated Circuit
简    称
IIC
所属领域
电子
中文名
集成电路总线
所在领域
嵌入式系统
IICIIC简介
即I2C,一种总线结构。例如:内存中的SPD信息,通过IIC,与BX芯片组联系,IIC 存在于英特尔PIIX4结构体系中。随着大规模集成电路技术的发展,把CPU和一个单独工作系统所必需的ROM、RAM、I/O端口、A/D、D/A等外围电路集成在一个单片内而制成的单片机或微控制器愈来愈方便。目前,世界上许多公司生产单片机,品种很多。其中包括各种字长的CPU,各种容量的ROM、RAM以及功能各异的I/O接口电路等等,但是,单片机的品种规格仍然有限,所以只能选用某种单片机来进行扩展。扩展的方法有两种:一种是并行总线,另一种是串行总线。由于串行总线的连线少,结构简单,往往不用专门的母板和插座而直接用导线连接各个设备。因此,采用串行线可大大简化系统的硬件设计。PHILIPS公司早在十几年前就推出了I2C串行总线,利用该总线可实现多主机系统所需的裁决和高低速设备同步等功能。因此,这是一种高性能的串行总线。飞利浦电子公司日前推出新型二选一I2C主选择器,可以使两个I2C主设备中的任何一个与共享资源连接,广泛适用于从MP3播放器到服务器等计算、通信和网络应用领域,从而使制造商和终端用户从中获益。PCA9541可以使两个I2C主设备在互不连接的情况下与同一个从设备相连接,从而简化了设计的复杂性。此外,新产品以单器件替代了I2C多个主设备应用中的多个芯片,有效节省了系统成本。
收起全文
精华内容
下载资源
问答
  • iic

    千次阅读 2016-09-12 21:12:25
    1.IIC 时序的重要特点是在时钟SCK为高电平期间SDA作出各种变化来表示起始,终止,应答,非应答,发送数据时是在时钟为高电平期间让SDA稳定,读数据是在SCK为高电平期间采样从设备的数据,所以起始,终止,应答,非...

    1.IIC 时序的重要特点是在时钟SCK为高电平期间SDA作出各种变化来表示起始,终止,应答,非应答,发送数据时是在时钟为高电平期间让SDA稳定,读数据是在SCK为高电平期间采样从设备的数据,所以起始,终止,应答,非应答,发送数据都要先操作SDA,再操作SCK,如果先操作SCK,则会导致起始,终止,应答,非应答,发送数有干扰

    START信号为例:

    写成这样是对的

    void IIC_START()

    {

        SDA = 1;

    SCK = 1;

    delay_us(1);

    SDA = 0;

    delay_us(1);

     

    SCK = 0;    

    }

     

    这样是有问题的

    void IIC_START()

    {

    SCK = 1;

        SDA = 1;

    delay_us(1);

    SDA = 0;

    delay_us(1);

     

    SCK = 0;    

    }

     

     

    2.接收数据为读SDA线的数据,不会有这个问题。

     

    3.IIC 时序的数据传送从高位开始

     

    4.从本质上理解IIC,主机给从机发送数据,从机是在时钟的下降沿采集SDA的数据,所以主机给出的8个时钟是有讲究的,你要给出有8个下降沿的时钟,应当是这样的:

     

     

     

    而不是这样的

     

    所以代码这样写是对的:

    void IIC_SendByte(unsigned char byte)

    {

     unsigned char i;

     unsigned char temp;

     temp = byte;

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

     {

        SDA = temp & 0x80;

      SCK = 1;

      delay_us(1);

      SCK = 0;

      delay_us(1);

      temp <<= 1;

     }

    }

    这样写会少了一个下降沿,丢掉一个数据位

    void IIC_SendByte(unsigned char byte)

    {

     unsigned char i;

     unsigned char temp;

     temp = byte;

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

     {

      SCK = 0;

      delay_us(1);

        SDA = temp & 0x80;

      SCK = 1;

      delay_us(1);

      temp <<= 1;

     }

    }

     

     

     

     

    5.主机从从机读取数据,本质上是从机检测主机发出的时钟信号有8个上升沿

     

    所以你的读数据代码一定要写出有8个上升沿

    unsigned char IIC_ReceiveByte()

    {

    unsigned char temp = 0;

    unsigned char i;

     

    SDA = 1;              

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

    {

    SCK = 0;

    delay_us(1);

    SCK = 1;

    temp <<= 1;

    if(SDA)  temp = temp + 1;

    delay_us(1);

    }

     

    SCK = 0;

    return temp;

    }

    仔细体会其中奥妙,同样是8个时钟,发送接收数据对于时钟有不同的要求 

     

    6.很多人会遇到这样一个现象:连续从EEPROM读取数据,第一个读出来的是对的,往后读出来的都是0,原因是当读取第一个字节时,主机给从机的应答信号将SDA拉低,即SDA线被钳住,从从机读取数据时,SDA给出的高电平无法将SDA线拉高,导致读出来的数据都为0所以在读取一个新的字节时,一定要将SDA拉高,才可以正常接收EEPROM的数据。

    展开全文
  • IIC

    千次阅读 2018-07-25 15:41:15
    大致的一个数据传输流程是:主机向SDA线上发送一个起始信号,表示有信号进行传输,此时所有连接到IIC总线上的芯片都处于接收状态,接下来,主机发送想要与其进行数据传输的从机地址信号,所有的从机都会接收到该地址...

      大致的一个数据传输流程是:主机向SDA线上发送一个起始信号,表示有信号进行传输,此时所有连接到IIC总线上的芯片都处于接收状态,接下来,主机发送想要与其进行数据传输的从机地址信号,所有的从机都会接收到该地址信号并和自己固有的地址信号进行匹配,当配对成功时,接下来就在时钟信号的带动下进行数据传输,数据的传输是按照每8位一个单元进行数据的传输。每一位的传输过程中,在SCL高电平期间,一定要保证SDA数值的稳定,否则会出现出错的情况,SDA数值的改变发生在SCL的低电平期间。最终8位全部传输完毕,从机产生一个应答信号给主机,主机在接收到该应答信号后决定接下来是发送一组新的数据还是终止发送。

    //I/O方向设置

    #define SDA_OUT() {GPIOB->CRL &= 0X0FFFFFFF;GPIOB->CRL |= 3<<28;} //0011 通用推挽输出,50M

    #define SDA_IN() {GPIOB->CRL &= 0X0FFFFFFF;GPIOB->CRL |= 8<<28;} //1000 上拉/下拉输入

     

    //I/O操作函数

    #define IIC_SCL PBout(6)

    #define IIC_SDA PBout(7)

    #define READ_SDA PBin(7)

     

    其中SDA_IN()、 SDA_OUT()设置了I/O口的方向,也就是SDA是输入那?还是输出那?

    IIC_SCL   是SCL输出管脚

    IIC_SDA   是SDA输出管脚

    READ_SDA  读取SDA输入引脚,等待应答信号时要用。

     

    OK!接下来就磨刀霍霍,开始分析数据传输过程

    1、起始信号

    首先,SDA、SCL线默认是高,表示总线处于空闲状态,接着SDA线被主机拉低,表示主机有信号进行传输,要么是发数据,或者是要进行读数据,当SDA线拉低之后,SCL线也同样被拉低,准备接下来的数据传输。用了原子哥的模拟IIC,此开始信号的产生如下:

    声明:此时将CPU当做了主机,而AT24C02当做了从机。

    //起始信号

    void IIC_Start(void)

    {

    SDA_OUT(); //设为主机输出

    IIC_SDA = 1;

    IIC_SCL = 1;

    delay_us(4);

    IIC_SDA = 0; //scl为高电平时,SDA出现低电平跳变,表示传输开始

    delay_us(5);

    IIC_SCL = 0;    //钳位SCL,方便接下来进行数据传输

     

    }

    此程序产生的波形就是上面起始信号所对应的波形。

     

    2、终止信号

     

    终止信号:就是在SCL为高电平时,SDC出现一个上升沿的跳变,即表示终止信号

    源程序如下:

    //终止信号

    void IIC_Stop(void)

    {

    SDA_OUT();    //设为主机输出

    IIC_SDA = 0;

    IIC_SCL = 1;

    delay_us(2);   //scl为高电平,SDA出现高电平跳变,表示传输结束

    IIC_SDA = 1;

     

    }

    3、等待应答信号

      主机将一组数据发送完毕,接下来就进入等待从机发送応答信号的到来,从机会在第9个时钟信号时,发送该应答信号,此应答信号就是将SDA信号线拉低,而又由于SDA、SCL为开漏输出,故支持线与的形式,当从机那边将SDA线拉低,而主机这边依旧是将SDA线置高,主机通过判断SDA线的高低电平就可以知道是否收到了该应答信号,当然,接收到该应答信号的前提是,使能了从机的应答功能,否则接受不到,之能通过每组发送完毕的一个长延时来保证从机接收数据完毕。

    //等待应答信号到来

    //返回值:1,接收应答失败

    //        0,接收应答成功

    u8 IIC_Wait_Ack(void)

    {

    u16 Errtime=0;

    SDA_IN(); //此时设置为输入

    IIC_SCL = 1;

    IIC_SDA = 1; //线与的关系

    if(READ_SDA)

    {

    Errtime++;

    if(Errtime > 250)

    {

    IIC_Stop();

    return 1;

    }

    }

    else

    IIC_SCL = 0; //钳位,方便下次传输

    return 0;

    }

    程序中需要注意的地方是,虽然设置了GPIO的方向为输入,但是GPIO的电平还是可以设置的,没有矛盾,GPIO方向由CRL  CRH来决定,输出电平由ODR寄存器来决定

     

    4、产生应答信号

    如上所说,从机可以选择产生应答信号,同样也可以选择不产生应答信号。

    产生应答辛号就是从机将SDA线拉低,不产生应答辛号就是SDA线一直保持高电平

    //产生应答信号

    void IIC_Ack(void)

    {

    SDA_OUT();  //此时,相当于主机在接收数据,是被动方

    IIC_SCL = 0;

    IIC_SDA = 0;

    IIC_SCL = 1;

    delay_us(2);

    IIC_SCL = 0;

    }

    需要注意的是,此时,CUP相当于是从机,故I/O方向为输出

    5、不产生应答信号

    //不产生应答信号

    void IIC_NAck(void)

    {

    SDA_OUT();  //此时,相当于主机在接收数据,是被动方

    IIC_SCL = 0;

    IIC_SDA = 1;

    IIC_SCL = 1;

    delay_us(2);

    IIC_SCL = 0;

     

    }

    道理同上

     

    6、写一个字节

    按照该时序图就可以写数据了,流程如下:

     主机:起始信号----->第一位------>第二位--------->   ........    -------->第八位--------->   等待应答信号------->停止信号

    SDA数据的有效性,在上面已经分析过,就是在SCL高电平期间,SDA数据不能改变,这样才是一个有效数据,否则无效

          SDA只能在SCL低电平期间改变

    //发送一个字节数据

    void IIC_Send_Byte(u8 txd)

    {

    u8 i;

    SDA_OUT();

    IIC_SCL=0;//拉低时钟开始数据传输

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

    {

    if(txd & 0x80)

    IIC_SDA = 1;

    else

    IIC_SDA = 0;

    txd <<= 1;

    delay_us(2);

    IIC_SCL = 1;

    delay_us(2);

    IIC_SCL = 0;

    delay_us(2);

    }

    高位先发,程序中严格保证了SDA的有效性

    7、读一个字节

    此时CPU相当于是从机,而芯片才是主机,主机在发数据,从机在接受

    //读一个字节数据   ack = 1 时,产生应答    ack = 0  ,不产生应答

    u8 IIC_Read_Byte(unsigned char ack)

    {

    unsigned char i,receive=0;

    SDA_IN();

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

    {

    IIC_SCL = 0;

    delay_us(2);

    IIC_SCL = 1;   //高电平时数据已经稳定,故进行数据的读取

    receive <<= 1;

    if(READ_SDA)

    receive++;

    }

    if(ack)

    IIC_Ack();

    else

    IIC_NAck();

    return receive;

    }

    展开全文
  • STM32 IIC详解

    万次阅读 多人点赞 2016-07-15 21:25:29
    IIC 即Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,IIC是一种多向控制总线,也就是说...

    目录

    1、IIC定义

    2、IIC协议规范

    2.1 SDA和SCL信号

    2.2 数据有效性​

    2.3 开始和结束信号​

    2.4 字节格式

    2.5 从机地址和读写位​

    3、计算IIC的频率

    4、PCF8536

    4.1 Acknowledge

    4.2 Addressing

    4.3 读写时序


    1、IIC定义

    IIC 即Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司(后被NXP收购)在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,IIC是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。多主多从的通讯协议。

    下文将结合NXP官方的IIC手册讲解IIC协议。下载链接见文末。

    I2C串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。速率最高400Kbit/s。

    在1998年的修订中增加了高速模式,速率高达3.4Mbit/s。(这里不讲,只说快速模式)。

    2、IIC协议规范

    2.1 SDA和SCL信号

    连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能,当总线空

    闲时这两条线路都是高电平。SDA 和SCL 都是双向线路都通过一个电流源或上拉电阻连接到正的电源电压。一般情况下我们都采用上拉电阻的方式

    2.2 数据有效性

    在SCL高电平的时候采样,也就是有效。低电平的时候切换数据

    2.3 开始和结束信号

    起始条件:SCL线是高电平时,SDA线从高电平向低电平切换。

    停止条件:SCL线是高电平时,SDA线从低电平向高电平切换。

    动画展示启动信号

    代码实现

    void I2C_Start(void)
    {
      //IO输出
      SDA_OUT(); 
      SCL_OUT(); 
      I2C_DELAY();
      //IO置高
      SDA_SET();  
      SCL_SET(); 
      //延时
      I2C_DELAY();  
      //为低
      SDA_CLR();
      I2C_DELAY();
      I2C_DELAY();
      SCL_CLR();
    }

    结束信号时类似的方式(不是动图)

    代码实现

    void I2C_Stop(void)
    {
      //IO输出
      SDA_OUT(); 
      SCL_OUT();
      //IO置0
      SDA_CLR();  
      SCL_CLR(); 
      I2C_DELAY();
      SCL_SET();
      //延时
      I2C_DELAY();  
      I2C_DELAY();
      I2C_DELAY();
      //SDA置1
      SDA_SET();
      I2C_DELAY();
      I2C_DELAY();
    }

    2.4 字节格式

    SDA数据线上的每个字节必须是8位,每次传输的字节数量没有限制。每个字节后必须跟一个响应位(ACK)。首先传输的数据是最高位(MSB),SDA上的数据必须在SCL高电平周期时保持稳定,数据的高低电平翻转变化发生在SCL低电平时期。

    每一个字节后面跟着一个ACK,有ACK就可以继续写或读。NACK,就停止

    ACK:主机释放总线,传输完字节最后1位后的SCL的高电处,从机拉低电平。

    NACK:主机释放总线,传输完字节最后1位后的SCL的高电处,从机无响应,总线为高电平。

    动画描述写入的过程

    代码实现

    uint8_t I2C_Send_byte(uint8_t data)
    {
      uint8_t k;
      //发送8bit数据
      for(k=0;k<8;k++){
        
        I2C_DELAY();
        if(data&0x80){
          SDA_SET();
        }
        else{
          SDA_CLR();
        }
        data=data<<1;
        I2C_DELAY();
        SCL_SET();
        I2C_DELAY();
        I2C_DELAY();
        SCL_CLR();
      }
      //延时读取ACK响应
      I2C_DELAY();
      SDA_SET();
      //置为输入线
      SDA_IN();
      I2C_DELAY();
      SCL_SET();   
      I2C_DELAY(); //这里出现了问题,延时变的无限大
      //读数据
      k=SDA_READ();
      if(k){ NACK响应
        return 0;
      }
      I2C_DELAY();
      SCL_CLR();
      I2C_DELAY();
      SDA_OUT();
      if(k){ NACK响应
        return 0;
      }
      return 1;
    }
    
    uint8_t I2C_Receive_byte(uint8_t flg)
    {
      uint8_t k,data;
      //接收8bit数据
      //置为输入线
      
      SDA_IN();
      data=0;
      for(k=0;k<8;k++){
        I2C_DELAY();
        SCL_SET();
        I2C_DELAY();
        //读数据
        data=data |SDA_READ();
        data=data<<1;
        I2C_DELAY();
        SCL_CLR();
        I2C_DELAY(); 
      }
      data=data>>1; //往回移动1次
      //返回ACK响应
      //置为输出线
      SDA_OUT();
      if(flg){
        SDA_SET(); //输出1-NACK
      }else{
        SDA_CLR();//输出0-ACK
      }
      I2C_DELAY();
      SCL_SET();
      I2C_DELAY();
      I2C_DELAY();
      SCL_CLR();
      I2C_DELAY();
      SDA_OUT();
      //返回读取的数据
      return (uint8_t)data;
    }

    2.5 从机地址和读写位

    开始信号—>地址—>读写位—>ACK—>数据—>ACK.............—>停止位

    这里只说7位地址,前7位为地址,最后一位为读写位,1表示读操作,0表示写操作

    主机发给从机数据,也就是写,没有数据转向时

    主机立即读从机数据,从第一个字节

    (Combined)综合数据格式

    3、计算IIC的频率

    通信频率由主机掌控,也就是代码中的延时函数决定的

    从上面,我们得知最高速为400Kbit/s。我们设计300Kbit/s。

    速率300Kbit/s,对应周期1/300ms=10/3us≈3us,4分频就是3/4us。

    我们使用的延时是,1/120MHZ*3*30 =3/4us

    也就是频率是300Kbit/s

    和SPI类似,时钟下降沿时,数据转换,时钟上升沿时,采样数据。也就是时钟高电平数据有效。

    /*120Mhz时钟时,当ulCount为1时,函数耗时3个时钟,延时=3*1/120us=1/40us*/
    /*
    SystemCoreClock=120000000
    
    us级延时,延时n微秒
    SysCtlDelay(n*(SystemCoreClock/3000000));
    
    ms级延时,延时n毫秒
    SysCtlDelay(n*(SystemCoreClock/3000));
    
    m级延时,延时n秒
    SysCtlDelay(n*(SystemCoreClock/3));
    */
    
    #if defined   (__CC_ARM) /*!< ARM Compiler */
    __asm void
    SysCtlDelay(unsigned long ulCount)
    {
        subs    r0, #1;
        bne     SysCtlDelay;
        bx      lr;
    }
    #elif defined ( __ICCARM__ ) /*!< IAR Compiler */
    void
    SysCtlDelay(unsigned long ulCount)
    {
        __asm("    subs    r0, #1\n"
           "    bne.n   SysCtlDelay\n"
           "    bx      lr");
    }
    
    #elif defined (__GNUC__) /*!< GNU Compiler */
    void __attribute__((naked))
    SysCtlDelay(unsigned long ulCount)
    {
        __asm("    subs    r0, #1\n"
           "    bne     SysCtlDelay\n"
           "    bx      lr");
    }
    
    #elif defined  (__TASKING__) /*!< TASKING Compiler */                           
    /*无*/
    #endif /* __CC_ARM */
    
    
    /*
     * @brief  SysCtlDelay
     * @param  ulCount 延时值,必须大于0
     * @retval None
     */
    void SysCtlDelay_IIC(unsigned long ulCount)
    {
    	SysCtlDelay(ulCount);
    }
    
    
    /定义时钟频率,300KHz
    #define I2C_DELAY()  SysCtlDelay_IIC(30)

    4、PCF8536

    4.1 Acknowledge

    这个地方能看到关于2.4节关于ACK和NACk的说明

    4.2 Addressing

    这里直接给出读地址和写地址,也就是最后一位的区别

    4.3 读写时序

    其实就是按照IIC协议的

    读指定器件的指定寄存器

    主机设置完寄存器地址之后,再去读写

    注意:读取多个字节,最后一个字节的回包应该是NACK

    主机立即从机第一个字节读取

    注意:读取多个字节,最后一个字节的回包应该是NACK

    开源地址:

    https://github.com/strongercjd/STM32F207VCT6

    点击查看本文所在的专辑,STM32F207教程

    资料下载:

    https://pan.baidu.com/s/1qn3ATCX6JQ-18CIj5C6vyQ   提取码:cwsx

    如果链接失效,请关注微信公众号,找到下载专区->博客附件,编号0012的下载资源,免费获得。

    12

    展开全文
  • IIC模拟通用驱动,适用于绝大多数IIC器件
  • iic_IIC_源码

    2021-10-01 10:40:38
    iic配置eeprom
  • IIC讲义 IIC

    2012-03-25 22:35:19
    IIC讲义 IIC
  • IIC_IIC_源码

    2021-09-30 17:28:09
    STM32中使用IIC通信的.C文件,可以直接使用。
  • IIC的基本介绍 IIC的简介 IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备。它是半双工通信方式。 IIC总线最主要的优点是其简单性和有效性...

    IIC的基本介绍

    IIC的简介

    IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备。它是半双工通信方式。

    • IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。
    • IIC总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

    IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。

    一般情况下,数据线SDA和时钟线SCL都是处于上拉电阻状态。因为:在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

    STM32的IIC接口

    目前绝大多数的MCU都附带IIC总线接口,STM32也不例外。但是在本文中,我们不使用STM32的硬件IIC来读取24C02,而是通过软件的方式来模拟。

    原因是因为:STM32的硬件IIC非常复杂,更重要的是它并不稳定,故不推荐使用。

     

    IIC协议

    IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。同时我们还要介绍其空闲状态、数据的有效性、数据传输。

    先来看一下IIC总线的时序图:

    这可能会比较复杂,可以先看一份简化了的时序图:

    空闲状态

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

    起始信号与停止信号

    • 起始信号:当时钟线SCL为高期间,数据线SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号;
    • 停止信号:当时钟线SCL为高期间,数据线SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

    应答信号

    发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 

    • 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
    • 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 

    对于反馈有效应答位ACK的要求是:接收器在第9个时钟脉冲之前的低电平期间将数据线SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放数据线SDA,以便主控接收器发送一个停止信号P。

    数据有效性

    IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定;只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 

    即:数据在时钟线SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。

    数据的传达

    在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。

    延时时间

     

    IIC总线的数据传送

    IIC总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(地址通过物理接地或者拉高),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。

    也就是说,主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。

    • 主设备往从设备中写数据。数据传输格式如下:

    淡蓝色部分表示数据由主机向从机传送,粉红色部分则表示数据由从机向主机传送。

    写用0来表示(高电平),读用1来表示(低电平)。

    • 主设备从从设备中读数据。数据传输格式如下:

    在从机产生响应时,主机从发送变成接收,从机从接收变成发送。之后,数据由从机发送,主机接收,每个应答由主机产生,时钟信号仍由主机产生。若主机要终止本次传输,则发送一个非应答信号,接着主机产生停止条件。

    •  主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:

    在多主的通信系统中,总线上有多个节点,它们都有自己的寻址地址,可以作为从节点被别的节点访问,同时它们都可以作为主节点向其它的节点发送控制字节和传送数据。但是如果有两个或两个以上的节点都向总线上发送启动信号并开始传送数据,这样就形成了冲突。要解决这种冲突,就要进行仲裁的判决,这就是I2C总线上的仲裁。

    I2C总线上的仲裁分两部分:SCL线的同步和SDA线的仲裁。

    这部分就暂时不介绍了,想要了解:可以参考链接浅谈I2C总线I2C总线协议图解

     

    IIC底层驱动程序分析

    现拟采用PB6、PB7来模拟IIC时序,其中:PB6为时钟线,PB7为数据线。

    首先进行一些必要的宏定义:

    //IO方向设置
    #define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
    #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
    
    //IO操作函数	 
    #define IIC_SCL    PBout(6) //SCL
    #define IIC_SDA    PBout(7) //SDA	 
    #define READ_SDA   PBin(7)  //输入SDA 
    
    //IIC所有操作函数
    void IIC_Init(void);                //初始化IIC的IO口				 
    void IIC_Start(void);				//发送IIC开始信号
    void IIC_Stop(void);	  			//发送IIC停止信号
    void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
    u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
    void IIC_Ack(void);					//IIC发送ACK信号
    void IIC_NAck(void);				//IIC不发送ACK信号

    由于IIC是半双工通信方式,因而数据线SDA可能会数据输入,也可能是数据输出,需要定义IIC_SDA来进行输出、READ_SDA来进行输入,与此同时就要对IO口进行模式配置:SDA_IN()和SDA_OUT()。

    而时钟线SCL一直是输出的,所以就没有数据线SDA麻烦了。

    //初始化IIC
    void IIC_Init(void)
    {					     
    	GPIO_InitTypeDef GPIO_InitStructure;
    	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOB时钟
    	   
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽输出
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); 	//PB6,PB7 输出高,空闲状态
    }
    //产生IIC起始信号
    void IIC_Start(void)
    {
    	SDA_OUT();     //sda线输出
    	IIC_SDA=1;	  	  
    	IIC_SCL=1;
    	delay_us(4);
     	IIC_SDA=0;    //START:when CLK is high,DATA change form high to low 
    	delay_us(4);
    	IIC_SCL=0;    //钳住I2C总线,准备发送或接收数据 
    }	  
    //产生IIC停止信号
    void IIC_Stop(void)
    {
    	SDA_OUT();    //sda线输出
    	IIC_SCL=0;
    	IIC_SDA=0;    //STOP:when CLK is high DATA change form low to high
     	delay_us(4);
    	IIC_SCL=1; 
    	IIC_SDA=1;    //发送I2C总线结束信号
    	delay_us(4);							   	
    }
    //发送数据后,等待应答信号到来
    //返回值:1,接收应答失败,IIC直接退出
    //        0,接收应答成功,什么都不做
    u8 IIC_Wait_Ack(void)
    {
    	u8 ucErrTime=0;
    	SDA_IN();      //SDA设置为输入  
    	IIC_SDA=1;delay_us(1);	   
    	IIC_SCL=1;delay_us(1);	 
    	while(READ_SDA)
    	{
    		ucErrTime++;
    		if(ucErrTime>250)
    		{
    			IIC_Stop();
    			return 1;
    		}
    	}
    	IIC_SCL=0;    //时钟输出0 	   
    	return 0;  
    } 
    //产生ACK应答
    void IIC_Ack(void)
    {
    	IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=0;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(2);
    	IIC_SCL=0;
    }
    //不产生ACK应答		    
    void IIC_NAck(void)
    {
    	IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=1;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(2);
    	IIC_SCL=0;
    }					 				     
    //IIC发送一个字节
    //返回从机有无应答
    //1,有应答
    //0,无应答			  
    void IIC_Send_Byte(u8 txd)
    {                        
        u8 t;   
    	SDA_OUT(); 	    
        IIC_SCL=0;            //拉低时钟开始数据传输
        for(t=0;t<8;t++)
        {              
            //IIC_SDA=(txd&0x80)>>7;
    		if((txd&0x80)>>7)
    			IIC_SDA=1;
    		else
    			IIC_SDA=0;
    		txd<<=1; 	  
    		delay_us(2);       //对TEA5767这三个延时都是必须的
    		IIC_SCL=1;
    		delay_us(2); 
    		IIC_SCL=0;	
    		delay_us(2);
        }	 
    } 	    
    //读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
    u8 IIC_Read_Byte(unsigned char ack)
    {
    	unsigned char i,receive=0;
    	SDA_IN();        //SDA设置为输入
        for(i=0;i<8;i++ )
    	{
            IIC_SCL=0; 
            delay_us(2);
    		IIC_SCL=1;
            receive<<=1;
            if(READ_SDA)receive++;   
    		delay_us(1); 
        }					 
        if (!ack)
            IIC_NAck();        //发送nACK
        else
            IIC_Ack();         //发送ACK   
        return receive;
    }

    这里是通过普通IO口(PB6、PB7)来模拟IIC时序的程序,其实本质上都是严格按照IIC的时序图进行的,认真读,仔细对比,应该是没有什么困难的。

    就提一下:IIC_Read_Byte()函数,这个函数的参数表示读取一个字节之后,需要给对方应答信号或非应答信号。

     

    普通IO口模拟IIC时序读取24C02

    24C02芯片介绍

    EEPROM (Electrically Erasable Programmable read only memory),带电可擦可编程只读存储器——一种掉电后数据不丢失的存储芯片。 

    24Cxx芯片是EEPROM芯片的一种,它是基于IIC总线的存储器件,遵循二线制协议,由于其具有接口方便,体积小,数据掉电不丢失等特点,在仪器仪表及工业自动化控制中得到大量的应用。24Cxx在电路的作用主要是在掉电的情况下保存数据。

    本文使用的是24C02芯片,总容量是2k个bit(256个字节)。这里芯片名称里的02代表着总容量。

    24C02芯片的引脚分布和具体的作用见下图:

     

    24C02芯片的引脚说明
    引脚名称说明
    A0-A2地址输入线
    SDA数据线
    SCL时钟线
    WP写保护
    GND、VCC提供电源

    下图是本文中24C02和STM32的引脚连接图:

    从图中可以看出:A0、A1、A2都为0。

    对于并联在一条IIC总线上的每个IC都有唯一的地址。那么看一下从器件地址,可以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:

    根据图中的内容:如果是写24C02的时候,从器件地址为10100000(0xA0);读24C02的时候,从器件地址为10100001(0xA1)。

    24C02芯片的时序图

    这部分的内容应结合上文:I2C总线的数据传送的内容一起理解。

    24C02字节写时序

    对24C02芯片进行写字节操作的时候,步骤如下:

    1. 开始位,后面紧跟从器件地址位(0xA0),等待应答,这是为了在IIC总线上确定24C02的从地址位置;
    2. 确定操作24C02的地址,等待应答,也就是将字节写入到24C02中256个字节中的位置;
    3. 确定需要写入24C02芯片的字节,等待应答,停止位。

    24C02字节读时序

    对24C02芯片进行读字节操作的时候,步骤如下:

    1. 开始位,后面紧跟从器件地址位(0xA0),等待应答,这是为了在IIC总线上确定24C02的从地址位置;
    2. 确定操作24C02的地址,等待应答,也就是从24C02中256个字节中读取字节的位置;
    3. 再次开始位,后面紧跟从器件地址位(0xA1),等待应答;
    4. 获取从24C02芯片中读取的字节,发出非应答信号,停止位。

    读取24C02芯片程序

    #define AT24C01		127
    #define AT24C02		255
    #define AT24C04		511
    #define AT24C08		1023
    #define AT24C16		2047
    #define AT24C32		4095
    #define AT24C64	    8191
    #define AT24C128	16383
    #define AT24C256	32767  
    //Mini STM32开发板使用的是24c02,所以定义EE_TYPE为AT24C02
    #define EE_TYPE AT24C02
    //初始化IIC接口
    void AT24CXX_Init(void)
    {
    	IIC_Init();
    }
    //在AT24CXX指定地址读出一个数据
    //ReadAddr:开始读数的地址  
    //返回值  :读到的数据
    u8 AT24CXX_ReadOneByte(u16 ReadAddr)
    {				  
    	u8 temp=0;		  	    																 
            IIC_Start();  
    	if(EE_TYPE>AT24C16)            //为了兼容24Cxx中其他的版本
    	{
    		IIC_Send_Byte(0XA0);	   //发送写命令
    		IIC_Wait_Ack();
    		IIC_Send_Byte(ReadAddr>>8);    //发送高地址
    		IIC_Wait_Ack();		 
    	}else      IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据 	 
    
    	IIC_Wait_Ack(); 
            IIC_Send_Byte(ReadAddr%256);   //发送低地址
    	IIC_Wait_Ack();	    
    	IIC_Start();  	 	   
    	IIC_Send_Byte(0XA1);           //进入接收模式			   
    	IIC_Wait_Ack();	 
            temp=IIC_Read_Byte(0);	    //读一个字节,非应答信号信号	   
            IIC_Stop();        //产生一个停止条件	    
    	return temp;
    }
    //在AT24CXX指定地址写入一个数据
    //WriteAddr  :写入数据的目的地址    
    //DataToWrite:要写入的数据
    void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
    {				   	  	    																 
            IIC_Start();  
    	if(EE_TYPE>AT24C16)
    	{
    		IIC_Send_Byte(0XA0);	    //发送写命令
    		IIC_Wait_Ack();
    		IIC_Send_Byte(WriteAddr>>8);    //发送高地址
     	}else
    	{
    		IIC_Send_Byte(0XA0+((WriteAddr/256)<<1));   //发送器件地址0XA0,写数据 
    	}	 
    	IIC_Wait_Ack();	   
            IIC_Send_Byte(WriteAddr%256);   //发送低地址
    	IIC_Wait_Ack(); 	 										  		   
    	IIC_Send_Byte(DataToWrite);     //发送字节							   
    	IIC_Wait_Ack();  		    	   
            IIC_Stop();    //产生一个停止条件 
    	delay_ms(10);	 
    }
    //在AT24CXX里面的指定地址开始写入长度为Len的数据
    //该函数用于写入16bit或者32bit的数据.
    //WriteAddr  :开始写入的地址  
    //DataToWrite:数据数组首地址
    //Len        :要写入数据的长度2,4
    void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
    {  	
    	u8 t;
    	for(t=0;t<Len;t++)
    	{
    		AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
    	}												    
    }
    
    //在AT24CXX里面的指定地址开始读出长度为Len的数据
    //该函数用于读出16bit或者32bit的数据.
    //ReadAddr   :开始读出的地址 
    //返回值     :数据
    //Len        :要读出数据的长度2,4
    u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
    {  	
    	u8 t;
    	u32 temp=0;
    	for(t=0;t<Len;t++)
    	{
    		temp<<=8;
    		temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 	 				   
    	}
    	return temp;												    
    }
    //检查AT24CXX是否正常
    //这里用了24XX的最后一个地址(255)来存储标志字.
    //如果用其他24C系列,这个地址要修改
    //返回1:检测失败
    //返回0:检测成功
    u8 AT24CXX_Check(void)
    {
    	u8 temp;
    	temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX			   
    	if(temp==0X55)return 0;		   
    	else//排除第一次初始化的情况
    	{
    		AT24CXX_WriteOneByte(255,0X55);
    	    temp=AT24CXX_ReadOneByte(255);	  
    		if(temp==0X55)return 0;
    	}
    	return 1;											  
    }
    
    //在AT24CXX里面的指定地址开始读出指定个数的数据
    //ReadAddr :开始读出的地址 对24c02为0~255
    //pBuffer  :数据数组首地址
    //NumToRead:要读出数据的个数
    void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
    {
    	while(NumToRead)
    	{
    		*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);	
    		NumToRead--;
    	}
    }  
    //在AT24CXX里面的指定地址开始写入指定个数的数据
    //WriteAddr :开始写入的地址 对24c02为0~255
    //pBuffer   :数据数组首地址
    //NumToWrite:要写入数据的个数
    void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
    {
    	while(NumToWrite--)
    	{
    		AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
    		WriteAddr++;
    		pBuffer++;
    	}
    }
    //要写入到24c02的字符串数组
    const u8 TEXT_Buffer[]={"WarShipSTM32 IIC TEST"};
    #define SIZE sizeof(TEXT_Buffer)	
    	
     int main(void)
     {	 
    	u8 key;
    	u16 i=0;
    	u8 datatemp[SIZE];
    	delay_init();	    	 //延时函数初始化	  
            NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
    	uart_init(115200);	 	//串口初始化为115200
    	LED_Init();		  		//初始化与LED连接的硬件接口
    	LCD_Init();			   	//初始化LCD 	
    	KEY_Init();				//按键初始化		 	 	
    	AT24CXX_Init();			//IIC初始化 
    
     	POINT_COLOR=RED;//设置字体为红色 
    	LCD_ShowString(30,50,200,16,16,"WarShip STM32");	
    	LCD_ShowString(30,70,200,16,16,"IIC TEST");	
    	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
    	LCD_ShowString(30,110,200,16,16,"2015/1/15");	
    	LCD_ShowString(30,130,200,16,16,"KEY1:Write  KEY0:Read");	//显示提示信息		
     	while(AT24CXX_Check())//检测不到24c02
    	{
    		LCD_ShowString(30,150,200,16,16,"24C02 Check Failed!");
    		delay_ms(500);
    		LCD_ShowString(30,150,200,16,16,"Please Check!      ");
    		delay_ms(500);
    		LED0=!LED0;//DS0闪烁
    	}
    	LCD_ShowString(30,150,200,16,16,"24C02 Ready!");    
     	POINT_COLOR=BLUE;//设置字体为蓝色	  
    	while(1)
    	{
    		key=KEY_Scan(0);
    		if(key==KEY1_PRES)//KEY_UP按下,写入24C02
    		{
    			LCD_Fill(0,170,239,319,WHITE);//清除半屏    
     			LCD_ShowString(30,170,200,16,16,"Start Write 24C02....");
    			AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
    			LCD_ShowString(30,170,200,16,16,"24C02 Write Finished!");//提示传送完成
    		}
    		if(key==KEY0_PRES)//KEY1按下,读取字符串并显示
    		{
     			LCD_ShowString(30,170,200,16,16,"Start Read 24C02.... ");
    			AT24CXX_Read(0,datatemp,SIZE);
    			LCD_ShowString(30,170,200,16,16,"The Data Readed Is:  ");//提示传送完成
    			LCD_ShowString(30,190,200,16,16,datatemp);//显示读到的字符串
    		}
    		i++;
    		delay_ms(10);
    		if(i==20)
    		{
    			LED0=!LED0;//提示系统正在运行	
    			i=0;
    		}		   
    	}
    }

     

    IIC总结

    1. 进行数据传送时,在SCL为高电平期间,SDA线上电平必须保持稳定,只有SCL为低时,才允许SDA线上电平改变状态。并且每个字节传送时都是高位在前;
    2. 对于应答信号,ACK=0时为有效应答位,说明从机已经成功接收到该字节,若为1则说明接受不成功;
    3. 如果从机需要延迟下一个数据字节开始传送的时间,可以通过把SCL电平拉低并保持来强制主机进入等待状态;
    4. 主机完成一次通信后还想继续占用总线在进行一次通信,而又不释放总线,就要利用重启动信号。它既作为前一次数据传输的结束,又作为后一次传输的开始;
    5. 总线冲突时,按“低电平优先”的仲裁原则,把总线判给在数据线上先发送低电平的主器件;
    6. 在特殊情况下,若需禁止所有发生在I2C总线上的通信,可采用封锁或关闭总线,具体操作为在总线上的任一器件将SCL锁定在低电平即可;
    7. SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的。

     

    展开全文
  • IIC实验_IIC_源码

    2021-09-29 17:56:13
    stm32上IIC通信的源代码,包括所有模块
  • IIC_IIC实验_源码

    2021-10-04 08:10:46
    基于stm32的IIC实验,很简单适合入门的新手
  • IIC_test_IIC实验.rar

    2021-04-02 23:07:32
    IIC_test_IIC实验代码
  • 基于stm32的软件iic实现,解决stm32硬件iic偶尔死机问题
  • STM32 IIC 硬件iic

    2019-05-07 16:05:05
    STM32F40XX硬件IIC,使用两个IIC模块。IIC1作为主查询方式发送和接受;IIC2作为从使用中断方式发送和接收。IIC1(主机)发送数据给从机,主机接收的数据是从机接收主机的数据,以此验证主机发送和接受的正确性,以及从...
  • IIC ip 验证通过
  • IIC原理超详细讲解---值得一看

    万次阅读 多人点赞 2020-04-11 16:30:10
    文章目录IIC 简介IIC的物理层IIC的高阻态IIC物理层总结:IIC的协议层IIC 总线时序图初始(空闲)状态开始信号:停止信号数据有效性应答信号IIC数据传送数据传送格式IIC发送数据IIC读数据:以AT24C02为例子软件...
  • software_stm32硬件IIC_stm32iic_软件iic.zip
  • IIC详解,包括原理、过程,最后一步步教你实现IIC

    万次阅读 多人点赞 2017-04-25 19:59:08
    IIC详解   1、I2C总线具有两根双向信号线,一根是数据线SDA,另一根是时钟线SCL    2、IIC总线上可以挂很多设备:多个主设备,多个从设备(外围 设备)。上图中主设备是两个单片机,剩下的都是从设备。  ...
  • stm32f103 IIC通讯-存储-模拟IIC,库函数编程
  • 用FPGA实现iic总线的模拟
  • RT-Thread 开启软件IIC

    万次阅读 2021-06-14 09:25:28
    使用RT-Thread Studio,点亮软件IIC
  • avr iic 程序avr iic 程序avr iic 程序avr iic 程序avr iic 程序avr iic 程序avr iic 程序avr iic 程序
  • stm32f103 iic 驱动oled
  • software_stm32硬件IIC_stm32iic_软件iic_源码.zip
  • STM8L--IIC通信--AT24C02 模拟IIC+库函数IIC
  • IIC总线协议IIC总线协议IIC总线协议

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 32,807
精华内容 13,122
关键字:

iic