IO口软件模拟IIC

 

1、IIC时序

IIC(Inter-Integrated Circuit, 内部集成电路)总线是飞利浦公司开发的两线式串行总线,用于短距离传输,经常使用语微控制器及其外围设备。它是由数据线SDA和时钟线SCL构成的串行总线,可发送和接收数据。数组

I2C总线经过上拉电阻接正电源。即当总线空闲时,两根线均为高电平。如此,连在总线上的任一器件输出的低电平,均可以使得总线的信号变低,也就是说各器件的SDA和SCL都是线"与"关系。 并发

I2C是同步串行总线,由SCL为全部设备提供统一的时钟信号。 即便多个节点发送时钟信号时,因为SCL"线与"的缘由,SCL上为统一的时钟信号。函数

数据位(1\0)有效性规定:I2C总线进行数据传送时,时钟信号为高电平期间,SDA线上的数据必须保持稳定;只有在SCL线的信号为低电平器件,SDA线的才可进行高低电平状态变化。ui

 起始信号、终止信号、应答信号spa

起始信号: SCL线为高电平期间,SDA线由高电平向低电平跳变(降低沿)----是一种电平跳变时序信号blog

终止信号:SCL线为高电平期间,SDA线由低电平向高电平跳变(上升沿)-----是一种电平跳变的时序信号接口

应答信号:在接收数据的IC(接收器)在接收到8bit数据后,向发送数据的IC(发送器)发出特定的低电平脉冲,表示已收到数据。即发送器在时钟脉冲9期间释放数据线,这样接收器就能够反馈一个应答信号。ACK(低电平)---规定为有效应答位,NACK(高电平),规定为非应答位,表示接收器接收该字节咩有成功。开发

为了时钟信号的统一,咱们假定只有一个设备发出时钟信号,该设备称为主机。其余IIC设备做为从机存在。同步

 

2、从STM32出发 IO软件模拟I2C的驱动程序

步骤一、IO管脚初始化

假设使用的 IIC_SCL  IIC_SDA 输出  以及READ_SDA输入it

#define SDA_IN()      宏定义设置管脚为输入模式

#define SDA_OUT()   宏定义设置管脚为输出模式

步骤二、发出起始信号    

采用4us 

void IIC_Starts(void)

{

      SDA_OUT() ;

       IIC_SCL  =  1;

        IIC_SDA = 1;   //空闲态

       dealy_us(4);

       IIC_SCL = 1;

       IIC_SDA = 0;   //当SCL线为高电平是, SDA线上由高到低电平跳变

       delay_us(4);

      IIC_SCL = 0;    //钳住,等待发送或接受

}

步骤三、发出终止信号

void IIC_Stop(void)

{

       SDA_OUT() ;    

        IIC_SCL  =  0;

        IIC_SDA  = 0;   //

       dealy_us(4);

       IIC_SCL = 1;

       IIC_SDA = 1;   //当SCL线为高电平是, SDA线上由低到高电平跳变

       delay_us(4);

}

步骤4 等待ACK应答

uint8_t IIC_Wait_ACK(void)

{

      uint8_t ucTimeCnt= 0;

     SDA_IN() ;       //SDA管脚设为输入模式

     IIC_SDA = 1;

     delay_us(1);

    IIC_CLK = 1;

    delay_us(1);

   while(READ_SDA)

   {

             ucTimeCnt ++;

             if(ucTimeCnt>250)

             {

                      IIC_Stop();

                     return 1;

             }

   }

  //等到应道地电平

 IIC_CLK = 0; //钳住,等待发送或接受      

}

步骤5 发送一字节数据

void  IIC_Send_Byte(uint8 byte)

{

     uint8_t i = 0;

     SDA_OUT() ; 

     IIC_SCL = 0;   // 只有在SCL线为低电平时,SDA线才能够改变

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

     {

           IIC_SDA = (byte&0x80)>>7;   //每次发最高位

          byte<<1;    //更新最高位

         delay_us(2);

         IIC_CLK = 1;

         delay_us(2);

        IIC_CLK = 0;

       delay_us(2);      //第三个2us是为何???

   } 

}

步骤6 读取一个字节,并发送ACK或NACK,发送NACK即通知从机发送器结束数据发送,释放SDA线(SDA接口置1)。

uint8IIC_Read_Byte(uint8_t  ack)

{

    uint8_t i, recvVal=0;

     SDA_IN();

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

   {

          IIC_CLK = 0;

          delay_us(2);  //等待输出

          IIC_CLK = 1;  //可读取

          recvVal<<1;  //将最低位空出

          if(IIC_SDA()) recvVal++;   //高电平,则最低位为1

         delay_us(1);        

   }

   if(ack)

  {

       IIC_ACK();

 }

else

{

      IIC_NACK();

}

  retrun recvVal;   

}

步骤7   发出应答ACk

void IIC_ACK(void)

{

       SDA_OUT() ;

       IIC_CLK = 0;

       IIC_SDA = 0;

      delay_us(2);

      IIC_CLK = 1;

     delay_us(2);

     IIC_CLK = 0;

}

步骤8 发出应答NACK

void IIC_NACK(void)

{

       SDA_OUT() ;

       IIC_CLK = 0;

       IIC_SDA = 1;

      delay_us(2);

      IIC_CLK = 1;

     delay_us(2);

     IIC_CLK = 0;

}

 

3、从具体I2C器件中读写数据

一、主器件先从器件写数据

上图所说的就是I2C向从机特定地址写数据。

二、主器件接收从机数据

注意 从写完地址后,要从新发起地址+接收。

三、DEVICEADDRESS

DECVICEADDR地址的8位地址信息组成以下所示,以24C02 EEPROM为例说明

对于24C02而言,最高位的4bit为1010(0xA), A2 A1 A0为管脚地址位,必须与硬件输入引脚一致,假设接地,LSB为读/写操做选择位,高为读操做,低位写操做。  0xA0---写  0xA1-----读

对于EEPROM而言,比较器件地址一致后,输出应答为"0"。若是不一致,则返回到待机状态。

章节2 完成了GPIO口模拟IIC的驱动,如今就是落实到实际应用。AT24C02 EEPROM和MPU6050,这两个器件都是做为IIC从机。

这里对从机和主机 以及发送器和接收器再次说明

主机: 发出时钟脉冲的IIC器件,这里是软件模拟的IIC 处于主动控制地位

从机: 目标的IIC器件 挂在IIC总线上的器件

发送器:在数据传输中处于发送地位

接收器:在数据传输中处于接收地址

若接收器是主机时,他在接收到最后一个自节后,发送一个NACK信号,以通知被控(从机)发送器结束数据发送,并释放SDA线,以便主控接收器发送一个终止信号P。

四 以AT24C02 EEPROM操做为例说明

一、字节写操做

 写操做要求AT24C02在接收器件地址后和ACK应答后,在接收8位的字地址,AT24C02接收到地址后应答"0",而后接收一个8位数据,在接收8位数据后,AT24C02应答"0”。最后必须由主器件发送终止信号P在终止写序列。  这以后AT24C02进入内部写周期twr,数据写入非易失性存储器中。在这期间全部输入都是无效的,直到写周期完成,AT24C02才有应答。

//WriteAddr :写入数据的AT24C02目的存储地址   DataToWrite 待写入的值

void   AT24CXX_WriteOneByte(u16 WriteAddr, u8 DataToWrite)

{

             IIC_Starts();   //发出起始信号

            IIC_Send_Byte(0xA0);   //写器件地址+写操做

            IIC_Wait_Ack();

           IIC_Send_Byte(WriteAddr>>8); //发送高地址

           IIC_Wait_Ack();

           IIC_Send_Byte(WriteAddr%256);    //发送低地址

          IIC_Wait_Ack(); 

          IIC_Send_Byte(DataToWrite); 

           IIC_Wait_Ack(); 

          IIC_Stop();

         delay_ms(10);// 等AT24C02写数据   

}

二、片写操做

即在发送一个8位数据后,在发7个数据(对于AT24C02而言)。每一个数据都要有ACK,最后再由主器件发出终止信号P。

AT24C02器件按照8字节/页执行页写;AT24C04/08/16器件按16字节/页执行页写。AT24C02在接收到每一个数据后,字地址的低3位内部自动加1,高位地址不变,维持在当前页内,注意当内部产生的自地址达到该页边界地址时,随后的数据将写入该页的页首。  如写入的字节数超过8个,字地址将回转到该页的首字节,先前的字节将会被覆盖

 接口函数待定

void  AT24CXX_WritePage(u16 PageAdd, u8 * pBuffer,u16 NumToWrite)

{

 

}

三、连续写入任意字节数目---不超过全地址

//WriteAddr:开始写入的地址 对于24C02 为0~255    pBuffer:数据数组首地址     NumToWrite:要写入数据的个数

void AT24CXX_Write(u16 WriteAddr, u8 *pBuffer, u16 NumToWrite)

{

      while(NumToWrite--)

     {

           AT24CXX_WriteOneByte(WriteAddr, *pBuffer);

           WriteAddr ++;   //地址加1

           pBuffer++;       //数组地址+1

     }

}

四、当前地址读

读操做和写操做初始化相同,只是器件地址中的读/写选择位应为"1"。

AT24C02内部地址计数器只要芯片有电,就会保存着上次访问时最后一个地址加1的值。此时直接读,可读到该地址的值。当读到最后页的最后字节,地址会回转到0。

五、随机读

     随机读须要先写一个目标字地址,一旦AT24C02 接收了器件地址和字地址 并应答了ACK,主器件就产生一个重复的起始信号。接着主器件发送器件地址|读操做,EEPROM应答ACK,并随时钟送出数据,主器件NACK,可是发送终止信号。

//ReadAddr   读目标地址

u8 AT24CXX_ReadOneByte(u16 ReadAddr)

{

        u8  tempVal = 0;

        IIC_Starts();   //发出起始信号

        IIC_WriteOneByte(0xA0);  //写器件地址+写操做

        IIC_Wait_Ack();    //等待应答

        IIC_WriteOneByte(ReadAddr>>8);//高字节地址

        IIC_Wait_Ack();

        IIC_WriteOneByte(ReadAddr%256); //低字节地址

        IIC_Wait_Ack();

        IIC_Starts();  //从新发出起始信息 

        IIC_WriteOneByte(0xA1);   //读操做

       IIC_Wait_Ack();

       tempVal = IIC_ReadOneByte(0);  //读取一个字节,发出NACK

       IIC_Stop();     //发出终止信号

       return tempVal;   

}

 

六、顺序读

      顺序读能够经过"当前地址读‘’或“随机读”启动,主器件接收到一个数据后,应答ACK。只要AT24C02接收到ACK后,将自动增长字地址并继续随时钟发送后面的数据。若达到存储器地址末尾,地址自动回转到0,仍可继续顺序读取数据。

主器件NACK+终止条件,便可结束顺序读操做。

 void AT24CXX_Read(u16 ReadAddr, u8 *pBuffer, u16 NumToRead)

{

         while(NumToRead--)

        {

              *pBuffer++ = AT24CXX_ReadOneByte(ReadAddr++);

        }

}

七、设备检查是否存在,首次在末尾字节写入0x55, 其余读末尾字节看是不是0x55

void AT24CXX_Check(void)

{

      u8 tmpVal;      

       tmpVal = AT24CXX_ReadOneByte(255); 

      if(tmpVal == 0x55) retrun 0;

      else{

               AT24CXX_WriteOneByte(0x55);              

              tmpVal = AT24CXX_ReadOneByte(255); 

              if(tmpVal == 0x55) retrun 0;     

             }

     return 1;

}