As part of the electrical interface of I2C, a pair of pull up resistors are required as shown in the diagram above. There are two separate resistors:
- One between SDA and VCC
- One between SCL and VCC
Where VCC is the logic level for the controller and target. Quite often, this is also power. So for example, the same 3.3V line that powers the target device.
As mentioned above, the pull up resistors should be connected to the same voltage as the "logic level" for the device. This shouldn't be confused with the "power" voltage. The common values for both are 5V and 3.3V.
If "power" and "logic level" are the same, either 5V or 3.3V, then things are simple. There's generally only one voltage level in play.
Sometimes a board will have 5V "power" (like from a USB port) but the "logic level" (voltage on GPIO pins) is 3.3V. This can potentially lead to mixed voltage levels since there are now two voltages in play. Different processors have different levels of tolerance for this. For example, it has been noted that the SAMD51 really doesn't like the I2C lines pulled up higher than 3.3V.
For a self contained setup, like an iPhone, the pull up resistors can be located anywhere convenient inside the iPhone itself. Everything - the host controller, all the target devices, wiring between them, etc. - are all in one blob. So the pull up resistors are just somewhere in that blob.
Things become a little trickier when the host controller and the target devices are separated. This is the situation that generally exists for Makers. There are numerous options for host controllers, for example a Raspberry Pi or an Arduino board. There are also lots of I2C based target devices, for example - all the STEMMA sensor breakouts. Where are the pull up resistors to be located in this scenario? Unfortunately, there is no standard. So it all depends. But there are generally only two options for where to locate the pull up resistors:
- on the host controller
- on the target device(s)
The Raspberry Pi is one example where the pull up resistors are located on the host controller. Here's an example showing the I2C pull up resistors on the schematic for a Raspberry Pi 3 Model B+.
The down side to this is the pull up resistors are always present. So if one wanted to use the GPIO pins for something other than I2C, the pull up resistors would still be there, physically connected to the same pins, and could cause potential interference.
This is the approach taken by pretty much all Adafruit I2C breakouts. Here's an example showing the pull up resistors for the STEMMA version of the BMP280 breakout.
The down side to this approach is the varying effective pull up resistance value when multiple target devices are attached. The multiple sets of pull up resistors act in parallel and thus reduce the total pull up resistance as seen by the I2C bus. However, this is generally not an issue, since I2C will function over a fairly wide range of pull up resistance values.
Another minor point is the increased cost. The I2C bus only needs one set of pull up resistors, regardless of how many target devices are connected. So by putting the resistors on every breakout board, there is unnecessary hardware redundancy.
It is very possible to end up in a situation where there are no pull up resistors included at all - on either the controller or the target. Maybe a sensor breakout was designed for a controller, like a Raspberry Pi, that has pull ups. Or the exact resistor values were left to the end user to decide upon. So the resistors were left off. Then one tries to use such a breakout with a controller, like a Metro, that also does not have pull ups. This will not work.
In this situation, pull up resistors must be added somehow. If things are setup on a breadboard, this can be done fairly easy. For example, the AM2320 does not have pull up resistors inside its case. So they must be added externally as shown here: