⌁ Hardware & electronics

I2C, SPI and UART: how sensors and microcontrollers talk

The differences between I2C, SPI and UART: wires, speed, addresses and logic levels. When to choose each one and how to fix wiring problems.

As soon as you go beyond the LED and the button, you run into acronyms that appear on every datasheet: the temperature sensor speaks I2C, the display speaks SPI, the GPS module sends data over UART. They are three different ways of letting chips talk to each other on the same board or a few centimetres apart. Understanding how they work saves hours spent hunting for a fault that lies in a swapped wire or a missing resistor.

The big picture

UART I2C SPI
Signal wires 2 (TX, RX) 2 (SDA, SCL) 4 (clock, two data, select)
Shared clock no yes yes
Devices two, point to point many on the same bus many, one per select line
Addressing none address in the message dedicated select line
Typical speed low-medium low-medium high

All of them also need a common ground: without it, no signal has a reference.

UART: asynchronous serial

UART is the oldest and simplest. Two devices exchange bytes over two lines: each transmits on its own TX and receives on its own RX. There is no shared clock: both must be set to the same speed, the baud rate, and the same format (usually 8 data bits, no parity, one stop bit, abbreviated 8N1). Values such as 9600 or 115200 baud are common.

The two most frequent mistakes:

  • TX connected to TX: the wires must be crossed, one device's TX to the other's RX;
  • mismatched baud rate: the data arrives, but as meaningless characters.

UART is perfect for modules that send streams of text, such as many GPS receivers, for the debug consoles of routers and boards, and for communication between a microcontroller and a computer through a USB-serial adapter.

I2C: two wires, many devices

I2C uses a data line (SDA) and a clock line (SCL). The device in charge, called the controller, generates the clock and starts every exchange by stating the address of the device it is talking to, usually 7 bits. All sensors share the same two wires and respond only when they hear their own address.

Two features to know:

  1. Pull-up resistors are mandatory. I2C lines are "open-drain": devices can only pull them low, while the high level is provided by resistors to the supply. Many modules already have them fitted; if you connect many modules, the parallel pull-ups can become too strong.
  2. Addresses can clash. Two identical sensors have the same address. Some let you change it with a jumper; otherwise you need an I2C multiplexer.

The first diagnostic step is always a bus scan: a small program that tries every address and reports which ones answer. On Raspberry Pi there is the command i2cdetect -y 1; for Arduino, ready-made scanner sketches exist. If the sensor doesn't show up, the problem is in the wiring or the power, not in the code.

I2C is designed for short distances, on the same board or with short cables: making the wires too long increases the line's capacitance, and the signal edges round off until it becomes unreadable.

SPI: fast and simple

SPI uses a clock, one line for data out of the controller, one for data in, and a select line (chip select) for each device. The historical names are MOSI, MISO, SCK and SS; more recent documentation uses names such as COPI, CIPO and CS, or SDO and SDI.

There are no addresses: the controller pulls low the select line of the device it wants to talk to, and only that one listens. Transmission and reception happen at the same time. The result is a bus much faster than I2C, suited to displays, memory cards and fast converters.

The price is the number of wires: each extra device needs another select line. And you must set the correct mode, that is, on which clock edge data is read: it is stated in the datasheet, and getting it wrong produces shifted or random data.

Logic levels

Even before the protocol, voltage matters. Many modern sensors run at 3.3 volts, the Arduino Uno at 5 volts, the Raspberry Pi at 3.3 volts. Connecting a 5-volt output to an input that doesn't tolerate it can damage it permanently. The solutions:

  • choose modules stated to be compatible with both voltages;
  • use a bidirectional level shifter, essential for I2C;
  • for a line that only goes from 5 to 3.3 volts, a simple resistor divider may be enough.

Which one to choose

Often you don't choose: the component decides. When there is a choice, a rule of thumb:

  • I2C for many slow sensors with few wires;
  • SPI when you need speed and large amounts of data;
  • UART to connect two devices, or for a debug console.

In short

  • All three buses need a common ground.
  • UART: TX and RX crossed, the same baud rate on both sides.
  • I2C: mandatory pull-ups, unique addresses, a bus scan as the first test.
  • SPI: more wires, no addresses, high speed; mind the mode.
  • Always check logic levels before connecting 5 V and 3.3 V devices.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. With degrees in computer science and psychology, he combines technical analysis with an understanding of the human factor.

Read also