Jump to content
  • 0
JanSwanepoel

How does PS2 keyboard interface work

Question

Hi everyone,

I am trying to understand how PS2 keyboard output is read by the CommanderX16.
From my understanding the data and clock pins of the keyboard is connected to VIA2 pins PA0 and PA1. Initially I thought that the VIA would fire an interrupt when the clock changed which samples the data pin. But glancing over the datasheet of the 65C22 I don't see the possibility to generate an interrupt if the clock pin (PA1) would change. Maybe the clock is also separately connected to an interrupt pin? If not, then I guess the keyboard is not interrupt driven?

Next I tried to read the kernal keyboard driver code but my assembly skills are a bit limited. Looks like clock (PA1) is held low which prevents the keyboard from sending data. When the program want to read the keyboard it releases the clock line and then starts to sample the data and clock pins until the whole scancode was read. But this would mean that when the program wants to check for a key press it will block until the user presses a key which also does not sound correct?

Does anybody have some insights on how this works?

Share this post


Link to post
Share on other sites

14 answers to this question

Recommended Posts

  • 0

So the thing you’re missing about the keyboard is that the keyboard generates the clock. And since PS/2 is an event driven protocol (messages are sent when a key is pressed or released), the system requires interrupts. 
 

So the keyboard and mouse ports will be hooked to the IRQ line, because they have to be.

So the process would be that when you press a key, the keyboard will cycle the clock line, triggering an interrupt, and the computer will then start reading the clock and data lines. Once the data has been read from the port, the character will be placed in the keyboard buffer. 
 

I haven’t looked at the keyboard code in the emulator yet, but it’s possible that the emulator is injecting values directly into the system; that’s the approach I took when I first started my own virtual computer project. 

Share this post


Link to post
Share on other sites
  • 0

We have new code in testing that the emulator doesn’t reflect. When then code passes and we know it works reliably we will update the emulator and documentation. How it works now is not how it will work down the road.

 

 

Sent from my iPhone using Tapatalk

  • Like 1

Share this post


Link to post
Share on other sites
  • 0

One thing to note though is you should from a good coding standpoint, never attempt to read the keyboard or mouse directly. You should use the KERNAL routines instead. If you try to write your own routines it will cause several bad side effects.

One is that if the way the keyboard works changes, it will break your code every time. What if a future version used a USB keyboard or a direct matrix? If you use the KERNAL routines you will always work and it would be the KERNAL that would change to match the hardware.

Also we have found some keyboards have different requirements. We are making our routine as robust as possible. Custom routines may not be as forgiving.


Sent from my iPhone using Tapatalk

  • Like 2

Share this post


Link to post
Share on other sites
  • 0
One thing to note though is you should from a good coding standpoint, never attempt to read the keyboard or mouse directly. You should use the KERNAL routines instead. If you try to write your own routines it will cause several bad side effects.

One is that if the way the keyboard works changes, it will break your code every time. What if a future version used a USB keyboard or a direct matrix? If you use the KERNAL routines you will always work and it would be the KERNAL that would change to match the hardware.

Also we have found some keyboards have different requirements. We are making our routine as robust as possible. Custom routines may not be as forgiving.


Sent from my iPhone using Tapatalk

Will the new kernal routines allow you to detect when a key is held and released?


Sent from my iPhone using Tapatalk

Share this post


Link to post
Share on other sites
  • 0

Just a thought here:
One possibility could be the kbd/mouse firmware just store in RAM the data.
That will save some cycles on the caller side. just read RAM rather than call a sys function.

Share this post


Link to post
Share on other sites
  • 0
18 hours ago, TomXP411 said:

So the process would be that when you press a key, the keyboard will cycle the clock line, triggering an interrupt, and the computer will then start reading the clock and data lines. Once the data has been read from the port, the character will be placed in the keyboard buffer. 

That makes much more sense. So then it means that the clock line is not only connected to PA1 but also tied to the interrupt pin (probably the non-maskable pin) of the 6502. So in the ISR it could then check PA1 to determine if the keyboard generated the interrupt...

15 hours ago, Lorin Millsap said:

One is that if the way the keyboard works changes, it will break your code every time. What if a future version used a USB keyboard or a direct matrix? If you use the KERNAL routines you will always work and it would be the KERNAL that would change to match the hardware.

Good point, I will for sure rather use the Kernal routines. The question was more out of curiosity and to understand how the mechanism works. Most of the projects I have seen in the past used some micro-controller to handle the PS2 keyboard and this direct handling had me curious.

Share this post


Link to post
Share on other sites
  • 0

For anybody curious, Ben Eater published a deep dive into PS/2 kbds last week.  Very useful and top quality work as usual.

This is not about X16 specifically but to the OP's question... "how PS/2 keyboard is read..." this will solve the mystery regardless of target platform at the lowest level (serial pulses to parallel shift with overflow/carry is built piece by piece in expert but easy to consume fashion).

His next video is being contemplated now.  As a Patreon support of Ben's, I requested that he both interface directly to his 6502 project (which would be very close to what X16 has to do) and leverage an inexpensive microcontroller to do the dirty work.

 

 

Share this post


Link to post
Share on other sites
  • 0
Posted (edited)
On 1/16/2021 at 11:26 AM, JanSwanepoel said:

Hi everyone,

I am trying to understand how PS2 keyboard output is read by the CommanderX16.
From my understanding the data and clock pins of the keyboard is connected to VIA2 pins PA0 and PA1. Initially I thought that the VIA would fire an interrupt when the clock changed which samples the data pin. But glancing over the datasheet of the 65C22 I don't see the possibility to generate an interrupt if the clock pin (PA1) would change. Maybe the clock is also separately connected to an interrupt pin? If not, then I guess the keyboard is not interrupt driven?

Next I tried to read the kernal keyboard driver code but my assembly skills are a bit limited. Looks like clock (PA1) is held low which prevents the keyboard from sending data. When the program want to read the keyboard it releases the clock line and then starts to sample the data and clock pins until the whole scancode was read. But this would mean that when the program wants to check for a key press it will block until the user presses a key which also does not sound correct?

Does anybody have some insights on how this works?

I think the Kernal (R38) currently does the following.

At system startup:

  • ioinit function is called (kernal/cbm/init.s:20)
  • ioinit function calls ps2_init function (kernal/drivers/x16/x16.s:27)
  • ps2_init disables all PS/2 communication by pulling clock pin low and data pin high (kernal/drivers/x16/ps2.s:27-38)

As far as I understand, the PS/2 communication stays in this state until VBLANK:

  • The function kbd_scan is called as part of the IRQ handler (kernal/cbm/irq.s:50)
  • kbd_scan calls _kbd_scan (kernal/drivers/x16/ps2kbd.s:49)
  • _kbd_scan calls receive_down_scancode_no_modifiers (kernal/drivers/x16/ps2kbd.s:126)
  • receive_down_scancode_no_modifiers calls receive_scancode (kernal/drivers/x16/ps2kbd.s:261)
  • receive_scancode calls ps2_receive_byte (kernal/drivers/x16/ps2kbd.s:225)

ps2_receive_byte is the function that actually fetches PS/2 data:

  • First it releases the clock and data pins (kernal/drivers/x16/ps2.s:51-53). As you might remember, these pins were pulled low and high at system startup.
  • The function polls the state of the clock and data pins at most 10 times (kernal/drivers/x16/ps2.s:55-60) looking for a start condition (clock and data low). I guess 10 times is a timing issue. I haven't done the math on how much time is really spent in that loop before aborting.
  • If no start condition found within the 10 loops:
    • Branch to line 98 where the ps/2 disable function is called, the same that disabled PS/2 communication at system startup
  • If start condition found:
    • 8 bits and 1 parity bit are read and stored (kernal/drivers/x16/ps2.s:62-81)
    • ps2dis is called to disable PS/2 communication again (kernal/drivers/x16/ps2.s:82).
    • And finally the function returns after loading the received byte into A 

Some conclusions:

  • The Kernal isn't faking it. It reads a byte bit by bit, as you would expect
  • PS/2 communication is disabled almost all the time by the Kernal pulling the clock and data pins
  • The Kernal only releases the clock and data pins every VBLANK to read the keyboard
  • It reads the keyboard by polling the state for a period of time that is sufficiently long to follow PS/2 standard
  • The above suggests that there is no special IRQ line driven by the keyboard. 

I'm sorry if I have misunderstood any aspect of how this works.

It will be interesting to see what changes, as the team is redesigning this code according to @Lorin Millsap above in this thread.

Edited by Stefan
Language errors
  • Like 1

Share this post


Link to post
Share on other sites
  • 0
Posted (edited)

A small correction to my previous post 🙂

  • The ps2_receive_byte doesn't poll the PS/2 state 10 times, but 10  times multiplied by processor frequency in MHz => 80 times in total
  • Manually calculating the cycles spent in the polling loop, I got 11 cycles per loop (more if the code would be split over a page boundary)
  • That sums up to 880 cycles equivalent to about 110 us @ 8 MHz
  • I've read that a PS/2 device is required to buffer data to be sent if the host holds the clock line low, and that transmission should begin not before 50 us after the clock line is released
  • The Kernal is waiting and polling the PS/2 state for about 0.66% of the total VBLANK (0.01667 s @ 60 Hz), if my calculations are right (= 0.00011 s / 0.01667 s).
  • And probably double that time if we take into consideration the PS/2 mouse
Edited by Stefan

Share this post


Link to post
Share on other sites
  • 0
22 hours ago, EMwhite said:

His next video is being contemplated now.  As a Patreon support of Ben's, I requested that he both interface directly to his 6502 project (which would be very close to what X16 has to do) and leverage an inexpensive microcontroller to do the dirty work.

Would be very interesting to see what he comes up with! To me using a microcontroller would be cheating a little bit, unless it is one that was also available in the same era or is less powerful than the main CPU 😉

@Stefan Great analysis work! I take it you are also interested in how this works.

20 hours ago, Stefan said:

It will be interesting to see what changes, as the team is redesigning this code according to @Lorin Millsap above in this thread.

Same here, very curious to see what they come up with. I guess the interrupt driven method saves some cycles compared to constantly polling at some interval.

By the way, @kktos also had a great idea by storing key presses in RAM to be able to access them very quickly! I think this is also done by the C64 KERNAL...

Share this post


Link to post
Share on other sites
  • 0
39 minutes ago, JanSwanepoel said:

Would be very interesting to see what he comes up with! To me using a microcontroller would be cheating a little bit, unless it is one that was also available in the same era or is less powerful than the main CPU 😉

I actually don't really view the X16 as  "Retro" computer, but more a bare-metal 8-bit learning, gaming, and demoscene computer. It has several modern implementations already when you look at it (like an ATX form factor, modern SRAM chips, modern banking implementation, SD card support, etc.).

It's just a frame of mind and just my opinion but this means, to me, using a microcontroller would be a-ok here. Of note, the PS/2 keyboards likely had some sort of uC or at least a set of logic gates for the output. And given Kevin like's ATTiny's (given his latest video), while that's a modern uC, it's still 8-bit. And it's through hole and also updateable which I think would be really need for a keyboard controller. You could upload your own keymap behavior for instance. It's also off the shelf, popular, cheap and with great availability.

If we were really leaning on Commodore heritage here, using another 6502 to manage peripherals might be a more period-specific implementation here (thinking about the disk drives having their own 6502 variants for instance). But that would be a lot more costly in board design and part counts than just using a cheap uC.

uC's may be necessary for these sorts of things, but at least if using something well known like Arduino-compatible chips, it means the system is still largely off the shelf, hackable, and still 8-bit.

Share this post


Link to post
Share on other sites
  • 0

So Ben's next video is out and he did not disappoint by coming up with a very interesting solution without using a microcontroller! I suppose the only drawback is that you can't really talk back to the keyboard to switch the LED's and stuff like that. Losing the parity bit is probably also not really a big issue unless your circuit is really noisy. If it's really an issue I guess it could also even be handled by some additional circuitry...   

 

  • Like 2

Share this post


Link to post
Share on other sites
  • 0
On 3/11/2021 at 5:52 PM, m00dawg said:

Of note, the PS/2 keyboards likely had some sort of uC or at least a set of logic gates for the output.

The PC/AT and the PS/2 series both used an Intel 8042 microcontroller to operate the host end of the keyboard interface.

Share this post


Link to post
Share on other sites
  • 0

It’s a shame PS2 sends 11 bits because the VIA has built-in shift register functionality, but only 8 bits wide. The VIA could shift in the PS2 data automatically and the kernel could just read the latched value at leisure.

But not only are there too many bits, there are also too many bytes... maybe VIA could pause the PS2 device between each byte but the problem remains that the parity and stop bits will push the 2 MSB of the keycode out of the latch.

now, the joysticks don’t have this problem. THEY return 8 bits per latch (rather 16 but that’s just two runs of shifting in 8 bits) However, I bet the clock/latch pins of the controller ports aren’t connected to the correct pins of the VIA to do this in HW.

If this is the case, understandably, they’re not going to redesign the board to leverage this.

now I’m curious and gonna go look that up... lol

Update:

Quote

I bet the clock/latch pins of the controller ports aren’t connected to the correct pins of the VIA to do this in HW.

Confirmed - the emulator's via.h specifies that the DATA pins of the controllers 0..4 are connected to VIA1:PA7..PA4, and the common latch/clock pins are driven by PA2 and PA3, respectively.

The shift register uses CB1 and CB2. Using these, it would've been possible to do hardware-accelerated joystick polling, but it would've limited the system to using one joystick per VIA, and would still have required one data pin to be an output for sending latch commands to the controllers. Using SR mode 2, the VIA would shift in 8 bits from the controller every time you read from the shift register. Mode 2 means shift one bit each clock cycle, and stop after shifting 8 bits. So in this regime, you can assume the latch holds the previous value shifted in (except for the very first time, which could be done once during system init to seed this). You would just read from the latch however many bytes you want from the controller, and prior to reading the last byte (every byte for NES, every other byte for SNES), you would set and clear the latch pin so the final read will simultaneously trigger the VIA to shift in the first byte of this latching.

Edited by ZeroByte

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...

Important Information

Please review our Terms of Use