Interfacing laptop keyboard matrix with Leonardo

Hello,

So I've got a bit of a tricky one. Well, I'm finding it tricky anyway, the solution might be really simple. I started a project a while ago to gut a dead* 2002 Titanium Powerbook (15 inch DVI) and install a Raspberry Pi in place of the logic board. I want to use the built-in keyboard directly and figured a Leonardo would be the best - well, easiest - way to create a USB interface for that matrix.

So far I have:

  • Verified that a Leonardo can be used as a keyboard to control the Pi, I did this by creating a simple sketch to type the letter 'a' when a pin went low, which worked, so that's good enough for me!

  • Created a pinout for the keyboard connector by probing all of the connections, everything is present and correct.

  • Read the resistance for each key on the keyboard. Yeah, this one threw me a little but it makes sense, as I'll explain soon.

My problem now is: How do I read the keyboard quickly enough for it to react at the speed you'd expect of a normal keyboard? Or am I supposed to use some sort of buffer?

Going back to the key's resistance; I discovered almost by accident that each key has a resistance that is unique. Some of the resistance values are repeated on different columns, but each column has a unique resistance for each key. So this makes me wonder if the ten columns were being read by an ADC to get a binary value for each key to prevent ghosting... But do I need to worry about ghosting, really? I can't think of a time I've ever had to press two non-modifier keys at the same time and I generally don't type quickly enough to ever press two keys at the same time accidentally! It seems like a lot more work to use ADCs to read the voltage given by each key, and I need to keep the size of whatever circuit I come up with really small anyway. It's not as if I'm going to be gaming with my Pi!

But anyway, getting back to the point; am I supposed to keep the latency of reading the rows right down so that the Leonardo can read it in real time and hope that I don't type so quickly that it can't keep up, or do I need some sort of a buffer? If I do have to have a buffer, where do I even start with that? I've never used anything remotely buffer-like (except for using an Arduino itself to shift bytes in from a CMOS PISO register and back out to a TTL SIPO one).

Let's say I make a system that uses the Leonardo to shift out a constantly left-shifting byte (i.e. 0000 0001, 0000 0010, 0000 0100, etc.) to cycle the rows, and at the same time it 'listens' to the ten columns directly. When it digitally reads a '1' on, say, pins 2-11, it will work out which byte it was shifting out at the time (say for the sake of argument it's 0000 0001, as in row A) and compare that to the pin that went high (again for the sake of argument it's pin 2, as in column 1) to get the co-ordinate of the pressed key (which would be "F1"). Could that be done quickly enough, or would I need to have a buffer between the columns and the Leonardo to 'save' the state of the row and column until the Leo can read it, at which point it resets again?

So yeah, I don't know. In terms of the whole project this is really the only part that has to work, I really don't mind using a mouse with it instead of the trackpad and there's only a really, really slim chance the display will work anyway; if it doesn't I might fabricate a custom lid for a different 15 inch LCD, but I'm more likely to install a plethora of display connectors on the back instead and just use it as a portable Pi without having to carry all the superfluous parts around.

TL, DR: Does keyboard ghosting really matter, and can a Leonardo work quickly enough by itself to read the inputs without missing any keystrokes?

Ladies and gentlemen, I thank you all greatly in advance.

*Dead = missing hard drive, backlight inverter... That was it, so not really all that dead.

I think I've worked out some pseudo code which will hopefully get some conversation going!

Basically I'd have a 2D array with the keys all organised in row and column order, except for the modifier keys which aren't on the rows and columns at all, these I suppose would be treated separately. The Arduino's main loop raises one row at a time and reads all of the column inputs to wait for a high signal. When it gets a signal it uses the currently active row and the column that responded to get the pressed key from the array and then sends that out as a keystroke. I'd also like the Arduino to wait while a key is pressed so that it doesn't go berserk and starts typing every key from that column as it continues to cycle through rows.

I type at about 80wpm, which according to this http://en.wikipedia.org/wiki/Keystroke-level_model Wikipedia page means my average keystrokes are over 120ms (120ms is the average for 90wpm typists), but I can't work out what that means for the required polling rate for it to pick up enough keystrokes to not be frustrating. I would guess that if it could run at 100Hz I'd probably be ok, but that is, as I say, a complete guess. I'd need to be able to, say, type a '2' and then a '1' immediately afterwards without it missing the second keystroke. I've read that keyboards these days poll at anywhere between 200 and 1000Hz, but I imagine that's so they can keep up with the fastest typists, if I'm designing this for me then it doesn't matter if it's a little slower, does it?

So what do you think? Is 100Hz feasible for a sketch that operates that way? As I'm far more about hardware than software (software, to me, is something I have to struggle through when I can't do what I want using hardware instead) I'd much prefer to get the basic operation nailed before I start actually coding anything, because it's so easy to get lost. Hopefully if I familiarise myself with how the code should work I'll be able to code it properly from the start, instead of my usual method where I end up with tens of unused variables and redundant code.

Pin count is a concern because I have five more connections on the keyboard ribbon connector than I have pins on the Arduino, but I think I could use a SIPO shift register which simply shifts a bit left to cycle through the rows and a PISO register to read the value of the modifier key inputs, as they sit outside the usual matrix anyway. The other option is to just forego the two LEDs and just have the SIPO register doing its thing.

Seems feasible, but I wouldn't recommend the 'waiting' approach you seem to be suggesting.

I'd suggest you poll the keypad matrix at some high (and relatively constant) frequency and maintain a status for each button. You can perform any debounce that may be required here. For each button there would be a state machine which you can use to perform edge detection so that you can detect key-up / key-down events and also perform a timed auto-repeat while the key remains held down, if that's something you wanted.

Oh yeah, I completely forgot about debouncing! By 'waiting' approach, you're referring to "I'd also like the Arduino to wait while a key is pressed so that it doesn't go berserk and starts typing every key from that column as it continues to cycle through rows", right? Since typing it I've realised it's probably unnecessary and there are better ways around it, as you suggested keeping track of the status of each key would be one way to do it... Careful coding would be another.

I'm concerned, though; if I maintain a status for each button - presumably by having variables for each of the 76 keys - wouldn't that slow the Arduino down? I must admit, I haven't coded anything on an Arduino for a few years now (I've been studying electronic engineering instead, which is 99% hardware, 1% software (at the moment)) and even when I was doing it more often I still wasn't particularly good at it!

When I have some code that at least works I'll be sure to post it here, but that'll probably take a while because of real life getting in the way. This project has been going for a few months already, actually, but it's only been in the last few weeks I've decided to actually get on with getting the keyboard working.

neema_t: Oh yeah, I completely forgot about debouncing!

I guess, if scan speed is not ultra high, then debouncing will not be a problem. For a start, I wouldn't worry about it.

My plan so far is to simply record when a key is pressed and call Keyboard.press. When the controller detects that the key is not pressed anymore, it calls Keyboard.release.

I'm concerned, though; if I maintain a status for each button - presumably by having variables for each of the 76 keys - wouldn't that slow the Arduino down?

To record the status of one key should always take the same time, independent of the number of keys. The instruction is: Write up/down (e.g. 1/0) into the memory location reserved for the status of that key. Just look that you don't run out of memory, and you should be fine.

I must admit, I haven't coded anything on an Arduino for a few years now

I've never worked with an Arduino at all. I'm more a software guy, and electronics I've always regarded somewhat as voodoo.

You're probably right about the debouncing but it seems like good etiquette anyway, I guess I'll see how it goes; not hard to implement with software anyway.

So did you settle for a Leonardo or a Due? I'm hoping the Leonardo will be enough but thinking about it, a Due might've been the better choice; the 3.3V would be better for the tiny membrane traces, but then I do want to desolder the headers and barrel jack receptacle because space inside the laptop is obviously minimal so the Leonardo is perfect for that. And I only have something like five pads more on the keyboard connector than I have pins on the Leo, and it was half the price.

Am I right in saying that if I code this badly, as in using far more lines than is necessary because of not knowing a cleaner way to do something, the polling rate will be affected? Therefore I should use SPI for the shift registers? Wow, it's only just occurred to me that if I do that I wouldn't need to use six pins for two shift registers, I could just use four... I think. It's too late to be thinking about this now (0140 GMT) so I'm going to stop!