FixTheRobot Explore Robotics / Systems Engineering / Engineering Guides / Module 04

Engineering Guides · Module 04

Wiring and configuring UART, SPI and I2C

Most failures of simple serial links are a mismatch of settings: baud, mode, voltage level, address or pull-up.

  • Intermediate
  • Module 04: Embedded Systems & Hardware Interfaces
  • Written for ROS 2 Jazzy where ROS 2 is used
  • Sources checked 2026-11-06

Assumptions: Numbers in the examples are illustrative and the code is executed by the site’s tests. Facts taken from a cited source are marked; other statements are general engineering knowledge and must be confirmed on the datasheet of the part you use.

1. Engineering problem

UART, SPI and I²C are simple to wire and unforgiving about settings. Both ends must agree exactly on speed and format, and the electrical levels and wiring must suit the bus.

2. When is this needed?

  • Connecting a GNSS receiver, IMU or small sensor board to a controller.
  • When a link works on the bench and fails on the robot.
  • When reading a datasheet’s timing and wiring diagrams.

3. Selection and design decisions

Sourced facts (Wikipedia): a UART frame sends a start bit (logic low), the data bits and one or two stop bits (logic high), so 8-N-1 is 10 line bits per byte; SPI has a clock polarity (CPOL) and phase (CPHA) that the controller must set to match the device, with an active-low slave-select line; I²C uses SDA and SCL, both bidirectional and pulled up with resistors, with a 7-bit address space and common speeds of 100 and 400 kbit/s.

Overview
BusWiresSettings that must matchTypical wiring faults
UARTTX, RX, GND (cross TX and RX)Baud rate, data bits, parity, stop bits, voltage levelTX to TX; different grounds; 3.3 V and 5 V mixed
SPISCLK, MOSI, MISO, one select per device, GNDClock polarity and phase (mode 0 to 3), clock rate, bit orderWrong mode; long wires at high clock; floating select line
I²CSDA, SCL, GND, pull-upsAddress, speed, voltageMissing or too weak pull-ups; address clash; too much bus capacitance

4. Advantages and disadvantages

  • Higher speed shortens the cable you can use.
  • Fewer wires (I²C) against slower speed and bus-sharing faults.
  • Level shifters add parts and are needed when voltages differ.

5. Requirements and constraints

  • All devices share a common ground.
  • Voltage levels match, or a level shifter is used.
  • Baud error within the receiver’s tolerance in the datasheet.

6. Architecture

controller TX ──▶ RX device      SPI: SCLK, MOSI ──▶ device, MISO ◀── device, CS per device
controller RX ◀── TX device      I²C: SDA, SCL (pull-ups to Vcc) shared by addressed devices

7. Implementation

  1. Read the datasheet for baud, mode, address and voltage.
  2. Compute the actual baud rate from the clock divider (below) and its error.
  3. Set SPI CPOL and CPHA to the device mode; mode = 2·CPOL + CPHA in the usual numbering.
  4. Wire with a common ground, correct crossing and pull-ups.
  5. Verify with a logic analyser or oscilloscope before writing the driver.
def uart_error(clock_hz, baud, oversample=16):
    div = round(clock_hz / (oversample * baud))
    actual = clock_hz / (oversample * div)
    return div, actual, (actual - baud) / baud * 100
for clk in (16e6, 12e6):
    d, a, e = uart_error(clk, 115200)
    print(f"{clk/1e6:.0f} MHz: divisor {d}, actual {a:.0f} baud, error {e:+.2f} %")
def spi_mode(cpol, cpha):
    return 2 * cpol + cpha
print("CPOL=1 CPHA=0 -> SPI mode", spi_mode(1, 0))

Printed (checked by the site’s tests):

16 MHz: divisor 9, actual 111111 baud, error -3.55 %
12 MHz: divisor 7, actual 107143 baud, error -6.99 %
CPOL=1 CPHA=0 -> SPI mode 2

Reading it: a 16 MHz clock divided by 16 × 9 gives 111 111 baud, 3.55 % below 115 200; a 12 MHz clock gives 107 143 baud, 6.99 % low. Whether that error is acceptable depends on the receiver’s tolerance, which is in its datasheet; fractional dividers or a different clock reduce it. CPOL = 1 with CPHA = 0 is SPI mode 2. Clock values are illustrative.

8. Real examples

  • GNSS on UART: the receiver’s default baud is set in its datasheet; set the controller to match and raise both together.
  • IMU on SPI: the datasheet gives the mode; set it and check a known register (such as a device-ID register) first.

9. Failure scenarios

  • Reading garbage because the baud rate error is too large or the receiver is set to another baud.
  • SPI returns all zeros or ones due to wrong mode or a floating select line.
  • I²C works with one device and fails with two (address clash or pull-ups).

10. Troubleshooting

  1. No data: swap TX and RX, check ground and voltage.
  2. Garbage: check baud and frame format.
  3. SPI wrong values: try the other modes and slower clock.
  4. I²C no acknowledge: check address, pull-ups and power.

11. Validation

  • The example prints the lines shown (executed by the site tests).
  • A known register or ID reads back correctly 1000 times in a row.

12. Engineering checklist

13. Further references

  • Wikipedia, UART, SPI and I²C, read 2026-11-06 (secondary sources; the I²C specification itself was not read).

Related on FixTheRobot