Embedded Development Hardware I2C Debugging Tips: Logic Analyzer Packet Capture in Practice
Friends doing embedded development all know that I2C protocol sounds simple - just two wires (SDA and SCL) - but when you actually use it, it can drive you crazy questioning your life choices. Device not responding, data reading incorrectly, timing issues… Today I’ll demonstrate with a logic analyzer how to catch these strange I2C problems one by one.
What Do You Need to Prepare?
| Item | Model/Specification | Price |
|---|---|---|
| Logic Analyzer | Saleae Logic 8 / Domestic Clone | ¥80-300 |
| I2C Device | MPU6050 Accelerometer | ¥15 |
| Development Board | CH559 Development Board | ¥25 |
| Jumper Wires | Male-to-Female 10cm | ¥5 |
| Breadboard | Mini Type | ¥8 |
| Total | ¥133-253 |
You don’t necessarily need to buy the original Saleae; domestic clones cost just tens of yuan and are completely sufficient for daily debugging. The one I have is 8-channel, more than enough for I2C.
Step 1: Set Up Test Environment
Let’s connect the hardware first. I2C wiring is very simple:
CH559 MPU6050
───── ───────
3.3V → VCC
GND → GND
P1.4(SCL) → SCL
P1.5(SDA) → SDA
Notes: ⚠️
-
I2C needs pull-up resistors! Most modules already have built-in 4.7kΩ pull-ups, but if not, remember to connect a 4.7kΩ resistor from both SDA and SCL to VCC
-
Voltage must match! CH559 is 3.3V logic, MPU6050 is also 3.3V; if connecting 5V devices you need level shifting
-
The logic analyzer’s ground must share ground with the circuit under test, otherwise you won’t capture the signal
Next, install the logic analyzer software. Domestic clones typically use Sigrok/PulseView:
# Ubuntu/Debian
sudo apt-get install pulseview
# Fedora
sudo dnf install pulseview
# Or download Saleae official software (for original version users)
# https://www.saleae.com/downloads
Step 2: Write I2C Test Code
We’ll write a simple I2C read/write program using CH559. CH559’s hardware I2C is on pins P1.4 and P1.5:
#include <CH559.H>
#include <stdint.h>
// I2C pin definitions
sbit I2C_SDA = P1^5;
sbit I2C_SCL = P1^4;
// MPU6050 address (7-bit address, 0x68)
#define MPU6050_ADDR 0x68
#define WHO_AM_I_REG 0x75
// I2C delay (adjust according to actual clock)
void I2C_Delay() {
_nop_();
_nop_();
}
// I2C start signal
void I2C_Start() {
I2C_SDA = 1;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 0;
I2C_Delay();
I2C_SCL = 0;
}
// I2C stop signal
void I2C_Stop() {
I2C_SDA = 0;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 1;
I2C_Delay();
}
// I2C send one byte
uint8_t I2C_SendByte(uint8_t dat) {
uint8_t i, ack;
for (i = 0; i < 8; i++) {
I2C_SDA = (dat >> 7) & 0x01;
dat <<= 1;
I2C_SCL = 1;
I2C_Delay();
I2C_SCL = 0;
I2C_Delay();
}
// Read acknowledge
I2C_SDA = 1;
I2C_SCL = 1;
I2C_Delay();
ack = I2C_SDA;
I2C_SCL = 0;
I2C_Delay();
return ack;
}
// I2C receive one byte
uint8_t I2C_RecvByte(uint8_t ack) {
uint8_t i, dat = 0;
I2C_SDA = 1;
for (i = 0; i < 8; i++) {
dat <<= 1;
I2C_SCL = 1;
I2C_Delay();
dat |= I2C_SDA;
I2C_SCL = 0;
I2C_Delay();
}
// Send acknowledge
I2C_SDA = ack ? 1 : 0;
I2C_SCL = 1;
I2C_Delay();
I2C_SCL = 0;
I2C_Delay();
I2C_SDA = 1;
return dat;
}
// I2C write register
void I2C_WriteReg(uint8_t reg, uint8_t dat) {
I2C_Start();
I2C_SendByte(MPU6050_ADDR << 1); // Write address
I2C_SendByte(reg);
I2C_SendByte(dat);
I2C_Stop();
}
// I2C read register
uint8_t I2C_ReadReg(uint8_t reg) {
uint8_t dat;
I2C_Start();
I2C_SendByte(MPU6050_ADDR << 1); // Write address
I2C_SendByte(reg);
I2C_Start(); // Repeated start
I2C_SendByte((MPU6050_ADDR << 1) | 0x01); // Read address
dat = I2C_RecvByte(1); // NACK
I2C_Stop();
return dat;
}
void main() {
uint8_t who_am_i;
// Configure clock (12MHz)
CCLK_CFG = 0x00;
// Configure GPIO (open-drain output)
P1_MOD_OC |= 0x30; // P1.4/P1.5 open-drain
P1_DIR_PU |= 0x30; // P1.4/P1.5 input
// Initialize serial (for debug output)
mInitSTDIO();
printf("I2C Test Starting...\n");
while (1) {
// Read WHO_AM_I register (should be 0x68)
who_am_i = I2C_ReadReg(WHO_AM_I_REG);
printf("WHO_AM_I: 0x%02X\n", who_am_i);
if (who_am_i == 0x68) {
printf("MPU6050 detected!\n");
} else {
printf("Device not found!\n");
}
DelayMs(1000);
}
}
Compile and flash:
# Install SDCC
sudo apt-get install sdcc
# Compile
sdcc --code-loc 0x0000 --xram-loc 0x0000 main.c
# Flash (using wchisp)
wchisp flash main.bin
Step 3: Logic Analyzer Packet Capture
Hardware and code are ready, now for the main event - packet capture analysis!
-
Connect logic analyzer channels to SCL and SDA, ensure common ground
-
Add I2C protocol decoder in PulseView, configure sampling rate to at least 10 times the SCL frequency
-
Set trigger conditions to capture complete I2C transactions (Start → Address → Data → Stop)
-
Compare decoding results with expected data, analyze anomaly locations (NACK, incorrect data, etc.)
SCL: ──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──
└──┘ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘
SDA: ──┐ ┌────────────────────────────┐ ┌──
START └──┘ Address + R/W bit + ACK + Data └──┘ STOP
PulseView has an I2C protocol decoder; after loading it will automatically parse out:
-
Start/stop conditions
-
Device address (0x68)
-
Read/write direction
-
Register address
-
Data content
-
ACK/NACK status
Step 4: Analyze Waveforms to Find Problems
Normal Waveform Characteristics
Normal I2C communication should satisfy:
-
SCL high and low level times are basically symmetrical (50% duty cycle)
-
SDA only changes when SCL is low (except for start/stop conditions)
-
After each byte there is an ACK (SDA pulled low)
-
Start condition: SDA falling edge when SCL is high
-
Stop condition: SDA rising edge when SCL is high
Common Problem Waveforms
Problem 1: No ACK
Waveform manifestation: On the 9th clock cycle SDA stays high (not pulled low)
Possible causes:
-
Device address incorrect (MPU6050 is 0x68, not 0x69)
-
Device not powered or damaged
-
Pull-up resistors missing or value too large
-
Wiring loose
Problem 2: Data Read Back All 0xFF or 0x00
Waveform manifestation: SDA always high or always low
Possible causes:
-
Pull-up resistor too large/too small
-
Bus capacitance too large (wires too long)
-
Device in error state, needs reset
Problem 3: Timing Chaotic
Waveform manifestation: SDA changes when SCL is high
Possible causes:
-
Code delay incorrect
-
Interrupts interfered with I2C timing
-
Used wrong GPIO mode (should use open-drain)
Common Problem Troubleshooting
Problem 1: Device Address Wrong, Always NACK
-
Cause: I2C addresses have both 7-bit and 8-bit representations. MPU6050’s 7-bit address is 0x68, but some libraries use 8-bit address (shifted left one bit + R/W bit)
-
Solution: Confirm your code uses 7-bit address. When writing shift left one bit (0xD0), when reading shift left one bit +1 (0xD1)
Problem 2: Waveform Has Many Glitches
-
Cause: Poor grounding, wires too long, interference sources nearby
-
Solution: Shorten jumper wires, ensure common ground, stay away from interference sources like motors/relays
Problem 3: Logic Analyzer Can’t Capture Signal
-
Cause: Probe not connected well, trigger level set incorrectly, sampling rate too low
-
Solution: Check GND connection, adjust threshold voltage (set to around 1.5V for 3.3V logic), increase sampling rate
Problem 4: Sometimes Can Read Data, Sometimes Can’t
-
Cause: Timing margin insufficient, power supply unstable
-
Solution: Increase I2C delay, add decoupling capacitors, check power supply quality
Summary
The core of I2C debugging is “seeing” the signal. Without a logic analyzer, we can only guess: “Is the address wrong?” “Is there a timing problem?” With a logic analyzer, everything is right before your eyes, problems clear at a glance.
Several lessons learned:
-
I2C must have pull-up resistors (4.7kΩ-10kΩ), otherwise signal edges are slow, communication unstable
-
Pay attention to the difference between 7-bit and 8-bit address representations, avoid writing incorrectly causing NACK
-
The shorter the jumper wires the better; long wires introduce parasitic capacitance, affecting signal quality
-
Using a logic analyzer to capture packets is the most direct method for locating I2C problems; waveforms don’t lie
Finally a reminder, you don’t necessarily need to buy an expensive logic analyzer. Domestic clones costing just tens of yuan on Taobao are completely sufficient for daily debugging of I2C/SPI/UART. Let it heat up, just don’t let it glow - stable operation is more important than anything.
Hope this blog post is helpful to you!
Related Resources: