The TTL serial port of the Raspberry PLC is the UART of its Raspberry Pi, at 3.3 V. On Linux you use it as /dev/serial0. To enable it, open sudo raspi-config, go to Interface Options > Serial Port, answer No to the login shell and Yes to the serial hardware, and reboot: this writes enable_uart=1 in /boot/firmware/config.txt (Bookworm and later). Then read and write it from Python with pyserial. Never connect it directly to the 5 V TTL port of an M-Duino or an Ardbox: use a level shifter.
- Step 1Enable the UARTraspi-config or config.txt
- Step 2Check /dev/serial0Device and user permissions
- Step 3Test with pyserialLoopback, then a real device
- Step 4Connect a 5 V PLCM-Duino or Ardbox with a level shifter
What TTL serial is
A UART (Universal Asynchronous Receiver-Transmitter) sends data one bit at a time over two lines: TX (transmit) and RX (receive), plus a common GND. It is full duplex and point to point: one device on each end. "TTL" means that the lines use the logic levels of the chip, 0 V and 3.3 V on a Raspberry Pi, and 0 V and 5 V on the 5 V Arduino boards.
TTL serial is not differential like RS-485 and has no line drivers like RS-232, so it is for short cables inside the same cabinet. For longer distances or electrical noise, use the RS-485 ports of the PLC.
| Port | Signal | Typical use |
|---|---|---|
| TTL serial (UART) | Single-ended, 3.3 V or 5 V logic | Modules and boards a few centimetres or decimetres away |
| RS-232 | Single-ended, higher voltage levels | One device a few metres away |
| RS-485 | Differential pair | Several devices on a bus, long cables, noisy plants |
Step 1: enable the UART
A Raspberry PLC prepared with the Industrial Shields image or install.sh script already has the UART enabled: the script adds enable_uart=1 to the boot configuration and removes the serial console. Check it in step 2. On a new Raspberry Pi OS installation, enable it with raspi-config:
sudo raspi-config
- Go to Interface Options > Serial Port.
- "Would you like a login shell to be accessible over serial?": No. The console would use the same port and send boot messages to your device.
- "Would you like the serial port hardware to be enabled?": Yes.
- Finish and reboot.
To do it by hand, edit the boot configuration:
sudo nano /boot/firmware/config.txt
Add this line at the end:
enable_uart=1
Then remove this text from /boot/firmware/cmdline.txt, which is a single line, and reboot:
console=serial0,115200
On Bookworm and later the files are in /boot/firmware/. On Bullseye and earlier they are /boot/config.txt and /boot/cmdline.txt.
Step 2: check /dev/serial0 and the permissions
ls -l /dev/serial*
/dev/serial0 is a link to the primary UART, the one on the TTL pins. Use this name in your programs, because the real device changes with the configuration:
| Device | What it is |
|---|---|
/dev/serial0 | Link to the primary UART. Use this name |
/dev/ttyS0 | Mini UART. It is the primary UART by default on a Raspberry Pi 4 with Bluetooth on |
/dev/ttyAMA0 | Full UART (PL011). Used by Bluetooth by default |
/dev/ttySC0 and following | RS-485 ports of the Raspberry PLC, a different chip. Not the TTL port |
The mini UART has no parity and its speed depends on the clock of the processor. For 8N1 at 9600 or 115200 baud it is enough. If the other device needs parity (8E1, for example), give the full UART to the pins with this line in /boot/firmware/config.txt, which moves Bluetooth to the mini UART, and reboot:
dtoverlay=miniuart-bt
Your user must be in the dialout group to open the port without sudo. Check it and, if it is missing, add it and log in again:
groups
sudo usermod -aG dialout $USER
Step 3: read and write the port
Quick test from the terminal
Join TX and RX of the Raspberry PLC with a wire (loopback; both are 3.3 V, so it is safe). Set the port to 9600 baud in raw mode without echo, read it in the background and send a line:
stty -F /dev/serial0 9600 raw -echo
cat /dev/serial0 &
echo "hello" > /dev/serial0
The word hello appears again on the screen: it went out on TX and came back on RX. Stop the background reader with kill %1.
Python with pyserial
pyserial is the Python library for serial ports. The Industrial Shields script installs it; on another system install the Debian package:
sudo apt install -y python3-serial
Save this program as serial_ping.py. It sends PING every second and prints what comes back:
#!/usr/bin/env python3
# Send PING on the TTL serial port every second and print the replies
import sys
import time
import serial
PORT = sys.argv[1] if len(sys.argv) > 1 else "/dev/serial0" # primary UART
BAUD = 9600 # use the same speed on both devices
ser = serial.Serial(PORT, BAUD, bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE, timeout=1)
ser.reset_input_buffer()
try:
while True:
ser.write(b"PING\n") # send one line
line = ser.readline() # wait up to 1 s for a full line
if line:
print("Received:", line.decode(errors="replace").strip())
else:
print("No reply")
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
ser.close()
Run it with the loopback wire still in place:
python3 serial_ping.py
Received: PING
Received: PING
Stop it with Ctrl+C. Without the wire and without a device, it prints No reply. The port and the speed are at the top of the program: the other device must use the same speed and the same format (8 data bits, no parity, 1 stop bit).
Step 4: connect the Raspberry PLC to an M-Duino or an Ardbox
Check the voltage levels first. The TTL port of the Raspberry PLC works at 3.3 V, direct from the Raspberry Pi board, and its inputs do not accept 5 V. The M-Duino (Arduino Mega 2560) and the Ardbox (Arduino Leonardo) work at 5 V. A 5 V TX line connected directly to the RX of the Raspberry PLC can damage the Raspberry Pi.
| Controller | Board inside | TTL level | Connection to the Raspberry PLC |
|---|---|---|---|
| Raspberry PLC | Raspberry Pi | 3.3 V | Not applicable |
| M-Duino | Arduino Mega 2560 | 5 V | Level shifter between the two |
| Ardbox | Arduino Leonardo | 5 V | Level shifter between the two |
| Ardbox Q | Arduino UNO Q | No TTL port listed | Use RS-485 |
Wire it like this:
- TX of the Raspberry PLC to the low voltage side (LV, 3.3 V) of a bidirectional level shifter, and the high voltage side (HV, 5 V) to the RX of the 5 V PLC.
- TX of the 5 V PLC to the HV side of the level shifter, and the LV side to the RX of the Raspberry PLC.
- GND of both PLCs together, and to the GND of the level shifter. Without a common GND the data is not received.
The minimum is a voltage divider on the line from the 5 V TX to the Raspberry RX (for example 1 kΩ in series and 2 kΩ to GND, which gives about 3.3 V). In the other direction, 3.3 V is close to the minimum high level that a 5 V input accepts, so it can work without margin. A level shifter solves both directions.
On the M-Duino, use Serial1 (pins TX1 and RX1) and leave Serial for the USB port, which shares TX0 and RX0. On some models TX1 and RX1 share terminals with inputs and you select them with switches: see how to use TTL on an Arduino PLC. On the Ardbox, check in the User Guide of your model which pins give TTL serial.
This sketch for the M-Duino answers each line that the Raspberry PLC sends:
// M-Duino: answer each line received from the Raspberry PLC on Serial1 (TX1/RX1)
void setup() {
Serial.begin(115200); // USB port, to see the messages on the PC
Serial1.begin(9600); // TTL port to the Raspberry PLC, same speed
}
void loop() {
if (Serial1.available()) {
String line = Serial1.readStringUntil('\n'); // waits up to 1 s
line.trim();
Serial.println("From Raspberry PLC: " + line);
Serial1.print("ACK ");
Serial1.println(line); // reply ends with CR LF
}
}
With the M-Duino connected and serial_ping.py running on the Raspberry PLC, the output is:
Received: ACK PING
Received: ACK PING
For more than one metre of cable, or in a cabinet with drives and contactors, use RS-485 instead: both PLCs have it. See RS-485 between a Raspberry Pi and an M-Duino and RS-485 with a Raspberry PLC.
FAQ about the TTL serial port of the Raspberry PLC
Which device name do I use: serial0, ttyS0 or ttyAMA0?
/dev/serial0. It always points to the UART on the TTL pins, whatever the configuration. /dev/ttyACM0, used in some examples, is a USB device, not the TTL port.
Is the TTL port 5 V tolerant?
No. It works at 3.3 V, direct from the Raspberry Pi. Use a level shifter with 5 V devices.
Why do I receive strange characters?
The speed or the format is different on each side, the serial console is still enabled on the port, or the GND is not common. Check the three.
Can I use parity?
Not with the mini UART. Add dtoverlay=miniuart-bt to give the full UART to /dev/serial0, as in the Modbus RTU guide for the TouchBerry Pi.
How can I see the signal?
With a logic analyzer on TX, RX and GND: see logic analyzer and serial port with a Raspberry PLC.
Serial communication with Industrial Shields PLCs
The Raspberry PLC family has a TTL serial port, two RS-485 ports and two Ethernet ports, with Debian Bookworm 64-bit preinstalled. The M-Duino family has RS-485, RS-232 and TTL serial on an Arduino board, and the Ardbox has RS-485 (check the User Guide of your model for its other serial ports). To compare them, see how to choose an industrial controller.
Take it further
A Raspberry PLC often drives instruments and controllers over a serial line. These pages show it with complete code.
PLC Solution Library
SCPI power supply control with a Raspberry PLC in Python
A Raspberry PLC drives a programmable power supply over a serial line with pyserial: setpoints, output switching and readback.
See the example
Guide
Modbus TCP and RTU on a Raspberry PLC
Python and Node-RED examples to read and write Modbus devices over the RS-485 ports and Ethernet.
Read the guide