Possible to use Teensy AS the keyboard to replace 8x8 matrix?

Tuc

New member
Hi,

My ultimate goal is to be able to add a USB Keyboard to a TRS-80 Model I. In talking with the TRS people, the best I will be able to do is use something that will act like an 8x8 matrix switch and tap into the lines.

I see projects where people interfaced X as a keyboard, but has anyone been able to use it AS the keyboard? (8x8 matrix output).

Interested in what anyone might have to say or pointers to people who might have done it.

Tnx, Tuc
 
Hi,

My ultimate goal is to be able to add a USB Keyboard to a TRS-80 Model I. In talking with the TRS people, the best I will be able to do is use something that will act like an 8x8 matrix switch and tap into the lines.

I see projects where people interfaced X as a keyboard, but has anyone been able to use it AS the keyboard? (8x8 matrix output).

Interested in what anyone might have to say or pointers to people who might have done it.

Tnx, Tuc

I'm not sure whether I understand your question, but, have you looked at the Arduino Keypad library?

https://playground.arduino.cc/Code/Keypad/

This supports matrix keypads in row and column format.
 
I recall just a few weeks ago we had someone ask basically the same question, about 3 or 4 times (starting a new threads after each prior attempt failed to start much conversation) and even posted a timing diagram a couple times. I'm not sure if it was TRS-80 or some other retro computer they were trying to interface.


In talking with the TRS people

Was this talking done on a public forum or social media? Can you give a link?



Just to make sure I understand, my guess is you want to have Teensy watch the lines which are driven by the TRS-80 to scan its keyboard (as the Keypad library would do) and then when you wish to "type" a key you would drive one of the other lines which the physical key would normally connect. Maybe?
 
I'm not sure whether I understand your question, but, have you looked at the Arduino Keypad library?

https://playground.arduino.cc/Code/Keypad/

This supports matrix keypads in row and column format.

Do you maybe have it backward? I've found thousands of articles for 8x8->USB, but not USB->8x8 from my Googling.

I need to have USB be the INPUT, and 8x8 matrix as OUTPUT. Theres also a bit of a kink with a KYBD signal too :)

Tnx, Tuc
 
I recall just a few weeks ago we had someone ask basically the same question, about 3 or 4 times (starting a new threads after each prior attempt failed to start much conversation) and even posted a timing diagram a couple times. I'm not sure if it was TRS-80 or some other retro computer they were trying to interface.

Wasn't me, promise!




Was this talking done on a public forum or social media? Can you give a link?

It's on the VCF Forum at https://forum.vcfed.org/index.php?threads/has-anyone-worked-with-the-model-i-keyboard-circuits.1244968/

Just to make sure I understand, my guess is you want to have Teensy watch the lines which are driven by the TRS-80 to scan its keyboard (as the Keypad library would do) and then when you wish to "type" a key you would drive one of the other lines which the physical key would normally connect. Maybe?

That sounds the opposite. I need to be able to type on a USB keyboard, and then output an 8x8 signal TO the TRS-80. I'm looking to add USB keyboard functionality to a TRS-80 Model I so I can ultimately use a remote KVM to drive it. Given it does RCA composite out, I can get that to HDMI, and then would allow me to sit upstairs and work on the TRS-80 in the basement. ;)

Thanks, Tuc
 
Wasn't me, promise!






It's on the VCF Forum at https://forum.vcfed.org/index.php?threads/has-anyone-worked-with-the-model-i-keyboard-circuits.1244968/



That sounds the opposite. I need to be able to type on a USB keyboard, and then output an 8x8 signal TO the TRS-80. I'm looking to add USB keyboard functionality to a TRS-80 Model I so I can ultimately use a remote KVM to drive it. Given it does RCA composite out, I can get that to HDMI, and then would allow me to sit upstairs and work on the TRS-80 in the basement. ;)

Thanks, Tuc
Given this, you want a Teensy 4.1 and use USB host support. It has 5 pins on the inside that you can solder a USB A cable (which PJRC.COM sells, but it is commonly used in making PCs, so you can buy it elsewhere). Alternatively, the Teensy 3.6 has the same support (but Teensy 3.x's can be hard to find these days). The Teensy 4.0 has the support for USB host, but it involves soldering wires to the board. I know there is support for reading USB keyboards, mice, disks, and more.

Note, because the USB host support was originally written for the Teensy 3.6, the name of the library is USBHost_t36.h. But it will work on Teensy 4.0 and 4.1.

In the examples directory is Examples -> USBhost_t36 -> keyboard_viewer which reads a USB keyboard, and displays the keys pressed on a display.
 
That sounds the opposite. I need to be able to type on a USB keyboard, and then output an 8x8 signal TO the TRS-80. I'm looking to add USB keyboard functionality to a TRS-80 Model I so I can ultimately use a remote KVM to drive it.

What Paul's describing is exactly how you do that; the way the matrix works is that the device cycles through each of the drive lines sequentially setting them high and checking which input lines read high at the same time, indicating a keypress / link between that input and the corresponding drive line. To emulate that with a microcontroller, it needs to watch the drive lines and raise the appropriate inputs to match the keypresses it wants to send.
 
The project can be split into two parts:

  1. Teensy as a host for a standard USB keyboard.

    This is well supported by Teensyduino: you can use Teensy 4.1 or 3.6 (easiest, with USB host port exposed on pins, so you only need to plug in a suitable cable), or Teensy 4.0 (soldering an USB female connector to the pads underneath the board).
  2. Using 8 input and 8 output pins to interface to the TRS-80 8x8 scanning matrix.

    If I understood correctly, the TRS-80 keyboard matrix has 8 outputs (row selectors), and 8 inputs (columns), with each tactile switch connecting one output to one input when pressed, normally open; and running at 5 V logic. Because Teensy uses 3.3 V logic, you do need 8+8 voltage level shifters. I've found auto direction sensing to be problematic here (glitches in the low level signal due to direction mis-sensing), so I would definitely recommend unidirectional shifter here, say SN74LVC8T245's (8 signals per chip).

    One could also use say 16 NX138AKR (N-channel signal MOSFETs in SOT-23 footprint; my current favourite, since the gate can handle ±20 V, but fully switches with Teensy's 3.3V logic levels), but the configuration depends on whether the matrix row selectors are active high or active low. (These cost 0.13€ apiece at mouser, so 16 costs about 2.10€. I bought a strip of 100 for 6€. You also need 16 pull-up resistors of 2.2k-10k, and optionally 16 100-1000 ohm resistors to limit the current spike when switching the gate state.)
The Teensy code needs to maintain an internal bit map corresponding to the 8×8 matrix. This is a simple volatile unsigned char matrix_state[8]; whose bits are set when the corresponding keypress is received from the USB keyboard, and cleared at key releases. Note that modifier key states are reported for every keyboard USB HID event.

On the eight matrix row selector pins, you set an interrupt handler for the rising edge. In the interrupt handler, you set the eight column pins based on the matrix.

The latency on the interrupt handler should be short, so that the column matrix pins will reflect the correct state for the active row, fast enough for the matrix scanning circuitry to not notice. If you use Teensy 4.0 or 4.1, one useful 'trick' you might consider –– noting that standard digitalWriteFast() is likely fast enough! –– is to use column pins that are in the same GPIO bank. For example, Teensy pins 14 - 21 (GPIO6 bits 18, 19, 23, 22, 17, 16, 26, and 27), via a lookup table, uint32_t matrix_lookup[256];, which is initialized in setup():
Code:
volatile unsigned char  matrix_state[8];
uint32_t                matrix_lookup[256];

void setup() {

    for (int i = 0; i < 8; i++) {
        matrix_state[i] = 0;
    }

    for (unsigned int mask = 0; mask < 256; mask++) {
        uint32_t  value = 0;
        if (mask & (1<<0)) value |= 1 << 18;  // GPIO6 bit 18 = Teensy 4.x pin 14 = column 0
        if (mask & (1<<1)) value |= 1 << 19;  // GPIO6 bit 19 = Teensy 4.x pin 15 = column 1
        if (mask & (1<<2)) value |= 1 << 23;  // GPIO6 bit 23 = Teensy 4.x pin 16 = column 2
        if (mask & (1<<3)) value |= 1 << 22;  // GPIO6 bit 22 = Teensy 4.x pin 17 = column 3
        if (mask & (1<<4)) value |= 1 << 17;  // GPIO6 bit 17 = Teensy 4.x pin 18 = column 4
        if (mask & (1<<5)) value |= 1 << 16;  // GPIO6 bit 16 = Teensy 4.x pin 19 = column 5
        if (mask & (1<<6)) value |= 1 << 26;  // GPIO6 bit 26 = Teensy 4.x pin 20 = column 6
        if (mask & (1<<7)) value |= 1 << 27;  // GPIO6 bit 27 = Teensy 4.x pin 21 = column 7
        matrix_lookup[mask] = value;
    }

    pinMode(14, OUTPUT);
    pinMode(15, OUTPUT);
    pinMode(16, OUTPUT);
    pinMode(17, OUTPUT);
    pinMode(18, OUTPUT);
    pinMode(19, OUTPUT);
    pinMode(20, OUTPUT);
    pinMode(21, OUTPUT);
    GPIO6_DR_CLEAR = matrix_lookup[255];
}
The matrix_state array is marked volatile, to tell the compiler it cannot cache or infer the value it should have based on the code it sees. I am assuming an interrupt handler may read or modify its contents, and as that interrupts the normal program flow in unpredictable ways, the compiler must not cache or infer the value from normal program flow, and must actually examine the contents whenever the code refers to the array.

Assuming the row selectors are active high, any row selector going low should clear all the matrix outputs, ie. GPIO6_DR_CLEAR = matrix_lookup[255];.

When the row selector for row row (0 to 7) goes high, you do GPIO6_DR_CLEAR = matrix_lookup[(~matrix_state[row]) & 255]; GPIO6_DR_SET = matrix_lookup[matrix_state[row] & 255]; to set the matrix outputs to correspond to the states on that row in matrix_state.

This is compatible with digitalWriteFast(), even with these same pins or pins in the same GPIO bank. The clear pulls the columns corresponding to inactive matrix bits low first, so that the likelihood of "ghosting" (matrix scanner seeing the state for the previous row) is minimised; and only then sets the columns corresponding to active matrix bits.
If the scanner checks the column bits faster than Teensy can set them, this way the key press is only delayed by one scan cycle.
If we don't clear the column bits fast enough, then a key press can occasionally "ghost", i.e. as if the same line on the next row is also active.
(There should be a reliable delay, though, because these signal lines have inherent capacitance; even the real hardware may "ghost" the same way if the delay between enabling the specific row and reading the column lines is not long enough. As typical rates for a 8-row matrix are between 400 Hz (2.5ms per row, 50 Hz key state check rate) and 2000 Hz (0.5ms per row, 250 Hz key state check rate), there should be at least 0.1 ms between the activation of a row, and the TRS-80 checking the column states. For Teensy 4.x, that is a long time; you can do a lot of stuff in 0.1 ms = 100 µs in Teensy 4.x.)

If you are or become proficient with Teensyduino programming, you can then replace the IRQ_GPIO6789 interrupt handler –– which handles all GPIO pin interrupts –– with one that checks the eight row selector pins, and sets the matrix outputs as above. (If exactly one row selector is active, that determines the row; otherwise, clear all outputs. Also, if the row selectors are in the same bank, and mask contains the states for the row selector bits, (mask && (mask & (mask - 1))) is true if and only if exactly one bit in mask is set; this is a trick one can use to speed up the cases where you support other pin state interrupts, but check the matrix row selectors first.)

This way you can trade some functionality (namely, other GPIO pin interrupt support) for a very fast interrupt handler, so that when Teensy 4.x runs at 600 MHz, the reaction delay is minimized to on the order of a single microsecond (1 µs = 0.001 ms), which should ensure a very robust keyboard matrix emulation, even at quite high (tens of kHz) matrix scanning rates.

This tricky stuff is definitely normally not needed for keyboards, but can be useful for implementing parallel buses, i.e. using Teensy to emulate some old 8-bit peripheral or perhaps even memory (but with a several MHz parallel 8-bit buses, I suspect one would need to do some of the stuff in extended inline assembly, sprinkling assembly functions for the critical bus data switching parts, but using the same above approach). If the output pins are not set by any other code, then the update can be made with a single oldmask ^= matrix_lookup[matrix_state[row]]; GPIOn_DR_TOGGLE = oldmask;, which simply tracks the pins that change (using global/static oldmask), toggling the state of the pins instead of explicitly setting and clearing them.
So, do consider most of this message as rambling.
 
You're probably going to need a 16 channel logic analyzer, so you can watch the signals the TRS-80 sends. You'll also use it see the result of pressing actual keys and watch what effect Teensy is having as you try to get this project working. In theory, maybe the project could be done without such a tool, but as a practical matter you'll be working blind if anything doesn't work perfectly.

Saleae Logic 16 is probably the best. But it's also quite expensive. Many cheap clones of older versions exist. Any should work. You don't need extremely high capture speed, since any ancient TRS-80 just isn't going to be capable of high speed. Teensy can of course give very fast waveforms, but in this case you're probably fine because you'll be putting using delays or elapsedMicros or other timing to create signals slow enough for TRS-80 to handle.

I haven't personally used any of the clones, nor any of the newer (very expensive) generation Saleae. But I know several people here have them.
 
Back
Top