Skip to main content
Project·

I Spent 6 Hours Debugging a Sensor Dropout. The Logic Analyzer Saved Me in 10 Minutes.

Building a multi-sensor data logger, the I2C bus started dropping packets. Here's exactly how I diagnosed the problem using the Innomaker LA1010 and PulseView.

After 6 Hours of Software Workarounds, I Realized I Had No Idea What Was Actually Happening

After six hours of adding software retry logic, exponential backoff, and buffer tweaks, I realized I was debugging my code, not the hardware. My multi-sensor data logger was dropping I2C packets at random. Sometimes the temperature sensor would respond, sometimes not. My code caught the failures and retried. My code logged the failures. My code did everything right. But the hardware kept failing anyway, and I had no idea what was actually happening on the wire. I was building an environmental data logger with three I2C sensors on the same bus: temperature, humidity, and barometric pressure. Every 10 seconds, the logger polled all three. For the first hour, everything worked. Then the dropouts started. One poll would return valid data. The next would time out on the humidity sensor. The sensor data sheet said it should respond in under 10 milliseconds. My code had timeout logic that would retry once if the first attempt failed. That seemed reasonable. The problem would vanish after a reboot and come back 20 minutes later. Classic intermittent hardware fault. So I did what any reasonable embedded engineer does: I added more retry logic. Retry after 100 milliseconds. If it still failed, try again. Add debug output to log the failures. Compile, flash, watch the serial monitor. The retries fired occasionally. Not constantly, not in any pattern. Sometimes the sensor would respond on the second try, sometimes the third, sometimes never. Every software fix felt like a reasonable hypothesis. Every fix sometimes made things slightly better, sometimes worse. I was flying blind.

The Realization That Tools Exist for a Reason

At hour six, I realized I wasn't debugging the I2C bus. I was debugging my assumptions about what was happening on the bus. My Arduino was telling me "sensor timeout," but that was just the outcome. I had no idea what was actually being transmitted, what the sensor was actually responding with, or where in the transaction the failure occurred. I needed to see what was really on the wire. I had an Innomaker LA1010 sitting in a drawer—cost me $60, ten channels, runs on USB power. I'd used it once before on a simple SPI project and then forgotten about it. I pulled it out, set up PulseView, clipped the probes to SDA and SCL, and started a capture. I didn't expect much. The logic analyzer was a curiosity tool, something I'd bought to learn with, not something that would actually solve my real problems. My oscilloscope was the real instrument. My logic analyzer was the toy. Then PulseView decoded the I2C protocol and showed me the exact byte where the slave was sending a NACK (negative acknowledgment) instead of the expected data. One transaction. 47 kilopoints of captured data. One visible failure.

The Capture That Changed Everything: 47 Kpts of Evidence

The Innomaker LA1010 can capture 100 MHz at 10-bit resolution across ten channels, with a maximum buffer depth of 10 kilopoints at full sample rate. That sounds limiting until you realize that for I2C debugging, you don't need 100 MHz. I2C at 100 kHz means the clock toggles every 5 microseconds. To see bit edges clearly, a 1 MHz sample rate is more than enough. At 1 MHz with a 10 Kpt buffer, I could capture 10 milliseconds of I2C traffic. I set up the trigger to watch for a START condition on SDA while SCL was high, then captured the next 50 transactions. The Innomaker filled the buffer with 47,000 points—about 47 milliseconds of I2C traffic. PulseView's I2C decoder did what the oscilloscope and my code never could: it showed me every byte, every ACK/NACK, and exactly where the timing went wrong. Here's what I saw: Transaction 1-35: Normal. Sensor responds correctly to all three address bytes and all data bytes. Every acknowledge lands where it should. Clock line rises smoothly, data setup times look good. Transaction 36: The master (my Arduino) sends the sensor address and a read command. The slave (humidity sensor) starts to acknowledge. Then the clock line stops rising. It creeps up from 0V to about 2V and just... stalls. After 5 milliseconds, it finally reaches 3.3V. The slave, waiting for the clock to complete, gives up and sends a NACK. Transaction 37-47: The same stalled clock. Every transaction with this sensor gets the same slow rise-time on SCL, eventually timing out. I2C is open-drain—the master and slave both pull the clock line low, but they can't actively push it high. They rely on a pull-up resistor to let the line float back to 3.3V. If the pull-up is weak, the rise-time gets slower. If the rise-time gets too slow, the slave interprets it as a clock-stretch violation and drops the ACK. I had found the problem in one capture.

The Root Cause: Pull-Up Resistors and Bus Capacitance

The I2C bus has two lines, SDA and SCL, each with a pull-up resistor. When there's no communication, both lines are pulled high to 3.3V by these resistors. When a device wants to send a bit, it pulls the line low by connecting its open-drain output to ground. To release the line and send a 1 bit, the device stops pulling, and the pull-up resistor brings the line back up. In theory, that's quick. In practice, there's capacitance on the bus—the capacitance of the PCB traces, the pins of each device, the internal transistor junctions. That capacitance has to charge through the pull-up resistor. The charging time depends on the resistance value and the total capacitance. The standard I2C spec defines minimum and maximum rise times: 300-1000 nanoseconds for fast-mode I2C (100 kHz). If the rise time is too slow, devices interpret it as a bus error. I opened PulseView's measurement tools and measured the actual rise time from my captures: 450 microseconds. Not 450 nanoseconds. Microseconds. The clock line was taking 450,000 nanoseconds to rise from 0V to 3.3V. That's 1500 times slower than the spec allows. I checked my schematic. I had used 10 kΩ pull-up resistors. That seemed reasonable—I'd seen 10k recommended on Arduino boards. But here's the thing about I2C: resistor value depends on your total bus capacitance, which depends on trace length, the number of devices, and the quality of the PCB. The calculation is straightforward: Rise Time = 0.47 × R × C Where R is the pull-up resistance in ohms, and C is the total bus capacitance in farads. If my rise time was 450 microseconds and my resistors were 10,000 ohms, I could solve for capacitance: 450 microseconds = 0.47 × 10,000 × C C = 450 microseconds / (0.47 × 10,000) C ≈ 96 nanofarads I was carrying about 96 nF of capacitance on my I2C bus. That's a lot. Probably because I had about 80 cm of traced bus on my prototype board, plus the input capacitances of three sensor modules. To bring the rise time down to the 300 nanosecond minimum, I could either reduce the resistance or reduce the capacitance. Reducing capacitance meant rerouting the board. Reducing resistance meant swapping resistors. Let me work backward from the spec: 300 nanoseconds = 0.47 × R × 96 nanofarads R = 300 ns / (0.47 × 96 nF) R ≈ 664 ohms I needed pull-up resistors of about 660 ohms to get within spec. That's a dramatic drop from 10k. But 660 ohms would work. I didn't have any 660 ohm resistors on hand, so I grabbed a 470 ohm resistor instead—smaller is better for rise time. I soldered two 470 ohm resistors in place of the 10k resistors and hooked the logic analyzer back up.

The Fix: Two Resistors Changed Everything

With 470 ohm pull-ups and the same 96 nanofarad bus capacitance: Rise Time = 0.47 × 470 × 96 nF Rise Time ≈ 21 microseconds The logic analyzer showed 18 microseconds on the actual hardware—slower than calculated, probably because I was still underestimating the total capacitance by a few pF, but well within the 300-1000 nanosecond spec. The clock line now rose cleanly and sharply. Every I2C transaction now completed with proper ACKs. All three sensors responded on the first try. The data logger ran through 500 consecutive polls without a single dropout. I had wasted six hours writing retry logic, adjusting timeouts, and swapping out resistor values with a multimeter, trying to guess the problem. The logic analyzer showed me the exact problem in the first capture. The fix took two minutes.

Why the Oscilloscope Didn't Show This Problem

I had an oscilloscope in the lab—a 100 MHz bench scope with a Rigol probe. Before bringing out the logic analyzer, I had actually probed the I2C lines with the scope, just to see what the signals looked like. The oscilloscope showed me the clock line. It showed me the slow rise time—I could see the rounding at the rising edge instead of a sharp corner. I could measure that rise time with the scope's cursor tools, and it would have told me it was slow. If I'd spent another hour fiddling with math and the I2C spec, I might have even realized it was the pull-up resistor value. But the oscilloscope didn't decode the I2C protocol. It didn't tell me which byte caused the failure. It didn't show me the NACK or the timing relationship between the bits. It showed me the analog waveform, which is useful for measuring things—rise time, voltage, frequency. Not useful for understanding why a digital protocol is failing. The logic analyzer, by contrast, decoded the protocol completely. It showed me that the slave was sending a NACK, which told me it detected a protocol violation. It showed me that the violation occurred during a clock rise-time, which pointed to a timing problem. It gave me the actual captured data so I could verify my fix worked. Both tools were essential. The oscilloscope measured the analog properties of the signal. The logic analyzer showed me the digital meaning. Neither one was sufficient alone, and the oscilloscope couldn't have revealed the problem without the logic analyzer to show me what to look for.

Understanding Why Tools Exist and What They're Actually For

This is the real lesson I took from the project. I had thought of the oscilloscope as the "real" tool and the logic analyzer as a "hobbyist" toy. The oscilloscope was more expensive, more complex, covered a wider frequency range. But they solve fundamentally different problems. An oscilloscope measures analog signals. It shows you voltage over time. You use it when you need to know "what is the actual shape of the signal?" Frequency, rise time, overshoot, noise—these are analog questions. Does a power supply have too much ripple? Is the clock signal stable? Those are oscilloscope questions. A logic analyzer decodes digital protocols. It shows you what the data bits mean. You use it when you need to know "what did the device actually say?" What address was requested? What data was returned? Did the slave acknowledge or NACK? Where did the transaction fail? Those are logic analyzer questions. I2C is a digital protocol, but it's also analog. The protocol is defined in terms of digital logic—1s and 0s, ACK and NACK. But the physical realization is analog—voltages rising and falling on a bus. A protocol failure can have either an analog root cause (slow rise time, noise, signal integrity) or a digital root cause (wrong data, no acknowledge, bus collision). To debug it properly, you need both tools. What I learned was not to choose one over the other, but to use them in sequence. When something doesn't work, ask "is this an analog problem or a digital problem?" Use the logic analyzer to decode the protocol and identify the failure point. Use the oscilloscope to measure the analog properties of the signals at that point. Then the problem becomes obvious. For my I2C issue, the logic analyzer immediately showed me the digital failure—the NACK—and pointed me at the clock rise-time. The oscilloscope confirmed the rise-time was slow. The fix was obvious after that.

What I'd Do Differently Next Time

On my next embedded project, I'm starting with the logic analyzer, not the oscilloscope. Not because the oscilloscope isn't useful, but because protocol decoding saves so much time when things go wrong. I'd also calculate the pull-up resistor value before building. I2C rise-time is not optional. The spec exists because slow rise times cause real failures. Knowing the trace length and the number of devices, I could estimate the bus capacitance ahead of time and choose pull-up resistors that land me comfortably in the spec window. It takes five minutes and prevents hours of debugging. For this project, I'm also adding a test point on the I2C bus so I don't have to solder probes to the sensor module. That's a small PCB change that would have saved soldering time and reduced the risk of damaging the sensor while probing. And I'm keeping the 470 ohm pull-ups. They work, they're well within spec, and they're cheap. The only downside is that they draw slightly more current during idle (about 7 mA per line at 3.3V), which is negligible for a data logger running on a power supply.

The Tools That Made This Project Possible

For this debugging session, I used three instruments. Each one had a specific job. The Innomaker LA1010 is a budget logic analyzer that costs $60 and works perfectly with the free PulseView software. Ten channels means I can probe SDA, SCL, power, and ground simultaneously, plus any extra signals I want to monitor. The 100 MHz maximum sample rate is overkill for I2C—I only used 1 MHz—but it's plenty for other protocols like SPI and UART. The 10 Kpt buffer depth is where the LA1010 shows its limits: at 1 MHz, you get 10 milliseconds of capture time. For short protocol sequences, that's enough. For long captures, you'd want something bigger. But for protocol debugging, 10 milliseconds is usually exactly what you need—long enough to see a complete transaction, short enough to fit everything on one screen. The Rigol RP2200A probe gives me a way to measure the analog signal quality on my oscilloscope. At $35, it's designed to work with Rigol oscilloscopes and includes all the accessories—hook clip, ground spring, IC clip. The 200 MHz bandwidth and 10X attenuation handle everything I need for hobbyist electronics. I used it to measure the I2C rise time and confirm that the analog signal was the problem. The UNI-T UT61E+ multimeter is where I verified resistor values. At $50, it's the cheapest true-RMS meter I've found that's actually worth using. The 22,000-count display gives enough precision to measure resistor tolerances and verify that my 470 ohm replacements were actually in the right ballpark. For this project, I only used the ohms function, but the multimeter also logs data over USB, measures capacitance and frequency, and can detect continuity. It's a tool that earns its place on the bench.

Frequently Asked Questions

Why didn't my oscilloscope show this problem?

Your oscilloscope measured the analog signal—the voltage on the clock line over time. It could show you that the rise-time was slow, which is useful for measuring signal quality. But it couldn't tell you that the I2C protocol was failing, or which byte caused the failure, or that the slave was sending a NACK. Those are digital questions that require a protocol decoder. The oscilloscope is great for measuring analog signal properties; the logic analyzer is essential for understanding digital protocol failures. You need both.

What pull-up resistor value should I use?

It depends on your total bus capacitance, which depends on trace length, PCB quality, and the number of devices. The rule of thumb is: Rise Time = 0.47 × R × C. I2C spec requires rise-time between 300 and 1000 nanoseconds. If you don't know your capacitance, start with 2.2 kΩ and measure the rise-time with an oscilloscope. If it's too slow, drop the resistor value. For my project with about 96 nF of capacitance, 470 ohms gave me an 18-microsecond rise-time well within spec.

How did you know it was a timing problem?

The I2C decoder in PulseView showed me the NACK appearing at the exact same point in every failed transaction—during the clock rise-time. That pattern told me the slave was detecting a protocol violation related to timing, not a data corruption. Once I knew it was a timing problem, the oscilloscope confirmed the clock rise-time was abnormally slow. With the rise-time identified, the pull-up resistor calculation followed directly.

Did you need the 10-channel Innomaker or would 8 channels work?

Eight channels would have been sufficient for this project. I only needed SDA, SCL, power, and ground to diagnose the I2C problem. The extra channels are nice for monitoring other signals simultaneously, but not essential. The LA1010's main advantage is that it works perfectly with PulseView and costs under $60. If you only ever debug I2C and SPI, you'd be fine with a smaller logic analyzer.

Gear Mentioned in This Post

Products referenced in this article. Read our full reviews before buying.

InnoMaker LA1010

8.2/ 5

InnoMaker · $59.99

Read ReviewBuy on Amazon

Rigol RP2200A

8.5/ 5

Rigol · $34.99

Read ReviewBuy on Amazon

UNI-T UT61E+

8.8/ 5

UNI-T · $49.99

Read ReviewBuy on Amazon

Related Posts