I2C的配置必须要按照其时序逻辑,首先先来了解I2C常见的几种工作情况:
A、主机向从机发送数据,数据传输方向在整个传输过程中不变:
B、主机在第一个字节后,立即从从机读数据:
C、在传输过程中,当需要改变传输方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反:
此外,I2C的时序如下图所示:
接下来正式开始进入I2C程序阶段(I2C管脚配置、宏定义等假设已完成):
(1)I2C起始函数(严格按照上图中起始信号的时序逻辑):
void IIC_Start(void)
{
IIC_SDA_OUT();//这里是I2C中数据信号输出的配置模式,一般配置为推挽输出
IIC_SDA_SET;//数据拉高
IIC_SCL_SET;//时钟拉高
IIC_DelayUs(5); //延时5us,满足起始信号刚开始时的大于4.7us的时序
IIC_SDA_CLR; //START: when CLK is high,DATA change form high to low;数据拉低
IIC_DelayUs(5); //保证满足至少大于4us的时序
IIC_SCL_CLR; //钳住I2C总线,准备发送或者接收数据;时钟拉低
}
(2)I2C终止函数(严格按照上图中终止信号的时序逻辑):
void IIC_Stop(void)
{
IIC_SDA_OUT();//同理,配置为推挽输出
IIC_SCL_CLR;//时钟拉低
IIC_SDA_CLR; //数据拉低;SCL在低电平期间,SDA变化无效
IIC_SCL_SET;//时钟拉高
IIC_DelayUs(5); //保持时间>4us
IIC_SDA_SET; //STOP:when CLK is high DATA change form low to high
IIC_DelayUs(5); //保持时间>4.7us
}
(3)I2C发送函数:
void IIC_SendData(uint8_t dat)
{
uint8_t i;
IIC_SDA_OUT();
IIC_SCL_CLR;
for(i=0; i<8; i++) //要发送8位,从最高位开始
{
if((dat & 0x80) == 0x80)
{
IIC_SDA_SET;
}
else
{
IIC_SDA_CLR;
}
dat <<= 1;
IIC_DelayUs(2);
IIC_SCL_SET;
IIC_DelayUs(5); //保持时间>4.7us
IIC_SCL_CLR;
IIC_DelayUs(2);
}
}
(4)I2C接收函数:
uint8_t IIC_ReceiveData(uint8_t ack)
{
uint8_t i, readValue;
IIC_SDA_OUT();
IIC_SDA_SET;
IIC_SDA_IN();
IIC_SCL_CLR;
for(i=0; i<8; i++) //接收8个字节,从高到低
{
IIC_SCL_SET;
IIC_DelayUs(2);
readValue <<= 1;
if(IIC_SDA != 0)
{
readValue |= 0x01;
}
IIC_DelayUs(1);
IIC_SCL_CLR;
IIC_DelayUs(5);
}
if(ack) //是否应答,1:产生应答;0:非应答
{
IIC_SendAck();
}
else
{
IIC_NoAck();
}
return readValue;//这里返回值即是读取到的数据
}
(5)I2C等待应答函数:
int8_t IIC_WaitAck(void)
{
uint32_t i;
IIC_SDA_IN();
IIC_SDA_SET;
IIC_DelayUs(1);
IIC_SCL_SET;
while(IIC_SDA)//在SCL高电平的情况下,SDA产生一个下降沿
{
i++;
if(i > 0xFFFF)
{
return 0xFF;
}
}
IIC_SCL_CLR;
IIC_DelayUs(2);
return 0; //返回值为1:应答成功;0:应答失败
}
(6)I2C发送应答函数:
static void IIC_SendAck(void)
{
IIC_SCL_CLR;
IIC_SDA_OUT();
IIC_SDA_CLR;
IIC_DelayUs(2);
IIC_SCL_SET;
IIC_DelayUs(2);
IIC_SCL_CLR;
}
(7)I2C无应答函数:
static void IIC_NoAck(void)
{
IIC_SCL_CLR;
IIC_SDA_OUT();
IIC_SDA_SET;
IIC_DelayUs(2);
IIC_SCL_SET;
IIC_DelayUs(2);
IIC_SCL_CLR;
}