IO口软件模拟IIC

news/2025/2/22 18:50:41/

 

一、IIC时序

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

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

I2C是同步串行总线,由SCL为所有设备提供统一的时钟信号。 即使多个节点发送时钟信号时,由于SCL"线与"的原因,SCL上为统一的时钟信号。

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

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

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

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

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

为了时钟信号的统一,我们假定只有一个设备发出时钟信号,该设备称为主机。其他IIC设备作为从机存在。

 

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

步骤1、IO管脚初始化

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

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

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

步骤2、发出起始信号    

采用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;    //钳住,等待发送或接受

}

步骤3、发出终止信号

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;

}

 

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

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

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

2、主器件接收从机数据

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

3、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操作为例说明

1、字节写操作

 写操作要求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写数据   

}

2、片写操作

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

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

 接口函数待定

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

{

 

}

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

//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

     }

}

4、当前地址读

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

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

5、随机读

     随机读需要先写一个目标字地址,一旦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;   

}

 

6、顺序读

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

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

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

{

         while(NumToRead--)

        {

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

        }

}

7、设备检查是否存在,首次在末尾字节写入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;

}

 


http://www.ppmy.cn/news/832511.html

相关文章

模拟IIC_读写BQ40Z50模块

读写BQ40Z50模块&#xff0c;我使用的是32单片机 该模拟IIC仅适用于BQ40Z50模块&#xff0c;因为每个模块延时有差异 1.初始化GPIO&#xff0c;根据你所使用的单片机完成初始化 __HAL_RCC_GPIOH_CLK_ENABLE();//初始化时钟/*IIC3 Configure GPIO pins : PIPin PIPin */GPIO_In…

8051单片机Proteus仿真与开发实例-PCF8574扩展接口驱动LCD1602显示屏仿真

PCF8574扩展接口驱动LCD1602显示屏仿真 1、PCF8574介绍 PCF8574已经在前面的文章中做了详细的描述,请参考: PCF8574扩展接口控制LED仿真2、LCD1602介绍 在前面的实例中,已经对LCD1602显示屏做过详细的介绍在这里就不再描述了,请参考: 8051单片机Proteus仿真与开发实例-…

MCS51单片机的输入/输出接口

I/O 口基本特性 51 系列单片机有 4 个 8 位并行 I/O 接口&#xff0c;并行就是所有各位数据同时并排传输的方式&#xff0c;每一个接口都有数据输出锁存器、输入缓冲器和输出驱动器。锁存器作为特殊的寄存器属于端口&#xff0c;具有端口地址。每一个接口只有一个端口&#xf…

51单片机——模拟I2C总线与AT24C02通信

目录 一、写在前面 二、功能描述 三、主要模块介绍 3.1 I2C总线介绍 3.2 I2C总线协议 3.2.1数据有效规定 3.2.2起始信号和停止信号 3.2.3 发送应答和接收应答 3.2.4 主机发送一个字节和接收一个字节 3.3 AT24C02介绍 3.3 字节写和随机读 四、测试文件test.c 五、现…

MAIX BIT K210与单片机通过串口通信

问题&#xff1a;在使用K210时使用官方介绍的串口通信&#xff0c;发送的数据为八位的数据&#xff0c;但是在使用中需要十六位的&#xff0c;因为所需数据可能涉及到百位。 解决方法&#xff1a;将数据打包后发送。 一下为打包函数&#xff1a; def sending_data(cx,cy,ch)…

使用51单片机模拟IIC从机,实现主机(51单片机)对模拟从机的读写操作

51单片机、IIC从机模拟、IIC协议、iiC读写 1.思路1.1写数据1.2读数据 2.从机IIC协议2.1起始信号和结束信号2.2从机读取和写数据2.3从机发送和接收ACK2.4判断主机发送读操作还是写操作2.5寄存器地址2.6主函数 3.主机IIC协议3.1起始信号和结束信号3.2主机写数据和读数据3.3主机发…

分享一款基于51单片机和MCP23017的IO扩展方案

大家好&#xff0c;我是『芯知识学堂』的SingleYork&#xff0c;今天笔者要给大家分享一款单片机IO扩展的设计方案。 学单片机的小伙伴们都知道&#xff0c;单片机的IO口数量都是固定的&#xff0c;在做项目的时候&#xff0c;需要根据自己实际用到的IO数量&#xff0c;再综合…

基于STM32单片机与wifi模块串口结合进行PC端或手机端无线通信(附项目资料包)

项目所需材料&#xff1a; stm32f103系列最小系统开发板一块。WiFi模块。具有WiFi功能的手机及PC机。达普电池一包&#xff08;或者学生电源一台&#xff09;。.1k电阻4个&#xff0c;杜邦线若干。 一.硬件环境搭建 1.1-----主控芯片采用的是stm32f103c8t6芯片。 1.2----- w…