1. 首页
  2. 技术文章

STM32F10x学习笔记—GPIO模拟I2C通讯AT24C02第一篇之IO模拟的初始化

在STM32的库函数应用中,很少使用硬件I2C,大部分时间我们都会应用模拟IO的高低电平,来仿I2C总线。这是因为,首先模拟IO的灵活性大,任意两个IO就可以;其次,硬件I2C的BUG比较多,偶会会出现不知名问题和故障。那么,从这章开始我们学习模拟I2C来通信AT24C02。

  • IO的初始化

代码如下:

void eeprom_I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  /*使能时钟 */
  RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB, ENABLE);  
/*****************这里可以是任意IO*******************/
  /* 初始化SCL和SDA*/
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6    ;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;          // SCA
  GPIO_Init(GPIOB, &GPIO_InitStructure);
 
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;          // SDA
  GPIO_Init(GPIOA, &GPIO_InitStructure);     
}

固定的初始化格式,模拟IO的时候 IO的模式为 开漏输出。其他根据引脚来配置。

二.I2C总线的GPIO模拟

  1. 我们首先看下I2C总线协议,我们知道I2C总线每次通信的开始,都是来源于起始信号;每次通信的结束来源于停止信号。如下图1所示。

STM32F10x学习笔记—GPIO模拟I2C通讯AT24C02第一篇之IO模拟的初始化

图1 I2C的开始和停止信号

        从图1中我们看到,开始信号是在SCL为高电平的时候,SDA从高电平到低电平;停止信号与开始信号相反,SLC为高电平的时候,SDA从低电平到高电平。

首先在在对应的.h文件中定义宏:

#define eeprom_I2C_SCL_Set        GPIO_SetBits(GPIOB, GPIO_Pin_6)
#define eeprom_I2C_SCL_Reset      GPIO_ResetBits(GPIOB, GPIO_Pin_6)

#define eeprom_I2C_SDA_Set        GPIO_SetBits(GPIOA, GPIO_Pin_6)
#define eeprom_I2C_SDA_Reset      GPIO_ResetBits(GPIOA, GPIO_Pin_6)

#define eeprom_I2C_SDA_Read() GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)

我们定义 ..Set 为高电平;..Reset为低电平;..Read()为读取IO状态。

有了宏定义,我们根据图1,模拟开始信号,代码如下:

void eeprom_I2C_Start(void)
{
 /*****I2C的起始信号:SCL在高电平时候,SDA从高电平到低电平,然后SCL变低电平***********/
        eeprom_I2C_SDA_Set;   //  SDA至高电平
	eeprom_I2C_SCL_Set;   // SCL至高电平	
        delay_for_eeprom();   //  这里的延迟是考虑,CPU执行快,需要等待上面完成结果。
	eeprom_I2C_SDA_Reset;  // SDA产生低电平 	
	delay_for_eeprom();	
	eeprom_I2C_SCL_Reset;  // 拉低SCL
	delay_for_eeprom();
}

中间延迟函数,是非精准延迟函数,根据经验测试而来,大家可以自己修改时间进行尝试,主要为了等待执行结果。图1中起始信号最后并没有拉低SCL信号,这里拉低了是因为按照SCL顺序都是持续循环的来,我们顺着周期来也就是该低电平了。

停止信号代码如下:

void eeprom_I2C_Stop(void)
{
	/*****I2C停止信号:SCL在高电平时候,SDA从低电平拉高*************/
	eeprom_I2C_SDA_Reset;
	eeprom_I2C_SCL_Set;
	delay_for_eeprom();
	eeprom_I2C_SDA_Set;
}

2. 有了其实启停信号后,我们还需要接收数据、发送数据、接收应答信号和发送应答信号。在看这些信号前,同样的我们先看I2C通信协议,我们首先看图2的数据有效性。

STM32F10x学习笔记—GPIO模拟I2C通讯AT24C02第一篇之IO模拟的初始化

图2 I2C数据的有效性

从图2中可知,只有当SCL在高电平的时候,SDA的电平才会有效,在SCL为低的时候,SDA电平无效,也就是说,在SCL为低的时候,我们会变换SDA电平。

有了数据有效性的概念我们就可以学习,数据的发送和接收了。我们知道I2C每一次发送数据或者接收数据都是1个字节,8bit;我们首先看发送1个字节数据,代码如下:

void eeprom_I2C_SendByte(unsigned char Send_Buffer)
{
  unsigned char i;  
  for(i=0; i<8; i++)         //一个字节8位
  {
      if(Send_Buffer & 0x80)   // 0x80==1000 0000  也就是说罢Send_Buffer的最高位取出来
       {    
        eeprom_I2C_SDA_Set;    //最高位为1 发送高
       }
     else 
      {
      eeprom_I2C_SDA_Reset;    //最高位为0,发送低电平
      }     
     delay_for_eeprom();     
    /********考虑数据的有效性*************/ 
     eeprom_I2C_SCL_Set;     //数据都是在SCL高电平时候有效,在高电平时发送。
     delay_for_eeprom();
     eeprom_I2C_SCL_Reset;   //数据在SCL低电平时候 进行电平转换, 发送完毕后 进行拉低。    
    if (i==7)
    {
     eeprom_I2C_SDA_Set;      // 最后一位数据发送完毕后,总线拉高,表示总线空闲
    }
     Send_Buffer<<= 1;  // 左移1位 把下一位 移到最高位,进行发送前的准备。 下次循环开始进行判断最高位电平。
     delay_for_eeprom();
  }  
}

待发送的数据Send_Buffer,我们需要1位1位的发送,那么我们就需要把数据的每一位取出来然后发送出去。Send_Buffer&0x80 这个就是把最高位电平取出来。0x80二进制为10000000;Send_Buffer的最高位和10000000相与,Send_Buffer最高位为高即为高,为低即为低。发送后Send_Buffer向左位移1位,这样就这Send_Buffer第二位就位移到最高位在于0x80相与,如此循环直到Send_Buffer发送完毕。其他部分注释很明白了。

同样的我们接收数据道理是一样的。代码如下:

unsigned char eeprom_I2C_ReadByte(void)
{
   unsigned char i;
   unsigned char Read_Data=0;
  for(i=0; i<8; i++)         //一个字节8位  开始读取
  {
    Read_Data<<= 1; 		  //数据左移1位 为下一位数据做准备。
    eeprom_I2C_SCL_Set;    //数据有效性,高电平 数据有效。
    delay_for_eeprom();    
    if(eeprom_I2C_SDA_Read())       //读取当前电平值
     {
      Read_Data = Read_Data + 0x01;  //高电平时 +1
	//	 Read_Data++;  //与上面相同
     } 
    else
    {
      Read_Data = Read_Data;   //这一句可以省略
    }              
   eeprom_I2C_SCL_Reset;    //拉低SCL
	 delay_for_eeprom();
  }   
  return Read_Data;    //取完8bit数据后,返回该数据
}

每读取一次电平,存放到Read_Data,然后左移一位,然后在存放到Read_Data,直到循环完成。

3.我们在看下ACK应答信号的产生和非应答ACK信号的产生。ACK应答信号产生图3:

STM32F10x学习笔记—GPIO模拟I2C通讯AT24C02第一篇之IO模拟的初始化

图3 ACK产生图

如图3所示,ACK应答信号的产生就是,在发送数据后,数据线拉低,并保持在SCL高电平期间。反之,非应答信号是在发送数据后,数据线保持高电平。因此,应答信号代码如下:

void eeprom_I2C_Ack(void)
{
	eeprom_I2C_SDA_Reset;  //产生应答信号
	delay_for_eeprom();	
	eeprom_I2C_SCL_Set;  //数据的有效性
	delay_for_eeprom();	
	eeprom_I2C_SCL_Reset;  //数据的有效性
	delay_for_eeprom();
	eeprom_I2C_SDA_Set;  //释放掉控制权
}

非应答信号的产生代码如下:

void eeprom_I2C_NAck(void)
{
	eeprom_I2C_SDA_Set;  //产生非应答信号
	delay_for_eeprom();	
	eeprom_I2C_SCL_Set;  //数据的有效性
	delay_for_eeprom();	
	eeprom_I2C_SCL_Reset;  //数据的有效性
	delay_for_eeprom();
}

最后我们还需要读取从机发送过来应答信号,前面我们知道ACK信号的产生,那么只需要读取SDA是否拉低就可以了。
代码如下:

signed char eeprom_I2C_ReadAck(void)
{
	signed char re_ack;	
	eeprom_I2C_SDA_Set;  //释放掉控制权
	delay_for_eeprom();
	eeprom_I2C_SCL_Set;  //数据的有效性
	delay_for_eeprom();
	if(!eeprom_I2C_SDA_Read())    //如果是低电平
	{
	 re_ack = 1;		           //接收到应答信号
	}
       else  re_ack = 0;         //如果是高电平 ,表示无应答信号	
	eeprom_I2C_SCL_Reset;  //数据的有效性
	delay_for_eeprom();
	return re_ack;	
}

其中re_ack=1时候 表示读取应答成功,反之失败。

到这里,模拟I2C基本状态我们已经学习完毕了,下一章节,我们在这基础上,学习完成 任意数据的读取和任意数据的写入,思维和硬件I2C是一样的。具体我们下章节见。

原创文章,作者:小峰,如若转载,请注明出处:http://www.wfblog.com/archives/402.html

联系我们

400-800-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息