Explain the I2C physical layer. Why does it use open-drain outputs with pull-up resistors?
I2C uses two wires — SDA (data) and SCL (clock) — both configured as open-drain (or open-collector) outputs with external pull-up resistors. Open-drain means each device can only actively pull the line low by turning on its NMOS transistor; it cannot actively drive the line high. When no device is pulling the line low, the pull-up resistor passively returns it to VDD.
This design solves a fundamental problem: multiple devices share the same wires, and any of them might need to drive the bus at any time. With push-pull outputs, if one device drives high while another drives low, a short-circuit current flows from VDD through the high-side driver of one device and the low-side driver of the other. Open-drain eliminates this possibility — the worst case is two devices both pulling low simultaneously, which is electrically benign (they both agree on the bus state).
This wire-AND topology enables three critical I2C features: (1) multi-master arbitration — a master can detect that another master is pulling the bus low when it expected it to be high, and gracefully back off; (2) clock stretching — a slave can hold SCL low to pause the master; (3) ACK/NACK signaling — the slave pulls SDA low during the ACK clock pulse to acknowledge a byte. None of these would work with push-pull outputs, because the devices need to collaboratively share the same signal lines without conflict.
The trade-off is speed: the pull-up resistor and bus capacitance form an RC time constant that limits how fast the line can transition from low to high. The falling edge (active pull-down) is fast, but the rising edge (passive pull-up) is slow. This asymmetry is why I2C is fundamentally slower than SPI and why pull-up resistor selection directly affects maximum bus speed.
Source: I2C Q&A
