模拟i2c不受端口限制,但是通信速率没有硬件I2C高
配置为开漏输出时可以读取输入引脚电平,需要外接上拉电阻。
配置为推挽输出时读出的数据是上一次写的数据,不能输入。
I2C起始条件和停止条件

读写时序:主机先发一个7位地址,再发一位读写位,从机应答;如果此时是写操作:主机收到应答后,就写入一个八位数据,从机再应答,以此循环;如果此时是读操作:从机发送完应答位之后,再次发送一个八位数据,主机发送应答,以此循环;
终止传输条件:接收设备不应答或者发送设备不想再发送
我们以max30102为例子来写一个i2c

由图可知,SDA和SCL都已经接了上拉电阻,所以我们直接将io口设置为开漏模式即可;
器件地址

手册对时序的介绍,起始信号和重复起始信号一样



软件模拟i2c步骤:
首先模拟起始信号和停止信号:
//起始信号:scl在高电平时将sda拉低
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
//停止信号:SCL在高电平时将SDA拉高
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
模拟写入单字节数据:
/*起始信号后,主机发送一个7位地址,再次发送一个数据读写位,从机应答,数据传输时,
8个bit为一个数据,9个bit为1帧,SCL低电平时SDA翻转数据,SCL高电平时SDA不许翻转数据,
高位先行*/
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
模拟读出单字节数据:
/*读数据,判断从机每次发给主机的数据是高还是低即可,先是释放总线,接着判断SDA的电平,再将SCL
拉低即可获取一位数据,循环八次即可获取一个字节数据*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
MyI2C_W_SCL(0);
}
return Byte;
}
主机应答信号
/*主机直接写低电平即可*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
从机应答
/*同样是释放总线,判断收到的信号是否是低电平,是就是应答,否就是非应答*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
通常最常见的使用方法:往一个寄存写值控制外设,或者不断读取一个寄存器,获取寄存器得到的数据
写值:主机发送起始信号,发送硬件地址,得到从机应答位,再次发送要写入的寄存器地址,得到应答位,写入数据,得到应答位,主机发送停止信号
void write_reg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(ID);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
读值:先写硬件地址,写入
/*主机发送起始信号,发送硬件地址,得到从机应答位,再次发送要写入的寄存器地址,得到应答位,
再次发送起始信号,发送硬件地址,此时要更改数据流向为读,接收到应答信号后,再次接收数据,
最后发送非应答或者应答后,主机发送停止信号*/
uint8_t read_reg(uint8_t ADDR)
{
uint8_t ret;
MyI2C_Start();
MyI2C_SendByte(ID);
MyI2C_ReceiveAck();
MyI2C_SendByte(ADDR);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(ID|0x01);
MyI2C_ReceiveAck();
ret=MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();
return ret;
}
注意释放总线是先拉高SDA再拉高SCL,如果搞反就是起始或者停止信号,在SCL高电平期间,从机拉高SDA是在传输数据,不会是停止信号,起始信号或者停止信号只有主机才拥有。