嵌入式_基于GD32F10x硬件IIC固件库的分析

嵌入式_基于GD32硬件I2C固件库分析

项目中使用2组硬件I2C使用多主模式实现16个设备间的通信,从新梳理简单记录一下对I2C的理解和注意事项


文章目录

  • 嵌入式_基于GD32硬件I2C固件库分析
  • 前言
  • 一、I2C物理层
  • 二、I2C协议层
  • 三、什么是I2C的多主模式?
  • 四、软件模型
    • 一.主机发送
    • 二.从机接收
  • 总结


前言

这里对I2C的协议内容就不再详细分析,有需要可以自己百度。
注:本项目基于GD32F103CBT6硬件平台, 使用标准库GD32F10x_Firmware_Library_V1.0.0(提示:此库坑多、慎用!)


一、I2C物理层

硬件上主要串行SCL(时钟线)和串行SDA(数据线)两根线,连接到总线上的设备通过这两根线互相传递信息。SDA 和 SCL 都是双向线,通过一个电流源或者上拉电阻接到电源正极。
嵌入式_基于GD32F10x硬件IIC固件库的分析_第1张图片

当总线空闲时,两条线都是高电平。连接到总线的设备输出极必须带开漏或者开集,以提供线与功能。 I2C 总线上的数据在标准模式下可以达到 100 kbit/s,在快速模式下可以达到 400 kbit/s。由于 I2C 总线上可能会连接不同工艺的设备(CMOS, NMOS, 双极性器件), 逻辑‘0’和逻辑‘1’的电平并不是固定的,取决于 VDD 的实际电平。

特点:I2C的通信方式为半双工,只有一根SDA线,同一时间只可以单向通信,485也为半双工,SPI和UART通信为全双工。主机和从机的概念:
主机:主动发送命令的称为主机,负责整个系统的任务协调与分配
从机:一般是通过主机的指令被动接收或回复完成某些特定的任务,
主机和从机之间可以通过总线连接,进行数据通信。

基本概念总结如下:

术语 说明
发送器 发送数据到总线的设备
接收器 从总线接收数据的设备
主机 初始化数据传输,产生时钟信号和结束数据传输的设备
从机 由主机寻址的设备
多主 多个主机可以尝试在不破坏信息的前提下同时控制总线
同步 同步两个或更多设备之间的时钟信号的过程
仲裁 如果超过一个主机同时试图控制总线,只有一个主机被允许,且获胜主机的信息不被破坏

二、I2C协议层

软件协议我们以7位地址位为例,遵从以下数据协议:
嵌入式_基于GD32F10x硬件IIC固件库的分析_第2张图片
起始信号:SCL为高电平,SDA由高电平向低电平跳变,开始传送数据
停止信号:SCL为高电平,SDA由低电平向高电平跳变,结束传送数据
应答信号:设备接收到IIC数据之后,向发送数据的设备发出特定的低电平脉冲,表示收到数据。
这些信号中,起始信号是必需的,结束信号和应答信号不是必需的。
从机地址:从机挂在在总线上的唯一地址码,用于寻找和识别设备。
读写位:告诉从机接下来的操作是读还是写操作。

作主机时:主机产生起始信号,然后发送从机地址和读写位,'1’表示读,'0’表示写,然后等待从机发送响应,如果写的话就以此写入1byte,然后等待从机发送响应,当从机无应答时产生时主机产生停止位或主机主动产生停止位表示终止传输。如果读的话就发送数据地址或命令码
作从机时:接收到起始信号并匹配地址后,分析读写操作,做出ACK应答,根据读位接收数据做出回应。
软件IIC相对简单,可以移植性也非常强。感兴趣的朋友可以参考我的另一篇文章:https://blog.csdn.net/Yin_w/article/details/129454353?spm=1001.2014.3001.5501

三、什么是I2C的多主模式?

一般情况下使用IIC读取EEPROM,将可以独立产生起始和停止信号的一端称为主机,根据起始信号和地址信号产生应答的称为从机。这种情况我们称为单主机模式。
多主模式就是两个或多个可以自主产生起始和停止信号,主动读写的控制器连接在一起,例如多个MCU的I2C接口连接在一起,类似形成一个I2C局域网相互进行通信,但是同一时刻只有一个控制器作为主机(产生起始信号)的模式,称之为多主模式。

时钟同步与仲裁:两个主机可以同时在空闲总线上开始传送数据,因此必须通过一些机制来决定哪个主机获取总线的控制权,这一般是通过时钟同步和仲裁来完成的。单主机系统下不需要时钟同步和仲裁机制,关于同步与仲裁这里不做过多阐述。

通讯流程:
I2C从机检测到I2C总线上的START信号之后,就开始从总线上接收地址,之后会把从总线接收到的地址和自身的地址(通过软件编程)进行比较,当两个地址相同时, I2C从机将发送一个确认应答(ACK),并响应总线的后续命令:发送或接收所需数据。此外,如果软件开启了广播呼叫,则I2C从机始终对一个广播地址(0x00)发送确认应答。 I2C模块始终支持7位和10位的地址。I2C主机负责产生START信号和STOP信号来开始和结束一次传输,并且负责产生SCL时钟

四、软件模型

根据IIC通信特点,这里可以分为四组通信模型:
■ 主机发送方
■ 主机接收方
■ 从机发送方
■ 从机接收方

我们多主模式主要使用从机接收与主机发送模式,硬件IIC比较脆弱,收发过程需要判断的各种标记位非常多,因此进行的每一步都需要得到有效保证,这是进行下一步的重要前提。在进行标记位的判断时,可以采用超时复位的方式,虽然繁琐但这是提高整个过程健壮性的有效方式。

一.主机发送

主机发送是由一个发送起始信号和设备地址的设备作为主设备,接收起始信号的设备作为从设备,依照7位地址为例,根据官方用户手册和个人理解,一个较为完整的数据发送过程应当是这样当的:

0.首先,软件应该使能I2C外设时钟,以及配置I2C_CTL1中时钟相关寄存器来确保正确的I2C时序。使能和配置以后, I2C运行在默认的从机模式状态,等待START信号,随后等待I2C总线寻址。

1.作为发送方,首先应当判断BSY位状态,等待总线空闲为空闲状态时才具备发送数据的条件

2.关闭IIC相关中断位,以保证本次发送能流畅进行,

3.准备工作做完,软件将START位置1,在I2C总线上产生一个START信号。

4.发送START信号后, I2C硬件将I2C_STAT0的SBSEND位置1然后进入主机模式。现在软件应该读I2C_STAT0寄存器查看SBSEND位是否被置位,如果尝长时间未被置位,则应当复位IIC总线。

5.然后写一个7位地址位或10位地址的地址头到I2C_DATA寄存器来清除SBSEND位。当SBSEND位被清0时, I2C就开始发送地址或者地址头到I2C总线。如果发送的地址是7位地址,硬件在发送地址的时候会将ADDSEND位置1,如果发送的地址是10位地址的地址头,硬件在发送地址头的时候会将ADD10SEND位置1,

6.现在软件应该读I2C_STAT0寄存器查看ADDSEND位是否被置位,如果尝长时间未被置位,则应当复位IIC总线。

7.软件通过读I2C_STAT0寄存器然后读I2C_STAT1寄存器清除ADDSEND位。

8.现在软件应该查看ADDSEND位是否被复位,只有ADDSEND被复位后才能开始发送数据,如果尝长时间未被复位,则应当复位IIC总线

9…现在软件应该等待TBE FLAG位被置起,只有当TBE位被置位,才能写数据到数据寄存器,如果超时未被置位,则会复位IIC

10.TBE FLAG位被置起,发送第一个数据,I2C进入数据发送状态,因为移位寄存器和数据寄存器I2C_DATA都是空的,所以硬件将TBE位置1。此时软件可以写第一个字节数据到I2C_DATA寄存器,但是TBE位此时不会被清零,因为写入I2C_DATA寄存器的字节会被立即移入内部移位寄存器。当移位寄存器非空时, I2C就开始发送数据到总线。

11.在第一个字节的发送过程中,软件可以写第二个字节到I2C_DATA,此时TBE会被清零,因为I2C_DATA寄存器和移位寄存器都不为空,任意时刻TBE被置1,软件都可以向I2C_DATA寄存器写入一个字节(为保证软件鲁棒性,我们可以发送一个数据判断一次TBE位)只要还有数据待发送。

12.在倒数第二个字节发送过程中,软件写入最后一个字节数据到I2C_DATA来清除TBE标志位,此后就不用关心TBE位的状态。 TBE位会在倒数第二个字节发送完成后被置起,直到发送STOP信号时被清零。

13.最后一个字节发送结束后, I2C主机将BTC位置起,因为移位寄存器和I2C_DATA寄存器此时都为空。软件此时应该配置STOP来发送一个STOP信号,此后TBE和BTC状态位都将被清0。所以此时数据发送完成,发送一个停止位

14.发送完成会判断是否释放总线仲裁权,如果超时未释放则复位,这里使用了SMBus模式,直接判断是否发送STOP信号

代码中注释编号与上述步骤对应(代码范例仅供参考使用)

/**********************************************************************
 *1-函数名:IIC_Write
 *2-函数功能:IIC_Write
 *3-输入参数:IIC编号(IIC1,IIC2),要发送的数据结构体
 **********************************************************************/
 uint8_t IIC_Write(uint8_t vIpmiID, IPMI_MsgType* IPMIMsg)
 {
   uint8_t NumByteToWrite;
   uint8_t DestAddress;
   I2C_TypeDef * IPMI_I2C;
   ReturnType ret = E_OK;
   uint16_t TempCounter = 0;
   uint8_t TxBufferTemp[IPMI_SLAVE_TX_MAX_LENGH];
   uint8_t i = 0;
   uint32_t TempARRegister = 0;
   TypeState Status;
   

     /*Check IPMI ID*/
    if(vIpmiID == IPMI_A)
    {
     IPMI_I2C = I2C1;
    }
    else if(vIpmiID == IPMI_B)
    {
     IPMI_I2C = I2C2;
    }
    else
    {
     IPMI_I2C = NULL_PTR;
    }
    
    if((IPMI_I2C == NULL_PTR) && (IPMIMsg == NULL_PTR))
    {
      ret = IPMI_COM_PARAERR;
      return ret;
    }
    
    if(ret == E_OK)
    {
      NumByteToWrite = IPMIMsg->MsgLengh;
      DestAddress = IPMIMsg->MsgHead.MsgHeadStruct.ResponseAddr;
      if((NumByteToWrite != 0u) && (NumByteToWrite < IPMI_SLAVE_TX_MAX_LENGH))
      {
        
        /*fill data and check sum*/
        for(i = 0; i < IPMI_MSGHEAD_SIZE; i++)
        {
          TxBufferTemp[i] = IPMIMsg->MsgHead.MsgHeadArr[i];
        }
        for(i = IPMI_MSGHEAD_SIZE; i < IPMIMsg->MsgLengh - 1; i++)
        {
          TxBufferTemp[i] = IPMIMsg->MsgData[i - IPMI_MSGHEAD_SIZE];
        }
        TxBufferTemp[IPMIMsg->MsgLengh - 1] = IPMI_CheckSumFun(&TxBufferTemp[3], NumByteToWrite - 4u);
        TxBufferTemp[2] = IPMI_CheckSumFun(TxBufferTemp, 2U);       
        
        TempCounter = 0;
        
        /*1.通过判断BSY位,判断并等待总线空闲,************************************************************************************/
        do
        {
          TempCounter++;
          Status = I2C_GetBitState(IPMI_I2C, I2C_FLAG_I2CBSY);
        }while((SET == Status) && (TempCounter < IPMI_BUSYSTATECOUNTER_MAX));
        if(TempCounter == IPMI_BUSYSTATECOUNTER_MAX)
        {
          ret = IPMI_COM_BUSBUSY;
          return ret;
        }
        
        /*2.关闭IIC中断,************************************************************************************/
        I2C_INTConfig(IPMI_I2C, I2C_INT_EIE | I2C_INT_EE | I2C_INT_BIE, DISABLE);

       /*3.发送起始信号,************************************************************************************/
        I2C_StartOnBus_Enable(IPMI_I2C, ENABLE);
        
        /*4.该函数作用为:等待SBSEND bit位被置起,如果超时未被置起,则会复位IIC************************************************************************************/
        ret = IPMI_I2CWaitStandbyState(IPMI_I2C, I2C_FLAG_SBSEND, (uint8_t)SET, IPMI_BUSYSTATECOUNTER_MAX);//SET
        if(ret != E_OK)
        {
          TempARRegister = I2C_GetCurrentState(IPMI_I2C);
          Debug("4 :I2C status reg vallue is %8x \r\n", TempARRegister);
          // IPMI_Enable(vIpmiID, DISABLE);
          IPMI_Enable(vIpmiID, ENABLE);
          return ret;
        }
        
		/*5.发送七位地址位************************************************************************************/
        I2C_AddressingDevice_7bit(IPMI_I2C, DestAddress, I2C_DIRECTION_TRANSMITTER);
       
        
        /*6.该函数作用为:等待ADDSEND bit位被置起,如果超时未被置起,则会复位IIC************************************************************************************/
        ret = IPMI_I2CWaitStandbyState(IPMI_I2C, I2C_FLAG_ADDSEND, (uint8_t)SET, IPMI_BUSYSTATECOUNTER_MAX);
        if(ret != E_OK)
        {
          TempARRegister = I2C_GetCurrentState(IPMI_I2C);
          Debug("0 :I2C status reg vallue is %8x \r\n", TempARRegister);
          IPMI_Enable(vIpmiID, ENABLE);
          return ret;
        }

		/*7.清除ADDSEND bit位************************************************************************************/
        /*clear and Wait until I2C_FLAG_ADDSEND bit is RESET by reading I2C_STAT0 and I2C_STAT1*/
        TempARRegister = I2C_GetCurrentState(IPMI_I2C);
        
        /*8.该函数作用为:等待ADDSEND bit位被清除,如果超时未被清除,则会复位IIC************************************************************************************/
        ret = IPMI_I2CWaitStandbyState(IPMI_I2C, I2C_FLAG_ADDSEND, (uint8_t)RESET, IPMI_BUSYSTATECOUNTER_MAX);
        if(ret != E_OK)
        {
          IPMI_Enable(vIpmiID, ENABLE);
          return ret;
        }
        
        /*9.该函数作用为:等待TBE FLAG位被置起,如果超时未被置起,则会复位IIC************************************************************************************/
        /*Wait until TBE FLAG is set*/
        ret = IPMI_I2CWaitStandbyState(IPMI_I2C, I2C_FLAG_TBE, (uint8_t)SET, IPMI_BUSYSTATECOUNTER_MAX);
        if(ret != E_OK)
        {
          TempARRegister = I2C_GetCurrentState(IPMI_I2C);
          Debug("1 :I2C status reg vallue is %8x \r\n", TempARRegister);
          // IPMI_Enable(vIpmiID, DISABLE);
          IPMI_Enable(vIpmiID, ENABLE);
          return ret;
        }

		 /*10.TBE FLAG位被置起,发送第一个数据,也就是上述的第五项所述************************************************************************************/
        I2C_SendData(IPMI_I2C,TxBufferTemp[0]);
        
        /*11.该函数作用为:等待TBE FLAG位被置起,如果超时未被置起,则会复位IIC************************************************************************************/
        /*if check I2C_FLAG_TBE, programe may go error,Must check I2C_FLAG_BTC*/
        ret = IPMI_I2CWaitStandbyState(IPMI_I2C, I2C_FLAG_TBE, (uint8_t)SET, IPMI_BUSYSTATECOUNTER_MAX);
        if(ret != E_OK)
        {
          TempARRegister = I2C_GetCurrentState(IPMI_I2C);
          Debug("2 :I2C status reg vallue is %8x \r\n", TempARRegister);
          // IPMI_Enable(vIpmiID, DISABLE);
          IPMI_Enable(vIpmiID, ENABLE);
          return ret;
        }

		/*12.循环发送数据,并在发送之前判断TBE FLAG位状态。如果超时未被置起,则会复位IIC************************************************************************************/
        for(i = 1; i < NumByteToWrite; i++)
        {
            I2C_SendData(IPMI_I2C,TxBufferTemp[i]);
          
            ret = IPMI_I2CWaitStandbyState(IPMI_I2C, I2C_FLAG_TBE, (uint8_t)SET, IPMI_BUSYSTATECOUNTER_MAX);
            if(ret != E_OK)
            {
              TempARRegister = I2C_GetCurrentState(IPMI_I2C);
              Debug("3 :I2C status reg vallue is %8x \r\n", TempARRegister);
              // IPMI_Enable(vIpmiID, DISABLE);
              IPMI_I2CReset(vIpmiID);
              IPMI_Enable(vIpmiID, ENABLE);
              return ret;
            }

        }
        
        /*13.发送停止位************************************************************************************/
        I2C_StopOnBus_Enable(IPMI_I2C,ENABLE);
//        ret = IPMI_I2CWaitStandbyState(IPMI_I2C, I2C_FLAG_LOSTARB, (uint8_t)RESET, IPMI_BUSYSTATECOUNTER_MAX);
        TempCounter = 0;

/*14.发送完成会判断是否释放总线仲裁权,如果超时未释放则复位,这里使用了SMBus模式,直接判断是否发送STOP信号,************************************************************************************/
        while(((IPMI_I2C->CTLR1 & 0x0200) == 0x0200) && (TempCounter < 10000))
        {
          TempCounter++;
        }
        
        if(TempCounter == 10000)
        {
          IPMI_I2CReset(vIpmiID);
          ret = IPMI_COM_TIMEOUT;
        }
        
        // Debug("send successful, StrRegValue = %8x \r\n", ((IPMI_I2C->STR1 | (IPMI_I2C->STR2 << 16u)) & 0x00FFFFFF));
        // IPMI_Enable(vIpmiID, DISABLE);
        IPMI_Enable(vIpmiID, ENABLE);
      }
      else
      {
        /*NumByteToWrite == 0u*/
        ret = IPMI_COM_PARAERR;
      }
    }
   return ret;
 }

这里贴一份用GD32F10x 户手册中对IIC主机发送的解析图(以10位地址为例):
嵌入式_基于GD32F10x硬件IIC固件库的分析_第3张图片

二.从机接收

0.首先,软件应该使能I2C外设时钟,以及配置I2C_CTL1中时钟相关寄存器来确保正确的I2C时序。使能和配置以后, I2C运行在默认的从机模式状态,等待START信号以及地址。所以先判断IIC是否运行在从机模式下。

1.在接收到START起始信号和匹配的7位或10地址之后, I2C硬件将I2C状态寄存器0的ADDSEND位置1,此位应该通过软件轮询或者中断来检测,发现置起后,软件通过先读I2C_STAT0寄存器然后读I2C_STAT1寄存器来清除ADDSEND位。当ADDSEND位被清0时, I2C就开始接收来自I2C总线的数据。

2.当接收到第一个字节时, RBNE位被硬件置1,软件可以读取I2C_DATA寄存器的第一个字节,此时RBNE位也被清0。任何时候RBNE被置1,软件可以从I2C_DATA寄存器读取一个字节。

3.接收到最后一个字节后, RBNE被置1,软件可以读取最后的字节。当I2C检测到I2C总线上一个STOP信号, STPDET位被置1,软件通过先读I2C_STAT0寄存器再写I2C_CTL0寄存器来清除STPDET位

代码中注释编号与上述步骤对应(代码范例仅供参考使用):

void IPMI_EVIrqProcess(uint8_t vIpmiID)
 {
   I2C_TypeDef * IPMI_I2C;
   volatile uint32_t STR1Register = 0, STR2Register = 0, I2cStateRegVallue = 0;
   static uint8_t RxBuffer[3][IPMI_SLAVE_RX_MAX_LENGH];
   static uint8_t RxBufferArrNum[3] = {0,0,0};
   volatile uint32_t STRRegister;
  //  uint8_t i = 0;
   static uint8_t TestCounter = 0;
   
   
   /*Check IPMI ID*/
   if(vIpmiID == IPMI_A)
   {
     IPMI_I2C = I2C1;
   }
   else if(vIpmiID == IPMI_B)
   {
     IPMI_I2C = I2C2;
   }
   else
   {
     IPMI_I2C = NULL_PTR;
   }
   
   if(IPMI_I2C != NULL_PTR)
   {
     STR1Register = IPMI_I2C->STR1;
     STR2Register = IPMI_I2C->STR2;
     STRRegister = ((STR1Register | (STR2Register << 16u)) & 0x00FFFFFF);
//     A1 = I2C_GetBitState(I2C1, I2C_FLAG_RBNE);
//     A2 = I2C_GetBitState(I2C1, I2C_FLAG_STPSEND);

     /*0.判断当前I2C状态是否是从机模式*****************************************************************************************/
     if((STR2Register & 0x0001) != 0x0001)
     {
        /*Slave Mode*/
      //  if(I2C_GetBitState(IPMI_I2C, I2C_FLAG_ADDSEND))
	
		/*1.收到起始信号并且地址是否匹配*************************************************************************************/
      if((STR1Register & I2C_FLAG_ADDSEND) == 0x0002)
        {
          I2cStateRegVallue = I2C_GetCurrentState(IPMI_I2C); // clear ADDSEND bit by reading I2C_STR1 and I2C_STR2
          RxBufferArrNum[vIpmiID] = 0;
        }
		
		/*2.判断接收标记位是否被置位,如果被置位,则开始接收数据***************************************************************/
      else if((STR1Register & I2C_FLAG_RBNE) == 0x0040)
        {
          if(E_OK == IPMI_CheckMsgLengh(RxBufferArrNum[vIpmiID]))
          {
            RxBuffer[vIpmiID][RxBufferArrNum[vIpmiID]] = I2C_ReceiveData(IPMI_I2C); // if reception data register is not empty ,I2C2 will read a data from I2C_DTR   
            RxBufferArrNum[vIpmiID]++;
          }
          else
          {
            Debug("I2C Erq-Error, msg's Lengh is too long %d > %d \r\n", RxBufferArrNum[vIpmiID], sizeof(IPMI_MsgType));
            RxBuffer[vIpmiID][RxBufferArrNum[vIpmiID]] = I2C_ReceiveData(IPMI_I2C);
            /*Receive data lengh > sizeof(IPMI_MsgType),need to change MsgData[20] define;*/
          }
        }
		/*3.检测到停止信号,并清除STPSEND位***************************************************************************************/
      else if((STR1Register & I2C_FLAG_STPSEND) == 0x0010)
        {
          I2cStateRegVallue = I2C_GetCurrentState(IPMI_I2C);
          I2C_Enable(IPMI_I2C,ENABLE); // Clear the STPDET bit 
          IPMI_RxIndication(vIpmiID, RxBuffer[vIpmiID], RxBufferArrNum[vIpmiID]);
          RxBufferArrNum[vIpmiID] = 0;
          
        }
        else
        {
          RxBufferArrNum[vIpmiID] = 0;
          Debug("I2C Erq-Error, error Status I2C STR-REG is %8x \r\n" , STRRegister);
          IPMI_ClearInterruptFlag(IPMI_I2C);
          //IPMI_I2CReset(vIpmiID);
        }
     }
     else
     {
       /*Master Mode*/
       /*Exit Master Mode */
//       I2C_StopOnBus_Enable(IPMI_I2C, ENABLE);
//       IPMI_Enable(vIpmiID, ENABLE);
      Debug("I2C Tx-Erq-Error \r\n");
      IPMI_I2CReset(vIpmiID);
       
     }
   
   }
   
 }

这里贴一份用GD32F10x 户手册中对IIC主机发送的解析图(以10位地址为例):
嵌入式_基于GD32F10x硬件IIC固件库的分析_第4张图片
从机发送与主机接收原理与上述类似,都是依照用户手册的步骤进行的,这里不再讨论。


总结

其他收发过程都可以按照这个模式参考用户手册写代码,这里不再赘述。

你可能感兴趣的:(单片机嵌入式,stm32,嵌入式硬件)