Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PS/2 GPIO Bit-banging driver #15

Open
wants to merge 2 commits into
base: v3.2.0+zmk-fixes
Choose a base branch
from

Conversation

infused-kim
Copy link

@infused-kim infused-kim commented Apr 12, 2023

This PR adds a PS/2 GPIO bit-banging driver, which is needed for the zmk ps/2 mouse PR (zmkfirmware/zmk#1751).

Zephyr has support for PS/2, but only for very few PS/2 chips. It didn’t have support for a GPIO bit banging PS/2 driver.

This driver implements the basic communication over the PS/2 protocol.

The driver is completely interrupt driven and does zero polling. So it should be as battery efficient as possible.

Interrupt and timing challenges

Explanation

There were a few challenges with the implementation that I would like some feedback on.

Initially, I was testing it on a blackpill and over usb. It was working very well.

But as soon as I tried running it on a nice!nano, the driver wasn’t able to handle the interrupts fast enough.

To give you a little background info. The PS/2 protocol uses two pins, clock and data. The clock operates at 10.0–16.7 kHz (around 60-100 us per clock).

The ps/2 device controls the clock most of the time and data is read and written by the host on the falling edge. This has to happen within 30-50 us of the falling edge.

There is an interesting discussion on the [zephyr github about the expected latencies](zephyrproject-rtos#41401). And apparently just the lower half of the bluetooth interrupts consume over 100us.

So what I ended up having to do was change the interrupt priorities:

  • GPIOTE interrupt priority from 5 to 0
  • BT_CTLR_LLL_PRIO from 0 to 1
  • BT_CTLR_ULL_HIGH_PRIO and BT_CTLR_ULL_LOW_PRIO from 1 to 2
  • NRF_DEFAULT_IRQ_PRIORITY (basically all other interrupts) from 1 to 3

Additionally, it’s not possible to change the priority of a single pin. Most controllers (including nrf52840) raise one interrupt if there is a change on any of the gpio pins. Then a driver, called gpiote, scans the pins and calls the appropriate callbacks.

So, raising the gpiote interrupt doesn’t just raise it for the PS/2 pins, but also for the key matrix.

In my testing so far, this hasn’t been an issue though.

I didn’t like that I had to do this, but I don’t see another choice.

Implementation Feedback

Part of the difficulty is how to implement these interrupt priority changes. The ideal scenario is that the end-user shouldn’t have to worry about interrupt priorities.

The user adds the PS/2 driver to their devicetree, and the priorities should be set correctly so that it works.

The problem is that there are so many variables. Some chips need the priorities changed, some don’t…

Some priorities can be changed through Kconfig options and some need to be changed in the device tree.

The default interrupt priority on nrf chips is changed through the devicetree define #define NRF_DEFAULT_IRQ_PRIORITY 3 and it has to be added into the zmk (not zephyr owned file) app/dts/arm/nordic/override.dtsi.

As we discussed on discord devicetree files are processed before Kconfig settings. So we can’t rely on Kconfig alone.

For now I am overridng NRF_DEFAULT_IRQ_PRIORITY on the zmk side in my ps2 mouse PR. And I’m changing the BT priorities in the Kconfig.gpio on the zephyr side.

I imagine that will need to be changed, but I just wanted to submit the PR and get the conversation going.

Conclusion

I think, everything else was pretty straight forward. I look forward to hearing how to improve the code further.

When the PS/2 driver detects an error in an incoming transmission, it’s supposed to send 0xfe to the device to ask it to resend the last packet.

But a PS/2 packet can be more than one byte (such as a 3 or 4-byte mouse packet).

So, on reception of the 0xfe resend command, the PS/2 device resends ALL bytes in the packet and not just the one where the transmission failed.

This can cause the higher level driver’s packet buffer to become misaligned.

This PR adds a callback that notifies the higher level driver when the PS/2 driver requested a resend so that the higher level driver can expect a new packet and doesn’t get misaligned.
@infused-kim
Copy link
Author

I created a new driver that is using the UART controller instead of GPIO interrupts to improve the performance and reduce connection errors.

That is going to be a new PR that will superseed this one, but currently I can't open it, because it depends on zephyr 3.5 and that branch is only in petejohanson's private repo and not here.

For now the new code can be found here:
https://github.com/infused-kim/zmk-zephyr/commits/pr/ps2_uart/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant