Connect a 5V sensor to a 3.3V microcontroller without checking logic levels, and you can kill the GPIO pin, or the whole chip. Logic levels define which voltages a digital input reads as HIGH or LOW. This guide covers the thresholds, the TTL and CMOS families, the 3.3V vs 5V compatibility rules, and four proven level-shifting circuits with wiring and code.
Quick Takeaways
- Logic HIGH and LOW are voltage ranges, not fixed values. Each IC datasheet defines VIH, VIL, VOH, and VOL thresholds.
- A 3.3V output usually drives a 5V TTL input correctly, but not a 5V CMOS input. TTL needs only about 2.0V for HIGH; 5V CMOS needs about 3.5V.
- A 5V output must never drive a non-5V-tolerant 3.3V pin directly. Exceeding VDD + 0.3V can trigger latch-up or permanent damage.
- Pick the shifter by protocol: a BSS138 MOSFET for I2C, a 74LVC245 or TXS0108E buffer for SPI and fast signals, and a voltage divider for slow one-way signals.
| Logic Family | Supply (VCC) | Min VIH | Max VIL | Typical Use |
|---|---|---|---|---|
| 5V TTL (74LS/74HCT) | 5V | 2.0V | 0.8V | Legacy logic, Arduino Uno peripherals |
| 5V CMOS (74HC, ATmega) | 5V | 3.5V | 1.5V | General 5V microcontroller logic |
| 3.3V LVCMOS (ESP32, STM32) | 3.3V | 2.0V | 0.8V | Modern MCUs and sensors |
| 1.8V LVCMOS | 1.8V | 1.17V | 0.63V | Low-power SoCs, flash memory |
What Are Logic Levels?
A digital pin has no concept of “1” or “0.” It compares the incoming voltage against internal thresholds. Four parameters from every datasheet govern this behavior:
- VOH: Minimum output voltage the driver guarantees when sending HIGH.
- VOL: Maximum output voltage the driver guarantees when sending LOW.
- VIH: Minimum input voltage the receiver reads as HIGH.
- VIL: Maximum input voltage the receiver reads as LOW.
Between VIL and VIH lies the forbidden zone. A signal parked there produces undefined behavior: random bit flips, extra current draw, and oscillation.
Noise Margin
Two devices talk reliably only if the driver output clears the receiver input with room to spare:
- High-level noise margin: NMH = VOH(driver) − VIH(receiver)
- Low-level noise margin: NML = VIL(receiver) − VOL(driver)
Both values must be positive. Zero or negative margin means the link fails, or works only on your bench at room temperature.
Worked example: An ATmega328P at 5V outputs a VOH of roughly 4.2V at 20mA load. An ESP32 input has a VIH of about 0.75 × 3.3V = 2.475V. The noise margin is positive, so the signal is read correctly, but 4.2V far exceeds the ESP32’s absolute maximum rating of 3.6V. Margin is not the only test; you must also respect the absolute maximum input voltage.
TTL vs CMOS: Core Differences
TTL (Transistor-Transistor Logic)
TTL uses bipolar junction transistors (BJTs). The classic 7400-series runs at 5V with a narrow supply tolerance (4.75V to 5.25V).
- Input HIGH threshold: 2.0V
- Input LOW threshold: 0.8V
- Output HIGH: about 2.7V to 3.4V (not rail-to-rail)
- Draws significant static current
CMOS (Complementary MOS)
CMOS uses paired P-channel and N-channel MOSFETs. Static current is near zero, outputs swing nearly rail-to-rail, and the input thresholds scale with supply voltage.
- Input HIGH threshold: about 0.7 × VDD
- Input LOW threshold: about 0.3 × VDD
- Output swings within millivolts of VDD and GND
- High input impedance, so floating inputs pick up noise
The HCT Trick
The 74HCT family is CMOS internally with TTL-compatible input thresholds. This makes it a cheap level shifter in one direction: a 74HCT chip powered at 5V accepts a 3.3V HIGH (above its 2.0V VIH) and outputs a clean 5V swing.
| Parameter | TTL (74LS) | CMOS (74HC) | HCT (74HCT) |
|---|---|---|---|
| Input technology | BJT | MOSFET | MOSFET |
| VIH at 5V | 2.0V | 3.5V | 2.0V |
| Output swing | 0.4V to 3.4V | Rail-to-rail | Rail-to-rail |
| Static power | High | Very low | Very low |
| Accepts 3.3V input at 5V supply | Yes | No (unreliable) | Yes |
| Speed | Moderate | Fast | Fast |
3.3V vs 5V: Compatibility Rules
Rule 1: 3.3V Output → 5V Input
Check the receiver’s VIH.
- 5V TTL-threshold input (VIH = 2.0V): Works directly. A 3.3V output clears it.
- 5V CMOS input (VIH = 3.5V at 5V supply): Fails. 3.3V sits in the forbidden zone.
Many popular 5V devices, such as the ATmega328P, specify VIH as 0.6 × VCC = 3.0V. A 3.3V signal technically passes with little margin. Marginal designs fail in the field, so shift the signal.
Rule 2: 5V Output → 3.3V Input
Check the receiver’s absolute maximum rating, not just VIH.
- Standard ESP32, ESP8266, STM32 (non-FT pins), and Raspberry Pi GPIO are not 5V tolerant.
- Some STM32 pins marked “FT” are 5V tolerant. Verify in the datasheet pin table.
- Exceeding VDD + 0.3V turns on internal ESD protection diodes, which then conduct current into the supply rail.
Rule 3: Mind the Direction
Signals like UART TX/RX are unidirectional, so you can use simple resistor dividers. Buses like I2C are bidirectional, so you need a MOSFET or bidirectional shifter.
Compatibility Matrix
| Driver → Receiver | Direct Connection | Fix |
|---|---|---|
| 3.3V → 5V TTL input | Safe | None needed |
| 3.3V → 5V CMOS input | Unreliable | Level shifter or 74HCT |
| 5V → 3.3V (not 5V tolerant) | Unsafe | Divider or shifter |
| 5V → 3.3V (5V tolerant pin) | Safe | None needed |
| 3.3V ↔ 5V I2C | Unsafe | BSS138 bidirectional shifter |
| 1.8V → 3.3V | Often unreliable | TXB0104 or TXS0108E |
Level Shifting Methods
Method 1: Resistor Voltage Divider (Unidirectional, Slow Signals)
Cheapest option for 5V → 3.3V on signals under about 100kHz.
Components: R1 = 1kΩ, R2 = 2kΩ
5V signal ──[ R1 1kΩ ]──┬── 3.3V signal (to MCU input)
│
[ R2 2kΩ ]
│
GND
Formula: Vout = Vin × R2 / (R1 + R2) = 5V × 2000 / 3000 = 3.33V
Limits:
- Combined with the pin capacitance, the resistors form an RC low-pass filter. At 10kΩ total and 15pF, the time constant eats into signals above a few hundred kHz.
- It works in one direction only.
- Keep resistance low enough for edge speed, high enough to limit current.
Method 2: BSS138 MOSFET Shifter (Bidirectional, I2C)
The standard solution for I2C and other open-drain buses. It needs one BSS138 N-channel MOSFET and two pull-up resistors per line.
3.3V 5V
│ │
[ 10kΩ ] [ 10kΩ ]
│ │
LV side ─┴──── Source Drain ──────────┴─── HV side
BSS138
Gate ── 3.3V (LV)
How it works:
- LV side idle HIGH: the MOSFET is off (Vgs = 0). Each side is pulled to its own rail.
- LV side pulled LOW: Vgs becomes positive, the MOSFET conducts, and the HV side is pulled LOW.
- HV side pulled LOW: the body diode conducts first, pulling the source low, which turns the MOSFET on.
Adafruit and SparkFun sell four-channel breakout boards built on this design for a few dollars.
Limit: Works well up to about 400kHz (I2C Fast Mode). Rise times degrade beyond that.
Method 3: Dedicated Level Shifter ICs
| IC | Channels | Direction | Max Speed | Best For |
|---|---|---|---|---|
| TXB0104 | 4 | Auto-bidirectional | 100Mbps | SPI, push-pull signals |
| TXS0108E | 8 | Auto-bidirectional | 110Mbps (push-pull) | Mixed buses, I2C, 1-Wire |
| 74LVC245 | 8 | Fixed (DIR pin) | 100MHz+ | Parallel buses, SPI, displays |
| 74AHCT125 | 4 | Unidirectional | 50MHz+ | 3.3V → 5V, WS2812B LEDs |
| PCA9306 | 2 | Bidirectional | 400kHz+ | Dedicated I2C |
Warning on TXB0104: Its auto-direction sensing uses weak output stages. Pull-up resistors and heavy loads cause contention. Use it for light push-pull signals only, not I2C.
Method 4: 74HCT Buffer (3.3V → 5V)
A single 74HCT125 or 74AHCT125, powered from 5V, restores a 3.3V signal to a full 5V swing. Use it for WS2812B addressable LEDs, which need a VIH of about 0.7 × 5V = 3.5V.
Comparing Shifting Methods
| Method | Cost | Direction | Speed | Complexity | Best Use |
|---|---|---|---|---|---|
| Resistor divider | Lowest | One-way (high → low) | Slow | Very low | UART RX, button inputs |
| BSS138 MOSFET | Low | Bidirectional | Up to 400kHz | Low | I2C |
| TXS0108E | Medium | Auto-bidirectional | Fast | Low | Mixed prototypes |
| 74LVC245 | Medium | Switchable | Very fast | Medium | SPI, parallel buses |
| 74AHCT125 | Low | One-way (low → high) | Fast | Low | LED strips, 3.3V → 5V |
Pinout Walkthrough: 4-Channel BSS138 Module
A typical bidirectional breakout has the following pins:
- LV: Low-voltage reference (3.3V from the MCU).
- HV: High-voltage reference (5V from the peripheral supply).
- GND (both sides): Common ground. Connect both.
- LV1 to LV4: Low-side signal channels.
- HV1 to HV4: High-side signal channels matching LV1 to LV4.
Critical wiring rule: Always connect a shared GND between the two voltage domains. A level shifter without common ground has no reference.
Real-World Project: 3.3V ESP32 Reading a 5V I2C Sensor
Scenario: An ESP32 (3.3V) must read a 5V I2C sensor module with 4.7kΩ pull-ups tied to 5V.
Components:
- ESP32 DevKit
- 4-channel BSS138 bidirectional level shifter
- 5V I2C sensor
- Jumper wires
Wiring:
| Level Shifter Pin | Connects To |
|---|---|
| LV | ESP32 3V3 |
| HV | 5V rail |
| GND | Common ground |
| LV1 | ESP32 GPIO21 (SDA) |
| LV2 | ESP32 GPIO22 (SCL) |
| HV1 | Sensor SDA |
| HV2 | Sensor SCL |
Remove or disable the sensor’s onboard pull-ups to 5V only if the shifter board already includes its own. Double pull-ups lower the effective resistance and may exceed the 3mA I2C sink limit.
Firmware (Arduino C++):
#include <Wire.h>
#define SDA_PIN 21 // ESP32 GPIO21 -> LV1 on level shifter
#define SCL_PIN 22 // ESP32 GPIO22 -> LV2 on level shifter
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(100000); // 100kHz standard mode: safest through BSS138 shifters
Serial.println("I2C scanner started");
}
void loop() {
byte count = 0;
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) { // 0 = ACK received
Serial.print("Device found at 0x");
Serial.println(addr, HEX);
count++;
}
}
if (count == 0) Serial.println("No devices found. Check shifter wiring and pull-ups.");
delay(3000);
}
Start at 100kHz. Raise to 400kHz only after confirming clean waveforms on an oscilloscope.
Real-World Project: Driving WS2812B LEDs from a 3.3V MCU
A WS2812B strip powered from 5V expects a data HIGH of at least 0.7 × VDD = 3.5V. A 3.3V GPIO sits below that, so strips flicker or show wrong colors.
Fix: Place a 74AHCT125 (powered at 5V) between the MCU data pin and the strip.
Wiring:
- 74AHCT125 VCC to 5V, GND to common ground.
- Tie the OE (output enable) pin of the used gate to GND (active-low).
- MCU data pin to gate A input.
- Gate Y output to a 330Ω series resistor, then to strip DIN.
- Add a 1000µF capacitor across the strip’s 5V and GND to absorb inrush current.
The series resistor damps ringing on the data line.
Diagnosing Logic Level Problems
Work through these steps when a mixed-voltage circuit misbehaves:
- Measure the idle voltage on each signal line with a multimeter. A line stuck near half-rail points to a missing pull-up or a contention fault.
- Check VIH against VOH in both datasheets. Compute the noise margin.
- Scope the edges. Slow rise times from large pull-up values make fast buses unreliable.
- Confirm common ground. Floating grounds cause the most baffling errors.
- Feel the chips. A warm GPIO pin or regulator suggests a 5V signal is forcing current through an ESD diode.
- Test with lower bus speed. If a slower clock fixes it, the shifter or pull-up limits bandwidth.
| Symptom | Likely Cause | Fix |
|---|---|---|
| I2C scanner finds nothing | Missing pull-ups or wrong shifter side | Add 4.7kΩ pull-ups to each rail |
| LEDs flicker or wrong colors | 3.3V data below 5V strip VIH | Add 74AHCT125 |
| UART garbled characters | 5V TX into 3.3V RX with no divider | Add 1kΩ/2kΩ divider |
| MCU resets or runs hot | 5V on non-tolerant pin | Add level shifter immediately |
| Bus works at 100kHz, fails at 400kHz | Pull-ups too weak for capacitance | Lower pull-up value or use PCA9306 |
Design Best Practices
- Choose one voltage domain per board whenever possible. Modern sensors increasingly run at 3.3V or 1.8V.
- Read the absolute maximum ratings table before connecting anything.
- Use 5V-tolerant parts deliberately, not by assumption.
- Add series resistors (100Ω to 330Ω) on fast edges to limit ringing and protect pins.
- Place pull-ups on the correct rail. Each side of a BSS138 shifter pulls to its own supply.
- Decouple every IC with a 100nF ceramic capacitor close to the VCC pin.
- Power sequencing matters. Powering a 5V peripheral before the 3.3V MCU can back-feed current through input protection diodes.
Frequently Asked Questions
Can I connect a 3.3V device directly to a 5V device?
It depends on direction. A 3.3V output to a 5V input works only if the 5V input has a VIH of 3.3V or less, such as TTL-compatible inputs. A 5V output to a 3.3V input is unsafe unless the 3.3V pin is explicitly rated 5V tolerant. Use a level shifter when in doubt.
Is Arduino Uno 5V logic compatible with ESP32 3.3V?
Not directly. The Uno’s ATmega328P outputs 5V, which exceeds the ESP32 GPIO maximum of about 3.6V. Use a voltage divider for slow one-way signals or a bidirectional level shifter for I2C and SPI. In the opposite direction, the ESP32’s 3.3V output typically clears the ATmega328P’s 3.0V VIH threshold, though a shifter improves reliability.
What is the difference between TTL and CMOS logic levels?
TTL inputs switch at about 2.0V (HIGH) and 0.8V (LOW) with a 5V supply. CMOS thresholds scale with supply voltage, at roughly 70% and 30% of VDD. TTL outputs do not reach the full supply rail, while CMOS outputs swing nearly rail-to-rail.
Do I need a level shifter for I2C?
Yes, when devices run at different voltages. Use a BSS138-based bidirectional shifter or a PCA9306. Pull-up resistors must sit on each voltage side. If all devices run at the same voltage, no shifter is needed.




