Python Serial Communication — Core Concepts

What Serial Communication Is

Serial communication sends data one bit at a time over a wire. It is one of the oldest and most universal ways for computers to talk to external devices. Despite being decades old, serial remains the primary interface for Arduino boards, CNC machines, 3D printers, GPS modules, industrial PLCs, scientific instruments, and embedded systems.

Most modern “serial” connections actually use USB, which emulates a traditional serial port. When you plug in an Arduino, the operating system creates a virtual serial port that applications use exactly like a physical RS-232 connection.

Key Parameters

Both sides of a serial connection must agree on these settings:

  • Baud rate — Transmission speed in bits per second. Common values: 9600, 19200, 38400, 57600, 115200. The most common default is 9600 for simple devices and 115200 for faster ones.
  • Data bits — Bits per character. Almost always 8.
  • Stop bits — Marks the end of each character. Usually 1.
  • Parity — Error detection bit. Usually None for USB connections.
  • Flow control — Prevents buffer overflow. None for most hobbyist connections, hardware (RTS/CTS) for industrial equipment.

The shorthand “9600 8N1” means 9600 baud, 8 data bits, No parity, 1 stop bit.

The pyserial Library

pyserial is the standard Python library for serial communication:

pip install pyserial

Basic Read and Write

import serial
import time

# Open the serial port
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
time.sleep(2)  # wait for Arduino to reset after connection

# Send a command
ser.write(b'LED_ON\n')

# Read the response
response = ser.readline().decode('utf-8').strip()
print(f"Arduino says: {response}")

ser.close()

The timeout=1 parameter means readline() will wait up to 1 second for data. Without a timeout, it blocks forever if no data arrives.

Finding Available Ports

from serial.tools import list_ports

ports = list_ports.comports()
for port in ports:
    print(f"{port.device}: {port.description} [{port.hwid}]")

This helps you discover which port your device is on, especially when the port name changes between connections.

Context Manager Usage

import serial

with serial.Serial('/dev/ttyUSB0', 115200, timeout=1) as ser:
    ser.write(b'STATUS\n')
    response = ser.readline()
    print(response.decode().strip())
# Port automatically closed when exiting the block

Reading Strategies

Serial data arrives as a continuous stream. How you read it depends on what the device sends:

Line-Based Reading

Most devices end messages with a newline character:

while True:
    line = ser.readline().decode('utf-8').strip()
    if line:
        print(f"Received: {line}")

Fixed-Size Reading

Some protocols send fixed-length packets:

# Read exactly 10 bytes
data = ser.read(10)

Delimiter-Based Reading

Read until a specific byte sequence:

data = ser.read_until(b'\r\n')  # read until carriage return + newline

Non-Blocking Check

Check if data is available without blocking:

if ser.in_waiting > 0:
    data = ser.read(ser.in_waiting)
    process(data)

Practical Example: Arduino Sensor Logger

Python side:

import serial
import csv
import time

with serial.Serial('/dev/ttyUSB0', 9600, timeout=2) as ser:
    time.sleep(2)  # Arduino reset delay
    
    with open('sensor_log.csv', 'w', newline='') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(['timestamp', 'temperature', 'humidity'])
        
        while True:
            line = ser.readline().decode('utf-8').strip()
            if line:
                parts = line.split(',')
                if len(parts) == 2:
                    timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
                    writer.writerow([timestamp] + parts)
                    csvfile.flush()
                    print(f"{timestamp}: temp={parts[0]}, humidity={parts[1]}")

Arduino side sends: 23.5,65.2\n every second.

Common Misconception

A frequent mistake is opening the serial port and immediately sending data. Many devices (especially Arduino) reset when a serial connection is established. The bootloader runs for 1-2 seconds before your sketch starts. If you send data during this window, the device misses it. Always add a short delay after opening the port:

ser = serial.Serial('/dev/ttyUSB0', 9600)
time.sleep(2)  # wait for device to be ready
ser.write(b'START\n')

Troubleshooting Common Issues

  • Permission denied on Linux — Add your user to the dialout group: sudo usermod -a -G dialout $USER, then log out and back in
  • Garbled text — Baud rate mismatch. Both sides must use the same value.
  • No response — Check ser.in_waiting to verify data is arriving. Verify the correct port with list_ports.
  • Port busy — Another program (Arduino IDE serial monitor) has the port open. Only one program can use a serial port at a time.

One thing to remember: Serial communication with pyserial is deceptively simple — open a port, read and write bytes — but reliable communication requires matching parameters, handling timeouts, and respecting device startup timing.

pythonserialhardwarecommunication

See Also

  • Python Behavior Trees Robotics How robots make decisions using a tree-shaped rulebook that keeps them organized, like a flowchart that tells a robot what to do in every situation.
  • Python Bluetooth Ble How Python connects to fitness trackers, smart locks, and wireless sensors using the invisible radio signals all around you.
  • Python Circuitpython Hardware Why CircuitPython makes wiring up LEDs, sensors, and motors as easy as plugging in a USB drive.
  • Python Computer Vision Autonomous How self-driving cars use cameras and Python to see the road, spot pedestrians, read signs, and understand traffic — like giving a car human eyes and a brain.
  • Python Home Assistant Automation How Python turns your home into a smart home that reacts to you automatically, like a helpful invisible butler.