I had been fighting an I2C sensor for the better part of a fortnight, and the entire time I was debugging it blind. The microcontroller said the read had failed. The sensor's datasheet said the wiring was right. The multimeter said the pull-ups were where they should be. And in between those two facts sat a serial bus I could not see, doing something I could only guess at. So I gave in and bought a logic analyser, the cheapest one that would do the job, and it was the best twenty quid I have spent on this hobby.
the cheap clone that just works
The thing I bought is one of the ubiquitous little "Saleae-compatible" clones, an 8-channel USB unit built around a Cypress FX2 chip. The genuine Saleae kit is lovely and I am sure worth it, but the clones expose the same FX2 and the open-source world has supported them for years. You plug it in, it enumerates, and you are off.
The software is where the real value is, and the real value is free. sigrok and its GUI, PulseView, drive these analysers happily. On Linux it was a case of installing the packages and adding a udev rule so I did not have to run it as root:
# /etc/udev/rules.d/60-libsigrok.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="0925", ATTRS{idProduct}=="3881", MODE="0666"
After a udevadm control --reload and replugging the device, PulseView saw it immediately. Set the sample rate, pick how many samples to capture, hit run, poke the bus.
the moment it became a picture
The first capture was a revelation purely because it was a picture. Two channels, SDA and SCL, hooked to the two bus lines, and a sample rate comfortably above the 100 kHz the bus was running at. I triggered a read from the firmware, and there on the screen were the actual edges: clock pulses marching along, data wobbling between them, the whole conversation laid out in time.
But PulseView does the genuinely clever bit. It has protocol decoders, and the I2C decoder turns that wall of edges into something a human can read. You tell it which channel is SCL and which is SDA, and it overlays the decode right on top of the waveform: start condition, address byte, the read/write bit, the ACK or, crucially, the NACK, then the data bytes and the stop.
The bug was obvious within about thirty seconds of looking at the decoded trace. I was addressing the device at the wrong address. The datasheet listed a 7-bit address, my code was treating it as if the read/write bit was already shifted in, and the upshot was that I was calling out to a device that was not there. No device answered, so I got a NACK on the address byte, and the bus went no further. The firmware reported this as "read failed", which was technically accurate and completely unhelpful. The analyser showed me the NACK sitting right after the address, and the penny dropped.
why I should have bought one years ago
What strikes me, now that I can see the bus, is how much of my debugging up to this point had been a kind of educated superstition. I had theories about timing, about pull-up values, about clock stretching, none of which were the problem, all of which I had spent real evenings on. A multimeter tells you a line is high or low right now. An oscilloscope shows you the analogue shape, which matters when you are chasing ringing or marginal levels. But for "is the protocol doing what I think it is doing", a logic analyser with a decoder is the right tool, and it is the one I had been stubbornly doing without.
A few things I would tell my slightly-earlier self. Sample fast enough: a rule of thumb is at least four to ten times your bus clock so the edges land cleanly between samples, and faster never hurts on a short capture. Keep the leads short and the grounds solid, because these cheap units have modest input bandwidth and long flying leads pick up noise. And learn the decoders properly, because the raw waveform is satisfying but it is the decoded view that actually answers the question.
The sensor reads correctly now. The fix was one line, the address shift I had got wrong from the start. The lesson is the cheaper one and the more durable: when you are debugging a bus, stop guessing about what is on the wire and go and look. For the price of a takeaway, you can stop arguing with the datasheet and let the bus tell you what it is actually doing.