The puzzle
Two chips need to signal on the same wire: either one may want to say “attention”. If both pins are ordinary outputs, the first time one drives high while the other drives low the board gets warm and the level on the wire is nonsense. I²C puts a whole bus of devices on two wires and nothing ever fights. What is different about those pins?
STEP 1
Two ways to drive a wire
A push-pull output has two transistors: one connects the pin to VDD, the other to ground, and while it drives, exactly one is on (apart from a brief overlap during each transition). It drives both levels hard, so edges are fast and nothing else is needed on the wire.
An open-drain output (open-collector with a bipolar transistor) has only the transistor to ground. Writing 0 turns it on and pulls the wire low; writing 1 turns it off and lets go. Something else, a pull-up resistor, has to bring the wire high.
STEP 3
Open-drain: wired-AND and level shifting
With open-drain outputs nobody can fight. The wire is low if any device pulls it low and high only if all let go: a wired-AND. That is why I²C, interrupt lines shared by several chips and reset lines use open-drain drivers: any device can assert the signal, and a device can also see that someone else is holding it (I²C clock stretching and arbitration rely on this).
Because the pull-up sets the high level, an open-drain output can also drive a line pulled up to a different voltage, within the pin’s voltage rating: that is one simple way to signal between chips on different supplies.
STEP 4
The price: a slow rising edge
The falling edge is driven by a transistor and is fast. The rising edge is only the pull-up charging the wire’s capacitance (every pin, trace and cable on it), an RC curve:
The falling edge of an open-drain line is driven by a transistor; the rising edge is only the pull-up resistor charging the wire’s capacitance, an RC curve. The time to reach a receiver’s high threshold, taken here as 0.7 × VDD, is RC · ln(1/0.3) ≈ 1.2RC. Capacitance counts every pin, trace and cable on the line.
The I²C specification defines rise time between 30 % and 70 % of VDD, which is 0.8473·RC, and limits it to 1000 ns in Standard-mode and 300 ns in Fast-mode. A smaller pull-up is faster but sinks more current whenever the line is low, so the resistor is a compromise between speed and the current the drivers can sink.
STEP 5
Open-drain without an open-drain mode
The STM32F4 has an open-drain output type in OTYPER. The RP2040’s pads do not (its pad bit OD is output disable). Open-drain is emulated by keeping the output value at 0 and switching the output enable: enabled means “pull low”, disabled means “let go”. The pin must be under software control first (gpio_init()), and the pad’s reset pull-down stays on unless disabled, slightly loading the external pull-up.
gpio_put(pin, 0); /* the value is always 0 */
gpio_set_dir(pin, GPIO_OUT); /* assert: drive low */
gpio_set_dir(pin, GPIO_IN); /* release: the pull-up takes the line high */
Never emulate open-drain by writing 1 with the output enabled: that is push-pull high, and it will fight any device that pulls the line low.
STEP 6
Worked example: sizing an I²C pull-up
A Fast-mode I²C bus has 150 pF of capacitance and runs at 3.3 V.
Upper limit from rise time: .
Lower limit from drive strength (VOL ≤ 0.4 V at 3 mA): .
Any value between about 1 kΩ and 2.2 kΩ works; 2.2 kΩ gives a 30–70 % rise time of 0.8473 × 2.2 kΩ × 150 pF ≈ 280 ns and sinks 1.5 mA when low. A 10 kΩ pull-up, fine for a slow interrupt line, would give about 1.3 µs here and break Fast-mode timing.
MYTHS AND FACTS
Common misconceptions
Any output can drive a shared line
Only open-drain (or tri-state outputs that are carefully enabled one at a time) can; push-pull outputs fight.
Open-drain outputs cannot drive high
They cannot drive it; the pull-up does, slowly.
Smaller pull-ups are always better
They are faster but draw more current when low, and may exceed what the weakest driver on the bus can sink.
Writing 1 to an emulated open-drain pin releases it
Only if the output is disabled; with the output enabled a 1 drives high.
Check yourself
Answer in your head, then open the card.
Three open-drain devices share an interrupt line with a pull-up. Device B pulls low; A and C have let go. What level is the line, and can A tell someone is asserting it?
Low. Yes: A reads its own pin (the input still works with the output off) and sees 0 although it is not pulling.
A 10 kΩ pull-up drives a line with 50 pF. How long does the line take to reach 0.7 × VDD?
1.2 × RC = 1.2 × 10 kΩ × 50 pF = 0.6 µs.
During bring-up a pin that should be an input is left as a push-pull output driving 0, while a sensor drives the line high. What happens?
Contention: the sensor's high-side and the MCU's low-side transistor conduct in series; tens of milliamps may flow, the level is indeterminate, and one or both drivers can be damaged.
Why does the I²C specification put a minimum on the pull-up resistance?
When a device pulls low it must sink the pull-up current and still meet VOL; R must be at least (VDD − VOL) / IOL, about 967 Ω at 3.3 V with 0.4 V at 3 mA.
Sources (3)
- STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_gpio.h and cmsis-device-f4 stm32f407xx.h — GPIO_MODE_OUTPUT_PP “Output Push Pull Mode”, GPIO_MODE_OUTPUT_OD “Output Open Drain Mode”, GPIO_MODE_AF_OD; OTYPER (offset 0x04) selects the output type per pin; BSRR (0x18) sets and resets pins atomically
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_gpio/gpio.h and hardware_regs pads_bank0.h, sio.h — the pad has no open-drain type (its OD bit is “Output disable”); SIO GPIO_OE_SET/CLR and GPIO_OUT_SET/CLR (sio.h offsets 0x24, 0x28, 0x14, 0x18) set output enable and value per pin atomically; gpio_set_dir, gpio_put
- NXP, UM10204 “I²C-bus specification and user manual”, Rev. 7.0 (1 October 2021), §7.1 “Pull-up resistor sizing”, p. 50; Tables 9 and 10 — Rp(min) = (VDD − VOL(max)) / IOL with VOL ≤ 0.4 V at 3 mA; Rp(max) = t_r / (0.8473 · Cb); rise time ≤ 1000 ns (Standard-mode) and ≤ 300 ns (Fast-mode); bus capacitance ≤ 400 pF (as cited in unit 1, lesson 3)