The first time I worked on an automotive-style project, I assumed I could get away with a simple UART link between two boards, the way I always had on hobby projects. I was wrong almost immediately — noisy environments, multiple nodes needing to share a single bus, and the need for guaranteed message delivery meant I had to learn CAN and LIN properly. In this article, I want to break down how embedded systems actually implement these communication protocols, from the physical wire up to the application layer.
Why Communication Protocols Matter in Embedded Systems
Most embedded systems don’t operate in isolation — they need to talk to sensors, actuators, other microcontrollers, or a central gateway. The choice of communication protocol depends on distance, speed, number of nodes, noise immunity, and cost. CAN (Controller Area Network) and LIN (Local Interconnect Network) are two of the most widely used protocols in automotive and industrial embedded systems, but the same underlying principles apply to UART, SPI, I2C, and other protocols too.
flowchart TB
A[Application Layer<br/>Sensor Data, Commands] --> B[Protocol Stack<br/>CAN/LIN Driver]
B --> C[Peripheral Controller<br/>CAN/LIN Hardware Module]
C --> D[Physical Transceiver<br/>CAN Transceiver IC]
D --> E[Physical Bus<br/>Twisted Pair Wires]
How an Embedded System Handles Communication Protocols: The General Model
Regardless of the specific protocol, an embedded system handles communication through a layered approach:
- Physical Layer — dedicated hardware (transceiver ICs) converts logic-level signals into the electrical characteristics required by the bus (differential voltage for CAN, single-wire for LIN).
- Peripheral Controller — a dedicated hardware block inside the microcontroller (like STM32’s bxCAN or FDCAN peripheral) handles bit timing, arbitration, and framing automatically, offloading this work from the CPU.
- Driver/HAL Layer — firmware that configures the peripheral registers, sets up interrupts or DMA, and exposes a simpler API to application code.
- Application Layer — the actual business logic that decides what data to send and how to interpret received data (often built on higher-level protocols like CANopen, J1939, or UDS on top of raw CAN).
Controller Area Network (CAN)
CAN was originally developed by Bosch for automotive applications and has become a standard for reliable, multi-master communication in noisy electrical environments.
CAN Physical Layer
CAN uses a two-wire differential bus (CAN_H and CAN_L), which makes it highly resistant to electromagnetic interference — a critical requirement in a vehicle full of motors, ignition systems, and switching power electronics. The bus is terminated at each end with 120-ohm resistors to prevent signal reflections.
flowchart LR
N1[Node 1: ECU] ---|CAN_H/CAN_L| BUS((CAN Bus))
N2[Node 2: Sensor] ---|CAN_H/CAN_L| BUS
N3[Node 3: Display] ---|CAN_H/CAN_L| BUS
N4[Node 4: Gateway] ---|CAN_H/CAN_L| BUS
BUS --- T1[120Ω Termination]
BUS --- T2[120Ω Termination]
CAN Frame Structure
A standard CAN 2.0A frame includes an 11-bit identifier, a control field, up to 8 bytes of data, a CRC field for error checking, and acknowledgment bits.
flowchart LR
SOF[SOF<br/>1 bit] --> ID[Identifier<br/>11 bits]
ID --> RTR[RTR<br/>1 bit]
RTR --> CTRL[Control<br/>6 bits]
CTRL --> DATA[Data Field<br/>0-8 bytes]
DATA --> CRC[CRC<br/>15 bits + delim]
CRC --> ACK[ACK<br/>2 bits]
ACK --> EOF[EOF<br/>7 bits]
Arbitration: How Multiple Nodes Share the Bus Without Collisions
One of CAN’s most elegant features is non-destructive bitwise arbitration. Every node can attempt to transmit at the same time; the bus resolves conflicts based on message identifier priority, without needing a bus master.
CAN uses “dominant” (logic 0) and “recessive” (logic 1) bit states. If two nodes transmit simultaneously, and one sends a dominant bit while another sends a recessive bit, the dominant bit wins on the physical bus. Each node monitors the bus while transmitting; if it sees a dominant bit when it sent recessive, it knows it lost arbitration and backs off, letting the higher-priority message continue uninterrupted.
// STM32 HAL example: Configuring and sending a CAN message
CAN_TxHeaderTypeDef TxHeader;
uint8_t TxData[8];
uint32_t TxMailbox;
void can_send_engine_temp(uint8_t temp_celsius) {
TxHeader.StdId = 0x100; // Message identifier - determines priority
TxHeader.RTR = CAN_RTR_DATA;
TxHeader.IDE = CAN_ID_STD;
TxHeader.DLC = 1; // 1 byte of data
TxHeader.TransmitGlobalTime = DISABLE;
TxData[0] = temp_celsius;
if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK) {
Error_Handler();
}
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
CAN_RxHeaderTypeDef RxHeader;
uint8_t RxData[8];
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK) {
if (RxHeader.StdId == 0x200) {
process_brake_status(RxData[0]);
}
}
}
Error Handling in CAN
CAN has one of the most robust built-in error detection mechanisms of any common embedded protocol:
- CRC check — detects corrupted frames.
- Bit stuffing/monitoring — a transmitting node monitors its own output; if it doesn’t match what it sent (outside arbitration), it flags a bit error.
- Form check — verifies fixed-format fields have correct values.
- ACK check — a receiving node pulls the ACK bit dominant if it received the frame correctly; if no node acknowledges, the sender knows the frame was missed.
Each node tracks a Transmit Error Counter (TEC) and Receive Error Counter (REC). If errors accumulate past thresholds, a node transitions through Error Active → Error Passive → Bus Off states, ultimately disconnecting itself from the bus if it’s misbehaving — a self-protection mechanism that prevents one faulty node from jamming the whole network.
Local Interconnect Network (LIN)
While CAN is used for critical, high-speed automotive communication, LIN is designed for simpler, lower-cost, lower-speed applications — window controls, seat adjustment, mirror controls, and similar body-electronics functions where CAN’s cost and complexity aren’t justified.
LIN Physical Layer
LIN uses a single-wire bus (plus ground), operating at speeds up to 20 kbps, far slower than CAN’s typical 500 kbps to 1 Mbps. This single-wire design significantly reduces wiring harness cost and complexity across a vehicle.
LIN Master-Slave Architecture
Unlike CAN’s multi-master arbitration, LIN uses a strict master-slave model. One master node controls all bus communication by sending “headers” that identify which slave should respond, and slave nodes simply respond when addressed.
sequenceDiagram
participant M as LIN Master
participant S1 as Slave 1 (Window Motor)
participant S2 as Slave 2 (Mirror)
M->>S1: Header (Break + Sync + ID)
S1-->>M: Response Data
M->>S2: Header (Break + Sync + ID)
S2-->>M: Response Data
// Simplified LIN master frame transmission (conceptual, register-level)
void lin_send_header(uint8_t frame_id) {
lin_send_break(); // 13+ dominant bits to signal frame start
lin_uart_write(0x55); // Sync byte for baud rate detection
lin_uart_write(frame_id | lin_calculate_parity(frame_id));
}
uint8_t lin_calculate_parity(uint8_t id) {
uint8_t p0 = ((id >> 0) ^ (id >> 1) ^ (id >> 2) ^ (id >> 4)) & 0x01;
uint8_t p1 = ~((id >> 1) ^ (id >> 3) ^ (id >> 4) ^ (id >> 5)) & 0x01;
return (p0 << 6) | (p1 << 7);
}
Why LIN Complements CAN Rather Than Replacing It
LIN is typically used as a sub-network hanging off a CAN gateway node. The gateway translates between the LIN sub-bus (for low-priority body functions) and the main CAN bus (for powertrain, safety, and higher-priority systems), keeping cost down where full CAN bandwidth isn’t needed.
flowchart TB
CANBUS((Main CAN Bus)) --- GW[Gateway ECU]
GW --- LINBUS((LIN Sub-Bus))
LINBUS --- L1[Window Motor]
LINBUS --- L2[Mirror Control]
LINBUS --- L3[Seat Position]
CANBUS --- ECU1[Engine ECU]
CANBUS --- ECU2[ABS/Brake ECU]
Other Common Embedded Communication Protocols
While CAN and LIN dominate automotive contexts, embedded systems generally use several protocol families depending on the requirement:
| Protocol | Speed | Topology | Typical Use |
|---|---|---|---|
| UART | Up to ~few Mbps | Point-to-point | Debug console, GPS modules, simple sensor links |
| I2C | Up to 3.4 Mbps (Fast+) | Multi-drop, 2-wire | Onboard sensors, EEPROMs, short distance |
| SPI | Up to tens of Mbps | Point-to-point/multi-slave | Displays, flash memory, high-speed sensors |
| CAN | Up to 1 Mbps (up to 8 Mbps CAN FD) | Multi-master bus | Automotive, industrial control |
| LIN | Up to 20 kbps | Single-master bus | Body electronics, low-cost sub-systems |
| Modbus | Varies (RS-485 based) | Master-slave | Industrial automation, PLCs |
| Ethernet/TCP-IP | 10/100/1000 Mbps | Star/switched | IoT gateways, industrial networking |
How Firmware Manages Multiple Protocol Stacks Simultaneously
In real products, a single microcontroller often needs to handle several protocols at once — for example, reading a sensor over I2C, logging over UART, and reporting over CAN. This is typically managed using an RTOS (like FreeRTOS), where each protocol’s handling runs as its own task, communicating through queues, and interrupt-driven or DMA-based peripheral drivers ensure no protocol blocks another.
// FreeRTOS task structure example for handling multiple protocols concurrently
void vCanTask(void *pvParameters) {
CanMessage_t msg;
for (;;) {
if (xQueueReceive(canRxQueue, &msg, portMAX_DELAY) == pdTRUE) {
process_can_message(&msg);
}
}
}
void vSensorI2CTask(void *pvParameters) {
for (;;) {
SensorData_t data = read_i2c_sensor();
xQueueSend(sensorDataQueue, &data, portMAX_DELAY);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void vUartLogTask(void *pvParameters) {
SensorData_t data;
for (;;) {
if (xQueueReceive(sensorDataQueue, &data, portMAX_DELAY) == pdTRUE) {
uart_log_sensor_data(&data);
}
}
}
Performance, Reliability, and Security Considerations
- Performance: CAN’s arbitration guarantees the highest-priority message always gets through first, which is why safety-critical messages (like brake commands) are assigned the lowest (most dominant) identifiers.
- Reliability: LIN’s single-master design is inherently less fault-tolerant than CAN’s distributed arbitration — if the master fails, the entire LIN sub-bus goes silent, which is acceptable for a window motor but would be unacceptable for a braking system.
- Security: Classic CAN has no built-in authentication or encryption — any node can send any message, which is why modern vehicles pair CAN with a secure gateway, message authentication codes (via CAN FD or higher-layer protocols), and intrusion detection systems to prevent spoofed messages.
Frequently Asked Questions
Q: Why is CAN used in cars instead of simpler protocols like UART? CAN allows many nodes to share a single bus reliably, has built-in error detection and prioritization, and is highly resistant to electrical noise — none of which a simple point-to-point UART link provides.
Q: Can LIN and CAN coexist on the same vehicle network? Yes, this is the standard architecture — LIN sub-networks connect to the main CAN backbone through a gateway ECU, balancing cost and performance across different vehicle systems.
Q: What happens if two CAN nodes send messages with the same identifier at the same time? This is a design error that should be avoided; if it does happen, the bus can’t distinguish between them, potentially causing message corruption. Well-designed CAN networks always allocate unique identifiers per message type.
Q: How does an embedded system prioritize which messages to send first on a shared bus? On CAN, priority is determined by the message identifier value — lower numerical values equal higher priority, and this is enforced automatically by the bitwise arbitration mechanism.
Summary
Embedded systems handle communication protocols like CAN and LIN through a layered combination of dedicated hardware peripherals, driver software, and application logic, each layer responsible for a specific job — from converting logic levels to bus voltages, to framing and error-checking messages, to deciding what data actually needs to be sent. CAN’s differential signaling and bitwise arbitration make it ideal for critical, multi-node systems, while LIN’s simpler single-wire master-slave design serves cost-sensitive, lower-priority sub-systems. Understanding these protocols at both the physical and firmware level is essential for building embedded systems that communicate reliably in real-world, electrically noisy environments.