The PiModem Project

Connect to BBS's (via telnet) and the Internet (via PPP) from your old computer!

There has been a resurgence recently in BBSing. While the days of phone lines are long gone, more and more BBS's are popping up - some new, some returning from dusty backup media. Without phones and modems, these "modern" BBS's are reachable over the Internet via a telnet connection.

Obviously, the best way to connect to a BBS is through an (at least loosely) era-appropriate system and a serial communications program. There are several purpose-built devices out there that emulate a modem command set for just this purpose - the WiModem232 from being one of the more recent additions. If you are looking for an out of the box option for some retro BBSing, you can't go wrong with this device.

I'm a bit of a tinkerer and I wanted to build a DIY device similar to the WiModem232. I wanted to retrofit it into an existing modem for a look of authenticity. I also wanted to connect the LEDs on the front of the modem so that they actually indicate modem status. And I wanted to add PPP capability so that I can connect some of my "newer" old systems to the Internet without needing an Ethernet card.

Having used Raspberry Pi's and tcpser before, I decided to use them as the foundation of my project. It took me awhile to find all of the information I needed, so this article is an attempt to gather the important bits in one place. My intent here is not to write a step by step guide, but to put all of the information one might need to do a similar project into one place.


The Raspberry Pi Zero W is a natural match for tcpser. It's small, powerful, has an onboard UART (i.e. serial port), onboard Wi-Fi, and I can buy them at my local Micro Center for only $5. The Pi does have a few limitations that we need to work around...

  1. There are actually two UARTs onboard, one more capable than the other. The "good" UART is mapped to the Bluetooth module by default.
  2. Although the "good" UART supports hardware flow control (CTS/RTS), it's not enabled by default.
  3. The Pi, like many microcontrollers, uses TTL signaling, with 0v representing a zero and 3.3v repersenting a 1. The RS-232 standard that computer serial ports use have a much different voltage range: anywhere from -25 to +25 volts! Not only would this not work, it could damage the Pi.
  4. The Pi Zero W's header does not come populated with pins, so some soldering is necessary.
These are all easily resolved with a bit of hardware and software magic, which we will cover below. Or, if you want to keep things simple and don't care about some extra bulk, you can simply buy a USB to Serial adapter and skip ahead to the software section. Using a USB to Serial adapter should require little to no hardware tweaking, but what's the fun in that?!

TTL vs RS-232

To make the onboard UART compatible with a standard RS-232 serial port, a logic level shifter is needed. There is an IC called the MAX-232 that will do this, and tons of information is available on building this basic circuit. But why reinvent the wheel? Several companies make small boards with a DB-9 port, MAX-232, and all supporting circuitry already onboard. I purchased this one from my local Micro Center for about $15. There are plenty of other options out there. I'd recommend finding one that has CTS/RTS pins, some do not. One with TX/RX LEDs on them to indicate activity could be handy as well.

These modules have all of the goodies already in place - from a hardware perspective it's simply a matter of connecting everything as shown on the left. I had to desolder / remove the header pins on my Schmartboard module, and then simply used some small gauge wire to connect the appropriate pins together.

Connecting it all together

The connections shown in red are the only truly necessary connections: Power, and TX, RX, and ground. You'll note that in the diagram, the TX pin on the Pi should connect to the RX pin on the serial board, and vice versa. This wires it up in such a way that you need a standard cable, not a null modem cable, to connect to your PiModem. This is the way traditional modems work, so it made sense to me to do it this way.

Note: The Schmartboard labels the VIN pin as +5V, but it supports 5v or 3.3v. You will want to follow my diagram and power it with pin 1, the 3.3v output of the Pi. The MAX232 uses the input voltage as a reference, and will output this voltage to the Pi's RX pin, which expects 3.3v. You might damage the Pi if you actually connect 5v to the Schmartboard device.

The lines in gold are for hardware flow control, which is a mechanism that modems and computers traditionally use to signal back and forth if they are ready to receive data (i.e. there is room in the buffer). For systems that support it, this can make things stable at higher baud rates It's usually not necessary at slower baud rates (2400 or below). This will be covered in more detail below.

The blue connections are used to control some LEDs on the front of the modem, and are optional as well. I will cover this in more detail later on.

You will need to power your Raspberry Pi as well. There are various schools of thought on this. The easiest way is to use the on-board micro USB power port. I simply supply +5v directly to the header pins (4 and 6) directly. This is frowned upon by some, as the Pi does not have any protection or regulation in place on these pins, but it has worked well for me.


First, you will need to get Raspbian onto your Pi. There are two flavors of Raspbian available - a desktop version, and a lite version. I suggest the lite version.

There are several ways to install Raspbian on your Pi. I took the headless approach, or you can follow the official documentation. Using the headless method, you perform the installation and basic configuration (enable SSH and configure Wi-Fi) from a regular computer. The remaining instructions assume that you have you have a Pi Zero W connected to your wireless network, it is running Raspbian, and you are able to connect to it via SSH.

Edit config.txt and cmdline.txt

In the /boot directory, you will need to edit two files. These adjustments enable the onboard UART, disable Bluetooth so that we have access to the "good" UART, and disable the serial console so that the UART is free for tcpser to use.

In /boot/config.txt, add the following lines to the bottom of the file:

# Disable Bluetooth

# Enable UART

Once config.txt is edited, reboot your Pi:

sudo reboot

By default, once the UART is enabled, the Pi will open a serial console on the port, which makes it unusable by anything else. You will need to edit the file /boot/cmdline.txt and change any existing console= value to tty1. The end result should look something like this, all on one line.

dwc_otg.lpm_enable=0 console=tty1 root=PARTUUID=0d4f86da-02 rootfstype=ext4
elevator=deadline rootwait

Once these adjustments are done, you will need to reboot your pi a second time:

sudo reboot

Download and compile tcpser

There are several versions of tcpser out there. We will be using this one from FozzTexx (of /r/retrobattlestations fame). It has several improvements over the original. Execute the following commands to download and compile it:

cd ~
cd tcpser-master
sudo cp -p tcpser /usr/local/bin
Note: The above instructions use a locally archived copy of tcpser to guarantee compatibility. You may prefer to clone the latest version directly from Github instead.

Congratulations, you've now downloaded and installed tcpser! There's still plenty to do, but now is a good time to take a little break and test what we've done thus far:
  1. Connect a straight through serial cable between your PiModem and a computer. You might want to use a modern computer first.
  2. On the Pi, run the following command:
    tcpser -s 1200 -d /dev/ttyAMA0
  3. Launch a terminal program on your computer and set the baud rate to 1200.
  4. Type AT into the terminal program and press enter. If everything is working, you should get an OK in response.
  5. Press Ctrl-C on your Pi to terminate tcpser.

If tcpser started without error, but you do not receive an OK in response to AT, the most likely cause is something with your serial cable or wiring. Double check all of your connections, and make sure you are using a straight cable and not a null modem cable. You can try swapping the TX/RX pins as well.

Hardware handshaking (CTS/RTS)

I mentioned previously that the CTS/RTS pins were optional. That is true, but whether or not we connected them - and whether or not we use them - will determine how we configure tcpser, and how you will want to configure your terminal program.

By default, tcpser enables hardware flow control, which means it will try to make use of the CTS/RTS pins. Since tcpser emulates a Hayes compatible modem, this behavior can be controlled with the Hayes command set. The &K series of commands defines whether or not the modem (or tcpser in our case) will have flow control enabled. Here are the relevant &K commands:

&K0 Disables all flow control
&K3 Enables hardware (CTS/RTS) flow control (default)
&K4 Enables software (XON/XOFF) flow control
&K6 Enables both hardware and software flow control

There are two ways to use Hayes commands in tcpser - by issuing the commands from within our terminal emulator, or by specifying them on the command line. If the CTS/RTS pins are not wired up, or if you will exclusively be using older systems that don't need and/or don't support hardware flow control, it may be best to always disable it at the tcpser command line. We will cover how to do this when we are creating our startup scripts below.

If you did wire up the CTS/RTS pins and would like the flexibility of enabling/disabling hardware flow control, simply use the relevant AT commands when needed. Most terminal programs allow you to define a modem init string - simply define it as AT&K0 on systems that do not need/support hardware flow control, and AT&K3 on systems that do.

Ultimately, you always want to make sure that tcpser, your computer / terminal emulation software, and the wiring in place between the two are all in agreement with regards to hardware handshaking. This may require some experimentation.

I also mentioned earlier that CTS/RTS is not enabled by default on the Pi. If you did connect the CTS/RTS pins and want to be able to use hardware flow control, you will need to download and compile a small program:

cd ~
cd rpirtscts-master
rm -f rpirtscts
sudo cp -p rpirtscts /usr/local/bin
This program must also be called before we start tcpser - we'll cover that just ahead.

Startup scripts

Now that we have our software in place, we need to get it to start up automatically at boot. To do this, we will create a shell script to handle the startup of tcpser, and a systemd service to handle automatically starting everything as a service at boot.

First, create the following files. Use your favorite text editor, and make sure to use sudo for proper permissions. If you are unfamiliar with editing text files through SSH, here is a helpful guide on how to use nano.




for i in `cat $phonebook`; do
	n="$n -n$i "

sudo /usr/local/bin/rpirtscts on
/usr/local/bin/tcpser -l4 -s ${baud} -d ${dev} $n

Note: If you are not using CTS/RTS, remove the rpirtscts line. You will also want to add -i '&K0' to the tcpser command line to permanently disable flow control. Also, adjust the baud rate as desired. I've found 9600 to be reasonable for most of my machines, but you may wish to use 1200 or 2400 if you are using some earlier machines. And finally, if you are using a USB to Serial adapter, you will more than likely want to change the dev line to /dev/ttyUSB0.


Description=Modem emulator tcpser




Note: You can customize this to include or exclude any BBSes you wish. The startup script looks for this file, so it should be there with at least one entry. If you don't wish to use phonebook functionality, you can omit this file and modify to remove this feature.

Once all of the above is in place, you can enable and start your new tcpser service. Run the following commands:

sudo chmod +x /usr/local/bin/
sudo systemctl daemon-reload
sudo systemctl enable tcpser
sudo systemctl start tcpser

Testing it all out

Now that everything is running, you should be ready to connect to a BBS. Let's try it out.

  1. Open your terminal emulator program and make sure it's set to 9600 baud - or whatever baud rate you configured tcpser for above.
  2. Make sure your flow control settings are correct
  3. Type AT and hit Enter. You should get an OK in response.
  4. Type ATDT5551000 and hit Enter. This tells tcpser to dial 555-1000 from its phonebook. This should connect you to the Level 29 BBS.
  5. Type +++ (without hitting Enter) and wait a second or so for an OK response. This puts your "modem" into command mode to accept AT commands, but it does not hang up / disconnect.
  6. Type ATH and hit Enter. This tells your "modem" to hang up, or rather drop the telnet session.

If everything went well, you can now use your favorite terminal program's built in phone book functionality. Simply add to or modify /home/pi/phonebook.txt to add additional phone numbers. Make sure to restart tcpser with the command service tcpser restart any time you do.

You can also connect to BBSes, or any other telnet host, by typing the actual hostname and port number instead of a phonebook entry. For example:


Many (most?) terminal programs are not going to accept long alphanumeric strings as a phone number, so using the phonebook features of tcpser allow you to use your terminal program's built in directory feature.

Add some Blinkenlights!

If you've never worked with LEDs, or used GPIO pins to control things on a Pi (or other microcontroller) before, take a moment to read up on the topic. There are tons of great articles out there, like this one.

Since my PiModem is built into an old modem, I wanted to re-purpose some of the front panel lights so that they actually work. The modem I chose to "upcycle" has 6 LEDs on the front, I decided to re-purpose 5 of them: Power, TR, CD, TX, and RX.


First I needed to isolate the built-in LEDs so that they are no longer attached to any bits of the original modem circuitry. I used a multimeter to trace everything out, and then a set of precision tools (a Dremel and a drill) to cut through any traces needed. If I were doing it all over again, I would likely have discarded the original internals and glued some new LEDs to the case.

Next I needed to determine how the positive side of each LED would be connected and controlled. For the Power LED, I simply connected it directly to the power source (with a 1k resistor in series, of course).

The Schmartboard serial level module has TX and RX LEDs on board, so I simply extended a wire for each, connecting the positive side of the modem LED to the positive side of the existing LED. This was a bit challenging as the Schmartboard LEDs are extremely small SMT components. I managed to destroy both LEDs in the process, but it didn't matter since the board is hidden away in the modem, and the TX/RX LEDs on the front of the modem are now functional.

Finally, I connected the remaining two LEDs - TR and CD - to GPIO pins on the Raspberry Pi. I connected the TR LED to pin 18 (GPIO 24), and the CD LED to pin 16 (GPIO 23). I placed a 1k resistor in series between the positive side of the LED and the GPIO pin.


Since the TR and CD LEDs are connected to GPIO pins on the Pi, a bit of software is needed to make them do something. On a real modem, the TR LED means Terminal Ready, while the CD LED means Carrier Detect. I wanted to keep the functionality similar, with the TR light indicating that tcpser was running and ready to go, and the CD light indicating that the "modem" was connected to something.

I contemplated modifying the tcpser code to control the LEDs directly, but I decided not to go down that path just yet. Instead I created a script that uses the fuser command to determine if the serial port is locked, and the netstat command to see if tcpser has any active connections. It's a somewhat rough approach, but it works. The script is started by systemd and checks every 2 seconds to see if either LED should be toggled. A companion Python script handles setting the GPIO pins as outputs and turning the lights on or off.

Assuming you have your own TR and CD LEDs to control, and they are tied to the same GPIO pins as mine, the following instructions should work. If not, feel free to use any parts of my code you wish to craft your own solution.

  1. Grab and and copy them to /usr/local/bin on your PiModem:
    cd ~
    sudo mv /usr/local/bin
  2. Make both of the scripts executable:
    chmod +x /usr/local/bin/ /usr/local/bin/
  3. Grab set_leds.service and place it in /etc/systemd/system:
    sudo mv set_leds.service /etc/systemd/system
  4. Run the following to start set_leds and enable it at boot:
    sudo systemctl daemon-reload
    sudo systemctl enable set_leds
    sudo systemctl start set_leds
Assuming tcpser is running and attached to /dev/ttyAMA0, the TR light should come on within a few seconds. Test the CD light by connecting to a BBS - it should light up within a few seconds of the connection being made.

Configure PPP and enjoy "dialup" Internet service!

If you will only be doing BBSing, you can stop here. However, if you have a system that is capable of using PPP (the protocol used for dial-up Internet), you can also configure your modem to act as a PPP server and dial-up provider. Ethernet adapters for old computers are becoming harder (and more expensive) to procure. This is a great alternative way to get your old systems on the Internet (albeit a bit slowly). I have tested this on MacOS 7.5.5 with FreePPP 2.6 and on Atari TOS with STiNG 1.26. It should work with any system that has direct or third party PPP dialup support.

Install pre-requisites

We will need to add some additional software packages to our PiModem.

sudo apt install telnet xinetd telnetd ppp
Next, set the pppd binary needs the setuid bit set, which allows it to run with elevated permissions.
sudo chmod a+s /usr/sbin/pppd

Create ppp user

We will be creating a new user for the ppp service to run under. This user will have pppd set as its shell.

sudo useradd -m ppp
sudo usermod -aG dip ppp
sudo usermod -s /usr/sbin/pppd ppp
sudo touch /home/ppp/.hushlogin

Configure pppd

The pppd package puts several default configuration files into the /etc/ppp directory. We will be completely replacing two of them. First, move the originals out of the way:

sudo mv /etc/ppp/options /etc/ppp/options.orig
sudo mv /etc/ppp/pap-secrets /etc/ppp/pap-secrets.orig
And then replace them with the following content:


# We will be doing PPP over Telnet - disable serial control.

# Terminate connection if remote side stops responding.
lcp-echo-interval 30
lcp-echo-failure 4

# Debug adds a lot of detail into the system logs regarding PPP negotiation.
# This is helpful in debugging client issues.

# IP addresses to use in local:remote format.  We use NAT to share
# the Wi-Fi connection, make sure these are outside of your real subnet.

# Other sensible options
asyncmap 0
# Allow any username/password
*	*	""	*

Configure xinetd to enable ppp over telnet

Normally PPP establishes sessions over a serial line or a modem. We want to take a somewhat abnormal approach and configure it to answer on a telnet port. This allows pppd and tcpser to interoperate, while also maintaining the ability to connect to telnet BBSes.

Create the following files:


service pppd
    type = UNLISTED
    flags  = REUSE
    socket_type = stream
    wait  = no
    user  = root
    server  = /usr/sbin/in.telnetd
    server_args = -h -L /usr/local/bin/ppplogin
    disable  = no
    bind  =
    port = 2323
/bin/login -f ppp

We now need to set the ppplogin script we created to be executable and enable xinetd.

sudo chmod +x /usr/local/bin/ppplogin
sudo systemctl enable xinetd
sudo systemctl restart xinetd
Before continuing, make sure that xinetd is listening on port 2323:
pi@raspberrypi:~ $ netstat -an |grep :2323
tcp        0      0  *               LISTEN

Enable Network Address Translation

When your PiModem establishes a PPP session with the attached computer, it will bring up a new interface with a pair of IP addresses that we defined above in the options file. We need to configure iptables to perform Network Address Translation (NAT), allowing ppp to share the IP address of your Pi's Wi-Fi connection.

Run the following commands to configure IP masquerading (i.e. NAT):

sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
sudo sh -c "iptables-save > /etc/iptables.rules"
We also need to to enable IP forwarding. Edit /etc/sysctl.conf and uncomment the ip_forward line - it should be around line 28:
# Uncomment the next line to enable packet forwarding for IPv4
And finally, we need to create a simple script to restore the iptables rules upon reboot:


iptables-restore < /etc/iptables.rules

Make the script executable, and then reboot the Pi:

sudo chmod +x /etc/network/if-pre-up.d/iptables
sudo reboot

Once the Pi has rebooted, make sure that IP forwarding is enabled, and that the masquerade rule is in place:

pi@raspberrypi:~ $ cat /proc/sys/net/ipv4/ip_forward

pi@raspberrypi:~ $ sudo iptables -t nat -L POSTROUTING -nv
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    6   573 MASQUERADE  all  --  *      wlan0  

Add an entry to your tcpser phonebook

If you followed my tcpser setup guide, you should have a file called phonebook.txt in your home directory. You will want to add an entry for your new fake dialup ISP:

echo "5559000=localhost:2323" >> /home/pi/phonebook.txt
sudo service tcpser restart
Congratulations! You now have your own dialup ISP. Welcome to the 90s!

Client Configuration

Exact configuration will vary from client to client. Configure your PPP client to dial 5559000, or whatever you specified in your phonebook file above. You will also want to make sure the baud rate matches whatever you have tcpser set to. Experiment with increasing it - I've successfully run up to 57600 on a Mac LC III. Higher baud rates have proven unreliable.

You'll also need to configure DNS servers. You will usually want to specify your home router's IP address, but you can also try using Google's DNS servers ( and You'll want to leave most other items set to automatic or left blank. Your PiModem will automatically assign an IP address and handle negotiation of compression. If asked for a username/password, you can put in any thing you like. Here are a few screenshots of a working FreePPP configuration:

Additional Links and References

Original tcpser source from Jim Brain
The FozzTexx fork on Github
Raspberry Pi 3 Hardware Flow Control
Original source for tcpser
Putting Your Retro Computer On the Line
rpirtscts at Github