Dalius's blog

Tuesday, November 7, 2023

Keyboard with joysticks (part 5): QMK part

Previous parts:

Since I have working hardware (even if I don’t like it too much), I can work on software part now. Here I will try to describe what problems I have with joystick and how I have solved it.

TL;DR: read summary in the end if you need only practical advices


Analog joysticks are supposed to report axis value with number between 0 and 1024 (I guess different max value is possible as well, but I have not seen different value yet). Initially joystick is in some center position (let’s assume it is 512). So let’s say we are working with left-right axis:

  • If joystick reports value lower than 512, then we move to left.

  • If value is larger than 512 we move to right

  • If difference between center value (512) is low (e.g. 50) we move cursor slowly.

  • If difference is large (512 max in our example), we move cursor faster.

That’s theory, now let’s move to practice.

PSP 2000 is quite poor joystick (or at least version I have got from AliExpress, but I doubt that there is another one). Here is what reported by this joystick:

  • X and Y max value: 1024 (so far so good)

  • X min value: 217 (WTF?)

  • Y min value: 175 (double WTF?)

  • X center value: between 724 and 805 (depends on I move cursor left or right)

  • Y center value: between 685 and 753 (depends on I move cursor up or down)

Now this introduces some problems that must be solved.

Floating pointer (X/Y center problem)

As center does not have stable position (at least for this joystick) we might end up with floating pointer. E.g. you move joystick to left, stop operation, but it moves further slowly to left by itself. This happened with ANALOG_JOYSTICK_WEIGHTS I have introduced in Joystick algorithms, but has not happened with default QMK algorithm. I have decided to look why. Here is the code that is important to us (source):

float percent = (float)coordinate / 100;
result = percent * maxCursorSpeed * (abs(coordinate) / speedRegulator);

maxCursorSpeed equals to ANALOG_JOYSTICK_SPEED_MAX (default is 2) speedRegulator equals to ANALOG_JOYSTICK_SPEED_REGULATOR (default is 20) coordinate - is value between 0 and 100 that corresponds to relative joystick position from center.

Now let’s assume we have the following situation: max value is 1024, center position is 724 (registered by QMK on analog_joystick_init) and we move joystick towards to 1024 and release it. Then joystick stabilizes on 805. We can calculate coordinate now:

1024-724 = 300 (max value)
805-724 = 81 (current value)
81/300 * 100 = 27 (coordinate)

Now if we put it in formula above we get:

27 * 2 * (abs(0.27) / 20) = 0.729

This then is rounded down to 0 and that means that cursor does not move. We need at least 32 to get cursor moving. As well that means that by increasing speed (e.g. decreasing ANALOG_JOYSTICK_SPEED_REGULATOR) we would end up with floating pointer problem for default QMK algorithm.

That basically indicated that I need to set first 30 ANALOG_JOYSTICK_WEIGHTS to 0 like this:

#define ANALOG_JOYSTICK_WEIGHTS {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,28,29,29,30,31,33,34,35,36,37,40,41,43,44,48,49,51,56,58,60,65,68,70,73,79,82,85,89,96,100}

This solved floating pointer problem for me.

Min value problem

Now if min value is not 0 then you will get different max speeds when moving cursor to left and right. That’s not desirable behavior. QMK allows settings min value with ANALOG_JOYSTICK_AXIS_MIN. Now the problem for me that X and Y have different min values. As well, I think as user you shouldn’t care about min and max axis values. To solve this problem I have introduced ANALOG_JOYSTICK_AUTO_AXIS that resolves min/max values while you manipulate joystick and understands that different axis might have different mix/max value. This is only in my branch (this commit https://github.com/daliusd/qmk_firmware/commit/6569be899f755706ba582959e7cec49581f0448e), but I promise to make QMK PR when it will be possible.

Sliding joystick problem

Now imagine how you use joystick: you want to move cursor to some specific position and press joystick to desired location. You watch your cursor moving in screen and reaching the desired position. Then you release your cursor, but it overshoots and slides further. This happens because on joystick release it reports intermediate positions (e.g. while returning from 100 to 0, it might report at least 4 positions, like 86, 64, 36 and 18). To solve this problem I wrote some code to ignore when cursor is returning to center.

Again it is only in my branch now, commit here, but I hope it will reach QMK eventually: https://github.com/daliusd/qmk_firmware/commit/9c4bd415ee2fdaef639271fdcfafda25357bfeab

Scrolling using joystick

I have added functionality that if I click specific layer button then joystick can be used for scrolling. This is well documented in QMK, so I am only sharing the code here:


Practical experience

While PSP 2000 is bad joystick it is usable enough that I don’t want to move my hands from the keyboard to grab the mouse. In the future I hope to test different joysticks with expectation that they work better.


Overall I have found out that PSP 2000 joystick can be used as mouse with QMK, but it needs some tweaks. Mentioned settings are not in QMK as of 2023-11-07, but I hope I will manage to get them in there eventually:

  • If you have floating pointer problem then try to set 30 (or try different number) initial values of ANALOG_JOYSTICK_WEIGHTS to zero:
#define ANALOG_JOYSTICK_WEIGHTS {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,28,29,29,30,31,33,34,35,36,37,40,41,43,44,48,49,51,56,58,60,65,68,70,73,79,82,85,89,96,100}
  • Use ANALOG_JOYSTICK_AUTO_AXIS - should solve the need to debug what values are reported by your joystick.

  • Use ANALOG_JOYSTICK_CUTOFF - should help to move joystick more precisely.

  • You can use single joystick both for moving cursor and for scrolling (read QMK documentation).