在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模拟
- 我们首先看下I2C总线协议,我们知道I2C总线每次通信的开始,都是来源于起始信号;每次通信的结束来源于停止信号。如下图1所示。
图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的数据有效性。
图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:
图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