freedelity / ps2-keyboard

Library for Arduino implementing the PS/2 protocol
MIT License
33 stars 7 forks source link

Add to arduino library manager #2

Open Harvie opened 5 years ago

Harvie commented 5 years ago

Please get listed at arduino, so this library can be easily installed using arduino library manager using few clicks. It's quite simple. Just add "library.properties" file to your repo and let them know:

https://github.com/arduino/Arduino/wiki/Library-Manager-FAQ#how-can-i-add-my-library-to-library-manager

ndusart commented 5 years ago

Hi, thanks for your suggestion.

It clearly would be a useful integration but I wonder if it is meaningful in the current state. As pointed in another issue (#1), this is not compatible without modification for all the boards. This could be added of course but I have to find some time to do it right.

And I do not know if my use of the TIMER1 is compatible with all the Arduino libraries as I edit the registers directly.

I' didn't use the Arduino for some time so I do not know if all Arduino libraries from the Library Manager could be imported at once without conflicts but I can see it as an expected feature from the point of view of users of the IDE.

I can see that you started a project similar (https://github.com/Harvie/ps2dev) based on other implementations. Can you explain the main difference between mine and yours ? (from rapid checkout, seems like mine is interrupt-driven, so can be used easily for running other code in parallel without perturbing timings, yours seems to use delays)

Maybe we could join the effort :)

Harvie commented 5 years ago

I can see that you started a project similar (https://github.com/Harvie/ps2dev) based on other implementations.

I am not author of that code. It was some kind of library that was echoing through the internet forums, but was lacking proper repository and documentation. So i created the repository for it, added arduino metadata and made request so it can be added to arduino library manager. Also i added some basic documentation and examples on how to use it. I guess this will enable people to collaborate on improving it.

There are still some issues with timing, so there's need to call delay(50) after each byte is sent. Hopefully i'll track it down, so this is not needed.

I've just quickly peeked at your code and it seems to me that your code is bit more complex. Also ps2dev can only work as mouse/keyboard, but can't read data from keyboard.. While your library can do both directions... However in arduino library manager there are already some libraries which can read data from keyboard. But there is none, which can act as keyboard, that's why i started working on this...

Harvie commented 5 years ago

I do not know if all Arduino libraries from the Library Manager could be imported at once without conflicts

You can install as many libraries as you wish... IDE will just download it and put it in your libraries folder... But of course there might be conflicts when you #include incompatible libraries in single sketch... But that will hapen even without library manager...

And I do not know if my use of the TIMER1 is compatible with all the Arduino libraries as I edit the registers directly.

Arduino has some timer/interrupt abstraction layer, so if you can use that instead of directly modifiing registers, you should be OK. They have separate implementation for each board, but API is the same, so you can use that.

If you need something special that is not implemented in arduino, you can use #ifdef statements to choose the right code for each platform. But i doubt this will be needed for such simple case as ps2 protocol...

Maybe we could join the effort :)

These two implementations seem to be different and i don't see how we can merge them to single library. But i think we might synchronize the API, so people will be able to use both libraries interchangeably. But to be honest i like the ps2dev syntax better :-) Since it follows the arduino serial syntax. Note that there are multiple arduino serial implementations which can also be used interchangeably: Eg.: native serial, soft serial, usb serial. You can use any of them without changing the code...

Both ps2dev and ps2-keyboard are low level libs. But if we have common API, we can start implementing some high level features in way that it will be able to run on top of both libs. So people will be able to chose if they want interrupt based (when they need more free cycles) or synchronous communication (for when they need more free timers)

Eg.: scancode table which will enable us to send arbitrary string to be typed on computer screen without having to worry about stuff like make/break or shift for uppercase letters.

ndusart commented 5 years ago

I am not author of that code. It was some kind of library that was echoing through the internet forums, but was lacking proper repository and documentation. So i created the repository for it

However in arduino library manager there are already some libraries which can read data from keyboard. But there is none, which can act as keyboard, that's why i started working on this...

Didn't know about that library, but it's great to see these kind of libraries easily available. Thanks for fulling the gap of simulating a keyboard then.

There are still some issues with timing, so there's need to call delay(50) after each byte is sent. Hopefully i'll track it down, so this is not needed.

I was not talking about these delays but more about the delays in the library itself ( delayMicroseconds(CLKFULL)), this is fine for a project where it is fine for the CPU to be absolutely blocked when used for sending keystrokes.

My implementation may seem a bit more complex because my use case was to be able to do these two tasks concurrently:

These was for integrating an iPad generating some data for old cash system in a store. Some keyboards are very touchy about PS/2 timings, if you do not respect absolutely the 40us, you're out and sometime you even have to power cycle the keyboard to make it work again ! The DTMF decoding is also quite time sensitive. Keyboards and DTMF events can happen at the same time, so you don't have much time to react and you cannot just afford to do "wait for a key event, then listen for DTMF, then send keys, and repeat", all have to happen simultaneously.

Arduino has some timer/interrupt abstraction layer, so if you can use that instead of directly modifiing registers, you should be OK.

Unfortunately, this api does not let you configure the timer as precisely as I would have needed.

Both ps2dev and ps2-keyboard are low level libs. But if we have common API, we can start implementing some high level features in way that it will be able to run on top of both libs. So people will be able to chose if they want interrupt based (when they need more free cycles) or synchronous communication (for when they need more free timers)

I like that idea, we could tell people that synchronous library is more compatible with other libraries and should be used by default while interrupt based may not work with other libraries using the timer 1 (I do not know if there is a list for that) but could free up some CPU resources.

I think the only blocking point then is the compatibility with other boards. I may look at the references, but I don't have other boards to actually test it.

I'll try to do that when I have some time and I'll map my API to yours :)

Eg.: scancode table which will enable us to send arbitrary string to be typed on computer screen without having to worry about stuff like make/break or shift for uppercase letters.

A higher-level API would be greatly appreciated. I had contact with a few person wanting to use ps2-keyboard and they struggle to know how to use it to send actual keys to the computer. So would be definitely a good contribution :)

Harvie commented 5 years ago

Some keyboards are very touchy about PS/2 timings, if you do not respect absolutely the 40us, you're out and sometime you even have to power cycle the keyboard to make it work again !

Is 40us given by protocol specs? I was thinking about clocking whole thing bit faster for higher keystroke rates...

There are still some issues with timing, so there's need to call delay(50) after each byte is sent. Hopefully i'll track it down, so this is not needed.

I was not talking about these delays but more about the delays in the library itself ( delayMicroseconds(CLKFULL)

Yes i know. But there's some timing bug in ps2dev, which causes that there has to be aditional delay after each byte write() added separately by API user. I saw other people doing delay(100), but i tried delay(50) and it seems to work as well... But now that i know that ps2 timing is in microseconds i want to figure shortest possible delay and add it directly into the write() method, so people don't need to call the delay separately.

they struggle to know how to use it to send actual keys to the computer.

I struggle too... I've never learned scancodes before, but it turns out there are several scancode tables... I've found that this is how you press and release [0] button: 0x45 0xF0 0x45, but before i saw some scancode table that was doing release of button using single byte... it was something like 0x95 instead of 0xF0 0x45 to release [0], can't find it right now...

A higher-level API would be greatly appreciated.

I was implementing numeric-only keyboard and all i needed was this scancode table:

char scancodes[] = {0x45, 0x16, 0x1E, 0x26, 0x25, 0x2E, 0x36, 0x3D, 0x3E, 0x46, 0x7C, 0x7C}; //Scancodes for numbers 0-9, *

ndusart commented 5 years ago

I couldn't find "official" specifications unfortunately but I use this file: https://www.avrfreaks.net/sites/default/files/PS2%20Keyboard.pdf which is quite complete.

According to this paper, the clock period has to be between 30us and 50us. Some PS/2 controller may have some assumption about that (like, ok there is a falling edge, I still have this number of cycles to read the value) and may stop working with your device if you go faster.

But test it, it could work, the host should use the edges to determine when reading the data line and if it can, it will follow your rate. Maybe through an option that enable that behaviour, to let users that are more concerned about compatibility with old computers using your library.

I am not sure if this is caused by missing parity bit. Maybe the computer expects parity bit and if you start sending another byte at that time, the first bit is interpreted like parity of the previous byte, instead of first bit of new byte....

The parity bit is absolutely necessary. If it is wrong, the computer will discard your byte and will send you an error response. If your implementation does not write it, then the additional delays should not help because you still need to toggle the clock line for the host to read that parity bit. And there is a stop bit that is necessary as well (always 1).

Maybe you could go as low as a bit more than 50 us (delayMicroseconds(70)) for this additonal delay as, according to the paper, the clock line must be high for at least 50 us before sending each byte. This could be your problem fro successive write().

And you have to consider that the host may want to speak to your device too. Right now, write() just returns a negative integer for this case and does't provide the clock for the host to actually send its byte, so I wonder how may react some hosts in this case, they may just keep trying to repeat it during a timeout, and it could also explain why you have to use some longer delays between writes.

I struggle too... I've never learned scancodes before, but it turns out there are several scancode tables... I've found that this is how you press and release [0] button: 0x45 0xF0 0x45, but before i saw some scancode table that was doing release of button using single byte... it was something like 0x95 instead of 0xF0 0x45 to release [0], can't find it right now...

There is actually two different code sets. For press-release 0, one is indeed 0x45 0xF0 0x45 while the other is 0x0B 0x8B. These tables are provided in the given paper.

Harvie commented 5 years ago

Thanks for interresting insights!

The parity bit is absolutely necessary.

I've checked again and actually there's a code for parity bit, so this should be ok. I thought that i've read somewhere that parity is not supported, but it was probably different library.

the clock line must be high for at least 50 us before sending each byte.

Currently this is the last line of write() method: delayMicroseconds(50);, for some reason it's not enough... I think i will try 80us next time (twice the CLKFULL)... I don't have the hardware at hand right now...

There is actually two different code sets.

Which one is preffered? (in terms of having better support across devices)

Harvie commented 5 years ago

Eh. Checked again and the write ends like this:

  // stop bit
  gohi(_ps2data);
  delayMicroseconds(CLKHALF);
  golo(_ps2clk);
  delayMicroseconds(CLKFULL);
  gohi(_ps2clk);
  delayMicroseconds(CLKHALF); //20 us
  delayMicroseconds(50);

Which means, there actually is 70 us delay with CLK pulled high, but it's stiil not enough... I guess something fishy is going on...

Harvie commented 5 years ago

I've changed that last line to delayMicroseconds(500); and now it seems to work properly.

But i've realized that the keyboard is not working until i plug real ps2 keyboard to that ps2 port. Once i type few letters on real keyboard, unplug it and plug arduino in, it starts to work magicaly. Is there some init sequence that keyboard has to send before it can start sending scancodes? It's combined mouse/keyboard (purple/green) ps2 port, so maybe it is used to switch between keyboard/mouse modes...

ndusart commented 5 years ago

I think keyboards only sends 0xAA when powering up.

This command tells that the keyboard controller self-test was okay.

But generally, the computer generally sends a few commands to set the initial state of the LEDs (numlock, ...) and other settings. If you do not acknowledge them, it may think the keyboard is not working and stop listening to the port.

I do not know how this could fit in your sync API but your library should ack computer commands even if it does not provide a way to receive commands.

ndusart commented 5 years ago

I just read the commits from the past couple of days and it seems you just have implemented that acknowledgment, great :)

Can you get rid of the real ps2 keyboard then ?

Harvie commented 5 years ago

Can you get rid of the real ps2 keyboard then ?

What do you mean? It works so i was able to replace the ps2 keyboard with arduino. But had to increase the delay between sent bytes even further...

BTW i was thinking that i can just use attachInterrupt() to catch computer requesting to send data on CLK and then handle all communication synchronously just like i do now. It would be sensible tradeoff between doing everything using interrupts and doing everything using delays... Also easily portable and compatible. But still better than having to check CLK every 10ms like i do now...

Given that computer sending data to keyboard happens only occasionaly and takes just few ms...