Skip to content

Real time provider serial

Peter Corke edited this page May 25, 2026 · 1 revision

Real-time provider: Serial transport setup

This page covers shared serial setup and debugging for serial-backed realtime providers.

Applies to:

  • TCLab (serial provider + driver="tclab")
  • Firmata and Telemetrix style Arduino workflows (when implemented)
  • arduIO style serial transports

Serial provider model

BDRealTime selects provider serial, then device drivers are configured in bdsim.toml.

from bdsim.realtime import BDRealTime

rt = BDRealTime(
    io_provider="serial",
    io_provider_kwargs={"config_path": "bdsim.toml"},
)

Find your serial port

macOS:

ls -1 /dev/cu.*
ls -1 /dev/tty.*

Linux / Raspberry Pi:

ls -1 /dev/ttyUSB*
ls -1 /dev/ttyACM*

Tip: unplug and replug the device and compare listings.

Python dependency

Install pyserial in the environment running bdsim:

pip install pyserial

Permission setup

macOS

Most boards appear as /dev/cu.usb* and are often already usable.

If you get permission denied:

sudo chmod 666 /dev/cu.usbserial-XXXX

This is temporary and resets on reconnect/reboot.

Linux and Raspberry Pi OS

Add your user to dialout (and sometimes tty depending on distro):

sudo usermod -a -G dialout $USER
# optional on some systems
sudo usermod -a -G tty $USER

Then log out/in, or reboot.

Check permissions:

ls -l /dev/ttyACM0
ls -l /dev/ttyUSB0

Typical bdsim.toml structure

[io]
config_version = 1
default_provider = "serial"

[providers.serial]
type = "serial"
shutdown_on_signal = true
safe_shutdown_timeout_s = 0.5

[devices.dev0]
provider = "serial"
driver = "tclab"
port = "/dev/ttyACM0"
baud = 115200
startup_probe = true
startup_timeout_s = 2.0

Device-specific channel sections are covered in each provider/driver page.

Bring-up debug sequence

  1. Confirm device appears in /dev/*.
  2. Confirm permissions allow your user to open the port.
  3. Confirm no other process has the port open.
  4. Confirm baud and newline/command format for the device.
  5. Run a direct pyserial smoke test before launching bdsim.

Example smoke test:

python - <<'PY'
import serial
port = '/dev/ttyACM0'
with serial.Serial(port, 115200, timeout=1) as s:
    s.write(b'VER\n')
    print(s.readline().decode(errors='replace').strip())
PY

Common failure modes

  1. No such file or directory

Wrong port path or device disconnected.

  1. Permission denied

Fix user permissions/groups as above.

  1. Read timeout

Wrong baud, wrong command format, wrong firmware, or no newline terminator.

  1. Device resets on connect

Many Arduino-class devices reset on DTR transition when serial opens. Handle startup handshake accordingly.

USB serial latency and achievable sample rates

USB CDC serial (used by most Arduino boards) introduces a round-trip latency of roughly 30–70 ms per transaction on macOS, regardless of baud rate. This dominates the per-tick eval time and sets an upper bound on the usable sample rate.

Why it happens

A serial round-trip (send command → read response) goes through two USB transfers. Each transfer is subject to the USB host's polling interval and any additional buffering in the OS driver:

  • The Arduino firmware's CDC TX timeout flushes a USB packet after ~3 ms.
  • The macOS com.apple.driver.usb.cdc driver adds its own read latency on top.
  • On an M-series Mac Mini, observed round-trip times are ~35–65 ms per transaction.

Minimise transactions per tick

The bdsim serial provider uses fire-and-forget writes for analog outputs: the heater command is sent but the firmware echo is not read back. A reset_input_buffer() call discards the pending echo before the next read. This reduces a two-transaction tick (T1 read + Q1 write-with-echo) to a single round-trip, cutting mean eval time from ~130 ms to ~40 ms.

Practical maximum sample rates on macOS (Arduino UNO, 1 temperature read per tick):

Mean eval time Safe clock rate
~130 ms (read + write-with-echo) ≤ 5 Hz
~40 ms (read + fire-and-forget) ≤ 20 Hz

At 10 Hz (100 ms period) with ~40 ms eval time there is comfortable headroom; 20 Hz (50 ms period) is marginal — watch the overrun count in the stats.

Can the USB latency be reduced?

On macOS: No straightforward method. The macOS USB CDC driver exposes no latency-timer API; pyserial has no hook for it. The Arduino UNO's ATmega16U2 firmware hardcodes the USB endpoint interval — changing it requires reflashing the 16U2.

On Linux: Boards using an FTDI chip expose a latency timer:

cat /sys/bus/usb-serial/devices/ttyUSB0/latency_timer   # default 16
echo 1 | sudo tee /sys/bus/usb-serial/devices/ttyUSB0/latency_timer

Reducing from 16 ms to 1 ms brings round-trip latency down to ~5 ms, enabling 100 Hz+ sample rates.

Arduino Leonardo / Micro / Pro Micro (native USB): The CDC TX timeout is set in the ATmega32U4 firmware and can be patched, but requires firmware modification.

References

Clone this wiki locally