Containerizing Serial Communication: Extending the TaaS Model with UART
Learn how to set up remote serial communication for embedded development using Socat, Docker, and Raspberry Pi. Streamline UART access and debug from anywhere!
This post is part of the Tools as a Service Series. Full architecture overview here.
The Need for Remote Serial Communication
In the previous post, we explored the Tool as a Service (TaaS) approach for remote embedded development, focusing on debugging with OpenOCD. While debugging is crucial, embedded development often requires more, for example interfacing with UART peripherals.
Today, we'll extend our setup to include remote serial communication, allowing us to monitor and interact with our device’s serial output from anywhere.
Setting Up Serial Output on the Pico
Before diving into remote access, let's first start with a simple C example and view the output on the host. The following example program prints a message to the serial console every two seconds.
#include <stdio.h>
#include "pico/stdlib.h"
int main() {
stdio_init_all();
while (true) {
sleep_ms(2000);
printf("make progress!!\n");
}
}
Flash this program to your Pico. To view the output, we need to first SSH into our Raspberry Pi and locate the serial device (probably something like ttyACM0). The simplest method is to find the device by id using ls:
ls -l /dev/serial/by-id/
You should see something like this:
lrwxrwxrwx 1 root root 13 Feb 12 09:58 usb-Raspberry_Pi_Debug_Probe__CMSIS-DAP__E6633861A3725538-if01 -> ../../ttyACM0
Now to read the serial output we can simply use cat:
$ cat /dev/ttyACM0
make progress!!
make progress!!
If the output looks gibberish, reset the serial port settings as it might be a problem with the previous configuration - ask me how I know (╥‸╥)
sudo stty -F /dev/ttyACM0 115200 raw -echo -echoe -echok -echoctl -echoke
Making Serial Communication Remote
To access the serial output remotely, we need a way to expose the serial port over the network. We will be using socat, a versatile tool that establishes bidirectional byte streams between two points. Just what we needed!
If you do not have socat installed you can grab it with apt :
sudo apt install socat
Run it (if port 8282 is in use on your system pick another port):
socat tcp-listen:8282,fork,reuseaddr /dev/ttyACM0,raw,echo=0,b115200
Lets break it down:
tcp-listen:8282opens a TCP port for incoming connectionsforkallows multiple sessions, so the process doesn’t die when a connection closesreuseaddrensures the port can be reused if the program restarts/dev/ttyACM0is your serial devicerawtells the Linux TTY driver not to process the dataecho=0disables local echob115200sets the baud rate to 115200
Now, from your development PC on the same network, connect using nc (netcat) or a serial monitor in VS Code:
Voilà! You’re now remotely monitoring your Pico’s serial output.
Containerizing the Serial Bridge
Continuing the the TaaS mindset we'll be containerizing socat and create a reusable, isolated environment that simplifies deployment and ensures consistency.
Dockerfile:
FROM alpine:latest
RUN apk add --no-cache socat
EXPOSE 8282
ENTRYPOINT ["sh", "-c", "socat tcp-listen:${LISTEN_PORT},fork,reuseaddr ${SERIAL_DEV},raw,echo=0,b${BAUD}"]
To make device access seamless, create a udev rule to map the Pi Probe’s serial device to a consistent symlink.
Edit /etc/udev/rules.d/99-usb-debugger.rules and add:
KERNEL=="ttyACM*", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000c", SYMLINK+="debug/pi-probe-serial", MODE="0666", GROUP="plugdev"
For the new rules to take effect, reload them with:
sudo udevadm control --reload-rules
sudo udevadm trigger
Finally we can run the container:
docker run -d --name pico_bridge \
--device=$(readlink -f /dev/debug/pi-probe-serial) \
-p 8282:8282 \
-e LISTEN_PORT=8282 \
-e SERIAL_DEV=$(readlink -f /dev/debug/pi-probe-serial) \
-e BAUD=115200 \
--restart unless-stopped \
pi-serial-bridge
Finally we can get back to our development pc and try connecting to the remote serial output again:
$ nc 192.168.20.200 8282
make progress!!
make progress!!
What’s Next?
In the next post, we’ll explore adding even more tools to our TaaS toolkit. Stay tuned for more ways to streamline your embedded development workflow!
All Posts in This Series
- Part 1: Getting Started with OpenOCD: A Beginner's Guide
- Part 2: Remote Debugging with Raspberry Pi and OpenOCD
- Part 3: Cross-Compiling OpenOCD: A Step-by-Step Walkthrough
- Part 4: Simplifying OpenOCD Deployment with a Debian Package
- Part 5: Automating OpenOCD Distribution with a Private Gitea Package Registry
- Part 6: Running OpenOCD in Docker: A "Tools as Service" Approach to Embedded DevOps
- Part 7: Containerizing Serial Communication: Extending the TaaS Model with UART (Current Post)
How to Use a Raspberry Pi as a Remote OpenOCD Debugger
Learn how to turn a Raspberry Pi into a remote debugging server for the RP2040 using OpenOCD, a complete with step-by-step setup and instructions for building OpenOCD from source to resolve hardware compatibility issues.
Getting Started with OpenOCD: A Beginner’s Guide for Embedded Developers
A beginner-friendly guide to installing, configuring, and using OpenOCD for flashing and debugging microcontrollers.
Cortex-M0 Profiling: How to Trace Without Hardware Support
The ARM Cortex-M0 and M0+ lack hardware tracing features like SWO, ETM, and ITM, so how do you profile code on them? In this post, I explore software-based techniques to get deeper insight into performance and debugging on these resource-constrained MCUs.
Whether you're building something new, fixing stability issues, or automating what slows your team down — we can help.