I2C pull-up resistors versus Bidirectional logic level converter

Hello,
I have a question about some electronics aspects of an I2C connection. Please advise if it is more appropriate to post this question in the General Electronics section instead of here.
I need to connect a couple of 3V3 sensors to a 5V Arduino board. I would like to better understand the different options (if any) and their potential consequences.

  1. Direct connection of the SCL, resp. SDA lines with pull-up resistors to 3V3
  • the HIGH signals will be at 3V3; the I2C standard specifies at least 0.7Vdd or 3V5 for the Arduino; does this mean that the communication is less robust, more prone to errors at the Arduino side?
  • if the Arduino enables its internal pull-ups on the SCL resp. SDA, a HIGH level will be somewhere between 3V3 and 5V, depending on the external/internal pull-ups that constitute a voltage divider. Can this harm the sensor’s input circuits (input higher than the voltage at which the sensor is powered).
  1. Bidirectional logic level converter, such as the Sparkfun BOB12009
  • I read somewhere that a logic level converter introduces some signal distortion; where is this distortion coming from? Is the distortion bad enough to jeopardise the I2C robustness of communication?
  • do I need to have pull-up resistors at both the 5V and 3V3 sides?
  • the logic level converter has 10k resistors on a signal line, both 5V and 3V3 sides; do I need to take into account these when determining the value of the external pull-ups?
    Thank you very much for my education.
    Guy

The I2C bus requires pullup resistors. That is part of the design, which was intended for chip to chip communications on a single PCB.

Bidirectional logic level shifters should have pullups on both sides of the converter.

The value of the resistor is very important, and should be as low as reasonable to reduce problems with external noise. The actual values you can use depend on the external circumstances.

The best option will always to be to use a 3.3V processor with 3.3V sensors or other types of chips. Any intermediate circuitry will reduce reliability.

gbaets, the answer to all your questions is "yes". All your concerns are valid. There is even more !
In general, the I2C bus often works. When the I2C bus does not work, then there is often a number of those problems.

Sparkfun has a tutorial: Bi-Directional Logic Level Converter Hookup Guide - SparkFun Learn.

This is an image from that tutorial:

When LV1 turns low, then the Gate is at 3.3V and the mosfet turns on, pulling HV1 towards LV1.
That also means that a device at LV1 has to sink both the 3.3V side and the 5V side to make both signals low.

When HV1 turns low, then it gets hairy. The diode of the mosfet is a "intrinsic body diode". It is not a real diode that was added, the body diode is a result of how the mosfet is made.
So when HV1 turns low, then LV1 is pulled low via the diode. However, the gate is still at 3.3V, so the mosfet turns on, and a reverse current going through the drain and source. You have to measure what the result is, because the datasheet only gives information when the gate voltage is 0.
And of course: a device at HV1 has to sinks both the 5V and the 3.3V side to pull the signals low.

There must be at least one pullup resistor at the 5V side and at least one resistor at the 3.3V side.
They can be anywhere, they don't have to be directly next to the level shifter. The 10k pullup resistors on the module with the level shifters are added to be sure.

Even though the level shifter is not ideal, the alternative is to damage a sensor. The newer Bosch sensors really don't like a higher voltage at SDA and SCL.

All this trouble disappears magically into another universe when you use a 3.3V Arduino board as jremington already wrote. For example a MKR board with the SAMD processor.

Thank you Remington and keeper for the quick replies.
I am constrained by 5V Arduino (Arduino UNO WiFi Rev2, already bought in the context of STEM education in our schools).
We will then use a logic level shifter in the circuit, with a 4k7 pull-up at the 5V side; the sensors are on break-out boards with each a 4k7 pull-up at the 3V3 side.
Then the total current a device (Arduino or sensor) needs to sink is around 3.3mA. I will have a look at the ATmega4809 data sheet, and need to find out for the sensors.
Thanks again.
Guy

I meant koepel. Automatic spelling correction

Some Arduino boards (Uno for example) only use the internal pullups in the '328P for I2C pullup.
After Wire.begin() in setup, I believe you can add
digitalWrite (SCL, LOW);
digitalWrite (SDA, LOW);
to turn the internal pullups off and then use external pullup resistors to 3.3V.

On the Mega, there are onboard pullups to 5V, you'd have to physically remove them, or cut the traces to them.

Other Arduinos, need to investigate the hardware. Arduino UNO WiFi Rev2 ?

I don't see any discrete pullups on the schematic

Thank you CrossRoads.
For the Arduino UNO WiFi Rev2 (megaAVR family) I find in the Wire library (or rather in the twi.c sublibrary) the statements

#ifdef NO_EXTERNAL_I2C_PULLUP
    pinMode(PIN_WIRE_SDA, INPUT_PULLUP);
    pinMode(PIN_WIRE_SCL, INPUT_PULLUP);
#endif

This is in the TWI_MasterInit routine, which is called by Wire.begin(). But I don't know where NO_EXTERNAL_I2C_PULLUP is defined, or that I should define it myself somewhere ?
Thank you everybody for the feedback.
Guy

Link to source with the "NO_EXTERNAL_I2C_PULLUP": ArduinoCore-megaavr/twi.c at master · arduino/ArduinoCore-megaavr · GitHub

I ran a search in the 'megaavr' build environment on my computer.
It is only used in "boards.txt": nona4809.build.extra_flags={build.328emulation} -DMILLIS_USE_TIMERB3 -DNO_EXTERNAL_I2C_PULLUP
It could be only temporary to fix something.

Perhaps you can add a #define or a compiler flag in your sketch with a #pragma ?

Is this really needed ? They are only weak pullup resistors. A problem should be solved by fixing the problem itself.

The standard I2C specification says that a device should be able to sink at least 3mA. Some Arduino boards (Arduino Uno) can do much more, about 10 times more. Sometimes a manufacturer of a I2C sensor makes it cheap and and they can barely sink 3mA. That is enough to make it according to the I2C standard. I suggest to do it right and not go above 3mA, even though 10% above it is not worth mentioning.

Did you know that you can measure the sink current ? Make a sketch with Wire.begin() in setup() and nothing else. The loop() is empty. Now the I2C bus is initialized but idle. Measure with a multimeter the shortcut current from SDA to GND and from SCL to GND.

Thank you very much Koepel.
If I understand correctly, when the I2C bus is idle, both SCL and SDA are HIGH. By measuring the current between one of them and GND, I pull down the line and I get the current through all the pullups. Nice. Another thing learned!
I just looked into the ATmega4809 datasheet. Internal pullups are nominally 35kOhm, so they play a marginal role.
In my first post I did not specify the problem I try to solve. That is against forum rules. Apologies for that.

I try to read a Sparkfun CCS811 air quality sensor and BME280 environmental sensor (separate breakouts, not the combo). On an Arduino UNO Rev3 that runs perfectly for days. On an Arduino UNO WiFi Rev2 it runs haphazardly and hangs. I know that the Wire library for the megaavr family does not implement time-out functionality (it is blocking), contrary to the Wire version for the avr family. That is why I try to learn as much as possible about I2C and its intricacies.
Thanks again to all.
Guy

The Sparkfun CCS811 module has no onboard level shifter: SparkFun Air Quality Breakout - CCS811 - SEN-14193 - SparkFun Electronics and the CCS811 is for 3.3V.
The Sparkun BME280 module has also no onboard level shifter: SparkFun Atmospheric Sensor Breakout - BME280 - SEN-13676 - SparkFun Electronics.

On this forum are a number of problems with the Wire library of the megaavr branch, such as for the ATmega4809.
The Wire library for the avr branch took many year to get mature. I don't know if there are still problems in the megaavr branch, it seems there are :frowning: The hardware in the microcontroller for the I2C bus differs also from the ATmega328P.

The Wire library for the avr branch is also blocking. Recently low-level timeouts have been added. They are not yet turned on by default.

gbaets:
On an Arduino UNO Rev3 that runs perfectly for days.

It could be barely running, almost destroying the BME280, and operating outside the specifications in the datasheet. It is not good enough to say that it works. You have to do the I2C right to be able to add another sensor for example.

gbaets:
That is why I try to learn as much as possible about I2C and its intricacies.

Really ? As much as possible ? Are you sure ?

Fun fact 1: When the SDA turns low a START condition could begin. That means that if SDA is kept high, you can do with SCL whatever you want. If there are external pullup resistors, you can call Wire.end() and Wire.begin() without disturbing the I2C bus. So it is possible to use the SCL as a pin for a keyboard matrix for example. Don't be tempted to try it, staying out of trouble is more important :wink:
Fun fact 2: When there are two or more I2C busses (for example with a software I2C implementation) then a single SCL can be used for all the I2C busses.
Fun fact 3: When the Slaves are sensors and so, but not a microcontroller, then the SCL does not have to be bi-directional. The weak SCL could be amplified with a strong digital driver. The SCL needs only to be bi-directional in case a Slave does clock-pulse-stretching (keeping the SCL low to pause the Master). The Arduino as a Slave pauses the Master to be able to run the onRequest handler.
Sad fact 4: The Arduino Due has pullup resistors of 1k5 or even 1k to 3.3V. That is a big mistake. They should be 10k.
Informative fact 5: More here: Home · Koepel/How-to-use-the-Arduino-Wire-library Wiki · GitHub. If you understand the "Tricks that are (almost) allowed" section then you are can wear the "super advanced I2C user"-badge :smiley:

I have a logic level shifter between the Arduino and the CCS811/BME280 sensors.
One of my errors is that I added pullups at the 5V side, because I initially and wrongly believed that a logic level shifter somehow (op-amps or something) decoupled the 5V/3V3 sides with respect to the bus sink currents. The explanation in one of your posts above thaught me that a device has to sink the whole current of both the 5V and the 3V3 side. I will remove my 4k7 pullups from the 5V side, that should decrease the sink current from 3.3mA to something like 2.3mA.
Thanks for the tutorial on github, I will try to go for the superuser badge ;D (my "as much as possible" should be understood as "as much as I can understand" ...).

Thanks again, for all the efforts by you and the other people on this forum.

Guy