Close

[C] KMK module for ADNS-5050 optical sensor

A project log for WK-50 Trackball Keyboard

Reverse-engineering a bat-shaped, hot-swappable keyboard with a 38mm trackball and RGB-backlit encoder.

kelvinakelvinA 09/01/2024 at 08:510 Comments

The general gist of events over the past 2 days was that:

  1. I read through QMK's ADNS5050 implementation and KMKs ADNS9800 and Pimoroni Trackball implementations
  2. Started skimming though the ADNS5050 and ADNS9800's datasheets to see differences
    1. It was only until yesterday that I found the full 9800 datasheet (with timing information) instead of the 9-page one I was reading the day before.
  3. Copied over the 9800 and started making changes, such as importing the 5050's register list and timing requirements.
  4. Learned about SPI phase (CPHA) which determines if data is sampled on the first or second edge.
  5. Find some code to get the 2s complement.
    1. Then, on day 2, just port over the method from QMK because it was more readable.
  6. Find out that busio can only do half_duplex on STM chips, which is the last thing on Day 1
  7. Day 2, changed to bitbangio. Still no readout from sensor.
  8. Did another continuity test to confirm that the sensor pins are correct.
    1.  I also don't think GPIO11 is connected to anything and there's just a via nearby to allow GND into the copper pour.
  9. Looked into RP2040 5V pin tolerance, because there's no level shifters between the 3.3V output of the RP2040 and the 5V-supplied ADNS-5050.
  10. Learned that microcontroller.delay_us and time.sleep work in opposite ways to what I thought. The latter is the one that holds up everything, and is why it's only recommended for very short delays.
  11. Decided not to assume how bitbangio worked and instead ported over the QMK implementation
    1. Not exactly sure why one timing was implemented the way it was and so changed it to better reflect the datasheet:
    2. def adns_serial_write(self, data):
          
          Before:
          microcontroller.delay_us(2)
          self.clk.value = True
      
          After:
          microcontroller.delay_us(1)
          self.clk.value = True
          microcontroller.delay_us(1)
      
  12. Finished porting and was able to read the Product ID and Revision ID from the sensor!
  13. Squashed bugs related to the fact that every function in here needs to have self as the first argument.
  14. Implement the get/set CPI functions and a debug readout of if the sensor is in Mouse Control 1 or 2 and the CPI if it was in Control2 mode.
  15. Found out that the trackball polling is max 120Hz, usually in the 90Hz range.
  16. Tried messing around with the LED register. 
    1. Seems setting bit 6 forces the LED into pulse mode and the sensor won't detect anything.
    2. Setting bit 7, as mentioned in the datasheet, keeps the LED in DC mode. It's less distracting but it also means that it's more susceptible of small mouse vibrations when the ball is stationary.
  17. Remove the pins that were never used and sent a PR to CircuitPython for the keyboard, now that I know it all works.
  18. Found a nice and easy way to do north rotations using complex numbers and implemented it.
  19. Found a write-up for analog-stick deadzones and I implemented it but it doesn't work as well for mice, which doesn't have continuous displacement.
  20. Lastly, I tried my finger and it seemed to track fine, so it might be an issue with the matte grey ball or it's mounted distance to the sensor.

I am glad that I decided to go the KMK route. Things like probing the LED register would've taken much longer if I had to recompile and flash every single time.

Discussions