WiFi9600 Modem retro-cycles my childhood Practical Perpherials 9600SA RS-232RS-232 based modem to use the Internet. The goal of the project is to enable my vintage computers to telnet to modern BBSes. (Which, of course, is so that I can play Tradewars 2002!) For an authentic experience, the modem emulates proper baud rates, plays modem connecting sounds, and replicates the front panel LEDs from my original modem. For a longer description of the project, check out WiFi9600 Modem Introduction and Prototype Demo, Part 1. In this post, I cover the firmware changes I made and briefly described the mechanical pieces I 3d printed. Part 3 covers retro-brighting the front panel after a disaster. Jump to Part 4 to see WiFi9600 Modem connect to a BBS.
Step 5: Modifying the WiFi modem Firmware
There are several WiFimodem firmware options for the ESP8266. The first I found that I liked was WiFimodem by David Hansel. It has a useful AT-command set, supports up to 19200 baud, and is written very straightforward. I had no trouble modifying the code to suit my needs. It also has a feature that allows a WiFi-client to connect via telnet. For example, a modern PC could connect via the WiFiModem to experience vintage 2400 or 9600 baud speeds!
Dee-Dee-Dee-ShshshshshshsCrrrrrrrrrrrrrruk-bong
The first round of modification was playing the modem connecting sounds via the MP3 player. I had two sounds, some DTMF numeric tones, and then a carrier detection sequence. (14.4k, I think). The first issue I ran into is that the sounds took WAY longer to play than the telnet connection cycle. The second issue is that I can't play the carrier detect sequence until the remote side answers. But, most BBSes almost immediately send some escaped terminal sequences to detect your terminal type. So, the modem really must respond right after connecting. Third, the ESP8266 HATED being put into a 3-5 second delay(). After about 2.5 seconds, the ESP8266's watchdog would reset the processor. Adding yield()s inside of a loop helped but was super kludgey.
My solution started by cutting down the carrier detect sequence in Adobe Audition so that I had a trade-off between that nostalgic sound and keeping the connection alive. To address the ESP's rebooting, I implemented a small loop that checks whether the MP3 player is done playing the track. If not, it calls yield(), which keeps the ESP's watchdog timer happy.
void mp3_play_carrier_detect() { mp3.stop(); mp3.playTrack(3); delay(100); unsigned long start_time = millis(); while( mp3.isPlaying() && (millis() - start_time < 2500)) { delay(100); yield(); } }
After typing atdt <hostname> into the terminal, there are three major steps in the firmware:
- DNS the hostname passed by "atdt."
- If valid IP, play the dialing sequence (and wait).
- If the TCP connection is valid, play the connecting sound (but only wait a little bit.)
The dialout() function blocks the code until that sound is finished playing. The carrier detection sequence only waits about 2.5 seconds and then continues. It is not perfect, but TCP/IP is a bit faster than the old phone line method!
Modem Status Lights
It turned out that nearly all of the lights on the front panel indicate something valid with the WiFiModem firmware.
- MR: (Always On)
- TR: Connected to WiFi Access Point (Terminal Ready)
- TX: Transmit Activity
- RX: Receive Activity
- OH: telnet is active (Off-Hook)
- CD: connected to a telnet host (Carrier Detect)
- AA: Nothing (originally this was an auto-answer)
- HS: Speed indicator. 9600 or higher Green, otherwise Red, when connected
- EC: Nothing, always green. (Error correction)
- DC: Nothing, always red (Data Compression)
So, I turn on two of the LEDs just for the sake of decoration. But the others are just as useful as they were back in the day. Getting TX and RX to work took a bit of extra effort.
TX and RX Lights
One of the most important elements to me was duplicating the TX and RX activity lights. I was not sure how often to turn them on and for how long. For example, if I turn them on for each bit or byte received, how long should the light persist? To test various methods, I wrote some simulation code that created pseudo-random sequences to see how different decay routines functioned. Eventually, I found a solution I liked.
unsigned long activity_interval = 47; // void activity_decay() { unsigned long current_millis = millis(); if (current_millis - previous_RX_act >= activity_interval) { // previous_RX_act = current_micros; update_led(RX, TURN_OFF, true); } current_millis = millis(); if (current_millis - previous_TX_act >= TX_activity_interval) { // previous_RX_act = current_micros; update_led(TX, TURN_OFF, true); TX_activity_interval = 0; } }
RX is the most accurate. Each time the ESP receives one byte from the telnet host, a millis() based timer is reset. While it is counting down, the light stays on. Since the WiFiModem firmware purposely delays the incoming data (to simulate the appropriate baud rate), this method works fantastic. It took a little tweaking, but I settled on a decay time of 50 milliseconds. (And then a viewer suggested I use an E12 number, so I changed it to 47 milliseconds.)
TX is a different story. The WiFimodem doesn't do anything to limit the datarate TO the remote host. And because of how it buffers data from the serial port, there isn't a clean way to do the timer per byte that I did with RX. So. I cheated! I look at the TX buffer size, multiply it by the bit rate, then multiply by a constant, and then add it to the TX decay timer. It's not perfect, but it is close. AND! It reminds me of using a 9600 baud modem with a non-16550 UART chip. The buffers worked funny, which caused the TX/RX lights on a real modem to act funny too. Yeah, that's it. It isn't a bug... it is another retro-themed feature!
if( SerialData.handleTelnetProtocol && !modemTelnetState.sendBinary && buf[n-1] == 0x0d && !Serial.available() ) buf[n++] = 0; TX_activity_interval += millisPerChar*n*3; // handle decay for TX buffer update_led(TX, TURN_ON, true); modemClient.write(buf, n); previous_TX_act = millis();
Hanging-Up
The "Answer" button on the front panel had two original functions. The first was to enable "auto answer." This feature meant the modem would automatically answer incoming calls. I couldn't think of a modern use for that. The other use was to disconnect the active call. In WiFi9600, the answer button causes the modem to disconnect from the remote server. It's rather fun. Plus, with the actula relay clicks, it adds a ton of nostalgia.
if (previous_ans_pb_state != current_ans_pb_state) { if (current_ans_pb_state == PRESSED) { if (modemClient.connected()) modemClient.stop(); update_led(AA, TURN_ON, true); } else { update_led(AA, TURN_OFF, true); } previous_ans_pb_state = current_ans_pb_state; }
Testing connections
Initially, I was connecting to the Level 29 BBS for testing. But I realized I was frequently disconnecting. So, to keep from being a bad BBS user, I needed a local solution. This guide to install Sychronet on a Pi helped me get a local BBS up and running.
Once I had Power working, the answer button doing its thing, and the LED panel looking real, I turned my attention to designing the mechanical pieces.
Step 6: Designing the brackets
Remember that my goal was to make no permanent modifications to the Practical Perpherials 9600SA. When Retro-cycling, I always want the option to return a device to its original state. The front panel driver has a solid friction fit with the edge connector. The back panel carrier board bolted directly to the frame. But this left several pieces with no obvious place to go:
- The whip antenna. The modem used to have RJ-11 jacks for the "Line" and a "Phone" handset. Since those were gone, I decided to use that hole for the whip antenna.
- USB Power-In. Originally the modem used a barrel jack (and an on-board 7805!) for power. I decided to use a USB-Micro B connection so that I could power directly from 5 volts. One reason for this decision is that my modern C64 power supply has a USB-A jack on it. The reason I used USB Micro instead of USB-C is very scientific: I had Micro-USB breakouts lying around.
- Relay Board. Nothing special, they just need a place to live.
- MP3 Board. To get the sweet modem sounds, we need this board and its speaker. I opted to mount the speaker over one of the ventilation holes for maximum modem sound loudness.
My CAD tool of choice is Fusion 360, and my output method was a 3d printer. Some of these pieces could have been laser cut, but my laser cutter isn't operational right now. Even though I am terrible at mechanical design, I got the pieces I needed. I'm sure anyone that knows Fusion 360 would cringe at the crazy things I did to make these parts!
To hold the MP3 board and the relays in place, I made a rail system that mounts to the end of the back-panel control board. Then I made a gasket that bolted the speaker to the ventilation holes to make sure it did not move around. The USB-Micro port was tricky. I used the original case's board holders as a "snap" for a plastic block. That block holds the USB-Micro breakout board in place. When possible, I used heat-set insert nuts on the 3d-printed parts. These are satisfying to insert into a 3d print, and they are also very functional.
My favorite piece was the whip antenna holder (upper right-hand corner of the picture above.) It friction fits into the slot where the RJ11s used to be. I wanted to make sure it didn't fall, so I added a bolt and created my own wing-nut style. These additions were effortless with Fusion 360's "Thread" feature. In short, you create a cylinder, highlight the face, and then add threads. You can change the size, type, and direction. As long as the bolt and the nut's settings match, they mate quite well.
This last picture is from very late in the project, so you can see everything eventually came together. However, before I got to this stage, I went through the process of retro-brighting. But even before that happens, I encountered a disaster, which I'll explain in the next post.