Happy birthday!

2023-08-22 (created), 2023-08-24 (updated)

I’m writing a little hardware I²C clock stretcher (I²C, oh! Big stretch) to help me make my I²C controller implementation actually support it.

These are some moments I’ve had while doing so.

Out-of-band experience

I added a tiny UART module to help me debug it. First I emitted a < character when starting a big stretch, and a > character once we’re all relaxed.

I was going to then write the number of cycles counted during the SCL tLOW period in decimal but eventually decided, Who Really Can Be Bothered, and just shoved it out onto the UART, LSB first.

The initial test went great:

@vyx ~> tio /dev/ttyUSB1 -b9600
[15:05:11.940] tio v2.5
[15:05:11.940] Press ctrl-t q to quit
[15:05:11.941] Connected
[15:10:05.065] Switched to hexadecimal mode
3c 0f 3e 3c 3e 3c 3e 3c 3e 3c 0f 3e

3c and 3e are < and > respectively. You can see I had some fun while realizing that I need to actually take steps to restart the FSM — disabling it isn’t enough.

The I²C bus is running at 400kHz, meaning we expect the SCL low period to last 1/800,000th of a second.

The iCEBreaker the sleepy kitty is running on is running at 12MHz. At that speed, 1/800,000th of a second passes in 12,000,000/800,0001 = 15 cycles.

And we were seeing 0f in the output, 15! Perfect.

I recompiled the controller to run at 100kHz and continued the test.

3c 3c 3e 3c 3c 3e 3c 3c 3e 3c 3c 3e 3c 3c 3e 3c 3c 3e

??? I thought I made a logic error and we were somehow resetting back to the initial state without finishing measurement.

And then I said, “don’t fucking tell me,” because it’s not too hard to add 0f to itself repeatedly in your head and so 0f, 1e, 2d, 3c. Happy birthday!

At this point I promptly changed the start/stop characters to ff and fe, and then — detecting that I was just setting up the next, much larger footgun for myself — decided to dump the count one nibble at a time and thus render any byte with a non-zero high nibble officially out-of-band, and thus:

ff 0c 03 fe

Modes of measurement

The I²C controller I’m testing with/for outputs its clock at a 50% duty cycle exactly2. I probably even verified that with a logic analyzer or oscilloscope at some stage! Point is, my initial idea was to train on the SCL tLOW period, and then start holding it low for ~twice that period as of the next low, and thus stretch the clock (a LOT).

When you’re a noob like me, you may encounter this:

(image link no longer valid – alt text was: a few breadboards with various boards on them, an OLED, a few LEDs, an oscilloscope, way too many cables, a mess)

Here we have the bus only getting pulled up halfway. This is what it looks like when someone is trying to ground your bus at the same time as someone trying to put high out on it. An I²C bus is designed to be pulled high so that anyone can pull it low, but we’re seeing it driven high.

The controller needed to turn SCL’s output-enable off, to let it get pulled up high on its own, and then only to switch the output-enable on when it needs to be driven low. This lets anyone else on the bus keep SCL low, thus stretching the clock.

This worked nicely when it comes to letting the bus stretch, but today I was trying to get the measurement to come out right — I wanted to have the little debugging app that talks via UART reporting the correct I²C bus speed — and noticed that, actually, we’re getting it wrong not only because I’m failing to count correctly, but because the waveform doesn’t have a 50% duty cycle!

My poor baby oscilloscope can’t actually measure fast enough to see it, but turns out letting a pull-up resistor bring a bus high takes a little bit longer than driving it high. As a result, the tail end of the tLOW period is eating into the start of tHIGH as it shakily makes its way back up to full voltage. I absolutely need a way to see this, so I need a better scope I guess.

Anyway, I’ll also measure from rising edge to rising edge (and falling edge to falling edge), and that should give me some more insight. Logically, I expect the falling-to-falling to be very consistent, because that transition is driven, whereas the rising-to-rising might vary depending on when the signal gets high enough to be considered “high” each cycle.

I just need to not accidentally reinvent the Glasgow Interface Explorer while I’m here.

Footnotes

  1. 1/(12MHz/800kHz) = 1/((1/12,000,000)/(1/800,000)) = 12,000,000/800,000.

  2. Hey, that sounds like something I could really formally verify. I have the start of a verification setup, might as well use it.