When you search for an lcd example online, 95% of the tutorials hand you a 5V Arduino Uno, a 1602 screen, and tell you to plug it in. But when you migrate that exact same example to a 3.3V ESP32 running at 400kHz, the display stays blank or throws I2C NACK errors. The missing link in almost every tutorial is the bus physics. This walkthrough treats the I2C LCD example as an engineering problem, calculating the exact pull-up resistor requirements to guarantee reliable communication.
The Problem Statement: ESP32 to I2C LCD Bus Sizing
Exam Problem: You are designing a sensor dashboard using an ESP32-WROOM-32 (3.3V logic) and a standard 1602 LCD equipped with a PCF8574 I2C backpack. The I2C bus will operate in Fast Mode (400kHz). The total estimated bus capacitance ($C_b$), including the ESP32 pins, wiring, and the PCF8574 input capacitance, is 150pF. Calculate the acceptable range for the I2C pull-up resistors ($R_p$), select the optimal standard E12 resistor value, and determine the default I2C address.
The Method & The Trap
Which theorem applies and why? We must apply the RC transient response theorem alongside Ohm's Law. I2C is an open-drain protocol; the microcontroller can only pull the line LOW. The pull-up resistor is solely responsible for pulling the line HIGH. The resistor and the bus capacitance form an RC low-pass filter. If the resistance is too high, the voltage won't rise fast enough to meet the I2C timing specifications (rise time limit). If the resistance is too low, the microcontroller's internal sink transistor cannot pull the line down to a valid logic LOW without exceeding its maximum current rating.
The Trap: The most common failure in this lcd example is blindly reusing the 4.7kΩ or 10kΩ resistors that come pre-soldered on 5V Arduino kits. At 3.3V logic and 400kHz, a 4.7kΩ resistor combined with 150pF of capacitance results in a rise time that violates the NXP I2C specification, causing the ESP32 to misread bits and drop the bus.
Step-by-Step Algebraic Solution
We will solve for the maximum and minimum allowable resistance, then pick a standard value.
Step 1: Calculate Maximum Pull-Up Resistance ($R_{p(max)}$)
According to the NXP I2C-bus specification (UM10204), the maximum rise time ($t_r$) for Fast Mode (400kHz) is 300ns. The formula for the 10% to 90% rise time of an RC circuit is:
$t_r = 0.8473 \times R_p \times C_b$
Rearranging to solve for $R_p$:
$R_{p(max)} = \frac{t_r}{0.8473 \times C_b}$
Substitute the known values ($t_r = 300 \times 10^{-9}$ s, $C_b = 150 \times 10^{-12}$ F):
$R_{p(max)} = \frac{300 \times 10^{-9}}{0.8473 \times 150 \times 10^{-12}}$
$R_{p(max)} = \frac{300}{0.127095} \approx 2360.4\ \Omega$
Step 2: Calculate Minimum Pull-Up Resistance ($R_{p(min)}$)
The ESP-IDF I2C documentation and ESP32 datasheet specify a maximum sink current ($I_{OL}$) of 3mA for standard GPIO pins, and a maximum acceptable LOW voltage ($V_{OL}$) of 0.4V. Using Ohm's Law:
$R_{p(min)} = \frac{V_{CC} - V_{OL}}{I_{OL}}$
Substitute the known values ($V_{CC} = 3.3$V, $V_{OL} = 0.4$V, $I_{OL} = 0.003$A):
$R_{p(min)} = \frac{3.3 - 0.4}{0.003} = \frac{2.9}{0.003} \approx 966.7\ \Omega$
Step 3: Select the Optimal Standard Value
Our valid resistance window is 967Ω to 2360Ω. We want the highest possible resistance within this window to minimize current draw and power dissipation when the line is pulled LOW. Looking at the standard E12 resistor series (1.0k, 1.2k, 1.5k, 1.8k, 2.2k), the highest value that does not exceed 2360Ω is 2.2kΩ.
Sanity Check & Independent Verification
Order of Magnitude & Power Check: A 2.2kΩ resistor is standard for 3.3V I2C buses. Let's verify power dissipation. When the ESP32 pulls the line LOW, the voltage across the resistor is $3.3V - 0.4V = 2.9V$.
$P = \frac{V^2}{R} = \frac{2.9^2}{2200} = \frac{8.41}{2200} \approx 3.82\text{mW}$
This is well below the 250mW rating of a standard 1/4W through-hole resistor or the 100mW rating of an 0805 SMD resistor. The math holds.
How to verify the answer independently: Do not trust the math blindly on a noisy breadboard. Connect an oscilloscope probe to the SDA line. Trigger on the rising edge. Measure the time it takes for the waveform to rise from 0.33V (10% of 3.3V) to 2.97V (90% of 3.3V). If your measured rise time is under 300ns and the waveform looks like a clean exponential curve rather than a jagged stair-step, your 2.2kΩ pull-up is correctly sized. If you see severe ringing, your breadboard capacitance is higher than 150pF, and you may need to drop to 1.8kΩ.
Decision Tree: Choosing Your Exact LCD Example Components
Use this decision matrix to terminate your design phase and lock in your bill of materials. Do not guess; match your logic voltage and speed to the row below.
| Logic Level | Bus Speed | Est. Capacitance | Calculated Pull-Up | Concrete Pick (Buy This) |
|---|---|---|---|---|
| 5V (Arduino Uno) | 100kHz | < 200pF | 4.7kΩ | Standard 1602 + 4.7kΩ Resistor |
| 3.3V (ESP32/RP2040) | 400kHz | ~ 150pF | 2.2kΩ | PCF8574 Backpack + 2.2kΩ 1/4W Resistor |
| 3.3V (ESP32) | 1MHz (Fast+) | > 200pF | 1.0kΩ | PCF8574 Backpack + 1.0kΩ 1/4W Resistor |
Final Default Recommendation: For 99% of modern ESP32 lcd example projects running at 400kHz, desolder the pre-installed 4.7kΩ surface-mount resistors on the PCF8574 backpack and solder in 2.2kΩ through-hole resistors between SDA/SCL and VCC. Set your I2C address to 0x27 (the default for the PCF8574A chip used on most blue-backlight modules).
FAQ: Common LCD Example Pitfalls
Why does my LCD show only a row of white blocks?
This is a contrast issue, not an I2C communication failure. The white blocks mean the LCD controller is initialized, but the liquid crystals are fully biased. Use a small flathead screwdriver to turn the blue potentiometer on the back of the PCF8574 backpack counter-clockwise until the blocks fade and characters appear.
Can I use the internal ESP32 pull-up resistors instead of external ones?
No. The internal GPIO pull-ups on the ESP32 are typically around 45kΩ. This is far too weak to pull the I2C bus high within the 300ns rise time limit, even at 100kHz. You must use external discrete resistors.
My I2C scanner finds the LCD at 0x3F instead of 0x27. Why?
Manufacturers use two different I2C expander chips: the PCF8574 (default address 0x27) and the PCF8574A (default address 0x3F). The pinouts are identical, but the base I2C address is hardcoded in silicon. Update your code's initialization object to LiquidCrystal_I2C lcd(0x3F, 16, 2); to match your specific hardware.






