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:
- 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.
- 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.