Monday, August 19, 2019

Brother SX4000 teletype

Converting a typewriter to a teletype an impact printer

I don't really remember the origins of this project.   What I've accomplished thus far really does not qualify as making a "teletype".  What I have at this point is really an impact printer, since I'm not capturing keystrokes from the keyboard.

From a historical perspective, I've always liked teletypes.  Some of my earliest computing memories were playing games on teletypes at the Lawrence Hall of Science in Berkeley, CA.  There, you could get a limited amount of time on one of their terminals, and play games like Trek and Hunt the Wumpus!  And one of the more rewarding things to do was simply to print some ASCII art.

For this project, one path might have been that I bought a typewriter online with the intent to turn it into a teletype, and then found an explanation online.  But it's more likely that it went the other way around: I found the explanation online, and then searched for and found exactly the same model of typewriter.

In any case, it starts with a Brother SX4000 electronic typewriter, and a nice write-up by numist.net.
This is the typewriter.  I think I paid about $35 for it.  It's rather bothersome that they list for much more than that on eBay nowadays.  A few weeks back (August, 2019) I found another one, exactly the same model, at a nearby Goodwill for $15.



It's nice.  And it's old, circa 2003.  It has a daisywheel mechanism, which means there's a spinning plastic disk and each "petal" of the "daisy" has a character at the end.  The wheel spins to the letter you want to type, a hammer whacks the letter against some ink ribbon, and the ink gets transferred to your paper.

The typewriter supports some unusual characters, like 1/2, 1/4, a +/- symbol, a degree symbol, a cent symbol, a paragraph symbol, and a section symbol.  Some of those have interesting names in Unicode land.  To me, they're so unusual that it's easier to type them as sequences of ASCII characters, rather than looking up their true symbolic representations in modern editing tools.

It also does not support some of what we'd consider standard characters today, including the caret "^", "squiggly braces" { and }, the pipe "|" symbol, and the backslash "\".  Yes, that's a backslash.  "Slash" is the forward-leaning thing "/" that we've had all along.  As evidence, I submit this typewriter.

It has a "shift lock".  Note that that's different from a "caps lock".  If you engage the "shift lock", it shifts all characters, including numbers!  That gets to be really annoying if you're trying to type on it, and you're used to today's "caps lock" behavior that only affects alphabetic letters. 

There are three modifiers: SHIFT, CODE, and ALT.  Those can be used in conjunction with some keys to type different letters are make the typewriter behave differently.  Of interest are the CODE+O and CODE+P keystrokes, which are REV and INDEX, respectively.  REV rolls the paper up a half line, and INDEX does the opposite.  Along with the normal BACKSPACE, this allows me to combine multiple characters to create interesting effects.

The other neat thing about the SX4000 is that it's a portable typewriter.  It has a non-detachable power cord with a little hole for the cord to go into.  It has a covering case to keep dust from getting in.  It has a handle so you can pick it up and walk around it with it (though that handle and its hinge pins are made of plastic).  And it's fairly light, certainly lighter than the old Selectric tanks.

My initial goals were to print ASCII art, and it was important to me that the machine support BACKSPACE.  There are ASCII art files out there, especially the ones I grew up with, where the teletype would print one character, step back, and then overstrike with another character to get a particular effect.  I'm not quite sure where to find those.  Googling "ASCII art with backspaces" doesn't really get me what I want.

Disassembly

The most nerve-wracking part about this project really was disassembling the case.  There were two visible screws that were easily removed.  Those sit behind the platen and are accessed from above.  All the rest was held together by compression-type plastic clips and relied on the plasticity of the plastic to come apart.  Coming at it blind, I didn't know whether top was clipped to bottom, or the opposite.

Fortunately, there is a hole at the back right that houses the power cord.  That served as a good starting point to get that first clip undone.  With some wriggling and minor prying, the top came apart slightly from the bottom, and I was on my way.  (Typical technique: wear eye protection, pry a section apart using one screwdriver, wriggle another screwdriver into place, push and pull until something pops apart, repeat.)  Here's a picture of one of the clips.


Oriented normally, it looks like this (ASCII form)

|
|_    the clip
-/

  --+
    |  the thing the clip hooks onto
    |

So the top comes down and snicks down, hooking onto the bottom.

There are five of these clips along the thin, front edge plastic (just in front of the keys) and they point backwards.

Front-side clip (top of picture with hook shape) and receiver (bottom edge thing that has a rectangular hole in it)

At  the back, there are three clips.  Each has two hooks and is about 1/2" wide.  The first is around 1" from the left back, then about 4" spacing, another two-hook clip, 4" more spacing, and the final two-hook clip.  Again, the the hooks point to the back.
Back side clip -- the thing with two hooks




That means that the back clips are facing outward, and the front clips are facing inward.

For removal, I started at the back and very gently (but firmly) loosened the first one (back rightmost), probably by pushing inward along the top case edge, and pulling outward along the bottom case edge.

After the back edge was unclipped, and the two screws were removed, I unclipped the side clips.  Those are visible and accessible within the carriage area.  Turn off and unplug the machine, and manually slide the carriage to one end or the other to get access to the opposite side's clip.  These are different from the rest.  On each side of the top case, there is a plastic tongue with a hole.  Normally, the tongue slips over a clip that protrudes from the bottom, and the clip clicks into the hole.  To undo these, you have to gently pull the tip of the tongue inward (towards the center of the machine), while applying upward force to the case top.

Finally, the clips at the very front of the machine remain.  Remember that these are along the weakest plastic section of the whole machine, so be gentle.  On these, the clips are along the top and pointing inward, so you can defeat them by pushing the bottom case inward, and pressing outward along the case top, working from one side to the other.

As with any prying disassembly, wear eye protection, and consider using screwdrivers or other holders to keep the case parts separated, similar to how you might use tire irons to work a bike tire off its rim.  With the kind of plastic used here, it's easy to mar the case, since it's so pliable.  Be gentle.  And don't be surprised if you break something.

With the case removed, the power supply in the back is exposed but the wiring remains enclosed or under the transformer board.  Also, the holes that once held the screws are now exposed.  To avoid losing those screws, I just put them back into the screw holes and gave them a few turns.

Here is a picture of the typewriter with the top case removed, but with the keyboard still in place.
Outer case removed, keyboard still in place


The keyboard is also held down by clips.  Here is an image of one of them.
Keyboard clip, bottom right


There are only four.  Each side has two: one near the edge in the front, and one at the bottom right/left side.  I find it easiest to press outward on the rightmost one, and get it loose by lifting upward on the keyboard back at the same time.  With that held partially unclipped, I can undo the bottom right one. That then lets me pivot the whole keyboard a bit, making it easier to do the bottom left one.  The hardest to remove is the leftmost one. and get done with the bottom left, and then the left ones.

When you have those all unclipped, you can peek underneath to see how the keyboard is attached.  Fortunately, it's not complicated.  The keyboard attaches to internal circuitry at three points.
Keyboard lifted up to see what lies beneath


Two of the connections are white wire cables that connect the LCD display to the circuit board.  One is a 1x4 cable, and the other is 1x6.  The headers on the PCB are the kind where they have a compression holder.  You lift up on the white side that faces the typist and that allows you to wiggle the cable out.  The "cables" end with multiple wires.  The wires go into holes in the PCB header.  But they are not keyed, so you can accidentally put them in backwards.  I marked my headers and cables on one side with blue Sharpie to try to avoid putting them back in the wrong way.
White wire cable headers with blue Sharpie markings to remember orientation

White wire cables unseated, wire ends visible

The third connection is a one-sided, flexible flat ribbon cable with a 1x16 arrangement and 0.1" pitch.
Original flexible flat cable (FFC) inserted into a vertically mounted, through-hole header

The PCB header is a spring-loaded set of pins, through-hole, vertically mounted.  With care, you can just gently wiggle the ribbon cable out of the header, but be careful to avoid bending or breaking the ribbon, because if you do, you're probably SOL.  The ribbon is unique to the machine, and you simply can't find things like them any more.

Here is a picture of the PCB with the keyboard removed.
Original circuit board, FFC and white cables and keyboard removed


I was surprised at this point to see a flat-pack microcontroller in the typewriter.  I didn't expect that kind of fine circuitry to be involved, given the product's age.  I supposed I was expecting something clunkier.

The bottom black cylinder is a speaker/beeper.

The right, top header with orange and black wires is DC power coming from the transformer.  I measured them, and they were delivering 8+ VDC (black negative, orange positive).  The rest of the wires connect to the carriage and roller.

The carriage has a motor for rack-and-pinion linear motion left and right, a motor to spin the daisywheel, and a solenoid to whack a letter, but there's more than that.  It also has to handle changes between ink and corrective tape, and manage point size changes.  It has some mechanism for resetting the daisywheel, like it's using a limit switch.  Also, upon full reset, it does some funny things where it pops the cartridge up and back down, and unlocking the carriage if it scrolls all the way left.

The roller motor control has to support bidirectional movement, given the REV and INDEX functions (and, obviously, a normal carriage return).

The keyboard
As mentioned earlier, the keyboard connects to the electronics via three ribbon cables.  The two white cables strictly relate to the LCD panel.  That helps the typist see status in various forms.  The flexible flat cable is the one that provides information about all the keys, and one special pseudo-key, the limit switch on the left side.

Keyboard removed


Once the keyboard is removed, you can see that it has a flat metal base that is, again, held on strictly by plastic compression clips.  Beware of sharp edges along this base.  Mine wasn't ground down along the edges, and that probably makes sense.  These things weren't really meant for consumer interfacing.
Keyboard inverted, flat metal based clipped in place still

Working from one side to the other, you can undo the clips and pull off the metal base plate.
Keyboard inverted, rotated 180, base metal plate tipped away

Keyboard flexible circuit board exposed. White lines are facing up (in this perspective) and black traces facing down (in this perspective) are conductive.

What's happening is that each key has a piece of conductive material at its base under a piece of springy rubber.  When a key is pressed, a connection is made between two of the sixteen lines.  It is all passive.  I was hoping there would be power and ground, and some logic circuits converting values into ASCII or similar, but instead it's a bunch of physical switches.  A quick resistance check found that each pad has around 300 ohms resistance.

Decoding the keyboard signals

My next step was to figure out a way to know which lines connected to which keys.  For this, I used Adobe Photoshop Elements.

I took my pictures of the original keyboard and the upside-down traces.  The main layer of the photo was the right-side-up keyboard.  I then used a second layer for the traces.  I inverted the trace layer (flipped it horizontally) and then applied about a 50% transparency to the trace layer so I could see through to the actual keys.  Then, I manually resized, rotated, and translated the trace layer so that the pads would align with the keys.

I then started using PSE's "magic wand" selection tool in Contiguous mode with various tolerance settings in an attempt to "select all similar white pixels" of each trace.  This worked, mostly.  There were lines that, due to contrast problems and photo lighting, didn't connect well.  As I would select each trace, I would apply a color to it by using PSE's paint tool.

I think of the ribbon wires as being numbered from 1 to 16, with line 1 being at topmost ribbon trace when the keyboard is right-side up.

Here is an example of what I got when I had colored lines 9 and 16 orange and blue, respectively.
Initial line coloring using PSE


A few things to note at this point: the 16-contact flexible flat cable is actually part of an entire flexible flat circuit board.  It's not a separate FFC.

The keyboard circuit board is two-sided.  The bottom side has the white traces that connect outward to the ribbon cable area.  The top side has the contact switch and also has jumpers, which you can see as black segments bridging a white wire from one point to another.  I don't know how they connect through.  I get how plated through-hole works on normal PCBs, but this is some kind of through-plastic, through FFC, magic mojo.

The switches themselves are round, orange, rubber baby buggy bumpers with conductive, resistive material at the base.  They appear to be glued onto the plastic.

I colored all lines, and then "drew" wire numbers to keep things straight.  This is what it looks like, nearly complete.  (This may yet contain a few errors.)
Full line coloring and numbering using PSE

As examples, the left and right SHIFT keys are treated the same, both forming a connection between lines 1 and 16.  A key press of the letter 'a' is detected when lines 2 and 14 form a connection.

There are a few odd optional contact switches on the far left side of the keyboard.  They don't appear to serve any purpose.

The limit switch is the one at the top left.  At boot-up, the carriage moves left, pushing against a lever which in turn forms a connection on lines 7 and 14.  If you don't have the keyboard in the typewriter and turn it on, the carriage will move left and grind away until you turn the machine off.  You can't just short these two, either.  The boot-up sequence expects to move left, hit the limit, move right and the move right to see the limit switch release.

This is the limit switch lever and pad, shown from a different angle.


The key map

I laid out all the keys and their line pairs, and eventually found that every key press is based on a connection between something in traces 1..8 and something in traces 9..16.

There are multiple switches connected to each line.  For example, in the 1..8 range, contact 1 works in combination with contact...
9 = comma
10 = period
11 = 1/2 or 1/4 shifted
12 = semicolon or colon
13 = ] or [
14 = apostrophe or quotation mark
15 = space
16 = SHIFT

And similarly in the 9..16 range, contact 9 works in combination with contact...
1 = comma
2 = slash or question mark
3 = 1
4 = 3
5 = 7
6 = 5
7 = hyphen or underscore
8 = 9

Logically, it would only make sense for the typewriter to be doing some kind of iterative checking to see which lines were connected.  It has to either sweep through lines 1..8, signaling each one at a time to make something happen as contacts 9..16 are inspected.  Or, the opposite is happening: it sweeps through 9..16 iteratively, and while each is down, it checks to see if 1..8 is connected.


I took multiple stabs at finding out which were the scan lines and which weren't.
My initial assumption was that contacts 1..8 were the scan lines.  I made the broad assumption that the ground line going in to the circuit board would be the same ground for everything, so I stripped a little of its shielding off.  I could then set myself up with some alligator clips and patch wire to check each of contacts 1..8 using an oscilloscope.

The limit switch

In order to test contacts 1..8 on a scope, I would have to turn the typewriter on.  So I did that and <<grrrrrind>> it sent the carriage left, trying to find the limit switch.  Quick turn off the machine!  When it does this movement, the carriage can do some weird things.  It can pop the cartridge up to an odd height, or even lock the carriage into a clipped position on the left.  (To unlock, turn off the machine, press the locking clip loose with a pen or pencil, and manually move the carriage to the right.)

I knew already that lines 7 and 14, when connected, would fake a contact switch closure, so I had to wire those up separately.  I picked up a random contact switch from the HackerLab electronics drawers, and soldered it on (normally open, contact on closure) with a 300 ohm resistor in series.  Then, after turning the machine on, I would manually close the switch when it looked like the carriage was far enough to the left.  As the carriage would then move right (presumably to find the point when the limit switch would release), I would let go of the switch button.

Later, when doing the "real" assembly, I kind of taped/glued the contact switch to the side of the typewriter interior such that the carriage would ram into it on startup.  I got a little technical in doing this by measuring when the actual limit switch would hit, measuring the position of the carriage, and attempting to replicate that location with my own switch.  What I ended up with was a good approximation: a chunk of a 3M Command Strip double-stick Velcro thing glued a lever-based contact switch to the left side.

Back to measuring the signals

With the machine on and the initial scoping set up, I tested all of lines 1..8, and found that they all registered high around 5v.  They didn't show any indication of a periodic drop to ground.

I then just put a voltmeter across the pins and found that lines 9..16 registered less than 5v, maybe around 4.6v, suggesting those were the scan lines.  The lower average voltage would indicate a PWM-type behavior.  I was expecting it to be about a 1/8th drop in voltage or 4.375v.  In the end, it really should have been a 1/9th drop or 4.44v.  But I am not entirely sure what I got at that point.  I know for sure, though, that the value was less than on lines 1..8.

The breakout board

At this point, it was getting cumbersome to check the lines and the keyboard signals.  I wanted to make by own breakout board that would let me "convert" the flexible flat cable into something where I could attach typical through-hole pitch headers.

Unlike numist.net and another example I saw, I really didn't want to connect 30ga wire to available solder points on the circuit board.  I just didn't trust my fine soldering skills (and/or my eyesight?) and didn't want to risk any damage to the board.

In my mind, I generally envisioned having a board set-up where
1.  A substitute ribbon cable would come off the circuit board to mine, so I'd need a ribbon and a 1x16, 0.1", FFC header.
2.  A 1x16 set of 0.1" pitch female headers would be available for inspecting what the circuit board was doing on each line.
3.  I'd have a second FFC header so I could connect the original keyboard, if desired.  In the end, I added this but haven't used it.
4.  I'd have another 1x16 set of 0.1" pitch female headers to inspect what the keyboard was doing.
5.  In-between the two FFC headers, I would either have straight jumpers or 300 ohm resistors for each bank of signals.  The scan lines would have no resistance, but the "signal" lines (or "sense" lines) would have resistance similar to what the keyboard itself was generating on each key press.

The headers

Sourcing the 1x16, 0.1" FFC header was a challenge.  FFC cables these days typically use 1mm or 0.5mm spacing.  Having 0.1" pitch is old school.  But, I did find it eventually: a "6-520315-6 TE Connectivity" header on mouser.com.  I got that after applying the filters FFC/FPC/FFC & FPC Connectors, Number of Positions = 16, Pitch = 2.54mm, Termination Style = Through Hole, and Mounting Angle = Vertical.  There also was a lot available on eBay -- 100 for about $20 -- if I wanted a bunch of them.

I built the initial board just by using a plain old breadboard and doing a lot of solder bridging.
Breadboarded breakout board, front and back, allowing patch wire inspection of scan lines and signal lines


The substitute ribbon cable (aka the Fake FFC)

My next challenge was to create my own FFC 16-line, 0.1" ribbon cable.  I noodled on this for quite a while.  There are ways to make them the expensive way, using Kapton film coated with a copper layer, and then using chemical-based etching, but I didn't want to spend that kind of money.

Fortunately for me, just a few days earlier, Jim S at the lab had shown me something he had built.  He had used some copper tape as a common ground line for a device he'd put together.  I hadn't seen copper tape and didn't know what it could do.  But I found a small roll of it in the HackerLab drawers and, uh, rolled with the idea of using that tape for each of the 16 lines.  The copper tape was 1/4" or 0.25" wide, and so I would need to cut strips lengthwise to end up with the "right" thickness.  Given we had 0.1" pitch wires, each of my strips would need to be less than 0.1" so I settled on trying for a 0.6" width per line.

The plastic backing for the FFC was the next challenge.  I would need something flexible and thin, but not too stiff, and non-conductive.  Fortune again shone on the project.  It was August, and it was back-to-school season.  I went to the local Dollar Tree store, and got a pack of 3-ring binder index separators, basically five or six sheets of thin, transparent plastic.  They were pretty close on stiffness/flexibility and perfect for thickness.  Added bonus: I could choose from an assortment of colors.

Materials for fake FFC assembly


Lining up the copper lines would be a challenge, so I just drew my 16 target lines in CorelDraw.  I intended to make each line around 0.6" wide, but annoyingly, CorelDraw only let me specify the line width in "points".  Assuming 72 (or is it 72.27?) points per inch I ended up using a 4.0-point line thickness, which computes out to about 0.055" wide.  I modified the grid settings to align to a 0.1" pitch, just to visualize things.  I also used a dashed line format to make it easier for taping.  Once that was set up, I just printed it on normal paper.
PSE drawing of 4pt dashed lines, separated at 0.1" pitch

I put the transparent index divider atop the paper, and cut out a chunk around the pattern, leaving a lot of excess on the width on the right side.  That allowed me to tape the right edge temporarily (using plain old cellophane tape) in such a way that the tape would not interfere with the copper lines, but it would keep the plastic and paper together.  The top, left, and bottom edges were trimmed exactly to the red lines.
At this point, I could start assembly.  I would measure a piece of copper tape (with adhesive backing still in place) to be longer than the "ribbon" length to allow overlap on both ends.  I would then cut lengthwise so that I would end up, hopefully, with something around a 0.04"-0.06" width.  Then, I would undo the adhesive backing on one end, tape it down to the won't-stick-to-it-forever lab desktop, line it up with one of the ribbon lines, peel away the remaining backing, and press down.  Each copper tape end would be folded over to the back of the plastic.  Then, I would just use the back of a fingernail to smooth out any copper foil wrinkles.



With all the copper lines down, I trimmed the right edge to the red line, and in so doing I cut away the tape that was holding the paper to the plastic.


There were, of course, some mistakes made along the way, but they were pretty easy to correct.  To be safe, I continuity-tested each pair of lines to make sure I didn't short anything.

When I was done, I wrapped up the copper side with more cellophane tape to prevent accidental electrical contact with anything else.  I left enough exposed contact space at the end to fit into the FFC connectors.  This actually worked out really well.  Not only did the tape prevent electrical connection, it also added decent stiffness to the plastic, which by itself was a little too flexible for my tastes.

Fake FFC, front and back -- note inconsistency of copper tape cuts, how copper tape loops over ends to be conductive on both sides, and cellophane tape usage to insulate and stiffen


Happily, this put me back to a point where I had a breakout board connected to the typewriter circuit board. 

Breakout board attached, but bad design prevents access to headers

I realized too late that the placement of the female header pins was totally inconvenient.  It would have been better to have had them on the "inside" part of the board, because at this point the ribbon was interfering with access.

I also realized too late that I wanted specific breakout lines for the limit switch.  I hacked in some patch wires for lines 7 and 14, and solder-bridged them.  (Those are the white wires and the contact switch shown in the picture above.)

In the end, I got a decent breakout board, and I know how I'd make a better one next time.

Back to the testing the electronic signals

At this point, I had a breakout board connected by fake FFC to the original circuit board, and a hacked-in limit switch.  The machine could be booted up without the original keyboard, as long as I clicked and unclicked the switch at the right times.

I broke out my oscilloscope and took a look at the various lines after booting up.  There, plain as day, I could see the signal drops along lines 9..16.  They would stay high most of the time, then drop down for about 2ms, and then go high again.  I only had two lines on my scope but with iterative checking, I could see that the 2ms low period would apply to pin 9, then 10, then 11, and so on, until pin 16 went low.
 

However, after pin 16 went high, all of the 9..16 lines would stay high for 2ms until pin 9 would go low again.  So that meant that the total period of scanline activity was not 8 x 2ms, but 9 x 2ms.

That also meant I would have 2 ms to do something to trigger key and modifier presses while a given scan line was down.  And I didn't have an example of what the keyboard would actually do with a real key press.

How would I actually "connect" a pair of wires to simulate a key press?  For example, if I were to try to connect 7 and 11 for a letter "n", I would have to have an electronically controlled switch for that line combination, and trigger it from a microcontroller.  For a basic test of this, I wired in a basic BJT transistor (with appropriate pull-ups and base resistor), tested it in isolation, and then connected lines 7 and 11 to either end.  I tried triggering that manually, and got nothing from the typewriter.  I also considered using an optocoupler, but if a transistor wasn't working, and opto wouldn't.

After several frustrated attempts, I chose to do the brute force and semi-dangerous thing -- I just put a patch wire into line 11 and touched (no resistor) it to GND.  The typewriter sprang to life and jammed out several characters at once: 1/2, q, e, t, o, u, n, v, or a subset of that sequence.  So that meant the typewriter was working, and it was handling the scanline 11 with sense lines 1..8 in that order, but for some reason I could not trigger it that way electronically.

I didn't really want to, but I went ahead and directly connected Arduino Mega 2560 lines to scan lines and sense lines.  I wrote a quick sketch to look for a drop on a given scan line, and had that fire off a drop on a sense line.  Nothing.  (This was a bit of a dangerous test, not knowing the current draw of each line.)

I did similar testing using a Saleae 16-pin logic analyzer that Jim let me borrow.  Sure enough, I could see the drop of the scanline, and I could see the drop on the sense line, but no activity came from the typewriter.

Saleae screenshot.  Channel 1 is the scan line, Channel 3 is the sense line.  Channel 3 goes low for a delayMicroseconds(1800) == 1.8ms period.  However, no character is printed.


I tried tightening up the code.  Maybe the delay between the sense line trigger point and the sense line drop point was a problem.  No luck.  (Typical delays, like Serial operations, were removed.  Also, I tried switching from the slooow digitalWrite function to using direct bit setting, specific to the Mega2560 internal chip.)

I tried dropping the sense line low longer than the duration of the 2ms scan line down time.  I thought, maybe if I'm too late generating the sense line pulse, I'll start off with it low going into the next scan line.  Still nothing.

(Here, channel 1 is scan line, channel 3 is sense line.  Sense line overshoots scan line pulse by 442 usecs.)


Around this time, I finally gave in and started looking at numist.net's code for ideas.

One thing that came up was that I should simulate holding the key press down for more than a single scan line pulse.  The onboard circuitry may be using that as a debounce indication, or some such.  I also, at this point, started experimenting with having the Arduino pulse control a transistor to separate out the current flow.

So in this shot, we have channel 3 as the scan line, channel 1 as the Arduino high signal controlling a transistor, and channel 0 as the transistor's output, tied to the sense line.  I pretend the key is held down by pulsing across four consecutive scan lines.  Still nothing.


In this latest attempt, I was triggering the sense line 24 usecs after the scan line low state was detected.  I was using digitalRead() to see the scan line, and digitalWrite to set the sense line.

To tighten that up, I went to direct port manipulation on write.  I left the read function as it was.  Using PORTC on an Arduino Mega2560, I would be able to change the bit state on all eight sense lines in a single byte write.  But it also meant that I would have to re-wire my pins as
a8 = pc0 = digital37
a9 = pc1 = digital36
a10 = pc2 = digital35
a11 = pc3 = digital34
a12 = pc4 = digital33
a13 = pc5 = digital32
a14 = pc6 = digital31
a15 = pc7 = digital30

(Ref: https://www.arduino.cc/en/uploads/Hacking/PinMap2560big.png)


Unfortunately due to physical layout, that meant the sense pins would be sort of upside-down compared to what I wanted.  No matter, I went ahead and wired it that way and adjusted the code.  To do that, I also went to using DDRC to set all sense lines as output pins in one go.



This brought the delay time down to 12 usecs instead of 24 usecs.

But it still didn't generate a typed character.

Help!!!

At this point, I finally had to give up and go to the cheat page to figure out why I wasn't getting a printed character.  I visited numist.net's page, and then pored over the related Arduino code to get ideas.  A bit at a time, I adapted things and suddenly I was getting characters!

There were several magic tricks to it.

First, the numist.net page indicated that current draw along any line was a mere 9 mA, well within the safety zone for an Arduino output pin.  As such, I didn't have to do any trickery with transistors or optocouplers.

Second, the code was using direct port manipulation to read and write pin values.  I had adapted to using the write portion, greatly reducing the time between the sync line high-low transition and my sense line high-low setting.  I ended up not needing to do direct port reads to see the scan lines.

Third, the code would raise all output lines high upon seeing a scan line going low.  I don't think that really triggers anything.  After all, all sense lines would be high anyway during a period of keyboard inactivity, and I wasn't seeing typing in my earlier tests.  Still, I went ahead and adopted the "set all lines high" approach.

Fourth, and possibly most importantly, the numist.net code would set a signal / sense line low multiple consecutive times.  I had tried that before to no avail, but at that time I didn't have all the lines connected, and I might have floated some lines, so maybe that influenced the behavior.  After some experimentation, I found that it only required two consecutive iterations to work.  That meant for any lower case character, I would use (8 scanlines + 1 unused) x 2ms x 2 iterations, or 36 ms to get an unmodified character communication done.

Given all that, I rearranged my code a bit, and much to my surprise and relief, the typewriter whacked a letter onto the paper!

After a few more iterations, I was able to use modifiers, too.  I had thought that I could just jam in the modifier behavior in the same data set as the keys, but it actually required me to simulate the modifier down, repeat on the next cycle, then add the key down, and repeat again another cycle.  So, modified keys would take 72 ms (four cycles, 18ms apiece) to go out.

Software

I built this using an Arduino Mega2560.  I chose that because it had lots of output pins.

The setup() function of the sketch precomputes all the scanline bytes for each character.  Each character is represented by a 16-bit quantity with the low 8 bits representing signal line values (active low), and the high 8 bits representing scanline match.  Given the example above where 'a' is scan line 14 and signal 2, the signals array would be set up as
  signals['a'] = (1<<(2-1)) | (1<<(14-1));
Later, I could take that value and split it apart like this:
  signalBits = signals['a']  & 0xff;
  scanBits = signals['a'] & 0xff00;
Then, I could "not" the signalLine value to drop a particular value low (signalBits = ~signalBits;).

As for scan lines, I was iterating in a loop anyway, waiting for a line to go low, so I could set up a mask bit and compare against it with each line I was looking for.  Conceptually, something like this:
  scanMask = 0x01;
  scanLine = 9;
  if (digitalRead(scanLine) was high and is low now) {
    scanLine++;
    if (scanBits & scanMask) {
      PORTC = 0xFF; // force all signal lines high
      PORTC = scanBits;      maybe delayMicroseconds here
    }
    scanMask <<= 1;
  }
  if (scanLine == 17) {
    // we're done with this character
  }
And then other trickiness would be set up to handle modifiers.


The loop() for the sketch basically runs a state machine.  The states:
1.  Wait for a character to print.  In early code form, I just hardcoded a single character to repeat every few seconds.  Later, I made the code read frequently from the Serial input line.  If there is no character to print, wait some significant amount, like 50ms.

2.  Prepare signals  Get the precomputed signal values and scan line information for the character and modifiers in question.  In early form, I set up the character signals at the same time as the modifier signals, thinking both could be sent in and be recognized.  But instead based on numist.net, I found that I had to send the modifiers for a few cycles on their own, and then send the character with modifiers.  In retrospect, this "state 2" could have been split into a "send modifiers" state first, and then a "send character signal with modifier signal" state.

3.  Once the signals are set up, I am in wait state, looking for a low signal, starting with line 9.  Upon seeing that, I set PORTC to 0xFF to force all lines high.  Then, I grab the precomputed signal value (bytes[9], initially) and put that value into PORTC, thus setting all values for lines 1..8 that are appropriate when scan line 9 is down.  The values going into lines 1..8 are always default high, active low.

After this, I might wait a very small amount of time (delayMicroseconds) to let the controller see my signal lines.

Then I iterate on each subsequent scan line.

If the overall process of sending a batch of 8 signal lines takes more than 2ms, I'm screwed.  But it runs quite quickly.

4.  After line 16 is done, I return to state #3 for any iterations of modifier-only signals or character+modifier repetition.  Then, when that's all done, I return to state #1.

Conceptually, my code is similar to what numist.net did.  The numist.net code does all character set-up in the 2ms high time (after scan line 16 goes low-to-high and before scan line 9 goes high-to-low).  My state machine allows me to set up the output line bytes at any time before scan line 9 goes low.

Driver software

Unfortunately, the Arduino Mega2560 does not support native XON/XOFF flow control.  I was too lazy to do that myself, so I wrote a Python program and had it set up with timing delays between characters.  The carriage return would be given special treatment, delaying 1.5s after that was sent.

More physical build

As it turns out, my ribbon length was long enough for me to be able to put everything under the original keyboard.  The limit switch was glued to the far left.  Just inside of that was my breakout board, followed by the fake FFC, and the original circuit board.  To the right of all that, I put in the Arduino.  This meant having long patch wires stretching from the breadboard over the original PCB to the Arduino, but it did reach, and no one would know.

The original transformed DC voltage was coming in around +8VDC, so I was able to tap into both the high and low lines for that, and plug them in to Vin and GND, respectively, on the Arduino.  I haven't seen any ill effects of having the Arduino and original circuit board drawing power at the same time, but bear in mind that my current configuration has disabled the LCD display panel.  The power lines still continue onward to provide power to the original circuit board.


The original case has a little cut-out rectangle on the interior of the cord storage area!  That was quite a fortunate thing.  That allowed me to connected a USB-A cable to the Arduino, and tuck it in along the side of the box, flowing it outward near the 110VAC cord.  So, while not properly secured with strain relief, etc., etc., it meant that I could mostly close up the case while still having an Arduino programming and communications path.  (If that didn't work, I would consider adding a bluetooth board or something like that.)

Gotchas

Carriage returns on PCs are implemented using character 10, which technically is a "line feed" character.  When you edit a text file in Windows, the file already magically knows whether or not it's Unix-based (CRLF = both chars 13 and 10) or Windows-based (CR = char 10 on its own).  To treat them the same, I made the Arduino code recognize char 10 as a carriage return, and ignore char 13.

Arduino has problems with memory usage.  In my case, I didn't have to use the PROGMEM construct, but if you use more memory, you could start to see very random crashes.  Numist.net referred to this.  In my case, I didn't have to use PROGMEM.

The machine gets very confused if I have the Arduino connected to my PC via USB at the same time as when I'm booting up the typewriter.  My implementation causes the 7 and 14 lines to be high, thus blinding the system to the real state of the limit switch.  I'm in the habit now of unplugging USB, then turning on the machine, then letting it find the limit switch, then plugging in the USB.  If you get the sequence wrong, you can start over, and the machine will correct itself.

If you run the machine and start getting really weird characters, it's possible that the full boot sequence wasn't done, and the daisywheel didn't reset its orientation.  Do what a tech support person might tell you to do: remove the USB cable, power off the typewriter, and power it back on again, and see if that solves the problem.  In my experience, a reboot works -- not just for this typewriter but for a host of tech ailments.

Conclusion

I couldn't have done this without the existing code from numist.net.  Or, at least that saved me a bunch of time so I didn't have to sniff the actual signals emitted during a real key press.

In doing this project, I also found out rather accidentally that I'm 2 degrees of separation from numist.net in real life.

My initial goal was to make a teletype.  I'm only half way there.  At this point, I only have an impact printer.  But it's still totally satisfying to see it work.

I'm disappointed in the Arduino lack of XON/XOFF protocol support.  I could implement that myself, and it's possible it's available in a SoftwareSerial implementation, but it's not really worth the effort.

Next steps

I'd like write some software that generates the right text patterns for printing a grayscale image.  I'm sure others have done this, but for my purposes I'd like to do it in such a way that it is limited to the specific characters that this typewriter prints, and it takes advantage of the backspace, half backspace, REV, and INDEX functions natively supported by the typerwriter.

I'm also curious what it would take to 3d-print my own ABS daisywheel.  Ooo, and for that matter, what if the daisywheel itself were to print pixel patterns?  That's a head trip, innit?

Arduino sketch code

// This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.

// teletype
//
// This is Dave's hack project modifying a Brother SX4000 typewriter so that it's controlled
// by Arduino.  This is an output-only project. 
//
// Credit to numist.net for his work on this ca. 2010.  He has much tighter code for
// this, and it handles both inputs and outputs.
//
// Much of this project was created separate from the Numist.net work.  The keyboard mappings
// were constructed based on direct analysis of the device's keyboard circuitry.
// The rough idea of scanline and signal (sense) circuitry was implied by the keyboard
// switch layout, but I probably gleaned some of the concept from Numist.net when I read
// about this several years ago.
//
// The main areas where I benefitted from Numist.net:
// 1.  There appears to be a need to repeat signals at least twice
// 2.  Modifiers appears to have to be sent in solo before modified chars.
// 3.  It appears you have to set all sense lines high before dropping any low.
// 4.  Using direct pin mappings for sense line writes

// Where my code differs from numist.net
// 1.  Conceptually I have a state machine with three main states
// 2.  My layout of the keyboard mappings is different than his.
// 3.  I'm still using individual pin inputs for scan lines.  I could switch, just haven't done so.
// 4.  I support ctrl+H and half-backfeed (superscripting).  I dunno, maybe he does, too.
// 5.  Mine was made 9 years after he was done.
// 6.  Mine is specifically for ATMega2560.
// 7.  Mine tries to handle the limit switch specially.  (I found that due to combination
//     sense 7 / scan 14,
//     if I set 7 initially as an OUTPUT pin, I plow over what the actual limit switch is trying
//     to indicate, mine being high when they try to show low, and thus I disable the switch.
//     So, on boot-up, I set that as an INPUT pin until the system quiesces.
//     That also assumes my Arduino will be activated when the typewriter boots up.
//     If for some reason the typewriter seeks the limit switch some time after boot,
//     then it might grind.)
unsigned char outbuf[80];
unsigned char* outbufPtr = outbuf;
unsigned char charToPrep = 'a';
unsigned char lastCharOut = 0;

#define STATE_WAIT_FOR_CHAR 0
#define STATE_PREP_PINS 1
#define STATE_WRITE_PINS 2

int curState = STATE_WAIT_FOR_CHAR;
int oldScanLineState = HIGH;
int newScanLineState = HIGH;

int modOnlyIterations = 0;
int totalIterations = 0;
int outCycleCounter = 0;

// This is how "long" we hold the key down while writing.
// In clock time, this is n * 18ms since each sequence
// of the scanline scans is 8 pins x 2ms plus 2ms high.
// I am doing this because numist.net did this, and
// when I only had one iteration, nothing was typed.
// So I'm hoping having multiple iterations of the same
// letter (kind of a press and hold, but short) does something
/// magic to make the system recognize the key press.
//

#define KEY_PRESS_ITERATIONS 2
// The typewriter does not react if KEY_PRESS_ITERATIONS is 1

// This section lays out the mapping of keyboard
// ribbon pins to Arduino Mega2560 pins, so I can
// address them by typewriter keyboard name.

// K01 is top left of keyboard, and is the first
// of the K01..K08 "sense" pin set.
// K09..K16 is the "scanline" pin set, the ones
// that the keyboard circuit drops low for 2ms
// at a time to try to trigger a low connection
// across a typewriter keyboard physical switch.
#define K16 53
#define K15 51
#define K14 49
#define K13 47
#define K12 45
#define K11 43
#define K10 41
#define K09 39
// K01..K08 aren't  used any more since I switched to using DDRC and PORTC on Mega2560.
// But it doesn't hurt to document the layout here.
// They do go in the reverse order compared to scanlines -- just the way it worked out.
#define K08 30
#define K07 31
#define K06 32
#define K05 33
#define K04 34
#define K03 35
#define K02 36
#define K01 37

// bytes is an array of values indicating what I'll
// use for pins 1..8 when a given scanLine goes low.
// The sense pin values are stored in bytes[9..16]
// so when scanline 9 goes low, I can just choose the
// precomputed value of bytes[9] without offsets
// for simplicity.  So unlike other arrays, bytes
// is allocated to allow values 9..16 as indexes,
// hence it is 17 bytes in size.

// modOnlyBytes was added afterward.  That allows me to
// send only the mod byte values in lead-up to sending
// the signals for the modified characters.  The easiest
// thing to do was to mirror existing bytes[]-oriented
// code and make the "bits to send" just source from a
// different place during a lead-up period.
unsigned char bytes[17];
unsigned char modOnlyBytes[17];
unsigned char *curBytePtr = 0;
unsigned char *curModOnlyBytePtr = 0;

// bitmaps contain 8-bit values representing the
// values 1..8 (3 bits) for which scanline is held
// low for a given key, and 1..8 (3 bits) for which
// sense line is triggered low when the scanline is low.
// Each byte value in bitmaps has sense line in the low
// 4 bits, and scanline in the high 4 bits.
unsigned char bitmaps[256];

// modifiers contains a bitwise value of 1, 2, or 4
// to indicate whether the shift, alt, or code key
// must be held down simultaneously with a key in order
// to get a particular key emitted.  For example,
// a capital A not only is indicated by the scanline/
// sense line values in bitmaps['A'], but also must be
// accompanied by a MOD_SHIFT value in modifiers['A']
unsigned char modifiers[256];
#define MOD_NONE  0
#define MOD_SHIFT 1
#define MOD_ALT   2
#define MOD_CODE  4

// sensePins and scanPins are ways
// for me to loop through the sense and scan pins
// one at a time without naming them by name.
//int sensePins[8];
// Sense pins are now PORTC
// bit 0 is pin 37, bit 7 is pin 30
// so if I put a MSB sense pin byte into PORTC
// then its lowest bit (what was sense 1)
// is now in 37, and its highest bit (what was sense 8)
// is now in 30.  Physically, this is using the second
// column of pins and in reverse order of what I was doing.
//
// For testing, sense 2 is now pin 36.
int scanPins[8];

// Sense lines trigger 2n2222 transistor
// to allow ground current to flow cleanly to
// kbd circuit board now, so a LOW does not saturate
// and a HIGH does.
// For that reason, too, sense pin 7 is a PULLDOWN
// on startup, not a PULLUP.???  Or should I control
// that using some other special mechanism?
#define SENSE_DEFAULT LOW
#define SENSE_TRIGGER HIGH

// curScanLine is used in the WRITE state to check
// each scanline for a high-low transition.
// It's meant to march through values 9..16
// so it's numeric keyboard pin values.
int curScanLine = 9;

int charRepeatCount = 0;
// if charRepeatCount is 1 or more when entering handleWaitForChar,
// it just re-uses the last determined charToPrep for output and continues
// to typing, decrementing charRepeatCount.  if charRepeatCount is zero,
// it pulls something into charToPrep using its normal means (e.g.,
// read from a static buffer, get char from Serial).
// .

void setup()
{
  Serial.begin(9600);
 
  // To send a file using Windows cmd, try
  // mode COM21 BAUD=9600 PARITY=n DATA=8 XON=on
  // copy yourfile.txt /B \\.\COM21 /B

//  sensePins[0] = K01;
//  sensePins[1] = K02;
//  sensePins[2] = K03;
//  sensePins[3] = K04;
//  sensePins[4] = K05;
//  sensePins[5] = K06;
//  sensePins[6] = K07;
//  sensePins[7] = K08;
 
  scanPins[0] = K09;
  scanPins[1] = K10;
  scanPins[2] = K11;
  scanPins[3] = K12;
  scanPins[4] = K13;
  scanPins[5] = K14;
  scanPins[6] = K15;
  scanPins[7] = K16;

  memset(bitmaps,0,256);
  memset(modifiers,0,256);
 
  // Map ctrl-H to backspace half.  Other code will have to run that twice
  // to get a full backspace.
//  bitmaps[0x08] = 4 | (  15 << 4 ); modifiers[0x08] = MOD_CODE;
  // Machine has both a 4/15+CODE (half BS) and 6/15+NONE (full backspace).
  // I just need the full BS and won't need to implement char repeat function.
  bitmaps[0x08] = 6 | (  15 << 4 );
  // Faked chars 30 and 31 to support REV and INDEX command.
  // REV is a roll up 1/2 line command
  // INDEX -- well, I think that's un-REV, I hope
 
  // (Per SX4000 manual)
  // REV (code+oh) = lower the paper a half line
  // INDEX (code+p) = raise the paper a half line
  bitmaps[30] = 5 | (  11 << 4 ); modifiers[30] = MOD_CODE;
  bitmaps[31] = 5 | (  13 << 4 ); modifiers[31] = MOD_CODE;
  bitmaps['1'] = 3 | (  9 << 4 );
  bitmaps['2'] = 3 | ( 10 << 4 );
  bitmaps['3'] = 4 | (  9 << 4 );
  bitmaps['4'] = 4 | ( 10 << 4 );
  bitmaps['5'] = 6 | (  9 << 4 );
  bitmaps['6'] = 6 | ( 10 << 4 );
  bitmaps['7'] = 5 | (  9 << 4 );
  bitmaps['8'] = 5 | ( 10 << 4 );
  bitmaps['9'] = 8 | (  9 << 4 );
  bitmaps['0'] = 8 | ( 10 << 4 );

  bitmaps['!'] = 3 | (  9 << 4 ); modifiers['!'] = MOD_SHIFT;
  bitmaps['@'] = 3 | ( 10 << 4 ); modifiers['@'] = MOD_SHIFT;
  bitmaps['#'] = 4 | (  9 << 4 ); modifiers['#'] = MOD_SHIFT;
  bitmaps['$'] = 4 | ( 10 << 4 ); modifiers['$'] = MOD_SHIFT;
  bitmaps['%'] = 6 | (  9 << 4 ); modifiers['%'] = MOD_SHIFT;
//  bitmaps['6'] = 6 | (  9 << 4 ); cent
  bitmaps['&'] = 5 | (  9 << 4 ); modifiers['&'] = MOD_SHIFT;
  bitmaps['*'] = 5 | ( 10 << 4 ); modifiers['*'] = MOD_SHIFT;
  bitmaps['('] = 8 | (  9 << 4 ); modifiers['('] = MOD_SHIFT;
  bitmaps[')'] = 8 | ( 10 << 4 ); modifiers[')'] = MOD_SHIFT;

  bitmaps['-'] = 7 | (  9 << 4 );
  bitmaps['_'] = 7 | (  9 << 4 ); modifiers['_'] = MOD_SHIFT;
  bitmaps['='] = 7 | ( 10 << 4 );
  bitmaps['+'] = 7 | ( 10 << 4 ); modifiers['+'] = MOD_SHIFT;

  bitmaps['q'] = 2 | ( 11 << 4 );
  bitmaps['w'] = 2 | ( 13 << 4 );
  bitmaps['e'] = 3 | ( 11 << 4 );
  bitmaps['r'] = 3 | ( 13 << 4 );
  bitmaps['t'] = 4 | ( 11 << 4 );
  bitmaps['y'] = 4 | ( 13 << 4 );
  bitmaps['u'] = 6 | ( 11 << 4 );
  bitmaps['i'] = 6 | ( 13 << 4 );
  bitmaps['o'] = 5 | ( 11 << 4 );
  bitmaps['p'] = 5 | ( 13 << 4 );
  bitmaps['['] = 1 | ( 13 << 4 ); modifiers['['] = MOD_SHIFT;
  bitmaps[']'] = 1 | ( 13 << 4 );
  bitmaps['a'] = 2 | ( 14 << 4 );
  bitmaps['s'] = 5 | ( 12 << 4 );
  bitmaps['d'] = 5 | ( 14 << 4 );
  bitmaps['f'] = 3 | ( 12 << 4 );
  bitmaps['g'] = 3 | ( 14 << 4 );
  bitmaps['h'] = 4 | ( 12 << 4 );
  bitmaps['j'] = 4 | ( 14 << 4 );
  bitmaps['k'] = 6 | ( 12 << 4 );
  bitmaps['l'] = 6 | ( 14 << 4 );
  bitmaps[';'] = 1 | ( 12 << 4 );
  bitmaps[':'] = 1 | ( 12 << 4 ); modifiers[':'] = MOD_SHIFT;
  bitmaps['\''] = 1 | ( 14 << 4 );
  bitmaps['"'] = 1 | ( 14 << 4 ); modifiers['"'] = MOD_SHIFT;
  bitmaps[0x0a] = 2 | ( 15 << 4 ); // Treat LF as a carriage return+LF.  Ignore 0x0d
  bitmaps['z'] = 2 | ( 12 << 4 );
  bitmaps['x'] = 7 | ( 12 << 4 );
  bitmaps['c'] = 8 | ( 12 << 4 );
  bitmaps['v'] = 8 | ( 11 << 4 );
  bitmaps['b'] = 8 | ( 13 << 4 );
  bitmaps['n'] = 7 | ( 11 << 4 );
  bitmaps['m'] = 7 | ( 13 << 4 );
  bitmaps[','] = 1 | (  9 << 4 );
  bitmaps['.'] = 1 | ( 10 << 4 );
  bitmaps['/'] = 2 | (  9 << 4 );
  bitmaps['?'] = 2 | (  9 << 4 ); modifiers['?'] = MOD_SHIFT;
  bitmaps[' '] = 1 | ( 15 << 4 );
 
  bitmaps['<'] = 2 | ( 13 << 4 ); modifiers['<'] = MOD_CODE;
  bitmaps['>'] = 6 | ( 11 << 4 ); modifiers['>'] = MOD_CODE;
 
  for (int i='a'; i<='z'; i++)
  {
    int v = i-'a'+'A';
    bitmaps[v] = bitmaps[i];
    modifiers[v] = MOD_SHIFT;
  }
 
  // Pre-set the sense pins HIGH.
  // We draw them low as typing occurs.
  // But on boot-up, we don't want them to
  // be in a low state when we set the pins
  // to OUTPUT mode.
//  for (int j=0; j<8; j++)
//  {
//    digitalWrite(sensePins[j], SENSE_DEFAULT);
//  }
  PORTC = 0xFF; // Default everything "high".
 
  // ref https://forum.arduino.cc/index.php?topic=173099.0
  // I want to default output pins to HIGH before
  // turning on pinMode to avoid an accidental startup
  // condition, since output pins default LOW.
  //
  // Pin 7 gets special treatment for the boot period
  // so that the carriage can auto-seek and not
  // conflict with the Arduino's use of the pin.
  // After initial start-up, pin 7 will be disconnected
  // by the limit switch, so I can regain control.
//  for (int j=0; j<8; j++)
//  {
//    if (sensePins[j] == K07)
//    {
//      pinMode(sensePins[j], INPUT);
//    } else {
//      pinMode(sensePins[j], OUTPUT);
//    }
//  }
  // K07 is sense pin #6 in range of 0..7
  DDRC = 0B10111111;
 
  for (int j=0; j<8; j++)
  {
    pinMode(scanPins[j], INPUT_PULLUP);
  }
 
  delay(5000);
 
//  Serial.println("starting");

  // Assuming carriage did seek operation within
  // 15 seconds of Arduino start-up, now it's safe
  // to use K07 for output.
  PORTC = 0xFF;
  DDRC |= 0B01000000; // turn sense 6 to output

//  digitalWrite(K07, HIGH);
//  pinMode(K07,OUTPUT);
  outbuf[0] = 0;
//  strcpy((char *)outbuf,"1234567890\r!@#$%^&*()"); // not cool cast
//  strcpy((char *)outbuf,"abcdefghijklmnopqrstuvwxyz"); // not cool cast // works
//  strcpy((char *)outbuf,"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
//  int len = 0;
//  strcpy((char *)outbuf, "Inte");
//  len = strlen((char *)outbuf);
//  outbuf[len++] = 8;
//  len = strlen((char *)outbuf);
//  outbuf[len++] = '\'';
//  strcpy((char *)outbuf+len,"ressan");
//  len = strlen((char *)outbuf);
//  outbuf[len++] = 8;
//  len = strlen((char *)outbuf);
//  outbuf[len++] = 30; // Faked char for REV to superscript next char
//  len = strlen((char *)outbuf);
//  outbuf[len++] = '-'; // There is no tilde, superscript a hyphen?
//  len = strlen((char *)outbuf);
//  outbuf[len++] = 31; // Faked char for INDEX to un-superscript next char
//  strcpy((char *)outbuf+len,"t 0");
//  len = strlen((char *)outbuf);
//  outbuf[len++] = 8;
//  strcpy((char *)outbuf+len,"/");
  // Part of doing the strcpy above is that it adds a terminating 0 byte
 
 
  curState = STATE_WAIT_FOR_CHAR;
}

void handleWaitForChar() {
//  Serial.println("Wait for char");
  // This chunk of code is set up as its own state
  // to allow future management of serial IO or some such.
  // For now, it just loops through a fixed array of chars
  // to print.
#ifdef SUPPORT_REPEAT
  if (charRepeatCount > 0) {
    --charRepeatCount;
    curState = STATE_PREP_PINS;
  } else {
#endif
    if (Serial.available()) {
      int c = Serial.read();
      if (c == 0x0d) return; // ignore CR.  Support LF (0x0a) (10) only.
      charToPrep = c;
      if (charToPrep == lastCharOut) {
        delay(36); // let two passes of scanlines go by so it doesn't look like key was held down
      }
      outbuf[0] = c;
      outbuf[1] = 0;
      outbufPtr = outbuf;
    }
    if (*outbufPtr) {
      charToPrep = *outbufPtr++;
      // If it's not a mapped char, then use '?'
      if (!bitmaps[charToPrep]) {
        charToPrep = '?';
      }
//      Serial.print("Ready to prep pins for ");
//      Serial.println(charToPrep);
      curState = STATE_PREP_PINS;
#ifdef SUPPORT_REPEAT
      charRepeatCount = (charToPrep == 0x08)?1:0; // repeat ctrl-H next time we get in this function
#endif
    } else {
      // When in this state and there is nothing to type,
      // poll slowly (every 0.1s) for some new input.
      // No need for high frequency polling.
      // 300ms is fast typing speed (200 cpm)
      delay(300);
    }
#ifdef SUPPORT_REPEAT
  }
#endif
}

void handlePrepPins() {
//  Serial.println("handlePrepPins");
//  Serial.print ("char to prep is " );
//  Serial.println((char)charToPrep);
  // When we get into this state, it's assumed
  // that charToPrep has a single, typeable letter
  // to set up.  For that, we need bits to be twiddled
  // for setting on K01..K08, each of those bits only
  // set when certain scan lines K09..K16 go low.
  //
  // This state is all about getting bytes teed up
  // with the right values when the time-critical stuff
  // starts happening in handleWritePins.
  //
  // Since this set-up is all in-memory and not actually
  // reading or affecting pins, it can happen at any time,
  // regardless of real scan line high/low state.

  // It is assumed that this is called one time per output char.
 
  // Zero all the output values per scanline
  memset(bytes+9,        0x0, 8);
  memset(modOnlyBytes+9, 0x0, 8);
 
  // TODO: is charToPrep signed?  Can it be declared as
  // an unsigned byte instead?
 
  // scanLine will have a value 9..16
  int scanLine = bitmaps[charToPrep] >> 4;
 
  // signalLine gives a value 1..8
  // signalBit is that value, decremented to 0..7
  // and then set up as a mask bit, 2^(0..7)
  unsigned char signalBit = ( 1 << ((bitmaps[charToPrep] & 0x0f) - 1) );
//  Serial.print("bitmaps val is ");
//  Serial.println((int)(bitmaps[charToPrep] & 0x0f));
//
//  Serial.print ("Setting bytes[");
//  Serial.print (scanLine);
//  Serial.print ("] to or with ");
//  Serial.println (signalBit);
  bytes[scanLine] |= signalBit;
 
  int meta = 0;

  if (modifiers[charToPrep] & MOD_SHIFT) {
    int modifierScanLine = 16;
    unsigned char signalBit = ( 1 << (1 - 1) );
    bytes[modifierScanLine] |= signalBit;
    modOnlyBytes[modifierScanLine] |= signalBit;
    meta = 1;
  }
  if (modifiers[charToPrep] & MOD_ALT) {
    int modifierScanLine = 15;
    unsigned char signalBit = ( 1 << (5 - 1) );
    bytes[modifierScanLine] |= signalBit;
    modOnlyBytes[modifierScanLine] |= signalBit;
    meta = 1;
  }
  if (modifiers[charToPrep] & MOD_CODE) {
    int modifierScanLine = 15;
    unsigned char signalBit = ( 1 << (8 - 1) );
    bytes[modifierScanLine] |= signalBit;
    modOnlyBytes[modifierScanLine] |= signalBit;
    meta = 1;
  }
  curState = STATE_WRITE_PINS;
 
  // After we're done this state, no delay at all
  // in the overall loop.  We want to start the pin
  // detect phase right away in the next loop() iteration.
 
//  for (int i=9; i<=16; i++) {
//    Serial.print("bytes[");
//    Serial.print(i);
//    Serial.print("] = " );
//    Serial.println(bytes[i]);
//  }

  curScanLine = 9;
  oldScanLineState = digitalRead(scanPins[curScanLine-9]);
  curBytePtr        = bytes        + curScanLine;
  curModOnlyBytePtr = modOnlyBytes + curScanLine;
  modOnlyIterations = meta * KEY_PRESS_ITERATIONS;
  totalIterations = KEY_PRESS_ITERATIONS + modOnlyIterations;
  outCycleCounter = 0;
}

void handleWritePins()
{
  // In this state, we have all the output stuff
  // ready in bytes[] so that we can transfer its
  // info to the actual Arduino pins.
  // But, we have to do that in order of pins 9..16,
  // and on specific HIGH-LOW transitions of the scanLines.
  // We can't start part way through (e.g., starting
  // with output for scan line 12) else we'd miss the boat
  // on earlier scan lines.
  // We also must complete the pin assignments as quickly as
  // possible.  There is no documentation re: how long
  // the set-up time is before the typewriter microcontroller
  // starts reading pin states.
  //

  newScanLineState = digitalRead(scanPins[curScanLine-9]);
  if (oldScanLineState == HIGH
   && newScanLineState == LOW) {
    // bytes[9..16] hold the prepared bits
//    Serial.println("Saw high low on ");
//    Serial.println(curScanLine);
    PORTC = 0xff;
   
    unsigned char v;
    if (outCycleCounter < modOnlyIterations) {
      v = *curModOnlyBytePtr;
    } else {
      v = *curBytePtr;
    }
//    Serial.print("v is " );
//    Serial.println(v);
   
    if (v) {
//      delayMicroseconds(1800);
      PORTC = ~v;
//      char mask = 1;
//      for (int j=0; j<8; j++)
//      {
//        if (v & mask) {
////          Serial.print("Setting sense pin ");
////          Serial.print(sensePins[j]);
////          Serial.println(" to LOW");
//          digitalWrite(sensePins[j], SENSE_TRIGGER);
//        }
//        mask <<= 1;
//      }
 
      // All pins were set LOW as needed while scanline is low.
      // Hopefully, that happened pretty quickly.
      // Leave lines low for a bit, then reset them to high.
//      delayMicroseconds(1900-40);
     
//      PORTC = 0x0;
//      mask = 1;
//      for (int j=0; j<8; j++)
//      {
//        if (v & mask) {
//          digitalWrite(sensePins[j], SENSE_DEFAULT);
//        }
//        mask <<= 1;
//      }
    }
    // Bump to the next scan line to wait for its output
    ++curScanLine;
    if (curScanLine <= 16) {
      oldScanLineState = digitalRead(scanPins[curScanLine-9]);
      curBytePtr++;
      curModOnlyBytePtr++;
    } else {
      // re-init output state for scan line 9
      curScanLine = 9;
      oldScanLineState = digitalRead(scanPins[curScanLine-9]);
      curBytePtr        = bytes        + curScanLine;
      curModOnlyBytePtr = modOnlyBytes + curScanLine;
     
      ++outCycleCounter;
      if (outCycleCounter >= totalIterations) {
        // OK, all values are out now.
        // Allow the typewriter microcontroller to recognize
        // the set of 8 values on the sense pins
        // and type a character.
        // Then exit this state.
        PORTC = 0xFF;
        lastCharOut = charToPrep;

        delay(10);
        curState = STATE_WAIT_FOR_CHAR;
      }
      // else we keep in WRITING mode
    }
  } else {
    // If we didn't see a high-low transition, iterate
    // rapidly back to the top of the loop to look again.
    oldScanLineState = newScanLineState;
  }
 
  // No delay at the end of this function.  We want
  // rapid iteration to pick up the next scan line check.
}

void loop()
{
  switch (curState)
  {
    case STATE_WAIT_FOR_CHAR:
      handleWaitForChar();
      break;
    case STATE_PREP_PINS:
      handlePrepPins();
      break;
    case STATE_WRITE_PINS:
      handleWritePins();
      break;
    default:
      // should never get here
      curState = STATE_WAIT_FOR_CHAR;
      break;
  }
}

Python file transfer code

(I just pasted the code below here.  If you copy/paste, be sure to check indentation as Python is sensitive to tabbing.)
# To use this:
# python -m pip install pyserial
#
# I installed python from python.org
# and edit within a cygwin window.
# Execution is done from a Windows cmd shell
# Since I installed python without changing overall env vars,
# you have to do this in Windows:
# PATH %PATH%;D:\pythonsw
# D:
# cd pythonmine
# python sendfile.py
#
# This code doesn't quit at end of file for some reason.
# so hit ctrl-C when it stops typing.
# The delay of 200ms per char works.
# The code also roughly gives 1.5s for a carriage return.
#
# Further attempts could be made to wind down the delay.
# Its purpose is to keep the Arduino non-flow-control Serial
# line from overflowing since there is no native xon/xoff protocol
# so the best I can do is throttle the output on this end
# (without getting into writing a "send/receive" protocol).
#
import serial
import time
import sys

arduino = serial.Serial('COM15', 9600, timeout=.1)
time.sleep(2) # time for comms line to settle

if (len(sys.argv) > 1):
        fname = sys.argv[1];
else:
        fname = "filename.txt";
       
myfile = open(fname,"rb")
try:
    byte = myfile.read(1)
    while byte != '':
        arduino.write(byte)
        arduino.flush()
        if (byte == 10):
            time.sleep(1.3)
        elif (byte == ' '):
            time.sleep(0.1)
        else:
            time.sleep(0.2)
        byte = myfile.read(1)
finally:
    myfile.close()

arduino.close()

Tuesday, May 21, 2019

Roomba RC car mash-up


I was very lucky in finding a used Roomba at a garage sale a few years back.  I bought for all of $5.  And then I left it in a box, moved, and kept ignoring it.

Well, finally I found the time and got the nerve to try to do something with it, so I took it to the HackerLab and opened it up.

The box contained the Roomba, a charging station, the power supply, and an electronic fence emitter.

I guess part of the reason I was hesitant to play with it was because it would have been a messy ordeal.  After all, what do you expect from a $5 Roomba?

Being the hacker I am, I started unscrewing this and that, and didn't take the obvious path.  It turns out that Roombas are quite well designed.  Most everything clicks apart.  The dust bin was super easy to remove, and yeah, it was full of caked-on dust.  The "filter" was so clogged up that it, itself, was pretty clean.  All the other dust was blocking air passage through it.  After a short time and using a vacuum to vacuum the vacuum, I had a nice, cleaner machine.

The only broken part I could find was that one of the wheels was missing some of its tread, but it didn't affect the movement.

I then went to google to find out how to get it to interface with an Arduino.  I recall having done that before, and that led to another reason I was scared to touch it.  Roombas of ca. 2005 can be pre-2005 or post-2005, and the earlier ones do not support SCI interfacing.  (Credit to www.jimisaak.com for the pointers here -- http://www.jimisaak.com/Home/stem-4-all/roomba-arduino.)

I flipped my Roomba over, and didn't see the model number, simply because it was blocked by the spinner brush.  But I did find a 2005 trademark.  As it turns out, it's a model 4130.

I charged up the battery a bit, and then hit the "clean" button, and it worked!  14 years later, and it still runs!

Wiring and wake-up

The web site mentioned earlier said I could interface with the machine through its port, and then talk to it via 57600 baud serial Rx/Tx. I found the little 8-pin DIN female port for interfacing.  It's not exactly a mini 8-in DIN like they had in old computers, but roughly it fits.  If you view it from one angle, it looks like this:


From this perspective, it has pins 1 and 2 on the top row, 3/null/4 on the middle row, and 5/6/7 at the bottom.  I avoided pins 1 and 2.  They carry Vpwr, unregulated.  Pins 3 and 4 are Rx and Tx, respectively.  Pin 5 is an active low signal to wake up the Roomba.  Pins 6 and 7 are ground.

So as an initial test, I wired up a switch.  It went from pin 5 to the switch to a 1k resistor and then back to either pin 6 or 7.  After  to a 1k resistor and over to ground. 


I clicked the switch and the Roomba woke up.  Test complete!

SCI support?

The next step was to wire up to the Arduino.  I was fortunate enough to have an Arduino Mega2560 handy.  And the native Arduino examples made things super easy.  There's a basic example called MultiSerialMega that does the trick.

The sketch talks to the Serial Monitor over the usual Serial object, but also communicates to anything else over Serial1.  Anything you type into the Serial Monitor gets conveyed to Serial1, and anything received on Serial1 gets printed to the Serial Monitor.  The only change required was to modify the Serial1.begin(9600) line to communicate at 57600 baud instead.

As with anything, before connecting any wires between the machines, I only connected the Arduino to my PC USB port, then built the sketch, and uploaded it to the Arduino.  That way, I'd have some certainty as to what the pins would be doing.

I then wired things up.  The Mega2560 has TX1 on pin 18, and RX1 on pin 19.  So I connected Roomba 3 (RXD) to Arduino 18 (TX1)
Roomba 4 (TXD) to Arduino 19 (RX1)
Roomba 5 (Wake-up) still connected as before
Roomba 6 or 7 to Arduino GND

With the Roomba powered off, I started the Arduino Serial Monitor, and clicked the Wake-up switch.  And that gave me a happy message that my Roomba was from a time point in 2005, which meant it probably would talk SCI.

SCI

The SCI command protocol is pretty basic.  It has single byte opcodes and operands.  The number of operands depends on the opcode.

Many of the opcodes and operands would end up being byte values that would end up as non-printable characters.  So I had to modify the MultiSerialMega example to make things more interactive.

The loop() code already had a sequence where it would check for available input from Serial, and then read() the value.  But in its original form, it would transmit that right away to Serial1.

Instead, I started make conditional blocks, so the sketch would respond to different "commands" that I would type.  If the character read was an 'S', it would send a "start" and "full operation" set of opcodes to the Roomba.  If an 's' was seen, it would start the spinner motor.  'x' would turn off all motors.  'X' would send a power-off command.  All those simply worked.

The set of SCI commands is interesting.  You can find it documented at various places online, like here.

An example set of code would be like this:
int inByte = Serial.read();
if (inByte == 'S') {
  Serial1.write((byte)128); // Start
  delay(120);
  Serial1.write((byte)132); // Full control mode
}
if (inByte == 'x') {
  Serial1.write((byte)138); // Motors
  Serial1.write((byte)1); // 2^0 bit is the side motor
}
if (inByte == 'X') {
  Serial1.write((byte)138); // Motors
  Serial1.write((byte)0); // All motors off
}

LEDs

The LEDs are controllable by SCI, but different ones have different capabilities and colors.  The main spot, clean, and max buttons have plain green LEDs.  To the right of those is a blue LED for "dirt detect".  Each can be turned on or off using the four lowest bits of the first data byte operand of opcode 139.

The next LED to the left is the "power" button's.   Its color and intensity are set by the second and third data byte operands of the command.  Finally, the leftmost LED is the status indicator, and its color and on/off setting is determined by bits 4 and 5 of the first data byte.

As such, doing a Cylon-style LED sweep is a little complex.  For a basic implementation, I set up a couple of byte arrays that would set the right bits at the right times for each of the first and third bytes, and always set the "power" button's color to green.

I used the same structure as above, but that was (and remains) hokey.  I was running a "for" loop within the loop()  function, thus hogging the Arduino CPU while cycling lights.

A better implementation would be to use a state indicating I'm in Cylon mode.  When in that state, every time I get called in loop(), I would check the current time, do some dividing and modulo'ing based on frequency of state change, and compute a byte offset into the arrays, and then send the right value if it's different than before.

Gremlins

While still using the "for" loop approach above, I changed the delay time between LED cycle changes.  At some values, it would make it through a few iterations of the LED sweep, but then quite disturbingly, it would kick the Roomba into "clean" mode!  Fortunately, there was still the emergency stop button (physically hit the power button).

Why would it do that?  The best I can guess is that I'm running too fast for the Roomba, or maybe overrunning its input buffer.  Then, as I send in an operand for lighting up lights, the Roomba ends up seeing that operand as an opcode instead (opcode 135, 136, 137, or 138 possibly could get the Roomba moving).

The behavior changed as I'd adjust the delay time between opcode sends, but I lost interest because I had other things to try.

Music

The Roomba can produce "songs".  You can store up to 16 songs, each having 16 notes.  Each note also has a duration, specified in 1/64 second increments.

Before long, I had my Roomba playing "Baba Yetu" and a rough approximation of a Pac Man starting theme.

But most songs are longer than 16 notes.  Both of the ones I attempted were.  I tried splitting the songs across multiple 16-note songs, and that worked pretty well.  But just like today's streamed music, if you play two songs that were joined together on vinyl, they end up having a hiccup between songs nowadays.  This happens on Arduino because it takes time to send a new "play song" opcode and operand.

For note durations, I used these kinds of values
1/4 note = 40/64ths
1/8 note = 20/64ths
1/16 note = 10/64ths
1/32 note = 5/64ths
That didn't play all that well.  The 32nd notes come out stuttered.  So it seems that while the Roomba supports 1/64th-second increments, there's a real life lower bound for the minimum duration of a tone.

Time to move

At this point, I tired of playing with the SCI interface.  I hadn't had it tell me the state of sensors yet, but trusted that that would work.  Instead, I wanted to clean up the interface, using a real mini DIN-8 connector instead of jumper wires, and do other stuff with it.

A trip to the Goodwill yielded four great things:
- $1.50 = A purple USB-A cable.  If I'm going to interface my PC to an Arduino, why not do it in purple?
- $1.50 = A serial cable with the right mini DIN-8 headers!
- $2.49 = A remote control Hot Wheels (27 MHz) car
- $14.99 = A Shark Lift-Away vacuum cleaner.  Score!  Okay, that's not relevant to this blog, but still, score!

My new project: take the Hot Wheels car apart, and have its electronics move the Roomba.

The Hot Wheels RC car

This is the Hot Wheels 27 MHz radio-controlled car. that I got.  Normally the body goes cleanly onto the chassis, but I've already messed with it, so it's loose here.  Slick, right?  As one of the guys at the lab said, I would have loved to have had something like this when I was a kid.
Hot Wheels RC car and transmitter

RC transmitter
I plugged in some batteries (3 AAAs in the remote, 4 AAs in the car) and tried driving the car around a bit, just for fun.  It drove around fairly well.  It wasn't all that fast, but that's to be expected.  Changing rapidly from forward to reverse would make the car spin out nicely.  I couldn't get the front wheels aligned, so it never really drove straight.  There is an adjustment for that, but its step granularity is too large.  I'm guessing that's a part of why it was at Goodwill. Or maybe someone much younger than I outgrew it.

The remote control has front/back controls on the left rocker switch, and right/left controls on the right rocker switch.

The middle button is like a "turbo" mode, and when in that mode, the forward thrust is engaged (overriding a neutral or reverse position on the thrust button, if that's pressed at the same time).  When in turbo, it also lights two blue LEDs (those clear rectangular things on the image below).

I took the car body off the chassis to expose the innards. The antenna wire was soldered to a hunk of copper tape, and that was stuck to the roof of the body.   I just peeled that off.  The circuit board processing the radio signal and controlling the motors was more complex than expected.
Circuit board under side
The 16-pin chip on the board is an MX1508.
MX1508 on board
I googled around a bit and found the MX1508 is a motor controller chip.  There are some example MX1508 boards out there that show how it's usually set up with capacitors in certain places.  Those pages roughly corresponded to what I was seeing on the car's board.  (What's also fun is that the price of that chip alone on some sites is more than what I paid for the toy.)

I was hoping for something where I could tap in to the radio interpretation signals to figure out which button was pressed.  One possibility would have been to pop off the MX1508, and solder to the pads, but it looked a bit intrusive and risky.

Instead, I decided to check the motor outputs.  There are two pairs of wires coming out.  The red-white line was controlling direction, and red-blue controlled thrust.

After driving the car around, it wasn't clear if the motors were just getting pure positive and negative DC, or if some kind of PWM was going on.  I de-soldered the motor-driving wire pairs, and tested them on the voltmeter.  What I got was somewhat expected of a cheap RC car:

Forward thrust was a about +5VDC, reverse was -5VDC.
Similar happened for the right/left turn motor voltages.
The voltage would drop a tad if both buttons were pressed at the same time.

Interestingly hitting the turbo button would give the voltages a bump of about 0.4VDC (positive or negative), in addition to turning on the LEDs.

Converting +/- voltage to Arduino inputs

My goal then became converting the motor voltages to something I could use as Arduino inputs.  But the Arduino would be happiest (i.e., I wouldn't risk killing the pins) if I could use a 0 to +5VDC range for the input pins, avoiding the negative voltage and the Turbo voltage boost.

So I drew up a circuit diagram.  I basically wanted some diodes to act as dams to prevent negative voltage, and subsequently I figured I'd use optocouplers to keep the Arduino voltages isolated from the RC car voltages.  Without involving optocouplers just yet, it would be sufficient to just wire to LEDs and appropriate resistors, and make sure I wouldn't burn out the LEDs.

Circuit sketch
Initially, I just wired one path: White -> negative diode -> positive diode -> voltmeter black -> voltmeter red -> red.  (I might have that backwards, but close enough...)

So in the initial measurement I had a diode, no LEDs, and a voltmeter to measure.  In just testing one rocker switch (e.g., left/right affecting the red/white line), I would get +5V in one direction, and something weird like -0.10V in the opposite direction.  The opposite movement wired to the other wire, would give me -5V to +0.10V.  So, for the "negative" side, I could still get a 5.1V delta, and use that, just by orienting the wires differently.

That led to the diagram above and the use of optocouplers.

In practice, the wiring of the circuit took on a slightly different but nicely parallelized look.  I started with a simple set of passives, directly soldered (no breadboard), just to see if I could light and not burn out some LEDs.  Effectively, the same would happen for the LED inside an optocoupler.
"Half rectifier"? for changing +/- voltage to two positive voltage lines
The photo above is the thrust wiring (red/blue wire pair).  There probably is a name for this thing.  It's kind of like a "half rectifier".  It's basically able to take both positive and negative 5VDC and turn it into two lines, each showing a range of -0.1VDC to +5VDC.  With the 220 ohm resistors, it limits current through the LEDs.  You can see the actual physical wiring doesn't directly match the sketched diagram, but it has a nice parallel look to it.  There's electrical tape (ugh!) thrown on to some of the wires to avoid accidental contact.

The nice thing about this is that it's entirely reversible.  I wanted "forward" to be yellow (headlights) and "back" to be the red LED.  If I got it wrong, I'd just switch the solder connections to the blue and red wires.

The exact same circuit was used for the right/left turn direction indicators.  I longed for red (port) and green (starboard) lights for that, but only got a red bulb and a clear bulb, and even the clear one shone red when powered.  Sad.

In the end, the real range of voltage ends up being about 5.5VDC before hitting the resistor.  That's because the voltage range is 5.0 (basic button press) + 0.4 (if turbo is turned on) - (-0.1).

(V-Vdrop) = I * R
Supposing 1.8V voltage drop for both red and yellow LEDs, I get
(5.5V-1.8V) = I * 220ohm
I = (5.5V-1.8V) / 220ohms = 16 mA current across the LED, which is safe (max 20mA, probably).  I could adjust accordingly for optocoupler's specs for its internal LEDs.

Here's what it looks like wired up with the thrust and turn connections.  (I also have the battery power wired through my own switch instead of using the native one.)  Eventually, I will remove the LEDs and connect to optocouplers instead, and swap in the correct resistors.  In this way, a variance of 0.4V due to turbo won't greatly affect things.  As long as the LED lights up, the optocoupler will let it's transistor do its thing.
Car wired up with to demonstrate LED signal detection instead of motors
With this, I need four optocouplers, one each for forward, reverse, right, and left.

I also can detach the boost LEDs.  They're through-hole, so they should be pretty easy to de-solder, and I can use their signal to light up a fifth optocoupler to know when that button is pressed.  Granted, when that button is pressed, it also forces the "forward" button to be pressed, but I can distinguish that in software by checking the state of the turbo signal, and only consider the thrust signal when turbo is off.

Here are some sample images of what the car looks like when I'm pressing the buttons.

This is backwards and right at the same time.

 And this is forwards (yellow) and right at the same time

Full build

I found a Mini DIN-8 connector and figured out its wires so I could have a solid connection to the Roomba.

The most important things for my purposes are:
- Roomba V+
- Gnd
- Rx
- Tx

Power

I wired Roomba V+ and Gnd through a buck converter, and adjusted it to product 9V.  That fed into the Arduino Vin/Gnd pins by way of a cheesy lamp cord switch.

I then built my own PCBs on the HackerLab CNC 3040 mill.  After an initial attempt to render the board as double-sided and mimicking the cross-wired resistor/diode thing I had earlier, I realized how clumsy that was to solder.

The second PCB iteration had me using a one-sided board for each signal.

Shield

At some subsequent iteration, I decided to make it into an Arduino Mega2560 shield.  I ordered up the headers that have 23mm length.  The ones I got were from Amazon.  I got two kinds to compare, but either would have worked.  One was "ELEDIY 40-Pin Stacking Header Extra Tall Header for Arduino/Raspberry Pi (pack of 5)".  The other was "Arduino A000040 Stackable Female Header for Shields - 1x8 and 1x6 Position, 23 mm H(Pack of 3)"  For the latter, "pack of 3" meant you got three 1x8 headers, and three 1x6 headers.

There are other header pins out there, but 23mm length is the important part for me.  I didn't want Arduino pins or components touching any of the shield copper, and there would be a lot of exposed copper since I was milling my own PCB.

Designing the shield itself was a bit cumbersome but doable in Target 3001!.  First, there are various places on the web that describe the pin locations, but nothing really in x/y text form.  The important thing there is that there's a set of headers at the top of the board that are offset 0.06" left from the others.  Then, there were considerations for creating headers, which was necessary to ensure circuit isolation for them when engraved.  Finally, I had to rejigger some of the diode pads in case I used old-school through-hole diodes, some of which have fatter wires.

I also had to consider having access to the reset button on the board, because I was finding that I had to stop the system and restart it now and then.  That's especially important because I connect to Serial1 on startup and never after, so if I ever cycle power on the Roomba, I have to cycle power on the Arduino afterward.

In the end, I built a shield that met these requirements:
- Vin and Gnd could be shunted in from buck converter + and - outputs running at 9V.
- There would be inputs for each motor's wire pairs.  So there would have to be 4 inputs running in the range of -5V to +5V.
- The motor inputs would "half rectify" and feed their output to optocouplers.  Arduino Vcc and Gnd would go through the opposite sides of the optocouplers to generate a normally HIGH signal, and it would go LOW upon button press.
- Access would still be available to the reset button. (I have a knock-off Sainsmart Arduino Mega2560, one that has this reset button.
- Rx and Tx lines were presented as headers so that I could wire the corresponding Tx and Rx lines from the Roomba.

As it turns out, I could still use the third "half rectifier" board I'd constructed earlier.  I unwired one of the RC car's "turbo" LEDs, and checked its voltage and amperage.  I didn't know the LED specs of my remaining optocoupler, but figured it would be close enough compared to the "turbo" LED.  I also figured the car must already have some kind of current limiting mechanism for its LED, so I could rely on that for my optocoupler LED.  So I changed my "half rectifier" board, replacing its 300 ohm resistor with a 1 (one) ohm resistor.  I made that wire into another input on the Arduino Mega2560, which was available (uncovered by the shield).

Coding - movement

Much of the basis of the coding of the Roomba was straightforward.

The loop() function would regularly poll to see if any of the direction buttons had been pressed.

The initial iterations of the code looked for on/off transitions.  A transition from neutral to on was treated as a momentary button press.

SCI has command 137 for setting both radius and velocity at once.  The format is:
byte 1 = 137
byte 2 = velocity high
byte 3 = velocity low
byte 4 = radius high
byte 5 = radius low

Velocity is a signed 16-bit quantity ranging from -500mm/sec to +500mm/sec.  -1 and +1 have special meanings for spinning in place clockwise and counter-clockwise, respectively.

For thrust, a global velocity variable, vel, was kept and limited to a range of -2..+2.  In this way, a single press forward would move at a predetermined low speed, a second press would make the velocity increase, and any subsequent forward presses would stay at speed +2.  Similarly, the speed could be reduced back to zero, or even put the Roomba in reverse.

https://www.usna.edu/Users/weapron/esposito/_files/roomba.matlab/Roomba_SCI.pdf

For turns, it was trickier.  The SCI interface allows you to specify the radius of movement.  If you look later in the spec for information about sensors, you can request Packet Subset 2 and get its "Angle" computation.  That value is computed based on a 258mm wheel separation.  In more recent Roomba 600 Open Interface specs, the value drops to 235mm.

Radius is a signed 16-bit quantity ranging from -2000mm to +2000mm, with special value 0x8000000 representing "straight".

I modeled the turn direction similar to how I'd done velocity.  Initially I'd envisioned having multiple values, and doing some math to compute different angles.  However, from a user interface standpoint, it became clear that a simple RC car-type interface would probably have been best.  As it is, I ended up with a simple range:
- Three clicks = rotate in place
- Two clicks = tight turn
- One click = gradual turn
- zero position = go straight.

Also, positive velocity with positive radius is a LEFT turn, whereas positive velocity with negative radius is a RIGHT turn.

If you think of these as indexable, it takes on a weird array structure like this:
long radiusValues[] =
{
 -1 // counter-clockwise
, 300 // tight LEFT turn when positive
, 1000 // gradual LEFT turn when positive
, 0x8000000 // special straight value
, -1000
, -300
, 1
};

Even with that, my code isn't prepared currently to handle negative movement followed by a "spin" command, because in those conditions, I really should be swapping -1 and 1 values.

In a maze running mode, it probably would be better to have a simple left/straight/right set of values, and set the radius value equal to the wheel separation distance.

"Turbo" = vacuum on/off

When I added recognition of the Turbo button, I re-encountered the problem where the native RC Car turbo would light LEDs and put the car in forward motion.  I put Turbo button detection early in the event detection if/else list, and made it so that other buttons would be ignored when Turbo transitioned low to high.

Then, it was a simple matter of retaining vacuum on/off state, and toggling it.  When it toggled and ended up in an "on" position, I would fire up a Motors opcode (138) followed by an operand turning on all vacuum related motors (0x7 = Main 0x04 | Vacuum 0x2 | Spin 0x1).  Turning off was the same, but using operand 0.

Sensors

After driving around a bit, I found myself crashing the Roomba into things and then having to rush over and lift it up.  Since the Roomba was running in Safe mode, it would reset upon wheel drop, and I'd have to start over again.

I added recognition of sensors as the first sequence in the loop() function.  That way it could detect a bump or wheel drop first thing.  In my initial iteration with this, detection of any event like that would stop all motors and set velocity to zero.  However, I need to be able to back out of such a condition without resetting the Roomba.  My idea on this is that I'll set a timer, recognizing when the crash occurred, stop all motors, and give the driver about two seconds to compensate.  After those two seconds are up, the normal sensor detection would kick in again.

If I don't use a timer, it will run through the loop, find that the bumper is still engaged, and stop motors again.  Since the loop does event (and control input) detection every 100ms or so, I'd never catch it in time and it would just keep stopping, even if I were to mash the buttons.

The biggest issue with sensors is the actual implementation of the API.  The sensor API has me send in a two byte command.  The opcode is 142 followed by an operand value of 0, 1, 2, or 3.  The Roomba returns 26, 10, 6, or 10 bytes in response to the command.  Operand 0 is saying "Send back the results of packet subsets 1, 2, and 3 in sequence".

Packet subset 1 is the one that provides the sensor information, including bumper, wheel drop, motor overcurrent, cliff detection, wall detection, and dirt levels.

Packet subset 2 provides information about remote control button presses, on-machine button presses, distance, and angle.

Packet subset 3 provides underlying electronics info, like battery charge, temperature,

For my purposes, I was only interested in Packet subset 1, and even then, only the first byte.

However, after sending in bytes {142,0} it wasn't clear how the timing would work, and how I should handle the possibility of transmission errors.  If I were to read 10 bytes back from the Roomba, how would I know if all ten were ready to transmit?  Does the Arduino Serial1.read() function block?  It seems it would not, because there's also a Serial1.available() function.

Furthermore, the Serial1 line could still have garbage on it at startup.  Remember that in early experiments with the Roomba, I would just receive and echo Serial1 characters, and that is when I saw the big string reporting the birth date of the machine.

I added code to wait and discard input from Serial1 in the initial boot-up phase.

But it was less clear how to handle the problem of the returned data.  I ended up using a very crude iterative loop, waiting to read each of the ten response bytes, and bailing after a brief period (250 milliseconds?) had gone by.  In initial experiments where I'd block and keep waiting for input, I would sometimes get into an infinite loop and then the robot would drive into a wall with no ability to control it (because the Serial1 check code wasn't relinquishing control back to the main loop() function).

So at this point I have these issues
- improper clockwise/counter-clockwise spin motion when in reverse
- need to have Serial1.read() in a breakable loop in case of insufficient sensor response data
- cannot stop the Roomba robot without causing Safe mode to end (lift robot, drop wheels)
- does not restart Serial1 if the robot stops and restarts

I also have had several requests to make the robot emit some kind of epithets or similar upon sensor activation.  To do that, I'd need to spend a bit more on an MP3 audio board for Arduino, and an amplifier and speakers.  I'm not sure if I want to make that investment yet.  Adafruit provides a fully functioning shield, minus speakers for $36.

There are some out there like this:
HiLetgo Mp3 Lossless Decoders Decoding Power Amplifier Mp3 Player Audio Module Mp3 Decoder Board support TF Card USB, $5.50


but those just have buttons to play back MP3 files with no control over which to play.  It may be hackable to get one, remove its soldered buttons, and replace those with FETs or similar.  However, I think some button debouncing on the device might cause you to have to scale back the frequency of Arduino-controlled "button clicking".

Another is:
Aideepen 5PCS DFPlayer Mini Mp3 Player Board YX5200 Module Support TF Micro SD Card U Disk Audio Music for Arduino, $14 for 5 ($2.80 each).
These look much more promising.  You can just trigger a pin to play back a sound.

HiLetgo 2pcs Arduino mp3 Player Mini MP3 Player Audio Voice Module TF Card U Disk Board DFPlayer Audio Voice Music Module
has a link to a nice YouTube video, showing it in action, and it looks dead simple to wire up, so long as all you're doing is playing back pre-recorded sounds.

The DFPlayer looks more promising than the older WTV20.  The WTV20 appears to require an AD4, mono audio file, and it seems (not sure) that DFPlayer is more modern, allowing for MP3 files natively (but are there restrictions?).  To interface with the DFPlayer, though, I need to use my Serial2 lines to send it Play, Pause, and Next commands.  It also would need GND and 5V from the Arduino, and I've already stolen those pins for the Turbo optocoupler, I think. Otherwise, it seems pretty simple to use and integrate.


Wednesday, June 20, 2018

6x6x6 LED cube

At some point in every hacker's life, one should make an LED cube.  And, yes, I mean it should be bigger than 1x1x1 or 2x2x2.

There are so many videos and tutorials about how to make these.  I figured I'd write about mine, more as a reminder of what to do when I make another one, and techniques I found for making things easier.

LED cube dimensions

Well, it should be a cube, right?  So it should be n x n x n in size.  I chose to make mine 6x6x6, mainly because that would give me enough lights to make interesting patterns, and getting much larger (8x8x8) would mean having over double the amount of soldering I'd have to do.

A 6x6x6 cube has 216 lights.  8x8x8 would mean having 512.

Some day... some day I'll do a 7x7x7 or an 8x8x8.  Once you get to that level, you can start playing off of old fonts and drawing letters of the same resolution as an Apple II.

Material selection


Which LEDs?  Well, there are several basic factors here:
1.  Size.  The first cube I made was with 5mm LEDs.  The second one used 3mm ones.  The 5mm cube will be larger, of course, and that means it gives you more room to work.  There is a price difference, though.  From what I've been seeing lately, the 5mm ones are about twice as expensive as the 3mm ones.
2.  Color.  This is kind of up to you, but I've seen some cool ones in blue, so that's what I went with the second time.  The first cube I made used white, and it didn't seem interesting enough.
3.  Light style.  There are lots of choices these days -- cubey ones, short domed ones, "regular" tall domed ones.  Price is a factor, again.  The regular ones are less expensive.  Also, you probably should consider viewing angle.  Things like surface-mounted LEDs don't typically have a lot of diffusion or range of visibility.
4.  Diffusion.  From what I've seen online, you want diffuse LEDs.  If you get clear ones, you don't get the same visual effect.
5.  Forward voltage drop.  Different LEDs have different voltage drop characteristics.  Depending on your resources (i.e., how many of what kind of resistors you have), you may choose one style over another.  Realistcally, though, you should buy resistors of the right type that matches your voltage and current needs.

In my latest build, I used a big bag of 3mm, blue, diffuse LEDs.  They have a stated voltage drop of 3.0-3.4V, though I measured it more in the range of 2.8 to 3.1.  The short leg of each LED is 17mm, and the long leg is 19mm.  The ratings suggest you should only have a sustained current of 20mA through them.

(Unfortunately, most LED specs shown on eBay do not include details about lead lengths.)

I just ordered another set of LEDs that are still the 3mm type, blue, diffuse, and they came in with leg lengths around 28mm, much longer than the first blue ones I got.  The new ones are eBay, "1000 Pcs Diffused Led 3Mm Color Blue Light Super Bright".  With those, I could perhaps get 0.8" to 1.0" separation between LEDs on each ledkebab (see below) and use up much more board real estate.

What kind of wire?

For the kind of cube I make, I use bare, tinned copper wire, and I try to get a kind whose gauge is roughly the same as that of the LED legs, so 24 AWG or 22 AWG.  If you get much thicker, there's a greater disparity in heat absorbtion between the wires and the LEDs, making it harder to solder properly.

PCB

For my purposes, I am cutting my own printed circuit boards at the local HackerLab.  I do isolation engraving there, so we're cutting away copper from a surface material, leaving isolated traces behind.  That means I have to use FR-1 type boards -- FR-4 / fiberglass, while more prevalent, is not allowed for health reasons.  I use a single-sided, 6" x 6" board for the base of the cube.  The only place I've found to source that is inventables.com.

There are other sources for FR-1 boards, but most are 6"x4" at their largest size.

Tools

Something for making an accurate jig (I used the laser cutter; you could use a drill press.)
Something for helping bend column guide wire circles (I use a narrow screwdriver)
Something for making accurate spacers (again, I could use the laser cutter.  For sake of ease, I used a miter saw with some scrap 0.2" ply.)
A mechanism for cutting your PCBs (I use the CNC 3040 at the HackerLab and do isolation engraving using Target 3001! software.)
A soldering iron, and related consumables.
A solder sucker and/or desoldering wick in case of mistakes.
A solder filter fan and/or a filtration mask for safety.
Eye protection

Steps

1. Measure your materials.

Start off by measuring your LEDs, both the outer diameter of the bulb, and the length of the leads.

Using the short lead's length, figure out how far apart you want your LEDs, keeping in mind that the short leg of one will bend flat and touch the other.  You won't want to land the tip of the bent leg of LED #1 to land right at the flat bottom of LED #2.  That can lead to having too much soldering heat where you have the plastic casing, thus melting LED #2.

What I got for mine was about 17mm for the short leg length.  The LED outer diameter was 3mm, and the lead starts half way down, so I placed the LEDs 14mm apart, leaving me 1.5mm of overlap of the LED #1's lead past LED #2's lead.

In retrospect, I may have done better using a multiple of 0.1" so that my overall layout could work on a typical perf board that has that spacing.  Using mm as the unit for the physical cube spacing, yet using 0.1" spacing for through-hole components (headers) made it painful to do the PCB layout.

2.  Build a jig

My jig was simply a piece of plywood that was larger than 5x14mm+3mm in each direction.  That's five separation distances between the 6 LEDs, plus 3mm for the LED outer diameter.



The jig ends up being a 6x6 plane of LED holes.  Each should be set up to receive an upside-down LED, so the head of the LED drops into the hole, and then you bend the wires as needed.

Try to keep the border narrow.  You want at least one row of the holes to be close to the edge.  That way, if you move a set of LEDs, you can do that without having interference. 

For one of the holes, I also drilled an extra hole to receive the narrow screwdriver.  I used that as a peg so I could loop the long LED lead around it, forming a wire loop.  Having this hole nearby made for consistent spacing of all 6x6 wire loops.  Each wire loop was coplanar with each plane of LEDs.

3.  Bend up some LEDs in batches

In the end, I needed to make 216 functional LEDs that had bent wires.

Pop an LED into a jig hole, so the head is in the hole, and the legs are sticking up.  The short leg should be to your left, and the long leg to your right.  The short leg is the cathode (or "-" side).

Push the cathode straight away from you, and push the anode (long leg) to the right, thus forming an ell shape.  Make as many of these as you want.


4.  Make an LED kebab

Line up six of the LEDs in the jig such that each cathode (the short leg that was pointing away from you after bending) is touching the next LED's cathode.  Start with the LED that's farthest away from you, still pointing the cathode away from you.  Then, lay down the next LED one row closer to you, and have its cathode tip overlapping the prior LED's cathode.


Here's the topmost pair prior to soldering.

Solder the topmost pair.  This will give you some stability.  I used a magnet and weight to stabilize things.

When you do this soldering, you really don't want to keep the soldering iron on the leg too long.  If you do, you can melt the housing of the LED and thereby kill it.  So clean your tip, tin the tip with a bit of solder, and then as briefly as you can, solder the wires together.  It's ok if you end up with a bit of a blob here.  You can clean that up later.

Move on to the next LED until you end up with a column of LEDs.  When done, you should have six LEDs with connected cathodes, and a bunch of anode wires sticking out to the right.


Hint: You might find as I did that LED legs can be attracted with magnetism.  So, you can have a weak magnetic field (e.g., using the magnetized tip of a small screwdriver) hold one LED to another while you're soldering.

Hint: Joining the first two LEDs of each shishkabob is usually the hardest part, because the first LED is likely to rotate around as you're trying to stabilize things for solering.  You might find ways to stabilize the first LED with tape or other mechanisms.  Once you have the first two joined, they stay in the jig, and that prevents further rotational problems.

Hint: A little "overbending" of the cathode might help.  Normally you start by bending the cathode flat against the jig, without touching the prior LED, and then position the leg to touch the prior LED.  If you go beyond flat by a little bit, it helps secure things for soldering.

Hint: The very first LED can have its cathode bent toward you instead of away from you.  The second LED will then bend its cathode away.  As such LEDs 1 and 2 will have a lot of wire overlap, making for better alignment and soldering.  More importantly, it means that you won't have to clip off any excess wire at LED #1's end.

When you're done, you might want to clean up those solder blobs.  I do that by pressing lightly down with a small, flathead screwdriver tip on top of an LED bulb's area, and then briefly wiping the soldering iron tip across the sides of the connection.  Done right, this helps wick solder into the gap between the wires.  The screwdriver helps act as a heat sink, trying to prevent damage to the bulb.

Repeat.  You'll need six of these LED kebabs to form a single LED layer, and you'll be building six layers.

5.  Bend the loops

This step could be done after bending each LED, if you want.
Place each LED (individually before soldering, or each one of the LED kebab) in the hole that has the peg jig (the one where I used a thin screwdriver shaft for the peg).  The anode lead should rest below the peg (i.e., closer to you).  Bend the anode, wrapping it once around the peg to make a complete circle, so the lead goes around the peg and then ends up pointing out to the right again.


Flatten the bent circle by pushing downward on the circle's wire formation (down = towards the plane of the jig).  This helps with overall consistency.


Finish all remaining loops of the kebab.


Clip off the excess wire past each loop.


6.  Straighten some wire.

Unspool a length of the tinned copper wire, but do not remove it from the spool.  Bend the top into a little loop hook, and tighten that into the chuck of a portable drill.  (Since you've made a loop, there will be two parts sticking out -- the bent tip, and the part still connected to the length of wire that leads to the spool.)  Try to make sure the wire that connects to the spool is centered in the drill chuck.

Clamp or step on the spool so it doesn't move.

Spin the drill for about 10 seconds at high speed.  Here, it helps to pinch a section of the wire near the drill chuck tip to reduce overall vibration.  As you spin the wire, you'll see it start to straighten.

The wire may break off near the drill chuck tip due to metal fatigue, and if it doesn't, clip it off at that end.  Then clip off the end near the spool.  You should now have a length of straightened wire.

7.  Build the outer square of the plane

Put two LED kebabs back into the jig.  One should be on one edge of the jig, and the other should be at the opposite edge.  Make sure the wire loops are all facing the same way.

Cut two lengths of your straightened wire.  The piece you cut should be around 6 x 14mm in length (or in general, n x separation distance).  This will provide some overlap that you can clip off later.  Having that overlap makes it easier to place the wire and keep it stable.

Lay each length of the straightened wire to form the other two edges of the square.  The wire can be anywhere you want, but you'll want one of them to stretch from between LED kebab 1's LED #1 and #2 to the same place as kebab 2, and similarly from between kebab 1 LEDs 5 and 6 to the same on kebab 2.

Try to square those up to look good, and solder.  It helps to have markings on the jig to keep things square.



Building this outer square of the plane serves two purposes.  The first is that it establishes some structure.  The wires also act as a common grounding point across all LED kebabs in a plane, and having two provides for some redundancy.

(It would also be doable to have a single grounding wire across all planes, if you want.  It just makes things really wobbly when you do later steps.)

8.  Fill in the rest of the plane

Remove the outer square that you just formed.
Lay down the inner four kebabs in the same orientation as the others had been.

Put the outer square back down atop the four inner kebabs

Solder the grounding wires to the inner shishkabobs.  You should end up with 2x(n-2) more solder points.


Hint: as with an individual kebab, gaps between the ground wire and a kebab can be resolved by a second soldering pass.
- Tin the tip of the soldering iron
- Melt an initial blob connecting the grounding wire to the kebab LED cathode.
- Do the other solder points
- Go back to over the solder points one by one.  Use another heat-conductive device (e.g., a screwdriver) to press the ground wire down atop the LED wire, in case there was some gap there during initial soldering.  Re-melt the solder point while gently pressing down, and then keep the joint stable as the solder cools.
- Avoid hitting the same solder point repeatedly.  That can cause excess heat and melt or kill an LED.  If you have to hit the same place several times, let it cool each time before you try again.

9.  TEST the plane

Set up a power supply.  The ground line should connect to any of the ground wire points -- either to a cathode or to one of the grounding wires.  Make sure you have an inline resistor between the power line and the test probe point, or else you'll fry out LEDs as you test.  (In my set-up, I have a current limiting/clamping bench power supply, so I  keep the voltage to around 3 VDC, and gradually bump the amperage up to a point where it leaks current through.)

10.  Repeat, building the other five planes.


11. Set up the corner columns

This step is a little difficult to get things squared up properly.
The idea here is that you want four columnar wires to stick up from the main board in such a way that they're square -- i.e., orthogonal to the main board.
In order to affix each wire to the main board, you will need to solder each corner wire to the main board, and so at some point you'll need to have the main board inverted (or on its side) to do the soldering.

Here's what i'm thinking for this step:
11a.  Add some felt drawer bumper pads to each corner of the main board (on the copper side, i.e., the "down" side).  This will allow you to poke columnar wires through the main board, and have a consistent amount of wire on the under side.

11b.  Straighten and cut 36 columnar wires of a consistent length.
For me, I had 14mm separations between LEDs.  If we have all six planes at a height of 14mm apart, and we start the first at 14mm, then I'll need at least 84mm of height.  And then I'll need a little more to get through the FR-1 board and out the bottom side for soldering.  However, I'll want it to be a bit longer than that, because I'll need soldering space between the upper layers.  So, I chose to add a few inches to the length.
84mm = about 3.3 inches
I cut mine to 5.5 inches.
I also used 22 gauge wire for these wires.  The holes for the main board were cut at 0.8mm, and 22 gauge wire is about 0.65mm, so the 22 gauge wires should slide through easily.

11c.  Set the board on a flat surface, felt bumpers (copper) side down.

11d.  Insert the four wires that are at the four corners of the cube.  Each should slide through easily and rest its bottom tip on the flat surface below the FR-1 board.  But, because of the slop in drill hole, they will not stand up straight.

11e.  Slide all six LED planes onto the four corner columnar wires.  All the planes should be oriented the same way.  I choose to make all the loops point toward the back, so they're all on the same side as the control headers.  Try to adjust the planes to ensure the topmost plane is level.  (Individual planes might have some warpage to them as a result of soldering.)
In the image above, I have the bottommost layer (layer #1) laid down near the board, and the five upper layers supported by a bridge made of test breadboards and my wooden 14mm spacers.  This is before I realized that having alligator clips would be a better approach.  Instead of the breadboard and spacers, I could have attached four clips, one per corner columnar wire, below layer #2.

11f.  Attach four alligator clips, one for each columnar wire, above the topmost plane.  All we're doing at this point is giving the columnar wires some structure.  Align the columns to try to make sure everything is squared up.

11g. Using care to keep the FR-1 board from slipping off the four columnar wires, invert the whole cube, allowing it to rest upside-down on  your flat surface, held up only by the four columnar wires.

11h.  If possible, use some kind of square object (e.g., books) to keep the whole contraption square.  Bump these up against the sides of the six LED planes.

11i. Apply a bit of solder flux to the holes where the columnar wires are sticking through.  Solder two opposite corners' columnar wires to the FR-1 board.  Don't do the other two yet.

11j.  Check for square.  If either of the wires isn't sticking straight down, orthogonal to the FR-1 plane, then gently melt the solder at that point while adjusting the whole assembly for square.

11k.  Solder down the remaining two corners.

12. Lift the planes
Re-right the assembly (so the copper side is down again).
Undo the alligator clips, and re-attach them at the top of each columnar wire.
Lift all six layers nearly to the top of the columnar wires, so the topmost layer is touching the alligator clips.
Add four more alligator clips just below the raised, bottom-most LED plane.
At the end of this, you should have all six planes elevated above the FR-1 non-copper surface.

13. Lower the first plane
Place 14mm spacers on the FR-1 board perpendicular to the grounding wires (i.e., parallel to each kebab).

Undo the four lower alligator clips.

Lower the first plane, but keep the remaining planes held high, supporting LED plane 2 at each corner.

The first plane should rest its ground wires upon the spacers.

14. Thread remaining columnar wires
At each of the remaining columnar wire points (n x n - 4) slide the wire through its set of LED loop holes, threading from top (layer #6) to bottom (layer #1).  Double-check to make sure you didn't miss any.  Slide the wire down through its proper hole in the FR-1 board.  A set of needlenose pliers can help move the wires through, and it helps if you thread the innermost wires (the ones at the center) first.  Each wire's bottom tip should touch the flat surface under the FR-1 board.



Steps 13 and 14 could be done in reverse order, if you wish.

15.  With layers #2 through #6 raised, solder layer #1 to all columnar wires.

Moving from the centermost columnar wires out, do your soldering, but use the spacers.  Remember that each kebab isn't perfectly formed, and may have some warpage.

Place the spacer below the ground wires at the center, ensure firm contact between the grounding wire and the spacers, and then do the soldering.

Note: the LED loops that were bent earlier allow for some slop in movement.  You might find some are tight, and some are loose, and that's ok.

16.  Test all the LEDs in the layer

This is the point where you should make sure you didn't kill anything while attaching the layer to the columnar wires.  Make sure your power supply is at a safe voltage, and is current-limited (either via power supply settings, or using a resistor that's appropriate, e.g., 220 ohms).  Attach a grounding wire to either ground wire of the plane.  Then, briefly touch the (current-limited) power line to each columnar wire's top end.  One light should light up with each touch.

Repeat the test if you want, to make sure you're not burning out LEDs as you're testing.


17.  Check below the board

With layer #6 secured from above (clips on corner columnar wires above layer #6), invert the entire cube.  The bottom of the board should look something like this, only having the four corner columnar wires affixed at this point:
If you got all the other columnar wires threaded through properly and soldered to the first layer, they should not fall out at this point, and they should be poking through the board as shown.  If any wire falls out, you didn't solder it properly layer #1, so go re-thread it all the way through the board, and try again.

If a wire is affixed to layer 1, but is not poked through the board, you have to fix that, too.  You'll need to de-solder the problematic wire where it joins to layer #1, slide the wire through the board, and re-solder at layer #1.  If the wire is aligned well with the board hole, you may be able to just warm the solder at the joint, slide the columnar wire down and through, and then finish the re-soldering in one motion.

I recommend against soldering all columnar wires to the board at this point.  Later, after the whole cube is soldered up, you may need to adjust things to be level, and it may be that the best way to do that is to de-solder a corner columnar wire and re-solder it.  Doing that with all columnar wires attached to the bottom board (safely) is very, very difficult.  So leave them loose for now, but check at this step to make sure everything looks ok.

This is also a good point to try to adjust your four columnar solder points to avoid shear and torque of the cube.  Do so by finding some other squaring devices to keep the planes aligned to each other, de-solder each corner point from the board, and twist move the board to get the overall cube shape as you want it.

17.  Solder and test each plane in turn

Lower layer #2 down.  That means: release the clips that are holding layer #2 in place, let layer #2 slide down the four corner wires, and then re-clip to keep layers #3 through #6 up high.

Separate layers #1 and #2 using a spacer.  Just as you had used the spacers to keep layer 1 from touching the board, use the same spacers to keep layers #1 and #2 apart.  (Note: in an ideal world, I could use four or eight laser-cut "ladder" spacers, which would have grooves for holding each layer at a known height.  Re-using spacers as I'm saying here runs a slight risk of magnifying error in the spacer heights, making one end of the cube increasingly higher than the other.)

With layer #2 safely in place, solder all columnar wires to the layer loops.

Once all solder points are done, test the entire layer.

If you find a dead LED while testing, it probably means you sat on a loop-column joint too long, and burned the LED out (melted the wire out of its protective covering).  There aren't great options at this point.  If it's on the outside, you stand a chance of replacing and re-attaching an individual LED.  That is doable and takes some patience and dexterity.  If, on the other hand, the burnt LED is somewhere inside the cube, you may have to de-solder everything on that layer, and remove it and all layers above.  Then, you get in the game of re-threading each layer from above.  That, too, is a game of patience

In any case, you don't want to end up with all layers in place, and only then find out that there's a dead LED somewhere below.  It's much easier to fix it before layers higher up are soldered. So be diligent, and test every light for each layer as you build up the cube.

This is a picture of layers #1..#3 in place, with layers #4..#6 still waiting their turns.
(If anyone's wondering, yes, I got some LED strip lights and put them onto my solder filter fan so I could have better lighting while I was soldering more safely.  Nothing too fancy.  It's powered by the same power supply I use for testing my LED cube, so I have to be careful to wind down the voltage every time I'm done using it.)

This is the LED cube after all six layers have been added.  Remember when we measured the columnar wires and allowed for a few extra inches on each one?  While that might seem wasteful, the purpose was to be able to keep layer #6 raised up while layer #5 was being soldered, giving you room to work.  Now with everything attached, there are lots of wires sticking up pretty high above layer #6.

18.  Trim the top, level the board

This is kind of your last good chance to test all LEDs on all layers, so do that.

When you are satisfied that everything is working, you can trim the excess wire off the top of each columnar wire.

Now, you should have a relatively flat top ot the cube.  There will some variance here or there, but it should be pretty flat.

However, it may be that your overall cube is not level with respect to the board.

There are several things that could be happening.
Roll.  If you look at the cube from the side, and the overall layout of the lights looks cube-ish, but it's not level to the board, it's rolling or pitching one way or another.  You'll be able to fix that by inverting the cube, resting it gently on the layer #6 LEDs, and then you can adjust the solder points at the four columnar corners.  Be extremely careful here not to let the board slip upward, and get unhooked from the cube.

Shear.  If you look at your If you have a bit of a parallelogram shape going on, it's really hard to fix.  You have to avoid that at an earlier stage.

Torque.  If you look at the cube from above, and it seems to have a bit of a twist going on, you may be able to bend things gently back into shape, physically twisting the cube into position.  But generally speaking, you should have tried to avoid this at an earlier stage.

Here's my cube with the top trimmed.


19.  Solder to the board

You should now be able to invert the board, and solder all columnar wires to the board copper.  Use a dab of solder flux at each point to get a good connection between the copper plane and the wire.

Clean up any rosin flux at this point.

20.  Attach the grounding wires

There are six wires that you need to connect between the board and the grounding line of each layer.

Set the cube right side up.  It should still be elevated off of your work surface by the felt pads.

You'll do each grounding wire in turn, starting with the innermost board hole connecting to the lowest layer.  Move out one board hole and up one layer as you proceed with doing subsequent layers.

Straighten some wire and cut off chunks that will reach from below the board to the grounding wire.  There are different artistic ways of setting this up.  A simple approach is just to poke the wire through the board hole, having it touch the desk surface beneath.  Then bend it at the point where it meets the board, and lean it over so that it touches the appropriate ground wire.  Solder the wire to the layer's ground wire, and trim any excess.

Another approach is to make an ell shape in the wire.  This takes more careful measurement, but can yield a pleasant appearance.

When all six layers have had their ground wires set up, invert the cube, and solder the ground wires to the board, using rosin flux at each point.

Clean up any rosin flux.

21.  (Optional) Test using board connections


At this point, you should have a nearly functional cube.  You should be able to use normal power supply probes (with proper current limitation), touching ground to a grounding wire solder point on the board, and touching (current-limited) power to each hole where the headers will connect.  Each light should light up in turn.  It's helpful to place the cube (gently) on its side for doing this test, and it also helps to have a mirror so you can see the lights light up.

22.  Solder all headers

For each header row, get the appropriate header pin set, and push it through the proper set of holes.  For this step, I buy 40-row female 0.1"-pitch headers, and I use a saw to cut them to the sizes I need.
 
I also have designed my board so that all power pins are in the same row, but there are some gaps along the way -- places where there's a pin in the header, but no corresponding hole -- so I trim some pins before inserting the component into the holes.


I recommend using solder flux around all the pins before soldering.

Roughly solder one end of the header to the board.  Then, applying light pressure from above, melt that solder point, make sure the header is flush to the board, and then let the solder point solidify.  If you're satisfied with how it looks, solder the opposite end, check for flush, and then solder remaining points.

Similarly solder the grounding wires' header pins.

Clean up any rosin flux.

This is what it looks like with the power headers in place, the ground wires attached, but no ground headers attached yet.  (Steps 20..22 can vary in order.)


23.  Trim excess

You may have excess wire at various points here, particularly at the end of each kebab, and at all poitns below the board.  The ones below the board are safe to trim.  The ones at the end of each kebab may be trimmed, but carefully.  If you built each kebab with the topmost LED cathode pointing away from you, it will have a very weak connection to the next LED's leg.  So before you trim those, double-check that the solder is good, and if you do trim it, try orienting your wire cutters so that they cut "sideways", i.e., the cut goes across both legs at once.

This is the view beneath the board prior to flux cleaning.


24.  You're done!

At this point, you're done with construction.  You may opt to do one more round of testing to make sure all the headers are talking to the board properly.

From this point, it's all about the controlling hardware (e.g., Arduino) and software.