Ever wondered what the world would have looked like if large language models were invented before we invented monitors? Probably not but talking to chatGPT on a typewriter is cool regardless. A few weekends ago, Raffi and I hooked up a chatGPT backend to a hacked vintage typewriter: here is a quick demo video!

Getting to this point was a bit of a mission so lets go back to the start of the pandemic when I bought my first typewriter from the Quebec version of Craigslist as my first dumb Covid purchase. I had the idea to turn a typewriter into a printer and instead of doing any research I just went online and bought the first one which was within driving distance. It was advertised as “working 2 years ago, but lost power cable since. Sold as is”. My first reaction was power cable???

Turns out typewriters come in three flavours:

  • Mechanical: What most people think when they picture a typewriter. All the force required to stamp a letter on the page comes from the finger hitting the key.
  • Electrical: Contain usually a single electric motor and a system of mechanical clutches and linkages which greatly reduce the actuation force of the keys.
  • Electronic: No mechanical linkage between the keys and the type. A micro-controller interfaces a keyboard of switches to some mechanical printing element.

I had bought an electric typewriter which, would have been cool in the 40s, but was still way too mechanical to automate easily. Between its mechanical complexity and the handful of issues that were not advertised in the posting, it took me over a year to admit defeat and look elsewhere to achieve my goal.

electirc typewriter electronic typewriter
Sears Celebrity electric typewriter I bought originally Brother AX-25 electronic typewriter pictured with raspberry pi power cord and ethernet cord

Enter the daisy-wheel typewriter: Brother AX-25, a fully electronic typewriter. This typewriter uses 3 stepper motors to move the carriage, advance the page, and rotate the typeface daisy-wheel. Since all printing is done via stepper motor, the brains of the operation is a PIC micro-controller which collects user input through a dome-switch keyboard. Having looked around the internet, I found that some people had managed to hack this style of typewriter to use it as an output device and others got it working as an input device. With some confidence that I could make this work I bought the typewriter which luckily arrived in “working condition”.

I quickly took the thing apart and soldered parallel connections to the 17 wires (+1 ground) connecting the main PCB with the keyboard. Un-surprisingly the keyboard switches are multiplexed by the main board into 9 rows and 8 columns where the main micro-controller polls one row at a time and reads out the 8 columns on each polling cycle. The polling pulse is an entire 1ms: fast by non-gamer human standards but painfully slow by computer standards. This made me think that I can read input and write output to the typewriter without having to program an embedded system.

typewriter circuit
Brain-board of the AX-25. The two ribbon cables at the bottom connect to the keyboard

A quick search of google shows that when running a ring oscillator on a raspberry-pi, one can achieve frequencies above 1MHz. The typewriter operates at only 1KHz which gives me a factor of 1000 margin. Even running python code might just be fast enough…

I started my timing tests with a python script that would pull an output pin down when it detected a polling input. This resulted in a mean latency of 0.2ms but would occasionally be late by anything from 0.5ms to missing the pulse outright. The problem is that linux is not a real-time OS and therefore there is no promise that your process will not be set aside to let some other process finish up what it was doing. Playing with the niceness did not yield acceptable results either.

Timing diagram with python script. Input pulse starts at -1ms. About 1 minute integration time

To get near real-time performance out of linux you have to use a much more primitive programming language like C and adjust the job scheduler to always give you priority. The article Almost Realtime Linux was my main resource for this and it worked great!

Switching to C and adjusting the scheduler to keep my thread on highest priority resulted in less than 0.1ms latency with no glitches or missed pulses at all! Finally wrapping the whole thing in python means that I get to avoid the annoyances quirks of C when connecting it to some web backend like chatGPT. At this point I had effectively re-invented the teletype, and it was ready to do some fun stuff!

asci_cow asci_fox
Demo of the default cowsay ubuntu utility cowsay with –fox switch

Getting the embedded-_like_ code to work took a few weeks but I was hoping to get it working by the time Raffi came to visit me. The original idea was to turn the thing into an email server because I had enjoyed typing letters on the typewriter in the past but found the whole mailing delay annoying. Connecting it to email would let me send and receive type-written letters but with much less delay. However, leading up to this weekend Alev suggested that we connect the typewriter to chatGPT to be able to have full conversations with a computer all on a piece of paper.

In comparison to getting to this point, this turned out to be really easy! We used the unofficial API since when we integrated this openAI had yet to release the official thing.

The final touches were to append “answer in 30 words or less” to all prompts and we were off to the races (turns out chatGPT is really chatty and typewriter ribbons are not infinite).

asci_panther poetry
ASCII art from the internet Some poetry demo with chatGPT

The project has since been put on the back-burner for now. I might get the motivation to connect it to an email server eventually but that might require me to re-write a portion of the input code. The current setup is synchronous and thus the read operations happen in blocks (about 20ms per block). This usually works perfectly fine but if you are typing very fast (hitting two keys within a 20ms window) my code can get confused and miss one of the inputs. The fix here is to run a C daemon and pipe output live to another thread which deals with the inputs. Although my love for C makes me reluctant to change things up.

If anyone made it this far and is still reading, all the code is on GitHub.