Osborne keyboard and KMK


11 Jan 2021, 03:46

Back in the before-times (last year), there was a discussion of keyboard firmware systems and CircuitPython came up.

In the meantime, there are mechanical keyboard kits available that use CircuitPython and the KMK project to define a QMK-like library for them.

Now, these libraries aren't primarily designed for what I want, which is converting random vintage keyboards with a wide variety of protocol interfaces to USB. By default, I use QMK and it isn't primarily for this, either. Hasu's original TMK was more oriented that way. Still, there are around a dozen converters in QMK's repo (count in my branch is more like forty). So, what I have to say isn't meant to propose any actual changes in objectives for any of these systems.

As far as I know, all CircuitPython capable boards are 3V. 3V for vintage keyboards is a challenge. One could always add a level shifter, but if breadboarding rather than designing an SMT PCB, that adds a lot of real estate. Except sometimes one is needed for RS-232 voltages, in which case we're all set.

Some keyboards have all the outputs open collector, due to differences at the time between TTL and CMOS. These might work with 3V, but not without risk.

But what about keyboards without active electronics at all? Specifically, with just a matrix of switches that the terminal scanned directly?

Which brings me to this Osborne 1 keyboard.
Osborne-case.jpg (285.5 KiB) Viewed 239 times
I don't know where that red CTRL key came from. I suppose it's a replacement, but where did someone find one with the right cross-mount? Anyway, I think I found a seller online with something closer to the original and I will fix it.

There are pictures of the insides in this post from a few years ago.

The Technical Manual on Bitsavers has schematics and details of the matrix.

Even though it's a direct matrix, the keyboard designers went to the trouble of connecting the two shift keys and all the numpad keys to corresponding main typewriter section keys. So while there are up transitions, there aren't as many key codes available as it might seem. Also there isn't a backspace key. So I figured to use the left arrow for that and the up/down arrows for layer shifts to get more codes.

Here is a straightforward converter using an Adafruit ItsyBitsy M4 Express, a board about the size of a Teensy and cheaper than one, too.
Osborne-converter.jpg (336.01 KiB) Viewed 239 times
Specifying the matrix pins and key mappings in Python code is straightforward. But the converter only sort-of works. The root problem is that there aren't any diodes in the Osborne matrix, a common problem with older (cheaper) boards. Consequently, you can't type shift-' to get " or shift-[ to get ] (or {) or ctrl-ESC. These are all pairs of keys in row zero. It's even worse if you switch the scan direction ("diode orientation") to ROWS: now shift T, G, and B and ctrl E, D, and C don't work!

The QMK default matrix scanning routine works like open collector scanning did in vintage firmware. The inputs (columns, usually, but not necessarily) have pull-up resistors (inside the MCU in QMK, in hardware in the old days). One of the outputs (rows) at a time is connected to ground. A pressed switch will cause the corresponding input (column) to sink current and sense low, while the pull-ups cause the rest to sense high. The rest of the outputs are kept at high impedance (hi-Z) by setting them as inputs (with additional pull-ups in QMK, but that's not actually essential).

The KMK matrix scanner is different. First off, it sets the selected output high and senses a pressed key as a high input. To some extent, this is an arbitrary choice. It does mean that the inputs need pull-down resistors not pull-up. Which can be inconvenient. While AVRs only did pull-up, these 32-bit MCUs can do both. But, for example, GPIO extenders like the MCP23008 also only do pull-up. There is even an example of using an MCP23017, but it has to be for the outputs (columns in this case); it would fail for the inputs.

More of a problem is what it does with the outputs other than the selected one. They are set low. Which means that without diodes they can compete with the one that is selected.

Now, of course, one way to fix this is to add some diodes. Specifically, a diode per row, if scanning by rows. That doesn't fix all ghosting, which needs them per switch, but it does solve the shift-key problem. Here is the same converter on a breadboard with the necessary diodes.
Osborne-converter-with-diodes.jpg (349.22 KiB) Viewed 239 times
But it's also possible by adding software: a scanner that keeps the non-selected outputs at hi-Z. I also switched the polarity to what I'm used to. Which does mean that if the with-diodes version were permanent, they would need to be reversed.

Naturally, that's just one-time work. This new class works fine for another matrix keyboard, like the TI-99/4A.
TI-99-converter.jpg (387.55 KiB) Viewed 239 times


11 Jan 2021, 03:53

What about smaller boards? From time to time I pick up these button sub-assemblies with interesting key switches. They aren't keyboards, per se, but the same approaches ought to be able to make them into macro pads. (Perhaps even usably so if I could manage 3D printing.)

Here are some old Micro Switch SN-series switches from some kind of industrial control. The black switches are sink pulse and the white ones sink level. Which is close enough to a switch to ground for QMK's DIRECT_PIN configuration to work.
Micro-Switch-SN-5.jpg (297.01 KiB) Viewed 232 times
For the main converter, I used a DFRobot Beetle.

For tiny stuff like this, QMK even supports ATtiny85 boards like Digispark. The second converter pictured above is some clone with an actual USB connector. The biggest problem is that only three GPIOs are available of the six after the onboard LED and software USB. So one of the buttons isn't connected.

It would work to add a MCP230xx and use the available pins for i2c, but that kind of defeats the purpose of using a tiny MCU and eats up the cost savings. Plus they aren't really supported in QMK. There are a number of (mostly split) keyboards there that use them, but through a custom matrix. Generalizing the GPIO routines in C is more of a big deal; in Python duck-typing mostly does it.

Back in the CircuitPython world, there are equally small form-factor Cortex M0 boards, like the Seeeduino XIAO or the Serpente. Or even the Adafruit ItsyBitsy M0 Express, which isn't physically smaller but has less oomph.

Here are some reed switches, from the memory (Память?) section of a Soviet-era calculator, connected to a Serpente.
Serpente-keypad.jpg (205.94 KiB) Viewed 232 times
Can it be made into a macro pad?

The issue is RAM. The KMK landing page is pretty explicit about needing the bigger boards.

Earlier this year, there was a Hackaday.io project update on a custom keyboard using CircuitPython. Regarding KMK, they are critical of the size and put it down to an "enterprise approach," which, in short, seems to mean too many abstractions. So they did things directly and offline serialized the board layout into a single byte array. I am not so sure we need to go that far; in fact, sometimes abstractions can be used to solve these problems. It's just that KMK didn't aim to be compact. Which, to again be clear, is fine.

On the other hand, these small boards aren't really that tight. They have 32KB RAM, 256KB program flash, and a 2MB SPI flash file system. The MCS-48 controllers that did keyboards back in the day had 64 bytes of memory and 1KB program flash. They didn't do USB, but they often did RS-232. There should be some payback for using Python.

Some ideas / guesses:
  • it should be possible to compile the keyboard itself on the fly
  • a few classes for the framework is a not an issue
  • one-time allocation for the actual layout and even every action in it is okay
  • allocation for every possible action is problematic
  • allocation of every actual event (key press / release) is likely all right
  • allocation for every scan of the matrix may be too much
So all the framework classes should be compiled into .mpy files. And while it's not necessary to reuse a key-press data struccture, it may be worth avoiding enumerate finding it.

kmk.keys.KC has a dictionary of all possible keycode objects (with some aliases). This alone takes around 80KB.

KMK imports are arranged to all preload via an explicit depth-first naming. Evidently this was motivated by some MicroPython issues with the natural deep import tree that one would ordinary see. But it does make it hard not to get all the features loaded, which weigh in over 150KB.

Instead of just making pronouncements, I had a go at building a smaller framework. It still has the basics like HID keyboard and consumer usages, pre-shifted keys, string macros, and layers.

For getting the list of usages, the Adafruit HID module seems adequate. But pre-compilation isn't enough; that would still mean putting them all in memory. So it needs to be frozen in flash. But the CircuitPython build for this board doesn't have enough room, so some built-ins need to be removed, like MIDI.

There's still some RAM left, so it's maybe possible to add more features, like Unicode or RGB, provided they are loaded dynamically (there's loads of file system space). But, really, for projects that want more, there's no reason not to go with the bigger board and something already more featureful like KMK.

User avatar

13 Jan 2021, 18:36

Oh this is awesome work. I don't enjoy typing on that Osborne keyboard enough to consider trying to convert it, but it still is a very cool piece. I haven't dug into KMK at all but this seems really intriguing for many obscure vintage boards.

Post Reply

Return to “Workshop”