I2C通讯为什么要用开漏输出和上拉电阻?

目录

一、I2C总线简介

二、 I2C接口接外部上拉电阻的原因


一、I2C总线简介

I2C(Inter-Integrated Circuit)总线是一种双向串行通信总线,由两根线组成:SDA(数据线)和SCL(时钟线)。这两根线都是双向的,并且是开漏输出的,这意味着每个设备都可以将线拉低(Ground),但不能将线拉高(Vcc)。这种设计使得多个设备可以共享同一条总线,以进行通信。

二、 I2C接口接外部上拉电阻的原因

I2C(Inter-Integrated Circuit)接口在使用时需要连接外部上拉电阻,主要原因包括以下几点:

开漏(Open-drain)或开集电极(Open-collector)输出:

I2C总线上的设备如主设备和从设备使用开漏或开集电极的输出方式来驱动总线。这意味着,设备只能将线路拉低(接地),而不能直接将线路拉高至供电电压。因此,需要外部上拉电阻来将线路拉高。

多主设备配置:

I2C允许多个主设备存在于同一总线上。为了防止输出冲突(例如,一个设备尝试将线路拉高,而另一个设备尝试将线路拉低),I2C设计为只能通过外部上拉电阻来将信号线拉高,从而简化了总线管理。

逻辑状态的稳定和可靠性:

外部上拉电阻确保在没有设备主动驱动线路时,数据线(SDA)和时钟线(SCL)能稳定地保持在高电平状态。这有助于提高信号的可靠性和减少误读。

灵活的电压级别:

由于I2C设备可以支持不同的逻辑电平,使用外部上拉电阻可以方便地匹配总线电平到特定的系统电压,例如3.3V或5V等,从而使得不同电压等级的设备可以共存于同一总线。

电气特性的优化:

通过选择合适的上拉电阻值,可以优化总线的电气特性,如上拉速率、功耗和噪声容限。电阻值太低会增加功耗和可能导致总线驱动器过载,而电阻值太高则可能导致信号上升时间过长,影响总线速率。

所以综上所述,外部上拉电阻在I2C通信中发挥着至关重要的作用,保证了通信的稳定性和灵活性。

在I2C通信中,使用推挽(push-pull)输出并不是标准的实现方式,因为这种输出方式与I2C设计的开漏(open-drain)或开集电极(open-collector)输出方式存在本质上的差异。下面详细解释为什么通常不使用推挽输出:

总线冲突的风险:

I2C总线设计为多主设备和多从设备可以共享同一总线。如果使用推挽输出,当一个设备试图将总线拉高而另一个设备试图将其拉低时,将会发生总线冲突,可能导致设备损坏。

信号完整性问题:

推挽输出可以同时驱动高电平和低电平,这在总线空闲和活跃时都维持总线状态。然而,这种方式缺乏开漏输出的灵活性,例如在总线检测和仲裁过程中动态改变控制权,这是I2C协议重要的一部分。

电平匹配和灵活性降低:

使用推挽输出意味着所有设备必须在相同的电压级别上操作,这限制了不同电压级别设备的互操作性。相比之下,开漏输出允许通过外部上拉电阻选择适当的电压级别,以匹配不同设备的电压要求。

仲裁和时钟同步问题:

I2C支持总线仲裁和时钟同步,这依赖于能够检测总线上的高电平和低电平状态。如果总线使用推挽输出,总线上的电平状态将由最后一个发送信号的设备完全控制,从而使得仲裁和同步变得困难或不可能。

因此,尽管理论上可以通过某些特定设计让I2C总线上的设备使用推挽输出,但这样做通常需要额外的硬件支持和复杂的总线管理策略,且违背了I2C协议的基本设计原则。如果需要在I2C总线上实现类似推挽的功能,通常建议使用其他通信协议,如SPI或UART,这些协议本身就设计为支持推挽输出。

  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下为使用C语言实现STC89C52芯片和PCF8591之间I2C通讯读取光敏电阻输出的电压值并且判断当处于黑暗时将P1.0口置于高电平点亮LED的代码: ```c #include <reg52.h> #define SCL P3_6 // SCL引脚 #define SDA P3_7 // SDA引脚 #define LED P1_0 // LED引脚 // I2C起始信号 void i2c_start() { SDA = 1; SCL = 1; SDA = 0; SCL = 0; } // I2C停止信号 void i2c_stop() { SDA = 0; SCL = 1; SDA = 1; } // I2C发送一个字节,并返回从设备的ACK位 bit i2c_send_byte(unsigned char byte) { bit ack; unsigned char i; for (i = 0; i < 8; i++) { SDA = (byte & 0x80) >> 7; byte <<= 1; SCL = 1; SCL = 0; } SDA = 1; SCL = 1; ack = SDA; SCL = 0; return ack; } // I2C接收一个字节,并发送ACK位 unsigned char i2c_recv_byte(bit ack) { unsigned char byte = 0; unsigned char i; SDA = 1; for (i = 0; i < 8; i++) { byte <<= 1; SCL = 1; byte |= SDA; SCL = 0; } SDA = !ack; SCL = 1; SCL = 0; return byte; } // 读取光敏电阻输出的电压值 unsigned char get_luminance() { unsigned char lum; i2c_start(); // 发送起始信号 i2c_send_byte(0x90); // 发送PCF8591器件地址 i2c_send_byte(0x40); // 发送控制字节 i2c_send_byte(0x00); // 发送数据字节 i2c_start(); // 发送起始信号 i2c_send_byte(0x91); // 发送PCF8591器件地址 lum = i2c_recv_byte(0); // 接收数据字节,并发送ACK位 i2c_stop(); // 发送停止信号 return lum; } // 延时函数 void delay(unsigned int t) { unsigned int i, j; for (i = 0; i < t; i++) for (j = 0; j < 125; j++); } int main() { unsigned char lum; while (1) { lum = get_luminance(); // 读取光敏电阻输出的电压值 if (lum < 50) // 判断是否处于黑暗 LED = 1; // 点亮LED else LED = 0; // 熄灭LED delay(100); // 延时100ms } return 0; } ``` 需要注意的是,以上代码中使用的是STC89C52单片机和PCF8591模数转换器进行I2C通讯读取光敏电阻输出的电压值,并判断是否处于黑暗时点亮LED。如果您使用的是其他型号的单片机或模数转换器,需要根据实际情况进行相应的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值