1.起始信号
时钟线SCL为高时,数据线SDA由高到低
最后一句操作:时钟线拉低,钳住IIC总线,准备发送数据
注意:SDA和SCL同时为高时,为IIC总线的空闲状态,也就是说当没有数据传输时这两根线应该都是高电平
【当没有数据传输时,SDA(数据线)的电平通常由设备的实现方式决定。在大多数情况下,SDA 会被拉高到逻辑高电平(通常为电源电压),以示空闲状态。但是也有一些设备在空闲时可能会将 SDA 拉低到逻辑低电平,具体取决于设备的设计和实现。】
2.停止信号
时钟线SCL为高时,数据线SDA由低到高
第一句确保时钟线为低时,数据线才能变化为0,否则这就可能成起始信号了!
注意:当 SDA 和 SCL 同时保持低电平时,通常表示总线被占用或者处于忙碌状态
3.ACK/NACK信号
从机在第9个时钟信号进行拉低回应,表示收到了主机发来的数据,拉高则表示不应答
4.等待ACK
主机做完操作之后需要等待从机的回应
5.发送一个字节
//==================================
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
//==================================
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT(); //SDA发送模式
IIC_SCL=0; //拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7; //SDA高低电平表示数据1和0
txd<<=1;
delay_us(2);
IIC_SCL=1; //SCL先上升
delay_us(2);
IIC_SCL=0; //SCL再下降,形成一个脉冲,发送一位数据生效
delay_us(2);
}
}
首先将SCL拉低,设置SDA为输出,然后根据输入的字节数据通过位移操作将该数按二进制解析出来,每解析出一位就拉高拉低SCL产生脉冲时钟传输数据
6.读取一个字节
读取一个字节,也是分8次循环,产生8个时钟信号,并读取SDA的高低电平信号,最后,根据要不要继续读下一个字节,发送第9位的Ack或nACK
/==================================
//读1个字节
//ack=1时,发送ACK,ack=0,发送nACK
//==================================
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN(); //SDA输入模式
for(i=0;i<8;i++ )
{
IIC_SCL=0; //SCL先下降,通过循环,形成时钟脉冲
delay_us(2);
IIC_SCL=1; //SCL上升
receive<<=1;
if(READ_SDA)
receive++; //读取并组合记录数据,++表示读到1了,最低位置1
delay_us(1);
}
//读取8位后,主机需要变为发送模式,在第9位进行应答或不应答
//此时CLK还是高电平状态,不过下面的应答会先将CLK拉低的
if (!ack)
{
//读1个字节,或读多个字节读到最后一个字节时,使用nACK
//然后配合使用IIC停止信号
IIC_NAck();//发送nACK
}
else
{
//读多个字节还没读完时,使用ACK,表示现在读的ok,还要继续读
IIC_Ack(); //发送ACK
}
return receive;
}
7.真实IIC波形
通过上面的介绍再看真实的波形,理解会更加透彻
【图片为借用】
从IIC实测波形入手,搞懂IIC通信 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/161710767
7.实际使用
比如我想需要读取一个芯片上的数据回来
//调用该函数
blRet = HAL_Sxxxx_READ_ARRAY(ADDR, u8ReadBuf, 2);
//上述函数再调用该函数
enuRet = GPIO_I2C_READ(GPIO_I2C_0, ADDRESS, u16RegAddr, 0, pu8ReadData, u8RdLen);
GPIO_I2C_READ(uint32_t I2Cx, uint8_t addr, uint16_t start_Addr, uint8_t Addr_Length,
uint8_t *buf, uint8_t Data_Length)
/上述参数含义:
I2Cx---【使用哪路IIC】
addr---【从机地址】
start_Addr---【读取数据的起始地址】
Addr_Length---【地址长度】
*buf---【返回读取数据的指针】
Data_Length---【数据长度】
最后数据就会返回到指针所指的地址之中
8.引脚配置
引脚配置比较简单:
GPIO_BOP(GPIO_SCL) = GPIO_Pin_SCL;
GPIO_BOP(GPIO_SDA) = GPIO_Pin_SDA;
gpio_init(GPIO_SCL, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, GPIO_Pin_SCL);
gpio_init(GPIO_SDA, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, GPIO_Pin_SDA);
配置解释:
-
这两行代码使用了一个宏GPIO_BOP(GPIO_SCL) = GPIO_Pin_SCL;
和GPIO_BOP(GPIO_SDA) = GPIO_Pin_SDA;
GPIO_BOP
,它的作用是将指定的 GPIO 引脚设为高电平输出。 -
这行代码使用了一个函数gpio_init(GPIO_SCL, GPIO_MODE_OUT_OD, GPIO_OSPEED_50MHZ, GPIO_Pin_SCL);
gpio_init
,它用于初始化指定的 GPIO 引脚。GPIO_MODE_OUT_OD
表示设置为开漏输出模式,GPIO_OSPEED_50MHZ
表示设置输出速度为 50MHz,GPIO_Pin_SCL
则表示要初始化的具体引脚号。
9.为什么要将模拟IIC引脚配置成开漏输出
-
双向通信:
I2C 总线是一个双向通信总线,其中 SDA(数据线)既可以作为输入线,也可以作为输出线。在这种情况下,开漏输出模式允许引脚在高电平和低电平之间切换,同时也可以让其他设备驱动总线。 -
总线共享:
I2C 总线是一种共享总线,可能有多个从设备连接在同一条总线上。开漏输出允许多个设备连接在一起,不会造成冲突。因为只有在低电平时设备才会驱动总线,而高电平是由上拉电阻提供的。 -
防止冲突:
在总线上,如果多个设备同时尝试驱动总线为高电平或低电平,就会造成电气冲突。开漏输出模式仅能将引脚拉低,而无法主动拉高电平,这样可以避免冲突。 -
上拉电阻:
在开漏输出模式下,SDA 和 SCL 引脚需要通过外部上拉电阻连接到电源电压。这确保了在没有设备主动驱动的情况下,总线处于高电平状态。
总之,使用开漏输出模式是为了确保 I2C 总线的安全、可靠的双向通信以及避免电气冲突。