< Back to top
USB teleprinter, part 2
Blake Thomas in hardware
2018-08-14

New paper

I picked up 1000 sheets of continuous-feed paper on Amazon for ~$16. Works a bit better than manually feeding filler paper. I haven't tested it with a long print run yet, so it's entirely possible I'll have to build some guides for it.

New Arduinos

I also grabbed some Arduino Nano clones to replace some of my "prototyping" Arduinos (I generally use Nanos/normal Arduinos with the headers removed for permanent projects, but keep a couple around that have breadboard pins precisely for this sort of prototyping). Replacing the boards was simple, obviously, and allowed me to close the device back up, so it now genuinely just looks like an electric typewriter with a USB cord.

Motion control improvements

One issue I noticed immediately was that the stepper motor control in the Arduinos was not the best. The driver was programmed to simply either run the stepper at a certain speed or not run it at all. To explain why that's not the best way to do it, here's a little about how stepper motors work physics-wise.

Essentially, the assumption that my code was making was that the stepper motion starts and stops instantly; the rotor is at angle x and not rotating, you apply a step, and the rotor is now at angle x+1 and not rotating. In reality, of course, that doesn't happen; the rotor has mass and therefore rotational inertia. What is happening is that the step applies a torque to the rotor, which needs to move it far enough before the next step happens to allow the next pole to 'grab' the rotor; if you try to step too fast, the rotor can't keep up and just sits in place and makes an annoying buzzing noise.

This limit to the rotor's angular acceleration restricts a naive controller to the maximum speed that can be obtained with one step's worth of acceleration. However, I know from my 3D printer experience that that speed is far below the actual top speed of the motor. Once the rotor is spinning, if you step slightly faster than is required to maintain its speed, you can accelerate the rotor beyond its current speed, and thereby reach a higher maximum velocity throughout the move. The process can be repeated to decelerate the motor at the end of the move.

I also at the same time decided to enhance the controls so that multiple moves could be executed at the same time. Previously, the carriage would advance, the wheel would spin, and the hammer would fire one after another with every part coming to a complete stop before the next move started. However, that wastes a lot of time. Ideally, each motor in the typewriter would be running all the time, and to achieve that I again looked at 3D printer controls. Most 3D printers use a 'motion planner', a subsystem entirely devoted to answering the question of "what is the best way to get from the machine's current position to the next vertex?", and I realized that this machine can be seen as a weird sort of Cartesian robot: each position can be represented as a triple (row, column, wheel index). It's then obvious that the easiest way to get from one character to the next is a diagonal move; i.e. moving both the carriage and the wheel simultaneously.

The system I ended up using is pretty simple; each axis stores its current position, its target position, and some auxiliary numbers related to its acceleration state. A rather high-frequency timer interrupt processes the movement queue and allows the main thread to block on stepper moves. That system manages the movement to the point where often the limiting factor in print speed is how fast the hammer can actuate.

Linux ttyUSBx devices are weird

So everyone knows howdisclaimer: not everyone might actually know how when you press ^S in the terminal it quits working until you press ^Q; this is a commonality among most UNIX terminals and is generally seen as a nuisance.

These control sequences are more properly known as Xon/DC1 for ^Q and Xoff/DC3 for ^S. Their origin lies in the days of slow modems and teleprinters that, while they may have a 9600 baud (say) connection to their computer, aren't actually capable of processing 1200 characters a second. The solution to this problem was something called flow control. When the terminal device (or modem or printer or whatever)'s internal recieve buffer was getting full, it would send an Xoff back to the computer, which would then stop sending and allow the device to drain its buffer until it sent an Xon instead.

Since this device is only capable of around 5-10 characters per second (for now; more movement optimizations are coming Soontm), it would obviously benefit from flow control. I set up some simple code to send Xon/Xoff appropriately and discovered (eventually) that Linux doesn't honor flow control on USB serial ports.

This just confuses me. There's flow control on my screen console / terminal emulator, but not on a physical serial device. How strange.

My solution was simply to write a getty-like program that handled flow control on its own and ran the serial line in raw mode. The program runs a shell in a PTY and just copies the PTY output to the serial line only when the flow control says it should. As far as I can tell, the reason Linux doesn't do the flow control on ttyUSBx ports is because the USB serial protocol has its own form of out-of-band flow control for the USB connection (if the USB bandwidth is exceeded it'll cause blocking reads/writes) and the maintainers figured nobody is gonna try to hook up a device that only handles 10 cps in 2018. Once again, the universe invents a better idiot.

Next steps