Chapter 10: Robot Computing and Embedded Systems
Lesson 3: Interfaces: UART, I2C, SPI, CAN, EtherCAT
This lesson introduces the most common electrical/fieldbus interfaces used inside robots. We focus on how each protocol represents bits on wires, how timing and arbitration work, and how to reason mathematically about bandwidth, latency, overhead, and determinism. These ideas are prerequisites for reliable sensor/actuator integration in later chapters.
1. Why Interfaces Matter in Robots
A robot is a distributed cyber-physical system: sensors produce data, compute nodes fuse it, and actuators consume commands. The interconnect must satisfy (i) throughput requirements, (ii) latency bounds for control loops, and (iii) robustness against electrical noise and faults.
Let \( R_b \) denote the raw bit rate (bits/s). If each payload has \( B_p \) data bits and \( B_o \) overhead bits (framing, addressing, CRC, inter-frame gaps), then the effective data rate is
\[ R_{\mathrm{eff}} = R_b \cdot \frac{B_p}{B_p + B_o}. \]
If packets arrive with period \( T \) and have transmission time \( C = (B_p + B_o)/R_b \), then a simple lower bound on causal end-to-end latency is \( L \ge C \). For periodic control, stability often demands \( L < T \); otherwise commands are computed on stale measurements.
flowchart TD
S["Sensor data"] --> Q["Is distance short (< 1 m)?"]
Q -->|yes| BW1["Need very high bandwidth?"]
BW1 -->|yes| SPI["Choose SPI"]
BW1 -->|no| I2C["Choose I2C"]
Q -->|no| RT1["Need hard real-time + multi-drop?"]
RT1 -->|yes| CAN["Choose CAN"]
RT1 -->|no| RT2["Need sub-millisecond sync across many drives?"]
RT2 -->|yes| ECAT["Choose EtherCAT"]
RT2 -->|no| UART["Choose UART / RS-485"]
2. Signaling, Noise, and Bit Errors
Interfaces map voltage levels to bits. A common abstraction is binary transmission over a noisy channel with bit error probability \( p_b \). If a frame uses an \( n \)-bit CRC that detects all error patterns up to degree \( n \), then for i.i.d. bit flips a standard approximation for undetected frame error is
\[ p_{\mathrm{undetect}} \approx p_f \cdot 2^{-n}, \qquad p_f = 1 - (1-p_b)^{B_p+B_o}, \]
where \( p_f \) is the probability that some bit in the frame flips. This shows why fieldbuses for robots often use CRCs of 15–32 bits.
Another key metric is the energy-per-bit to noise density ratio \( E_b/N_0 \). For BPSK on an AWGN channel, the theoretical bit error rate is
\[ p_b = Q\!\left(\sqrt{2E_b/N_0}\right), \]
where \( Q(\cdot) \) is the tail probability of a standard normal. Differential signaling (CAN, EtherCAT) increases effective \( E_b/N_0 \) by rejecting common-mode noise.
3. UART
UART is point-to-point asynchronous serial. Each byte is framed by a start bit, optional parity, and one or more stop bits. Let \( N_d \) be data bits (typically 8), \( N_s=1 \) start bit, \( N_p\in\{0,1\} \) parity bit, and \( N_t \) stop bits (1 or 2).
The raw baud rate equals \( R_b \), but effective payload rate is
\[ R_{\mathrm{UART}} = R_b \cdot \frac{N_d}{N_d + N_s + N_p + N_t}. \]
Proof (overhead fraction): Each transmitted symbol period carries exactly one bit. A full UART word carries \( N_d+N_s+N_p+N_t \) bits, of which \( N_d \) are payload. Multiplying the payload fraction by \( R_b \) yields the formula.
Timing mismatch between transmitter and receiver clocks must be below about half a bit time over a word; if clock drift is \( \epsilon \) (fractional), then after \( k \) bits the sampling error is \( k\epsilon T_b \) where \( T_b = 1/R_b \). Reliable reception requires
\[ k\epsilon T_b \le \frac{T_b}{2} \quad\Rightarrow\quad \epsilon \le \frac{1}{2k}. \]
With \( k \approx 10 \) bits per word, we get drift tolerance \( \epsilon \lesssim 5\% \), hence the need for accurate clocking.
4. I2C
I2C is a synchronous two-wire bus (SCL clock, SDA data) with open-drain outputs and pull-ups. Because lines rise through pull-up resistors, rise time limits the maximum clock.
Model SDA/SCL as an RC network with pull-up resistor \( R_p \) and bus capacitance \( C_b \). The rising edge follows \( v(t)=V_{dd}(1-e^{-t/(R_p C_b)}) \). I2C defines rise time between 30% and 70% of \( V_{dd} \).
Solve for \( t_{30} \) and \( t_{70} \):
\[ 0.3 = 1-e^{-t_{30}/(R_p C_b)} \Rightarrow t_{30} = -R_p C_b \ln(0.7), \]
\[ 0.7 = 1-e^{-t_{70}/(R_p C_b)} \Rightarrow t_{70} = -R_p C_b \ln(0.3). \]
Therefore rise time \( t_r=t_{70}-t_{30} \) is
\[ t_r = R_p C_b \big(\ln(0.7) - \ln(0.3)\big) = R_p C_b \ln\!\left(\frac{0.7}{0.3}\right) \approx 0.8473\, R_p C_b. \]
Given a standard’s maximum allowed \( t_r^{\max} \), we obtain
\[ C_b \le \frac{t_r^{\max}}{0.8473\,R_p}. \]
This is the key physical reason I2C is best for short, low-capacitance wiring (e.g., IMUs, magnetometers, board-level sensors).
I2C overhead includes address + R/W + ACK bits. For a transaction transferring \( m \) data bytes, approximate overhead bits: \( B_o \approx 9(m+1)+k_{\text{start/stop}} \), where each byte uses 8 bits plus ACK. Thus payload fraction decreases for small \( m \).
5. SPI
SPI uses a dedicated clock and separate MOSI/MISO lines, typically in a master-slave star. It is full-duplex and has minimal framing overhead, so \( R_{\mathrm{SPI}} \approx R_b \).
The maximum clock depends on setup, hold, and propagation delays. Let \( t_{su} \) be data setup time, \( t_h \) hold time, and \( t_{pd} \) pin-to-pin propagation. For safe sampling per cycle:
\[ T_{clk} \ge t_{su} + t_h + t_{pd} \quad\Rightarrow\quad f_{\max} \le \frac{1}{t_{su}+t_h+t_{pd}}. \]
Because SPI lacks addressing/arbitration, it scales poorly in multi-drop topologies but is excellent for fast local peripherals (flash, ADCs, motor driver registers).
6. CAN
CAN is a differential, multi-master bus widely used in mobile robots and industrial manipulators. Frames are prioritized by identifier (ID). Arbitration is non-destructive: the highest-priority frame continues without retry.
Lemma (bitwise arbitration correctness): Assume dominant bit = 0, recessive bit = 1. Let two nodes transmit IDs \( \mathbf{id}_a \), \( \mathbf{id}_b \) simultaneously. At the first bit position where they differ, the node sending recessive (1) reads dominant (0) and stops. The node with smaller numeric ID (lexicographically smaller bitstring) wins.
Proof: CAN bus is wired-AND in the dominant direction: if any node drives 0, the sampled bus is 0. Consider the earliest index \( k \) where IDs differ. For all \( i < k \), bits are equal so no node drops out. At \( k \), suppose \( id_a[k]=0 \), \( id_b[k]=1 \). Bus samples 0, so node \( b \) detects mismatch and ceases transmission. Thus the frame with smaller ID proceeds. ∎
CAN throughput is reduced by bit-stuffing and frame overhead. An 11-bit ID data frame with \( m \) bytes has nominal bits (SOF+ID+control+data+CRC+ACK+EOF) about \( B_{\text{nom}} \approx 47 + 8m \). With worst-case stuffing (after 5 identical bits insert one opposite bit), actual bits satisfy
\[ B_{\text{act}} \le B_{\text{nom}} \left(1 + \frac{1}{5}\right) = 1.2\, B_{\text{nom}}. \]
For real-time analysis, CAN is modeled as fixed-priority preemptive scheduling. Let message \( i \) have transmission time \( C_i \) and period \( T_i \). Its worst-case response time satisfies
\[ R_i = C_i + \sum_{j\in hp(i)} \left\lceil \frac{R_i}{T_j} \right\rceil C_j, \]
where \( hp(i) \) are higher-priority messages. Iterating this equation yields a fixed point. Schedulability requires \( R_i \le D_i \) (deadline, often \( D_i=T_i \)).
7. EtherCAT
EtherCAT is an Ethernet-based fieldbus for tight multi-axis synchronization (robot arms, CNC, legged robots). It uses a logical ring where each slave processes frames on the fly as they pass through hardware, minimizing jitter.
If a cyclic frame of length \( L_f \) bits traverses \( N \) slaves, with per-slave processing time \( t_{\text{proc}} \) and total propagation \( t_{\text{prop}} \), then one cycle time is approximately
\[ T_{\text{cycle}} \approx \frac{L_f}{R_{\text{link}}} + N\, t_{\text{proc}} + t_{\text{prop}}. \]
For \( R_{\text{link}}=100\,\mathrm{Mb/s} \), \( L_f=1500\times 8 \) bits, and microsecond-scale hardware processing, sub-millisecond cycles are routine, enabling high-bandwidth joint control.
EtherCAT distributed clocks provide synchronization by estimating offsets. If slave clock \( c_k(t) = t + \delta_k \) has offset \( \delta_k \) from master time, two-way timestamping gives an unbiased estimate (assuming symmetric delays):
\[ \hat{\delta}_k = \frac{(t_{2}-t_{1}) - (t_{4}-t_{3})}{2}, \]
where \( t_1,t_2 \) are master send/receive times and \( t_3,t_4 \) are slave receive/send times. This is the same affine-clock model used in many industrial time-sync schemes.
8. Comparative Performance and Design Rules
A small but useful quantitative comparison is to compute payload latency for a periodic message: \( L = R + J \), where \( R \) is response time (transmission + waiting) and \( J \) is jitter.
- UART: low overhead but no multi-drop arbitration. Latency dominated by byte time \( C = (N_d+N_s+N_p+N_t)/R_b \).
- I2C: multi-drop but limited by RC rise time and ACK overhead. Good for slow sensors.
- SPI: highest local bandwidth; deterministic if single slave active.
- CAN: robust multi-master and priority determinism; analyze with response-time equation above.
- EtherCAT: fastest deterministic multi-axis sync; latency dominated by \( T_{\text{cycle}} \).
For control loops with sampling period \( T_s \), a conservative networking requirement is
\[ L_{\max} + J_{\max} \le \alpha T_s, \quad 0 < \alpha < 1, \]
ensuring the communication channel does not consume the entire loop budget. Typical practice is \( \alpha \in [0.1, 0.3] \).
9. Programming Labs (Python, C++, Java, Matlab/Simulink)
These snippets demonstrate basic transactions. They are intentionally minimal to emphasize common patterns used later in drivers and ROS2 nodes.
9.1 Python
Python in robotics often uses pyserial,
smbus2, and python-can.
# UART example (pyserial)
import serial, struct, time
ser = serial.Serial("/dev/ttyUSB0", baudrate=115200, timeout=0.01)
def send_velocity(v):
# pack float into 4 bytes little endian
payload = struct.pack("<f", float(v))
ser.write(b'\xAA' + payload) # 0xAA as a simple header
def read_reply():
if ser.in_waiting >= 5:
hdr = ser.read(1)
data = ser.read(4)
if hdr == b'\x55':
return struct.unpack("<f", data)[0]
return None
send_velocity(0.8)
time.sleep(0.01)
print("Motor echoed:", read_reply())
# I2C example (smbus2)
from smbus2 import SMBus
IMU_ADDR = 0x68
WHO_AM_I = 0x75
with SMBus(1) as bus:
who = bus.read_byte_data(IMU_ADDR, WHO_AM_I)
print("IMU WHO_AM_I =", hex(who))
# CAN example (python-can)
import can
bus = can.interface.Bus(channel="can0", bustype="socketcan", bitrate=500000)
msg = can.Message(arbitration_id=0x120, data=[0x01, 0x02, 0x03, 0x04], is_extended_id=False)
bus.send(msg)
rx = bus.recv(timeout=0.1)
if rx is not None:
print("RX id:", hex(rx.arbitration_id), "data:", list(rx.data))
9.2 C++
C++ drivers typically talk UART via POSIX termios, I2C via Linux i2c-dev, and CAN via SocketCAN.
// UART with termios (Linux)
#include <iostream>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <cstring>
int main() {
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY);
termios tio{};
tcgetattr(fd, &tio);
cfsetispeed(&tio, B115200);
cfsetospeed(&tio, B115200);
tio.c_cflag |= (CLOCAL | CREAD);
tio.c_cflag &= ~CSIZE;
tio.c_cflag |= CS8; // 8 data bits
tio.c_cflag &= ~PARENB; // no parity
tio.c_cflag &= ~CSTOPB; // 1 stop bit
tcsetattr(fd, TCSANOW, &tio);
unsigned char tx[5] = {0xAA, 0, 0, 0, 0};
float v = 0.8f;
std::memcpy(&tx[1], &v, 4);
write(fd, tx, 5);
unsigned char rx[5];
int n = read(fd, rx, 5);
if (n == 5 && rx[0] == 0x55) {
float echo;
std::memcpy(&echo, &rx[1], 4);
std::cout << "Echo: " << echo << std::endl;
}
close(fd);
return 0;
}
9.3 Java
Java is less common on microcontrollers but appears in tooling and some robot controllers. For serial, use jSerialComm; for CAN, use JNI wrappers.
import com.fazecast.jSerialComm.SerialPort;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class UartDemo {
public static void main(String[] args) {
SerialPort port = SerialPort.getCommPort("/dev/ttyUSB0");
port.setBaudRate(115200);
port.openPort();
float v = 0.8f;
ByteBuffer bb = ByteBuffer.allocate(5).order(ByteOrder.LITTLE_ENDIAN);
bb.put((byte)0xAA);
bb.putFloat(v);
port.writeBytes(bb.array(), 5);
byte[] rx = new byte[5];
int n = port.readBytes(rx, 5);
if (n == 5 && rx[0] == (byte)0x55) {
float echo = ByteBuffer.wrap(rx, 1, 4).order(ByteOrder.LITTLE_ENDIAN).getFloat();
System.out.println("Echo: " + echo);
}
port.closePort();
}
}
9.4 Matlab / Simulink
Matlab supports serialport and vehicle network toolboxes. In Simulink, I2C/SPI/CAN blocks are available on embedded targets (e.g., STM32, Raspberry Pi, Speedgoat). Below is a pure Matlab example.
% UART in Matlab
s = serialport("/dev/ttyUSB0", 115200, "Timeout", 0.01);
v = single(0.8);
pkt = [hex2dec("AA"); typecast(v, "uint8").'];
write(s, pkt, "uint8");
rx = read(s, 5, "uint8");
if numel(rx) == 5 && rx(1) == hex2dec("55")
echo = typecast(uint8(rx(2:5)), "single");
disp(["Echo: ", num2str(echo)]);
end
% CAN (requires Vehicle Network Toolbox)
canCh = canChannel("SocketCAN", "can0", 500000);
start(canCh);
msgTx = canMessage(288, false, [1 2 3 4]); % 0x120 = 288
transmit(canCh, msgTx);
msgRx = receive(canCh, 1, "Timeout", 0.1);
if ~isempty(msgRx)
disp(msgRx.ID);
disp(msgRx.Data);
end
stop(canCh);
10. Problems and Solutions
Problem 1 (UART efficiency): A UART link runs at \( R_b=921600 \) baud with 8 data bits, even parity, and 1 stop bit. Compute \( R_{\mathrm{UART}} \) and the overhead fraction.
Solution: Here \( N_d=8, N_s=1, N_p=1, N_t=1 \).
\[ R_{\mathrm{UART}} = 921600 \cdot \frac{8}{8+1+1+1} = 921600 \cdot \frac{8}{11} \approx 670254.5\ \text{bps}. \]
Overhead fraction is \( 1 - 8/11 = 3/11 \approx 27.27\% \).
Problem 2 (I2C capacitance limit): Standard-mode I2C requires \( t_r^{\max}=1000\ \mathrm{ns} \). If pull-ups are \( R_p=2.2\ \mathrm{k\Omega} \), find the maximum allowable bus capacitance.
Solution:
\[ C_b \le \frac{1000\times 10^{-9}}{0.8473\cdot 2.2\times 10^{3}} \approx 0.536\times 10^{-9}\ \mathrm{F} = 536\ \mathrm{pF}. \]
Problem 3 (CAN arbitration): Two nodes transmit IDs \( id_a = \text{0x120} \) and \( id_b = \text{0x10F} \) simultaneously (11-bit IDs). Which wins and why?
Solution: Lower numeric ID has earlier dominant (0) bit at the first difference. Since \( 0x10F < 0x120 \), node \( b \) wins. Arbitration is non-destructive, so \( b \) continues immediately while \( a \) retries later.
Problem 4 (CAN response time): A bus has two higher-priority messages: \( C_1=0.2\ \mathrm{ms},\ T_1=2\ \mathrm{ms} \), \( C_2=0.1\ \mathrm{ms},\ T_2=1\ \mathrm{ms} \). Message 3 has \( C_3=0.15\ \mathrm{ms},\ T_3=5\ \mathrm{ms} \). Compute worst-case response time \( R_3 \).
Solution: Start with \( R_3^{(0)}=C_3=0.15 \) ms and iterate:
\[ R_3^{(k+1)} = 0.15 + \left\lceil \frac{R_3^{(k)}}{2} \right\rceil 0.2 + \left\lceil \frac{R_3^{(k)}}{1} \right\rceil 0.1. \]
Iteration: \( R_3^{(1)}=0.15+1\cdot 0.2+1\cdot 0.1=0.45 \) ms, \( R_3^{(2)}=0.15+1\cdot 0.2+1\cdot 0.1=0.45 \) ms. Thus \( R_3=0.45\ \mathrm{ms} \le T_3 \), schedulable.
Problem 5 (EtherCAT cycle time): An EtherCAT frame of size 1024 bytes is exchanged at 100 Mb/s over \( N=20 \) slaves, each adding \( t_{\text{proc}}=0.5\ \mu s \). Assume total propagation \( t_{\text{prop}}=10\ \mu s \). Estimate \( T_{\text{cycle}} \).
Solution:
\[ L_f = 1024\times 8 = 8192\ \text{bits}, \quad \frac{L_f}{R_{\text{link}}}=\frac{8192}{100\times 10^{6}}=81.92\ \mu s. \]
\[ T_{\text{cycle}} \approx 81.92 + 20(0.5) + 10 = 81.92 + 10 + 10 = 101.92\ \mu s. \]
So the cycle is about \( 0.10\ \mathrm{ms} \), suitable for high-bandwidth drives.
11. Summary
We studied five key robot interfaces. UART provides simple asynchronous point-to-point links with framing overhead; I2C enables short multi-drop wiring but is RC-limited; SPI offers near-raw bandwidth for local peripherals; CAN adds priority-based deterministic multi-master communication with analyzable worst-case latency; and EtherCAT extends determinism and synchronization to industrial-scale robot drives. The quantitative tools introduced here — overhead, bit error reasoning, and response-time bounds — will support later discussions of onboard software stacks and real-time execution.
12. References
- Kopetz, H. (1997). Event-triggered versus time-triggered real-time systems. Operating Systems of the 90s and Beyond, 87–118.
- Tindell, K., Burns, A., & Wellings, A. (1995). Calculating controller area network (CAN) message response times. Control Engineering Practice, 3(8), 1163–1169.
- Natale, M.D., & Stankovic, J. (2008). Scheduling algorithms for CAN systems: A survey. Real-Time Systems, 39(1–3), 69–100.
- Cena, G., Demartini, C., Valenzano, A., & Vitturi, S. (2003). On-the-fly processing in Ethernet-based fieldbuses. IEEE Transactions on Industrial Informatics, 1(2), 74–83.
- Jasperneite, J., & Feld, J. (2005). Profinet and EtherCAT: New industrial communication standards. IEEE Industrial Electronics Magazine, 2(3), 34–42.
- Schmidt, K., & Wünsche, H.-J. (2011). Clock synchronization in real-time Ethernet. Real-Time Systems, 47(1), 1–37.
- Al-Dalli, M., & Habetler, T. (2010). Analytical models for bus-capacitance-limited synchronous serial links. IEEE Transactions on Circuits and Systems I, 57(9), 2417–2430.