Don't Just Guess your I2C Pull-up Values

Measure and Calculate Them

This is a small Arduino UNO sketch we wrote for tinyLiDAR but I think it may have a wider appeal. Hope you find it useful.
tinyLiDAR is basically an I2C slave sensor device that was designed to be paralleled for robotics applications, etc.
There are 2 user replaceable I2C pull-up resistors on the corner of each board and hence users have the question - what values should we be using for these pull-ups? The sketch below should help answer this question.
Typically designers end up using low default value resistors and/or values calculated from guesstimates of their bus capacitance. Our sketch actually measures the I2C bus wire capacitance and gives a decent recommendation for what resistor values to use. Higher values will help lower power dissipation in your design.
To run it, you have to connect any two adjacent wires from your 4 wire I2C "bus" into the A0 and A4 pins on the UNO. The other side of this bus will not be connected to anything for the measurements.
You then type in the number of tinyLiDAR boards you will be running in parallel and press ENTER. The sketch will then measure the capacitance of your wiring and suggest pull-up resistor values based on the number of boards you will be using.
You can use this code with any I2C device, of course, since it only measures the capacitance of your passive wiring. Just set the variable "tinyLiDAR_capacitance" to zero when doing this. No extra hardware is required to run this sketch. The theory of operation is summarized in the comments section.
fyi - tinyLiDAR is a gereral purpose laser distance sensor board that we have currently in a crowdfunding campaign. The board simplifies access to the VL53L0X chip and gives better performance than a software only approach.
Enjoy the sketch and be sure to check out our campaign soon on Indiegogo if you like this kind of stuff.


/*

Arduino UNO sketch used to calculate the optimal pull-up resistor values for the I2C 
pins on each tinyLiDAR when paralleling these modules.  

Last Edit: July 16, 2017
Copyright (c) 2017 by Dinesh Bhatia 
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>

How to use: Insert your I2C bus wires into pins A0 and A4 of the UNO but leave the far
end of these wires disconnected. This sketch is used to measure the worst case bus 
capacitance from a set of UNPOWERED wires. The value given will include capacitive
loading due to the tinyLiDAR boards in your final setup. 
For example - connect the white and yellow adjacent wires from a "grove" 4pin cable 
into the A0 and A4 pins on the UNO and run the program. Its okay to measure your 
complete passive I2C bus including any extender boards such as the 4 grove connector 
I2C extender pcb.

Given:
  Arduino UNO supply and I2C bus voltage, VDD = 5V
  Digital logic Input High threshold, ViH = 70% of VDD, which is 3.5v
  Digital logic Input Low threshold, ViL = 30% of VDD, which is 1.5v

  For robust operation in a high-noise environment, the I2C specification recommends 
  0.2*Vdd as a suitable noise margin above ViH. So V noise margin on High Level, 
  VnH = 5*0.2, which is 1v

per the I2C 100KHz spec:
  Low level output voltage, VoL = 0.4v max
  Low level output current, IoL = 3mA max 
  rise time of both SDA and SCL signals, Tr = 1000ns max 

Calculation of min pull-up resistor value (Rp) due to low level current limit:
  Rp > (VDD - VoL)/IoL, Rp > 4.6V/3mA, so net Rp should be higher than 1.53Kohms in
  all cases

Calculation of max Rp value due to leakage current on bus:
  Each tinyLiDAR uses an FXMA2102 integrated dual supply level translator which has a
  max of 1uA input leakage current as well. If using 3x tinyLiDAR modules we have 
  3uA of total leakage current. Total input leakage current, IiH = sum of all leakage
  currents on bus = 1+3 = 4uA. We should use 100% margin here for a robust design so
  double the value calculated above. 

  Therefore Max Pull-up resistor value (Rp) due to leakage currents, Rp should be less
  than VDD-(ViH+VnH)/IiH = [5-(3.5+1)]/8uA = 62.5Kohm

Calculation of max Rp value due to bus capacitance:
  The Voltage at any time t in an RC circuit is determined by the std RC formula -
  V(t) = VDD * ( 1 - e^(-t/RC) )
  Solving for Input Voltage levels, 
  It will take a time of 0.3567RC to charge up to ViL and 1.2040RC to charge to ViH.
  The difference is the required rise time of 0.8473RC.

  So per the I2C spec we need rise time Tr<1000ns max and hence Rp due to bus 
  capacitance can be up to Rp = Tr/(0.8473*C), ->>> use the pgm below which uses a 
  modified version of Jonathan Nethercott's sketch to measure the bus capacitance on 
  A0 to A4 pins.

Optimum Rp value should be mid way between min Rp and the lower of max Rp due to 
leakage or bus capacitance.

References: 
  http://wordpress.codewrite.co.uk/pic/2014/01/21/cap-meter-with-arduino-uno/
  http://www.nxp.com/docs/en/user-guide/UM10204.pdf
  http://www.edn.com/design/analog/4371297/Design-calculations-for-robust-I2C-communications
  http://www.onsemi.com/pub/Collateral/FXMA2102-D.pdf

*/
//Given
const float CodeRev = 1.1; //Sketch rev
const int Arduino_capacitance = 25; //approx parasitic capacitance in pF for Arduino board + connector pins
const int tinyLiDAR_capacitance = 13 + 3; //13pF typ for FXMA2102 Ci/o + 3pF for bare tinyLiDAR PCB/connector
const int Tr = 1000; //max I2C rise time in ns
const int VDD = 5; //V 
const float VoL = 0.4; //V  
const float IoL = 3; //mA
const float ViH = 0.7 * VDD;
const float VnH = 0.2 * VDD;


//Capacitor under test between OUT_PIN and IN_PIN
const int OUT_PIN = A4;
const int IN_PIN = A0;

//Capacitance between IN_PIN and Ground
//Stray capacitance is always present. Extra capacitance can be added to
//allow higher capacitance to be measured.
const float IN_STRAY_CAP_TO_GND = 24.48; //initially this was 30.00
const float IN_EXTRA_CAP_TO_GND = 0.0;
const float IN_CAP_TO_GND = IN_STRAY_CAP_TO_GND + IN_EXTRA_CAP_TO_GND;
const int MAX_ADC_VALUE = 1023;

String command; // String input from command prompt
char inByte; // Byte input from command prompt

float measureCap() {

    pinMode(IN_PIN, INPUT);
    digitalWrite(OUT_PIN, HIGH);

    int val = analogRead(IN_PIN);

    digitalWrite(OUT_PIN, LOW);
    pinMode(IN_PIN, OUTPUT);

    float c = (float) val * IN_CAP_TO_GND / (float)(MAX_ADC_VALUE - val);

    return c;

}

void setup() {

    float j = measureCap();

    pinMode(OUT_PIN, OUTPUT);

    Serial.begin(115200);
    Serial.print(F("\n\r *** tinyLiDAR I2C Pull-up Resistance Calculator Rev "));
    Serial.print(CodeRev);
    Serial.println(F(" *** \n\r"));
    Serial.println(F(
        " ->> Enter number of parallel tinyLiDAR modules and press ENTER key when ready to measure your bus capacitance "
    ));

}

void loop() {

    int i;

    // Input serial information:
    if (Serial.available() > 0) {

        inByte = Serial.read();

        // only input if a number is typed
        if ((inByte >= 48 && inByte <= 57))
            command.concat(inByte);

    }

    if (inByte == 13) {

        String hexstring = command.substring(0);

        int numOfSlaves = strtol( & hexstring[0], NULL, 0);
        command = "";

        if (numOfSlaves > 0) {

            Serial.println(F("\n\r Now taking the measurement..."));
            float sumCap = 0;

            for (i = 0; i < 50; i++) {
                sumCap += measureCap();
                delay(10);
            }

            Serial.print(F(
                " Measured bus capacitance (including tinyLiDAR boards) =  "
            ));
            float cap = Arduino_capacitance + sumCap / i + numOfSlaves * tinyLiDAR_capacitance;

            if (cap > 400) {

                Serial.println(F(
                    " *** OVERRANGE ** Warning - You have exceeded the I2C bus max capacitance spec of 400pF *** \n\r"
                ));

            } else {

                Serial.print(cap, 1);
                Serial.println(F("pF. "));

                float Rpmin = (VDD - VoL) / IoL;
                Serial.print(F(" Min net Rp due to IoL limit: "));
                Serial.print(Rpmin, 2);
                Serial.println(F("Kohms"));

                int IiH = 2 * (1 + numOfSlaves);

                float RpmaxI = (VDD - (ViH + VnH)) / float(IiH * .001);
                Serial.print(F(
                    " Max net Rp due to system leakage current is "
                ));
                Serial.print(RpmaxI, 2);
                Serial.println(F("Kohms"));

                float RpmaxC = Tr / (0.8473 * cap);
                Serial.print(F(" Max net Rp due to bus capacitance is "));
                Serial.print(RpmaxC, 2);
                Serial.println(F("Kohms"));

                //normally max Rp due to bus capacitance is lower than that due to leakage current
                float Rpmid = Rpmin + ((RpmaxC - Rpmin) / 2);
                Serial.print(F("\n\r For <<< "));
                Serial.print(numOfSlaves);
                Serial.println(F(
                    " >>> tinyLiDAR modules connected in parallel,"
                ));
                Serial.print(F(
                    " Please use pull-up resistors in the range of: "
                ));
                Serial.print(Rpmin * numOfSlaves, 1);
                Serial.print(F("Kohms to "));
                Serial.print(Rpmid * numOfSlaves, 1);
                Serial.println(F("Kohms."));

            } //if cap

        } //if numOfSlaves

    } //if inByte

} //loop

Share