Set up a serial port for CODESYS on Linux

6 min read · Published November 3, 2026

Find the Linux device node with dmesg | grep tty, add it to the SysCom section of the runtime's CODESYSControl.cfg as a Linux.Devicefile entry, restart the runtime, and check the link with a serial example. The device node is the port name on Linux; a COM number is not.

You get the serial port by naming it the way Linux names it. Log in to the board, run dmesg | grep tty, take the device node the kernel prints (for example /dev/ttyUSB0), put that node in the [SysCom] section of /etc/CODESYSControl.cfg, restart the runtime, and check the link with a serial example. A Windows-style COM number names nothing here. On Linux the device node is the port name. The job runs about 15 minutes from shell access to a working link.

What do I need before I start?

You need shell access to the board, the CODESYS runtime installed and running, the runtime configuration file at /etc/CODESYSControl.cfg, the serial device wired or plugged in, and the baud rate, data bits, parity and stop bits from that device's manual.

Get these on the bench:

  • Shell access, by keyboard and monitor or over SSH.
  • The runtime up before you touch configuration. If it is down, check whether the CODESYS runtime is running or stopped first.
  • Port hardware. Two honest options: a USB-to-RS-232 adapter, which Linux enumerates on its own as /dev/ttyUSB0, or the 3.3 V UART on the header, which needs a level shifter to drive RS-232 equipment.
  • The peer device's manual page with its serial settings.
  • A loopback means: a jumper wire, or a paper clip for a DB9.

Safety line, once: the header UART is 3.3 V logic and RS-232 lines swing up to ±15 V. Never wire an RS-232 line straight to the header pins; that kills the input. Use the USB adapter or a proper 3.3 V level shifter.

How do I find which serial port Linux gives me?

Run dmesg | grep tty in a shell and read the lines the kernel printed when it registered the port: a USB serial adapter shows as /dev/ttyUSB0, the header UART as /dev/ttyAMA0 or /dev/ttyS0.

The clean way, five minutes: run the command with the adapter unplugged, plug it in, then run dmesg | tail -20. The new line ending in 'attached to ttyUSB0' names your port. Confirm the node exists with ls -l /dev/ttyUSB*. If you run more than one USB adapter, the plain numbers can swap when plug order changes; the names under /dev/serial/by-id/ stay with the physical adapter across reboots, so put one of those in the configuration instead.

Where does the device node go in the CODESYS configuration?

In the [SysCom] section of /etc/CODESYSControl.cfg, one line per port: Linux.Devicefile.1=/dev/ttyUSB0; the application then opens that node as SYS_COMPORT1, the next entry as SYS_COMPORT2, and the entry numbers you pick must match the port constants your example opens.

Edit it in this order:

  1. Back the file up: sudo cp /etc/CODESYSControl.cfg /etc/CODESYSControl.cfg.bak.
  2. Open it: sudo nano /etc/CODESYSControl.cfg.
  3. Find [SysCom]; add the section header if it is missing, then your line.
  4. Save and close. Two ports look like this:
[SysCom]
Linux.Devicefile.1=/dev/ttyUSB0
Linux.Devicefile.2=/dev/ttyAMA0

The vendor's SysCom FAQ page prints this syntax and the numbering for the runtime; keep it open while you edit and follow it if your runtime version numbers entries differently.

How do I make the runtime load the new setting?

Restart the runtime service so it reads the edited file: sudo systemctl restart codesyscontrol, then confirm the runtime came back up and shows no configuration error in its log.

The runtime reads the file at start, so an edit with no restart changes nothing. If a typo stops the runtime from coming up, restore the backup with sudo cp /etc/CODESYSControl.cfg.bak /etc/CODESYSControl.cfg and restart again.

How do I test the port before writing logic?

Close the loop on the connector first: bridge TX to RX on the port (pins 2 and 3 on a DB9), open SYS_COMPORT1 at the device's settings, send one short string, and the same string must come back.

The loopback proves the whole chain from the CODESYS application to the connector with nothing else in the circuit. Remove the jumper and wire the peer: three lines, board TX to device RX, board RX to device TX, GND to GND. At 115200 baud keep the RS-232 cable at 2 m or less; plan 15 m only at 9600 baud, the standard RS-232 length-versus-speed derating.

For a peer while you build, a public example pairs the CODESYS side with an Arduino sketch that echoes what it receives. The Arduino serial image command for memory-card playback page walks the same serial link from the Arduino end. For a display as the peer, start with serial command byte encoding for display control for the bytes the device expects.

Why does the port open but no data come through?

Work four checks in order: both ends on the same baud, data bits, parity and stop bits; TX and RX crossed to the peer's RX and TX; the node from dmesg in the .cfg, node for node; and a peer that is actually transmitting.

  • Mismatched settings: the port opens and garbage or framing errors arrive. Set both ends to the device manual's values. Most USB adapters and examples default to 115200 baud, 8 data bits, no parity, 1 stop bit.
  • Swapped TX and RX: the port opens and the line stays silent. Swap the two signal wires at one end and send again.
  • Wrong node: you opened a port that exists but is wired to nothing. Re-run the dmesg check and compare it with the .cfg line.
  • Console on the port: with the header UART (ttyAMA0, ttyS0), the OS serial login console often owns the node. Find the holder with sudo lsof /dev/ttyAMA0, then disable the serial console in the board's OS settings.

An open that fails outright is a different fault; see fix COM.Open error 5004 on a serial port for that one.

Will the same setup work on another board?

Run the dmesg check on that board itself before copying the configuration across: board generations expose the header UART under different node names, and on many boards the serial console owns that UART until you switch the console off.

Two things move between boards: the node name and the console claim. A line that works as Linux.Devicefile.1=/dev/ttyAMA0 on one board can name a node that is absent or busy on the next. The application needs no change; it still opens SYS_COMPORT1, and only the .cfg entry moves. A USB adapter is the portable choice, because every board enumerates it as /dev/ttyUSB0 and the same line works. Re-run the loopback on each new board and the port checks out in one pass.

What does a full worked example look like?

A shop-floor controller board shows shift targets on a serial display over RS-232 at 115200 baud, 8 data bits, no parity, 1 stop bit, through a USB adapter and a 2 m cable: here is the job with its numbers.

  1. Plug in the adapter, run dmesg | grep tty. The kernel prints 'attached to ttyUSB0', so the node is /dev/ttyUSB0.
  2. Back up the configuration, then add under [SysCom]: Linux.Devicefile.1=/dev/ttyUSB0.
  3. Restart the runtime: sudo systemctl restart codesyscontrol.
  4. Loopback: jumper DB9 pins 2 and 3, open SYS_COMPORT1 at 115200 baud 8N1, send TEST, read TEST back. The CODESYS side checks out.
  5. Remove the jumper, wire the display: adapter TX to display RX, adapter RX to display TX, GND to GND. Send the display's text command; the string appears on the display.

The mapping is the arithmetic: entry 1 opens as SYS_COMPORT1, entry 2 as SYS_COMPORT2, so a second display on /dev/ttyUSB1 costs one more line and one more port constant. The 2 m cable sits inside the 115200 baud limit, and the whole run is 15 minutes.

Recommendation: Configure every port by the device node that dmesg prints on the board that runs it, use a USB-to-RS-232 adapter wherever a USB port is free, and go to the 3.3 V header UART only through a level shifter when the USB ports are gone. Make the loopback the first test on every new board; it splits the CODESYS side from the wiring in one run.

Questions

What does SYS_COMPORT1 mean when Linux has no COM1?

It is a port number in the SysCom library that points at an entry you configured. The line Linux.Devicefile.1=/dev/ttyUSB0 defines port 1, and SYS_COMPORT1 in the application opens that node. The numbers are yours, set in the configuration file, and have nothing to do with Windows COM ports.

Why did my adapter move from ttyUSB0 to ttyUSB1 after a reboot?

Plain ttyUSB numbers follow enumeration order, so plug order and boot order can swap them. Give the configuration entry a stable name under /dev/serial/by-id/ instead; that name stays with the physical adapter across reboots, and the runtime opens the same port every time.

Is the 3.3 V header UART a direct replacement for the USB adapter?

No. It saves a USB port, but it is 3.3 V logic with no RS-232 voltage swing, so it needs a level shifter to talk to RS-232 equipment, and on many boards the OS serial console claims it first. Free the console, then configure its node exactly as you would the adapter node.

Can I leave the loopback jumper in place during normal operation?

No. The jumper ties TX to RX, so everything the port sends comes straight back and nothing reaches the device. Use it for the one test, then move the three signal wires to the peer and repeat the send.

Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.

Keep reading

Build your signAsk for a quote