软件模拟IIC的全面笔记(已调通)

@[toc] lib_i2c_simulation

/*
 * @Author: Haiyichen
 * @Date: 2023-09-21 16:16:16
 * @LastEditors: Haiyichen
 * @LastEditTime: 2023-10-31 18:01:10
 * @Description: Personal notes of i2c-simulation
 */

i2c基础

通讯流程

协议

除了文字解释,有用wavedrom简单画了一些各种信号的电平变化过程,但需要支持MPE才能看到,虽然CSDN上的在线编辑器无法渲染出图,但还是放出来了,如果有条件可以在自己的VSCode中配置MPE(markdown preview enhenced)

  1. 开始:SCL为高电平时,SDA从高电平状态切换到低电平状态。
    {signal:[
     {       name:'SCL',   wave:'p.Pp'},
     {       name:'data',  wave:"x.=x", 
             data:["Start" ]},
     {       name:'SDA',   wave:"1.0"}
    ]}
    
    
  2. 停止:SCL为高电平时,SDA从低电平状态切换到高电平状态。
    {signal:[
     {       name:'SCL',   wave:'p.PPp'},
     {       name:'data',  wave:"x.=x", 
             data:["Stop" ]},
     {       name:'SDA',   wave:"0.10"}
    ]}
    
    
  3. 应答:发送侧发送完8bit数据后,接收侧需要回复一个信号,即第9个SCL时,接收侧将SDA拉低,称作ACK。
    {signal:[
     {       name:'SCL',   wave:'p.Pp..|Pp'},
     {       name:'data',  wave:"x.==..|=x", 
             data:["Start", "SlaveAddress","Ack"]},
     {       name:'SDA',   wave:"0.10.1|0."}
    ]}
    
    
  4. 无应答:发送侧发送完8bit数据后,接收侧需要回复一个信号,即第9个SCL时,接收侧将SDA拉高(或叫释放SDA),称作NACK。NACK时同时会引起Master发生RESTART或STOP流程。
    {signal:[
    {       name:'SCL',   wave:'p.Pp..|Pp'},
    {       name:'data',  wave:"x.==..|=x", 
            data:["Start", "SlaveAddress","Nack"]},
    {       name:'SDA',   wave:"0.10.0|1."}
    ]}
    
    
  5. 地址命令:i2c的地址是7bit,第8bit是方向位。1代表Read、0代表Write

写流程

  1. Master发起START
  2. Master发送Slave地址(7bit)和W(0:写动作),等待Slave发动应答ACK
  3. Slave发送应答ACK
  4. Master发送寄存器地址(8bit),等待Slave发送应答ACK
  5. Slave发送应答ACK
  6. Master发送写入寄存器的数据(8bit),等待Slave发送应答ACK
  7. Slave发送应答ACK
  8. 6~7可以循环多次,即按顺序写多个寄存器
  9. Master发起STOP

读流程

  1. Master发起START
  2. Master发送Slave地址(7bit)和W(0:写动作),等待Slave发送应答ACK
  3. Slave发送应答ACK
  4. Master发送寄存器地址(8bit),等待Slave发送应答ACK
  5. Slave发送应答ACK
  6. Master发起START
  7. Master发送Slave地址(7bit)和R(1:读动作),等待Slave发送应答ACK
  8. Slave发送应答ACK
  9. Slave发送寄存器里数据(8bit),等待Master发送ACK
  10. Master发送ACK或NACK(出现NACK,后面直接就是STOP流程)
  11. 9~10可以循环多次,即顺序读多个寄存器
  12. Master发起STOP

i2c模拟驱动函数

相关硬件配置

/* IO definition of i2c simulation */
#define I2C_SIMULATION_GPIO_PORT             GPIOA
#define I2C_SIMULATION_SCL_PIN               GPIO_PINS_0
#define I2C_SIMULATION_SDA_PIN               GPIO_PINS_1

/* driver definition of i2c simulation for M1 */
#define I2C_SDA_H()        I2C_SIMULATION_GPIO_PORT->scr = I2C_SIMULATION_SDA_PIN;
#define I2C_SDA_L()        I2C_SIMULATION_GPIO_PORT->clr = I2C_SIMULATION_SDA_PIN; 
#define I2C_SCL_H()        I2C_SIMULATION_GPIO_PORT->scr = I2C_SIMULATION_SCL_PIN;
#define I2C_SCL_L()        I2C_SIMULATION_GPIO_PORT->clr = I2C_SIMULATION_SCL_PIN;

关于模拟i2c的IO配置细节

关于SDA引脚是配置成开漏还是推挽,

  • 推挽输出对应->需要切换输入输出模式;
  • 开漏输出对应->不需要切换输入输出模式.
    参考链接已验证

i2c_Delay()

//需要根据主控MCU频率和i的取值,调整i2c_Delay的时长,进而调整SCL的脉宽。(也受编译器“优化等级”影响)
/**
  * @name   i2c_Delay
  * @brief  soft delay for i2c clock
  * @param  none
  * @retval none
  */
static void i2c_Delay(void)
{
    uint8_t i;

    /*
		*AT32F425F6P7,
		*i = 100,SCL = 163.4KHZ,6.1us
		*i = 75, SCL = 243.9KHZ,4.1us
		*i = 50, SCL = 312.5kHZ,3.2us
    */
   for(i=0;i<100;i++);
}

hw_i2c_START()

/**
  * @name   hw_i2c_START
  * @brief  start signal for i2c simulation
  * @param  none
  * @retval none
  */
 void hw_i2c_START(void)
 {
    I2C_SDA_H();
    I2C_SCL_H();
    i2c_Delay();
    I2C_SDA_L();
    i2c_Delay();
    I2C_SCL_L();
    i2c_Delay();
 }

hw_i2c_ACK()

/**
  * @name   hw_i2c_ACK
  * @brief  ACK signal for i2c simulation
  * @param  none
  * @retval none
  */
void hw_i2c_ACK(void)
{
    I2C_SDA_L();
    i2c_Delay();
    I2C_SCL_H();
    i2c_Delay();
    I2C_SCL_L();
    i2c_Delay();
    I2C_SDA_H();

}

hw_i2c_WaitAck()

/**
  * @name   hw_i2c_WaitAck
  * @brief  Wait ACK signal for i2c simulation
  * @param  none
  * @retval uint8_t tempRe:Get slave ack signal or not
  */
 uint8_t hw_i2c_WaitAck(void)
{
    uint8_t tempRe;

    I2C_SDA_H();                //MCU(master) set SDA High
    i2c_Delay();
    I2C_SCL_H();                //MCU(master) send a new SCL signal, slave device should return an Ack signal
    i2c_Delay();
    tempRe = I2C_SDA_READ();    //MCU(master) read SDA state(1 or 0)
    I2C_SCL_L();
    i2c_Delay();
    return tempRe;
}

hw_i2c_NACK()

/**
  * @name   hw_i2c_NACK
  * @brief  Send NACK signal to slave for i2c simulation
  * @param  none
  * @retval none
  */
void hw_i2c_NACK(void)
{
    I2C_SDA_L();
    I2C_SCL_H();
    i2c_Delay();
    I2C_SDA_H();
}

hw_i2c_STOP()

/**
  * @name   hw_i2c_STOP
  * @brief  Send STOP signal to slave for i2c simulation
  * @param  none
  * @retval none
  */
void hw_i2c_STOP(void)
{
    I2C_SDA_L();
    I2C_SCL_H();
    i2c_Delay();
    I2C_SDA_H();
    i2c_Delay();
}

lib_i2c_SendByte()

/**
  * @name   lib_i2c_SendByte
  * @brief  Send Byte from master by simulation of i2c
  * @param  DataByte:data
  * @retval none
  */
void lib_i2c_SendByte(uint8_t DataByte)
{
  uint8_t i;
  for ( i = 0; i < 8; i++)
  {
      if (DataByte & 0x80)
      {
          I2C_SDA_H();
      }
      else
      {
          I2C_SDA_L();
      }
      i2c_Delay();
      I2C_SCL_H();
      i2c_Delay();
      I2C_SCL_L();
      if (i == 7)
      {
          I2C_SDA_H();      //MCU(master) set SDA high
      }
      DataByte <<= 1;
      i2c_Delay();        
  }
}
   

lib_i2c_ReadByte()

/**
  * @name   lib_i2c_ReadByte
  * @brief  Read Byte from slave device by simulation of i2c
  * @param  none 
  * @retval tempData:data
  */
uint8_t lib_i2c_ReadByte(void)
{
    uint8_t i;
		uint8_t	tempData = 0;
		uint8_t	tempRe = 0;
		for(i = 0; i < 8; i++)
		{
			tempData <<=1;
			I2C_SCL_H();
			i2c_Delay();
			tempRe = I2C_SDA_READ();
			if(tempRe)
			{
				tempData++;
			}
			I2C_SCL_L();
			i2c_Delay();
		}
    return tempData;
}

lib_i2c_ReadMutiBytes()

/**
  * @name   lib_i2c_ReadMutiBytes
  * @brief  Read Muti Bytes data from slave device
  * @param  slave_address
  * @param  reg_address
  * @param  pdatabuf
  * @param  len
  * @retval tempRe:whether read data successfully or not
  */
uint8_t lib_i2c_ReadMutiBytes(uint8_t slave_address, uint8_t reg_address, uint8_t* pdatabuf, uint8_t len)
{
    
    uint8_t tempData;		
		uint8_t tempRe = 0;
		uint8_t cnt = 0;
		uint8_t tempaddr_W = slave_address<<1;
		uint8_t tempaddr_R = tempaddr_W + 1;
		do
		{
      /* 1st:i2c start signal */
			hw_i2c_START();
      /* 2nd:write slave device address, bit0 is a read-write control bit, 0 for write, and 1 for read */
			lib_i2c_SendByte(tempaddr_W);
      /* 3rd:wait ack from slave device */
			tempRe = hw_i2c_WaitAck();
			if (tempRe){							// the return value is 1 ,that is to say SDA is not lowwed. the target ist8310 doesn't Ack
				 break;
			}
      /* 4th:send target register address */
			lib_i2c_SendByte(reg_address);
      /* 5th:wait Ack from slave device */s
			tempRe = hw_i2c_WaitAck();
			if (tempRe){							// the return value is 1 ,that is to say SDA is not lowwed. the target ist8310 doesn't Ack
				 break;
			}
			
      /* 6th:send a start signal to reset i2c bus, and then start to read data from slave device */
			hw_i2c_START();
      /* 7th:Send a read command(bit0) for the slave address */
			lib_i2c_SendByte(tempaddr_R);
      /* 8th:wait Ack from slave device */
			tempRe = hw_i2c_WaitAck();
			if (tempRe){							// the return value is 1 ,that is to say SDA is not lowwed. the target ist8310 doesn't Ack
				 break;
			}			
			//9th:read data by loop
			for(cnt = 0; cnt < len; cnt++)
			{
				pdatabuf[cnt] = lib_i2c_ReadByte();    //read 1 byte
				/* After reading each byte, an Ack needs to be sent, except for the last byte, which requires a Nack */
				if(cnt != len - 1)
				{
          /* After the middle byte is read, the CPU generates the ACK signal (Drive SDA = 0) */
					hw_i2c_ACK();
				}
				else
				{
          /* After reading the last byte, the CPU generates the NACK signal (drive SDA = 1) */
					hw_i2c_NACK();
				}
			}
		}while(0);
		/* Send I2C bus stop signal */
		hw_i2c_STOP();
		if(tempRe){
			tempRe = 0;
		}else{
			tempRe = 1;
		}
		return tempRe;
}

lib_i2c_WriteSingleByte()

/**
  * @brief  write single byte through the i2c1(For M1) by sofyware simulation.
  * @param  slave_address: address of tagert slave device
  * @param  reg_address: address of tagert register
  * @param  pdata: pointer to the data
  * @retval tempRe: Write data successfully or not
  */
uint8_t lib_i2c_WriteSingleByte(uint16_t slave_address, uint16_t reg_address, uint8_t pdata)
{
    uint8_t tempRe = 0;
    uint8_t tempData = pdata;
		uint16_t tempaddr = slave_address<<1;
    do
    {
		hw_i2c_START();
        lib_i2c_SendByte(tempaddr);
        tempRe = hw_i2c_WaitAck();
        if (tempRe){							// the return value is 1 ,that is to say SDA is not lowwed. the target ist8310 doesn't Ack
           break;
        }
        lib_i2c_SendByte(reg_address);
        tempRe = hw_i2c_WaitAck();
        if (tempRe){							// the return value is 1 ,that is to say SDA is not lowwed. the target ist8310 doesn't Ack
           break;
        }
        lib_i2c_SendByte(tempData);
        tempRe = hw_i2c_WaitAck();
        if (tempRe){							// the return value is 1 ,that is to say SDA is not lowwed. the target ist8310 doesn't Ack
           break;
        }
        hw_i2c_STOP();
    } while (0);
    return tempRe;
}

小结

因为用的芯片硬件IIC的底层官方函数一直卡死跑不通,于是干脆自己整理了一套软件模拟IIC的相关流程和函数,已经在项目中顺利调通了,项目换芯片也经历过不同芯片的移植,也很方便。

你可能感兴趣的:(单片机,囹圄起舞,心得,笔记,单片机,IIC)