[APM32F1] APM32F10x应用: gpio模拟iic与eeprom通信

[复制链接]
9047|73
 楼主| DKENNY 发表于 2023-8-10 09:29 | 显示全部楼层 |阅读模式
本帖最后由 DKENNY 于 2023-8-10 09:38 编辑

1. 背景      
        大多数嵌入式系统和单片机都提供了GPIO引脚,而I2C硬件接口可能并不总是可用或可用的GPIO引脚数量有限。通过使用GPIO模拟I2C,可以在没有硬件I2C接口的情况下实现I2C通信。使用软件IIC具有以下优点:
      (1) 适应性和灵活性:在某些应用中,可能需要在不同的GPIO引脚上实现I2C通信,以适应特定的硬件布局或连接要求。使用GPIO模拟I2C可以提供更大的灵活性。
     (2)自定义时序和速率:对于一些特殊需求的应用,可能需要自定义I2C通信的时序和速率。通过使用GPIO模拟I2C,可以通过软件控制时序和速率,以满足特定的需求。
     (3)教育和学习目的:使用GPIO模拟I2C可以帮助学生和初学者更好地理解和学习I2C通信协议。通过手动控制GPIO引脚的状态和时序,他们可以更深入地了解I2C的工作原理。
     (4)原型开发和快速迭代:在原型开发和快速迭代阶段,使用GPIO模拟I2C可以更快地验证和测试I2C设备的功能和交互。这样可以加快开发进度并节省成本。
       总之,使用GPIO模拟I2C的背景主要涉及硬件限制、适应性、自定义需求、教育学习和原型开发等方面。通过使用GPIO模拟I2C,可以克服硬件限制并提供更大的灵活性和控制能力。


2. IIC简介
        IIC是一种短距离通信协议,物理实现上,I2C总线由两根信号线(SDASCL)和一根地线组成,两根信号线为双向传输的。
            两根信号线,SCL时钟线、SDA数据线。由SCL为SDA提供时序,SDA串行发送/接收数据。
            SCL、SDA这两根信号线均为双向。
            两个系统使用IIC总线通信时共用同一根地线。
        如下图为IIC功能结构图。
            

iic功能结构图

iic功能结构图


    2.1 IIC协议概述        
      数据以帧的形式传输,每一帧中由 1 个字节(8 )组成。
      在 SCL 的上升沿阶段,SDA 需要保持稳定,SDA 在 SCL 为低期间作出改变。
      除了数据帧,I2C 总线还有起始信号,停止信号,应答信号。
         起始位:在 SCL 为稳定的高电平期间,SDA 的一个下降沿启动传输。
         停止位:在 SCL 为稳定的高电平期间,SDA 的一个上升沿停止传输。
         应答位:用于表示一个字节传输成功。总线发送器(无论主机还是从机),
      在发送 8 个位的数据后,SDA 将释放(由输出变为输入),在第九个时钟脉冲期间,接收器将 SDA 拉低,来应答接收到了数据。
         
           I2C通信读写过程      
          如下为主机写数据至从机图。
        

主机写数据至从机

主机写数据至从机
         
          如下为主机由从机读取数据图。
        

主机由从机读取数据

主机由从机读取数据

          备注:(IIC发送8位命令,最后一位代表读写位)
               (1) image005.png :此数据由主机传输到从机
               (2) S:起始信号
               (3) SLAVE ADDRESS:从机地址
               (4) image006.png :此数据由从机传输到主机
               (5) R/W :传输方向选择位
               (6) 1 为读取
               (7) 0 为写入
               (8) P:停止信号

         起始信号产生后,所有从机都将等待主机发送的从机地址信号,I2C 总线中,每个设备的地址都是唯一的,当地址信号与设备地址匹配后,从机将被选中,没被选中的从机将忽略以后的数据信号。

     主机方向为写数据时
     广播完地址后,接收到应答信号,主机向从机发传输数据,数据长度为一个字节,主机每次发完一个字节数据后,都需等待从机发送的应答信号,当传输的所有字节完成后,主机向从机发送一个停止信号(STOP),表示为传输完成。
     主机方向为读数据时
     广播完地址后,接收到应答信号,从机开始向主机传输数据,数据包的大小为 8位,从机每发送完一个字节数据,都要等待主机的应答信号,当主机想停止接收数据时,需要向从机返回一个非应答信号,则从机自动停止数据传输。

    2.2 IIC时序
      I2C在传输数据过程中共3条信号,分别是开始信号,结束信号和应答信号。
      2.2.1 IIC开始信号(START)
SATRT信号定义为:时钟线SCL保持高电平期间,数据线SDA电平拉低,标志着一次数据传输的开始。启动信号是一种电平跳转的时序信号,而不是一种电平信号。该信号由主设备发出,在建立该信号之前,IIC总线必须处于空闲状态。如下为START时序图。
        

start时序图

start时序图

       2.2.2 IIC停止信号(STOP)
       STOP信号定义为:时钟线SCL保持高电平期间,数据线SDA释放,返回高电平,标志着一次数据传输的结束。停止信号也是一种电平跳转的时序信号,而不是一种电平信号。该信号由主设备发出,建立该信号之后,IIC返回空闲状态。如下为STOP时序图。
         

stop时序图

stop时序图

      2.2.3 IIC应答信号(ACK)
       IIC总线上的所有数据都是以8位字节传送的,IIC通信时,发送器每发送一次数据,接收器都会反馈一个应答信号。
       应答信号为低电平时,规定为有效应答(ACK);应答信号为高电平时,规定为非有效应答(NACK)。
       反馈有效应答信号的要求:接收器在接收第9个脉冲前,将SDA线拉低,确保当SCL线为高电平时,SDA线输出稳定的低电平。
       如果接收器是主设备,则当其收到从设备发出的最后一个字节时,会发送一个NACK信号,以通知从设备停止数据传送,并释放SDA线,以便主设备发送一个停止信号。如下为ACK和NACK时序图。
         

ack时序图

ack时序图


3.  AT24C02模块
       AT24C02是一款串行EEPROM(Electrically Erasable Programmable Read-Only Memory)芯片,由Atmel(现在是Microchip Technology)公司生产。它是AT24C系列芯片中的一员,具有2K位(256字节)的存储容量。
      如下是AT24C02的性能特点。
     (1)采用I2C(Inter-Integrated Circuit)总线接口,这是一种常用的串行通信协议,可以在多个设备之间进行数据传输。它支持标准模式(100 KHz)和快速模式 (400 KHz)的I2C通信速率。
     (2)具有可擦写和可编程的功能,可以通过电源供电进行数据的读取和写入。它采用8位地址寻址,可以连接多个AT24C02芯片,使得系统能够扩展更大的存储容量。
     (3)数据存储是非易失性的,即使在断电情况下,数据仍然可以保持。它还具有内部写保护功能,可以通过硬件或软件方式来保护存储的数据,防止误写或擦除。
     (4)广泛应用于各种电子设备中,例如存储配置信息、校准数据、序列号、日志记录等。由于其容量适中、接口简单、低功耗等特点,它在嵌入式系统和小型电子设备中得到了广泛应用。总体而言,AT24C02芯片具有较小的存储容量、简单的接口、良好的可靠性和低功耗等特点,适用于许多嵌入式系统和小型电子设备中的数据存储需求。
       如下为AT24C02的通信时序图
         

at24c02模块通信时序图

at24c02模块通信时序图


4.硬件设计
        本次设计使用APM32F103PB6PB7引脚进行GPIO模拟IIC,将AT24C02SCLSDA分别连接在APM32F103PB6PB7上,连接关系如图所示。
        

iic接线连接图

iic接线连接图


5.软件设计
         本章主要介绍GPIO模拟IIC所实现的代码,以及APM32F103使用软件IICAT24C02通信。其工作流程图如下所示。
         

at24c02通信流程图

at24c02通信流程图

     5.1      SDA_IN_OUT
  1. //SDA设置为输入模式
  2. void SDA_IN(void)
  3. {
  4.     GPIOB->CFGLOW &= 0X0FFFFFFF;
  5.     GPIOB->CFGLOW |= (uint32_t)8<<28;
  6. }
  7. //SDA设置为输出模式
  8. void SDA_OUT(void)
  9. {
  10.     GPIOB->CFGLOW &= 0X0FFFFFFF;
  11.     GPIOB->CFGLOW |= (uint32_t)3<<28;
  12. }
       参照APM32F103用户手册关于CFGLOW寄存器的说明,SDA_IN()函数将GPIOB中的CFGLOW寄存器的高4位配置成了“1000”,对应设置PB7引脚为输入模式;SDA_OUT()函数将GPIOB中的CFGLOW寄存器高4位配置成了“0011”,对应PB7引脚为推挽输出模式。

      5.2     IIC_Init
  1. //PB6(SCL)、PB7(SDA)初始化
  2. void IIC_Init(void)
  3. {                                             
  4.     GPIO_Config_T gpioConfig;
  5.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOB);        
  6.     gpioConfig.pin = IIC_PIN_SCL|IIC_PIN_SDA;
  7.     gpioConfig.mode = GPIO_MODE_OUT_PP ;        //推挽输出
  8.     gpioConfig.speed = GPIO_SPEED_50MHz;
  9.     GPIO_Config(GPIOB, &gpioConfig);
  10.     GPIO_SetBit(GPIOB,IIC_PIN_SCL|IIC_PIN_SDA);            
  11. }
        IIC_Init()函数配置了GPIOB的时钟,将PB6(SCL)、PB7(SDA)配置成了推挽输出模式,由于IIC时序开始时为高电平,故将PB6、PB7默认置为高电平。

      5.3      IIC_Start
  1. //IIC 开始时序
  2. void IIC_Start(void)
  3. {
  4.     SDA_OUT();              //SDA输出
  5.     IIC_SDA_SET;                    
  6.     IIC_SCL_SET;
  7.     Delay_us(4);
  8.     IIC_SDA_RESET;                 //START:当CLK为高电平时,DATA从高电平变为低电平
  9.     Delay_us(4);
  10.     IIC_SCL_RESET;          //钳住I2C总线,准备发送或接收数据
  11. }
         根据IIC开始时序(START)图,当SCL线为稳定的高电平时,将SDA线拉低代表IIC时序的开始,故先将IIC_SDA_RESET,延时一段时间后,再将IIC_SCL_RESET,即可模拟IIC的START时序。

       5.4     IIC_Stop
  1. //IIC 停止时序
  2. void IIC_Stop(void)
  3. {
  4.     SDA_OUT();              //SDA线输出
  5.     IIC_SCL_RESET;
  6.     IIC_SDA_RESET;          //STOP:当CLK为高电平时,DATA从低电平变为高电平
  7.     Delay_us(4);
  8.     IIC_SCL_SET;
  9.     IIC_SDA_SET;            //发送I2C总线结束信号
  10.     Delay_us(4);                                                                  
  11. }
          根据IIC停止时序(STOP)图,当SCL线为稳定的高电平时,将SDA线拉高代表IIC时序的停止,故先将IIC_SCL_SET,再将 IIC_SDA_SET,即可模拟IIC的STOP时序。

        5.5      IIC_Wait_Ack
  1. // 等待ACK应答
  2. uint8_t IIC_Wait_Ack(void)
  3. {
  4.     uint8_t ucErrTime = 0;
  5.     SDA_IN();               //SDA设置为输入  
  6.     IIC_SDA_SET;
  7.     Delay_us(1);           
  8.     IIC_SCL_SET;
  9.     Delay_us(1);         
  10.     while(IIC_READ_SDA)
  11.     {
  12.         ucErrTime++;
  13.         if(ucErrTime > 250)
  14.         {
  15.             IIC_Stop();
  16.             return 1;
  17.         }
  18.     }
  19.     IIC_SCL_RESET;          //时钟输出0            
  20.     return 0;  
  21. }
        IIC_Wait_Ack()函数中,将SDA设置为输入模式,以读取SDA电平。将SCL和SDA拉高,若SDA线识别到低电平,则代表收到了ACK,返回0;若250次之后未收到ACK则返回1,同时中断此次IIC传输。
       在IIC_Wait_Ack()函数中,成功接收到ACK后,会执行IIC_SCL_RESET这一段代码,其会将SCL拉低一段时间,这个时间被称为“时钟拉低期”。拉低SCL的作用为:
             1. 稳定数据:拉低 SCK 线可以确保数据线(SDA)上的数据稳定,避免在时钟转换期间发生数据的变化。
             2. 适应从设备速度:从设备可能由于处理数据的速度较慢而需要更多时间来处理接收到的数据。通过拉低 SCK 线,主设备可以等待从设备完成数据处理,使得数据传输的速度适应 从设备的处理速度。
             3. 时钟同步:时钟拉低期可以确保主设备和从设备之间的时钟同步,使得数据传输的时序正确。   
       总的来说,将 SCK 线拉低一段时间是为了稳定数据传输、适应从设备的速度并确保时钟同步。这样可以保证在 I2C 通信中数据的准确传输和正确处理。

      5.6     IIC_Ack
  1. //GPIO模拟产生ACK时序
  2. void IIC_Ack(void)
  3. {
  4.     IIC_SCL_RESET;
  5.     SDA_OUT();
  6.     IIC_SDA_RESET;
  7.     Delay_us(2);
  8.     IIC_SCL_SET;
  9.     Delay_us(2);
  10.     IIC_SCL_RESET;
  11. }
        根据IIC应答时序(ACK)图,当SCL发生脉冲变化时,SDA一直处于低电平,确保当SCL为稳定的高电平时,SDA一直为低电平。故先将IIC_SDA_RESET,而后SCL产生一次脉冲,SCL由“0”跳变为“1”,再由“1”跳变为“0”,模拟IIC时序的ACK信号。

5.7      IIC_NAck
  1. //GPIO模拟产生NACK时序
  2. void IIC_NAck(void)
  3. {
  4.     IIC_SCL_RESET;
  5.     SDA_OUT();
  6.     IIC_SDA_SET;
  7.     Delay_us(2);
  8.     IIC_SCL_SET;
  9.     Delay_us(2);
  10.     IIC_SCL_RESET;
  11. }
        根据IIC非应答时序(NACK)图,当SCL发生脉冲变化时,SDA一直处于高电平,确保当SCL为稳定的高电平时,SDA一直为高电平。故先将IIC_SDA_RESET,而后SCL产生一次脉冲,SCL由“0”跳变为“1”,再由“1”跳变为“0”,模拟IIC时序的NACK信号。

       5.8      IIC_Send_Byte
  1. //GPIO模拟IIC:发送一个字节
  2. void IIC_Send_Byte(uint8_t txd)
  3. {                        
  4.     uint8_t t;   
  5.     SDA_OUT();            
  6.     IIC_SCL_RESET;              //拉低时钟开始数据传输
  7.     for (t = 0; t < 8; t++)
  8.     {              
  9.         IIC_SDA_WRITE((txd&0x80)>>7);
  10.         txd <<= 1;           
  11.         Delay_us(2);   
  12.         IIC_SCL_SET;
  13.         Delay_us(2);
  14.         IIC_SCL_RESET;        
  15.         Delay_us(2);
  16.     }         
  17. }
       由于IIC必须在SCL为低电平时,SDA线才能跳变,故现将IIC_SCL_RESET,而后调用IIC_SDA_WRITE()函数逐位写入对应被控设备的地址,写完一位数据后,将SCL拉高,确保此次传输数据的稳定性,而后IIC_SCL_RESET,准备下一次的数据传输。

      5.9      IIC_Read_Byte
  1. //GPIO模拟IIC:读取一个字节            
  2. uint8_t IIC_Read_Byte(unsigned char ack)
  3. {
  4.     unsigned char i,receive=0;
  5.     SDA_IN();                   //SDA设置为输入
  6.     for (i = 0; i < 8; i++ )
  7.     {
  8.         IIC_SCL_RESET;
  9.         Delay_us(2);
  10.         IIC_SCL_SET;
  11.         receive <<= 1;
  12.         if(IIC_READ_SDA)
  13.         {
  14.             receive++;  
  15.         }
  16.         Delay_us(1);
  17.     }                                         
  18.     if (!ack)
  19.     {
  20.         IIC_NAck();             //发送NACK
  21.     }  
  22.     else
  23.     {
  24.         IIC_Ack();              //发送ACK
  25.     }
  26.     return receive;
  27. }
      IIC_Read_Byte()函数中,SDA设置为输入模式,以便对IO口进行读取操作。随后给予SCL一个上升沿,以此触发数据的采样,函数中定义了一个“receive”变量,若IIC_READ_SDA为0,则代表读取到的数据为“0”,则recvice=0;相反,则receive=1。“receive”变量用来存储读取的数据。“ack”变量的作用是提示是否需要被控设备发送ACK应答信号,若“ack=1”,则代表控制设备的数据还未传输完,需要返回被控设备返回ACK应答;若“ack=0”,则代表控制设备的数据已经传输完,无需ACK应答,需要被控设备返回NACK应答。

5.10    AT24CXX_ReadOneByte
  1. uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr)
  2. {                                 
  3.     uint8_t temp=0;                                                                                                                                                               
  4.     IIC_Start();  
  5.     if(EE_TYPE>AT24C16)
  6.     {
  7.         IIC_Send_Byte(0XA0);           //发送写命令
  8.         IIC_Wait_Ack();
  9.         IIC_Send_Byte(ReadAddr>>8);//发送高地址
  10.         IIC_Wait_Ack();                 
  11.     }
  12.     else
  13.     {
  14.         IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据
  15.     }                 

  16.     IIC_Wait_Ack();
  17.     IIC_Send_Byte(ReadAddr%256);   //发送低地址
  18.     IIC_Wait_Ack();            
  19.     IIC_Start();                     
  20.     IIC_Send_Byte(0XA1);           //进入读模式                           
  21.     IIC_Wait_Ack();         
  22.     temp=IIC_Read_Byte(0);                  
  23.     IIC_Stop();                     //产生一个停止条件            
  24.     return temp;
  25. }
        AT24CXX_ReadOneByte(),读取一个字节,“ReadAddr”代表AT24C02模块要读取数据对应的地址。AT24C02在发送读命令之前,需要先发送写命令,设置要读取设备的地址或寄存器,而后发送读命令,才能对设备进行读操作。

5.11     AT24CXX_WriteOneByte
  1. void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite)
  2. {
  3.     IIC_Start();  
  4.     if(EE_TYPE>AT24C16)
  5.     {
  6.         IIC_Send_Byte(0XA0);            //发送写命令
  7.         IIC_Wait_Ack();
  8.         IIC_Send_Byte(WriteAddr>>8);//发送高地址
  9.     }
  10.     else
  11.     {
  12.         IIC_Send_Byte(0XA0+((WriteAddr/256)<<1));   //发送器件地址0XA0,写数据
  13.     }         
  14.     IIC_Wait_Ack();           
  15.     IIC_Send_Byte(WriteAddr%256);   //发送低地址
  16.     IIC_Wait_Ack();                                                                                                               
  17.     IIC_Send_Byte(DataToWrite);     //发送字节                                                           
  18.     IIC_Wait_Ack();                                 
  19.     IIC_Stop();//产生一个停止条件
  20.     Delay_ms(10);         
  21. }
        AT24CXX_WriteOneByte ()函数中,“WriteAddr”代表要写入数据的地址,“DataToWrite”代表要写入的数据。对于AT24C02模块的写操作没有读操作那么复杂,在发送写命令后,再发送要写入设备的地址和数据,即可完成模块的写入操作。


6. 实验现象
   在代码编译成功后,我们通过下载代码到APM32F103开发板上,将AT24C02模块的SCLSDA分别与APM32F103PB6PB7连接,将PA9PA10作为串口线输出,通过按键KEY1写入数据,然后按键KEY2读取数据,现象如图所示。
      

串口打印读取的数据

串口打印读取的数据


7. 代码及相关资料附件
AT24C02.pdf (484.85 KB, 下载次数: 10)

I2C_Software _AT24C02_Demo.zip (277.2 KB, 下载次数: 49)

欢迎大家下载学习,如有问题,也请在评论区指出,谢谢!













Addition 发表于 2023-8-15 17:16 | 显示全部楼层
谢谢老板
forgot 发表于 2023-8-30 18:45 来自手机 | 显示全部楼层
zerorobert 发表于 2023-9-5 09:58 | 显示全部楼层
在IIC通信中,需要对收到的数据进行校验,以保证数据的正确性。而在Eeprom通信中,由于Eeprom的读取时间较长,因此需要对收到的数据进行校验,以保证数据的正确性和可靠性。
qiufengsd 发表于 2023-9-5 10:21 | 显示全部楼层
IC通信需要严格控制时序,包括起始信号、结束信号、数据位的发送时序等。在使用GPIO模拟IIC通信时,需要仔细设计时序逻辑,保证时序的准确性。
backlugin 发表于 2023-9-5 10:35 | 显示全部楼层
根据I2C总线的特性,SCL和SDA需要使用开漏引脚,因此GPIO的选用要满足这一要求。
iyoum 发表于 2023-9-5 10:46 | 显示全部楼层
有些EEPROM芯片可能需要特定的电平电压来正确工作。
mollylawrence 发表于 2023-9-5 11:12 | 显示全部楼层
标准速度SCL通常为100KHz,高速SCL可达400KHz,根据I2C设备时钟特性,调试时需合理设置delay time。
10299823 发表于 2023-9-5 11:24 | 显示全部楼层
由于IIC和Eeprom的通信方式不同,需要选择正确的GPIO引脚进行通信。一般来说,IIC通信需要使用SDA和SCL两个GPIO引脚,而Eeprom通信需要使用VCC和GND两个GPIO引脚。
hilahope 发表于 2023-9-5 11:36 | 显示全部楼层
在GPIO模拟IIC与Eeprom通信时,需要根据具体的通信协议和数据格式进行通信,并进行合理的延时和数据校验,以保证通信的稳定性和可靠性。
sdCAD 发表于 2023-9-5 11:50 | 显示全部楼层
GPIO模拟I2C通信的时钟速率应该与EEPROM的规格相匹配。通常,I2C可以以不同的速率进行通信,例如标准模式(100 KHz)或快速模式(400 KHz)。确保时钟频率设置正确。
geraldbetty 发表于 2023-9-5 11:59 | 显示全部楼层
在IIC通信中,需要按照一定的数据格式进行通信。一般来说,IIC通信采用8位数据格式,而在Eeprom通信中,需要按照6位或8位数据格式进行通信。
burgessmaggie 发表于 2023-9-5 12:08 | 显示全部楼层
在通信开始前,发送起始条件,即SCL保持高电平的同时,SDA由高变低,然后再发出停止条件,即SCL保持高电平的同时,SDA由低变高。
linfelix 发表于 2023-9-5 12:17 | 显示全部楼层
I2C总线空闲时,信号线需处于高电平状态,总线无驱动时由电阻上拉到电源。MCU或其他SoC芯片可以使能内部上拉或使用外部电阻上拉。
vivilyly 发表于 2023-9-5 12:30 | 显示全部楼层
I2C协议是一种基于时序的通信协议,因此需要确保在通信过程中SCL时钟和SDA数据线的时序控制正确。
pl202 发表于 2023-9-5 12:42 | 显示全部楼层
使用GPIO模拟I2C总线需要通过软件模拟实现协议栈,因此需要编写相应的软件程序来模拟SCL时钟和SDA数据线的操作。需要仔细编写程序来确保正确性和可靠性。
tabmone 发表于 2023-9-5 12:54 | 显示全部楼层
在EEPROM通信过程中,可能会涉及到中断处理,需要注意中断的正确使用和处理。
sdCAD 发表于 2023-9-5 13:02 | 显示全部楼层
IIC和Eeprom的通信协议不同,需要根据实际情况选择正确的通信协议。一般来说,IIC通信采用主从设备通信方式,而Eeprom通信采用主设备通信方式。
burgessmaggie 发表于 2023-9-5 13:17 | 显示全部楼层
主机发送1字节数据后,在check ack之前要及时释放SDA。如果主机驱动SDA未及时释放,可能会产生冲突,导致SCL为高时检测ack出错,甚至会出现SCL为高时SDA的跳变产生错误Start/Stop标志。
xiaoyaodz 发表于 2023-9-5 13:28 | 显示全部楼层
GPIO模拟I2C总线通信时需要考虑的电气方面的问题包括:输出电平、电流、上拉电阻等。需要确保输出电平符合EEPROM和GPIO的电气特性,并保证通信质量。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

108

帖子

17

粉丝
快速回复 在线客服 返回列表 返回顶部