Disability Access to WASD via a light-weight joystick - Help needed please

Looking for help with the following:

I have a PS4/Xbox One (ALPS) thumb-stick. It's been lightened with a weaker spring and push button for a disabled person with minimal strength. They'd like to use this stick for key-presses

NORTH = KEY_W
WEST = KEY_A
SOUTH = KEY_S
EAST = KEY_D

With diagonals working too. The push button should work as another key, e.g. SPACE BAR.

The Arduino PCB I'm using is a Pro Micro ATMEGA32U4 (5v/16MHz). I have attached code where the joystick connects to GND, VCC (5V), A0 (x-axis), A1 (y-axis) for the analogue controls.

The attached code a (very busy) friend sent me sort of works... but if you actually try it in a game, it's stuttery in output. An example game here: Sacramento by Dziff, Ben Swinden that uses a mouse and WASD.

What I would like is....

  1. Someone to help revise the code so that it works as well as actually pressing WASD on a real keyboard in-game.

And if possible, ideally....

A deadzone variable called deadzone.
A sensitivity variable called sensitivity. Really helpful for people with very minimal use.
A way to add a digital push-button (e.g. pin 8 + GND when linked triggering SPACE BAR until released).

Any help would be hugely appreciated, as I'm a complete novice when it comes to Arduino programming. BBC Basic I can do. This, I'm floundering with.

Barrie
SpecialEffect.org.uk

WNSE_2_AWSD_v0.0.ino (1.73 KB)

BBC Basic is good enough, you have programming experience.

Arduino is for fast prototyping and learning about programming and electronics.
This forum is to you help you with that.

We can not provide a full working sketch. We don't have that joystick so we don't know what the values are or how accurate it is. We can throw code at you, but that would not be helpful.

You have made a good start with: Pro Micro board, joystick to A0 and A1, Keyboard library.

This is the Keyboard library: Keyboard - Arduino Reference.
This is an example: https://www.arduino.cc/en/Tutorial/BuiltInExamples/KeyboardMessage.
You are missing the Keyboard.begin(); in the setup() function.

The code keeps sending the Keyboard messages to the computer as long as the joystick is kept in that position. I think that drives the computer nuts. It should send those messages just once.
Do you want to walk through a game by keeping the keys pressed ? I'm not sure how to do that, so the code below might not work (not tested).

bool leftActivated = false;

void setup()
{
  Keyboard.begin();
}

void loop()
{
  if( !leftActivated)
  {
    // The 'left' is not active at the moment, check if it needs to turn on.
    if(rawX < leftThreshold - hysteresis)
    {
      Keyboard.press(0x41);
      leftActivated = true;
    }
  }
  else
  {
    // The 'left' is active, check if it needs to turn off.
    if(rawX > leftThreshold + hysteresis)
    {
      Keyboard.release(0x41);
      leftActivated = false;
    }
  }

  ...
}

Do the same for the others and show us the full sketch.
Can you put data into an array or struct and have that code just once instead of four times ?

Do you have a description of that joystick ? Is it a simple X/Y device with a single push button.
I would have thought that some explicit timings would also have been necessary to build up a profile of movements resulting in key presses.

Thank you Koepel. Really appreciated. I've integrated what I can from your suggestions into the code, which does compile. However, there's no keyboard like output when I move the joystick in Notepad.

@6v6gt - I'm using a very common ALPS 10kΩ analogue joystick module (specifically "ALPS RKJXV1224005" - with a data sheet here: https://www.mouser.co.uk/datasheet/2/15/RKJXK-1370937.pdf). It's essentially the same as the Adafruit type of thumb-stick I believe (e.g. http://https://www.adafruit.com/product/512). Two pots and one digital push-button.

This is the revised code we've been looking at directly to convert the joystick (NWSE compass points) to the keys WASD (including diagonals)...

[code]
// ALPS joystick (GND,VCC,A0,A1)
// to act as WASD for SpecialEffect.org.uk
// 3/4/2021
  
// Keycodes via: https://www.arduino.cc/reference/en/language/functions/usb/keyboard/keyboardmodifiers/
// and https://www.asciitable.com/
// W = Keyboard.press(0x57);
// A = Keyboard.press(0x41);
// S = Keyboard.press(0x53);
// D = Keyboard.press(0x44);


#include "Keyboard.h"

const int pinXAxis = A0;
const int pinYAxis = A1;

const int middleX = 512;
const int middleY = 512;
const int leftThreshold  = middleX - 256;
const int rightThreshold = middleX + 256;
const int upThreshold    = middleY - 256;
const int downThreshold  = middleY + 256;
const int hysteresis = 32;

bool leftActivated = false;
bool rightActivated = false;
bool upActivated = false;
bool downActivated = false;

void setup() {
  Keyboard.begin();
}


void loop() {
  int rawX, rawY;
  rawX = analogRead(pinXAxis);
  rawY = analogRead(pinYAxis);


 if( !leftActivated)
  {
    // The 'left' is not active at the moment, check if it needs to turn on.
    if(rawX < leftThreshold - hysteresis)
    {
      Keyboard.press(0x41);
      leftActivated = true;
    }
  }
  else
    // The 'left' is active, check if it needs to turn off.
    if(rawX > leftThreshold + hysteresis)
    {
      Keyboard.release(0x41);
      leftActivated = false;
    }


if( !rightActivated)
  {
    // The 'right' is not active at the moment, check if it needs to turn on.
    if(rawX < rightThreshold - hysteresis)
    {
      Keyboard.press(0x44);
      rightActivated = true;
    }
  }
  else
    // The 'right' is active, check if it needs to turn off.
    if(rawX > rightThreshold + hysteresis)
    {
      Keyboard.release(0x44);
      rightActivated = false;
    }

  

  if( !upActivated)
  {
    // The 'up' is not active at the moment, check if it needs to turn on.
    if(rawX < upThreshold - hysteresis)
    {
      Keyboard.press(0x57);
      upActivated = true;
    }
  }
  else
    // The 'up' is active, check if it needs to turn off.
    if(rawX > upThreshold + hysteresis)
    {
      Keyboard.release(0x57);
      upActivated = false;
    }
  


if( !downActivated)
  {
    // The 'down' is not active at the moment, check if it needs to turn on.
    if(rawX < downThreshold - hysteresis)
    {
      Keyboard.press(0x53);
      downActivated = true;
    }
  }
  else
    // The 'down' is active, check if it needs to turn off.
    if(rawX > downThreshold + hysteresis)
    {
      Keyboard.release(0x53);
      downActivated = false;
    }


  
  }

[/code]

Please press Ctrl+T in the Arduino IDE.

Check your links in your post. Use More/Modify in the lower-right corner of your post to modify your post and fix your links.

This is the link to the Alps Alpine stick: https://www.mouser.co.uk/ProductDetail/ALPS/RKJXV1224005?qs=RiQAlOPxzzAqLSX9lUzx8Q==.

We don't want to follow links to unknown things or open things. The sketch at dropbox is not needed, it does not have extra information.

I'm going to try to find a ATmega32U4 board :stuck_out_tongue:

Thanks. I've removed the Dropbox code, and have made the links more transparent.

I find that the board works the same for my simple scripts as a Leonardo Arduino if that's at all helpful. This type: https://uk.rs-online.com/web/p/arduino/7617315/

Your links are not created correctly (that could well be a forum software issue). It may also be better to paste the entire URL in the body of the post, then test it and edit it as necessary.

I'm sure timings are important also get the best effect. Hysteresis is actually timing dependent because it is the change that has occurred since the last measurement. In the case of the example code here the timing is implicit because it is the loop iteration time.

I'm sure there will be a lot of experiments necessary to to get a good solution which has the right combination of speed and accuracy. Is there any possibility of adding audio feedback, say to warn of entry into a dead zone, or maybe that the movement is already enough to identify the character selected, which would give the user an indication of his/her own performance ?

edit

post crossed with the OP

This is how I would do it. Look for state changes (see: File->Eamples->02.Digital->StateChangeDetection). When the state changes, determine if it is a button press or a button release.

// ALPS joystick (GND,VCC,A0,A1)
// to act as WASD for SpecialEffect.org.uk
// 3/4/2021


// Keycodes via: https://www.arduino.cc/reference/en/language/functions/usb/keyboard/keyboardmodifiers/
// and https://www.asciitable.com/
// UP = Keyboard.press('W');
// LEFT = Keyboard.press('A');
// DOWN = Keyboard.press('S');
// RIGHT = Keyboard.press('D');


#include "Keyboard.h"


const int pinXAxis = A0;
const int pinYAxis = A1;


const int middleX = 512;
const int middleY = 512;


const int DEADZONE = 32;


bool leftWasActivated = false;
bool rightWasActivated = false;
bool upWasActivated = false;
bool downWasActivated = false;


void setup()
{
  Keyboard.begin();
}




void loop()
{
  int rawX = analogRead(pinXAxis);
  int rawY = analogRead(pinYAxis);


  boolean upIsActivated = rawY > (middleY + DEADZONE);
  boolean leftIsActivated = rawX < (middleX - DEADZONE);
  boolean downIsActivated = rawY < (middleY - DEADZONE);
  boolean rightIsActivated = rawX > (middleX + DEADZONE);


  if (upIsActivated != upWasActivated)
  {
    // Up has changed
    upWasActivated = upIsActivated;
    if (upIsActivated)
      Keyboard.press('W');
    else
      Keyboard.release('W');
  }


  if (leftIsActivated != leftWasActivated)
  {
    // Left has changed
    leftWasActivated = leftIsActivated;
    if (leftIsActivated)
      Keyboard.press('A');
    else
      Keyboard.release('A');
  }


  if (downIsActivated != downWasActivated)
  {
    // Down has changed
    downWasActivated = downIsActivated;
    if (downIsActivated)
      Keyboard.press('S');
    else
      Keyboard.release('S');
  }


  if (rightIsActivated != rightWasActivated)
  {
    // Right has changed
    rightWasActivated = rightIsActivated;
    if (rightIsActivated)
      Keyboard.press('D');
    else
      Keyboard.release('D');
  }
}

Here is my $0.02. I added support for the joystick button. But it mostly works the same as the previous versions.

#include "Keyboard.h"   // Built-in library
#include "Bounce2.h"    // Install using library manager

// Assume analog joystick/thumbstick is connected A0 and A1.
const int PIN_X_AXIS = A0;  // middle=512, left<512, right>512
const int PIN_Y_AXIS = A1;  // middle=512, down<512, up>512
// Pots are not perfect so create a deadzone around the middle value
// to avoid drifting.
const int MIDDLE_X = 512;
const int MIDDLE_Y = 512;
const int DEADZONE = 32;

// Assume the stick button is connected to pin A2.
const int BUTTON_PIN = A2;
const int DEBOUNCE_INTERVAL = 10;
Bounce Button = Bounce();

#if 0
// Keyboard arrow keys for general use and most games.
#define UP_KEY    KEY_UP_ARROW
#define LEFT_KEY  KEY_LEFT_ARROW
#define DOWN_KEY  KEY_DOWN_ARROW
#define RIGHT_KEY KEY_RIGHT_ARROW
#else
// WASD works in games
#define UP_KEY      'w'
#define LEFT_KEY    'a'
#define DOWN_KEY    's'
#define RIGHT_KEY   'd'
#endif
#define BUTTON_KEY  ' '

bool Key_up_pressed = false;
bool Key_left_pressed = false;
bool Key_down_pressed = false;
bool Key_right_pressed = false;

void setup() {
  Keyboard.begin();
  Button.attach(BUTTON_PIN, INPUT_PULLUP);
  Button.interval(DEBOUNCE_INTERVAL);
}

void loop() {
  int rawX, rawY;

  rawX = analogRead(PIN_X_AXIS);
  if (rawX < (MIDDLE_X - DEADZONE)) {
    if (!Key_left_pressed) Keyboard.press(LEFT_KEY);
    Key_left_pressed = true;
    Key_right_pressed = false;
  }
  else if (rawX > (MIDDLE_X + DEADZONE)) {
    if (!Key_right_pressed) Keyboard.press(RIGHT_KEY);
    Key_right_pressed = true;
    Key_left_pressed = false;
  }
  else {
    if (Key_left_pressed) Keyboard.release(LEFT_KEY);
    Key_left_pressed = false;
    if (Key_right_pressed) Keyboard.release(RIGHT_KEY);
    Key_right_pressed = false;
  }

  rawY = analogRead(PIN_Y_AXIS);
  if (rawY < (MIDDLE_Y - DEADZONE)) {
    if (!Key_down_pressed) Keyboard.press(DOWN_KEY);
    Key_down_pressed = true;
    Key_up_pressed = false;
  }
  else if (rawY > (MIDDLE_Y + DEADZONE)) {
    if (!Key_up_pressed) Keyboard.press(UP_KEY);
    Key_up_pressed = true;
    Key_down_pressed = false;
  }
  else {
    if (Key_up_pressed) Keyboard.release(UP_KEY);
    Key_up_pressed = false;
    if (Key_down_pressed) Keyboard.release(DOWN_KEY);
    Key_down_pressed = false;
  }

  Button.update();
  if (Button.fell()) {
    Keyboard.press(BUTTON_KEY);
  }
  if (Button.rose()) {
    Keyboard.release(BUTTON_KEY);
  }
}

Thanks so much for trying to help me. I'm baffled, but my Arduino PCB isn't even working with the original code (copied below) that did before. I can programme it to work as an ordinary HID-joystick, but it will no longer behave like a keyboard at all. Truly odd. Any ideas from here appreciated. I can try a different PCB, but will take a bit of time to try.

Programmer "USBasp"
Board: "Arduino Leonardo"

BN: Arduino Leonardo
VID: 0x2341
PID: 0x8036

     /*
Version:    0.0    29/7/20

*/   

//*******************************************************************************************************************
//***************  User Config      *********************************************************************************   
//*******************************************************************************************************************
  
// #define PRE_RELEASE_VERSION
//*******************************************************************************************************************
//***************  Libraries        *********************************************************************************
//*******************************************************************************************************************

#include "Keyboard.h"



//*******************************************************************************************************************
//***************  System Constants *********************************************************************************
//*******************************************************************************************************************

  #define FALSE                    0
  #define TRUE                     1
  #define OFF                      0
  #define ON                       1
  #define NUL                     -1
  #define _10BIT                1024
  #define HALF_10BIT             512
  #define _8BIT                  255
  #define SPAN_8bit              255
  #define HALF_8BIT              127
  

  const long MPLEX_INTERVAL_xxMS  = 5;     

//*******************************************************************************************************************
// System Global Variables  *****************************************************************************************
//*******************************************************************************************************************

  static long PreviousMillis   = 0;

//*******************************************************************************************************************
// Hardware Definitions     *****************************************************************************************
//*******************************************************************************************************************

  #define JOY_Y_PIN   A0
  #define JOY_X_PIN   A1


//*************************************************************************************************
// MONITOR LIGHT **********************************************************************************
//*************************************************************************************************

  //Shorthands
  #define  d0              di[0]
  #define  d1              di[1]
  #define  d2              di[2]
  #define  d3              di[3]
  #define  d4              di[4]
  #define  d5              di[5]
  #define  d6              di[6]
  #define  d7              di[7]
  
  #define DEB_ARRAY_SIZE  8
  static int di[DEB_ARRAY_SIZE];          // main debug array (ints)
  static int di_shadow[DEB_ARRAY_SIZE];




//*************************************************************************************************
// APP DATA       *********************************************************************************
//*************************************************************************************************


//*************************************************************************************************
// MAIN          **********************************************************************************
//*************************************************************************************************

void setup()
{
#ifdef PRE_RELEASE_VERSION       
  F_MonitorLightInit(115200);
#endif  
  HW_ConfigInit();
  Keyboard.begin();
}


void loop()
{
  static byte  mplex_10ms = 0;
  unsigned long CurrentMillis;

  // FreeRunning Fast Stuff here *****

  // Main Multiplexer Controller Tick 1mS intervals >>>>>>>>>>>>>>>>>>>>>>>>>>>
  CurrentMillis = millis();
  if (CurrentMillis - PreviousMillis > MPLEX_INTERVAL_xxMS)
   {
   PreviousMillis = CurrentMillis;
   mplex_10ms++;    // 0...4 for ever   
   if (mplex_10ms > 4)  { mplex_10ms = 0; }
   SM_10ms(mplex_10ms);
   }
}


// Mutiplexer - called every 10ms
//*************************************************************************

#define DETECT_TRESH    200    // 150 - 250
#define HYSTERESIS       25    //   0 - 50


void SM_10ms(char mplex)
{
int t0;
static bool north = FALSE;
static bool east  = FALSE;
static bool south = FALSE;
static bool west  = FALSE;
  switch (mplex)
  {
    case 0:
      {
#ifdef PRE_RELEASE_VERSION        
      F_MonitorLightRun();
#endif      
      break;
      }
    case 1:
      {
      t0 = analogRead(JOY_X_PIN);
      if (t0 > (512 + DETECT_TRESH))               { west = TRUE;    }
      if (t0 < (512 + DETECT_TRESH - HYSTERESIS))  { west = FALSE;   }
      if (t0 < (512 - DETECT_TRESH))               { east = TRUE;    }
      if (t0 > (512 - DETECT_TRESH + HYSTERESIS))  { east = FALSE;   }        
      break;
      }
    case 2:
      {
      t0 = analogRead(JOY_Y_PIN);
      if (t0 > (512 + DETECT_TRESH))               { north = TRUE;   }
      if (t0 < (512 + DETECT_TRESH - HYSTERESIS))  { north = FALSE;  }
      if (t0 < (512 - DETECT_TRESH))               { south = TRUE;   }
      if (t0 > (512 - DETECT_TRESH + HYSTERESIS))  { south = FALSE;  }        
      break;
      }
    case 3:
      {
      if (north) { Keyboard.write  ('w'); }
      if (east)  { Keyboard.write  ('d'); }
      if (south) { Keyboard.write  ('s'); }
      if (west)  { Keyboard.write  ('a'); }        
      break;
      }
    case 4:
      {
      break;
      }
  }
}




//*******************************************************************************************************************
// Configure I/O directions  ****************************************************************************************
//*******************************************************************************************************************

void HW_ConfigInit (void)
{

}

Can you get it to work as a keyboard with some of the basic code?

Seems that there's something warped with my laptop. The PCB wouldn't output keys, but as soon as I tried two other machines, it worked fine. Very odd. But, thanks so much for all the help, it's really appreciated. I've tweaked the code to work well below.

Two improvements I'd love to see integrated, but am not 100% sure on.

  1. SENSITIVITY - a variable that amplifies input (once over the deadzone threshold) for someone with weak motion. E.g. within the deadzone do nothing... once over the deadzone * SENSITIVITY (where SENSITIVITY is set to 1.2) for instance.

  2. Someway to detect the resting position of the thumb-sticks in the Arduino monitor. If I could see the rawX and rawY values in the monitor, I could tweak the settings of the stick to make it act better.

//Pro Micro ATMEGA32U4 Arduino Compatible Board 5V 16MHz

#include "Keyboard.h"   // Built-in library
#include "Bounce2.h"    // Install using library manager

// Assume analog joystick/thumbstick is connected A0 and A1.
const int PIN_X_AXIS = A0;  // middle=512, left<512, right>512
const int PIN_Y_AXIS = A1;  // middle=512, down<512, up>512
// Pots are not perfect so create a deadzone around the middle value
// to avoid drifting.
const int MIDDLE_X = 518;
const int MIDDLE_Y = 504;
const int DEADZONE = 40;
const int SENSITIVITY = 40;

// Assume the stick button is connected to pin A2.
const int BUTTON_PIN = A2;
const int DEBOUNCE_INTERVAL = 10;
Bounce Button = Bounce();

#if 0
// Keyboard arrow keys for general use and most games.
#define UP_KEY    KEY_UP_ARROW
#define LEFT_KEY  KEY_LEFT_ARROW
#define DOWN_KEY  KEY_DOWN_ARROW
#define RIGHT_KEY KEY_RIGHT_ARROW
#else
// WASD works in games
#define UP_KEY      'w'
#define LEFT_KEY    'a'
#define DOWN_KEY    's'
#define RIGHT_KEY   'd'
#endif
#define BUTTON_KEY  ' '

bool Key_up_pressed = false;
bool Key_left_pressed = false;
bool Key_down_pressed = false;
bool Key_right_pressed = false;

void setup() {
  Keyboard.begin();
  Button.attach(BUTTON_PIN, INPUT_PULLUP);
  Button.interval(DEBOUNCE_INTERVAL);
}

void loop() {
  int rawX, rawY;

  rawX = analogRead(PIN_X_AXIS);
  if (rawX < (MIDDLE_X - DEADZONE)) {
    if (!Key_left_pressed) Keyboard.press(LEFT_KEY);
    Key_left_pressed = true;
    Key_right_pressed = false;
  }
  else if (rawX > (MIDDLE_X + DEADZONE)) {
    if (!Key_right_pressed) Keyboard.press(RIGHT_KEY);
    Key_right_pressed = true;
    Key_left_pressed = false;
  }
  else {
    if (Key_left_pressed) Keyboard.release(LEFT_KEY);
    Key_left_pressed = false;
    if (Key_right_pressed) Keyboard.release(RIGHT_KEY);
    Key_right_pressed = false;
  }

  rawY = analogRead(PIN_Y_AXIS);
  if (rawY < (MIDDLE_Y - DEADZONE)) {
    if (!Key_down_pressed) Keyboard.press(DOWN_KEY);
    Key_down_pressed = true;
    Key_up_pressed = false;
  }
  else if (rawY > (MIDDLE_Y + DEADZONE)) {
    if (!Key_up_pressed) Keyboard.press(UP_KEY);
    Key_up_pressed = true;
    Key_down_pressed = false;
  }
  else {
    if (Key_up_pressed) Keyboard.release(UP_KEY);
    Key_up_pressed = false;
    if (Key_down_pressed) Keyboard.release(DOWN_KEY);
    Key_down_pressed = false;
  }

  Button.update();
  if (Button.fell()) {
    Keyboard.press(BUTTON_KEY);
  }
  if (Button.rose()) {
    Keyboard.release(BUTTON_KEY);
  }
}

The Bounce2 library is good. Button.fell() to Keyboard.press() and Button.rose() to Keyboard.release() is very good. Those functions are made to be used that way 8)

I'm not sure about the up-down and left-right. If the joystick is in its middle position, then you keep on calling the Keyboard.release() functions.

  1. Sensitivity for what ? You use the joystick as buttons now. Do you want to use it in three ways (wasd, cursors, joystick) ?
  2. Windows can calibration a analog joystick. Some pointing devices have a button to reset the coordinates. It is not easy to do that automatically. You can send anything to the serial monitor. For example two times per second the raw values. Have you seen the famous Blink Without Delay page ? Then you can send text to the serial monitor with a slow rate and the rest of the sketch still runs at full speed.

If you have time, then you can do some tests with the laptop:

There are USB hubs/switches that see the keyboard and the Arduino serial port and decide it is probably not a keyboard. I have not heard that a computer has the same issue.
What if you add a USB hub and put the Arduino after the hub ?
There are a few different standard types of USB keyboards. Maybe the BIOS disturbs things. Does a power up with the Arduino connected behave different than when it is connected during runtime ?
Perhaps the laptop outputs a low voltage to the USB connector. Can you measure the Arduino 5V pin ?
If you have Windows 10 and linux running at that laptop, then you can test both and you would know more :stuck_out_tongue: A live-USB stick with linux is easy to make.

Added USB Serial output of rawX and rawY. Open the Serial Monitor to see the raw values. This extra output could slow down the sampling of the analog inputs so I would remove the Serial.print for real life use.

I am not sure how SENSITIVITY works. Once the stick moves far enough away from center +/- the deadzone, the key is pressed. The deadzone determines how far the stick must be moved from center before the key is sent.

Maybe hysteresis is SENSITIVITY. If the stick is right on the border of center +/- deadzone, any slight noise or tremor will cause the key to be pressed and released very fast as the raw value crosses back and forth over the deadzone border. Hysteresis can be implemented as a border inside the deadzone border that must be crossed to release the key. I do not have code for this case right now. I suppose sensitivity is the difference between the press and release borders.

//Pro Micro ATMEGA32U4 Arduino Compatible Board 5V 16MHz

#include "Keyboard.h"   // Built-in library
#include "Bounce2.h"    // Install using library manager

// Assume analog joystick/thumbstick is connected A0 and A1.
const int PIN_X_AXIS = A0;  // middle=512, left<512, right>512
const int PIN_Y_AXIS = A1;  // middle=512, down<512, up>512
// Pots are not perfect so create a deadzone around the middle value
// to avoid drifting.
const int MIDDLE_X = 518;
const int MIDDLE_Y = 504;
const int DEADZONE = 40;
const int SENSITIVITY = 40;

// Assume the stick button is connected to pin A2.
const int BUTTON_PIN = A2;
const int DEBOUNCE_INTERVAL = 10;
Bounce Button = Bounce();

#if 0
// Keyboard arrow keys for general use and most games.
#define UP_KEY    KEY_UP_ARROW
#define LEFT_KEY  KEY_LEFT_ARROW
#define DOWN_KEY  KEY_DOWN_ARROW
#define RIGHT_KEY KEY_RIGHT_ARROW
#else
// WASD works in games
#define UP_KEY      'w'
#define LEFT_KEY    'a'
#define DOWN_KEY    's'
#define RIGHT_KEY   'd'
#endif
#define BUTTON_KEY  ' '

bool Key_up_pressed = false;
bool Key_left_pressed = false;
bool Key_down_pressed = false;
bool Key_right_pressed = false;

void setup() {
  Serial.begin(115200);
  Keyboard.begin();
  Button.attach(BUTTON_PIN, INPUT_PULLUP);
  Button.interval(DEBOUNCE_INTERVAL);
}

void loop() {
  int rawX, rawY;

  rawX = analogRead(PIN_X_AXIS);
  Serial.print("rawX = ");
  Serial.print(rawX);
  if (rawX < (MIDDLE_X - DEADZONE)) {
    if (!Key_left_pressed) Keyboard.press(LEFT_KEY);
    Key_left_pressed = true;
    Key_right_pressed = false;
  }
  else if (rawX > (MIDDLE_X + DEADZONE)) {
    if (!Key_right_pressed) Keyboard.press(RIGHT_KEY);
    Key_right_pressed = true;
    Key_left_pressed = false;
  }
  else {
    if (Key_left_pressed) Keyboard.release(LEFT_KEY);
    Key_left_pressed = false;
    if (Key_right_pressed) Keyboard.release(RIGHT_KEY);
    Key_right_pressed = false;
  }

  rawY = analogRead(PIN_Y_AXIS);
  Serial.print(" rawY = ");
  Serial.println(rawY);
  if (rawY < (MIDDLE_Y - DEADZONE)) {
    if (!Key_down_pressed) Keyboard.press(DOWN_KEY);
    Key_down_pressed = true;
    Key_up_pressed = false;
  }
  else if (rawY > (MIDDLE_Y + DEADZONE)) {
    if (!Key_up_pressed) Keyboard.press(UP_KEY);
    Key_up_pressed = true;
    Key_down_pressed = false;
  }
  else {
    if (Key_up_pressed) Keyboard.release(UP_KEY);
    Key_up_pressed = false;
    if (Key_down_pressed) Keyboard.release(DOWN_KEY);
    Key_down_pressed = false;
  }

  Button.update();
  if (Button.fell()) {
    Keyboard.press(BUTTON_KEY);
  }
  if (Button.rose()) {
    Keyboard.release(BUTTON_KEY);
  }
}

Ok, now with hysteresis for key up and down. BORDER_UP and BORDER_DOWN determine how far the stick must be moved to trigger USB key press and release.

//Pro Micro ATMEGA32U4 Arduino Compatible Board 5V 16MHz

#include "Keyboard.h"   // Built-in library
#include "Bounce2.h"    // Install using library manager

// Assume analog joystick/thumbstick is connected A0 and A1.
const int PIN_X_AXIS = A0;  // middle=512, left<512, right>512
const int PIN_Y_AXIS = A1;  // middle=512, down<512, up>512
const int MIDDLE_X = 512;
const int MIDDLE_Y = 512;
const int BORDER_UP = 25;
const int BORDER_DOWN = 50;

// Assume the stick button is connected to pin A2.
const int BUTTON_PIN = A2;
const int DEBOUNCE_INTERVAL = 10;
Bounce Button = Bounce();

#if 0
// Keyboard arrow keys for general use and most games.
#define UP_KEY    KEY_UP_ARROW
#define LEFT_KEY  KEY_LEFT_ARROW
#define DOWN_KEY  KEY_DOWN_ARROW
#define RIGHT_KEY KEY_RIGHT_ARROW
#else
// WASD works in games
#define UP_KEY      'w'
#define LEFT_KEY    'a'
#define DOWN_KEY    's'
#define RIGHT_KEY   'd'
#endif
#define BUTTON_KEY  ' '

bool Key_up_pressed = false;
bool Key_left_pressed = false;
bool Key_down_pressed = false;
bool Key_right_pressed = false;

/*
 * Classify the raw input analog value into 1 of 5 zones.
 *
 *  C = center
 * BD = border for key down
 * BU = border for key up
 *
 * raw values:       0...C-BD...C-BU.......C+BU...C+BD...1023
 * Zone:               0      1        2        3      4
*/
uint8_t raw2zone(uint16_t rawAnalog, uint16_t center, \
    uint16_t border_up, uint16_t border_down)
{
    if (rawAnalog < (center - border_down)) return 0;
    if (rawAnalog < (center - border_up)) return 1;
    if (rawAnalog < (center + border_up)) return 2;
    if (rawAnalog < (center + border_down)) return 3;
    return 4;
}

void setup() {
  Serial.begin(115200);
  Keyboard.begin();
  Button.attach(BUTTON_PIN, INPUT_PULLUP);
  Button.interval(DEBOUNCE_INTERVAL);
}

void loop() {
  int rawX, rawY;
  uint8_t zone;

  rawX = analogRead(PIN_X_AXIS);
  Serial.print("rawX = ");
  Serial.print(rawX);
  zone = raw2zone(rawX, MIDDLE_X, BORDER_UP, BORDER_DOWN);
  Serial.print(" zoneX = ");
  Serial.print(zone);
  switch (zone) {
      case 0:
        if (!Key_left_pressed) Keyboard.press(LEFT_KEY);
        Key_left_pressed = true;
        if (Key_right_pressed) Keyboard.release(RIGHT_KEY);
        Key_right_pressed = false;
        break;
      case 1:
        if (Key_right_pressed) Keyboard.release(RIGHT_KEY);
        Key_right_pressed = false;
        break;
      case 2:
        if (Key_left_pressed) Keyboard.release(LEFT_KEY);
        Key_left_pressed = false;
        if (Key_right_pressed) Keyboard.release(RIGHT_KEY);
        Key_right_pressed = false;
        break;
      case 3:
        if (Key_left_pressed) Keyboard.release(LEFT_KEY);
        Key_left_pressed = false;
        break;
      case 4:
        if (!Key_right_pressed) Keyboard.press(RIGHT_KEY);
        Key_right_pressed = true;
        if (Key_left_pressed) Keyboard.release(LEFT_KEY);
        Key_left_pressed = false;
        break;
      default:
        Serial.print("Invalid zone=");
        Serial.println(zone);
        break;
  }

  rawY = analogRead(PIN_Y_AXIS);
  Serial.print(" rawY = ");
  Serial.print(rawY);
  zone = raw2zone(rawY, MIDDLE_Y, BORDER_UP, BORDER_DOWN);
  Serial.print(" zoneY = ");
  Serial.println(zone);
  switch (zone) {
      case 0:
        if (!Key_down_pressed) Keyboard.press(DOWN_KEY);
        Key_down_pressed = true;
        if (Key_up_pressed) Keyboard.release(UP_KEY);
        Key_up_pressed = false;
        break;
      case 1:
        if (Key_up_pressed) Keyboard.release(UP_KEY);
        Key_up_pressed = false;
        break;
      case 2:
        if (Key_down_pressed) Keyboard.release(DOWN_KEY);
        Key_down_pressed = false;
        if (Key_up_pressed) Keyboard.release(UP_KEY);
        Key_up_pressed = false;
        break;
      case 3:
        if (Key_down_pressed) Keyboard.release(DOWN_KEY);
        Key_down_pressed = false;
        break;
      case 4:
        if (!Key_up_pressed) Keyboard.press(UP_KEY);
        Key_up_pressed = true;
        if (Key_down_pressed) Keyboard.release(DOWN_KEY);
        Key_down_pressed = false;
        break;
      default:
        Serial.print("Invalid zone=");
        Serial.println(zone);
        break;
  }

  Button.update();
  if (Button.fell()) {
    Keyboard.press(BUTTON_KEY);
  }
  if (Button.rose()) {
    Keyboard.release(BUTTON_KEY);
  }
}

Thanks so much, this is brilliant stuff which I'll try first thing tomorrow. I was having a bad time with my PC, so am just on the otherside of a fresh install of Windows 10 and it's time for an old man to go to bed.

Regarding sensitivity, it's in mind of people who don't have even strength to push a joystick in all directions. Imagine someone with a thumb that can move fairly freely left and right, but struggles up and down. In such a case having variables to tweak to boost NESW movement would really help.... (you may have this covered with your latest reply)

NorthBoost = 1.2
EastBoost = 1
SouthBoost = 2
WestBoost = 1

...where this magnification would occur after the deadzone, to make the stick more sensitive in any of these four directions (in this example North would be a bit easier, and South a lot easier).

Now, in this keyboard example, it's not so important.... but still useful.... But I'd like to amend a joystick (ALPS style) to HID joystick method, and will try to transfer. If I get it all working, I'll share here.

Thanks again for all the help. It's really appreciated.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.