Hacker News new | past | comments | ask | show | jobs | submit login

I really don't like I2C. Yes, in principle it's pretty simple, but if you consider NACKS, slaves holding SCK low, what happens if your master resets while the slave is trying to send a 0 bit (hint: power cycle!), etc, it's so easy for the peripheral to get stuck.

SPI is much easier to write correctly, and pretty much only has the extra wire (usually not a problem) and the phase polarity issues as a negative point.




Same. I really dislike I2C, but it's universal and it's been around for decades, and it's hard to avoid designs without it. I2C keeps causing these additional issues which the article doesn't touch on:

* No way to safely bring the bus back to idle from mid-transaction. By "safely" I mean not accidentally transmit an extra byte which could e.g overwrite EEPROM memory. There is no combination of transitioning the 2-wire bus from an arbitrary state back to idle which works in the general case. If it's important, you end up adding a dedicated reset wire.

* No safe, universal way to bring the bus from tristate, or low, to pulled-up. There are designs where this ends up being necessary. You end up with a spurious transaction, which may wedge the bus, or having to add a reset wire or buffer.

* The protocol is extremely hostile to devices with non-zero latency response. It's designed as a simple "Address this register and then immediately read out a byte in the next clock cycle". Works great for trivial devices, but for anything more complex it ends up needing a bank of register acting as a "proxy" to access the higher latency side of the chip. At this point I2C is an awesomely bad choice, but people keep doing this, because it's so universal.


> No way to safely bring the bus back to idle from mid-transaction. [...] No safe, universal way to bring the bus from tristate, or low, to pulled-up.

These are great points, and I'll add a note about them in the article. Thanks!

> The protocol is extremely hostile to devices with non-zero latency response. [...]

Technically, this is what clock-stretching is for. In practice, you're right that complex devices implemented proxy registers. I've seen it on DP->MIPI bridges for example.


There is a safe, out of band way to do this that system designers can utilize. Reset all the other peripherals on the bus.


Wow


> No way to safely bring the bus back to idle from mid-transaction.

Why would you want to do that? Not having the ability to do this is part of the contract. If you design your device such that it always completes the transaction, then there should be no problem, unless one of the devices on the bus doesn't play fair but then you have a different problem.


Say you’ve asked an I2C ROM for a block read. After the first byte something, also on the bus, asserts an interrupt via a side band GPIO. I can’t read the something until the block read finishes.


The specific case I was thinking of was the host suffering an incident where it is not possible or practical for its software to know where it left off.

For example, you get a kernel panic, or soft-reset for some reason. When you recover, you now have a bus in an unknown state, possibly mid-transaction, and if you pick the wrong order in which to bring the bus back to idle, you might wedge it or accidentally cause a side-effect (e.g overwrite a byte in an EEPROM).


But doing bus communication from software is generally a bad idea. Best is to use a hardware controller dedicated to the task. Do you have examples for bus protocols that can be run from software?


I assert NACK, and the block read terminates. Then I service the interrupt.


I built a battery powered project designed to run continuously for ten years. Of all the things in that design, the I2C bus makes me the most nervous (1). Every time the MCU wakes up it has to use I2C to read from the RTC. If at any point in that ten year span the I2C bus gets jammed ...

I mitigated the issues by doing I2C resets on every read (see the Analog Devices I2C reset documentation linked in another comment) and reading multiple times to filter out any spurious bit errors.

Other than that I just have my fingers crossed that a bit doesn't accidentally flip and overwrite something in the RTC. Or that the bus somehow gets stuck driven between sleeps and drains the battery early. sigh SPI would have been so much nicer.

(1) I mean, okay, the massive lithium battery exploding is perhaps the most nerve racking component, but I2C is a close second.


I think I2C and SPI have very different use cases. Over I2C, you can interact with 127 devices with just 2 pins. To do the same with SPI, you'd need 130 (4 + an additional CS for every device on the bus).

You may think of the extra pins as not a problem, but on every product I've worked on we've been pin-limited on the MCU.


> Over I2C, you can interact with 127 devices with just 2 pins.

In practice, I don't see that many chips offering 7 bits of address configuration. You buy a chip, it has a hardwired address. Maybe a pin or two for selecting another address.


There was the design I did quite a few years ago now. Grabbed a old design, changed the board shape, put a third I2C device on. Everything powered up beautifully first go... and it was only then we worked out two of the devices from different vendors had the same I2C address. <facepalm>


That's still seven bits of address, though. If you're lucky, the hardwired part will be different enough between chips that you can still have a significant number of them on a bus.


I've yet to get above 4 devices without conflicts. Even with evenly distributed addresses, you reach about 50% chance of conflict with 13 devices because of the birthday paradox.


Agreed, but most I2C busses only have 2 or 3 devices on them. There are some boards with 16 or so devices on the same bus, but much more than that and you'd better hope you can either program their addresses or order them with a specific address, or you might end up with 2 chips with the same address.


I've worked on server hardware with dozens of devices on a bus :-). Making sure addresses were programmable was a must indeed.


This happened to me once. Used proximity infrared sensors that used I2C with a fixed address. Needed to use multiple of them. I was able to fix it with a tri-state buffer, but quite the pain to figure out why things were not working and coming up with a solution.


For that many devices on SPI, run 7 pins to an address decoder that fans out to 128. You can do this in the spare pins of an FPGA that you might have for some other purpose, and the 7 pins are cut down to one or less if you've already put much into the FPGA. For example, the FPGA provides 4096 bytes of registers (12 address bits) to the MCU but only needs 3700 registers, so use one of the spare bytes to control which SPI CS is enabled.

I've also seen JTAG as an alternative to I2C and SPI. JTAG can be part of normal operation.


Not always the case that you need the pin count, a good number of SPI devices support daisy chaining[1].

[1] https://www.maximintegrated.com/en/design/technical-document...


Cool! I've never seen this before.


The downside is of course that you need a lot of clock cycles before the data reaches the corresponding device, which makes this too inefficient for certain applications.


If you only want to select one device at a time (which is often the case) then you only need log2(devices) pins on the microcontroller because you can decode that binary number into the appropriate chip select.

Obviously that requires more hardware on the board tho'.


I don't think I've ever run more than 5 or 6 devices on an I2C bus without running into rise time, bus contention, or address collision issues. Some devices scoop tons of addresses, and most only allow re-assignment to a few alternate addresses.

I agree it's still lower pin count than SPI, but realistically you don't get anywhere near 127 devices.


this is a very good point, and i have had the same experience. its also super common for very cheap, very low end custom ics to use i2c for the same reason; keeping the pin count and package size down


Don't forget the reset lines to each device that you need to release the bus when they get locked up!


No reason why you need to reset them individually ;-).


For exactly that reason, I've encountered peripherals getting stuck during i2c transactions far too many times. At least a handful of times I've had to add boot (and sometimes runtime) logic to clear stuck transactions, due to the lack of a dedicated way to reset said peripheral.

It's quite annoying because one stuck device can block all other communication on the bus. Let's hope your reset pin (if you have it), isn't on an i2c expander on the same i2c bus.


For what it's worth, here is a good doc on I2C resets: https://www.analog.com/media/en/technical-documentation/appl...


> SPI is much easier to write correctly

I'm not sure that I buy that. A daisy chained SPI bus is a rats nest of configuration hassle and basically impossible to debug. You're trading hardware robustness for software complexity, and that's not always a win.

And as far as hardware messes: SPI doesn't synchronize anything beyond the bit level (modulo an out of band chip enable or reset, of course, which would work to "fix" an I2C device too), making shift/offset bugs a real danger.

Board-level digital interfacing is just hard. That's why we do it as little as possible and put everything on the SoC these days.


There are so many buggy I2C devices out there, I wouldn't say you're gaining hardware robustness, either.


I2C has caused me probably the most grief out of all the buses, as it's been the most painful to work with when designing the schematic (because even with only 3 devices you have to deal with address conflicts), initially writing the software (because the specification is quite lax on what can happen when, inevitably the interface between hardware and software is quite complex and a lot of it is very latency sensitive. A lot of hardware for it is quite buggy or just poorly designed as well), and in terms of ongoing bugs and issues (because of said latency sensitivity and propensity for locking up).

I try to avoid it as much as possible. If I need a peripheral, I prefer SPI, if I need a bus for connecting devices I control, I use CAN. If I'm connecting two systems together, a UART.


This sounds much like a brown-out where only half the system resets or worse, parts of it go into an undefined state. The proper way to deal with this is a complete reset/power cycle.

Granted, that's not easy to do if your peripherals have no reset pin or their power source cannot be switched by the bus master.

Would your problems go away if you had I2C plus a dedicated reset pin on all peripherals?


SMBus (which is close enough to call I2C) is also a major pain in the ass for Linux integration. If a master goes out of sync with a slave, or a poorly programmer slave tries to multi-master the bus or something, your kernel will hang and crash.


On the other hand you need a lot more pins for SPI.

One more advantage of SPI that I see is higher data rates, full-duplex communication etc..




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: