The common i2c bus (or TWI, or whatever non-trademarked name your manufacturer is using) is fairly easy to design with. It’s just a couple of wires, right? Well, mostly…
You also need a pair of pull-up resistors.
I just ran into this on the LetterBox Project, and while I get the concept, I wasn’t real sure what the rules were for calculating the values of the resistors. That led to to Google, which led me to this ‘application report’ from Texas Instruments.
It makes things fairly straightforward.
The Minimum Value
In my case, I’m hooking an MCP23017 to an ESP32-S2-MINI-1 module via i2c. Before you ask, yes, it has to be i2c. While the 23017 is available in an SPI variant, the RTC domain in the ESP does not have access to an SPI interface, and this needs to be available from there.
That app note tells us that the minimum pull-up resistance is (Vcc - VOL) / IOL. If I look up the appropriate values in the 23017 datasheet, I get the following:
3.3V - 0.6V Rp(min) = ----------- = 900 ohms .003A
Note that the datasheet is a little unclear on certain points. For
example, it specifies two VOL values for SDA; one is
SO, SDA. I’m not sure why they did that. The one
depicted above is what I consider the “worse case scenario”.
I also substituted VDD for VCC. They’re interchangeable in this case.
Okay, so that’s our minimum. What’s our maximum? That one is a little more complicated.
The Maximum Value
Yeah, I’m not an expert or a math brain. I don’t know all the math bits behind this (the one electronics class I took didn’t get this far). I tried to run the math, got what I thought were obviously wrong answers (which in retrospect were probably correct) and gave up; it’s not important enough for me to pursue and figure it all out right at this moment.
Rabbit holes: I’m trying hard to avoid them. It’s on my list for when I have time to refresh my math skills, since they seem to have atrophied a bit.
The important bit is that the app note boils it all down to a simplifed equation that’s good for VIL of 0.3 VCC and a VIH of 0.7 VCC. That’s close enough for government work; as long as we stay away from the very edges of the resulting range, we should be fine:
tr Rp(max) = ----------- 0.8473 * Cb
Where tr is the rise time, and Cb is the bus capacitance. That’s defined in the datasheet, too, as 400pf, and the rise time for 100Khz mode is defined as a maximum of 1000ns. Therefore:
0.000001 Rp(max) = ---------------- = 2950.549 0.00000000033892
That seems rather low to me, honestly. If we change to the 400kHz mode, where the rise time is 300ns, it gets even smaller at 885 ohms, which just seems bogus (less than the minimum!).
That’s… what it says, however.
Part of my confusion is probably on the topic of bus capacitance. Reading further, it looks like the bus capacitance is specified as a maximum, and that maximum is much lower (100pF) for the 1.7MHz mode (and probably should be for the 400Khz mode as well). That makes all the difference, bringing our Rp(max) for 1.7MHz up to around 1600 ohms, which makes a great deal more sense.
If we assume a practical bus capacitance of 200pF, then the 400KHz case starts to make sense again as well with Rp(max) in the neighborhood of 1770 ohms.
I want to, at least for now, leave the super-fast 1.7MHz mode available as an option (though I’m not actually sure the ESP32-S2 will support it in the ULP). It seems to me, though, that we have plenty of range. A 1.2K resistor should do the job nicely, leaving us a nice margin of error even in the super-fast case. I have vague recollections of going with 10K resistors the last time I did something like this, but it’s been too long for me to remember, so 1.2K it is.
At least until I get my hands on a way to check out the expected capacitance of the i2c traces on the board. If it’s 10pF or something, then the numbers change rather dramatically.
Of course, I don’t have an ESP32-S2 to test with just yet. We’ll see if this pans out.
And some day I’ll put some sort of math tool in here so I can render proper equations in my posts. For now, my bad ASCII will have to do…